/*----------------------------------------------------------------------------+ | | | Enhanced HEYU Functionality for Uploaded Timers and Macros | | Copyright 2002,2003,2004,2005,2006 Charles W. Sullivan | | All Rights Reserved | | | | | | This software is licensed free of charge for non-commercial distribution | | and for personal and internal business use only. Inclusion of this | | software or any part thereof in a commercial product is prohibited | | without the prior written permission of the author. You may copy, use, | | and distribute this software subject to the following restrictions: | | | | 1) You may not charge money for it. | | 2) You may not remove or alter this license, copyright notice, or the | | included disclaimers. | | 3) You may not claim you wrote it. | | 4) If you make improvements (or other changes), you are requested | | to send them to the Heyu maintainer so there's a focal point for | | distributing improved versions. | | | | As used herein, HEYU is a trademark of Daniel B. Suthers. | | X10, CM11A, and ActiveHome are trademarks of X-10 (USA) Inc. | | The author is not affiliated with either entity. | | | | Charles W. Sullivan | | Co-author and Maintainer | | Greensboro, North Carolina | | Email ID: cwsulliv01 | | Email domain: -at- heyu -dot- org | | | | Disclaimers: | | THERE IS NO ASSURANCE THAT THIS SOFTWARE IS FREE OF DEFECTS AND IT MUST | | NOT BE USED IN ANY SITUATION WHERE THERE IS ANY CHANCE THAT ITS | | PERFORMANCE OR FAILURE TO PERFORM AS EXPECTED COULD RESULT IN LOSS OF | | LIFE, INJURY TO PERSONS OR PROPERTY, FINANCIAL LOSS, OR LEGAL LIABILITY. | | | | TO THE EXTENT ALLOWED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED "AS IS",| | WITH NO EXPRESS OR IMPLIED WARRANTY, INCLUDING, BUT NOT LIMITED TO, THE | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.| | | | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW WILL THE AUTHOR BE LIABLE | | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL | | DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THIS SOFTWARE EVEN IF | | THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. | | | +----------------------------------------------------------------------------*/ #include #include #include #include #if defined(SYSV) || defined(FREEBSD) || defined(OPENBSD) #include #else #include #endif #include #include #include #include #include #include "x10.h" #include "sun.h" #include "process.h" #include "version.h" struct dststruct { long elapsed; /* Elapsed minutes from 00:00 hours legal time Jan 1st */ int offset; /* Minutes to add to ST to get Legal Time during each */ /* interval. */ } lgls[5], dsts[5], stds[5], stdr[5]; #define NDSTINTV (int)(sizeof(lgls)/sizeof(struct dststruct)) /* Contains info written to or read from the x10record file */ struct record_info x10record = {0, 0, 0, 0, 0, 0, 0, 0}; struct record_info *x10recordp = &x10record; #define PROMSIZE 1024 /* Global variables */ int line_no; int timer_size = 0; int timer_maxsize = 0; int timer_savesize = 0; int tevent_size = 0; int tevent_savesize = 0; int tevent_maxsize = 0; int current_timer_generation = 0; int save_timer_generation = 0; int timer_generation_delta = 0; int current_tevent_generation = 0; int save_tevent_generation = 0; int tevent_generation_delta = 0; char default_housecode = 'A'; long int std_tzone; /* Timezone in seconds West of Greenwich */ char *heyu_tzname[2]; /* Directory (terminated with /) containing the critical */ /* heyu files x10config, x10record, and x10macroxref */ char heyu_path[PATH_LEN + 1]; char schedfile[PATH_LEN + 1]; char heyu_script[PATH_LEN + 1]; /* State file */ extern char statefile[]; /* Alternate optional directory (specified in x10config) */ /* for report and non-critical files */ char alt_path[PATH_LEN + 1]; char heyu_config[PATH_LEN + 1]; /* Filename of Heyu configuration file */ int is_writable; /* Flag: Heyu directory is writable */ /* External variables */ extern int verbose, i_am_relay; extern CONFIG config; extern CONFIG *configp; extern struct opt_st *optptr; char *wday_name[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; char *month_name[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; /* Extended error management functions - these allow stringing together */ /* error messages from a tree of function calls into a (hopefully) */ /* meaningful message for the user. */ /*----------------------------------------------------------------------------+ | Pass back pointer and size of error message buffer - for internal use by | | functions below. | +----------------------------------------------------------------------------*/ void error_area ( char **pointer, int *space ) { static char error_buffer[1024]; *pointer = error_buffer; *space = (sizeof(error_buffer)/sizeof(char)); return; } /*----------------------------------------------------------------------------+ | Save an error message in error message buffer | +----------------------------------------------------------------------------*/ void store_error_message ( char *message ) { char *buffp = NULL; int space; error_area(&buffp, &space); if ( space < (int)strlen(message) + 1 ) { fprintf(stderr, "Internal error: no space - store_error_message()\n"); exit(1); } strncpy2(buffp, message, space - 1); return; } /*----------------------------------------------------------------------------+ | Prepend a message to an existing error message in buffer | +----------------------------------------------------------------------------*/ void add_error_prefix ( char *prefix ) { char *buffp = NULL; char *oldmsg = NULL; int space; error_area(&buffp, &space); if ( space < ((int)strlen(prefix) + (int)strlen(buffp) + 1) ) { fprintf(stderr, "Internal error: no space - add_error_prefix()\n"); exit(1); } if ( (oldmsg = strdup(buffp)) == NULL ) { fprintf(stderr, "Unable to allocate memory for error message\n"); exit(1); } strncpy2(buffp, prefix, space - 1); strncat(buffp, oldmsg, space - 1 - strlen(buffp)); free(oldmsg); return; } /*----------------------------------------------------------------------------+ | Append a message to an existing error message in buffer | +----------------------------------------------------------------------------*/ void add_error_suffix ( char *suffix ) { char *buffp = NULL; int space; error_area(&buffp, &space); if ( space < ((int)strlen(buffp) + (int)strlen(suffix) + 1) ) { fprintf(stderr, "Internal error: no space - add_error_suffix()\n"); exit(1); } strncat(buffp, suffix, space - 1 - strlen(buffp)); return; } /*----------------------------------------------------------------------------+ | Get pointer to error message buffer | +----------------------------------------------------------------------------*/ char *error_message ( void ) { char *buffp = NULL; int space; error_area(&buffp, &space); return buffp; } /*----------------------------------------------------------------------------+ | Clear the error message buffer | +----------------------------------------------------------------------------*/ void clear_error_message( void ) { char *buffp = NULL; int space; error_area(&buffp, &space); *buffp = '\0'; return; } /* Debugging functions */ void tp ( int point ) { printf("testpoint %d\n", point); fflush(stdout); return ; } int show_timer_links( TIMER *timerp ) { int j; for ( j = 0; j < timer_maxsize; j++ ) printf("%3d %3d\n", j, timerp[j].link); return 0; } /*----------------------------------------------------------------------------+ | Display tokens | +----------------------------------------------------------------------------*/ void display_tokens ( int tokc, char *tokv[] ) { int j; for ( j = 0; j < tokc; j++ ) { printf(" \"%s\"", tokv[j]); } printf("\n"); return; } /*----------------------------------------------------------------------------+ | Verify that the start and stop times in any timer are not the same. | | (If they are, CM11A will not launch the stop macro.) | | Return the number of timers failing the test. | +----------------------------------------------------------------------------*/ int check_timer_start_stop ( TIMER *timerp ) { int j, count; if ( !timerp ) return 0; j = 0; count = 0; while ( timerp[j].line_no > 0 ) { if ( timerp[j].generation != current_timer_generation || timerp[j].flag_start == NO_EVENT || timerp[j].flag_stop == NO_EVENT ) { j++; continue; } if ( timerp[j].offset_stop == timerp[j].offset_start ) count++; j++; } return count; } /*----------------------------------------------------------------------------+ | Verify an unbroken chain of TEVENT links. | +----------------------------------------------------------------------------*/ int verify_tevent_links ( TEVENT *teventp ) { int *linker, *linkee; int j, k, start, size, retcode; static int sizeint = sizeof(int); if ( !teventp ) return 0; size = 0; while ( teventp[size].line_no > 0 ) size++; linker = calloc( size, sizeint ); if ( linker == NULL ) { (void)fprintf(stderr, "verify_tevent_links() - Unable to allocate memory 1\n"); return -1; } linkee = calloc( size, sizeint ); if ( linkee == NULL ) { (void)fprintf(stderr, "verify_tevent_links() - Unable to allocate memory 2\n"); return -1; } /* Find the start of the chain, i.e., the tevent not linked to by any other */ for ( j = 0; j < size; j++ ) { linker[j] = 0; } for ( j = 0; j < size; j++ ) { k = teventp[j].link; if ( k > (size - 1) ) { (void)fprintf(stderr, "verify_tevent_links() - link %d out of bound %d at index %d\n", k, size - 1, j); return -1; } if ( k >= 0 ) linker[k] = 1; } for ( start = 0; start < size; start++ ) { if ( linker[start] == 0 ) break; } for ( j = 0; j < size; j++ ) { linkee[j] = 0; linker[j] = -1; } k = start; for ( j = 0; j < size; j++ ) { k = teventp[k].link; if ( k < 0 && j < (size - 1) ) { (void)fprintf(stderr, "verify_tevent_links() - premature end of %d length chain at index %d\n", size, j); return -1; } if ( k > (size - 1) ) { (void)fprintf(stderr, "verify_tevent_links() - link %d out of bound %d at index %d\n", k, size - 1, j); return -1; } if ( k >= 0 ) { linkee[k] += 1; linker[k] = j; } } retcode = 0; for ( k = 1; k < size; k++ ) { if ( linkee[k] == 0 ) { (void)fprintf(stderr, "teventp[linkee[%d]] is not in chain.\n", k); retcode = 1; } if ( linkee[k] > 1 ) { (void)fprintf(stderr, "teventp[linkee[%d]] is multiply linked.\n", k); retcode = 1; } if ( linkee[k] == -1 ) { (void)fprintf(stderr, "End of chain at index %d\n", linker[k]); retcode = 1; } } free(linker); free(linkee); return retcode; } /*----------------------------------------------------------------------------+ | Verify an unbroken chain of TIMER links. | +----------------------------------------------------------------------------*/ int verify_timer_links ( TIMER *timerp ) { int *linker, *linkee; int j, k, start, size, retcode; static int sizeint = sizeof(int); if ( !timerp ) return 0; size = 0; while ( timerp[size].line_no > 0 ) size++; linker = calloc( size, sizeint ); if ( linker == NULL ) { (void)fprintf(stderr, "verify_timer_links() - Unable to allocate memory 1\n"); return -1; } linkee = calloc( size, sizeint ); if ( linkee == NULL ) { (void)fprintf(stderr, "verify_timer_links() - Unable to allocate memory 2\n"); return -1; } /* Find the start of the chain, i.e., the timer not linked to by any other */ for ( j = 0; j < size; j++ ) { linker[j] = 0; } for ( j = 0; j < size; j++ ) { k = timerp[j].link; if ( k > (size - 1) ) { (void)fprintf(stderr, "verify_timer_links() - link %d out of bound %d at index %d\n", k, size - 1, j); return -1; } if ( k >= 0 ) linker[k] = 1; } for ( start = 0; start < size; start++ ) { if ( linker[start] == 0 ) break; } for ( j = 0; j < size; j++ ) { linkee[j] = 0; linker[j] = -1; } for ( j = 0; j < size; j++ ) { linkee[j] = 0; linker[j] = -1; } k = start; for ( j = 0; j < size; j++ ) { k = timerp[k].link; if ( k < 0 && j < (size - 1) ) { (void)fprintf(stderr, "verify_timerp_links() - premature end of %d length chain at index %d\n", size, j); return -1; } if ( k > (size - 1) ) { (void)fprintf(stderr, "verify_timerp_links() - link %d out of bound %d at index %d\n", k, size, j); return -1; } if ( k >= 0 ) { linkee[k] += 1; linker[k] = j; } } retcode = 0; for ( k = 1; k < size; k++ ) { if ( linkee[k] == 0 ) { (void)fprintf(stderr, "timerp[linkee[%d]] is not in chain.\n", k); retcode = 1; } if ( linkee[k] > 1 ) { (void)fprintf(stderr, "timerp[linkee[%d]] is multiply linked.\n", k); retcode = 1; } if ( linkee[k] == -1 ) { (void)fprintf(stderr, "End of chain at index %d\n", linker[k]); retcode = 1; } } free( linker ); free( linkee ); return retcode; } /* String/array manipulation functions */ /*----------------------------------------------------------------------------+ | Trim leading and trailing whitespace from argument string and return | | pointer to string. Argument string itself is modified. | +----------------------------------------------------------------------------*/ char *strtrim( char *string ) { char *ss ; char *sd ; ss = sd = string ; /* Move pointer to first non-whitespace character */ while ( *ss == ' ' || *ss == '\t' || *ss == '\n' || *ss == '\r') ss++ ; /* Close up leading whitespace */ while ( (*sd++ = *ss++) != '\0' ) ; /* Back up pointer to character before terminating NULL */ sd -= 2 ; /* Replace trailing whitespace with NULLs */ while( sd >= string && ( *sd == ' ' || *sd == '\t' || *sd == '\n' || *sd == '\r' ) ) *sd-- = '\0' ; return string ; } /*----------------------------------------------------------------------------+ | Convert a string to lower case. Return pointer to string. | +----------------------------------------------------------------------------*/ char *strlower ( char *string ) { char *sp = string ; while ( *sp ) { *sp = tolower((int)(*sp)); sp++ ; } return string; } /*----------------------------------------------------------------------------+ | Convert a string to Upper case. Return pointer to string. | +----------------------------------------------------------------------------*/ char *strupper ( char *string ) { char *sp = string ; while ( *sp ) { *sp = toupper((int)(*sp)); sp++ ; } return string; } /*----------------------------------------------------------------------------+ | Copy n characters from source to target string and append a trailing null. | | Return a pointer to the target. (Length of target string must be n+1). | +----------------------------------------------------------------------------*/ char *strncpy2 ( char *target, char *source, int n ) { char *sp, *tp; int count; sp = source; tp = target; count = n; while ( count-- > 0 && *sp ) { *tp++ = *sp++ ; } *tp = '\0'; return target; } /*--------------------------------------------------------------------+ | Break up the string 'str' into tokens delimited by characters in | | 'delim' and create the list 'tokv' of pointers to these tokens. | | The original string is modified. Free 'tokv' after use. | +--------------------------------------------------------------------*/ int tokenize ( char *str, char *delim, int *tokc, char ***tokv ) { char *sp, *tp; static int sizchptr = sizeof(char *); *tokc = 0; *tokv = NULL; sp = str; while ( *sp != '\0' ) { /* Bypass leading delimiters */ while ( *sp != '\0' && strchr(delim, *sp) != NULL ) sp++; if ( *sp == '\0' ) return 0; tp = sp; /* Advance to the next delimiter */ while ( *sp != '\0' && strchr(delim, *sp) == NULL ) sp++; /* Terminate the token if not already at end of string */ if ( *sp != '\0' ) *sp++ = '\0'; /* Allocate space for the pointer */ if ( *tokc == 0 ) *tokv = calloc(1, sizchptr); else *tokv = realloc(*tokv, (*tokc + 1) * sizchptr); if ( *tokv == NULL ) { fprintf(stderr, "Unable to allocate memory in tokenize()\n"); exit(1); } (*tokv)[(*tokc)++] = tp; } /* Add a terminating NULL */ if ( *tokc == 0 ) *tokv = calloc(1, sizchptr); else *tokv = realloc(*tokv, (*tokc + 1) * sizchptr); if ( *tokv == NULL ) { fprintf(stderr, "Unable to allocate memory in tokenize()\n"); exit(1); } (*tokv)[(*tokc)] = (char *)NULL; return 0; } /*--------------------------------------------------------------------+ | Copy maxlen-1 characters to target of the token delimited by delim | | from the char string *nextpp. On return, *nextpp points to the | | character following the token (or the part copied thereof). | | The original string is unchanged. | +--------------------------------------------------------------------*/ char *get_token( char *target, char **nextpp, char *delim, int maxlen ) { char *sp, *tp; int count = 0; sp = *nextpp; tp = target; /* Bypass leading delimiters */ while ( *sp && strchr(delim, *sp) ) { sp++ ; } /* Transfer up to maxlen-1 characters */ while ( *sp && !strchr(delim, *sp) && count < maxlen ) { *tp++ = *sp++ ; count++ ; } *nextpp = sp; /* Terminate with a null character */ *tp = '\0'; return target; } /*---------------------------------------------------------------------+ | Left rotate the contents of an array. | +---------------------------------------------------------------------*/ void lrotarray ( unsigned char *array, int length ) { unsigned char hold; int j; hold = array[0]; for ( j = 0; j < length - 1; j++ ) array[j] = array[j + 1]; array[length - 1] = hold; return; } /*---------------------------------------------------------------------+ | Right rotate the contents of an array. | +---------------------------------------------------------------------*/ void rrotarray ( unsigned char *array, int length ) { unsigned char hold; int j; hold = array[length - 1]; for ( j = length - 1; j > 0; j-- ) array[j] = array[j - 1]; array[0] = hold; return; } /* X10 Encoding/Decoding functions */ /*---------------------------------------------------------------------+ | Convert housecode letter to x10 code. | +---------------------------------------------------------------------*/ unsigned char hc2code ( char hc ) { /* X10 codes for housecode letters A through P */ static unsigned char code[] = {6,14,2,10,1,9,5,13,7,15,3,11,0,8,4,12}; return code[(toupper((int)hc) - 'A') & 0x0f]; } /*---------------------------------------------------------------------+ | Convert x10 code to housecode letter. | +---------------------------------------------------------------------*/ char code2hc ( unsigned char code ) { char *hcode = "MECKOGAINFDLPHBJ"; return hcode[code & 0x0fu]; } /*---------------------------------------------------------------------+ | Convert X10 unit number (1-16) to x10 code. | +---------------------------------------------------------------------*/ unsigned char unit2code ( int unit ) { /* X10 codes for unit 1 through 16 */ static unsigned char code[] = {6,14,2,10,1,9,5,13,7,15,3,11,0,8,4,12}; return code[unit - 1]; } /*---------------------------------------------------------------------+ | Convert X10 code to X10 unit number (1-16). | +---------------------------------------------------------------------*/ int code2unit ( unsigned char code ) { static int units[] = {13,5,3,11,15,7,1,9,14,6,4,12,16,8,2,10}; return units[code & 0x0fu]; } /*---------------------------------------------------------------------+ | Convert bit position (0-15) in X10 bitmap to unit number. | +---------------------------------------------------------------------*/ int bitpos2unit ( int bitpos ) { static int units[] = {13,5,3,11,15,7,1,9,14,6,4,12,16,8,2,10}; return units[bitpos & 0x0f]; } /*---------------------------------------------------------------------+ | Return X10 bitmap for Days of Week string. | +---------------------------------------------------------------------*/ unsigned char dow2bmap ( char *dow ) { char *pattern = "smtwtfs"; unsigned char bmap = 0; unsigned char mask = 0x01; char buffer[16]; int j; if ( strlen(dow) != 7 ) return 0xff; strncpy2(buffer, dow, sizeof(buffer) - 1); strlower(buffer); for ( j = 0; j < 7; j++ ) { if ( buffer[j] == pattern[j] ) { bmap |= mask; mask = mask << 1 ; } else if ( buffer[j] == '.' ) mask = mask << 1 ; else return 0xff ; } return bmap; } /*---------------------------------------------------------+ | Return Days of Week string for X10 bitmap argument | +---------------------------------------------------------*/ char *bmap2dow( unsigned char bmap ) { static char buff[10]; int j; char *days = "smtwtfs"; char *err = "-error-"; unsigned char mask = 0x01; if ( bmap > 127 ) return err; for ( j = 0; j < 7; j++ ) { buff[j] = (mask & bmap) ? days[j] : '.' ; mask = mask << 1; } buff[7] = '\0'; return buff; } /*---------------------------------------------------------+ | Return Linux tm_wday (Sun = 0) for X10 bitmap. | | (Assumes only one wday represented in the bitmap.) | +---------------------------------------------------------*/ int bmap2wday ( unsigned char bmap ) { int j; j = 0; while ( (bmap = (bmap >> 1)) != 0 ) j++; return j; } /*---------------------------------------------------------+ | Return ASCII weekday name for X10 DOW bitmap. | | (Assumes only one wday represented in the bitmap.) | +---------------------------------------------------------*/ char *bmap2ascdow ( unsigned char bmap ) { return wday_name[bmap2wday(bmap)]; } /*---------------------------------------------------------------------+ | Reverse the order of the lower 4 bits, e.g., 1 -> 8 and 8 -> 1 | | while leaving the upper bits unchanged. | +---------------------------------------------------------------------*/ unsigned char rev_low_nybble ( unsigned char input ) { static unsigned char rev_table[] = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}; int j; j = (int)(input & 0x0fu); return (input & 0xf0u) | rev_table[j]; } /*---------------------------------------------------------------------+ | Reverse the order of the bits in a byte. | +---------------------------------------------------------------------*/ unsigned char rev_byte_bits ( unsigned char input ) { int j; unsigned char output = 0; for ( j = 0; j < 8; j++ ) { if ( input & (1 << j) ) output |= (0x80 >> j); } return output; } /*---------------------------------------------------------------------+ | Rotate the x10 Day of Week bitmap one bit to the left. This has | | the effect of moving the day of an event into the following day, | | e.g., bmap(".mt..fs") ==> bmap("s.tw..s") | +---------------------------------------------------------------------*/ unsigned char lrotbmap ( unsigned char bmap ) { return ( ((bmap & 0x40u) >> 6) | ( (bmap & 0x3fu) << 1 ) ); } /*---------------------------------------------------------------------+ | Rotate the x10 Day of Week bitmap one bit to the right. This has | | the effect of moving the day of an event into the preceding day, | | e.g., bmap(".mt..fs") ==> bmap("sm..tf.") | +---------------------------------------------------------------------*/ unsigned char rrotbmap ( unsigned char bmap ) { return ( ((bmap & 0x01u) << 6) | ( (bmap & 0x7fu) >> 1 ) ); } /*---------------------------------------------------------------------+ | Return the code for a unit in a bitmap which is presumed to contain | | only a single unit or 0. If the bitmap contains more than a single | | unit, an error value of 0xff is returned. | +---------------------------------------------------------------------*/ unsigned char single_bmap_unit( unsigned int bitmap ) { unsigned int invmap[] = {6,14,2,10,1,9,5,13,7,15,3,11,0,8,4,12}; unsigned int mask = 1; unsigned char units[17], nunits; int j; nunits = 0; units[0] = 0; for ( j = 0; j < 16; j++ ) { mask = 0x01 << invmap[j] ; if ( bitmap & mask ) { units[nunits++] = invmap[j]; } } return (nunits <= 1) ? units[0] : 0xff; } /*---------------------------------------------------------------------+ | Parse the units list and return an X10 unit bitmap containing only | | the first unit in the units list, with unit 0 acceptable. | +---------------------------------------------------------------------*/ unsigned int units2single ( char *str ) { static int umap[] = {6,14,2,10,1,9,5,13,7,15,3,11,0,8,4,12}; int unit; char buffer[256]; char errmsg[64]; char *tail, *sp; tail = str; if ( *get_token(buffer, &tail, "-,", 255) == '\0' ) return 0; unit = (int)strtol(buffer, &sp, 10); if ( *sp ) { sprintf(errmsg, "Warning: Invalid character '%c' in units list (ignored).\n", *sp); store_error_message(errmsg); return 0; } if ( unit < 0 || unit > 16 ) { sprintf(errmsg, "Warning: Unit number %d outside range 0-16 (ignored).\n", unit); store_error_message(errmsg); return 0; } if ( unit == 0 ) return 0; return ( 1 << umap[unit - 1] ); } /*---------------------------------------------------------------------+ | Parse the flags list and return a bitmap, with bit 0 = flag 1, | | bit 1 = flag 2, etc. | +---------------------------------------------------------------------*/ unsigned int flags2bmap ( char *str ) { char buffer[256]; char errmsg[80]; char *tail, *sp; int flist[16]; int j, ustart, flag; unsigned int bmap; if ( strchr(str, '*') ) { bmap = 0xffff; return bmap; } for ( j = 0; j < 16; j++ ) flist[j] = 0; ustart = 0; tail = str; while ( *(get_token(buffer, &tail, "-,", 255)) ) { flag = (int)strtol(buffer, &sp, 10); if ( *sp ) { sprintf(errmsg, "Invalid char '%c' in flags list.", *sp); store_error_message(errmsg); return 0; } if ( flag < 1 || flag > 16 ) { sprintf(errmsg, "Flag number %d outside range 1-16.", flag); store_error_message(errmsg); return 0; } if ( *tail == ',' || *tail == '\0' ) { if ( ustart ) { for ( j = ustart; j <= flag; j++ ) flist[j-1] = 1; ustart = 0; continue; } else { flist[flag-1] = 1; continue; } } else { ustart = flag; } } bmap = 0; for ( j = 0; j < 16; j++ ) bmap |= (flist[j]) << j; return bmap; } /*---------------------------------------------------------------------+ | Parse the units list and return the X10 unit bitmap. | +---------------------------------------------------------------------*/ unsigned int units2bmap ( char *str ) { static int umap[] = {6,14,2,10,1,9,5,13,7,15,3,11,0,8,4,12}; char buffer[256]; char errmsg[80]; char *tail, *sp; int ulist[16]; int j, ustart, unit; unsigned int bmap; for ( j = 0; j < 16; j++ ) ulist[j] = 0; ustart = 0; tail = str; while ( *(get_token(buffer, &tail, "-,", 255)) ) { unit = (int)strtol(buffer, &sp, 10); if ( *sp ) { sprintf(errmsg, "Invalid char '%c' in units list.", *sp); store_error_message(errmsg); return 0; } if ( unit < 1 || unit > 16 ) { sprintf(errmsg, "Unit number %d outside range 1-16.", unit); store_error_message(errmsg); return 0; } if ( *tail == ',' || *tail == '\0' ) { if ( ustart ) { for ( j = ustart; j <= unit; j++ ) ulist[j-1] = 1; ustart = 0; continue; } else { ulist[unit-1] = 1; continue; } } else { ustart = unit; } } bmap = 0; for ( j = 0; j < 16; j++ ) bmap |= (ulist[j]) << umap[j]; return bmap; } /*---------------------------------------------------------------------+ | Return string listing X10 units for X10 bitmap argument. | +---------------------------------------------------------------------*/ char *bmap2units ( unsigned int bmap ) { static int invmap[] = {6,14,2,10,1,9,5,13,7,15,3,11,0,8,4,12}; static char buffer[128]; char minbuf[8]; unsigned int mask = 0x01; int j, count, nunits; int units[17]; int first; if ( bmap == 0 ) { (void)strncpy2(buffer, "0", sizeof(buffer) - 1); return buffer; } nunits = 0; for ( j = 0; j < 16; j++ ) { mask = 0x01 << invmap[j] ; if ( bmap & mask ) { units[nunits++] = j + 1; } } units[nunits++] = 0; buffer[0] = buffer[1] = '\0'; first = units[0]; count = 1; for (j=1; j < nunits; j++) { if ( units[j] == units[j-1] + 1) { count++; continue; } switch ( count ) { case 1 : sprintf(minbuf, ",%d", first); break; case 2 : sprintf(minbuf, ",%d,%d", first, units[j-1]); break; default : sprintf(minbuf, ",%d-%d", first, units[j-1]); break; } (void) strncat(buffer, minbuf, sizeof(buffer) - 1 - strlen(buffer)); first = units[j]; count = 1; } return buffer + 1; } /*---------------------------------------------------------------------+ | Return a 16 character ASCII string displaying in descending order | | an X10 unit bitmap, i.e., char[0] -> unit 16, char[15] -> unit 1. | | The argument chrs is a two-character string, the 1st character of | | represents 'unset' units and the 2nd character the 'set' bits. | | Example: With chrs = "01", a bitmap for units 1,5,6 | | (bitmap 0x0242) will be represented as "0000000000110001". | +---------------------------------------------------------------------*/ char *bmap2rasc ( unsigned int bitmap, char *chrs ) { int j; static char outbuf[17]; for ( j = 0; j < 16; j++ ) { if ( bitmap & (1 << j) ) outbuf[16 - code2unit(j)] = chrs[1]; else outbuf[16 - code2unit(j)] = chrs[0]; } outbuf[16] = '\0'; return outbuf; } /*---------------------------------------------------------------------+ | Return a 16 character ASCII string displaying in ascending order | | an X10 unit bitmap, i.e., char[0] -> unit 1, char[15] -> unit 16. | | The argument chrs is a two-character string, the 1st character of | | represents 'unset' units and the 2nd character the 'set' bits. | | Example: With chrs = "01", a bitmap for units 1,5,6 | | (bitmap 0x0242) will be represented as "1000110000000000". | +---------------------------------------------------------------------*/ char *bmap2asc ( unsigned int bitmap, char *chrs ) { int j; static char outbuf[17]; for ( j = 0; j < 16; j++ ) { if ( bitmap & (1 << j) ) outbuf[code2unit(j) - 1] = chrs[1]; else outbuf[code2unit(j) - 1] = chrs[0]; } outbuf[16] = '\0'; return outbuf; } /*---------------------------------------------------------------------+ | Parse the list of integers and return a linear bitmap. The list | | is comprised of comma-separated positive integers or ranges of | | integers, e.g., 3,2,4-7,11 The integers are restricted to the | | values minval through maxval (within the range 0-31). | +---------------------------------------------------------------------*/ unsigned long list2linmap ( char *str, int minval, int maxval ) { char buffer[256]; char errmsg[80]; char *tail, *sp; int ulist[32]; int j, ustart, unit, temp; unsigned long linmap; for ( j = 0; j < 32; j++ ) ulist[j] = 0; ustart = -1; tail = str; while ( *(get_token(buffer, &tail, "-,", 255)) ) { unit = (int)strtol(buffer, &sp, 10); if ( *sp ) { sprintf(errmsg, "Invalid char '%c' in the list.", *sp); store_error_message(errmsg); return 0; } if ( unit < minval || unit > maxval ) { sprintf(errmsg, "outside range %d-%d", minval, maxval); store_error_message(errmsg); return 0; } if ( *tail == ',' || *tail == '\0' ) { if ( ustart >= 0 ) { if ( unit < ustart ) { temp = ustart; ustart = unit; unit = temp; } for ( j = ustart; j <= unit; j++ ) ulist[j] = 1; ustart = 0; continue; } else { ulist[unit] = 1; continue; } } else { ustart = unit; } } linmap = 0; for ( j = 0; j < 32; j++ ) linmap |= (ulist[j]) << j; return linmap; } /*---------------------------------------------------------------------+ | Return a comma-separated list of integers and ranges of integers | | represented by the linear bitmap argument, where bit0 -> 0, etc. | +---------------------------------------------------------------------*/ char *linmap2list ( unsigned long linmap ) { static char buffer[128]; char minbuf[8]; unsigned long mask = 1; int j, count, nunits; int units[32]; int first; buffer[0] = buffer[1] = '\0'; if ( linmap == 0 ) { return buffer; } nunits = 0; for ( j = 0; j < 31; j++ ) { mask = 1 << j ; if ( linmap & mask ) { units[nunits++] = j; } } units[nunits++] = 0; first = units[0]; count = 1; for (j = 1; j < nunits; j++) { if ( units[j] == units[j-1] + 1) { count++; continue; } switch ( count ) { case 1 : sprintf(minbuf, ",%d", first); break; case 2 : sprintf(minbuf, ",%d,%d", first, units[j-1]); break; default : sprintf(minbuf, ",%d-%d", first, units[j-1]); break; } (void) strncat(buffer, minbuf, sizeof(buffer) - 1 - strlen(buffer)); first = units[j]; count = 1; } return buffer + 1; } /* Date and Calendar functions */ /*------------------------------------------------------------------+ | Determine the user's standard timezone, defined here as the | | offset in seconds of local Standard Time from GMT, with West of | | Greenwich positive, and store in global variable std_tzone. | | Some C libraries provide this directly as a global variable; | | for others it must be determined from struct member tm_gmtoff, | | which provides the offset of local Legal Time and has the | | opposite sign. | +------------------------------------------------------------------*/ #ifdef HASTZ void get_std_timezone ( void ) { struct tm *tmp; time_t now; /* Fill in the tm structure for the current date */ time(&now); tmp = localtime(&now); /* The library includes the global variable "timezone" */ std_tzone = (long)timezone; return; } #else void get_std_timezone ( void ) { struct tm *tmp; time_t now; long int jan_off, jul_off; /* Fill in the tm structure for the current date */ time(&now); tmp = localtime(&now); /* struct tm includes the element tm_gmtoff */ /* Get the GMT offset for January */ tmp->tm_mon = 0; tmp->tm_mday = 1; tmp->tm_hour = 12; tmp->tm_min = tmp->tm_sec = 0; tmp->tm_isdst = -1; mktime(tmp); jan_off = tmp->tm_gmtoff; /* Get the GMT offset for July */ tmp->tm_mon = 6; tmp->tm_mday = 1; tmp->tm_hour = 12; tmp->tm_min = tmp->tm_sec = 0; tmp->tm_isdst = -1; mktime(tmp); jul_off = tmp->tm_gmtoff; /* The lesser value corresponds to Standard Time.*/ /* Change sign to make West of Greenwich positive */ std_tzone = -min(jan_off, jul_off); return; } #endif /* End of #ifdef */ /*------------------------------------------------------------------+ | For some places in the world, e.g., Australia, there's no | | distinction in the current Linux timezone files between TZ names | | for Standard and Daylight time. We gerry-rig that here, at | | least for Heyu's purposes. | +------------------------------------------------------------------*/ void fix_tznames ( void ) { time_t now; extern char *tzname[], *heyu_tzname[]; static char std[16], dst[16]; /* Get current date and time */ time(&now) ; (void)localtime(&now); (void)strncpy2(std, tzname[0], sizeof(std) - 1); (void)strncpy2(dst, tzname[1], sizeof(dst) - 1); if ( strcmp(tzname[0], tzname[1]) == 0 ) (void)strncat(dst, " (DST)", sizeof(dst) - 1 - strlen(dst)); heyu_tzname[0] = std; heyu_tzname[1] = dst; return; } /*-----------------------------------------------------------------+ | Return pointer to string containing "asif" date and time. | +-----------------------------------------------------------------*/ char *asif_time_string ( void ) { time_t now; struct tm *tms; static char buffer[32]; extern char *heyu_tzname[]; fix_tznames(); time(&now); tms = localtime(&now); if ( configp->asif_date > 0 ) { tms->tm_year = (int)(configp->asif_date / 10000L) - 1900; tms->tm_mon = (int)(configp->asif_date % 10000L) / 100 - 1; tms->tm_mday = (int)(configp->asif_date % 100L); } if ( configp->asif_time >= 0 ) { tms->tm_hour = 0; tms->tm_min = configp->asif_time; tms->tm_sec = 0; } tms->tm_isdst = -1; (void)mktime(tms); (void)sprintf(buffer, "%s %s %02d %4d %02d:%02d:%02d %s", wday_name[tms->tm_wday], month_name[tms->tm_mon], tms->tm_mday, tms->tm_year + 1900, tms->tm_hour, tms->tm_min, tms->tm_sec, heyu_tzname[tms->tm_isdst]); return buffer; } /*-----------------------------------------------------------------+ | Return pointer to string containing current system Legal Time. | +-----------------------------------------------------------------*/ char *legal_time_string ( void ) { time_t now; struct tm *tms; static char buffer[32]; extern char *heyu_tzname[]; fix_tznames(); time(&now); tms = localtime(&now); (void)sprintf(buffer, "%s %s %02d %4d %02d:%02d:%02d %s", wday_name[tms->tm_wday], month_name[tms->tm_mon], tms->tm_mday, tms->tm_year + 1900, tms->tm_hour, tms->tm_min, tms->tm_sec, heyu_tzname[tms->tm_isdst]); return buffer; } /*-----------------------------------------------------------------+ | Return pointer to tm structure with local Standard Time. | +-----------------------------------------------------------------*/ struct tm *stdtime( const time_t *timep ) { time_t now; /* Set the local timezone variable */ get_std_timezone(); now = *timep - (time_t)std_tzone; return gmtime(&now); } /*-----------------------------------------------------------------+ | Get count of days for argument date at minutes after 00:00 | | time, counted from 1 Jan 1970 00:00 hours Standard time in the | | local timezone. | +-----------------------------------------------------------------*/ long day_count ( int year, int month, int mday, int minutes ) { time_t now; struct tm mytm, *tms; tms = &mytm; tms->tm_year = year - 1900; tms->tm_mon = month - 1; tms->tm_mday = mday; tms->tm_hour = 0; tms->tm_min = minutes; tms->tm_sec = 0; tms->tm_isdst = -1; now = mktime(tms); return ((long)now - std_tzone) / 86400L; } /*-----------------------------------------------------------------+ | Given the count of days from 1 Jan 1970 at 00:00:00 GMT, | | return the Julian Day corresponding to Noon on that day. | +-----------------------------------------------------------------*/ long int daycount2JD ( long int daycount ) { return (daycount + 2440588L); } /*-----------------------------------------------------------------+ | Return 1 if the argument year is a leap year; 0 otherwise. | +-----------------------------------------------------------------*/ int isleapyear ( int year ) { return ((year % 400) == 0) ? 1 : ((year % 100) == 0) ? 0 : ((year % 4 ) == 0) ? 1 : 0 ; } /*-----------------------------------------------------------------+ | Determine elapsed minutes from 0:00 hrs Standard Time on Jan 1 | | of the current year until the next NDSTINTV changes between | | Standard/Daylight Time. Store results in global struct | | dststruct array lgls[] and in struct config. | +-----------------------------------------------------------------*/ int get_dst_info ( int year ) { time_t now, seconds, startsec, jan1sec, jul1sec, delta ; struct tm jultms, *tmjan, *tmjul, *tms; int indx, nintv, val, startval, dstminutes; int iter, result = -1, restart; int offset[2]; /* Get current date and time */ time(&now) ; tmjan = localtime(&now); if ( year >= 1970 ) tmjan->tm_year = year - 1900; /* Get calendar seconds at 0:00 hours Legal Time on Jan 1st of this year */ tmjan->tm_mon = 0; tmjan->tm_mday = 1; tmjan->tm_hour = 0; tmjan->tm_min = tmjan->tm_sec = 0; tmjan->tm_isdst = -1; jan1sec = mktime(tmjan); tmjul = &jultms; memcpy( (void *)tmjul, (void *)tmjan, sizeof(struct tm) ); /* Calendar seconds at same legal time on July 1st */ tmjul->tm_mon = 6; tmjul->tm_mday = 1; tmjul->tm_hour = 0; tmjul->tm_min = tmjul->tm_sec = 0; tmjul->tm_isdst = -1; jul1sec = mktime(tmjul); /* Reduce difference by full days of 86400 seconds */ dstminutes = (int)((jul1sec - jan1sec) % (time_t)86400 / (time_t)60); dstminutes = min( dstminutes, 1440 - dstminutes ); configp->dstminutes = dstminutes; offset[0] = 0; offset[1] = dstminutes; /* Reduce to seconds at 0:00 hours Standard Time */ jan1sec = ((jan1sec - std_tzone) / (time_t)86400) * (time_t)86400 + std_tzone; if ( (val = tmjan->tm_isdst) > 0 ) { /* Daylight time in Southern hemisphere */ configp->isdst = 1; indx = 1; startval = val; } else if ( (val = tmjul->tm_isdst) > 0 ) { /* Daylight time in Northern hemisphere */ configp->isdst = 1; indx = 0; startval = 0; } else { /* Daylight time never in effect */ configp->isdst = 0; for ( nintv = 0; nintv < NDSTINTV; nintv++ ) lgls[nintv].elapsed = -1; stds[nintv].elapsed = -1; return 0; } nintv = 0; startsec = jan1sec; while ( nintv < NDSTINTV ) { iter = 0; result = -1; restart = 1; while ( iter < 1000 && !iter_mgr(result, (long *)(&delta), 30*86400L, &restart) ) { iter++; seconds = startsec + delta; tms = localtime(&seconds); result = (tms->tm_isdst == startval) ? -1 : 1 ; } if ( iter > 999 ) { (void) fprintf(stderr, "convergence error in get_dst_info()\n"); exit(1); } /* Store as elapsed minutes from 0:00 hours Jan 1 Standard Time */ /* adjusted for changeover from daylight to standard time and */ /* for changeover from standard to daylight time. */ lgls[nintv].elapsed = (long)(seconds - jan1sec)/60L + offset[indx]; lgls[nintv].offset = offset[indx]; if ( UNDEF_TIME == DST_TIME ) stds[nintv].elapsed = lgls[nintv].elapsed; else stds[nintv].elapsed = (long)(seconds - jan1sec)/60L + dstminutes; stds[nintv].offset = offset[indx]; stdr[nintv].elapsed = (long)(seconds - jan1sec)/60L; stdr[nintv].offset = offset[indx]; nintv++; indx = (indx + 1) % 2; startval = (startval == val) ? 0 : val; startsec = seconds + (time_t)86400 ; } return nintv; } /*-----------------------------------------------------------------+ | Determine whether DST is in effect on a given yday & minutes | | (measured from midnight) by comparison with the data in struct | | dststruct dsts[], which must previously have been loaded by a | | call to function get_dst_info(). The number of minutes to add | | to Standard to get Legal Time is returned. | +-----------------------------------------------------------------*/ int isdst_test ( int yday, int minutes ) { int j; long elapsed; elapsed = (long)1440 * (long)yday + (long) minutes; for ( j = 0; j < NDSTINTV; j++ ) { if ( elapsed < lgls[j].elapsed ) return lgls[j].offset; } return 0; } /*-----------------------------------------------------------------+ | Return the appropriate time adjustment for periods of Standard | | and Daylight Time. | +-----------------------------------------------------------------*/ int time_adjust ( int yday, int minutes, unsigned char mode ) { int j, offset = 0; long elapsed; elapsed = (long)1440 * (long)yday + (long) minutes; if ( mode == LGL2STD ) { for ( j = 0; j < NDSTINTV; j++ ) { if ( elapsed < stds[j].elapsed ) { offset = stds[j].offset; break; } } } else { for ( j = 0; j < NDSTINTV; j++ ) { if ( elapsed < stdr[j].elapsed ) { offset = stdr[j].offset; break; } } } return offset; } /*---------------------------------------------------------+ | Display system calendar configuration: | | Current date and time. | | Timezone. | | Begin and end of Daylight Time for the current year. | +---------------------------------------------------------*/ void display_sys_calendar ( void ) { extern char *heyu_tzname[]; /* Months beginning with 1 */ static char *m_names[] = { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static char *display[] = { "Daylight Time begins yday: %-3d %s %02d %s %4d at %02d:%02d %s\n", "Standard Time resumes yday: %-3d %s %02d %s %4d at %02d:%02d %s\n" }; time_t now; struct tm *tms ; int j, k; int month, day, yday, wday, minute, year, year0; long jan1day; /* Determine the user's standard timezone */ get_std_timezone(); /* Create distinctive TZ names if necessary */ fix_tznames(); /* Get current date and time */ time(&now) ; tms = localtime(&now); /* Display current date and time */ (void) printf("\nDate: %s %02d %s %04d yday: %-3d\n", wday_name[tms->tm_wday], tms->tm_mday, month_name[tms->tm_mon], 1900+tms->tm_year, tms->tm_yday); (void) printf("Time: %02d:%02d:%02d %s\n", tms->tm_hour, tms->tm_min, tms->tm_sec, heyu_tzname[tms->tm_isdst == 0 ? 0 : 1] ); year0 = tms->tm_year + 1900; /* Display Timezone */ if ( timezone < 0L ) (void) printf("Standard Time Zone: %.2f hours East of Greenwich\n", -(double)std_tzone / 3600.); else (void) printf("Standard Time Zone: %.2f hours West of Greenwich\n", (double)std_tzone / 3600.); if ( !get_dst_info(0) ) { (void) printf("Daylight time is not in effect at any time during the year.\n"); return; } else { /* Get the starting day for the year */ tms->tm_year = year0 - 1900; tms->tm_mon = 0; tms->tm_mday = 1; tms->tm_hour = 0; tms->tm_min = tms->tm_sec = 0; tms->tm_isdst = -1; now = mktime(tms); jan1day = (long)((now - std_tzone) / (time_t)86400); /* Display the dates in the order they occur during the year. */ for ( j = 0; j < NDSTINTV; j++ ) { k = lgls[j].offset == 0 ? 0 : 1 ; yday = lgls[j].elapsed / 1440; minute = lgls[j].elapsed % 1440; yday2date( jan1day, yday, &year, &month, &day, &wday ); (void)printf(display[k], yday, wday_name[wday], day, m_names[month], year, minute/60, minute%60, heyu_tzname[k] ); } } (void) printf("\n"); return; } /*---------------------------------------------------------------------+ | Get today's date info and save in CALEND structure. | +---------------------------------------------------------------------*/ void calendar_today ( CALEND *calendp ) { time_t now; struct tm tmstr, *tmp, *tms; int year, month, mday, minutes; if ( !configp->read_flag ) { (void) fprintf(stderr, "Function calendar_today() says: Configuration file has not yet been read.\n"); exit(1); } fix_tznames(); tmp = &tmstr; time(&now); tms = localtime(&now); calendp->asif_flag = ASIF_NONE; if ( configp->asif_date > 0 ) { tms->tm_year = (int)(configp->asif_date / 10000L) - 1900; tms->tm_mon = (int)(configp->asif_date % 10000L) / 100 - 1; tms->tm_mday = (int)(configp->asif_date % 100L); calendp->asif_flag |= ASIF_DATE; } if ( configp->asif_time >= 0 ) { tms->tm_hour = 0; tms->tm_min = configp->asif_time; tms->tm_sec = 0; calendp->asif_flag |= ASIF_TIME; } tms->tm_isdst = -1; now = mktime(tms); calendp->year = year = tms->tm_year + 1900; calendp->month = month = tms->tm_mon + 1; calendp->mday = mday = tms->tm_mday; calendp->minutes = minutes = 60 * tms->tm_hour + tms->tm_min; calendp->jan1day = day_count( year, 1, 1, 0 ); calendp->create_day = day_count( year, month, mday, minutes ) - calendp->jan1day; if ( configp->mode == COMPATIBLE ) { calendp->today = calendp->jan1day; calendp->yday = 0; calendp->day_zero = 0; } else { calendp->today = day_count( year, month, mday, minutes ); calendp->yday = calendp->today - calendp->jan1day; calendp->day_zero = calendp->yday; } get_dst_info(year); calendp->valid = 1; return; } /*---------------------------------------------------------------------+ | Advance the date info in a CALEND structure by argument ndays. | +---------------------------------------------------------------------*/ void advance_calendar ( CALEND *calendp, int ndays ) { struct tm *tms, mytm; if ( !calendp || calendp->valid != 1 ) { (void) fprintf(stderr, "advance_calendar() : No valid existing CALEND structure to advance.\n"); exit(1); } tms = &mytm; tms->tm_year = calendp->year - 1900; tms->tm_mon = calendp->month - 1; tms->tm_mday = calendp->mday + ndays; tms->tm_min = calendp->minutes; tms->tm_hour = tms->tm_sec = 0; tms->tm_isdst = -1; (void) mktime(tms); calendp->year = tms->tm_year + 1900; calendp->month = tms->tm_mon + 1; calendp->mday = tms->tm_mday; calendp->minutes = (configp->asif_time > 0) ? configp->asif_time : 60 * tms->tm_hour + tms->tm_min; calendp->isdst = tms->tm_isdst; calendp->today = day_count( calendp->year, calendp->month, calendp->mday, calendp->minutes); calendp->jan1day = day_count( calendp->year, 1, 1, 0); calendp->yday = calendp->today - calendp->jan1day; calendp->day_zero = (configp->mode == COMPATIBLE) ? 0 : calendp->yday; /* Turn off the asif_date flag */ calendp->asif_flag &= ~ASIF_DATE; return; } /* Other functions */ /*---------------------------------------------------------------------+ | Return string containing full pathname to a Heyu file. | +---------------------------------------------------------------------*/ char *pathspec ( char *filename ) { static char full_path[PATH_LEN + 1]; (void)strncpy2(full_path, heyu_path, sizeof(full_path) - 1); (void)strncat(full_path, filename, sizeof(full_path) - 1 - strlen(full_path)); return full_path; } /*---------------------------------------------------------------------+ | Return string containing full pathname to a Heyu report file. | +---------------------------------------------------------------------*/ char *altpathspec ( char *filename ) { extern char alt_path[]; static char full_path[PATH_LEN + 1]; (void)strncpy2(full_path, alt_path, sizeof(full_path) - 1); (void)strncat(full_path, filename, sizeof(full_path) - 1 - strlen(full_path)); return full_path; } /*---------------------------------------------------------------------+ | Search the array of SCENEs for label. Return the index in the | | array if found, otherwise return -1. | +---------------------------------------------------------------------*/ int lookup_scene ( SCENE *scenep, char *label ) { int j = 0; while ( scenep && scenep[j].line_no > 0 ) { if ( strcmp(label, scenep[j].label) == 0 ) { return j; } j++; } return -1; } /*---------------------------------------------------------------------+ | Search the array of ALIAS structures for the argument alias label. | | If found, pass the encoded housecode and unit bitmap back through | | the argument list and return the index in the array. Otherwise | | return -1. | +---------------------------------------------------------------------*/ int get_alias ( ALIAS *aliasp, char *label, char *hc, unsigned int *bmap ) { int j; j = 0; while ( aliasp && aliasp[j].line_no > 0 ) { if ( !strcmp(aliasp[j].label, label) ) { *hc = aliasp[j].housecode; *bmap = aliasp[j].unitbmap; return j; } j++ ; } *hc = '_'; *bmap = 0; return -1; } /*---------------------------------------------------------------------+ | Search the array of ALIAS structures for multiple instances of | | aliases having the argument housecode and bitmap. Return each | | alias label as found and update the index pointer to the following | | address. Return NULL when no more aliases are found. | +---------------------------------------------------------------------*/ char *lookup_label_mult ( char hc, unsigned int bitmap, int *indxp ) { ALIAS *aliasp; int j; aliasp = configp->aliasp; hc = toupper((int)hc); j = *indxp; while ( aliasp && aliasp[j].line_no > 0 ) { if ( hc == aliasp[j].housecode && bitmap == aliasp[j].unitbmap ) { *indxp = j + 1; return aliasp[j].label; } j++; } return (char *)NULL; } /*---------------------------------------------------------------------+ | Search the array of ALIAS structures for multiple instances of | | aliases having the argument housecode and bitmap. Return each | | alias label as found and update the index pointer to the following | | address. Return NULL when no more aliases are found. | +---------------------------------------------------------------------*/ char *alias_rev_lookup_mult ( char hc, unsigned int bitmap, int *indxp ) { ALIAS *aliasp; int j; aliasp = configp->aliasp; hc = toupper((int)hc); j = *indxp; while ( aliasp && aliasp[j].line_no > 0 ) { if ( hc == aliasp[j].housecode && bitmap == aliasp[j].unitbmap ) { *indxp = j + 1; return aliasp[j].label; } j++; } return (char *)NULL; } #if 0 /*---------------------------------------------------------------------+ | Search the array of ALIAS structures for an alias with optflag | | MOPT_SENSOR and having the argument housecode and bitmap. Return | | the timestamp if found, otherwise 0. | +---------------------------------------------------------------------*/ long get_sensor_timestamp ( unsigned char hcode, unsigned char ucode ) { char hc; unsigned int bitmap; int j; if ( !(aliasp->configp->aliasp) ) return 0; hc = hc2code(hcode); bitmap = (1 << ucode); j = 0; while ( aliasp && aliasp[j].line_no > 0 ) { if ( hc == aliasp[j].housecode && bitmap == aliasp[j].unitbmap && aliasp[j].optflag & MOPT_SENSOR ) { return aliasp[j].timestamp; } j++; } return 0; } #endif /*---------------------------------------------------------------------+ | Search the array of ALIAS structures for an alias having the | | argument housecode and bitmap. Return the alias label if found, | | otherwise "_no_alias_". | +---------------------------------------------------------------------*/ char *lookup_label ( char hc, unsigned int bitmap ) { ALIAS *aliasp; int j; aliasp = configp->aliasp; hc = toupper((int)hc); j = 0; while ( aliasp && aliasp[j].line_no > 0 ) { if ( hc == aliasp[j].housecode && bitmap == aliasp[j].unitbmap ) { return aliasp[j].label; } j++; } return "_no_alias_"; } /*---------------------------------------------------------------------+ | Search the array of ALIAS structures for an alias having the | | argument housecode, bitmap and ident. Return the alias index if | | found, otherwise -1. | +---------------------------------------------------------------------*/ int alias_rev_index ( char hc, unsigned int bitmap, unsigned char vtype, unsigned char ident ) { ALIAS *aliasp; int j, k; aliasp = configp->aliasp; hc = toupper((int)hc); j = 0; while ( aliasp && aliasp[j].line_no > 0 ) { if ( hc == aliasp[j].housecode && bitmap == aliasp[j].unitbmap && vtype == aliasp[j].vtype ) { if ( vtype == 0 ) return j; for ( k = 0; k < aliasp[j].nident; k++ ) { if ( ident == aliasp[j].ident[k] ) return j; } } j++; } return -1; } /*---------------------------------------------------------------------+ | Search the array of ALIAS structures for an alias having the | | argument housecode and bitmap. Return the alias label if found, | | otherwise "_no_alias_". | +---------------------------------------------------------------------*/ char *alias_rev_lookup ( char hc, unsigned int bitmap ) { ALIAS *aliasp; int j; aliasp = configp->aliasp; hc = toupper((int)hc); j = 0; while ( aliasp && aliasp[j].line_no > 0 ) { if ( hc == aliasp[j].housecode && bitmap == aliasp[j].unitbmap ) { return aliasp[j].label; } j++; } return "_no_alias_"; } /*---------------------------------------------------------------------+ | Search the array of ALIAS structures for the argument alias label. | | If found, pass the encoded housecode and unit bitmap back through | | the argument list and return the index in the array. Otherwise | | return -1. | +---------------------------------------------------------------------*/ int alias_lookup ( char *label, char *hc, unsigned int *bmap ) { return get_alias( configp->aliasp, label, hc, bmap ); } /*---------------------------------------------------------------------+ | Return a count of the total number of defined aliases. | +---------------------------------------------------------------------*/ int alias_count ( void ) { ALIAS *aliasp; int count = 0; aliasp = configp->aliasp; while ( aliasp && aliasp[count].line_no > 0 ) count++; return count; } /*---------------------------------------------------------------------+ | Return the macro execution time of the earliest event whose time | | is defined by "now+NN" in the schedule file. The time includes the | | macro delay time, if any, and is in minutes after 00:00 Legal Time. | | If no such event defined, return -1 | | (This function must be called before the input time has been | | converted from Legal to Standard Time.) | +---------------------------------------------------------------------*/ int get_first_now_time ( TEVENT *teventp ) { int j; int exec_time, earliest; j = 0; earliest = 9999; while ( teventp && teventp[j].line_no > 0 ) { if ( teventp[j].generation == current_tevent_generation && (teventp[j].flag & NOW_EVENT) ) { exec_time = teventp[j].offset + teventp[j].delay; earliest = min(earliest, exec_time); } j++; } if ( earliest < 9999 ) return earliest; return -1; } /*---------------------------------------------------------------------+ | Interpret the time token from a timer command as either the | | clock time in minutes or the offset from dawn or dusk in minutes. | | Return a flag indicating whether Clock or Dawn or Dusk relative. | | The time token may also be "now[+offset]" for quick testing. | +---------------------------------------------------------------------*/ int parse_time_token ( char *str, int *stime ) { int hour, min, flag = 0; char buf[64]; char *sp; struct tm *tms; time_t now; (void) strncpy2(buf, str, 63); (void) strlower(strtrim(buf)); if ( *(sp = buf + strlen(buf) - 1) == 's' ) { flag |= SEC_EVENT; *sp = '\0'; } if ( !strncmp(buf, "dawn", 4) ) { flag |= DAWN_EVENT ; *stime = (*(buf+4)) ? (int)strtol(buf+4, NULL, 10) : 0 ; } else if ( !strncmp(buf, "dusk", 4) ) { flag |= DUSK_EVENT ; *stime = (*(buf+4)) ? (int)strtol(buf+4, NULL, 10) : 0 ; } else if ( !strncmp(buf, "now", 3) ) { /* (For quick testing timers) */ *stime = (*(buf+3)) ? (int)strtol(buf+3, NULL, 10) : 0 ; if ( *stime < 0 || flag & SEC_EVENT ) { flag = INVALID_EVENT; return flag; } time(&now); tms = localtime(&now); /* Round up, allowing at least 15 seconds for uploading */ *stime += 60 * tms->tm_hour + tms->tm_min + (tms->tm_sec + 75)/60; if ( *stime < 0 || *stime > 1439 ) { flag = INVALID_EVENT; return flag; } else flag |= (CLOCK_EVENT | NOW_EVENT); } else if ( (sp = strchr(buf, ':')) != NULL ) { min = (int)strtol(sp+1, NULL, 10); *sp = '\0'; hour = ( *buf ) ? (int)strtol(buf, NULL, 10) : 0 ; *stime = 60 * hour + min ; if ( *stime < 0 || *stime > 1439 ) flag = INVALID_EVENT; else flag |= CLOCK_EVENT; } else { flag = INVALID_EVENT ; } return flag; } /*-----------------------------------------------------------+ | Return the index of a child tevent which is a duplicate | | of the parent except that it's one generation higher and | | the links of both parent and child are updated to insert | | the child into the linked list after the parent. | +-----------------------------------------------------------*/ int spawn_child_tevent ( TEVENT **teventpp, int parent_index ) { int cindx; /* Get a new tevent */ cindx = tevent_index( teventpp ); /* Copy all fields from the parent tevent */ (void) memcpy( (void *)(*teventpp + cindx), (void *)(*teventpp + parent_index), sizeof(TEVENT) ); /* Reset the flags defined as "don't copy" */ (*teventpp)[cindx].flag &= ~(NOCOPY_EVENTS); /* Set the new generation */ increment_tevent_generation( *teventpp, cindx ); /* Insert the child into the linked list */ (*teventpp)[cindx].link = (*teventpp)[parent_index].link; (*teventpp)[parent_index].link = cindx; /* Set the reverse link */ (*teventpp)[cindx].plink = parent_index; return cindx; } /*-----------------------------------------------------------+ | Return the index of a child timer which is a duplicate | | of the parent except that it's one generation higher and | | the links of both parent and child are updated to insert | | the child into the linked list after the parent. | +-----------------------------------------------------------*/ int spawn_child_timer ( TIMER **timerpp, int parent_index ) { int cindx; /* Get a new timer */ cindx = timer_index( timerpp ); /* Copy all fields from the parent timer */ (void) memcpy( (void *)(*timerpp + cindx), (void *)(*timerpp + parent_index), sizeof(TIMER) ); /* Set the new generation */ increment_timer_generation( *timerpp, cindx ); /* Insert the child into the linked list */ (*timerpp)[cindx].link = (*timerpp)[parent_index].link; (*timerpp)[parent_index].link = cindx; return cindx; } /*---------------------------------------------------------------------+ | Return next available index in array of TIMER structures | +---------------------------------------------------------------------*/ int timer_index ( TIMER **timerpp ) { extern int timer_size, timer_maxsize ; int j = 0; int blksize = 20; static int siztimer = sizeof(TIMER); /* Allocate initial block of memory */ if ( *timerpp == NULL ) { *timerpp = calloc(blksize, siztimer ); if ( *timerpp == NULL ) { (void) fprintf(stderr, "Unable to allocate memory for Timer.\n"); exit(1); } timer_maxsize = blksize; timer_size = 0; for ( j = 0; j < timer_maxsize; j++ ) { (*timerpp)[j].line_no = -1 ; (*timerpp)[j].link = j + 1; (*timerpp)[j].cancel = 0; } } /* Check to see if there's an available location */ /* If not, increase the size of the memory allocation. */ /* (Always leave room for a final termination indicator.) */ if ( timer_size == (timer_maxsize - 1)) { timer_maxsize += blksize ; *timerpp = realloc(*timerpp, timer_maxsize * siztimer ); if ( *timerpp == NULL ) { (void) fprintf(stderr, "Unable to increase size of Timer list.\n"); exit(1); } /* Initialize the new memory allocation */ for ( j = timer_size; j < timer_maxsize; j++ ) { (*timerpp)[j].line_no = -1; (*timerpp)[j].link = j + 1; (*timerpp)[j].cancel = 0; } } j = timer_size; timer_size += 1; return j; } /*---------------------------------------------------------------------+ | Return next available index in array of TEVENT structures | +---------------------------------------------------------------------*/ int tevent_index ( TEVENT **teventpp ) { extern int tevent_size, tevent_maxsize ; int j; int blksize = 20; static int siztevent = sizeof(TEVENT); /* Allocate initial block of memory */ if ( *teventpp == NULL ) { *teventpp = calloc(blksize, siztevent ); if ( *teventpp == NULL ) { (void) fprintf(stderr, "Unable to allocate memory for tevent.\n"); exit(1); } tevent_maxsize = blksize; tevent_size = 0; for ( j = 0; j < tevent_maxsize; j++ ) { (*teventpp)[j].line_no = -1 ; (*teventpp)[j].combined = 0; (*teventpp)[j].plink = -1; (*teventpp)[j].chain_len = 1; (*teventpp)[j].link = j + 1; } } /* Check to see if there's an available location */ /* If not, increase the size of the memory allocation. */ /* (Always leave room for a final termination indicator.) */ if ( tevent_size == (tevent_maxsize - 1)) { tevent_maxsize += blksize ; *teventpp = realloc(*teventpp, tevent_maxsize * siztevent ); if ( *teventpp == NULL ) { (void) fprintf(stderr, "Unable to increase size of tevent list.\n"); exit(1); } /* Initialize the new memory allocation */ for ( j = tevent_size; j < tevent_maxsize; j++ ) { (*teventpp)[j].line_no = -1; (*teventpp)[j].combined = 0; (*teventpp)[j].plink = -1; (*teventpp)[j].chain_len = 1; (*teventpp)[j].link = j + 1; } } j = tevent_size; tevent_size += 1; return j; } /*---------------------------------------------------------------------+ | Save the state of TIMERs and TEVENTs | +---------------------------------------------------------------------*/ void save_state ( TIMER *timerp, TEVENT *teventp ) { extern int timer_size, timer_savesize, save_timer_generation; extern int tevent_size, tevent_savesize, save_tevent_generation; if ( timerp ) { timer_savesize = timer_size; save_timer_generation = current_timer_generation; } if ( teventp ) { tevent_savesize = tevent_size; save_tevent_generation = current_tevent_generation; } return; } /*---------------------------------------------------------------------+ | Save the initial TIMER configuration | +---------------------------------------------------------------------*/ void save_timer_config ( TIMER *timerp ) { extern int timer_size, timer_savesize, save_timer_generation; if ( timerp ) { timer_savesize = timer_size; save_timer_generation = current_timer_generation; } return; } /*---------------------------------------------------------------------+ | Save the initial TEVENT configuration | +---------------------------------------------------------------------*/ void save_tevent_config ( TEVENT *teventp ) { extern int tevent_size, tevent_savesize, save_tevent_generation; if ( teventp ) { tevent_savesize = tevent_size; save_tevent_generation = current_tevent_generation; } return; } /*---------------------------------------------------------------------+ | Restore TIMER and TEVENT configuration to saved state. | +---------------------------------------------------------------------*/ void restore_state ( TIMER *timerp, TEVENT *teventp ) { extern int timer_size, timer_savesize, timer_maxsize; extern int save_timer_generation; extern int tevent_size, tevent_savesize, tevent_maxsize; extern int save_tevent_generation; int j; if ( timerp ) { timer_size = timer_savesize; for ( j = timer_size; j < timer_maxsize; j++ ) { timerp[j].line_no = -1; timerp[j].link = j + 1; } current_timer_generation = save_timer_generation; } if ( teventp ) { tevent_size = tevent_savesize; for ( j = tevent_size; j < tevent_maxsize; j++ ) { teventp[j].line_no = -1; teventp[j].link = j + 1; teventp[j].trig = -1; } current_tevent_generation = save_tevent_generation; } return; } /*---------------------------------------------------------------------+ | Restore TIMER configuration to saved state. | +---------------------------------------------------------------------*/ void restore_timer_config ( TIMER *timerp ) { extern int timer_size, timer_savesize, timer_maxsize; extern int save_timer_generation; int j; if ( timerp ) { timer_size = timer_savesize; for ( j = timer_size; j < timer_maxsize; j++ ) { timerp[j].line_no = -1; timerp[j].link = j + 1; } current_timer_generation = save_timer_generation; } return ; } /*---------------------------------------------------------------------+ | Restore TEVENT configuration to saved state. | +---------------------------------------------------------------------*/ void restore_tevent_config ( TEVENT *teventp ) { extern int tevent_size, tevent_savesize, tevent_maxsize; extern int save_tevent_generation; int j; if ( teventp ) { tevent_size = tevent_savesize; for ( j = tevent_size; j < tevent_maxsize; j++ ) { teventp[j].line_no = -1; teventp[j].link = j + 1; teventp[j].trig = -1; } current_tevent_generation = save_tevent_generation; } return; } /*---------------------------------------------------------------------+ | Update the timer generation counter and return the new generation. | +---------------------------------------------------------------------*/ int update_current_timer_generation ( void ) { extern int current_timer_generation, timer_generation_delta; current_timer_generation += timer_generation_delta; timer_generation_delta = 0; return current_timer_generation; } /*---------------------------------------------------------------------+ | Update the tevent generation counter and return the new generation. | +---------------------------------------------------------------------*/ int update_current_tevent_generation ( void ) { extern int current_tevent_generation, tevent_generation_delta; current_tevent_generation += tevent_generation_delta; tevent_generation_delta = 0; return current_tevent_generation; } /*---------------------------------------------------------------------+ | Increment the current tevent generation by at least one. | +---------------------------------------------------------------------*/ int update_current_tevent_generation_anyway ( void ) { extern int tevent_generation_delta; if ( tevent_generation_delta == 0 ) tevent_generation_delta = 1; return update_current_tevent_generation(); } /*---------------------------------------------------------------------+ | Increment the generation of a tevent (without changing the current | | generation). | +---------------------------------------------------------------------*/ void increment_tevent_generation ( TEVENT *teventp, int index ) { extern int tevent_generation_delta; teventp[index].generation += 1; tevent_generation_delta = 1; return; } /*---------------------------------------------------------------------+ | Increment the generation of a timer (without changing the current | | generation). | +---------------------------------------------------------------------*/ void increment_timer_generation ( TIMER *timerp, int index ) { extern int timer_generation_delta; if ( timerp ) timerp[index].generation += 1; timer_generation_delta = 1; return; } /*---------------------------------------------------------------------+ | Return next available index in array of TRIGGER structures | +---------------------------------------------------------------------*/ int trigger_index ( TRIGGER **triggerpp ) { static int size, max_size ; int j; int blksize = 5; static int strucsize = sizeof(TRIGGER); /* Allocate initial block of memory */ if ( *triggerpp == NULL ) { *triggerpp = calloc(blksize, strucsize ); if ( *triggerpp == NULL ) { (void) fprintf(stderr, "Unable to allocate memory for Trigger.\n"); exit(1); } max_size = blksize; size = 0; for ( j = 0; j < max_size; j++ ) { (*triggerpp)[j].line_no = -1 ; } } /* Check to see if there's an available location */ /* If not, increase the size of the memory allocation. */ /* (Always leave room for a final termination indicator.) */ if ( size == (max_size - 1)) { max_size += blksize ; *triggerpp = realloc(*triggerpp, max_size * strucsize ); if ( *triggerpp == NULL ) { (void) fprintf(stderr, "Unable to increase size of Trigger list.\n"); exit(1); } /* Initialize the new memory allocation */ for ( j = size; j < max_size; j++ ) { (*triggerpp)[j].line_no = -1; } } j = size; size += 1; return j; } /*---------------------------------------------------------------------+ | Return an index to the list of macro elements with sufficient | | room for the argument number of bytes. | +---------------------------------------------------------------------*/ int macro_element_index ( unsigned char **elementpp, int nbytes ) { static int size, max_size ; static int ucsize = sizeof(unsigned char); int j; int blksize = 50; /* Must be at least greater than the largest */ /* sized macro element (currently 6 bytes). */ /* Allocate initial block of memory to begin with */ if ( *elementpp == NULL ) { max_size = max(blksize, nbytes); *elementpp = calloc(max_size, ucsize ); if ( *elementpp == NULL ) { (void) fprintf(stderr, "Unable to allocate memory for macro element.\n"); exit(1); } size = 0; for ( j = 0; j < max_size; j++ ) { (*elementpp)[j] = 0 ; } } /* Check to see if there's an available location with enough room. */ /* If not, increase the size of the memory allocation. */ if ( (size + nbytes) > max_size ) { max_size += max(blksize, nbytes) ; *elementpp = realloc(*elementpp, (max_size * ucsize) ); if ( *elementpp == NULL ) { (void) fprintf(stderr, "Unable to increase size of macro element list.\n"); exit(1); } for ( j = size; j < max_size; j++ ) { (*elementpp)[j] = 0 ; } } j = size; size += nbytes; return j; } /*---------------------------------------------------------------------+ | Return the index in array MACRO to the argument macro_name. | | If not found, create the entry. | | Also mark the flag in MACRO with the type of command which | | referenced or defined it, i.e., timer, trigger, or macro. | | If defined more than once by a macro command, mark it as a | | duplicate. | +---------------------------------------------------------------------*/ int macro_index ( MACRO **macropp, char *macro_label, unsigned char refer ) { static int size, max_size ; int j; int blksize = 10; static int sizmacro = sizeof(MACRO); /* Allocate initial block of memory */ if ( *macropp == NULL ) { *macropp = calloc(blksize, sizmacro ); if ( *macropp == NULL ) { (void) fprintf(stderr, "Unable to allocate memory for Macro list.\n"); exit(1); } max_size = blksize; size = 0; for ( j = 0; j < max_size; j++ ) { (*macropp)[j].line_no = -1; (*macropp)[j].refer = 0 ; (*macropp)[j].label[0] = '\0' ; (*macropp)[j].use = USED ; (*macropp)[j].modflag = UNMODIFIED; (*macropp)[j].isnull = 0; (*macropp)[j].trig = -1; (*macropp)[j].nelem = 0; (*macropp)[j].total = 0; } /* Make the first macro in the list the null macro */ /* and give it the label "null". */ (*macropp)[0].line_no = 9999; (*macropp)[0].isnull = 1; (*macropp)[0].refer = MACRO_PARSER; (void) strncpy2((*macropp)[0].label, "null", MACRO_LEN); size = 1; } /* See if the macro name is already in the list. */ /* If so, add parser's identity and return its index. */ /* However if this and the existing entry were made */ /* by the macro parser, also flag it as a duplicate. */ for ( j = 0; j < size; j++ ) { if ( !strcmp(macro_label, (*macropp)[j].label) ) { if ( (*macropp)[j].refer & (refer & MACRO_PARSER) ) refer |= MACRO_DUPLICATE ; (*macropp)[j].refer |= refer ; return j; } } /* Check to see if there's room for the new macro name. */ /* If not, increase the size of the memory allocation. */ /* (Always leave room for a final terminating NULL.) */ if ( size == (max_size - 1)) { max_size += blksize ; *macropp = realloc(*macropp, max_size * sizmacro ); if ( *macropp == NULL ) { (void) fprintf(stderr, "Unable to increase size of Macro list.\n"); exit(1); } /* Initialize the new memory allocation */ for ( j = size; j < max_size; j++ ) { (*macropp)[j].line_no = -1; (*macropp)[j].refer = 0; (*macropp)[j].label[0] = '\0' ; (*macropp)[j].use = USED ; (*macropp)[j].modflag = UNMODIFIED; (*macropp)[j].isnull = 0; (*macropp)[j].trig = -1; (*macropp)[j].nelem = 0; (*macropp)[j].total = 0; } } /* Now add the new macro label, mark caller's identity in flag, */ /* and return the index to its position in the list */ j = size ; (void) strncpy2((*macropp)[j].label, macro_label, MACRO_LEN); (*macropp)[j].refer |= refer ; size += 1 ; return j ; } /*---------------------------------------------------------------------+ | Return next available index in array of intervals. | +---------------------------------------------------------------------*/ int intv_index ( int **intvp , unsigned int *size ) { static int max_size ; static int sizeint = sizeof(int); int j; int blksize = 100; /* Allocate initial block of memory */ if ( *intvp == NULL ) { *intvp = calloc(blksize, sizeint ); if ( *intvp == NULL ) { (void) fprintf(stderr, "Unable to allocate memory for intervals list.\n"); exit(1); } max_size = blksize; *size = 0; } /* Check to see if there's an available location */ /* If not, increase the size of the memory allocation. */ /* (Always leave room for a final termination indicator.) */ if ( *size == (unsigned int)(max_size - 2)) { max_size += blksize ; *intvp = realloc(*intvp, max_size * sizeint ); if ( *intvp == NULL ) { (void) fprintf(stderr, "Unable to increase size of intervals list.\n"); exit(1); } } j = *size; *size += 1; return j; } /*---------------------------------------------------------+ | Trace a substitute event to the current generation. | +---------------------------------------------------------*/ int find_substitute ( TEVENT *teventp, int subindex ) { int j; j = subindex; while ( teventp[j].generation < (unsigned char)current_tevent_generation ) j = teventp[j].link; return j; } /*---------------------------------------------------------+ | Fix the begin or end date for February if necessary. | +---------------------------------------------------------*/ void fix_february ( int dsbegin, int dsend, int year, int *dbeginp, int *dendp ) { /* Pass back dates as-is in the event there's no change */ *dbeginp = dsbegin; *dendp = dsend; /* If the user has set FEB_KLUGE=YES in the x10config */ /* file, interpret either Feb 28 or Feb 29 as the last day */ /* of that month. */ if ( configp->feb_kluge == YES ) { /* Fix end of February date for the year */ if ( dsbegin == 228 || dsbegin == 229 ) *dbeginp = isleapyear(year) ? 229 : 228 ; if ( dsend == 228 || dsend == 229 ) *dendp = isleapyear(year) ? 229 : 228 ; /* Same thing, if the user has specified reverse dates. */ /* ( parse_sched() will have added 12 months to the end */ /* date, to push it into the following year.) */ if ( dsend == 1428 || dsend == 1429 ) *dendp = isleapyear(year + 1) ? 1429 : 1428; } /* Fix Feb 30th for the year, regardless */ if ( dsbegin == 230 ) *dbeginp = isleapyear(year) ? 229 : 228; if ( dsend == 230 ) *dendp = isleapyear(year) ? 229 : 228; /* Same thing, if user has specified a reversed date range */ if ( dsend == 1430 ) *dendp = isleapyear(year + 1) ? 1429 : 1428; return; } /*---------------------------------------------------------+ | Determine new tevent begin/end dates, filtering the | | date ranges specified in the x10sched file by the | | interval of N days (1-366) beginning today for which | | the CM11a is to be programmed. | +---------------------------------------------------------*/ void resolve_dates ( TEVENT **teventpp, CALEND *calendp, int ndays ) { extern int current_tevent_generation; int j, k, cindx, today, year, day_min, day_max; int offset, minutes, dayminute, delay, sec_adjust, sec_nomin; int dsbegin, dsend, dbegin, dend, beg, end; int notify, valb, vald, size; int day_zero, arrmax, day_adj; int bday[32], eday[32], keep[32], shday[32]; unsigned char dow_bmap, bmap; unsigned int flag; unsigned char breaker[734]; /* 366 + 366 + 2 */ int dst[734]; /* Nothing to do if no tevents */ if ( !(*teventpp) ) return; if ( verbose ) (void)printf("Entering resolve_dates() at generation %d\n", current_tevent_generation); year = calendp->year; today = calendp->yday; day_zero = calendp->day_zero; arrmax = (isleapyear(year) ? 366 : 365) + (isleapyear(year + 1) ? 366 : 365) + 1; j = 0; while ( (*teventpp)[j].line_no > 0 ) { if ( (*teventpp)[j].generation != current_tevent_generation ) { j++; continue; } /* Get dates (which are stored as mmdd = 100*mm + dd) */ dsbegin = (*teventpp)[j].sched_beg; dsend = (*teventpp)[j].sched_end; flag = (*teventpp)[j].flag; dow_bmap = (*teventpp)[j].dow_bmap; notify = min((*teventpp)[j].notify, ndays); sec_adjust = (*teventpp)[j].security; delay = (*teventpp)[j].delay; minutes = (*teventpp)[j].offset; day_adj = 0; /* Adjustment for security mode if necessary */ if ( flag & CLOCK_EVENT ) { minutes = minutes - sec_adjust; if ( minutes < 0 ) { /* Event occurs on the previous day */ minutes += 1440; day_adj = -1; dow_bmap = rrotbmap( dow_bmap ); } if ( minutes > 1439 ) { /* Event occurs on the following day */ minutes -= 1440; day_adj = 1; dow_bmap = lrotbmap( dow_bmap ); } } else { minutes = (*teventpp)[j].offset - sec_adjust; } for ( k = 0; k < 32; k++ ) shday[k] = day_adj; /* Initialize breaker and DST breaker arrays */ for ( k = 0; k < 734; k++ ) { breaker[k] = 0; dst[k] = 0; } /* Determine the day numbering as of Noon */ dayminute = 12 * 60; /* Fix February dates for this year if necessary */ fix_february( dsbegin, dsend, year, &dbegin, &dend ); /* Get day counts from Jan 1 of this year, offsetting by 1 day so we */ /* can keep track of events which are shifted into the previous day. */ beg = 1 + day_count( year, dbegin/100, dbegin % 100, dayminute ) - calendp->jan1day; end = 1 + day_count( year, dend/100, dend % 100, dayminute ) - calendp->jan1day; /* Limit the upper end */ end = min(end, arrmax - 1); if ( beg < 0 || end < 0 || beg > arrmax - 1 || end > arrmax - 1 ) { (void)fprintf(stderr, "Line %2d-%d: resolve_dates(): Begin/End Error 1: Begin = %d, End = %d\n", (*teventpp)[j].line_no, (*teventpp)[j].pos, beg, end); exit(1); } for ( k = beg + day_adj; k <= end + day_adj; k++ ) { breaker[k] = 1; } /* If the user has specified reverse dates, account for the */ /* beginning of year interval for this year carried over */ /* from last year. */ if ( dsend > 1231 ) { fix_february( dsbegin, dsend, year - 1, &dbegin, &dend ); beg = 1 + day_count( year, 1, 1, dayminute ) - calendp->jan1day; end = 1 + day_count( year, (dend - 1200)/100, (dend - 1200) % 100, dayminute ) - calendp->jan1day; for ( k = beg + day_adj; k <= end + day_adj; k++ ) { breaker[k] = 1; } } /* Fix February dates for next year if necessary */ fix_february( dsbegin, dsend, year + 1, &dbegin, &dend ); /* Get day counts for the same dates next year, but still */ /* counting from Jan 1 of this year. */ beg = 1 + day_count( year, dbegin/100 + 12, dbegin % 100, dayminute ) - calendp->jan1day; end = 1 + day_count( year, dend/100 + 12, dend % 100, dayminute ) - calendp->jan1day; /* Limit the upper end */ end = min(end, arrmax - 1); if ( beg < 0 || end < 0 || beg > arrmax - 1 || end > arrmax - 1 ) { (void)fprintf(stderr, "Line %d-%d: resolve_dates(): Begin/End Error 2: Begin = %d, End = %d\n", (*teventpp)[j].line_no, (*teventpp)[j].pos, beg, end); exit(1); } for ( k = beg + day_adj; k <= end + day_adj; k++ ) { breaker[k] = 1; } sec_nomin = (flag & SEC_EVENT) ? SECURITY_OFFSET_ADJUST : 0; /* If the event is a clock event, set up the dst array to */ /* indicate periods of Standard and Daylight Time */ if ( flag & CLOCK_EVENT ) { for ( k = 0; k < 733; k++ ) { dst[k + 1] = time_adjust(k, minutes + delay + sec_nomin, LGL2STD); } dst[0] = dst[arrmax]; } /* Here we create a list of interval begin and end days */ /* for the specified date range and for periods of Standard */ /* and Daylight Time. */ size = 0; valb = breaker[0]; vald = dst[0]; bday[size] = 0; for ( k = 1; k < arrmax; k++ ) { if ( breaker[k] != valb || dst[k] != vald ) { if ( !valb ) { bday[size] = k; valb = breaker[k]; vald = dst[k]; continue; } eday[size] = k - 1; bday[++size] = k; valb = breaker[k]; vald = dst[k]; } } if ( valb ) eday[size++] = k - 1; /* Store the date ranges in the tevent structure */ for ( k = 0; k < size; k++ ) { beg = bday[k]; end = eday[k]; offset = minutes; bmap = dow_bmap; /* Adjust for DST as required */ if ( flag & CLOCK_EVENT ) { offset -= dst[beg]; if ( offset < 0 ) { /* Event occurs on previous day */ offset += 1440; bmap = rrotbmap(bmap); shday[k] = -1; bday[k] = beg -= 1; eday[k] = end -= 1; } if ( offset > 1439 ) { /* Event occurs on following day */ offset -= 1440; bmap = lrotbmap(bmap); shday[k] = 1; bday[k] = beg += 1; eday[k] = end += 1; } } /* Now discard/clip the intervals outside the program days */ /* range and create a new generation of tevents for those */ /* which remain. */ day_min = today + 1; day_max = (today + ndays - 1) + 1; /* For substitute events */ if ( flag & SUBST_EVENT ) { if ( beg <= day_min && end >= day_min ) { beg = end = day_min; flag &= ~TMP_EVENT; } else { continue; } } /* For "expire-dd" events only */ if ( notify >= 0 ) day_min = day_max - notify; /* Discard intervals entirely outside the program days range */ if ( end < day_min || beg > day_max ) { keep[k] = 0; continue; } keep[k] = 1; beg = max( beg, day_min ); end = min( end, day_max ); cindx = spawn_child_tevent( teventpp, j ); /* Store the resolved begin and end days in the new tevent */ /* structure, removing the one-day offset added earlier. */ /* In HEYU mode, the intervals are offset such that today */ /* is day 0 in the CM11a clock. */ (*teventpp)[cindx].resolv_beg = (beg - 1 - day_zero); (*teventpp)[cindx].resolv_end = (end - 1 - day_zero); (*teventpp)[cindx].offset = offset; (*teventpp)[cindx].dow_bmap = bmap; (*teventpp)[cindx].flag = flag & ~(NOCOPY_EVENTS); /* Internal check */ if ( beg - 1 - day_zero < 0 || end < beg ) { (void)fprintf(stderr, "Internal error: Line %d: resolve_dates(): beg = %d, end = %d day_zero = %d\n", (*teventpp)[j].line_no, beg, end, day_zero); } } /* Go back and check the discarded interval so as to note any event */ /* which was "lost" by being shifted outside the program-days range */ /* due to security or DST day adjustments. */ for ( k = 0; k < size; k++ ) { if ( keep[k] || shday[k] == 0 ) continue; if ( shday[k] == -1 && eday[k] == (today + 1) - 1 ) { (*teventpp)[j].lostday = today - day_zero; (*teventpp)[j].flag |= LOST_EVENT; } if ( shday[k] == 1 && bday[k] == (today + 1) + 1 ) { (*teventpp)[j].lostday = today + ndays - 1 - day_zero; (*teventpp)[j].flag |= LOST_EVENT; } } j++ ; } /* Disable all (unused) temporary events in the child generations */ j = 0; while ( (*teventpp)[j].line_no > 0 ) { if ( (*teventpp)[j].generation == (current_tevent_generation + 1) && (*teventpp)[j].flag & TMP_EVENT ) { (*teventpp)[j].flag = NO_EVENT; } j++; } /* Update the generation regardless of whether */ /* or not there are any active tevents. */ (void) update_current_tevent_generation_anyway(); return; } /*---------------------------------------------------------+ | Resolve Timer options DAWNGT, DAWNLT, DUSKGT, DUSKLT | +---------------------------------------------------------*/ void resolve_dawndusk_options ( TEVENT **teventpp, CALEND *calendp ) { extern int current_tevent_generation; unsigned char ddoptions; int j, k, cindx, beg, end, val, size; int breaker[366]; int scode[366], dawn[366], dusk[366]; int intv[32]; int day_zero, opttime, stime; double latitude, longitude; time_t tzone; long julianday; /* See if we need to do anything here */ if ( *teventpp == NULL ) return; ddoptions = 0; j = 0; while ( (*teventpp)[j].line_no > 0 ) { if ( (*teventpp)[j].generation == current_tevent_generation && (*teventpp)[j].flag != NO_EVENT ) { ddoptions |= (*teventpp)[j].ddoptions; } j++; } if ( ddoptions == 0 ) return; /* Load arrays with Dawn and Dusk values */ /* Get geographic location and timezone from */ /* that stored in the global CONFIG structure. */ if ( configp->loc_flag != (LATITUDE | LONGITUDE) ) { (void)fprintf(stderr, "LATITUDE and/or LONGITUDE not specified in %s\n", pathspec(CONFIG_FILE)); exit(1); } latitude = configp->latitude; longitude = configp->longitude; tzone = configp->tzone; /* Calculate sunrise and sunset for 366 days from today's */ /* date and store in arrays. */ /* Get today's Julian Day - the big number, not the day */ /* of the year. */ julianday = daycount2JD(calendp->today); for ( j = 0; j < 366; j++ ) { scode[j] = suntimes( latitude, longitude, tzone, julianday, configp->sunmode, &dawn[j], &dusk[j], NULL, NULL ); julianday++ ; } /* For Arctic/Antarctic regions - Substitute a dawn and/or */ /* dusk time for days when the sun is continually above or */ /* below the horizon, or no sunrise or sunset. */ for ( j = 0; j < 366; j++ ) { if ( scode[j] & UP_ALL_DAY ) { dawn[j] = 1; /* 00:01 */ dusk[j] = 1438; /* 23:58 */ } else if ( scode[j] & DOWN_ALL_DAY ) { dawn[j] = 1438; dusk[j] = 1; } else if ( scode[j] & NO_SUNRISE ) { dawn[j] = 1; } else if ( scode[j] & NO_SUNSET ) { dusk[j] = 1438; } } j = 0; while ( (*teventpp)[j].line_no > 0 ) { if ( (*teventpp)[j].generation != current_tevent_generation || (*teventpp)[j].flag == NO_EVENT ) { j++; continue; } if ( (ddoptions = (*teventpp)[j].ddoptions) == 0 ) { increment_tevent_generation( *teventpp, j ); j++; continue; } for ( k = 0; k < 366; k++ ) breaker[k] = 1; day_zero = calendp->day_zero; if ( ddoptions & DAWNGT ) { opttime = (*teventpp)[j].dawngt; for ( k = 0; k < 366; k++ ) { stime = opttime - time_adjust(k + day_zero, opttime, LGL2STD); if ( dawn[k] <= stime ) breaker[k] = 0; } } if ( ddoptions & DAWNLT ) { opttime = (*teventpp)[j].dawnlt; for ( k = 0; k < 366; k++ ) { stime = opttime - time_adjust(k + day_zero, opttime, LGL2STD); if ( dawn[k] >= stime ) breaker[k] = 0; } } if ( ddoptions & DUSKGT ) { opttime = (*teventpp)[j].duskgt; for ( k = 0; k < 366; k++ ) { stime = opttime - time_adjust(k + day_zero, opttime, LGL2STD); if ( dusk[k] <= stime ) breaker[k] = 0; } } if ( ddoptions & DUSKLT ) { opttime = (*teventpp)[j].dusklt; for ( k = 0; k < 366; k++ ) { stime = opttime - time_adjust(k + day_zero, opttime, LGL2STD); if ( dusk[k] >= stime ) breaker[k] = 0; } } beg = (*teventpp)[j].resolv_beg; end = (*teventpp)[j].resolv_end; val = breaker[beg]; size = 0; intv[size++] = beg; for ( k = beg; k <= end; k++ ) { if ( val != breaker[k] ) { if ( k > beg ) { intv[size++] = k - 1; intv[size++] = k; } val = breaker[k]; } } intv[size++] = end; for ( k = 0; k < size; k += 2 ) { beg = intv[k]; end = intv[k + 1]; cindx = spawn_child_tevent( teventpp, j ); (*teventpp)[cindx].resolv_beg = beg; (*teventpp)[cindx].resolv_end = end; if ( breaker[beg] == 0 ) (*teventpp)[cindx].flag |= CANCEL_EVENT; } j++; } (void) update_current_tevent_generation(); return; } /*---------------------------------------------------------------------+ | Duplicate a macro and its elements, giving it a new label (which | | must be unique), and return the index to it. | +---------------------------------------------------------------------*/ int macro_dupe ( MACRO **macropp, int macindex, unsigned char **elementp, char *newlabel ) { int j, total, eindx; /* Verify the new label is unique */ j = 0; while( (*macropp)[j].line_no > 0 ) { if ( strcmp(newlabel, (*macropp)[j].label) == 0 ) { (void)fprintf(stderr, "Internal error: New macro label is not unique.\n"); exit(1); } j++; } j = macro_index( macropp, newlabel, DERIVED_MACRO ); memcpy((void *)(*macropp + j), (void *)(*macropp + macindex), sizeof(MACRO)); (void)strncpy2((*macropp)[j].label, newlabel, MACRO_LEN); (*macropp)[j].line_no = 9999; (*macropp)[j].modflag |= DELAY_MOD; /* Copy the macro elements */ total = (*macropp)[macindex].total; eindx = macro_element_index(elementp, total); memcpy((void *)(*elementp + eindx), (void *)(*elementp + (*macropp)[macindex].element), total * sizeof(unsigned char)); (*macropp)[j].element = eindx; return j; } /*---------------------------------------------------------------------+ | Special duplicate macro. | | Check to see whether a macro with the original label and zero | | delay already exists. If so, return its index. If not, create a | | duplicate with a '_' prefixed label and zero delay. | +---------------------------------------------------------------------*/ int macro_dupe_special ( MACRO **macropp, int macindex, unsigned char **elementp ) { int j, total, eindx; char *label; char labuf[MACRO_LEN + 2]; label = (*macropp)[macindex].label; labuf[0] = '_'; (void)strncpy2(labuf + 1, label, sizeof(labuf) - 2); labuf[MACRO_LEN] = '\0'; j = 0; while( (*macropp)[j].line_no > 0 ) { if ( (strcmp(label, (*macropp)[j].label) == 0 || strcmp(labuf, (*macropp)[j].label) == 0) && (*macropp)[j].delay == 0 ) { return j; } j++; } j = macro_index( macropp, labuf, DERIVED_MACRO ); memcpy((void *)(*macropp + j), (void *)(*macropp + macindex), sizeof(MACRO)); (void)strncpy2((*macropp)[j].label, labuf, MACRO_LEN); (*macropp)[j].line_no = 9999; (*macropp)[j].delay = 0; (*macropp)[j].modflag |= DELAY_MOD; /* Copy the macro elements */ total = (*macropp)[macindex].total; eindx = macro_element_index(elementp, total); memcpy((void *)(*elementp + eindx), (void *)(*elementp + (*macropp)[macindex].element), total * sizeof(unsigned char)); (*macropp)[j].element = eindx; return j; } /*---------------------------------------------------------------------+ | Clear tevent tree of disabled events by spawning another generation | | without them | +---------------------------------------------------------------------*/ void clear_disabled_events ( TEVENT **teventpp ) { int j; if ( *teventpp == NULL ) return; if ( verbose ) (void)printf("Entering clear_disabled_events() at generation %d\n", current_tevent_generation); j = 0; while ( (*teventpp)[j].line_no > 0 ) { if ( (*teventpp)[j].generation == current_tevent_generation && (*teventpp)[j].flag != NO_EVENT && ((*teventpp)[j].flag & CANCEL_EVENT) == 0 ) { (void) spawn_child_tevent( teventpp, j ); } j++; } (void) update_current_tevent_generation_anyway(); return; } /*---------------------------------------------------------------------+ | Generate a pseudo random variation +/- 30 min for an offset | | but remaining within the range 0-1439 | +---------------------------------------------------------------------*/ int randomize_offset ( int offset ) { /* Balanced pseudo-randoms */ static int harrand[24] = {6,18,19,28,21,12,10,11,23,6,11,4,21, 15,8,22,26,22,24,15,12,25,4,17}; time_t now; int r; long int day; time(&now); day = ((long)now - configp->tzone)/ 86400L; r = (day % 2L) ? harrand[day % 23L] : -harrand[day % 23L]; if ( (offset + r) < 0 || (offset + r) > 1439 ) return (offset - r); return (offset + r); } /*---------------------------------------------------------------------+ | For events where security mode is set, 30 minutes are to be | | subtracted from the event time so the "random" variation will be | | +/- 30 minutes when the CM11a adds the variable time 0-60 minutes | | in this mode. | | However since the CM11a can not wrap times past 23:59 into the next | | day, the adjusted time cannot be earlier than 0:00 or later than | | 22:59. If such would otherwise be the case, we use the delayed | | macro capability of the CM11a to correct the situation. | | | | This function does not actually change the timer offsets, but | | determines the amount of the adjustment, leaving the actual | | adjustment to function resolve_dates(). | +---------------------------------------------------------------------*/ void security_adjust_legal ( TEVENT **teventpp, MACRO **macropp, unsigned char **elementp ) { int j, count, modify; int macro, mindx, submac, cindx, sindx; int good1, good2, offset, delay, event, delta; char *label; unsigned int flag; if ( !(*teventpp) ) return; if ( verbose ) (void)printf("Entering security_adjust_legal() at generation %d\n", current_tevent_generation); /* These are the lower and upper bounds of the region where */ /* the event time can simply be adjusted without problems, i.e. */ /* when after subtracting 30 minutes the resulting time falls */ /* between 0:00 and 22:59 at any time of year. */ good1 = SECURITY_OFFSET_ADJUST + configp->dstminutes; good2 = 1440 - SECURITY_OFFSET_ADJUST - 1; /* See if there are any security clock events that will require */ /* modifying the timer and/or macro. */ j = 0; modify = NO; while ( (*teventpp)[j].line_no > 0 ) { count++; if ( (*teventpp)[j].generation != current_tevent_generation || (flag = (*teventpp)[j].flag) == NO_EVENT || (flag & SEC_EVENT) == 0 || (flag & (DAWN_EVENT | DUSK_EVENT)) != 0 ) { j++; continue; } event = (*teventpp)[j].offset + (*teventpp)[j].delay; if ( event % 1440 >= good1 && event % 1440 <= good2 ) { j++; continue; } else { modify = YES; j++; continue; } } count = j; /* Simple situation - store the adjustment to be made later. */ if ( modify == NO ) { for ( j = 0; j < count; j++ ) { if ( (*teventpp)[j].generation != current_tevent_generation || (*teventpp)[j].flag == NO_EVENT ) { continue; } if ( (*teventpp)[j].flag & SEC_EVENT ) (*teventpp)[j].security = SECURITY_OFFSET_ADJUST; else (*teventpp)[j].security = 0; } return ; } /* More complex case - create a new timer and possibly a delayed */ /* macro if necessary. */ j = 0; while ( (*teventpp)[j].line_no > 0 ) { if ( (*teventpp)[j].generation != current_tevent_generation || (*teventpp)[j].flag == NO_EVENT ) { j++; continue; } cindx = spawn_child_tevent( teventpp, j ); flag = (*teventpp)[cindx].flag; offset = (*teventpp)[cindx].offset; delay = (*teventpp)[cindx].delay; event = offset + delay; if ( !(flag & SEC_EVENT) ) { (*teventpp)[cindx].security = 0; j++; continue; } if ( flag & (DAWN_EVENT | DUSK_EVENT) || (event >= good1 && event <= good2) ) { (*teventpp)[cindx].security = SECURITY_OFFSET_ADJUST; j++; continue; } /* Create a new macro duplicating the original */ macro = (*teventpp)[j].macro; label = unique_macro_name( ¯o, 1, *macropp, SECUR_MAC_PREFIX ); mindx = macro_dupe ( macropp, macro, elementp, label ); (*macropp)[mindx].line_no = 9999; (*macropp)[mindx].delay = delay = 0; (*macropp)[mindx].modflag |= DELAY_MOD; (*teventpp)[cindx].offset = offset = event; (*teventpp)[cindx].macro = mindx; (*teventpp)[cindx].flag |= (PRT_EVENT); (*teventpp)[cindx].print += 1; (*teventpp)[cindx].plink = j; if ( offset < good1 ) { delta = good1 - offset; if ( delay >= delta ) { /* Delay can be decreased, keeping the event in the same day */ delay -= delta; (*teventpp)[cindx].security = SECURITY_OFFSET_ADJUST - delta; } else { /* Delay must be increased, shifting event into the previous day */ delta = 1440 + offset - good2; delay += delta; (*teventpp)[cindx].security = SECURITY_OFFSET_ADJUST + delta; /* Create a non-security substitute event for use as first day if needed */ sindx = spawn_child_tevent( teventpp, j ); submac = macro_dupe_special(macropp, (*teventpp)[j].macro, elementp); event = (*teventpp)[j].offset + (*teventpp)[j].delay; (*teventpp)[sindx].offset = randomize_offset(event % 1440); (*teventpp)[sindx].delay = 0; (*teventpp)[sindx].macro = submac; (*teventpp)[sindx].flag |= (SUBST_EVENT | TMP_EVENT); (*teventpp)[sindx].plink = j; if ( submac != (*teventpp)[j].macro ) { (*teventpp)[sindx].flag |= PRT_EVENT; (*teventpp)[sindx].print += 1; } (*teventpp)[sindx].flag &= ~SEC_EVENT; } } else { /* Offset > good2 */ delta = offset - good2; if ( delay < 240 - delta ) { /* Delay can be increased - keeping event in the same day */ delay += delta; (*teventpp)[cindx].security = SECURITY_OFFSET_ADJUST + delta; } else { /* Delay can only be decreased, shifting event into the following day */ delta = 1440 + good1 - offset; delay -= delta; (*teventpp)[cindx].security = SECURITY_OFFSET_ADJUST - delta; /* Create a non-security substitute event for use as last day if needed */ sindx = spawn_child_tevent( teventpp, j ); submac = macro_dupe_special(macropp, (*teventpp)[j].macro, elementp); event = (*teventpp)[j].offset + (*teventpp)[j].delay; (*teventpp)[sindx].offset = randomize_offset(event % 1440); (*teventpp)[sindx].delay = 0; (*teventpp)[sindx].macro = submac; (*teventpp)[sindx].flag |= (SUBST_EVENT | TMP_EVENT); (*teventpp)[sindx].flag &= ~SEC_EVENT; } } (*macropp)[mindx].delay = delay; (*teventpp)[cindx].delay = delay; j++; } (void)update_current_tevent_generation(); return; } /*---------------------------------------------------------------------+ | Warn of duplicate timer in schedule file | +---------------------------------------------------------------------*/ int warn_duplicate_timer ( TIMER *timerp ) { int j, k, count; int *arrp; static int sizint = sizeof(int); if ( !timerp ) return 0; j = 0; while ( timerp[j].line_no > 0 ) j++; count = j; if ( (arrp = calloc(count, sizint)) == NULL ) { fprintf(stderr, "warn_duplicate_timer() - Unable to allocate memory.\n"); exit(1); } for ( j = 0; j < count; j++ ) arrp[j] = 1; for ( j = 0; j < count; j++ ) { if ( arrp[j] == 0 ) continue; for ( k = j + 1; k < count; k++ ) { if ( arrp[k] == 0 ) continue; if ( timerp[j].generation == timerp[k].generation && timerp[j].dow_bmap == timerp[k].dow_bmap && timerp[j].sched_beg == timerp[k].sched_beg && timerp[j].sched_end == timerp[k].sched_end && timerp[j].offset_start == timerp[k].offset_start && timerp[j].offset_stop == timerp[k].offset_stop && timerp[j].flag_start == timerp[k].flag_start && timerp[j].flag_stop == timerp[k].flag_stop && timerp[j].notify == timerp[k].notify && timerp[j].macro_start == timerp[k].macro_start && timerp[j].macro_stop == timerp[k].macro_stop ) { fprintf(stderr, "Lines %02d,%02d: Warning - Duplicate timer\n", timerp[j].line_no, timerp[k].line_no); arrp[k] = 0; } } } free(arrp); return 0; } /*---------------------------------------------------------------------+ | Warn of duplicate timed event in input timers. | +---------------------------------------------------------------------*/ int warn_duplicate_tevent ( TEVENT *teventp ) { int j, k, count; int *arrp; static int sizint = sizeof(int); if ( !teventp ) return 0; j = 0; while ( teventp[j].line_no > 0 ) j++; count = j; if ( (arrp = calloc(count, sizint)) == NULL ) { fprintf(stderr, "warn_duplicate_tevent() - Unable to allocate memory.\n"); exit(1); } for ( j = 0; j < count; j++ ) arrp[j] = 1; for ( j = 0; j < count; j++ ) { if ( arrp[j] == 0 ) continue; for ( k = j + 1; k < count; k++ ) { if ( arrp[k] == 0 ) continue; if ( teventp[j].generation == teventp[k].generation && teventp[j].dow_bmap == teventp[k].dow_bmap && teventp[j].sched_beg == teventp[k].sched_beg && teventp[j].sched_end == teventp[k].sched_end && teventp[j].offset == teventp[k].offset && teventp[j].flag == teventp[k].flag && teventp[j].notify == teventp[k].notify && teventp[j].macro == teventp[k].macro ) { fprintf(stderr, "Lines %02d,%02d: Warning - Duplicate Timed Event\n", teventp[j].line_no, teventp[k].line_no); arrp[k] = 0; } } } free(arrp); return 0; } /*---------------------------------------------------------------------+ | Warn of duplicate trigger initiator in schedule file | +---------------------------------------------------------------------*/ int warn_duplicate_trigger ( TRIGGER *triggerp ) { int j, k, count; int *arrp; static char *offon[] = {"off", "on"}; static int sizint = sizeof(int); if ( !triggerp ) return 0; j = 0; while ( triggerp[j].line_no > 0 ) j++; count = j; if ( (arrp = calloc(count, sizint)) == NULL ) { fprintf(stderr, "warn_duplicate_trigger() - Unable to allocate memory.\n"); exit(1); } for ( j = 0; j < count; j++ ) arrp[j] = 1; for ( j = 0; j < count; j++ ) { if ( arrp[j] == 0 ) continue; for ( k = j + 1; k < count; k++ ) { if ( arrp[k] == 0 ) continue; if ( triggerp[j].housecode == triggerp[k].housecode && triggerp[j].unitcode == triggerp[k].unitcode && triggerp[j].command == triggerp[k].command ) { fprintf(stderr, "Lines %02d,%02d: Warning - Duplicate trigger '%c%d %s'\n", triggerp[j].line_no, triggerp[k].line_no, code2hc(triggerp[j].housecode), code2unit(triggerp[j].unitcode), offon[triggerp[j].command] ); arrp[k] = 0; } } } free(arrp); return 0; } /*---------------------------------------------------------------------+ | Parse schedule file. Store parameters for timers, triggers, and | | macros in their respective structure arrays. | +---------------------------------------------------------------------*/ int parse_sched ( FILE *fd_sched, TIMER **timerpp, TRIGGER **triggerpp, MACRO **macropp, unsigned char **elementp ) { extern int line_no, current_timer_generation, timer_generation_delta; int j; int index; int err, errors; char buffer[LINE_LEN]; char cmdbuf[256]; char cmdbufl[256]; char *sp, *tail; /* Make two passes through the schedule file, the first */ /* concerned only with configuration overrides. */ line_no = 0; errors = 0; while ( fgets(buffer, LINE_LEN, fd_sched) != NULL ) { line_no++ ; /* Make sure buffer is properly terminated */ buffer[LINE_LEN - 1] = '\0'; /* Get rid of comment strings */ if ( (sp = strchr(buffer, '#')) != NULL ) *sp = '\0'; /* Verify the entire line fits into the buffer */ if ( strlen(buffer) == (LINE_LEN - 2) ) { (void) fprintf(stderr, "Line %02d: Line too long.\n", line_no); if ( ++errors > MAX_ERRORS ) return errors; continue; } /* Remove leading and trailing whitespace */ (void) strtrim(buffer); if ( *buffer == '\0') continue; tail = buffer; /* Get the first token on the line. */ (void) get_token(cmdbuf, &tail, " \t", 255); strncpy2(cmdbufl, cmdbuf, sizeof(cmdbufl) - 1); strlower(cmdbufl); if ( strcmp(cmdbufl, "config") == 0 ) { err = parse_config_tail(tail, SRC_SCHED); if ( err || *error_message() != '\0' ) { fprintf(stderr, "Line %02d: %s\n", line_no, error_message()); clear_error_message(); } errors += err; if ( errors > MAX_ERRORS ) return errors; } } finalize_config(CONFIG_INIT); /* Second pass */ rewind(fd_sched); line_no = 0; while ( fgets(buffer, LINE_LEN, fd_sched) != NULL ) { line_no++ ; /* Make sure buffer is properly terminated */ buffer[LINE_LEN - 1] = '\0'; /* Get rid of comment strings */ if ( (sp = strchr(buffer, '#')) != NULL ) *sp = '\0'; /* Verify the entire line fit into the buffer */ if ( strlen(buffer) == (LINE_LEN - 2) ) { (void) fprintf(stderr, "Line %02d: Line too long.\n", line_no); if ( ++errors > MAX_ERRORS ) return errors; continue; } /* Remove leading and trailing whitespace */ (void) strtrim(buffer); if ( *buffer == '\0') continue; tail = buffer; /* Get the first token on the line. */ (void) get_token(cmdbuf, &tail, " \t", 255); strncpy2(cmdbufl, cmdbuf, sizeof(cmdbufl) - 1); strlower(cmdbufl); /* Pass the tail of the command line to the appropriate */ /* parsing function. */ if ( strcmp(cmdbufl, "timer") == 0 ) { errors += parse_timer(timerpp, macropp, tail); } else if ( strcmp(cmdbufl, "trigger") == 0 ) { errors += parse_trigger(triggerpp, macropp, tail); } else if ( strcmp(cmdbufl, "macro") == 0 ) { errors += parse_macro(macropp, elementp, tail); } else if ( strcmp(cmdbufl, "config") == 0 ) { /* Ignore config lines in this pass */ continue; } else if ( strcmp(cmdbufl, "section") == 0 ) { continue; } else { (void) fprintf(stderr, "Line %02d: Unrecognized keyword '%s' in schedule file.\n", line_no, cmdbuf); errors++ ; } if ( errors > MAX_ERRORS ) return errors; } /* Verify that all macros referenced by timers and triggers have */ /* been defined, and that macros have not been multiply defined. */ /* Warn if a macro is unused by timers or triggers. */ /* Also, link the timers together in a linked list and set the */ /* generation as generation 0. */ /* Also store the macro delay times in the timer. */ current_timer_generation = 0; timer_generation_delta = 0; if (*timerpp) { j = 0; while ( (*timerpp)[j].line_no > 0 ) { (*timerpp)[j].link = j + 1; (*timerpp)[j].generation = 0; index = (*timerpp)[j].macro_start ; if ( !((*macropp)[index].refer & MACRO_PARSER) ) { (void) fprintf(stderr, "Line %02d: Macro '%s' is not defined.\n", (*timerpp)[j].line_no, (*macropp)[index].label ); if ( ++errors > MAX_ERRORS ) return errors; } (*timerpp)[j].delay_start = (*macropp)[index].delay; index = (*timerpp)[j].macro_stop ; if ( !((*macropp)[index].refer & MACRO_PARSER) ) { (void) fprintf(stderr, "Line %02d: Macro '%s' is not defined.\n", (*timerpp)[j].line_no, (*macropp)[index].label ); if ( ++errors > MAX_ERRORS ) return errors; } (*timerpp)[j].delay_stop = (*macropp)[index].delay; j++; } /* Terminate the linked list with a -1 */ (*timerpp)[j - 1].link = -1; warn_duplicate_timer(*timerpp); } if (*triggerpp) { j = 0; while ( (*triggerpp)[j].line_no > 0 ) { index = (*triggerpp)[j].macro ; if ( !((*macropp)[index].refer & MACRO_PARSER) ) { (void) fprintf(stderr, "Line %02d: Macro '%s' is not defined.\n", (*triggerpp)[j].line_no, (*macropp)[index].label ); errors++; } if ( errors > MAX_ERRORS ) return errors; j++; } if ( configp->macterm == YES ) warn_duplicate_trigger(*triggerpp); } if (*macropp) { /* Start with j = 1, ignoring the null macro */ j = 1; while ( (*macropp)[j].line_no > 0 ) { if ( (*macropp)[j].refer & MACRO_DUPLICATE ) { (void) fprintf(stderr, "Line %02d: Macro '%s' is multiply defined.\n", (*macropp)[j].line_no, (*macropp)[j].label ); if ( ++errors > MAX_ERRORS ) return errors; } if ( !((*macropp)[j].refer & (TIMER_PARSER | TRIGGER_PARSER)) ) { (void) fprintf(stderr, "Line %02d: Warning: Macro '%s' is not called by a timer or trigger and will be ignored.\n", (*macropp)[j].line_no, (*macropp)[j].label ); } j++; } } line_no = 0; return errors; } /*---------------------------------------------------------------------+ | Parse the tail of a timer command. | +---------------------------------------------------------------------*/ int parse_timer_old( TIMER **timerpp, MACRO **macropp, char *tail) { extern int line_no; int mdays[] = {0,31,30,31,30,31,30,31,31,30,31,30,31}; unsigned int flag; int count; int time; int index, macindex; int month, day; char weekdays[16]; char daterange[16]; char datebegin[16], dateend[16]; char tstart[20], tstop[20]; char macstart[40], macstop[40]; count = sscanf(tail, "%10s %15s %16s %16s %37s %37s", weekdays, daterange, tstart, tstop, macstart, macstop); if ( count != 6 ) { (void) fprintf(stderr, "Line %02d: Invalid timer command.\n", line_no); return 1; } /* If both start and stop macros are null macros, just return */ if ( !strcmp(macstart, "null") && !strcmp(macstop, "null") ) return 0; /* Get the index in array timerp to store the timer parameters. */ index = timer_index( timerpp ); (*timerpp)[index].line_no = line_no; (*timerpp)[index].line1 = (*timerpp)[index].line2 = line_no; (*timerpp)[index].pos1 = 1; (*timerpp)[index].pos2 = 2; if ( ((*timerpp)[index].dow_bmap = dow2bmap(weekdays)) == 0xff ) { (void) fprintf(stderr, "Line %02d: Timer contains invalid weekdays string.\n", line_no); return 1; } /* Determine whether the command contains clock or dawn or dusk relative */ /* events and get time offset from either midnight or dawn/dusk. */ if ( (flag = parse_time_token(tstart, &time)) == INVALID_EVENT ) { (void) fprintf(stderr, "Line %d: Timer contains invalid start time token.\n", line_no); return 1; } else { (*timerpp)[index].offset_start = time; (*timerpp)[index].flag_start = flag ; } if ( (flag = parse_time_token(tstop, &time)) == INVALID_EVENT ) { (void) fprintf(stderr, "Line %d: Timer contains invalid stop time token.\n", line_no); return 1; } else { (*timerpp)[index].offset_stop = time; (*timerpp)[index].flag_stop = flag ; } (void) strtrim(daterange); /* Check for the special case "expire-ddd" */ (*timerpp)[index].notify = -1; if ( !strncmp(daterange, "expire", 6) ) { /* Fill in full year, store days, and adjust later */ (void)strncpy2(datebegin, "01/01", sizeof(datebegin) - 1); (void)strncpy2(dateend, "12/31", sizeof(dateend) - 1); if ( sscanf(daterange, "expire-%d", &day) == 1 ) (*timerpp)[index].notify = day; else (*timerpp)[index].notify = 0; } /* Otherwise look for the standard "mm/dd-mm/dd" string */ else if ( sscanf(daterange, "%5s-%5s", datebegin, dateend) != 2 ) { (void)fprintf(stderr, "Line %02d: Invalid timer date range %s.\n", line_no, daterange); return 1; } if ( sscanf(datebegin, "%d/%d", &month, &day) != 2 || month < 1 || month > 12 || day < 1 || day > mdays[month] ) { (void)fprintf(stderr, "Line %02d: Invalid timer mm/dd begin date.\n", line_no); return 1; } (*timerpp)[index].sched_beg = 100*month + day; /* Save as mmdd */ if ( sscanf(dateend, "%d/%d", &month, &day) != 2 || month < 1 || month > 12 || day < 1 || day > mdays[month] ) { (void)fprintf(stderr, "Line %02d: Invalid timer mm/dd end date.\n", line_no); return 1; } (*timerpp)[index].sched_end = 100*month + day; /* Save as mmdd */ /* If the user has specified reversed dates, move the end date */ /* into the following year by adding 12 months. */ if ( (*timerpp)[index].sched_end < (*timerpp)[index].sched_beg ) (*timerpp)[index].sched_end += 1200; if ( (int)strlen(macstart) > MACRO_LEN || (int)strlen(macstop) > MACRO_LEN ) { (void) fprintf(stderr, "Line %02d: Macro name exceeds %d characters.\n", line_no, MACRO_LEN); return 1; } /* Register the macro labels and store the indexes thereto. */ /* If the macro is the null macro, correct the timer flag. */ (*timerpp)[index].macro_start = macindex = macro_index(macropp, macstart, TIMER_PARSER); if ( macindex == NULL_MACRO_INDEX ) (*timerpp)[index].flag_start = NO_EVENT; (*timerpp)[index].macro_stop = macindex = macro_index(macropp, macstop, TIMER_PARSER); if ( macindex == NULL_MACRO_INDEX ) (*timerpp)[index].flag_stop = NO_EVENT; /* For convenience, store the combined start and stop flags */ (*timerpp)[index].flag_combined = (*timerpp)[index].flag_start | (*timerpp)[index].flag_stop; return 0; } /*---------------------------------------------------------------------+ | Parse the tail of a timer command, with DAWNLT,...etc options. | +---------------------------------------------------------------------*/ int parse_timer ( TIMER **timerpp, MACRO **macropp, char *tail) { extern int line_no; int mdays[] = {0,31,30,31,30,31,30,31,31,30,31,30,31}; unsigned int flag; int time; int index, macindex; int month, day; int j, tokc; char **tokv = NULL; char datebegin[16], dateend[16]; char minibuf[32]; tokenize(tail, " \t\n", &tokc, &tokv); /* Required tokens are: [0] Weekday map [1] Date range [2] Start time [3] Stop time [4] Start macro [5] Stop macro Optional tokens: Keyword 'option' and keywords for supported options. */ if ( tokc < 6 ) { fprintf(stderr, "Line %02d: Invalid timer command.\n", line_no); free(tokv); return 1; } /* If both start and stop macros are null macros, just return */ if ( !strcmp(tokv[4], "null") && !strcmp(tokv[5], "null") ) { free(tokv); return 0; } /* Get the index in array timerp to store the timer parameters. */ index = timer_index( timerpp ); (*timerpp)[index].line_no = line_no; (*timerpp)[index].line1 = (*timerpp)[index].line2 = line_no; (*timerpp)[index].pos1 = 1; (*timerpp)[index].pos2 = 2; if ( ((*timerpp)[index].dow_bmap = dow2bmap(tokv[0])) == 0xff ) { (void) fprintf(stderr, "Line %02d: Timer contains invalid weekdays string.\n", line_no); free(tokv); return 1; } /* Determine whether the command contains clock or dawn or dusk relative */ /* events and get time offset from either midnight or dawn/dusk. */ if ( (flag = parse_time_token(tokv[2], &time)) == INVALID_EVENT ) { (void) fprintf(stderr, "Line %d: Timer contains invalid start time token.\n", line_no); free(tokv); return 1; } else { (*timerpp)[index].offset_start = time; (*timerpp)[index].flag_start = flag ; } if ( (flag = parse_time_token(tokv[3], &time)) == INVALID_EVENT ) { (void) fprintf(stderr, "Line %d: Timer contains invalid stop time token.\n", line_no); free(tokv); return 1; } else { (*timerpp)[index].offset_stop = time; (*timerpp)[index].flag_stop = flag ; } (void) strtrim(tokv[1]); /* Check for the special case "expire-ddd" */ (*timerpp)[index].notify = -1; if ( !strncmp(tokv[1], "expire", 6) ) { /* Fill in full year, store days, and adjust later */ (void)strncpy2(datebegin, "01/01", sizeof(datebegin) - 1); (void)strncpy2(dateend, "12/31", sizeof(dateend) - 1); if ( sscanf(tokv[1], "expire-%d", &day) == 1 ) (*timerpp)[index].notify = day; else (*timerpp)[index].notify = 0; } /* Otherwise look for the standard "mm/dd-mm/dd" string */ else if ( sscanf(tokv[1], "%5s-%5s", datebegin, dateend) != 2 ) { (void)fprintf(stderr, "Line %02d: Invalid timer date range %s.\n", line_no, tokv[1]); free(tokv); return 1; } if ( sscanf(datebegin, "%d/%d", &month, &day) != 2 || month < 1 || month > 12 || day < 1 || day > mdays[month] ) { (void)fprintf(stderr, "Line %02d: Invalid timer mm/dd begin date.\n", line_no); free(tokv); return 1; } (*timerpp)[index].sched_beg = 100*month + day; /* Save as mmdd */ if ( sscanf(dateend, "%d/%d", &month, &day) != 2 || month < 1 || month > 12 || day < 1 || day > mdays[month] ) { (void)fprintf(stderr, "Line %02d: Invalid timer mm/dd end date.\n", line_no); free(tokv); return 1; } (*timerpp)[index].sched_end = 100*month + day; /* Save as mmdd */ /* If the user has specified reversed dates, move the end date */ /* into the following year by adding 12 months. */ if ( (*timerpp)[index].sched_end < (*timerpp)[index].sched_beg ) (*timerpp)[index].sched_end += 1200; if ( (int)strlen(tokv[4]) > MACRO_LEN || (int)strlen(tokv[5]) > MACRO_LEN ) { (void) fprintf(stderr, "Line %02d: Macro name exceeds %d characters.\n", line_no, MACRO_LEN); free(tokv); return 1; } /* Register the macro labels and store the indexes thereto. */ /* If the macro is the null macro, correct the timer flag. */ (*timerpp)[index].macro_start = macindex = macro_index(macropp, tokv[4], TIMER_PARSER); if ( macindex == NULL_MACRO_INDEX ) (*timerpp)[index].flag_start = NO_EVENT; (*timerpp)[index].macro_stop = macindex = macro_index(macropp, tokv[5], TIMER_PARSER); if ( macindex == NULL_MACRO_INDEX ) (*timerpp)[index].flag_stop = NO_EVENT; /* For convenience, store the combined start and stop flags */ (*timerpp)[index].flag_combined = (*timerpp)[index].flag_start | (*timerpp)[index].flag_stop; /* Look for timer Dawn/Dusk options */ (*timerpp)[index].ddoptions = 0; (*timerpp)[index].dawnlt = -1; (*timerpp)[index].dawngt = -1; (*timerpp)[index].dusklt = -1; (*timerpp)[index].duskgt = -1; for ( j = 6; j < tokc; j++ ) { strncpy2(minibuf, tokv[j], 31); strupper(minibuf); if ( strcmp("DAWNGT", minibuf) == 0 ) { if ( (*timerpp)[index].ddoptions & DAWNGT ) { fprintf(stderr, "Line %02d: Duplicate DAWNGT option in Timer.\n", line_no); free(tokv); return 1; } if ( tokc < j + 2 ) { fprintf(stderr, "Line %02d: Missing hh:mm following DAWNGT option\n", line_no); free(tokv); return 1; } if ( (flag = parse_time_token(tokv[j + 1], &time)) != CLOCK_EVENT ) { fprintf(stderr, "Line %d: Invalid DAWNGT time token '%s'.\n", line_no, tokv[j + 1]); free(tokv); return 1; } (*timerpp)[index].dawngt = time; (*timerpp)[index].ddoptions |= DAWNGT; j++; } else if ( strcmp("DAWNLT", minibuf) == 0 ) { if ( (*timerpp)[index].ddoptions & DAWNLT ) { fprintf(stderr, "Line %02d: Duplicate DAWNLT option in Timer.\n", line_no); free(tokv); return 1; } if ( tokc < j + 2 ) { fprintf(stderr, "Line %02d: Missing hh:mm following DAWNLT option\n", line_no); free(tokv); return 1; } if ( (flag = parse_time_token(tokv[j + 1], &time)) != CLOCK_EVENT ) { fprintf(stderr, "Line %d: Invalid DAWNLT time token '%s'.\n", line_no, tokv[j + 1]); free(tokv); return 1; } (*timerpp)[index].dawnlt = time; (*timerpp)[index].ddoptions |= DAWNLT; j++; } else if ( strcmp("DUSKGT", minibuf) == 0 ) { if ( (*timerpp)[index].ddoptions & DUSKGT ) { fprintf(stderr, "Line %02d: Duplicate DUSKGT option in Timer.\n", line_no); free(tokv); return 1; } if ( tokc < j + 2 ) { fprintf(stderr, "Line %02d: Missing hh:mm following DUSKGT option\n", line_no); free(tokv); return 1; } if ( (flag = parse_time_token(tokv[j + 1], &time)) != CLOCK_EVENT ) { fprintf(stderr, "Line %d: Invalid DUSKGT hh:mm token '%s'.\n", line_no, tokv[j + 1]); free(tokv); return 1; } (*timerpp)[index].duskgt = time; (*timerpp)[index].ddoptions |= DUSKGT; j++; } else if ( strcmp("DUSKLT", minibuf) == 0 ) { if ( (*timerpp)[index].ddoptions & DUSKLT ) { fprintf(stderr, "Line %02d: Duplicate DUSKLT option in Timer.\n", line_no); free(tokv); return 1; } if ( tokc < j + 2 ) { fprintf(stderr, "Line %02d: Missing hh:mm following DUSKLT option\n", line_no); free(tokv); return 1; } if ( (flag = parse_time_token(tokv[j + 1], &time)) != CLOCK_EVENT ) { fprintf(stderr, "Line %d: Invalid DUSKLT hh:mm token '%s'.\n", line_no, tokv[j + 1]); free(tokv); return 1; } (*timerpp)[index].dusklt = time; (*timerpp)[index].ddoptions |= DUSKLT; j++; } else { fprintf(stderr, "Line %02d: Invalid Timer option '%s'.\n", line_no, tokv[j]); free(tokv); return 1; } } free(tokv); return 0; } /*---------------------------------------------------------------------+ | Parse the tail of a trigger command. | +---------------------------------------------------------------------*/ int parse_trigger( TRIGGER **triggerpp, MACRO **macropp, char *tail) { extern int line_no; int index, macindex; char hc; int tokc; char **tokv; unsigned int bitmap, aflags; tokenize(tail, " \t\n", &tokc, &tokv); /* Expected tokens are: */ /* tokv[0] = Housecode|Unit */ /* tokv[1] = "on" or "off" */ /* tokv[2] = macro label */ if ( tokc < 3 ) { (void) fprintf(stderr, "Line %02d: Invalid trigger (too few fields).\n", line_no); free(tokv); return 1; } if ( tokc > 3 ) { (void) fprintf(stderr, "Line %02d: Invalid trigger (too many fields).\n", line_no); free(tokv); return 1; } index = trigger_index( triggerpp ); (*triggerpp)[index].line_no = line_no; aflags = parse_addr(tokv[0], &hc, &bitmap); if ( !(aflags & A_VALID) ) { fprintf(stderr, "Line %02d: %s\n", line_no, error_message()); clear_error_message(); free(tokv); return 1; } if ( !(aflags & A_HCODE) || !(aflags & A_BMAP) || aflags & (A_PLUS | A_MINUS | A_DUMMY | A_MULT) ) { fprintf(stderr, "Line %02d: Invalid trigger Housecode|Unit '%s'\n", line_no, tokv[0]); free(tokv); return 1; } (*triggerpp)[index].housecode = hc2code(hc); (*triggerpp)[index].unitcode = single_bmap_unit(bitmap); if ( strcmp(tokv[1], "on") == 0 ) (*triggerpp)[index].command = TRIGGER_ON; else if ( strcmp(tokv[1], "off") == 0 ) (*triggerpp)[index].command = TRIGGER_OFF; else { (void) fprintf(stderr, "Line %02d: Trigger command not 'on' or 'off'.\n", line_no); free(tokv); return 1; } if ( (int)strlen(tokv[2]) > MACRO_LEN ) { (void) fprintf(stderr, "Line %02d: Macro label exceeds %d characters.\n", line_no, MACRO_LEN); free(tokv); return 1; } if ( !strcmp(tokv[2], "null") ) { (void) fprintf(stderr, "Line %02d: Macro label \"null\" is not valid in a trigger.\n", line_no); free(tokv); return 1; } /* Register the macro label and store the index thereto */ macindex = macro_index(macropp, tokv[2], TRIGGER_PARSER); (*triggerpp)[index].macro = macindex; /* Cross-reference the macro with the trigger */ (*macropp)[macindex].trig = index; free(tokv); return 0; } /*---------------------------------------------------------------------+ | Parse the tail of a macro command in the upload schedule. | +---------------------------------------------------------------------*/ int parse_macro ( MACRO **macropp, unsigned char **elementp, char *tail) { extern int line_no; extern char *typename[]; int i, j, k, m, n, index, loc, retcode = 0; int delay, nelem, nbytes; char buffer[1024]; unsigned char elembuff[256]; int macc, cmdc, tokc, scmdc; char **macv, **cmdv, **scmdv, **tokv; char *sp, *scp, *tailsave; SCENE *scenep; scenep = configp->scenep; /* First token is macro label */ if ( *(get_token(buffer, &tail, " \t", 255)) == '\0') { fprintf(stderr, "Line %02d: Too few fields.\n", line_no); return 1; } if ( (int)strlen(buffer) > MACRO_LEN ) { fprintf(stderr, "Line %02d: Macro label '%s' exceeds %d characters.\n", line_no, buffer, MACRO_LEN); return 1; } if ( !strcmp(buffer, "null") ) { fprintf(stderr, "Line %02d: null macro may not be re-defined.\n", line_no); return 1; } if ( strchr("_+-$", *buffer) != NULL ) { fprintf(stderr, "Line %02d: Macro labels beginning with '_', '+', '-', '$' are reserved.\n", line_no); return 1; } /* Get index in array of MACRO structures */ index = macro_index(macropp, buffer, MACRO_PARSER); (*macropp)[index].line_no = line_no; /* Get the index into the element array where we will start to */ /* store the elements. */ (*macropp)[index].element = macro_element_index( elementp, 0 ); /* Second token is delay time (optional if zero) */ tailsave = tail; if ( *(get_token(buffer, &tail, " \t", 255)) == '\0') { fprintf(stderr, "Line %02d: Too few fields.\n", line_no); return 1; } delay = (int)strtol(buffer, &sp, 10); if ( !strchr(" \t", *sp) ) { /* Non-numeric, so use default zero delay and backup tail pointer */ delay = 0; tail = tailsave; } if ( delay < 0 || delay > 240 ) { fprintf(stderr, "Line %02d: Macro delay is outside range 0 - 240 minutes.\n", line_no); return 1; } (*macropp)[index].delay = (unsigned char)delay; /* Divide the remainder of the tail into ';' delimited x10 commands */ tokenize(tail, ";", &macc, &macv); for ( i = 0; i < macc; i++ ) { strtrim(macv[i]); if ( *macv[i] == '\0' ) continue; /* Divide commands into tokens */ tokenize(macv[i], " \t", &cmdc, &cmdv); /* See if it's a user-defined scene from the config file */ if ( (k = lookup_scene(scenep, cmdv[0])) >= 0 ) { /* Check if needed number of parameters are supplied */ if ( cmdc < scenep[k].nparms + 1 ) { fprintf(stderr, "Line %02d: (Config %02d: %s '%s'): Too few parameters - %d required.\n", line_no, scenep[k].line_no, typename[scenep[k].type], scenep[k].label, scenep[k].nparms); free(cmdv); free(macv); return 1; } else if ( cmdc > scenep[k].nparms + 1 ) { fprintf(stderr, "Line %02d: (Config %02d: %s '%s'): Too many parameters - only %d accepted.\n", line_no, scenep[k].line_no, typename[scenep[k].type], scenep[k].label, scenep[k].nparms); free(cmdv); free(macv); return 1; } /* Tokenize individual commands in scene */ scp = strdup(scenep[k].body); tokenize(scp, ";", &scmdc, &scmdv); for ( m = 0; m < scmdc; m++ ) { strtrim(scmdv[m]); if ( *scmdv[m] == '\0' ) continue; /* Break up commands into tokens */ tokenize(scmdv[m], " \t", &tokc, &tokv); /* Substitute for any dummy parameters */ replace_dummy_parms(tokc, &tokv, cmdv); retcode = macro_command(tokc, tokv, &nelem, &nbytes, elembuff); /* Free each member of tokv, then tokv itself */ for ( j = 0; j < tokc; j++ ) free(tokv[j]); free(tokv); if ( retcode != 0 ) { /* Display stacked error message from macro_command() */ fprintf(stderr, "Line %02d: (Config %02d: %s '%s'): %s.\n", line_no, scenep[k].line_no, typename[scenep[k].type], scenep[k].label, error_message()); clear_error_message(); free(scmdv); free(scp); free(cmdv); free(macv); return 1; } else if ( *error_message() ) { /* Display any warning message */ fprintf(stderr, "Line %02d: %s.\n", line_no, error_message()); clear_error_message(); } (*macropp)[index].nelem += nelem; (*macropp)[index].total += nbytes; /* Make space for the element and copy it there */ loc = macro_element_index( elementp, nbytes ); for ( n = 0; n < nbytes; n++ ) { (*elementp)[loc + n] = elembuff[n]; } } free(scmdv); free(scp); } else { /* It's an individual command */ retcode = macro_command(cmdc, cmdv, &nelem, &nbytes, elembuff); free(cmdv); if ( retcode != 0 ) { /* Display stacked error message from macro_command() */ fprintf(stderr, "Line %02d: %s.\n", line_no, error_message()); free(macv); return 1; } (*macropp)[index].nelem += nelem; (*macropp)[index].total += nbytes; /* Make space for the element and copy it there */ loc = macro_element_index( elementp, nbytes ); for ( n = 0; n < nbytes; n++ ) (*elementp)[loc + n] = elembuff[n]; } } free(macv); /* Check that the macro has at least one element */ if ( (*macropp)[index].nelem < 1 ) { fprintf(stderr, "Line %02d: macro contains no commands.\n", line_no); return 1; } return 0; } /*----------------------------------------------------------------------------+ | Split timers into individual tevents | +----------------------------------------------------------------------------*/ void split_timers ( TIMER *timerp, TEVENT **teventpp ) { int j, indx = 0; if ( !timerp ) return; j = 0; while( timerp[j].line_no > 0 ) { if ( timerp[j].flag_start != NO_EVENT ) { indx = tevent_index( teventpp ); (*teventpp)[indx].line_no = timerp[j].line_no; (*teventpp)[indx].pos = 1; (*teventpp)[indx].link = indx + 1; (*teventpp)[indx].timer = j; (*teventpp)[indx].generation = 0; (*teventpp)[indx].dow_bmap = timerp[j].dow_bmap; (*teventpp)[indx].sched_beg = timerp[j].sched_beg; (*teventpp)[indx].sched_end = timerp[j].sched_end; (*teventpp)[indx].notify = timerp[j].notify; (*teventpp)[indx].resolv_beg = timerp[j].resolv_beg; (*teventpp)[indx].resolv_end = timerp[j].resolv_end; (*teventpp)[indx].flag = timerp[j].flag_start | PRT_EVENT; (*teventpp)[indx].print = 1; (*teventpp)[indx].flag2 = 0; (*teventpp)[indx].cancel = timerp[j].cancel; (*teventpp)[indx].offset = timerp[j].offset_start; (*teventpp)[indx].delay = timerp[j].delay_start; (*teventpp)[indx].security = timerp[j].security_start; (*teventpp)[indx].ddoptions = timerp[j].ddoptions; (*teventpp)[indx].dawnlt = timerp[j].dawnlt; (*teventpp)[indx].dawngt = timerp[j].dawngt; (*teventpp)[indx].dusklt = timerp[j].dusklt; (*teventpp)[indx].duskgt = timerp[j].duskgt; (*teventpp)[indx].macro = timerp[j].macro_start; (*teventpp)[indx].ptr = timerp[j].ptr_start; (*teventpp)[indx].intv = timerp[j].intv; (*teventpp)[indx].nintv = timerp[j].nintv; } if ( timerp[j].flag_stop != NO_EVENT ) { indx = tevent_index( teventpp ); (*teventpp)[indx].line_no = timerp[j].line_no; (*teventpp)[indx].pos = 2; (*teventpp)[indx].link = indx + 1; (*teventpp)[indx].timer = j; (*teventpp)[indx].generation = 0; (*teventpp)[indx].dow_bmap = timerp[j].dow_bmap; (*teventpp)[indx].sched_beg = timerp[j].sched_beg; (*teventpp)[indx].sched_end = timerp[j].sched_end; (*teventpp)[indx].notify = timerp[j].notify; (*teventpp)[indx].resolv_beg = timerp[j].resolv_beg; (*teventpp)[indx].resolv_end = timerp[j].resolv_end; (*teventpp)[indx].flag = timerp[j].flag_stop | PRT_EVENT; (*teventpp)[indx].print = 1; (*teventpp)[indx].flag2 = 0; (*teventpp)[indx].cancel = timerp[j].cancel; (*teventpp)[indx].offset = timerp[j].offset_stop; (*teventpp)[indx].delay = timerp[j].delay_stop; (*teventpp)[indx].security = timerp[j].security_stop; (*teventpp)[indx].ddoptions = timerp[j].ddoptions; (*teventpp)[indx].dawnlt = timerp[j].dawnlt; (*teventpp)[indx].dawngt = timerp[j].dawngt; (*teventpp)[indx].dusklt = timerp[j].dusklt; (*teventpp)[indx].duskgt = timerp[j].duskgt; (*teventpp)[indx].macro = timerp[j].macro_stop; (*teventpp)[indx].ptr = timerp[j].ptr_start; (*teventpp)[indx].intv = timerp[j].intv; (*teventpp)[indx].nintv = timerp[j].nintv; } j++ ; } (*teventpp)[indx].link = -1; return; } /*----------------------------------------------------------------------------+ | Associate each tevent with the trigger which call its macro (if any). | +----------------------------------------------------------------------------*/ void associate_tevent_triggers ( TEVENT *teventp, MACRO *macrop ) { int j; if ( !teventp ) return; j = 0; while ( teventp[j].line_no > 0 ) { teventp[j].trig = macrop[teventp[j].macro].trig; j++; } return; } /*----------------------------------------------------------------------------+ | Replace events involving delayed macros with new events calling new macros | | with zero delay. (Necessary to avoid pending delayed macros, which are | | erased when a new schedule is uploaded.) Security events (handled | | separately) and Dawn/Dusk related events are not modified. | +----------------------------------------------------------------------------*/ void replace_delayed_events ( TEVENT **teventpp, MACRO **macropp, unsigned char **elementp ) { int j, cindx, macro, delay; unsigned int flag, needed; if ( *teventpp == NULL ) return; if ( verbose ) (void)printf("Entering replace_delayed_events() at generation %d\n", current_tevent_generation); /* Check whether we need to do anything at all */ needed = NO; j = 0; while ( (*teventpp)[j].line_no > 0 ) { if ( (*teventpp)[j].generation != current_tevent_generation || (flag = (*teventpp)[j].flag) == NO_EVENT || (*teventpp)[j].delay == 0 || (flag & SEC_EVENT) != 0 || (flag & CLOCK_EVENT) == 0 ) { j++; continue; } needed = YES; break; } if ( needed == NO ) return; j = 0; while ( (*teventpp)[j].line_no > 0 ) { if ( (*teventpp)[j].generation != current_tevent_generation || (flag = (*teventpp)[j].flag) == NO_EVENT ) { j++; continue; } /* Create a new tevent */ cindx = spawn_child_tevent( teventpp, j ); if ( (delay = (*teventpp)[j].delay) == 0 || (flag & SEC_EVENT) != 0 || (flag & CLOCK_EVENT) == 0 ) { j++; continue; } if ( verbose ) (void)printf("Replacing macro '%s' with non-delayed macro.\n", (*macropp)[(*teventpp)[j].macro].label); /* Create a duplicate macro with a new name and zero delay */ macro = macro_dupe_special(macropp, (*teventpp)[j].macro, elementp); (*teventpp)[cindx].offset += (*teventpp)[j].delay; (*teventpp)[cindx].delay = 0; (*teventpp)[cindx].macro = macro; (*teventpp)[cindx].flag |= (PRT_EVENT); (*teventpp)[cindx].print += 1; j++; } (void)update_current_tevent_generation(); return; } /*----------------------------------------------------------------------------+ | Try to create a unique macro name for a combined macro which will fit | | in the allowed space and yet be meaningful to the user. | +----------------------------------------------------------------------------*/ char *unique_macro_name ( int *maclist, int listlen, MACRO *macrop, char *prefix ) { /* Quick fix for now */ static char macname[MACRO_LEN + 1]; static int macnum = 0; (void)sprintf(macname, "%s_%02d", prefix, macnum++); return macname; } /*----------------------------------------------------------------------------+ | Return 1 if two tevents are the same except for the macros, and the macros | | have the same delay time. | +----------------------------------------------------------------------------*/ int is_tevent_similar ( int one, int two, TEVENT *teventp ) { unsigned int mask = (TIME_EVENTS | SEC_EVENT); return teventp[one].generation != teventp[two].generation ? 0 : teventp[one].dow_bmap != teventp[two].dow_bmap ? 0 : teventp[one].sched_beg != teventp[two].sched_beg ? 0 : teventp[one].sched_end != teventp[two].sched_end ? 0 : teventp[one].notify != teventp[two].notify ? 0 : (teventp[one].flag & mask) != (teventp[two].flag & mask) ? 0 : teventp[one].offset != teventp[two].offset ? 0 : teventp[one].notify != teventp[two].notify ? 0 : teventp[one].ddoptions != teventp[two].ddoptions ? 0 : teventp[one].dawnlt != teventp[two].dawnlt ? 0 : teventp[one].dawngt != teventp[two].dawngt ? 0 : teventp[one].dusklt != teventp[two].dusklt ? 0 : teventp[one].duskgt != teventp[two].duskgt ? 0 : teventp[one].delay != teventp[two].delay ? 0 : 1; } /*----------------------------------------------------------------------------+ | Combine similar tevents if possible, by creating secondary macros. | +----------------------------------------------------------------------------*/ void combine_similar_tevents ( TEVENT **teventpp, MACRO **macropp, unsigned char **elementp ) { int j, k, m; int sm, nsim, bsm, linki, linkm; int elem, mac, nelem, loc, indx, cindx, ibuff; int simlist[256]; int maclist[256]; unsigned char elembuff[4096]; char macname[MACRO_LEN]; if ( *teventpp == NULL ) return ; if ( verbose ) (void)printf("Entering combine_similar_events() at generation %d\n", current_tevent_generation); /* Reset the "done" flags */ j = 0; while ( (*teventpp)[j].line_no > 0 ) { (*teventpp)[j].done = 0; j++; } j = 0; while ( (*teventpp)[j].line_no > 0 ) { if ( (*teventpp)[j].generation != current_tevent_generation || (*teventpp)[j].flag == NO_EVENT || (*teventpp)[j].done ) { j++; continue; } /* Compile a list of similar tevents, i.e., with same date range, */ /* dow bmap, type, offset, security, macro delay. */ k = j + 1; nsim = 0; simlist[nsim++] = j; while ( (*teventpp)[k].line_no > 0 ) { if ( (*teventpp)[k].generation != current_tevent_generation || (*teventpp)[k].flag == NO_EVENT || (*teventpp)[k].done ) { k++; continue; } if ( is_tevent_similar(j, k, *teventpp) ) simlist[nsim++] = k; k++; } /* Make a list of the corresponding macro indexes */ for ( k = 0; k < nsim; k++ ) maclist[k] = (*teventpp)[simlist[k]].macro; /* Reorder the links to group together similar tevents */ /* for reporting purposes. */ for ( k = nsim - 1; k > 0; k-- ) { (*teventpp)[simlist[k]].plink = simlist[k - 1]; } indx = j; for ( k = 1; k < nsim; k++ ) { sm = simlist[k]; if ( (*teventpp)[indx].link == sm ) { indx = sm; continue; } m = 0; bsm = -1; while ( (*teventpp)[m].line_no > 0 ) { if ( (*teventpp)[m].link == sm ) { bsm = m; break; } m++; } if ( bsm < 0 ) { (void)fprintf(stderr, "Internal error: combine_similar_tevents(): No back link to tevent %d\n", sm); exit(1); } linki = (*teventpp)[indx].link; linkm = (*teventpp)[sm].link; (*teventpp)[sm].link = linki; (*teventpp)[indx].link = sm; (*teventpp)[bsm].link = linkm; indx = sm; } /* Create a new TEVENT linked to the last in the group */ cindx = spawn_child_tevent( teventpp, simlist[nsim - 1]); /* Replace its line_no and pos with that of the first */ /* in the group. */ (*teventpp)[cindx].line_no = (*teventpp)[simlist[0]].line_no; (*teventpp)[cindx].pos = (*teventpp)[simlist[0]].pos; /* Store the number of combined events */ (*teventpp)[cindx].combined = nsim; /* If more than 1 tevent in the list, flag them for printing */ if ( nsim > 1 ) { for ( k = 0; k < nsim; k++ ) (*teventpp)[simlist[k]].flag |= PRT_EVENT; (*teventpp)[cindx].flag |= (PRT_EVENT | COMB_EVENT); (*teventpp)[cindx].print = nsim + 1; } /* If more than 1 tevent in the list, we must create a */ /* new macro for the combined tevent. */ if ( nsim > 1 ) { /* Concatenate the macro elements of the similar events */ ibuff = 0; nelem = 0; for ( k = 0; k < nsim; k++ ) { mac = maclist[k]; elem = (*macropp)[mac].element; nelem += (*macropp)[mac].nelem; for ( m = 0; m < (*macropp)[mac].total; m++ ) { elembuff[ibuff++] = (*elementp)[elem++]; } } /* Create a combined macro */ (void)strncpy2(macname, unique_macro_name(maclist, nsim, *macropp, COMB_MAC_PREFIX), sizeof(macname) - 1); mac = macro_index(macropp, macname, DERIVED_MACRO); (*teventpp)[cindx].macro = mac; loc = macro_element_index( elementp, ibuff ); (*macropp)[mac].element = loc; (*macropp)[mac].total = ibuff; (*macropp)[mac].nelem = nelem; (*macropp)[mac].line_no = 9999; /* (*macropp)[mac].modflag |= COMBINED; */ (*macropp)[mac].delay = (*teventpp)[cindx].delay; for ( k = 0; k < ibuff; k++ ) { (*elementp)[loc++] = elembuff[k]; } } /* Mark the similar tevents as "done" */ for ( k = 0; k < nsim; k++ ) { sm = simlist[k]; (*teventpp)[sm].done = 1; } } update_current_tevent_generation(); return; } /*----------------------------------------------------------------------------+ | Return 1 if two tevents have the same dow_bmask and begin/end days but | | not the same offsets. (If a timer has two events with the same time, | | only the first will be executed.) | +----------------------------------------------------------------------------*/ int is_compatible ( TEVENT *teventp1, TEVENT *teventp2 ) { return teventp1->dow_bmap != teventp2->dow_bmap ? 0 : teventp1->resolv_beg != teventp2->resolv_beg ? 0 : teventp1->resolv_end != teventp2->resolv_end ? 0 : teventp1->offset == teventp2->offset ? 0 : teventp1->notify != teventp2->notify ? 0 : 1 ; } /*----------------------------------------------------------------------------+ | Copy data from two tevents into one timer. If the second index is -1, the | | "stop" part of the timer will be a null event. | +----------------------------------------------------------------------------*/ void copy_tevents_to_timer ( int tev1, int tev2, int timr, TEVENT *teventp, TIMER *timerp) { extern int current_timer_generation; if ( !teventp ) return; timerp[timr].line_no = 9999; timerp[timr].generation = current_timer_generation; timerp[timr].dow_bmap = teventp[tev1].dow_bmap; timerp[timr].sched_beg = teventp[tev1].sched_beg; timerp[timr].sched_end = teventp[tev1].sched_end; timerp[timr].notify = teventp[tev1].notify; timerp[timr].resolv_beg = teventp[tev1].resolv_beg; timerp[timr].resolv_end = teventp[tev1].resolv_end; timerp[timr].line1 = teventp[tev1].line_no; timerp[timr].pos1 = teventp[tev1].pos; timerp[timr].tevent_start = tev1; timerp[timr].flag_start = teventp[tev1].flag; timerp[timr].flag_combined = teventp[tev1].flag | teventp[tev1].flag2; timerp[timr].offset_start = teventp[tev1].offset; timerp[timr].delay_start = teventp[tev1].delay; timerp[timr].security_start = teventp[tev1].security; timerp[timr].macro_start = teventp[tev1].macro; if ( tev2 >= 0 ) { timerp[timr].line2 = teventp[tev2].line_no; timerp[timr].pos2 = teventp[tev2].pos; timerp[timr].tevent_stop = tev2; timerp[timr].flag_stop = teventp[tev2].flag; timerp[timr].flag_combined |= teventp[tev2].flag | teventp[tev2].flag2; timerp[timr].offset_stop = teventp[tev2].offset; timerp[timr].delay_stop = teventp[tev2].delay; timerp[timr].security_stop = teventp[tev2].security; timerp[timr].macro_stop = teventp[tev2].macro; } else { timerp[timr].line2 = 0; timerp[timr].pos2 = 0; timerp[timr].tevent_stop = -1; timerp[timr].flag_stop = NO_EVENT; timerp[timr].offset_stop = NULL_TIME; timerp[timr].delay_stop = 0; timerp[timr].security_stop = 0; timerp[timr].macro_stop = NULL_MACRO_INDEX; } return; } /*----------------------------------------------------------------------------+ | Recombine tevents into new timers. | +----------------------------------------------------------------------------*/ int reconstruct_timers ( TEVENT *teventp, TIMER **timerpp ) { extern int current_tevent_generation; extern int timer_generation_delta; int j, k, endch, tnum, indx = 0, matched; unsigned int flag; if ( !teventp ) return 0; if ( verbose ) (void)printf("Entering reconstruct_timers() at tevent generation %d\n", current_tevent_generation); timer_generation_delta = 1; update_current_timer_generation(); /* Find the end of the existing linked timer chain */ endch = -1; tnum = 0; while ( (*timerpp)[tnum].line_no > 0 ) { if ( (*timerpp)[tnum].link == -1 ) endch = tnum; tnum++; } /* Count the number of timed events and reset the "done" flags */ tnum = 0; while ( teventp[tnum].line_no > 0 ) { teventp[tnum].done = 0; tnum++; } /* First try to combine like events, i.e., Dawn with Dawn, */ /* Dusk with Dusk, or Clock with Clock. */ for ( j = 0; j < tnum; j++ ) { if ( teventp[j].generation != current_tevent_generation || teventp[j].flag == NO_EVENT || teventp[j].flag & CHAIN_EVENT || teventp[j].done ) continue; flag = teventp[j].flag & TIME_EVENTS; matched = 0; for ( k = j + 1; k < tnum; k++ ) { if ( teventp[k].generation != current_tevent_generation || teventp[k].flag == NO_EVENT || (teventp[k].flag & TIME_EVENTS) != flag || teventp[k].done ) continue; if ( is_compatible(&teventp[j], &teventp[k]) ) { matched = 1; break; } } if ( matched ) { /* Two tevents found which can be combined into one timer */ indx = timer_index(timerpp); copy_tevents_to_timer( j, k, indx, teventp, *timerpp ); /* Remove these tevents from further consideration */ teventp[j].done = 1; teventp[k].done = 1; /* Link to existing chain */ (*timerpp)[endch].link = indx; (*timerpp)[indx].link = -1; endch = indx; } } /* Next try to combine any leftover Dawn/Dusk events with */ /* each other if allowed. */ if ( configp->res_overlap == RES_OVLAP_COMBINED ) { for ( j = 0; j < tnum; j++ ) { if ( teventp[j].generation != current_tevent_generation || teventp[j].flag == NO_EVENT || teventp[j].flag & (CLOCK_EVENT | CHAIN_EVENT) || teventp[j].done ) continue; matched = 0; for ( k = j + 1; k < tnum; k++ ) { if ( teventp[k].generation != current_tevent_generation || teventp[k].flag == NO_EVENT || teventp[k].flag & (CLOCK_EVENT | CHAIN_EVENT) || teventp[k].done ) continue; if ( is_compatible(&teventp[j], &teventp[k]) ) { matched = 1; break; } } if ( matched ) { /* Two tevents found which can be combined into one timer */ indx = timer_index(timerpp); copy_tevents_to_timer( j, k, indx, teventp, *timerpp ); /* Remove these tevents from further consideration */ teventp[j].done = 1; teventp[k].done = 1; /* Link to existing chain */ (*timerpp)[endch].link = indx; (*timerpp)[indx].link = -1; endch = indx; } else { /* Singleton tevent; set the timer stop event to null */ indx = timer_index(timerpp); copy_tevents_to_timer( j, -1, indx, teventp, *timerpp ); /* Remove this tevent from further consideration */ teventp[j].done = 1; /* Link to existing chain */ (*timerpp)[endch].link = indx; (*timerpp)[indx].link = -1; endch = indx; } } } /* Finally, try to combine all remaining events, excluding */ /* Dawn and Dusk combinations. */ for ( j = 0; j < tnum; j++ ) { if ( teventp[j].generation != current_tevent_generation || teventp[j].flag == NO_EVENT || teventp[j].flag & CHAIN_EVENT || teventp[j].done ) continue; matched = 0; flag = teventp[j].flag & (DAWN_EVENT | DUSK_EVENT); for ( k = j + 1; k < tnum; k++ ) { if ( teventp[k].generation != current_tevent_generation || teventp[k].flag == NO_EVENT || teventp[k].flag & CHAIN_EVENT || (teventp[k].flag & (DAWN_EVENT | DUSK_EVENT) && flag) || teventp[k].done ) continue; if ( is_compatible(&teventp[j], &teventp[k]) ) { matched = 1; break; } } if ( matched ) { /* Two tevents found which can be combined into one timer */ indx = timer_index(timerpp); copy_tevents_to_timer( j, k, indx, teventp, *timerpp ); /* Remove these tevents from further consideration */ teventp[j].done = 1; teventp[k].done = 1; /* Link to existing chain */ (*timerpp)[endch].link = indx; (*timerpp)[indx].link = -1; endch = indx; } else { /* Singleton tevent; set the timer stop event to null */ indx = timer_index(timerpp); copy_tevents_to_timer( j, -1, indx, teventp, *timerpp ); /* Remove this tevent from further consideration */ teventp[j].done = 1; /* Link to existing chain */ (*timerpp)[endch].link = indx; (*timerpp)[indx].link = -1; endch = indx; } } return indx; } /*---------------------------------------------------------------------+ | Store a sequence number 1-7 with each trigger corresponding to its | | order among those triggers which execute the same macro. | | Return non-zero if the sequence number is greater than 7, which is | | the largest number the CM11A hardware can report (reserving 7 for | | cases of 7 or higher) to unambiguously identify the particular | | trigger. | +---------------------------------------------------------------------*/ int set_trigger_tags ( TRIGGER *triggerp ) { int j, count, warn; int index[1000]; if ( triggerp == NULL ) return 0; for ( j = 0; j < 1000; j++ ) index[j] = 0; j = 0; warn = 0; while ( triggerp[j].line_no > 0 ) { count = ++index[triggerp[j].macro]; if ( count > 6 ) warn = 1; triggerp[j].tag = min(7, count); j++; } return warn; } /*----------------------------------------------------------------------------+ | Mark the macros actually in use in the current program interval | +----------------------------------------------------------------------------*/ void identify_macros_in_use ( MACRO *macrop, TEVENT *teventp ) { extern int current_tevent_generation; int j; /* First, those called by triggers. Others not */ j = 0; while ( macrop[j].label[0] != '\0' ) { /* Set all offsets to zero */ macrop[j].offset = 0; if ( macrop[j].refer & TRIGGER_PARSER ) macrop[j].use = USED; else macrop[j].use = NOTUSED; j++; } /* Then those called by tevents which are active in this interval */ if ( teventp == NULL ) return; j = 0; while ( teventp[j].line_no > 0 ) { if ( teventp[j].generation == current_tevent_generation && teventp[j].flag != NO_EVENT ) { macrop[teventp[j].macro].use = USED; } j++; } return; } /*----------------------------------------------------------+ | Create separate tevents for any interval where dawn/dusk | | tevent intervals partially overlap, so that on any given | | date each tevent will have the same value for dawn and/or| | dusk. | +----------------------------------------------------------*/ void resolve_tevent_overlap_comb ( TEVENT **teventpp ) { extern int current_tevent_generation; int breaker[366]; unsigned int tflag, flag[366]; int intv[732]; int beg, end; int i, j, val, indx, cindx, size, nintv; if ( !(*teventpp) ) return; if ( verbose ) (void)printf("Entering resolve_tevent_overlap_comb() at generation %d\n", current_tevent_generation); /* If there are no dawn/dusk tevents, there's nothing */ /* to do here. */ i = 0; val = 0; while ( (*teventpp)[i].line_no > 0 ) { if ( (*teventpp)[i].generation != current_tevent_generation ) { i++; continue; } if ( (*teventpp)[i].flag & (DAWN_EVENT | DUSK_EVENT) ) val++; i++; } if ( val == 0 ) return; /* Initialize arrays for a breaker and for flags */ for ( j = 0; j < 366; j++ ) { breaker[j] = 0; flag[j] = 0; } /* Fill in the breaker array for dawn/dusk tevents. */ /* Increment the generation of clock tevents */ /* and we're done with them. */ i = 0; while ( (*teventpp)[i].line_no > 0 ) { if ( (*teventpp)[i].generation != current_tevent_generation ) { i++; continue; } if ( (*teventpp)[i].flag & (DAWN_EVENT | DUSK_EVENT) ) { for ( j = (*teventpp)[i].resolv_beg; j <= (*teventpp)[i].resolv_end; j++ ) breaker[j] += 1; } else { (*teventpp)[i].flag2 = 0; increment_tevent_generation( *teventpp, i ); } i++; } size = 0; i = 0; while ( (*teventpp)[i].line_no > 0 ) { if ( (*teventpp)[i].generation != current_tevent_generation ) { i++; continue; } tflag = (*teventpp)[i].flag & (DAWN_EVENT | DUSK_EVENT) ; beg = (*teventpp)[i].resolv_beg; end = (*teventpp)[i].resolv_end; val = breaker[beg]; (*teventpp)[i].intv = size; intv[size++] = beg; for ( j = beg; j <= end; j++ ) { /* Flags for all sun timers are OR'd together. */ flag[j] |= tflag; if ( val != breaker[j] ) { if ( j > beg ) { intv[size++] = j - 1; intv[size++] = j; } val = breaker[j]; } } intv[size++] = end; (*teventpp)[i].nintv = (size - (*teventpp)[i].intv) / 2; i++; } i = 0; while ( (*teventpp)[i].line_no > 0 ) { if ( (*teventpp)[i].generation != current_tevent_generation ) { i++; continue; } nintv = (*teventpp)[i].nintv; indx = (*teventpp)[i].intv + 2 * (nintv - 1); for ( j = 0; j < nintv; j++ ) { beg = intv[indx]; end = intv[indx + 1]; indx -= 2; tflag = flag[beg]; cindx = spawn_child_tevent( teventpp, i ); /* The child tevent flag2 indicates whether to resolve */ /* sun times based on dawn only, dusk only, or both. */ (*teventpp)[cindx].flag2 |= tflag; (*teventpp)[cindx].resolv_beg = beg; (*teventpp)[cindx].resolv_end = end; } i++; } (void) update_current_tevent_generation(); return; } /*----------------------------------------------------------+ | Create separate tevents for any interval where dawn/dusk | | tevent intervals partially overlap, so that on any given | | date each tevent will have the same value for dawn and/or| | dusk. | | This alternate function does dawn and dusk separately. | +----------------------------------------------------------*/ void resolve_tevent_overlap_sep ( TEVENT **teventpp ) { extern int current_tevent_generation; int breaker[366]; unsigned int dflag; int intv[732]; int beg, end; int i, j, k, val, indx, cindx, size, nintv; if ( !(*teventpp) ) return; if ( verbose ) (void)printf("Entering resolve_tevent_overlap_sep() at generation %d\n", current_tevent_generation); /* If there are no dawn/dusk tevents, there's nothing */ /* to do here. */ i = 0; val = 0; while ( (*teventpp)[i].line_no > 0 ) { if ( (*teventpp)[i].generation != current_tevent_generation ) { i++; continue; } if ( (*teventpp)[i].flag & (DAWN_EVENT | DUSK_EVENT) ) val++; i++; } if ( val == 0 ) return; /* Increment the generation of non-dawn/dusk tevents */ /* and then we're done with them. */ i = 0; while ( (*teventpp)[i].line_no > 0 ) { if ( (*teventpp)[i].generation != current_tevent_generation || (*teventpp)[i].flag & (DAWN_EVENT | DUSK_EVENT) ) { i++; continue; } (*teventpp)[i].flag2 = 0; increment_tevent_generation( *teventpp, i ); i++; } /* Set dflag for the first pass through the loop */ dflag = DAWN_EVENT; for ( k = 0; k < 2; k++ ) { /* Initialize arrays for a breaker and for flags */ for ( i = 0; i < 366; i++ ) { breaker[i] = 0; } /* Fill in the breaker array */ i = 0; while ( (*teventpp)[i].line_no > 0 ) { if ( (*teventpp)[i].generation != current_tevent_generation || ((*teventpp)[i].flag & dflag) == 0 ) { i++; continue; } for ( j = (*teventpp)[i].resolv_beg; j <= (*teventpp)[i].resolv_end; j++ ) breaker[j] += 1; i++; } size = 0; i = 0; while ( (*teventpp)[i].line_no > 0 ) { if ( (*teventpp)[i].generation != current_tevent_generation || ((*teventpp)[i].flag & dflag) == 0 ) { i++; continue; } beg = (*teventpp)[i].resolv_beg; end = (*teventpp)[i].resolv_end; val = breaker[beg]; (*teventpp)[i].intv = size; intv[size++] = beg; for ( j = beg; j <= end; j++ ) { if ( val != breaker[j] ) { if ( j > beg ) { intv[size++] = j - 1; intv[size++] = j; } val = breaker[j]; } } intv[size++] = end; (*teventpp)[i].nintv = (size - (*teventpp)[i].intv) / 2; i++; } i = 0; while ( (*teventpp)[i].line_no > 0 ) { if ( (*teventpp)[i].generation != current_tevent_generation || ((*teventpp)[i].flag & dflag) == 0 ) { i++; continue; } nintv = (*teventpp)[i].nintv; indx = (*teventpp)[i].intv + 2 * (nintv - 1); for ( j = 0; j < nintv; j++ ) { beg = intv[indx]; end = intv[indx + 1]; indx -= 2; cindx = spawn_child_tevent( teventpp, i ); /* The child tevent flag2 indicates whether to resolve */ /* sun times based on dawn only, dusk only, or both. */ (*teventpp)[cindx].flag2 |= dflag; (*teventpp)[cindx].resolv_beg = beg; (*teventpp)[cindx].resolv_end = end; } i++; } /* Set dflag for second pass through the loop */ dflag = DUSK_EVENT; } (void) update_current_tevent_generation(); return; } /*---------------------------------------------------------+ | Manage a binary search for the highest integer yielding | | a "good" result, i.e., last_result => 0, such that | | integer+1 yields a "bad" result, i.e., last_result < 0. | | The function returns 0 until such time as the value in | | next_try is the desired integer, when it then returns 1.| +---------------------------------------------------------*/ int iter_mgr ( int last_result, long *next_try, long max_step, int *restart ) { static long last_bad, last_good ; static int stage; long step; if ( *restart ) { *restart = 0; stage = 0 ; } switch ( stage ) { case 0 : /* Start by trying 0 */ stage = 1; *next_try = 0L; return 0; case 1 : /* Then try 1 */ stage = 2 ; if ( last_result < 0 ) { last_bad = 0L; *next_try = 1L; return 0; } else { return 1; } case 2 : /* Now keep doubling until we get a "good" result */ if ( last_result < 0 ) { last_bad = *next_try; step = min( *next_try + 1L, max_step ); *next_try += step; return 0; } else { last_good = *next_try; if ( last_result == 0 || (last_good - last_bad) == 1L ) return 1; else { stage = 3; *next_try = (last_bad + last_good) / 2L; return 0; } } case 3 : /* Now keep splitting the range until the values */ /* yielding good and bad results differ by only 1 */ if ( last_result < 0 ) { last_bad = *next_try; *next_try = max((last_bad + last_good)/2L, last_bad + 1L); return 0; } else { if ( last_result == 0L || (last_good - last_bad) == 1L ) return 1; last_good = *next_try; *next_try = max((last_bad + last_good)/2L, last_bad + 1L); return 0; } } return 0; /* Keep the compiler happy */ } /*---------------------------------------------------------+ | Return the fixed time approximating dawn or dusk times | | over an interval from the array of daily values, the | | beginning and ending year days, and the option chosen | | by the user. Recognized options (defines) are: FIRST, | | EARLIEST, LATEST, AVERAGE, and MEDIAN. | +---------------------------------------------------------*/ int set_suntime ( int *daily_sun, int begin_day, int end_day, unsigned char option, int *error ) { int rmin, rmax, dsj, value; int j; long int sum; if ( begin_day > end_day ) { (void)fprintf(stderr, "set_suntime(): Begin day (%d) > End day (%d)\n", begin_day, end_day); return -1; } rmin = rmax = daily_sun[begin_day]; sum = 0L; for ( j = begin_day; j <= end_day; j++ ) { dsj = daily_sun[j]; sum += (long)dsj; rmin = min(rmin, dsj); rmax = max(rmax, dsj); } *error = rmax - rmin; switch ( option ) { case FIRST : /* Use the value from the first day of the interval */ value = daily_sun[begin_day]; break; case EARLIEST : /* Use earliest value over the interval */ value = rmin; break; case LATEST : /* Use latest value over the interval */ value = rmax; break; case AVERAGE : /* Use average value over the interval */ value = (int)(sum / (long)(end_day - begin_day + 1)); break; case MEDIAN : /* Use median value over the interval */ value = (rmin + rmax) / 2; break; default : (void)fprintf(stderr, "set_suntime(): Option (%d) not recognized.", option); value = -1; break; } return value; } /*---------------------------------------------------------+ | For each timer with dawn/dusk based events, create one | | or more new timers with subintervals of the date range, | | with the subintervals chosen to minimize the error | | in the dawn/dusk times over that subinterval subject to | | the available CM11a memory free space. | | Then resolve the dawn/dusk based times into clock times.| | The resulting error in dawn/dusk times from actual | | (as determined by our sunrise/sunset calculator) is | | passed back to the caller. | +---------------------------------------------------------*/ int resolve_sun_times( TIMER **timerpp, CALEND *calendp, int freespace, int *tot_timers, int *max_error ) { extern int line_no, current_timer_generation; double latitude, longitude; time_t tzone; long julianday; long errval; unsigned int size, sizelimit /*, totintv = 0 */; int dawn[366], dusk[366], scode[366], dummy[366], dst[366]; int *ptr1 = NULL, *ptr2 = NULL; int *intvp = NULL; int i, j; int r, rmin, rmax, s, smin, smax, iter; int value; int indx, cindx; int result = -1, restart; int dbegin, dend; int year, month, day, yday; int dawnerr, duskerr, error; int sun_timers, res_timers; int status = 0, totintv = 0; enum { STtime, DLtime }; /* Report error as zero in case of early return */ *max_error = 0; /* If no timers at all, simply return */ if ( !(*timerpp) ) { *tot_timers = 0; *max_error = 0; return 0; } if ( verbose ) (void)printf("Entering resolve_sun_times() at timer generation %d\n", current_timer_generation); /* Get geographic location and timezone from */ /* that stored in the global CONFIG structure. */ if ( configp->loc_flag != (LATITUDE | LONGITUDE) ) { (void)fprintf(stderr, "LATITUDE and/or LONGITUDE not specified in %s\n", pathspec(CONFIG_FILE)); exit(1); } latitude = configp->latitude; longitude = configp->longitude; tzone = configp->tzone; /* Get date information from the CALEND structure */ year = calendp->year; month = calendp->month; day = calendp->mday; yday = calendp->yday; /* Count the timers which are already fully resolved and */ /* count the timers needing dawn/dusk time resolution. */ /* Change the generation of the fully-resolved timers to */ /* the next generation. */ j = 0; res_timers = sun_timers = 0; while ( (*timerpp)[j].line_no > 0 ) { if ( (*timerpp)[j].generation != current_timer_generation || (*timerpp)[j].flag_combined == NO_EVENT ) { j++; continue; } if ( ((*timerpp)[j].flag_combined & TIME_EVENTS) == CLOCK_EVENT ) { res_timers++; } else { sun_timers++; } j++; } /* If there are no timers needing dawn/dusk resolution, */ /* we're done here and can return. */ if ( sun_timers == 0 ) { *tot_timers = res_timers; *max_error = 0; (void) update_current_timer_generation(); return 0; } /* Calculate sunrise and sunset for 366 days from today's */ /* date and store in arrays. */ /* Get today's Julian Day - the big number, not the day */ /* of the year. */ julianday = daycount2JD(calendp->today); for ( j = 0; j < 366; j++ ) { scode[j] = suntimes( latitude, longitude, tzone, julianday, configp->sunmode, &dawn[j], &dusk[j], NULL, NULL ); julianday++ ; } /* Also create a dummy array */ for ( j = 0; j < 366; j++ ) dummy[j] = 0; /* For Arctic/Antarctic regions - Substitute a dawn and/or */ /* dusk time for days when the sun is continually above or */ /* below the horizon, or no sunrise or sunset. */ for ( j = 0; j < 366; j++ ) { if ( scode[j] & UP_ALL_DAY ) { dawn[j] = 1; /* 00:01 */ dusk[j] = 1438; /* 23:58 */ } else if ( scode[j] & DOWN_ALL_DAY ) { dawn[j] = 1438; dusk[j] = 1; } else if ( scode[j] & NO_SUNRISE ) { dawn[j] = 1; } else if ( scode[j] & NO_SUNSET ) { dusk[j] = 1438; } } /* Put upper and/or lower bounds on dawn if specified in */ /* the config file. */ if ( configp->min_dawn != OFF || configp->max_dawn != OFF ) { for ( j = 0; j < 366; j++ ) dst[j] = time_adjust(j + yday, dawn[j], LGL2STD); if ( configp->min_dawn != OFF ) { for ( j = 0; j < 366; j++ ) dawn[j] = max(dawn[j], configp->min_dawn - dst[j]); } if ( configp->max_dawn != OFF ) { for ( j = 0; j < 366; j++ ) dawn[j] = min(dawn[j], configp->max_dawn - dst[j]); } } /* Put upper and/or lower bounds on dusk if specified in */ /* the config file. */ if ( configp->min_dusk != OFF || configp->max_dusk != OFF ) { for ( j = 0; j < 366; j++ ) dst[j] = time_adjust(j + yday, dusk[j], LGL2STD); if ( configp->min_dusk != OFF ) { for ( j = 0; j < 366; j++ ) dusk[j] = max(dusk[j], configp->min_dusk - dst[j]); } if ( configp->max_dusk != OFF ) { for ( j = 0; j < 366; j++ ) dusk[j] = min(dusk[j], configp->max_dusk - dst[j]); } } /* In the following section, the date interval specified in */ /* a timer is broken up into subintervals, each subinterval */ /* having the same sunrise and/or sunset time error over */ /* the subinterval. The error is minimized subject to the */ /* constraint of the available freespace in the CM11a */ /* memory - each subinterval will require a separate timer */ /* occupying 9 bytes of memory. */ /* Set pointers to arrays depending whether clock or */ /* dawn/dusk timers and store them in the timer structure. */ i = 0; while ( (*timerpp)[i].line_no > 0 ) { if ( (*timerpp)[i].generation != current_timer_generation || (*timerpp)[i].flag_combined == NO_EVENT ) { i++; continue; } switch ( (*timerpp)[i].flag_combined & (DAWN_EVENT | DUSK_EVENT) ) { case DAWN_EVENT : (*timerpp)[i].ptr_start = dawn; (*timerpp)[i].ptr_stop = dawn; break; case DUSK_EVENT : (*timerpp)[i].ptr_start = dusk; (*timerpp)[i].ptr_stop = dusk; break; case (DAWN_EVENT | DUSK_EVENT) : (*timerpp)[i].ptr_start = dawn; (*timerpp)[i].ptr_stop = dusk; break; default : (*timerpp)[i].ptr_start = dummy; (*timerpp)[i].ptr_stop = dummy; break; } /* If both the start and stop pointers are the same */ /* we need use only one of them. */ (*timerpp)[i].num_ptr = (*timerpp)[i].ptr_start == (*timerpp)[i].ptr_stop ? 1 : 2 ; i++; } /* Now start the iteration to determine the minimum */ /* error in dawn and or dusk times consistant with */ /* the available freespace in the CM11a memory. */ /* Each iteration starts with a specified error in */ /* the dawn or dusk value and determines the total */ /* number of intervals required. Then iterates the */ /* error until the total number of intervals will just */ /* fit in the available CM11a memory freespace. */ /* Upper limit to speed up the iteration and prevent */ /* possible integer overflow in weird cases. */ sizelimit = 2 * PROMSIZE / 9 + 100 ; iter = 0; restart = 1; while ( iter < 1000 && !iter_mgr( result, &errval, 1000L, &restart) ) { iter++; totintv = 0; size = 0; i = 0; while ( (*timerpp)[i].line_no > 0 ) { if ( size > sizelimit ) break; if ( (*timerpp)[i].generation != current_timer_generation || (*timerpp)[i].flag_combined == NO_EVENT ) { i++; continue; } (*timerpp)[i].intv = size; ptr1 = (*timerpp)[i].ptr_start; ptr2 = (*timerpp)[i].ptr_stop; indx = 0; dbegin = (*timerpp)[i].resolv_beg; dend = (*timerpp)[i].resolv_end; intvp[intv_index(&intvp, &size)] = dbegin; switch ( (*timerpp)[i].num_ptr ) { case 1 : rmin = rmax = ptr1[dbegin]; for ( j = dbegin; j <= dend; j++ ) { r = ptr1[j]; rmin = min(rmin, r); rmax = max(rmax, r); if ( (rmax - rmin) > errval ) { if ( j > dbegin ) { intvp[intv_index(&intvp, &size)] = j-1; intvp[intv_index(&intvp, &size)] = j; } rmin = rmax = r; } } break; case 2 : rmin = rmax = ptr1[dbegin]; smin = smax = ptr2[dbegin]; for ( j = dbegin; j <= dend; j++ ) { r = ptr1[j]; s = ptr2[j]; rmin = min(rmin, r); smin = min(smin, s); rmax = max(rmax, r); smax = max(smax, s); if ( (rmax - rmin) > errval || (smax - smin) > errval ) { if ( j > dbegin ) { intvp[intv_index(&intvp, &size)] = j-1; intvp[intv_index(&intvp, &size)] = j; } rmin = rmax = r; smax = smin = s; } } break; } intvp[intv_index(&intvp, &size)] = dend; (*timerpp)[i].nintv = (size - (*timerpp)[i].intv)/2 ; i++; } totintv = size/2; result = freespace > 9 * totintv ? 1 : freespace < 9 * totintv ? -1 : 0 ; /* If the error exceeds 2 days ( 2880 minutes ) at this */ /* point there's no point in trying further. */ if ( errval > (2*24*60) ) { (void) fprintf(stderr, "Insufficient CM11a memory.\n"); return 1; } } /* Check that the iteration limit has not been exceeded */ if ( iter >= 1000 ) { (void) fprintf(stderr, "Iteration limit exceeded.\n"); return 1; } /* These are the max error in dawn/dusk times from "actual" */ /* and the total number of timers to be programmed. */ *max_error = (int)errval; *tot_timers = (int)totintv; /* In the following, a new timer is created for each */ /* date subinterval and linked in a chain to the */ /* original (parent) timers. The offsets from dawn */ /* and dusk are replaced by clock times. */ i = 0; while ( (*timerpp)[i].line_no > 0 ) { if ( (*timerpp)[i].generation != current_timer_generation || (*timerpp)[i].flag_combined == NO_EVENT ) { i++; continue; } line_no = (*timerpp)[i].line_no ; dawnerr = duskerr = 0; indx = (*timerpp)[i].intv; for ( j = 0; j < (*timerpp)[i].nintv; j++ ) { dbegin = intvp[indx]; dend = intvp[indx + 1]; indx += 2; /* Create a child timer */ cindx = spawn_child_timer( timerpp, i ); (*timerpp)[cindx].resolv_beg = dbegin; (*timerpp)[cindx].resolv_end = dend; /* Add the fixed time for dawn/dusk to the offsets therefrom */ /* stored in the timer structures. */ switch ( (*timerpp)[cindx].flag_start & (DAWN_EVENT | DUSK_EVENT) ) { case DAWN_EVENT : value = set_suntime(dawn, dbegin, dend, configp->dawn_option, &error); (*timerpp)[cindx].offset_start += value; (*timerpp)[cindx].error_start = error; break; case DUSK_EVENT : value = set_suntime(dusk, dbegin, dend, configp->dusk_option, &error); (*timerpp)[cindx].offset_start += value; (*timerpp)[cindx].error_start = error; break; default : value = 0; (*timerpp)[cindx].error_start = 0; break; } if ( (*timerpp)[cindx].flag_start != NO_EVENT && ( (*timerpp)[cindx].offset_start < 0 || (*timerpp)[cindx].offset_start > 1439 ) ) { (void)fprintf(stderr, "Line %d: Expanded timer[%d] start time %d falls outside range 0-1439\n", (*timerpp)[cindx].line1, cindx, (*timerpp)[cindx].offset_start ); status = 1; } switch ( (*timerpp)[cindx].flag_stop & (DAWN_EVENT | DUSK_EVENT)) { case DAWN_EVENT : value = set_suntime(dawn, dbegin, dend, configp->dawn_option, &error); (*timerpp)[cindx].offset_stop += value; (*timerpp)[cindx].error_stop = error; break; case DUSK_EVENT : value = set_suntime(dusk, dbegin, dend, configp->dusk_option, &error); (*timerpp)[cindx].offset_stop += value; (*timerpp)[cindx].error_stop = error; break; default : value = 0; (*timerpp)[cindx].error_stop = 0; break; } if ( (*timerpp)[cindx].flag_stop != NO_EVENT && ( (*timerpp)[cindx].offset_stop < 0 || (*timerpp)[cindx].offset_stop > 1439 ) ) { (void)fprintf(stderr, "Line %02d: Expanded timer[%d] stop time %d falls outside range 0-1439\n", (*timerpp)[cindx].line2, cindx, (*timerpp)[cindx].offset_stop ); status = 1; } } /* end j loop */ i++ ; } /* end i loop */ /* We're finished with the array of intervals */ free( intvp ); intvp = NULL; (void) update_current_timer_generation(); return status; } /*---------------------------------------------------------------------+ | Return the amount of CM11a memory available (bytes) when that used | | by triggers, macros, timers, internal pointers and list terminators | | is subtracted from the total size. Entering with one of the | | structure pointers set to NULL will exclude it from the count. | +---------------------------------------------------------------------*/ int get_freespace( int generation, TIMER *timerp, TRIGGER *triggerp, MACRO *macrop ) { int j; int timclk, timsun, timcount, trig, mac, macsize, used; j = 0; timclk = 0; timsun = 0; while ( timerp && timerp[j].line_no >= 0 ) { if ( timerp[j].generation != generation || timerp[j].flag_combined == NO_EVENT ) { j++; continue; } if ( (timerp[j].flag_combined & TIME_EVENTS) == CLOCK_EVENT ) timclk++ ; else if ( timerp[j].flag_combined & (DAWN_EVENT | DUSK_EVENT) ) timsun++ ; else (void) fprintf(stderr, "Timer error: Index = %d Flag = %02xh\n", j, (timerp[j]).flag_combined ); j++; } timcount = timclk + timsun ; j = 0; trig = 0; while ( triggerp && triggerp[j++].line_no >= 0 ) trig++; j = 0; mac = 0; macsize = 0; while ( macrop && macrop[j].line_no >= 0 ) { if ( !macrop[j].isnull && macrop[j].use == USED ) { mac++; macsize += macrop[j].total; } j++; } /* Note: This formula MUST be the same as that used */ /* in function create_memory_image_[high|low](). */ used = 2 /* Offset of Trigger table */ + 9 * timcount + 1 /* Timers + terminator */ + 3 * trig + 2 /* Triggers + terminator */ + 2 * mac + macsize /* User defined macros */ + 2; /* Terminator */ if ( configp->macterm == YES ) used += mac; return (PROMSIZE - used) ; } /*---------------------------------------------------------------------+ | Create the memory image to be downloaded to the CM11a | | This function loads the macros at the top of memory. | | Note: The formula used to compute free space at the end of function | | get_freespace() must agree with what is done in this function. | +---------------------------------------------------------------------*/ void create_memory_image_high ( unsigned char *prommap, TIMER *timerp, TRIGGER *triggerp, MACRO *macrop, unsigned char *elementp ) { extern int current_timer_generation; int j, k, size, offset; int begin, end, start, stop; int macro1, macro2; unsigned int security1, security2; /* Initialize memory image */ for ( j = 0; j < PROMSIZE; j++ ) prommap[j] = 0xff; /* Store zeros in the last two bytes for the terminator */ prommap[PROMSIZE - 1] = 0; prommap[PROMSIZE - 2] = 0; offset = PROMSIZE - 2; /* Establish the offset of each macro in the eeprom and store */ /* in the memory image. Macros are loaded at the top of */ /* memory but in the order they were defined, i.e., the last */ /* macro defined by a macro command in the user's schedule */ /* will be highest in memory, just below the terminator. */ /* Get the total space to be occupied by the macros */ size = 0; j = 0; while ( macrop[j].line_no > 0 ) { if ( !macrop[j].isnull && macrop[j].use == USED ) { if ( configp->macterm == YES ) size += macrop[j].total + 3; else size += macrop[j].total + 2; } j++; } offset -= size; macrop[NULL_MACRO_INDEX].offset = 0; j = 0; while ( macrop[j].line_no > 0 ) { if ( !macrop[j].isnull && macrop[j].use == USED ) { /* Save the offset */ macrop[j].offset = offset; prommap[offset++] = macrop[j].delay; /* Make sure the macro doesn't have too many elements */ if ( macrop[j].nelem > 255 ) { fprintf(stderr, "Combined macro %s has too many (%d) elements.\n", macrop[j].label, macrop[j].nelem); exit(1); } prommap[offset++] = (unsigned char)macrop[j].nelem; for ( k = 0; k < macrop[j].total; k++ ) { prommap[offset++] = elementp[macrop[j].element + k]; } if ( configp->macterm == YES ) prommap[offset++] = 0; } j++; } /* Store current timers */ j = 0; offset = 2; while ( timerp && timerp[j].line_no > 0 ) { if ( timerp[j].generation != current_timer_generation || timerp[j].flag_combined == NO_EVENT ) { j++; continue; } prommap[offset] = timerp[j].dow_bmap ; timerp[j].memloc = offset; begin = timerp[j].resolv_beg; end = timerp[j].resolv_end; prommap[offset+1] = (unsigned char)(begin & 0xff) ; prommap[offset+2] = (unsigned char)(end & 0xff) ; /* (The extra steps here handle the case of null macros) */ start = min(0x0f, timerp[j].offset_start / 120); stop = min(0x0f, timerp[j].offset_stop / 120); prommap[offset+3] = (unsigned char)((start << 4) | stop); start = 0x7f & (timerp[j].offset_start - 120 * start); stop = 0x7f & (timerp[j].offset_stop - 120 * stop); prommap[offset+4] = (unsigned char)(((begin & 0x100) >> 1) | start); prommap[offset+5] = (unsigned char)(((end & 0x100) >> 1) | stop); security1 = timerp[j].flag_start & SEC_EVENT ? SECURITY_ON : 0; security2 = timerp[j].flag_stop & SEC_EVENT ? SECURITY_ON : 0; macro1 = macrop[timerp[j].macro_start].offset; macro2 = macrop[timerp[j].macro_stop ].offset; prommap[offset+6] = (unsigned char)((macro1 & 0x300) >> 4) | (unsigned char)((security1 & 0x03u) << 6) | (unsigned char)((macro2 & 0x300) >> 8) | (unsigned char)((security2 & 0x03u) << 2); prommap[offset+7] = (unsigned char)(macro1 & 0xff); prommap[offset+8] = (unsigned char)(macro2 & 0xff); offset += 9; j++; } /* Terminate the timers */ prommap[offset++] = 0xff ; /* Load offset of trigger table into location 0 */ prommap[0] = (unsigned char)((offset & 0xff00) >> 8); prommap[1] = (unsigned char)(offset & 0xff); /* Now store the triggers @ 3 bytes each + 2 terminators */ j = 0; while ( triggerp && triggerp[j].line_no > 0 ) { triggerp[j].memloc = offset; prommap[offset++] = triggerp[j].housecode << 4 | triggerp[j].unitcode; macro1 = macrop[triggerp[j].macro].offset; prommap[offset++] = (unsigned char)((triggerp[j].command) << 7) | (triggerp[j].tag << 4) | (unsigned char)((macro1 & 0xf00) >> 8); prommap[offset++] = (unsigned char)(macro1 & 0xff); j++ ; } /* Terminate with 2 bytes of 0xff */ prommap[offset++] = 0xff; prommap[offset++] = 0xff; return ; } /*---------------------------------------------------------------------+ | Create the memory image to be downloaded to the CM11a | | This one loads the macros immediately folowing the triggers. | | Note: The formula used to compute free space at the end of function | | get_freespace() must agree with what is done in this function. | +---------------------------------------------------------------------*/ void create_memory_image_low ( unsigned char *prommap, TIMER *timerp, TRIGGER *triggerp, MACRO *macrop, unsigned char *elementp ) { extern int current_timer_generation; int j, k, offset; int begin, end, start, stop; int macro1, macro2; unsigned int security1, security2; /* Initialize memory image */ for ( j = 0; j < PROMSIZE; j++ ) prommap[j] = 0x00; /* Add up the space required for timers and triggers */ offset = 2; /* Initial jump instruction */ j = 0; while ( timerp && timerp[j].line_no > 0 ) { if ( timerp[j].generation != current_timer_generation || timerp[j].flag_combined == NO_EVENT ) { j++; continue; } offset += 9; j++; } offset += 1; /* Timer terminator */ /* Now count the triggers @ 3 bytes each + 2 terminators */ j = 0; while ( triggerp && triggerp[j].line_no > 0 ) { offset += 3; j++; } offset += 2; /* Trigger terminator */ /* Load the macros, storing their offsets */ macrop[NULL_MACRO_INDEX].offset = 0; j = 0; while ( macrop[j].line_no > 0 ) { if ( !macrop[j].isnull && macrop[j].use == USED ) { /* Save the offset */ macrop[j].offset = offset; prommap[offset++] = macrop[j].delay; /* Make sure the macro doesn't have too many elements */ if ( macrop[j].nelem > 255 ) { fprintf(stderr, "Combined macro %s has too many (%d) elements.\n", macrop[j].label, macrop[j].nelem); exit(1); } prommap[offset++] = (unsigned char)macrop[j].nelem; for ( k = 0; k < macrop[j].total; k++ ) { prommap[offset++] = elementp[macrop[j].element + k]; } if ( configp->macterm == YES ) prommap[offset++] = 0; } j++; } /* Store two zeros for the terminator */ prommap[offset++] = 0; prommap[offset++] = 0; /* Go back and load the timers and triggers. */ j = 0; offset = 2; while ( timerp && timerp[j].line_no > 0 ) { if ( timerp[j].generation != current_timer_generation || timerp[j].flag_combined == NO_EVENT ) { j++; continue; } prommap[offset] = timerp[j].dow_bmap ; timerp[j].memloc = offset; begin = timerp[j].resolv_beg; end = timerp[j].resolv_end; prommap[offset+1] = (unsigned char)(begin & 0xff) ; prommap[offset+2] = (unsigned char)(end & 0xff) ; /* (The extra steps here handle the case of null macros) */ start = min(0x0f, timerp[j].offset_start / 120); stop = min(0x0f, timerp[j].offset_stop / 120); prommap[offset+3] = (unsigned char)((start << 4) | stop); start = 0x7f & (timerp[j].offset_start - 120 * start); stop = 0x7f & (timerp[j].offset_stop - 120 * stop); prommap[offset+4] = (unsigned char)(((begin & 0x100) >> 1) | start); prommap[offset+5] = (unsigned char)(((end & 0x100) >> 1) | stop); security1 = timerp[j].flag_start & SEC_EVENT ? SECURITY_ON : 0; security2 = timerp[j].flag_stop & SEC_EVENT ? SECURITY_ON : 0; macro1 = macrop[timerp[j].macro_start].offset; macro2 = macrop[timerp[j].macro_stop ].offset; prommap[offset+6] = (unsigned char)((macro1 & 0x300) >> 4) | (unsigned char)((security1 & 0x03u) << 6) | (unsigned char)((macro2 & 0x300) >> 8) | (unsigned char)((security2 & 0x03u) << 2); prommap[offset+7] = (unsigned char)(macro1 & 0xff); prommap[offset+8] = (unsigned char)(macro2 & 0xff); offset += 9; j++; } /* Terminate the timers */ prommap[offset++] = 0xff ; /* Load offset of trigger table into location 0 */ prommap[0] = (unsigned char)((offset & 0xff00) >> 8); prommap[1] = (unsigned char)(offset & 0xff); /* Now store the triggers @ 3 bytes each + 2 terminators */ j = 0; while ( triggerp && triggerp[j].line_no > 0 ) { triggerp[j].memloc = offset; prommap[offset++] = triggerp[j].housecode << 4 | triggerp[j].unitcode; macro1 = macrop[triggerp[j].macro].offset; prommap[offset++] = (unsigned char)((triggerp[j].command) << 7) | (triggerp[j].tag << 4) | (unsigned char)((macro1 & 0xf00) >> 8); prommap[offset++] = (unsigned char)(macro1 & 0xff); j++ ; } /* Terminate with 2 bytes of 0xff */ prommap[offset++] = 0xff; prommap[offset++] = 0xff; return ; } /*---------------------------------------------------------------------+ | Write the EEPROM memory image to disk as a pure binary file. | +---------------------------------------------------------------------*/ int write_image_bin ( char *pathname, unsigned char *prommap ) { FILE *fd_bin; if ( !(fd_bin = fopen(pathname, "wb")) ) { (void) fprintf(stderr, "Unable to open CM11a memory image binary file '%s' for writing.\n", pathname); return 1; } if ( fwrite( prommap, 1, PROMSIZE, fd_bin) != PROMSIZE ) { (void) fprintf(stderr, "Unable to write memory image file '%s'\n", pathname ); (void) fclose( fd_bin ); return 1; } (void) fclose( fd_bin ); return 0; } /*---------------------------------------------------------------------+ | Write the EEPROM memory image to disk as a hexadecimal dump. | +---------------------------------------------------------------------*/ int write_image_hex ( char *pathname, unsigned char *prommap ) { FILE *fd_hex; char outbuf[80]; char buf[16]; int j, k, loc; if ( !(fd_hex = fopen(pathname, "w")) ) { (void) fprintf(stderr, "Unable to open CM11a memory image hex dump file '%s' for writing.\n", pathname); return 1; } loc = 0; for ( j = 0; j < PROMSIZE; j += 16 ) { (void) sprintf(outbuf, "%03X ", loc); for ( k = 0; k < 8; k++ ) { (void) sprintf(buf, "%02X ", prommap[j+k]); (void) strncat(outbuf, buf, sizeof(outbuf) - 1 - strlen(outbuf)); } (void) strncat(outbuf, " ", sizeof(outbuf) - 1 - strlen(outbuf)); for ( k = 8; k < 16; k++ ) { (void) sprintf(buf, "%02X ", prommap[j+k]); (void) strncat(outbuf, buf, sizeof(outbuf) - 1 - strlen(outbuf)); } (void) fprintf(fd_hex, "%s\n", outbuf); loc += 16; } (void) fclose(fd_hex); return 0; } /*---------------------------------------------------------------------* | Store information to be written to or read from the X10record file | | in one place. | +---------------------------------------------------------------------*/ void store_record_info ( CALEND *calendp ) { x10record.isready = 1; x10record.dayset = calendp->today; x10record.yday = calendp->yday; x10record.day_zero = calendp->day_zero; x10record.tzone = configp->tzone; x10record.flags = ( timer_size > 0 ) ? HAS_TIMERS : 0; x10record.flags |= configp->mode; x10record.dstminutes = configp->dstminutes; x10record.program_days = configp->program_days; return; } /*---------------------------------------------------------------------* | Function to delete the X10record file; for use when the CM11a | | EEPROM is erased. | +---------------------------------------------------------------------*/ void remove_record_file ( void ) { int code; if ( verbose ) printf("Deleting X10 Record File %s\n", pathspec(RECORD_FILE)); code = remove( pathspec(RECORD_FILE) ) ; if ( code != 0 && errno != 2 ) { (void)fprintf(stderr, "WARNING: Unable to delete X10 Record File %s - errno = %d\n", pathspec(RECORD_FILE), errno); } return; } /*---------------------------------------------------------------------+ | Write a file with configuration and calendar information which | | heyu will need to save to properly set the CM11a clock-calendar at | | any later time. | +---------------------------------------------------------------------*/ int write_record_file ( char *pathname, CALEND *calendp ) { FILE *fd; store_record_info( calendp ); if ( !(fd = fopen( pathname, "w" )) ) { (void) fprintf(stderr, "Unable to open record file '%s' for writing.\n", pathname); exit(1); } (void)fprintf(fd, "# Generated by heyu - do not delete or modify.\n"); (void)fprintf(fd, "# Config: %s\n", pathspec(heyu_config)); (void)fprintf(fd, "# Schedule: %s\n", schedfile); (void)fprintf(fd, "# Uploaded: %s\n", legal_time_string()); (void)fprintf(fd, "%ld %d %d %ld %d %d %d\n", x10record.dayset, x10record.yday, x10record.day_zero, x10record.tzone, x10record.flags, x10record.dstminutes, x10record.program_days); (void) fclose(fd); return 0; } /*---------------------------------------------------------------------+ | Read info from x10record file and store in record_info structure. | | Return one of the following values: | | VALID_RECORD_FILE | | NO_RECORD_FILE | | BAD_RECORD_FILE (i.e., corrupted) | +---------------------------------------------------------------------*/ int read_record_file ( void ) { extern int verbose, i_am_relay; FILE *fd; char buffer[256]; int n, lines; x10record.isready = 0; if ( verbose && i_am_relay != 1 ) { (void)printf("Searching for %s\n", pathspec(RECORD_FILE)); } if ( !(fd = fopen( pathspec(RECORD_FILE), "r" )) ) { if ( verbose && i_am_relay != 1 ) { (void)printf("File %s is absent\n", pathspec(RECORD_FILE)); } return NO_RECORD_FILE; } if ( verbose && i_am_relay != 1 ) { (void)printf("%s is present\n", pathspec(RECORD_FILE)); } n = 0; lines = 0; while ( fgets(buffer, 256, fd) != NULL ) { lines++; (void)strtrim(buffer); if ( *buffer == '#' || *buffer == '\0' ) continue; n = sscanf(buffer, "%ld %d %d %ld %u %d %d", &x10record.dayset, &x10record.yday, &x10record.day_zero, &x10record.tzone, &x10record.flags, &x10record.dstminutes, &x10record.program_days); break; } (void) fclose( fd ); /* A zero-length record file is equivalent to no record file. */ if ( lines == 0 ) { if ( verbose && i_am_relay != 1 ) { (void)printf("File %s is empty\n", pathspec(RECORD_FILE)); } return NO_RECORD_FILE; } if ( n != 7 ) { if ( verbose && i_am_relay != 1 ) { (void)printf("File %s is corrupted\n", pathspec(RECORD_FILE)); } return BAD_RECORD_FILE; } x10record.isready = 1; return VALID_RECORD_FILE; } /*---------------------------------------------------------------------+ | Return the number of days >= 0 until expiration of an uploaded | | schedule from the x10 Record File and the system date. | | Return SCHEDULE_EXPIRED if the schedule has expired. | | Return NO_EXPIRATION if there are no timers defined. | | Return NO_RECORD_FILE if no (or empty) record file. | | Return BAD_RECORD_FILE if the record file is corrupted. | +---------------------------------------------------------------------*/ int get_upload_expire ( void ) { int retcode, elapsed, expire; long daynow; time_t now; retcode = read_record_file(); if ( retcode != VALID_RECORD_FILE ) return retcode; if ( !(x10record.flags & HAS_TIMERS) ) return NO_EXPIRATION; time(&now); daynow = ((long)now - x10record.tzone)/86400L; elapsed = (int)(daynow - x10record.dayset) + x10record.yday - x10record.day_zero; expire = x10record.program_days - elapsed; if ( expire < 0 ) return SCHEDULE_EXPIRED; return expire; } /*---------------------------------------------------------------------+ | Translate the readings from the CM11a clock to Legal Time, using | | what is recorded in the X10 Record File (if one exists), otherwise | | assume the CM11a clock is set to Standard Time. The arguments are: | | *Idaysp Day of Week bitmap (Sun = 1, Sat = 64) | | *Ijdayp Day of Year counter (0-365) | | *Ihrp Hours (0-23) | | *Iminp Minutes (0-59) | | *Isecp Seconds (0-59) | | *expire Days until expiration of uploaded schedule (0-365) or | | invalidation code (-1 to -4) | +---------------------------------------------------------------------*/ struct tm *cm11a_to_legal ( int *Idaysp, int *Ijdayp, int *Ihrp, int *Iminp, int *Isecp, int *expire ) { struct tm *tms, *tmp; static struct tm tmstat; time_t dtimep; long daynow; int delta; unsigned char bmap; /* Get the user's timezone */ get_std_timezone(); /* Fix TZ names if necessary */ fix_tznames(); tms = &tmstat; time(&dtimep); tmp = stdtime(&dtimep); memcpy((void *)tms, (void *)tmp, sizeof(struct tm)); /* Load the required data into the x10record structure if the program */ /* has not already done so. */ *expire = get_upload_expire(); if ( *expire != NO_RECORD_FILE && *expire != BAD_RECORD_FILE ) { delta = *Ijdayp - x10record.yday + x10record.day_zero; daynow = x10record.dayset + (long)delta; } else { daynow = *Ijdayp - tms->tm_yday + ((long)dtimep - std_tzone)/86400L ; } dtimep = (time_t)3600 * (time_t)(*Ihrp) + (time_t)60 * (time_t)(*Iminp) + (time_t)(*Isecp) + (time_t)86400 * (time_t)daynow + (time_t)std_tzone; tmp = localtime(&dtimep); memcpy((void *)tms, (void *)tmp, sizeof(struct tm)); /* Adjust the displayed day of the week if the day has changed */ /* into the previous or following day */ if ( tms->tm_hour < *Ihrp ) { bmap = (unsigned char)(*Idaysp); *Idaysp = (int)lrotbmap(bmap); } tms->tm_wday = bmap2wday((unsigned char)(*Idaysp)); *Ihrp = tms->tm_hour; *Iminp = tms->tm_min; *Isecp = tms->tm_sec; *Ijdayp = tms->tm_yday; return tms; } /*---------------------------------------------------------------------+ | Return a pointer to a tm structure containing the appropriate | | settings for the CM11a clock based on the argument time_t timep and | | what is recorded in the x10 Record File (if one exists). | +---------------------------------------------------------------------*/ struct tm *legal_to_cm11a ( time_t *dtimep ) { extern int i_am_relay; static struct tm tmstat; struct tm *tms, *tmp; time_t seconds; long daynow; int note; /* Maintain our own static structure because the structure built in */ /* to the standard time functions is overwritten each call. */ tms = &tmstat; /* Load the record file data into the x10record structure */ (void) read_record_file(); tmp = stdtime(dtimep); memcpy( (void *)tms, (void *)tmp, sizeof(struct tm) ); /* Just return the pointer if no (or corrupt) x10record file */ /* or if the schedule does not expire because there are no */ /* timers defined. */ if ( x10record.isready == 0 || !(x10record.flags | HAS_TIMERS) ) { return tms; } /* Force the date to be greater than or the same as the date the CM11a */ /* was programmed by incrementing the year if necessary. (We can't set */ /* the CM11a clock to a negative day count.) */ note = 0; while ( *dtimep - (time_t)std_tzone < (time_t)(x10record.dayset * 86400L) ) { tms->tm_year += 1; *dtimep = mktime(tms); note = 1; } if ( note && i_am_relay != 1 ) { (void)fprintf(stderr, "Date adjusted forward to year %d\n", tms->tm_year + 1900); } seconds = *dtimep - (time_t)std_tzone; /* Convert to Standard Time */ if ( tms->tm_isdst > 0 ) { tmp = gmtime(&seconds); memcpy( (void *)tms, (void *)tmp, sizeof(struct tm) ); } daynow = (long)seconds/86400L; /* Set the value of the yday according to the Heyu programming mode */ tms->tm_yday = (int)(daynow - x10record.dayset); return tms; } /*---------------------------------------------------------------------+ | Display status message | +---------------------------------------------------------------------*/ void display_status_message ( int expire ) { switch ( expire ) { case NO_RECORD_FILE : (void) fprintf(stdout, "No schedule has been uploaded by Heyu.\n"); break; case NO_EXPIRATION : (void) fprintf(stdout, "Uploaded schedule does not expire.\n"); break; case BAD_RECORD_FILE : (void) fprintf(stdout, "X10 Record File '%s' has been corrupted.\n", pathspec(RECORD_FILE)); break; case SCHEDULE_EXPIRED : (void) fprintf(stdout, "Uploaded schedule has expired.\n"); break; default : (void) fprintf(stdout, "Uploaded schedule will expire in %d days.\n", expire); break; } return; } /*---------------------------------------------------------------------+ | Display CM11a status and required CM11a clock settings. | | display_mode = 1 displays human-readable message. | | display_mode = 0 displays only the code, for use with scripts. | +---------------------------------------------------------------------*/ void display_cm11a_status ( int display_mode ) { int expire; expire = get_upload_expire(); if ( display_mode == 1 ) display_status_message( expire ); else (void) fprintf(stdout, "%d\n", expire); return; } /*---------------------------------------------------------------------+ | qsort() compare function for write_macroxref() | +---------------------------------------------------------------------*/ int compmac( struct macindx *one, struct macindx *two ) { return (one->offset - two->offset) ; } /*---------------------------------------------------------------------+ | Write a macro xref table, i.e., label vs offset in EEPROM, to disk. | | Optionally append macro helper tokens. | +---------------------------------------------------------------------*/ int write_macroxref ( char *pathname, MACRO *macrop, unsigned char *elementp, int ichksum ) { FILE *fd ; int i, j, k, m, index, mask, count; unsigned char cmdcode, hcode; unsigned int bmap; char hc; int (*fptr)() = &compmac; static int strucsize = sizeof(struct macindx); static char *label[] = {"", "", "On","Off", "Dim", "Bright"}; struct macindx *macp; if ( !(fd = fopen(pathname, "w")) ) { (void) fprintf(stderr, "Unable to open macro xref file '%s' for write.\n", pathname); return 1; } /* Write the image checksum as a label at address 0 */ fprintf(fd, " 0 %d\n", ichksum); /* Count macros in use */ count = 0; j = 0; while ( macrop[j].line_no > 0 ) { if ( !macrop[j].isnull && macrop[j].use == USED ) count++; j++; } if ( (macp = calloc( count, strucsize )) == NULL ) { fprintf(stderr, "write_macroxref() - Unable to allocate memory.\n"); exit(1); } count = 0; j = 0; while ( macrop[j].line_no > 0 ) { if ( !macrop[j].isnull && macrop[j].use == USED ) { macp[count].index = j; macp[count].offset = macrop[j].offset ; count++; } j++ ; } qsort ( macp, count, sizeof(struct macindx), fptr ); for ( j = 0; j < count; j++ ) { index = macp[j].index; (void) fprintf(fd, "%4d %s", macp[j].offset, macrop[index].label); if ( configp->xref_append == YES ) { i = macrop[index].element ; for ( k = 0; k < macrop[index].nelem; k++ ) { cmdcode = elementp[i] & 0x0fu; hcode = (elementp[i] & 0xf0u) >> 4; hc = tolower((int)code2hc(hcode)); bmap = elementp[i+1] << 8 | elementp[i+2]; if ( cmdcode > 1 && cmdcode < 6 ) { mask = 1; for ( m = 0; m < 16; m++ ) { if ( bmap & mask ) fprintf(fd, " %c%d%s", hc, bitpos2unit(m), label[cmdcode]); mask = mask << 1; } i += (cmdcode < 4) ? 3 : 4; } else { i += (cmdcode == 7) ? 6 : 3; } } } fprintf(fd, "\n"); } (void) fclose(fd); free( macp ); return 0; } /*---------------------------------------------------------------------+ | Compute a 12 bit checksum of the memory image | +---------------------------------------------------------------------*/ int image_chksum ( unsigned char *prommap ) { long int sum = 0; unsigned char *sp; sp = prommap; while ( sp < (prommap + PROMSIZE) ) sum += (long)(*sp++); return (int)(sum & 0x0fff); } /*---------------------------------------------------------------------+ | Return the macro label corresponding to the argument address in the | | macro xref file. Return "Unknown" if no file or no address match. | | If the file includes the checksum of the image file (entry at | | address zero), pass it back through the argument list. | +---------------------------------------------------------------------*/ int lookup_macro ( int address, char *maclabel, int *ichksum ) { FILE *fd; char *sp; char buffer[127]; char minibuf[MACRO_LEN + 1]; *ichksum = -1; if ( !(fd = fopen(pathspec(MACROXREF_FILE), "r")) ) { strncpy2(maclabel, "_unknown_", MACRO_LEN); return 0; } while ( fgets(buffer, sizeof(buffer)/sizeof(char), fd) != NULL ) { if ( (int)strtol(buffer, &sp, 10) == 0 ) { get_token(minibuf, &sp, " \t\n", MACRO_LEN); *ichksum = (int)strtol(minibuf, &sp, 10); } else if ( (int)strtol(buffer, &sp, 10) == address ) { get_token(minibuf, &sp, " \t\n", MACRO_LEN); strncpy2(maclabel, minibuf, MACRO_LEN); fclose(fd); return 1; } } fclose(fd); strncpy2(maclabel, "_unknown_", MACRO_LEN); return 0; } /*---------------------------------------------------------------------+ | Return the macro EEPROM address corresponding to the argument macro | | name argument in the x10macroxref file. Return -1 if not found. | | If the file includes the checksum of the image file (entry at | | address zero), pass it back through the argument list. | +---------------------------------------------------------------------*/ int macro_rev_lookup ( char *macname, int *ichksum ) { FILE *fd; char *sp; char buffer[127]; int val, macaddr; int tokc; char **tokv; if ( !(fd = fopen(pathspec(MACROXREF_FILE), "r")) ) return -1; *ichksum = -1; macaddr = -1; while ( fgets(buffer, sizeof(buffer)/sizeof(char), fd) != NULL ) { tokenize(buffer, " \t\n", &tokc, &tokv); if ( tokc < 2 ) { free(tokv); continue; } if ( (val = (int)strtol(tokv[0], &sp, 10)) == 0 && *sp == '\0' ) *ichksum = (int)strtol(tokv[1], &sp, 10); else if ( strcmp(tokv[1], macname) == 0 ) { macaddr = val; free(tokv); break; } free(tokv); } fclose(fd); return macaddr; } #if 0 /*---------------------------------------------------------------------+ | Open the user's X10 configuration file and call parse_config() to | | parse it and fill in global structure configp-> exit(1) is called | | if the file cannot be found or read, or if it contains errors. | +---------------------------------------------------------------------*/ void get_configuration ( void ) { FILE *fd ; int error_count; char confp[PATH_LEN + 1]; /* Return if the configuration file has already been read into memory */ if ( configp->read_flag != 0 ) { return; } find_heyu_path(); strncpy2(confp, pathspec(heyu_config), sizeof(confp) - 1); if ( verbose ) (void) fprintf(stdout, "Reading Heyu configuration file '%s'\n", confp); if ( !(fd = fopen(confp, "r")) ) { (void)fprintf(stderr, "Unable to find (or open) Heyu configuration file '%s'\n", confp); exit(1); } error_count = parse_config( fd ); if ( error_count != 0 ) { (void)fprintf(stderr, "Quitting due to errors in configuration file '%s'\n", confp); exit(1); } (void) fclose( fd ); return; } #endif /*---------------------------------------------------------------------+ | This function supervises the whole job of reading the config and | | schedule files and creating the CM11a memory image file. It also | | creates a bunch of other files for reference and/or debugging. | | | | If the argument is PROC_UPLOAD, the memory image is uploaded to the | | CM11a interface, the X10 Record File is written, and the CM11a | | clock is set to the appropriate time. | +---------------------------------------------------------------------*/ int process_data ( int proc_code ) { FILE *fd; TIMER *timerp = NULL; TEVENT *teventp = NULL; TRIGGER *triggerp = NULL; MACRO *macrop = NULL; CALEND today, *calendp; unsigned char prommap[PROMSIZE]; unsigned char *elementp = NULL; char *sp; int retcode, freespace, tot_timers, max_error; int ss_error; int ichksum; int now_time = -1; extern int c_setclock(int, char **); extern void upload_eeprom_image(unsigned char *); /* Open and parse the x10 configuration file and store */ /* the information in the global CONFIG structure. */ get_configuration(CONFIG_INIT); line_no = 0; /* See if the user has specified the full path for a schedule */ /* file by a command line option or in an environment variable, */ /* otherwise use what's been specified in the configuration */ /* file (or its default). */ if ( !((sp = optptr->schedp) || (sp = getenv("X10SCHED"))) ) sp = pathspec(configp->schedfile); (void)strncpy2(schedfile, sp, sizeof(schedfile) - 1); /* Open the x10 schedule file */ if ( !(fd = fopen(schedfile, "r")) ) { (void)fprintf(stderr, "Unable to open schedule file '%s'\n", schedfile); exit(1); } (void) printf("Schedule: %s\n", schedfile); /* Parse the schedule file and store the data in structure arrays */ retcode = parse_sched ( fd, &timerp, &triggerp, ¯op, &elementp ); (void) fclose( fd ); if ( retcode ) { (void) fprintf(stderr, "Quitting due to errors in schedule file '%s'\n", schedfile); exit(1); } if ( !timerp && !triggerp ) { (void) fprintf(stderr, "No Timers or Triggers defined in schedule file %s\n", schedfile); exit(1); } /* Get today's date information and store it in a */ /* CALEND structure. */ calendp = &today; calendar_today( calendp ); /* Split the timers into individual timed events for start and stop */ split_timers( timerp, &teventp ); /* Save the earliest macro execution time if a timer in the */ /* schedule file specified the time as "now[+NN]". */ now_time = get_first_now_time(teventp); /* If a macro in a tevent is also called by a trigger, associate */ /* the tevent with the trigger. */ associate_tevent_triggers( teventp, macrop ); /* Replace delayed events with undelayed events having increased */ /* offsets (and new macros) to compensate if requested */ if ( configp->repl_delay == YES ) replace_delayed_events( &teventp, ¯op, &elementp ); /* Combine similar tevents if requested */ if ( configp->combine_events == YES ) combine_similar_tevents( &teventp, ¯op, &elementp ); /* Compress macros by merging unit codes for the same */ /* HouseCode and Command. */ if ( configp->compress_macros == YES ) compress_macros( macrop, elementp ); /* Adjust the times for events programmed with Security mode */ security_adjust_legal( &teventp, ¯op, &elementp ); /* Convert the programmed month/day ranges into day counts */ /* over the programmed interval and adjust purely Clock */ /* events for Daylight time if applicable. */ resolve_dates( &teventp, calendp, configp->program_days ); /* Resolve Dawn/Dusk timer options, if any */ resolve_dawndusk_options( &teventp, calendp ); clear_disabled_events( &teventp ); /* Resolve overlapping date ranges for Dawn/Dusk relative */ /* events so that on any given day all events will have the */ /* same value for Dawn and/or Dusk. */ if ( configp->res_overlap == RES_OVLAP_COMBINED ) resolve_tevent_overlap_comb( &teventp ); /* Older method */ else resolve_tevent_overlap_sep( &teventp ); /* Mark macros actually in use over the programmed interval. */ identify_macros_in_use(macrop, teventp); /* Internal check */ verify_tevent_links( teventp ); /* Recombine the individual tevents into timers */ (void) reconstruct_timers( teventp, &timerp ); /* Check total freespace before going any further. */ freespace = get_freespace(current_timer_generation, timerp, triggerp, macrop ); /* Subtract timer space we are holding in reserve */ freespace -= 9 * configp->reserved_timers; if ( freespace < 0 ) { (void) fprintf(stderr, "Schedule too large: CM11a memory size exceeded by %d bytes ( = %.1f%% )\n", -freespace, 100. * (double)(-freespace)/(double)PROMSIZE); exit(1); } /* Get freespace excluding timers */ freespace = get_freespace(-1, NULL, triggerp, macrop) - 9 * configp->reserved_timers; /* Resolve any dawn/dusk based timers into a sequence of clock */ /* based timers, choosing the date intervals so as to minimize */ /* the error in dawn and/or dusk times over each interval */ /* subject to the constraint of the available freespace. */ retcode = resolve_sun_times( &timerp, calendp, freespace, &tot_timers, &max_error); if ( retcode ) { (void)fprintf(stderr, "Quitting due to errors.\n"); return 1; } /* Set a sequence number for each trigger, warning if */ /* the number exceeds 6. */ if ( set_trigger_tags( triggerp ) ) { (void)printf("Warning: More than 6 triggers reference the same macro.\n"); } /* Get the remaining freespace. */ freespace = get_freespace( current_timer_generation, timerp, triggerp, macrop ); if ( calendp->asif_flag & (ASIF_DATE | ASIF_TIME) ) (void)printf("Simulation as if %s\n", asif_time_string()); (void) printf("Expanded timers = %3d\n", tot_timers); (void) printf( "Max dawn/dusk error over the %d day period = %d minutes.\n", configp->program_days, max_error); (void) printf( "Interface memory free = %d bytes ( = %.1f%% )\n", freespace, 100.*(double)freespace/(double)PROMSIZE); /* Check for start time = stop time error */ ss_error = check_timer_start_stop(timerp); /* Internal check */ verify_timer_links( timerp ); /* Create the memory image to be downloaded to the CM11a */ #ifdef LOADLOW create_memory_image_low ( prommap, timerp, triggerp, macrop, elementp ); #else create_memory_image_high ( prommap, timerp, triggerp, macrop, elementp ); #endif /* End of #ifdef */ /* Compute a (16 bit) checksum for the memory image */ ichksum = image_chksum( prommap ); /* Create a report for the user */ (void) final_report( altpathspec(REPORT_FILE), calendp, timerp, teventp, triggerp, macrop, elementp, proc_code ); if ( ss_error ) { (void) fprintf(stderr, "*** Internal error: stop time = start time ***\n"); (void) fprintf(stderr, "%d timers - see OUTPUT TIMERS section in file %s for details.\n", ss_error, altpathspec(REPORT_FILE)); (void) fprintf(stderr, "Quitting due to errors.\n"); return 1; } else (void) fprintf(stdout, "See file %s for details.\n", altpathspec(REPORT_FILE)); /* Upload the EEPROM image to the CM11a and create a file */ /* listing the dates and configuration which is necessary */ /* if we later want to update the CM11a clock or check the */ /* status of the uploaded data. */ if ( proc_code == PROC_UPLOAD ) { upload_eeprom_image( prommap ); (void) write_record_file(pathspec(RECORD_FILE), calendp); (void) write_macroxref( pathspec(MACROXREF_FILE), macrop, elementp, ichksum); (void) write_image_bin( pathspec(IMAGE_FILE), prommap); (void) printf("Setting interface clock to current Standard Time.\n"); c_setclock( 1, NULL ); } else if ( configp->checkfiles == YES ) { (void) printf("Writing .check and .hex files.\n"); /* Write the same record file with a ".check" extension */ (void) write_record_file( pathspec(RECORD_FILE_CHECK), calendp ); /* Write the same macroxref file with a ".check" extension */ (void) write_macroxref( pathspec(MACROXREF_FILE_CHECK), macrop, elementp, ichksum); /* Write the same image file with a ".check" extension */ (void) write_image_bin( pathspec(IMAGE_FILE_CHECK), prommap); /* Write the image file in hex format */ (void) write_image_hex( pathspec(IMAGE_HEX_FILE), prommap ); } /* Display "now" time, if any */ if ( now_time >= 0 ) (void) printf("The first 'now+NN' event macro will execute at %02d:%02d:00\n", now_time / 60, now_time % 60 ); free( timerp ); free( teventp ); free( triggerp ); free( macrop ); free( elementp ); return 0; } /*---------------------------------------------------------------------+ | This function simulates execution of heyu by cron on a daily basis | | for the next 366 days, to insure there are no snags in the schedule | | file. No files other than a cronreport file are written. | +---------------------------------------------------------------------*/ int crontest ( void ) { FILE *fd_sched, *fd_cron; TIMER *timerp = NULL; TEVENT *teventp = NULL; TRIGGER *triggerp = NULL; MACRO *macrop = NULL; unsigned char *elementp = NULL; CALEND today, *calendp; int retcode, freespace, minfree, maxfree; int min_timers, max_timers, tot_timers; int max_error, least_error, worst_error; int j; char *sp; /* Open and parse the x10 configuration file and store */ /* the information in the global CONFIG structure. */ get_configuration(CONFIG_INIT); /* See if the user has specified the full path for a schedule */ /* file by a command line option or in an environment variable, */ /* otherwise use what's been specified in the configuration */ /* file (or its default). */ if ( !((sp = optptr->schedp) || (sp = getenv("X10SCHED"))) ) sp = pathspec(configp->schedfile); (void)strncpy2(schedfile, sp, sizeof(schedfile) - 1); /* Open the x10 schedule file */ if ( !(fd_sched = fopen(schedfile, "r")) ) { (void)fprintf(stderr, "Unable to open schedule file '%s'\n", schedfile); exit(1); } /* Parse the schedule file and store the data in structure arrays */ retcode = parse_sched( fd_sched, &timerp, &triggerp, ¯op, &elementp ); (void) fclose( fd_sched ); if ( retcode ) { (void) fprintf(stderr, "Quitting due to errors in schedule file '%s'\n", schedfile); exit(1); } if ( configp->mode == COMPATIBLE ) { (void)fprintf(stderr, "croncheck is not applicable when configured for COMPATIBLE mode.\n"); exit(1); } /* Nothing to do if no timers */ if ( !timerp ) { (void) printf("No timers found in schedule file - nothing to do.\n"); exit(0); } /* Get today's date information and store it in a */ /* a CALEND structure. */ calendp = &today; calendar_today( calendp ); /* Split the timers into individual timed events for start and stop */ split_timers( timerp, &teventp ); /* If a macro in a tevent is also called by a trigger, associate */ /* the tevent with the trigger. */ associate_tevent_triggers ( teventp, macrop ); /* Replace delayed events with undelayed events having increased */ /* offsets (and new macros) to compensate if requested */ if ( configp->repl_delay == YES ) replace_delayed_events ( &teventp, ¯op, &elementp ); /* Combine similar tevents if requested */ if ( configp->combine_events == YES ) combine_similar_tevents ( &teventp, ¯op, &elementp ); /* Compress macros by merging unit codes for the same */ /* HouseCode and Command and eliminating duplicates. */ if ( configp->compress_macros == YES ) compress_macros ( macrop, elementp ); /* Adjust the times for events programmed with Security mode */ security_adjust_legal ( &teventp, ¯op, &elementp ); /* Save the timer and tevents up to this point */ save_state ( timerp, teventp ); /* Open a file to store the detailed results */ if ( !(fd_cron = fopen(altpathspec(CRON_REPORT_FILE), "w")) ) { (void)fprintf(stderr, "Unable to open cron report file '%s'\n", altpathspec(CRON_REPORT_FILE)); exit(1); } (void)fprintf(fd_cron, "Results of Daily Operation for the next 366 Days\n"); (void)fprintf(fd_cron, "================================================\n\n"); least_error = 10000; worst_error = 0; minfree = 10000; maxfree = 0; min_timers = 10000; max_timers = 0; (void) fprintf(stdout, "Begin date Time Timers Freespace Dawn/Dusk error\n"); (void) fprintf(stdout, "---------- ----- ------ ----------- ---------------\n"); (void) fprintf(fd_cron, "Begin date Time Timers Freespace Dawn/Dusk error\n"); (void) fprintf(fd_cron, "---------- ----- ------ ----------- ---------------\n"); for ( j = 0; j < 366; j++ ) { (void) fprintf(stdout, "%4d/%02d/%02d %02d:%02d ", calendp->year, calendp->month, calendp->mday, calendp->minutes / 60, calendp->minutes % 60); fflush(stdout); (void) fprintf(fd_cron, "%4d/%02d/%02d %02d:%02d ", calendp->year, calendp->month, calendp->mday, calendp->minutes / 60, calendp->minutes % 60); /* Convert the programmed month/day ranges into day counts */ /* over the programmed interval and adjust purely Clock */ /* events for Daylight time if applicable. */ resolve_dates ( &teventp, calendp, configp->program_days ); /* Resolve Dawn/Dusk timer options, if any */ resolve_dawndusk_options( &teventp, calendp ); clear_disabled_events( &teventp ); /* Resolve overlapping date ranges for Dawn/Dusk relative */ /* events so that on any given day all events will have the */ /* same value for Dawn and/or Dusk. */ if ( configp->res_overlap == RES_OVLAP_COMBINED ) resolve_tevent_overlap_comb( &teventp ); /* Older method */ else resolve_tevent_overlap_sep( &teventp ); /* Mark macros actually in use over the programmed interval. */ identify_macros_in_use(macrop, teventp); /* Recombine the individual tevents into timers */ (void) reconstruct_timers( teventp, &timerp ); /* Check total freespace before going any further. */ freespace = get_freespace(current_timer_generation, timerp, triggerp, macrop ); /* Subtract timer space we are holding in reserve */ freespace -= 9 * configp->reserved_timers; if ( freespace < 0 ) { (void) fprintf(stderr, "Schedule too large: CM11a memory size exceeded by %d bytes ( = %.1f%% )\n", -freespace, 100. * (double)(-freespace)/(double)PROMSIZE); exit(1); } /* Get freespace excluding timers */ freespace = get_freespace(-1, NULL, triggerp, macrop ) - 9 * configp->reserved_timers; /* Resolve any dawn/dusk based timers into a sequence of clock */ /* based timers, choosing the date intervals so as to minimize */ /* the error in dawn and/or dusk times over each interval */ /* subject to the constraint of the available freespace. */ retcode = resolve_sun_times( &timerp, calendp, freespace, &tot_timers, &max_error); if ( retcode ) { (void)fprintf(stderr, "\nQuitting due to errors.\n"); return 1; } min_timers = min(min_timers, tot_timers); max_timers = max(max_timers, tot_timers); least_error = min(least_error, max_error); worst_error = max(worst_error, max_error); /* Get the remaining freespace. */ freespace = get_freespace( current_timer_generation, timerp, triggerp, macrop ); minfree = min(minfree, freespace); maxfree = max(maxfree, freespace); /* Display the results */ (void) fprintf(fd_cron, "%3d %4d (%4.1f%%) %4d\n", tot_timers, freespace, 100.*(double)freespace/1024., max_error); (void) fprintf(stdout, "%3d %4d (%4.1f%%) %4d\r", tot_timers, freespace, 100.*(double)freespace/1024., max_error); fflush(stdout); /* Check for start time = stop time error */ if ( check_timer_start_stop(timerp) > 0 ) { (void) fprintf(stderr, "Internal error: start time = stop time\n"); (void) fprintf(stderr, "CM11A Bug! - will not execute stop event macro\n"); exit(1); } /* Advance our calendar by one day */ advance_calendar( calendp, 1 ); /* Restore timers and tevents to the state saved before the */ /* first pass through the loop. */ restore_state ( timerp, teventp ); } (void) printf("Overall results:%40s\n", " "); (void) fprintf(stdout, "Minimum %3d %4d (%4.1f%%) %4d\n", min_timers, minfree, 100.*(double)minfree/1024., least_error); (void) fprintf(stdout, "Maximum %3d %4d (%4.1f%%) %4d\n", max_timers, maxfree, 100.*(double)maxfree/1024., worst_error); (void) fprintf(stdout, "See file %s for details.\n", altpathspec(CRON_REPORT_FILE)); (void) fprintf(fd_cron, "\nOverall results:\n"); (void) fprintf(fd_cron, "Minimum %3d %4d (%4.1f%%) %4d\n", min_timers, minfree, 100.*(double)minfree/1024., least_error); (void) fprintf(fd_cron, "Maximum %3d %4d (%4.1f%%) %4d\n", max_timers, maxfree, 100.*(double)maxfree/1024., worst_error); (void) fclose( fd_cron ); free( timerp ); free( teventp ); free( triggerp ); free( macrop ); free( elementp ); return 0; } /*---------------------------------------------------------------------+ | Write a table of daily sunrise/set or twilight to disk using | | location and timezone parameters from the configuration file. | +---------------------------------------------------------------------*/ int write_sun_table ( int format, int year, int sunmode, int timemode ) { FILE *fd_sun; char filename[256]; static char *fname[] = {"SunRiseSet", "CivilTwilight", "NautTwilight", "AstronTwilight"}; get_configuration(CONFIG_INIT); if ( configp->loc_flag != (unsigned char)(LATITUDE | LONGITUDE) ) { fprintf(stderr, "LATITUDE and/or LONGITUDE not specified in %s\n", pathspec(CONFIG_FILE)); return 1; } if ( format == FMT_PORTRAIT ) sprintf(filename, "%s_%d.txt", altpathspec(fname[sunmode]), year); else sprintf(filename, "%s_%d_wide.txt", altpathspec(fname[sunmode]), year); if ( (fd_sun = fopen(filename, "w")) ) { printf("Writing file %s\n", filename); if ( format == FMT_PORTRAIT ) { display_sun_table(fd_sun, year, configp->tzone, sunmode, timemode, configp->lat_d, configp->lat_m, configp->lon_d, configp->lon_m); } else { display_sun_table_wide(fd_sun, year, configp->tzone, sunmode, timemode, configp->lat_d, configp->lat_m, configp->lon_d, configp->lon_m); } } else { fprintf(stderr, "Unable to open file '%s' for write\n", filename); return 1; } return 0; } /*---------------------------------------------------------------------+ | Convert a yday measured from Jan 1 of year0 to year, month, day. | +---------------------------------------------------------------------*/ void yday2date ( long jan1day, int yday, int *year, int *month, int *mday, int *wday ) { time_t now; struct tm *tms; long delta_days; if ( !configp->read_flag ) { get_std_timezone(); configp->tzone = std_tzone; } delta_days = (long)yday + jan1day; now = (time_t)(86400L * delta_days + configp->tzone ); tms = localtime( &now ); *year = tms->tm_year + 1900; *month = tms->tm_mon + 1; *mday = tms->tm_mday; *wday = tms->tm_wday; return; } /*---------------------------------------------------------------------+ | Symbols for event flags. | +---------------------------------------------------------------------*/ char flag_def( unsigned int flag ) { return (flag & CLOCK_EVENT) ? 'C' : (flag & DAWN_EVENT) ? 'R' : (flag & DUSK_EVENT) ? 'S' : 'X' ; } /*---------------------------------------------------------------------+ | Qsort compare function for display events() | +---------------------------------------------------------------------*/ int comp_events ( struct ev_s *one, struct ev_s *two ) { return (one->line > two->line) ? 1 : (one->line < two->line) ? -1 : (one->pos > two->pos) ? 1 : (one->pos < two->pos) ? -1 : (one->flag > two->flag) ? 1 : (one->flag < two->flag) ? -1 : (one->beg > two->beg) ? 1 : (one->beg < two->beg) ? -1 : 0 ; } /*---------------------------------------------------------------------+ | Find the start of the tevent chain, i.e., the tevent not linked to | | by any other. | +---------------------------------------------------------------------*/ int find_startchain ( TEVENT *teventp ) { int j, k, size, *evrlink; static int sizint = sizeof(int); size = 0; while ( teventp[size].line_no > 0 ) size++; if ( (evrlink = calloc( size, sizint )) == NULL ) { fprintf(stderr, "find_startchain() - Unable to allocate memory.\n"); exit(1); } for ( j = 0; j < size; j++ ) evrlink[j] = 0; for ( j = 0; j < size; j++ ) { k = teventp[j].link; if ( k > (size - 1) ) { (void)fprintf(stderr, "find_startchain() - link %d out of bound %d at index %d\n", k, size - 1, j); return -1; } if ( k >= 0 ) evrlink[k] = 1; } for ( j = 0; j < size; j++ ) { if ( evrlink[j] == 0 ) break; } free(evrlink); return j; } /*---------------------------------------------------------------------+ | Display the amount of EEPROM memory used by timers, triggers, and | | macros. | +---------------------------------------------------------------------*/ void display_eeprom_usage ( FILE *fd, TIMER *timerp, TRIGGER *triggerp, MACRO *macrop ) { int j; int sun_event, clk_event, ntimers, ntriggers, nmacros, macspace; int ovhead, free; sun_event = clk_event = ntimers = 0; j = 0; while ( timerp && timerp[j].line_no > 0 ) { if ( timerp[j].generation != current_timer_generation || timerp[j].flag_combined == NO_EVENT ) { j++; continue; } ntimers++; if ( timerp[j].flag_start & (DAWN_EVENT | DUSK_EVENT) ) sun_event++; else if ( timerp[j].flag_start & CLOCK_EVENT ) clk_event++; if ( timerp[j].flag_stop & (DAWN_EVENT | DUSK_EVENT) ) sun_event++; else if ( timerp[j].flag_stop & CLOCK_EVENT ) clk_event++; j++; } ntriggers = 0; j = 0; while ( triggerp && triggerp[j].line_no > 0 ) { ntriggers++; j++; } nmacros = macspace = 0; j = 0; while ( macrop[j].line_no > 0 ) { if ( !macrop[j].isnull && macrop[j].use == USED ) { nmacros++; macspace += macrop[j].total + 2; } j++; } macspace += (configp->macterm == YES) ? nmacros : 0 ; ovhead = 2 + /* Initial jump */ 1 + /* Timer terminator */ 2 + /* Trigger terminator */ 2 ; /* Final macro terminator */ free = (PROMSIZE) - 9 * ntimers - 3 * ntriggers - macspace - ovhead; fprintf(fd, "\nEEPROM Utilization\n"); fprintf(fd, "Type Nbr Size\n"); fprintf(fd, "---- --- ----\n"); fprintf(fd, "Timers %3d %4d (%4.1f%%)\n", ntimers, 9 * ntimers, 900. * (float)(ntimers) / 1024. ); fprintf(fd, "Triggers %3d %4d (%4.1f%%)\n", ntriggers, 3 * ntriggers, 300. * (float)ntriggers / 1024. ); fprintf(fd, "Macros %3d %4d (%4.1f%%)\n", nmacros, macspace, 100. * (float)macspace / 1024. ); fprintf(fd, "Overhead %4d (%4.1f%%)\n", ovhead, 100. * (float)ovhead / 1024.); fprintf(fd, "Freespace %4d (%4.1f%%)\n", free, 100. * (float)free / 1024.); fprintf(fd, " ----\n"); fprintf(fd, "Total %4d\n\n", PROMSIZE); return ; } /*---------------------------------------------------------------------+ | Display timer options | +---------------------------------------------------------------------*/ char *display_timer_options ( TIMER *timerp, int index ) { static char buffer[64]; char minibuf[32]; unsigned char ddopts; *buffer = '\0'; if ( !(ddopts = timerp[index].ddoptions) ) return buffer; if ( ddopts & DAWNGT ) { sprintf(minibuf, " DawnGT %02d:%02d", timerp[index].dawngt / 60, timerp[index].dawngt % 60); strncat(buffer, minibuf, sizeof(buffer) - 1 - strlen(buffer)); } if ( ddopts & DAWNLT ) { sprintf(minibuf, " DawnLT %02d:%02d", timerp[index].dawnlt / 60, timerp[index].dawnlt % 60); strncat(buffer, minibuf, sizeof(buffer) - 1 - strlen(buffer)); } if ( ddopts & DUSKGT ) { sprintf(minibuf, " DuskGT %02d:%02d", timerp[index].duskgt / 60, timerp[index].duskgt % 60); strncat(buffer, minibuf, sizeof(buffer) - 1 - strlen(buffer)); } if ( ddopts & DUSKLT ) { sprintf(minibuf, " DuskLT %02d:%02d", timerp[index].dusklt / 60, timerp[index].dusklt % 60); strncat(buffer, minibuf, sizeof(buffer) - 1 - strlen(buffer)); } return buffer; } /*---------------------------------------------------------------------+ | Display timed events included in timers. | | Null events are ignored. | +---------------------------------------------------------------------*/ int display_events ( FILE *fd, TIMER *timerp, TEVENT *teventp, MACRO *macrop, CALEND *calendp ) { extern int current_timer_generation; struct ev_s event[1024]; int (*fptr)() = &comp_events; int prtlist[1024]; int j, k, m, startchain, j1, j2, nevents, count, retcode; int line, pos, ncomb, macro, error, sflag; int dayzero, year, month, day, wday, sched_end; int beg, end, endprev, year2, month2, day2; int createday, endday, lostday; unsigned int flag, ddflag; long jan1day; char shift, secur = ' '; int legal, secnom, tadj, offset = 0; char *sec_flag = " sss"; char note[6]; char minibuf[32]; unsigned char ddoptions; if ( timerp == NULL ) return 0; jan1day = calendp->jan1day; dayzero = calendp->day_zero; createday = calendp->create_day; endday = calendp->yday + configp->program_days - 1; startchain = find_startchain( teventp ); ddflag = NO_EVENT; nevents = 0; j = 0; while ( timerp[j].line_no > 0 ) { if ( timerp[j].generation != current_timer_generation ) { j++; continue; } if ( timerp[j].macro_start != NULL_MACRO_INDEX ) { event[nevents].tevent = timerp[j].tevent_start; event[nevents].timer = j; event[nevents].line = timerp[j].line1; event[nevents].pos = timerp[j].pos1; event[nevents].tpos = 1; event[nevents].beg = timerp[j].resolv_beg; event[nevents].flag = timerp[j].flag_start & SEC_EVENT; ddflag |= timerp[j].flag_start & TIME_EVENTS; nevents++; } if ( timerp[j].macro_stop != NULL_MACRO_INDEX ) { event[nevents].tevent = timerp[j].tevent_stop; event[nevents].timer = j; event[nevents].line = timerp[j].line2; event[nevents].pos = timerp[j].pos2; event[nevents].tpos = 2; event[nevents].beg = timerp[j].resolv_beg; event[nevents].flag = timerp[j].flag_stop & SEC_EVENT; ddflag |= timerp[j].flag_stop & TIME_EVENTS; nevents++; } j++; } (void)fprintf( fd, "TIMED EVENTS as expanded and included in uploaded Timers\n"); if ( nevents == 0 ) { (void)fprintf( fd, "-- None --\n\n" ); retcode = 0; } else retcode = 1; /* Sort the events */ qsort((void *)event, nevents, sizeof(struct ev_s), fptr); if ( nevents > 0 ) { if ( ddflag & (DAWN_EVENT | DUSK_EVENT) ) { (void)fprintf( fd, "Line Week- Interval Sched/ Macro/ Dawn/Dusk\n"); (void)fprintf( fd, "-Pos days Beg End Date begin Date end Expand A.E.T. Error\n"); (void)fprintf( fd, "---- ------- --- --- ---------- ---------- ----- ----- --------\n"); } else { (void)fprintf( fd, "Line Week- Interval Sched/ Macro/\n"); (void)fprintf( fd, "-Pos days Beg End Date begin Date end Expand A.E.T.\n"); (void)fprintf( fd, "---- ------- --- --- ---------- ---------- ----- ------\n"); } } /* Isolate a group of events to print */ line = event[0].line; pos = event[0].pos; sflag = event[0].flag; j1 = j2 = 0; while ( j1 < nevents ) { while ( j2 < nevents && event[j2].line == line && event[j2].pos == pos && event[j2].flag == sflag ) { j2++; } /* Find the original tevent(s)/timer(s) which gave birth */ /* to this event and display them. */ ncomb = 0; j = event[j1].tevent; count = teventp[j].print; while ( count > 0 && j >= 0 ) { if ( teventp[j].generation == 0 ) { teventp[j].flag |= ACTIVE_EVENT; } if ( teventp[j].flag & PRT_EVENT ) { prtlist[ncomb++] = j; count--; } j = teventp[j].plink; } ddoptions = 0; for ( k = ncomb - 1; k >= 0; k-- ) { m = prtlist[k]; j = teventp[m].timer; macro = teventp[m].macro; flag = teventp[m].flag; offset = teventp[m].offset; pos = teventp[m].pos; secur = sec_flag[flag & SEC_EVENT ? SECURITY_ON & 0x03 : 0]; line = timerp[j].line_no; ddoptions |= timerp[j].ddoptions; if ( /* teventp[m].*/ flag & COMB_EVENT ) (void)fprintf( fd, " ** "); else (void)fprintf(fd, "%02d-%d ", line, pos); if ( timerp[j].notify >= 0 ) { (void)fprintf( fd, "%s expire-%-3d ", bmap2dow(timerp[j].dow_bmap), timerp[j].notify); } else { sched_end = timerp[j].sched_end; if ( sched_end > 1231 ) { /* Remove the kluge for reversed date range */ sched_end -= 1200; } (void)fprintf( fd, "%s %02d/%02d %02d/%02d", bmap2dow(timerp[j].dow_bmap), timerp[j].sched_beg / 100, timerp[j].sched_beg % 100, sched_end / 100, sched_end % 100); } if ( flag & CLOCK_EVENT ) { (void)fprintf( fd, " %02d:%02d%c %s%s\n", offset / 60, offset % 60, secur, macrop[macro].label, display_timer_options(timerp, j)); } else if ( flag & DAWN_EVENT ) { (void)sprintf(minibuf, " dawn%+d%c", offset, secur); (void)fprintf( fd, "%-15s %s%s\n", minibuf, macrop[macro].label, display_timer_options(timerp, j)); } else if ( flag & DUSK_EVENT ) { (void)sprintf(minibuf, " dusk%+d%c", offset, secur); (void)fprintf( fd, "%-15s %s%s\n", minibuf, macrop[macro].label, display_timer_options(timerp, j)); } else { (void)fprintf( fd, "\n"); } m = teventp[m].link; } /* Now display events as expanded by heyu. */ endprev = 0; for ( k = j1; k < j2; k++ ) { j = event[k].timer; if ( event[k].tpos == 1 ) { offset = timerp[j].offset_start; flag = timerp[j].flag_start; secur = sec_flag[flag & SEC_EVENT ? SECURITY_ON & 0x03 : 0]; secnom = flag & SEC_EVENT ? SECURITY_OFFSET_ADJUST : 0; macro = timerp[j].macro_start; error = timerp[j].error_start; } else { offset = timerp[j].offset_stop; flag = timerp[j].flag_stop; secur = sec_flag[flag & SEC_EVENT ? SECURITY_ON & 0x03 : 0]; secnom = flag & SEC_EVENT ? SECURITY_OFFSET_ADJUST : 0; macro = timerp[j].macro_stop; error = timerp[j].error_stop; } if ( flag & SEC_EVENT ) (void)strncpy2(note, "s", sizeof(note) - 1); else note[0] = '\0'; if ( flag & SUBST_EVENT ) (void)strncpy2(note, " r", sizeof(note) - 1); /* Display blank line if there is a gap in dates */ /* as the result of timer options. */ if ( k > j1 && ddoptions && timerp[j].resolv_beg != (endprev + 1)) (void)fprintf( fd, "\n"); endprev = timerp[j].resolv_end; (void)fprintf( fd, " %s %03d-%03d", bmap2dow(timerp[j].dow_bmap), timerp[j].resolv_beg, timerp[j].resolv_end); /* Determine the actual event execution legal time */ legal = offset + macrop[macro].delay + secnom; tadj = time_adjust(timerp[j].resolv_beg + dayzero, legal, STD2LGL); if ( time_adjust(timerp[j].resolv_end + dayzero, legal, STD2LGL) != tadj ) (void)strncat(note, "*", sizeof(note) - 1 - strlen(note)); legal += tadj; if ( legal < 0 ) { legal += 1440; shift = '<'; } else if ( legal > 1439 ) { legal -= 1440; shift = '>'; } else { shift = ' '; } yday2date(jan1day, timerp[j].resolv_beg + dayzero, &year, &month, &day, &wday); (void)fprintf( fd, " %02d/%02d/%04d-", month, day, year); yday2date(jan1day, timerp[j].resolv_end + dayzero, &year, &month, &day, &wday); (void)fprintf( fd, "%02d/%02d/%04d", month, day, year); if ( flag & (DAWN_EVENT | DUSK_EVENT) ) (void)fprintf( fd, " %02d:%02d%c %c%02d:%02d%-4s[%2d]\n", offset / 60, offset % 60, secur, shift, legal / 60, legal % 60, note, error); else (void)fprintf( fd, " %02d:%02d%c %c%02d:%02d%-4s\n", offset / 60, offset % 60, secur, shift, legal / 60, legal % 60, note); } (void)fprintf( fd, "\n"); j1 = j2 ; line = event[j1].line; pos = event[j1].pos; sflag = event[j1].flag; } /* Display LOST events */ (void)fprintf( fd, "TIMED EVENTS day-shifted out of this period.\n"); j = startchain; nevents = 0; while ( j >= 0 ) { if ( teventp[j].flag & LOST_EVENT ) prtlist[nevents++] = j; j = teventp[j].link; } if ( nevents == 0 ) { (void)fprintf( fd, "-- None --\n\n"); } else { (void)fprintf( fd, "Line Wdays Beg End Date begin Date end Time Macro\n"); (void)fprintf( fd, "---- ------- --- --- ---------- ---------- ----- -----\n"); for ( k = 0; k < nevents; k++ ) { j = prtlist[k]; lostday = teventp[j].lostday; yday2date(jan1day, lostday + dayzero , &year, &month, &day, &wday); if ( teventp[j].flag & COMB_EVENT ) (void)fprintf( fd, " ** "); else (void)fprintf( fd, "%02d-%d ", teventp[j].line_no, teventp[j].pos); (void)fprintf( fd, "%s %03d-%03d %02d/%02d/%4d-%02d/%02d/%4d %02d:%02d%c %s\n", bmap2dow(teventp[j].dow_bmap), lostday, lostday, month, day, year, month, day, year, teventp[j].offset / 60, teventp[j].offset % 60, sec_flag[teventp[j].flag & SEC_EVENT ? SECURITY_ON & 0x03 : 0], macrop[teventp[j].macro].label); } (void)fprintf( fd, "\n\n"); } /* Display CANCELLED events */ (void)fprintf( fd, "TIMED EVENTS skipped per Timer options.\n"); j = startchain; nevents = 0; while ( j >= 0 ) { if ( teventp[j].flag & CANCEL_EVENT ) prtlist[nevents++] = j; j = teventp[j].link; } if ( nevents == 0 ) { (void)fprintf( fd, "-- None --\n\n"); } else { (void)fprintf( fd, "Line Wdays Beg End Date begin Date end StdTime Macro\n"); (void)fprintf( fd, "---- ------- --- --- ---------- ---------- ------- -----\n"); for ( k = 0; k < nevents; k++ ) { j = prtlist[k]; beg = teventp[j].resolv_beg; end = teventp[j].resolv_end; offset = teventp[j].offset; macro = teventp[j].macro; flag = teventp[j].flag; secur = sec_flag[flag & SEC_EVENT ? SECURITY_ON & 0x03 : 0]; yday2date(jan1day, beg + dayzero, &year, &month, &day, &wday); yday2date(jan1day, end + dayzero, &year2, &month2, &day2, &wday); if ( teventp[j].flag & COMB_EVENT ) (void)fprintf( fd, " ** "); else (void)fprintf( fd, "%02d-%d ", teventp[j].line_no, teventp[j].pos); (void)fprintf( fd, "%s %03d-%03d %02d/%02d/%4d-%02d/%02d/%4d", bmap2dow(teventp[j].dow_bmap), beg, end, month, day, year, month2, day2, year2); if ( flag & CLOCK_EVENT ) { (void)sprintf(minibuf, " %02d:%02d%c", offset / 60, offset % 60, secur); (void)fprintf( fd, "%-10s %s\n", minibuf, macrop[macro].label); } else if ( flag & DAWN_EVENT ) { (void)sprintf(minibuf, " dawn%+d%c", offset, secur); (void)fprintf( fd, "%-10s %s\n", minibuf, macrop[macro].label); } else if ( flag & DUSK_EVENT ) { (void)sprintf(minibuf, " dusk%+d%c", offset, secur); (void)fprintf( fd, "%-10s %s\n", minibuf, macrop[macro].label); } else { (void)fprintf( fd, "\n"); } } (void)fprintf( fd, "\n\n"); } /* Display the tevents not included in this period */ (void)fprintf( fd, "TIMED EVENTS from schedule not active during this period.\n"); j = startchain; nevents = 0; while ( j >= 0 ) { if ( teventp[j].generation == 0 && (teventp[j].flag & ACTIVE_EVENT) == 0 ) prtlist[nevents++] = j; j = teventp[j].link; } if ( nevents == 0 ) { (void)fprintf( fd, "-- None --\n\n"); return retcode; } else { (void)fprintf( fd, "Line Wdays Beg End Date begin Date end Time Macro\n"); (void)fprintf( fd, "---- ------- --- --- ---------- ---------- ----- -----\n"); for ( k = 0; k < nevents; k++ ) { m = prtlist[k]; teventp[m].flag &= ~PRT_EVENT; j = teventp[m].timer; macro = teventp[m].macro; if ( teventp[m].pos == 1 ) { offset = timerp[j].offset_start; flag = timerp[j].flag_start; secur = sec_flag[flag & SEC_EVENT ? SECURITY_ON & 0x03 : 0]; line = timerp[j].line_no; pos = 1; } else { offset = timerp[j].offset_stop; flag = timerp[j].flag_stop; secur = sec_flag[flag & SEC_EVENT ? SECURITY_ON & 0x03 : 0]; line = timerp[j].line_no; pos = 2; } if ( teventp[m].flag & COMB_EVENT ) (void)fprintf( fd, " ** "); else (void)fprintf(fd, "%02d-%d ", line, pos); if ( timerp[j].notify >= 0 ) { (void)fprintf( fd, "%s expire-%-3d ", bmap2dow(timerp[j].dow_bmap), timerp[j].notify); } else { sched_end = timerp[j].sched_end; if ( sched_end > 1231 ) { /* Remove the kluge for reversed date range */ sched_end -= 1200; } (void)fprintf( fd, "%s %02d/%02d %02d/%02d", bmap2dow(timerp[j].dow_bmap), timerp[j].sched_beg / 100, timerp[j].sched_beg % 100, sched_end / 100, sched_end % 100); } if ( flag & CLOCK_EVENT ) { (void)fprintf( fd, " %02d:%02d%c %s\n", offset / 60, offset % 60, secur, macrop[macro].label); } else if ( flag & DAWN_EVENT ) { (void)fprintf( fd, " dawn%-+3d%c %s\n", offset, secur, macrop[macro].label); } else if ( flag & DUSK_EVENT ) { (void)fprintf( fd, " dusk%-+3d%c %s\n", offset, secur, macrop[macro].label); } else { (void)fprintf( fd, "\n"); } } } return retcode; } /*---------------------------------------------------------------------+ | Display timers for final_report() | | Also check for stop time = start time error and return 1 if so, | | otherwise return 0 if OK. | +---------------------------------------------------------------------*/ int display_timers( FILE *fd, TIMER *timerp, TEVENT *teventp, MACRO *macrop ) { extern int current_timer_generation; int j, maxlabel, status; char *sec_flag = " sss"; if ( !timerp ) { (void)fprintf( fd, "\n"); return 0; } /* Get maximum length of "Start" macro labels used in the timers */ maxlabel = 0; j = 0; while ( timerp[j].line_no > 0 ) { if ( timerp[j].generation == current_timer_generation ) { maxlabel = max(maxlabel, (int)strlen(macrop[timerp[j].macro_start].label)); } j++; } (void) fprintf( fd, "OUTPUT TIMERS - Times in minutes after midnight Standard Time\n-------------\n"); (void) fprintf( fd, "(Codes: C = Clock, R = Dawn, S = Dusk, X = null, s = Security)\n"); if ( configp->display_offset == YES ) (void) fprintf( fd, "Loc "); (void) fprintf( fd, "Line Line WkDay Beg End F Time F Time %-*s Macro\n", maxlabel, "Macro"); if ( configp->display_offset == YES ) (void) fprintf( fd, "--- "); (void) fprintf( fd, "---- ---- ------- --- --- - ---- - ---- %-*s -----\n", maxlabel, "-----"); j = 0; status = 0; while ( timerp[j].line_no > 0 ) { if ( timerp[j].generation != current_timer_generation ) { j++; continue; } /* Display the offset of the timer in the memory image */ if ( configp->display_offset == YES ) (void) fprintf( fd, "%03x ", timerp[j].memloc); /* Override line number and position for a combined event because */ /* it's not valid anyway; otherwise display it. */ if ( teventp[timerp[j].tevent_start].flag & COMB_EVENT ) (void) fprintf( fd, " ** " ); else (void) fprintf( fd, "%02d-%1d ",timerp[j].line1, timerp[j].pos1); if ( timerp[j].flag_stop == NO_EVENT ) (void) fprintf( fd, "---- " ); else if ( teventp[timerp[j].tevent_stop].flag & COMB_EVENT ) (void) fprintf( fd, " ** " ); else (void) fprintf( fd, "%02d-%1d ",timerp[j].line2, timerp[j].pos2); /* Display the rest of the data. */ (void) fprintf( fd, "%7s %03d-%03d %c %04d%c %c %04d%c %-*s %s\n", bmap2dow(timerp[j].dow_bmap), timerp[j].resolv_beg, timerp[j].resolv_end, flag_def(timerp[j].flag_start & TIME_EVENTS), timerp[j].offset_start, sec_flag[timerp[j].flag_start & SEC_EVENT ? SECURITY_ON & 0x03 : 0], flag_def(timerp[j].flag_stop & TIME_EVENTS), timerp[j].offset_stop, sec_flag[timerp[j].flag_stop & SEC_EVENT ? SECURITY_ON & 0x03 : 0], maxlabel, macrop[timerp[j].macro_start].label, macrop[timerp[j].macro_stop].label); if ( timerp[j].offset_stop == timerp[j].offset_start ) { fprintf(fd, "***** Internal error: stop time = start time *****\n"); fprintf(fd, " CM11A bug! - will not launch macro '%s'\n", macrop[timerp[j].macro_stop].label); status = 1; } j++; } (void)fprintf( fd, "\n"); return status; } /*---------------------------------------------------------------------+ | Generate a final report of Timers, Triggers, and Macros in use. | +---------------------------------------------------------------------*/ int final_report ( char *pathname, CALEND *calendp, TIMER *timerp, TEVENT *teventp, TRIGGER *triggerp, MACRO *macrop, unsigned char *elementp, int proc_code) { FILE *fd; int j; int original, current; int status; static char *off_on[] = {"off", "on "}; original = 0; current = update_current_timer_generation(); if ( !(fd = fopen(pathname, "w")) ) { (void) fprintf(stderr, "Unable to open report file '%s' for writing.\n", pathname); return -1; } (void)fprintf( fd, "Report of expanded Timers, Triggers, and Macros\n"); (void)fprintf( fd, "===============================================\n"); /* Display Config & Schedule files and the Date and Time created */ (void)fprintf( fd, "Version: %s\n", VERSION); (void)fprintf( fd, "Config File: %s\n", pathspec(heyu_config)); (void)fprintf( fd, "Schedule File: %s\n", schedfile); (void)fprintf( fd, "Report Date: %s\n\n", legal_time_string()); (void)fprintf( fd, "Upload schedule to the interface: %s\n\n", (proc_code == PROC_UPLOAD) ? "YES" : "NO" ); display_config_overrides( fd ); if ( calendp->asif_flag & (ASIF_DATE | ASIF_TIME) ) (void)fprintf( fd, "Simulation as if %s\n", asif_time_string()); if ( configp->mode == HEYU_MODE ) (void)fprintf( fd, "Configured for HEYU mode; Programmed for %d Days.\n\n", configp->program_days); else (void)fprintf( fd, "Configured for COMPATIBLE mode.\n\n"); (void)fprintf( fd, "Dawn/Dusk defined as %s\n\n", ((configp->sunmode == RiseSet) ? "Sunrise/Sunset" : (configp->sunmode == CivilTwi) ? "Civil Twilight" : (configp->sunmode == NautTwi) ? "Nautical Twilight" : (configp->sunmode == AstroTwi) ? "Astronomical Twilight" : "???")); (void) fprintf( fd, "Actual Event Times (A.E.T.) include macro delay, if any.\n"); (void) fprintf( fd, "Schedule Times and A.E.T. are Civil Time.\n"); (void) fprintf( fd, "Expanded times are Standard Time.\n\n"); (void) fprintf( fd, "Symbols used:\n"); (void) fprintf( fd, " ** Combined event.\n"); (void) fprintf( fd, " * Time change during interval.\n"); (void) fprintf( fd, " >/< Next/Previous day.\n"); (void) fprintf( fd, " r Heyu-generated random initial security day.\n\n"); /* Display individual timed events included in timers */ display_events( fd, timerp, teventp, macrop, calendp ); (void)fprintf( fd, "\n"); /* Display the uploaded timers */ status = display_timers( fd, timerp, teventp, macrop ); (void)fprintf( fd, "\n"); /* Display triggers */ (void) fprintf( fd, "TRIGGERS\n--------\n"); j = 0; while ( triggerp && triggerp[j].line_no > 0 ) { if ( configp->display_offset == YES ) (void) fprintf( fd, "%03x ", triggerp[j].memloc); (void) fprintf( fd, "trigger %c%-2d %3s %s\n", code2hc(triggerp[j].housecode), code2unit(triggerp[j].unitcode), off_on[triggerp[j].command], macrop[triggerp[j].macro].label); j++; } if ( triggerp == NULL ) (void) fprintf( fd, "-- None --\n"); (void) fprintf( fd, "\n"); /* Display uploaded macros */ (void) fprintf( fd, "\nMACROS uploaded ( * denotes modified delay and/or compression)\n---------------\n"); (void) display_macros( fd, USED, macrop, elementp ); /* Display unused macros */ (void)fprintf(fd,"\nMACROS unused/combined and omitted from upload\n"); (void)fprintf(fd,"----------------------------------------------\n"); if ( !display_macros( fd, NOTUSED, macrop, elementp ) ) (void)fprintf( fd, "-- None --\n"); display_eeprom_usage( fd, timerp, triggerp, macrop ); fclose( fd ); return status; } /*---------------------------------------------------------------------+ | Store in global variable heyu_path the directory containing the | | heyu configuration file and check if this directory is writable. | | Store the actual configuration filename in heyu_configp-> | | Set flag is_writable if heyu_path is writable. | +---------------------------------------------------------------------*/ void find_heyu_path( void ) { extern int is_writable; extern int verbose; extern int i_am_relay; FILE *fd; char *sp, *sp1; int pid; char subdir[PATH_LEN + 1]; char tmpfile[PATH_LEN + 1]; struct stat statbuf; /* Find the configuration file. */ for (;;) { /* Look for command line option or X10CONFIG environment variable */ if ( (sp = optptr->configp) || (!optptr->subp && (sp = getenv("X10CONFIG"))) ) { if ( (sp1 = strrchr(sp, '/')) == NULL ) { (void)strncpy2(heyu_path, "./", sizeof(heyu_path) - 1); (void)strncpy2(heyu_config, sp, sizeof(heyu_config) - 1); } else { sp1++; (void)strncpy2(heyu_path, sp, sp1 - sp); (void)strncpy2(heyu_config, sp1, sizeof(heyu_config) - 1); } if ( !(*heyu_config) ) { (void)fprintf(stderr, "No filename specified for '%s'\n", sp); exit(1); } /* Verify there is a readable config file */ if ( verbose && i_am_relay != 1 ) (void)printf("Searching for '%s'\n", pathspec(heyu_config)); if ( stat(pathspec(heyu_config), &statbuf) != 0 || !(statbuf.st_mode & S_IFREG) ) { (void)fprintf(stderr, "Bad configuration file '%s' specified.\n", pathspec(heyu_config)); exit(1); } if ( (fd = fopen(pathspec(heyu_config), "r")) ) { (void)fclose(fd); if ( verbose && i_am_relay != 1 ) (void)printf("Found configuration file '%s'\n", pathspec(heyu_config) ); break; } else { (void)fprintf(stderr, "Unable to find (or open) Heyu configuration file '%s'\n", pathspec(heyu_config) ); exit(1); } } /* Next check for subdirectory command line option */ /* (-0 ... -9) or HEYUSUB environment variable. */ if ( (sp = optptr->subp) ) { sprintf(subdir, "%s%s/", HEYUSUB_PREFIX, sp); } else if ( (sp = getenv("HEYUSUB")) ) { /* Remove leading / if any */ if ( *sp == '/' ) (void)strncpy2(subdir, sp + 1, sizeof(subdir) - 1); else (void)strncpy2(subdir, sp, sizeof(subdir) - 1); if ( *(sp = subdir + (strlen(subdir) - 1)) != '/' ) (void)strncat(subdir, "/", sizeof(subdir) - 1 - strlen(subdir)); } else { *subdir = '\0'; } /* Look in the user's $HOME/HOMEBASEDIR/ directory */ if ( (sp = getenv("HOME")) ) { (void)strncpy2(heyu_path, sp, sizeof(heyu_path) - 1); if ( *(sp = heyu_path + strlen(heyu_path) - 1) != '/') (void)strncat(heyu_path, "/", sizeof(heyu_path) - 1 - strlen(heyu_path)); (void)strncat(heyu_path, HOMEBASEDIR, sizeof(heyu_path) - 1 - strlen(heyu_path)); (void)strncat(heyu_path, subdir, sizeof(heyu_path) - 1 - strlen(heyu_path)); /* Verify there is a readable config file */ if ( verbose && i_am_relay != 1 ) (void)printf("Searching for '%s'\n", pathspec(CONFIG_FILE)); if ( (fd = fopen(pathspec(CONFIG_FILE), "r")) ) { (void)fclose(fd); (void)strncpy2(heyu_config, CONFIG_FILE, sizeof(heyu_config) - 1); break; } } /* Next look in SYSBASEDIR directory (or subdirectory thereof) */ (void)sprintf(heyu_path, "%s/%s", SYSBASEDIR, subdir); /* Verify there is a readable config file */ if ( verbose && i_am_relay != 1 ) (void)printf("Searching for '%s'\n", pathspec(CONFIG_FILE_ETC)); if ( (fd = fopen(pathspec(CONFIG_FILE_ETC), "r")) ) { (void)fclose(fd); (void)strncpy2(heyu_config, CONFIG_FILE_ETC, sizeof(heyu_config) - 1); break; } else if ( !i_am_relay ) { (void)fprintf(stderr, "Unable to find Heyu configuration file.\n"), exit(1); } } if ( verbose && i_am_relay != 1 ) (void)printf("Found configuration file '%s'\n", pathspec(heyu_config)); snprintf(tmpfile, sizeof(tmpfile) - 1, "X10CONFIG=%s", pathspec(heyu_config)); putenv(strdup(tmpfile)); /* Verify the heyu_path directory is readable and writable by */ /* creating and writing to a temporary file, then reading back */ /* what we wrote. */ sprintf(tmpfile, "heyu.%d.tmp", (int)getpid()); if ( (fd = fopen(pathspec(tmpfile), "w")) ) { fprintf(fd, "%d\n", (int)getpid()); fclose(fd); } else { is_writable = 0; (void)fprintf(stderr, "Unable to write in Heyu directory '%s'\n", heyu_path); return; } if ( (fd = fopen(pathspec(tmpfile), "r")) ) { fscanf(fd, "%d\n", &pid); fclose(fd); if ( pid == getpid() ) { remove(pathspec(tmpfile)); is_writable = 1; if ( verbose && i_am_relay != 1 ) (void)printf("Heyu directory %s is writable.\n", heyu_path); return; } } else { fprintf(stderr, "Read error while testing Heyu directory writeability\n"); remove(pathspec(tmpfile)); is_writable = 0; } return; } /*----------------------------------------------------------------------------+ | Compress the elements in a macro by merging the unit bmaps for commands | | which are otherwise similar for the same housecodes and removing | | duplicate commands, e.g.: | | A1 ON; B1 OFF; A2 ON --> A1,2 ON; B1 OFF | | A1 ON; A1 OFF; A1 ON --> A1 ON; A1 OFF | | | | Element ordering is otherwise preserved to the extent possible. | | | | Commands like DIM are merged only if they have the same "data", e.g. the | | same dim values. | +----------------------------------------------------------------------------*/ int compress_elements ( unsigned char *elemlist, int *nelem, int *total ) { int j, k, m, matched; int indxj, lenj, unitj, cmdlen, outelem, ntot; int *index, *length, *done; unsigned char hcfunj, cmdcode; unsigned char *outbuff; static int sizint = sizeof(int); static int sizuchr = sizeof(unsigned char); index = calloc( *total, sizint ); length = calloc( *total, sizint ); done = calloc( *total, sizint ); outbuff = calloc( *total, sizuchr ); if ( index == NULL || length == NULL || done == NULL || outbuff == NULL ) { fprintf(stderr, "compress_elements() - Unable to allocate memory.\n"); exit(1); } j = 0; k = 0; while ( j < *total ) { index[k] = j; done[k] = 0; cmdcode = elemlist[j] & 0x0fu; /* Find the length of the macro element in the master table */ if ( (cmdlen = macro_element_length(cmdcode)) <= 0 ) { (void)fprintf(stderr, "compress_elements(): Internal table error.\n"); return 0; } j += length[k] = cmdlen; k++; } if ( k != *nelem ) { (void)fprintf(stderr, "compress_elements(): Mismatch in number of elements.\n"); return 0; } ntot = 0; outelem = 0; for ( j = 0; j < *nelem; j++ ) { if ( done[j] ) continue; done[j] = 1; indxj = index[j]; hcfunj = elemlist[indxj]; lenj = length[j]; unitj = ntot + 1; for ( k = indxj; k < indxj + lenj; k++ ) outbuff[ntot++] = elemlist[k]; outelem++; for ( k = j + 1; k < *nelem; k++ ) { if ( done[k] ) continue; if ( hcfunj == elemlist[index[k]] && lenj == length[k] ) { /* Compare the element data bytes */ matched = 1; for ( m = 3; m < lenj; m++ ) { if ( elemlist[indxj + m] != elemlist[index[k] + m] ) { matched = 0; break; } } if ( matched ) { /* Combine the unit bmap */ outbuff[unitj] |= elemlist[index[k] + 1]; outbuff[unitj + 1] |= elemlist[index[k] + 2]; done[k] = 1; } } } } for ( j = 0; j < ntot; j++ ) elemlist[j] = outbuff[j]; *nelem = outelem; *total = ntot; free(index); free(length); free(done); free(outbuff); return 1; } /*----------------------------------------------------------------------------+ | Compress the elements in all macros. | +----------------------------------------------------------------------------*/ void compress_macros ( MACRO *macrop, unsigned char *elementp ) { int j, oldtotal; unsigned char *ptr; j = 1; while ( macrop[j].line_no > 0 ) { oldtotal = macrop[j].total; ptr = &elementp[macrop[j].element]; if ( !compress_elements(ptr, ¯op[j].nelem, ¯op[j].total) ) exit(1); if ( macrop[j].total < oldtotal ) macrop[j].modflag |= COMPRESSED; j++; } return; } /*---------------------------------------------------------------------+ | Read a 1024 byte file containing a CM11a memory image and upload | | it to the CM11a interface. | +---------------------------------------------------------------------*/ int upload_image_from_file ( char *pathspec ) { FILE *fd; int size; unsigned char prommap[2 * PROMSIZE]; extern void upload_eeprom_image(unsigned char *); if ( !(fd = fopen(pathspec, "r")) ) { (void)fprintf(stderr, "Unable to read file '%s'\n", pathspec); exit(1); } /* Try reading more than 1024 bytes to verify the size is correct */ size = fread( (void *)prommap, 1, 2 * PROMSIZE, fd); if ( size != PROMSIZE ) { (void)fprintf(stderr, "File size is not %d bytes.\n", PROMSIZE); exit(1); } /* Upload the image to the interface */ upload_eeprom_image( prommap ); return 0; } /*---------------------------------------------------------------------+ | Handle Heyu 'upload' command and arguments. | +---------------------------------------------------------------------*/ int c_upload ( int argc, char *argv[] ) { extern int is_writable; int retcode; if ( invalidate_for_cm10a() != 0 ) return 1; if ( argc == 2 ) { if ( !is_writable ) { (void)fprintf(stderr, "Heyu directory %s must be writable - quitting.\n", heyu_path); return 1; } retcode = process_data(PROC_UPLOAD); return retcode; } if ( argc == 3 ) { if ( strcmp("status", argv[2]) == 0 ) { display_cm11a_status(1); retcode = 0; } else if ( strcmp("cronstatus", argv[2]) == 0 ) { display_cm11a_status(0); retcode = 0; } else if ( !is_writable ) { (void)fprintf(stderr, "Heyu directory %s must be writable - quitting.\n", heyu_path); retcode = 1; } else if ( strcmp("check", strlower(argv[2])) == 0 ) { retcode = process_data(PROC_CHECK); } else if ( strcmp("croncheck", argv[2]) == 0 ) { retcode = crontest(); } else { (void)fprintf(stderr, "Usage: %s %s [check|croncheck|status|cronstatus]\n", argv[0], argv[1]); retcode = 1; } } else if ( argc == 4 && strcmp("imagefile", strlower(argv[2])) == 0 ) { /* Undocumented - for experimental use only */ retcode = upload_image_from_file(argv[3]); } else { (void)fprintf(stderr, "Usage: %s %s [check|croncheck|status|cronstatus]\n", argv[0], argv[1]); retcode = 1; } return retcode; } /*---------------------------------------------------------------------+ | Usage messages for 'heyu utility' | +---------------------------------------------------------------------*/ void display_utility_usage( int argc, char *argv[] ) { int j = 0; char *optmsg[] = { "syscheck Display your system calendar/clock configuration.\n", "dawndusk Display Dawn/Dusk today, per Dawn/Dusk definition", " in configuration file.\n", "suntable [-r|c|n|a|s|w] [yyyy]", " Write a file of daily dawn/dusk times (by default", " per Dawn/Dusk definition in configuration file)", " for the current year or year yyyy.", " Override options:", " -r Display Sunrise and Sunset", " -c Display Civil Twilights", " -n Display Nautical Twilights", " -a Display Astronomical Twilights", " Other options:", " -s Display Standard Time instead of Civil Time", " -w Wide format. (Printing on US letter or A4 size paper", " requires Landscape mode and 8-pt fixed font.)\n", "calibrate Calibration for fast timing loops.\n", "", }; (void)fprintf(stdout, "%s %s options:\n", argv[0], argv[1]); while ( *(optmsg[j]) != '\0' ) (void)fprintf(stdout, " %s\n", optmsg[j++]); return; } /*---------------------------------------------------------------------+ | Set the various status bits described in Sec 8 of protocol.txt | +---------------------------------------------------------------------*/ int c_set_status ( int argc, char *argv[] ) { extern int c_set_status_bits( unsigned char ); if ( strcmp("newbattery", strlower(argv[1])) == 0 ) { return c_set_status_bits( RESET_BATTERY_TIMER ); } else if ( strcmp("purge", strlower(argv[1])) == 0 ) { return c_set_status_bits( PURGE_DELAYED_MACROS ); } else if ( strcmp("clear", strlower(argv[1])) == 0 ) { return c_set_status_bits( MONITORED_STATUS_CLEAR ); } else if ( strcmp("reserved", strlower(argv[1])) == 0 ) { /* Undocumented */ /* This one to test whether the "reserved" bit actually */ /* doesn't do anything. */ return c_set_status_bits( RESERVED_STATUS_BIT ); } return 0; } /*---------------------------------------------------------------------+ | Handle Heyu 'utility' command and arguments. | | (There's no spool file open so commands are restricted.) | +---------------------------------------------------------------------*/ int c_utility ( int argc, char *argv[] ) { int j, sunmode, timemode, format, year; time_t utc0_dawn, utc0_dusk; time_t now; struct tm *tmp; char *sp; static char *sunmodelabel[] = {"Sunrise/Sunset", "Civil Twilight", "Nautical Twilight", "Astronomical Twilight"}; unsigned long loopcount; unsigned long loop_calibrate ( void ); if ( argc >= 3 ) { if ( strcmp("syscheck", strlower(argv[2])) == 0 ) { get_configuration(CONFIG_INIT); display_sys_calendar(); return 0; } else if ( strcmp("suntable", argv[2]) == 0 ) { get_configuration(CONFIG_INIT); if ( configp->loc_flag != (LATITUDE | LONGITUDE) ) { fprintf(stderr, "LATITUDE and/or LONGITUDE not specified in %s\n", pathspec(CONFIG_FILE)); return 1; } now = time(NULL); year = localtime(&now)->tm_year + 1900; sunmode = configp->sunmode; timemode = TIMEMODE_CIVIL; format = FMT_PORTRAIT; if ( argc > 3 && *argv[argc - 1] != '-' ) { year = (int)strtol(argv[argc - 1], &sp, 10); if ( !strchr(" /t/n", *sp) || year < 1970 || year > 2099 ) { fprintf(stderr, "Invalid year '%s'\n", argv[argc - 1]); return 1; } argc--; } for ( j = 3; j < argc; j++ ) { if ( *argv[j] != '-' || strlen(argv[j]) != 2 || !(sp = strchr("rcnasw", *(argv[j] + 1))) ) { fprintf(stderr, "Invalid parameter '%s'\n", argv[j]); return 1; } else if ( *sp == 'w' ) format = FMT_LANDSCAPE; else if ( *sp == 's' ) timemode = TIMEMODE_STANDARD; else { sunmode = ( *sp == 'r' ) ? RiseSet : ( *sp == 'c' ) ? CivilTwi : ( *sp == 'n' ) ? NautTwi : AstroTwi ; } } return write_sun_table(format, year, sunmode, timemode); } else if (strcmp("dawndusk", argv[2]) == 0 ) { get_configuration(CONFIG_INIT); fix_tznames(); if ( configp->loc_flag != (LATITUDE | LONGITUDE) ) { fprintf(stderr, "LATITUDE and/or LONGITUDE not specified in %s\n", pathspec(CONFIG_FILE)); return 1; } now = time(NULL); local_dawndusk(now, &utc0_dawn, &utc0_dusk); tmp = localtime(&utc0_dawn); printf("Dawn = %02d:%02d %s", tmp->tm_hour, tmp->tm_min, heyu_tzname[tmp->tm_isdst]); tmp = localtime(&utc0_dusk); printf(" Dusk = %02d:%02d %s", tmp->tm_hour, tmp->tm_min, heyu_tzname[tmp->tm_isdst]); printf(" (%s)\n", sunmodelabel[configp->sunmode]); return 0; } else if (strcmp("calibrate", argv[2]) == 0 ) { printf("Calibrating for fast timing loops.\n"); loopcount = loop_calibrate(); /* countdown for 1 second */ if ( loopcount == 0 ) { fprintf(stderr, "Internal error: Calibration failed - possible overflow.\n"); return 1; } printf("Paste this line into your Heyu configuration file:\n"); printf("TIMER_LOOPCOUNT %lu\n", loopcount); return 0; } else { display_utility_usage( argc, argv ); return 1; } } else { display_utility_usage( argc, argv ); return 1; } return 0; } /*---------------------------------------------------------------------+ | Display aliases from user's config file. | +---------------------------------------------------------------------*/ void show_aliases ( ALIAS *aliasptr ) { int j, count, maxlabel, maxunits; char hc; printf(" [Aliases]\n"); if ( aliasptr == (ALIAS *)NULL ) { printf(" -none-\n\n"); return; } j = 0; count = 0; maxlabel = maxunits = 0; while ( aliasptr[j].label[0] != '\0' ) { maxlabel = max(maxlabel, (int)strlen(aliasptr[j].label)); maxunits = max(maxunits, (int)strlen(bmap2units(aliasptr[j].unitbmap))); count++; j++; } if ( count == 0 ) { printf(" -none-\n"); return; } for ( hc = 'A'; hc <= 'P'; hc++ ) { for ( j = 0; j < count; j++ ) { if ( toupper((int)aliasptr[j].housecode) == hc ) { printf(" alias %-*s %c%-*s %s %s\n", maxlabel, aliasptr[j].label, aliasptr[j].housecode, maxunits, bmap2units(aliasptr[j].unitbmap), lookup_module_name(aliasptr[j].modtype), display_module_options(j)); } } } printf("\n"); return; } /*---------------------------------------------------------------------+ | Display scenes from user's config file. | +---------------------------------------------------------------------*/ void show_scenes ( SCENE *scenep ) { int j, count, maxlen; printf(" [Scenes]\n"); j = 0; count = 0; maxlen = 0; while ( scenep && scenep[j].label[0] != '\0' ) { if ( scenep[j].type == F_SCENE ) { maxlen = max(maxlen, (int)strlen(scenep[j].label)); count++; } j++; } if ( count == 0 ) { printf(" -none-\n\n"); return; } j = 0; while ( scenep && scenep[j].label[0] != '\0' ) { if ( scenep[j].type == F_SCENE ) printf(" scene %-*s %s\n", maxlen, scenep[j].label, scenep[j].body); j++; } printf("\n"); return; } /*---------------------------------------------------------------------+ | Display usersyns from user's config file. | +---------------------------------------------------------------------*/ void show_usersyns ( SCENE *scenep ) { int j, count, maxlen; printf(" [Usersyns]\n"); j = 0; count = 0; maxlen = 0; while ( scenep && scenep[j].label[0] != '\0' ) { if ( scenep[j].type == F_USYN ) { maxlen = max(maxlen, (int)strlen(scenep[j].label)); count++; } j++; } if ( count == 0 ) { printf(" -none-\n\n"); return; } j = 0; while ( scenep && scenep[j].label[0] != '\0' ) { if ( scenep[j].type == F_USYN ) { printf(" usersyn %-*s %s\n", maxlen, scenep[j].label, scenep[j].body); } j++; } printf("\n"); return; } /*---------------------------------------------------------------------+ | 'show' command #1 - for information which can be determined from | | the user's config file. Return 1 if command not recognized here. | +---------------------------------------------------------------------*/ int c_show1 ( int argc, char *argv[] ) { void x10state_show ( unsigned char ); void remove_x10state_file ( void ); void send_x10state_command ( unsigned char, unsigned char ); int read_x10state_file (void ); void show_housemap ( void ); void show_module_mask ( unsigned char ); void show_launcher ( unsigned char ); void show_powerfail_launcher ( void ); void show_sensorfail_launcher ( void ); void show_rfflood_launcher ( void ); void show_timeout_launcher ( void ); void show_all_launchers ( void ); char hc; if ( argc < 3 ) { printf("heyu show options:\n"); printf(" al[iases] Aliases defined in config file\n"); printf(" ar[med] Armed status of system (*)\n"); printf(" sc[enes] Scenes defined in config file\n"); printf(" se[nsors] Security sensor states (*)\n"); printf(" u[sersyns] Usersyns defined in config file\n"); printf(" m[odules] H Module attributes, housecode H\n"); printf(" l[aunchers] [H] Launchers, all or only housecode H or -p -s -r -t\n"); printf(" h[ousemap] [H] Overall system state, or details housecode H (*)\n"); printf(" da[wndusk] Display Dawn and Dusk times for today (*)\n"); printf(" di[mlevels] Dim levels of modules as percent (*)\n"); printf(" r[awlevels] Native levels of modules (*)\n"); printf(" f[lags] Flag states (*)\n"); printf(" ti[mers] Display active timers 1-16 (*)\n"); printf(" ts[tamp] Hu Date and time of last signal on Hu (*)\n"); printf(" g[roups] H Extended group assignments and levels (*)\n"); printf(" o[thers] Cumulative received address map (*) - clear with\n"); printf(" 'heyu initothers' or 'heyu initstate'\n"); printf(" (*) Require the heyu state engine to be running\n"); return 0; } get_configuration(CONFIG_INIT); if ( strlen(argv[2]) == 1 && strchr("asdt", *argv[2]) ) { fprintf(stderr, "'%s' is ambiguous - supply more characters\n", argv[2]); exit(1); } else if ( strncmp("aliases", argv[2], 2) == 0 ) show_aliases(configp->aliasp); else if ( strncmp("scenes", argv[2], 2) == 0 ) show_scenes(configp->scenep); else if ( strncmp("usersyns", argv[2], 1) == 0 ) show_usersyns(configp->scenep); else if ( strncmp("modules", argv[2], 1) == 0 ) { if ( argc < 4 ) { fprintf(stderr, "Housecode needed\n"); exit(1); } hc = toupper((int)(*argv[3])); if ( hc < 'A' || hc > 'P' ) { fprintf(stderr, "Invalid housecode '%s'\n", argv[3]); exit(1); } show_module_mask(hc2code(hc)); } else if ( strncmp("launchers", argv[2], 1) == 0 ) { if ( argc < 4 ) { show_all_launchers(); printf("\n"); } else if ( strncmp(argv[3], "-powerfail", 2) == 0 ) { show_powerfail_launcher(); printf("\n"); } else if ( strncmp(argv[3], "-sensorfail", 2) == 0 ) { show_sensorfail_launcher(); printf("\n"); } else if ( strncmp(argv[3], "-rfflood", 2) == 0 ) { show_rfflood_launcher(); printf("\n"); } else if ( strncmp(argv[3], "-timeout", 2) == 0 ) { show_timeout_launcher(); printf("\n"); } else { hc = toupper((int)(*argv[3])); if ( hc < 'A' || hc > 'P' ) { fprintf(stderr, "Invalid housecode '%s'\n", argv[3]); exit(1); } show_launcher(hc2code(hc)); printf("\n"); } } else { return 1; } return 0; } /*---------------------------------------------------------------------+ | 'show' command #2 - for information requiring the state engine be | | running. | +---------------------------------------------------------------------*/ int c_show2 ( int argc, char *argv[] ) { void x10state_show ( unsigned char ); int fetch_x10state ( void ); void show_housemap ( void ); void show_module_mask ( unsigned char ); void show_launcher ( unsigned char ); void show_all_dimlevels_raw ( void ); void show_sticky_addr ( void ); void show_flags (void ); int check_for_engine ( void ); int read_x10state_file ( void ); int show_signal_timestamp ( char * ); int show_security_sensors ( void ); void show_armed_status ( void ); int show_global_timers ( void ); int show_state_dawndusk (void ); void show_extended_groups ( unsigned char ); char hc; if ( check_for_engine() != 0 ) { fprintf(stderr, "State engine is not running.\n"); return 1; } if ( argc < 3 ) { fprintf(stderr, "Too few arguments\n"); return 1; } if ( read_x10state_file() != 0 ) { fprintf(stderr, "Unable to read state file.\n"); return 1; } if ( strncmp("housemap", argv[2], 1) == 0 ) { if ( argc == 3 ) { show_housemap(); } else { if ( fetch_x10state() != 0 ) return 1; hc = toupper((int)(*argv[3])); if ( hc < 'A' || hc > 'P' ) { fprintf(stderr, "Invalid housecode '%s'\n", argv[3]); exit(1); } x10state_show(hc2code(hc)); } } else if ( strncmp("dimlevels", argv[2], 2) == 0 ) { show_all_dimlevels(); } else if ( strncmp("rawlevels", argv[2], 1) == 0 ) { show_all_dimlevels_raw(); } else if ( strncmp("flags", argv[2], 1) == 0 ) { show_flags(); } else if ( strncmp("armed", argv[2], 2) == 0 ) { if ( fetch_x10state() != 0 ) return 1; show_armed_status(); } else if ( strncmp("sensors", argv[2], 2) == 0 ) { show_security_sensors(); } else if ( strncmp("others", argv[2], 1) == 0 ) { if ( fetch_x10state() != 0 ) return 1; show_sticky_addr(); } else if ( strncmp("tstamp", argv[2], 2) == 0 ) { if ( argc < 4 ) { fprintf(stderr, "Too few parameters.\n"); return 1; } else if ( argc > 4 ) { fprintf(stderr, "Too many parameters.\n"); return 1; } return show_signal_timestamp(argv[3]); } else if ( strncmp("timers", argv[2], 2) == 0 ) { if ( fetch_x10state() != 0 ) return 1; return show_global_timers(); } else if ( strncmp("dawndusk", argv[2], 2) == 0 ) { if ( fetch_x10state() != 0 ) return 1; return show_state_dawndusk(); } else if ( strncmp("groups", argv[2], 1) == 0 ) { if ( argc < 4 ) { fprintf(stderr, "Housecode needed\n"); return 1; } hc = toupper((int)(*argv[3])); if ( hc < 'A' || hc > 'P' ) { fprintf(stderr, "Invalid housecode '%s'\n", argv[3]); return 1; } show_extended_groups(hc2code(hc)); } else { fprintf(stderr, "Show argument '%s' not recognized.\n", argv[2]); exit(1); } return 0; } /*---------------------------------------------------------------------+ | Translate a macro from the image of an EEPROM in memory into | | direct commands and send to the CM11A as direct commands. | | Argument 'image' is a pointer to the 1024 byte image buffer in | | memory. Argument 'macaddr' is the offset of the macro in that | | buffer. | | The macro delay is ignored, and operation does not chain to a | | subsequent delayed macro as it would in actual CM11A operation. | | Return 0 if successful, otherwise 1 on first failure. | +---------------------------------------------------------------------*/ int send_macro_immediate ( unsigned char *image, unsigned int macaddr ) { unsigned char hcode, func = 0xff, nelem; unsigned char buf[16]; unsigned char *sp; unsigned int bitmap, staflag; int k, retcode = 0; int syncmode; int line_sync_mode(void); syncmode = line_sync_mode(); /* Ignore delay */ sp = image + macaddr + 1; nelem = *sp++; /* Sent each element */ for ( k = 0; k < (int)nelem; k++ ) { if ( sp < (image + 5) || sp > (image + 0x3ff) ) { fprintf(stderr, "Macro element is outside EEPROM image\n"); return 1; } hcode = (sp[0] & 0xf0u) >> 4; func = sp[0] & 0x0fu; bitmap = (sp[1] << 8) | sp[2]; staflag = 0; /* Send the address */ if ( send_address(hcode, bitmap, syncmode) != 0 ) { fprintf(stderr, "Unable to send address\n"); return 1; } /* Send the function */ if ( func == 4 || func == 5 ) { /* Dim or Bright function */ if ( sp[3] & 0x80u ) { /* Brighten before dimming */ buf[0] = 0x06 | (22 << 3); buf[1] = (sp[0] & 0xf0u) | 5; if ( (retcode = send_command(buf, 2, 0, syncmode)) != 0 ) break; } buf[0] = 0x06 | ((sp[3] & 0x7fu) << 3); buf[1] = sp[0]; if ( (retcode = send_command(buf, 2, 0, syncmode)) != 0 ) break; sp += 4; } else if ( func == 7 ) { /* Extended function */ buf[0] = 0x07u; buf[1] = (sp[0] & 0xf0u) | 0x07u; buf[2] = sp[3]; buf[3] = sp[4]; buf[4] = sp[5]; staflag = (buf[4] == 0x37) ? 1 : 0; /* Kluge "fix" for checksum 5A problem. */ /* CM11A seems to disregard the dim field */ /* in the header byte. */ if ( checksum(buf, 5) == 0x5A && configp->fix_5a == YES ) buf[0] = 0x0F; if ( (retcode = send_command(buf, 5, staflag, syncmode)) != 0 ) break; sp += 6; } else if ( func == 15 ) { /* Status Request function */ staflag = 1; buf[0] = 0x06; buf[1] = sp[0]; if ( (retcode = send_command(buf, 2, staflag, syncmode)) != 0 ) break; sp += 3; } else { /* Basic function */ buf[0] = 0x06; buf[1] = sp[0]; if ( (retcode = send_command(buf, 2, 0, syncmode)) != 0 ) break; sp += 3; } } if ( retcode != 0 ) fprintf(stderr, "Unable to send command function %d\n", func); return retcode; } /*---------------------------------------------------------------------+ | Translate a macro from the image of an EEPROM in memory into | | direct commands and send to the CM11A as direct commands. | | Argument 'image' is a pointer to the 1024 byte image buffer in | | memory. Argument 'macaddr' is the offset of the macro in that | | buffer. | | The macro delay itself is ignored, but operation chains to a | | subsequent delayed macro as it would in actual CM11A operation. | | Return 0 if successful, otherwise 1 on first failure. | +---------------------------------------------------------------------*/ int send_macro_chain_immediate ( unsigned char *image, unsigned int macaddr ) { unsigned char hcode, func = 0xff, nelem; unsigned char buf[16]; unsigned char *sp; unsigned char ischained; unsigned int bitmap, staflag; int k, retcode = 0; int syncmode; int line_sync_mode(void); ischained = 1; sp = image + macaddr + 1; syncmode = line_sync_mode(); while ( ischained != 0 ) { nelem = *sp++; /* Sent each element */ for ( k = 0; k < (int)nelem; k++ ) { if ( sp < (image + 5) || sp > (image + 0x3ff) ) { fprintf(stderr, "Macro element is outside EEPROM image\n"); return 1; } hcode = (sp[0] & 0xf0u) >> 4; func = sp[0] & 0x0fu; bitmap = (sp[1] << 8) | sp[2]; staflag = 0; /* Send the address */ if ( send_address(hcode, bitmap, syncmode) != 0 ) { fprintf(stderr, "Unable to send address\n"); return 1; } /* Send the function */ if ( func == 4 || func == 5 ) { /* Dim or Bright function */ if ( sp[3] & 0x80u ) { /* Brighten before dimming */ buf[0] = 0x06 | (22 << 3); buf[1] = (sp[0] & 0xf0u) | 5; if ( (retcode = send_command(buf, 2, 0, syncmode)) != 0 ) break; } buf[0] = 0x06 | ((sp[3] & 0x7fu) << 3); buf[1] = sp[0]; if ( (retcode = send_command(buf, 2, 0, syncmode)) != 0 ) break; sp += 4; } else if ( func == 7 ) { /* Extended function */ buf[0] = 0x07; buf[1] = (sp[0] & 0xf0u) | 0x07u; buf[2] = sp[3]; buf[3] = sp[4]; buf[4] = sp[5]; staflag = (buf[4] == 0x37) ? 1 : 0; /* Kluge "fix" for checksum 5A problem. */ /* CM11A seems to disregard the dim field */ /* in the header byte. */ if ( checksum(buf, 5) == 0x5A && configp->fix_5a == YES ) buf[0] = 0x0F; if ( (retcode = send_command(buf, 5, staflag, syncmode)) != 0 ) break; sp += 6; } else if ( func == 15 ) { /* Status Request function */ buf[0] = 0x06; buf[1] = sp[0]; staflag = 1; if ( (retcode = send_command(buf, 2, staflag, syncmode)) != 0 ) break; sp += 3; } else { /* Basic function */ buf[0] = 0x06; buf[1] = sp[0]; if ( (retcode = send_command(buf, 2, 0, syncmode)) != 0 ) break; sp += 3; } } ischained = *sp++; } if ( retcode != 0 ) fprintf(stderr, "Unable to send command function %d\n", func); return retcode; } /*---------------------------------------------------------------------+ | Return the total length in bytes of the macro pointed to by macptr | +---------------------------------------------------------------------*/ unsigned char macro_length ( unsigned char *macptr ) { unsigned char k, nelem, func; unsigned char *sp; sp = macptr + 1; nelem = *sp++; for ( k = 0; k < nelem; k++ ) { func = *sp & 0x0fu; if ( func == 4 || func == 5 ) sp += 4; else if ( func == 7 ) sp += 6; else sp += 3; } return (sp - macptr); } /* Structure used by c_catchup() */ struct catchup_st { unsigned int start; unsigned int macaddr; unsigned char delay; }; /*---------------------------------------------------------------------+ | qsort compare function for c_catchup() | | Delayed events are ordered before undelayed events if the actual | | execution times are the same. | +---------------------------------------------------------------------*/ int cmp_catchup ( struct catchup_st *one, struct catchup_st *two ) { return (one->start < two->start) ? -1 : (one->start > two->start) ? 1 : (one->delay > two->delay) ? -1 : (one->delay < two->delay) ? 1 : 0 ; } /*---------------------------------------------------------------------+ | Execute the commands in macros in an uploaded schedule for timer | | events scheduled between 0:00 hours and the current time today. | +---------------------------------------------------------------------*/ int c_catchup ( int argc, char *argv[] ) { time_t now; struct tm *tmp; int j, ichksum, size; unsigned int day, minutes; int (*fptr)() = &cmp_catchup; unsigned char dow, dow_bmap; unsigned char *image, *sp, *dp; unsigned int beg, end, start, stop, delay; unsigned int macstart, macstop, macaddr; unsigned char *image_ptr(void); struct catchup_st table[210]; char maclabel[MACRO_LEN + 1]; if ( invalidate_for_cm10a() != 0 ) return 1; switch ( get_upload_expire() ) { case NO_RECORD_FILE : fprintf(stderr, "No schedule has been uploaded by Heyu.\n"); return 1; case NO_EXPIRATION : fprintf(stderr, "Uploaded schedule contains no Timers.\n"); return 1; case BAD_RECORD_FILE : fprintf(stderr, "X10 Record File has been corrupted.\n"); return 1; case SCHEDULE_EXPIRED : fprintf(stderr, "Uploaded schedule has expired.\n"); return 1; default : break; } lookup_macro(0, maclabel, &ichksum); if ( !loadcheck_image(ichksum) ) { fprintf(stderr, "Unable to load x10image file, ichksum = %d\n", ichksum); return 1; } image = image_ptr(); time(&now); tmp = legal_to_cm11a(&now); minutes = 60 * tmp->tm_hour + tmp->tm_min + ((tmp->tm_sec > 0) ? 1 : 0); day = tmp->tm_yday; dow = 1 << (tmp->tm_wday); sp = image + 2; size = 0; while ( *sp != 0xff ) { dow_bmap = sp[0]; beg = sp[1] | ((sp[4] & 0x80u) << 1); end = sp[2] | ((sp[5] & 0x80u) << 1); if ( !(dow & dow_bmap) || day < beg || day > end ) { sp += 9; continue; } start = 120 * ((sp[3] & 0xf0u) >> 4) + (sp[4] & 0x7fu) + SECURITY_OFFSET_ADJUST * ((sp[6] & 0x80u) >> 7); stop = 120 * (sp[3] & 0x0fu) + (sp[5] & 0x7fu) + SECURITY_OFFSET_ADJUST * ((sp[6] & 0x08u) >> 3); macstart = sp[7] | ((sp[6] & 0x30u) << 4); macstop = sp[8] | ((sp[6] & 0x03u) << 8); /* Add macro delay to start time */ delay = *(image + macstart); start += delay; if ( start < minutes ) { table[size].start = start; table[size].macaddr = macstart; table[size].delay = (delay > 0) ? 1 : 0; size++; /* Include any chained macros */ dp = image + macstart; while ( *(dp += macro_length(dp)) > 0 ) { start += *dp; if ( start >= minutes ) break; table[size].start = start; table[size].macaddr = dp - image; table[size].delay = 1; size++; } } delay = *(image + macstop); stop += delay; if ( stop < minutes ) { table[size].start = stop; table[size].macaddr = macstop; table[size].delay = (delay > 0) ? 1 : 0; size++; /* Include any chained macros */ dp = image + macstop; while ( *(dp += macro_length(dp)) > 0 ) { stop += *dp; if ( stop >= minutes ) break; table[size].start = stop; table[size].macaddr = dp - image; table[size].delay = 1; size++; } } sp += 9; } if ( size == 0 ) { fprintf(stderr, "No macros scheduled for execution before now.\n"); return 0; } /* Sort the table in order of ascending execution times */ qsort((void *)table, size, (sizeof(struct catchup_st)), fptr); /* Now send the macro commands */ for ( j = 0; j < size; j++ ) { macaddr = table[j].macaddr; lookup_macro(macaddr, maclabel, &ichksum); printf("Emulating macro %s at address %d\n", maclabel, macaddr); fflush(stdout); if ( send_macro_immediate(image, macaddr) != 0 ) return 1; } return 0; } /*---------------------------------------------------------------------+ | Execute the commands in an uploaded macro. | +---------------------------------------------------------------------*/ int c_macro ( int argc, char *argv[] ) { int macaddr, ichksum; unsigned char *image; unsigned char *image_ptr(void); if ( invalidate_for_cm10a() != 0 ) return 1; switch ( get_upload_expire() ) { case NO_RECORD_FILE : fprintf(stderr, "No schedule has been uploaded by Heyu.\n"); return 1; case BAD_RECORD_FILE : fprintf(stderr, "X10 Record File has been corrupted.\n"); return 1; default : break; } if ( argc < 3 ) { fprintf(stderr, "Macro name needed.\n"); return 1; } if ( (macaddr = macro_rev_lookup(argv[2], &ichksum)) < 0 ) { fprintf(stderr, "Macro '%s' not found in %s file.\n", argv[2], pathspec(MACROXREF_FILE)); return 1; } if ( !loadcheck_image(ichksum) ) { fprintf(stderr, "Unable to load x10image file, ichksum = %d\n", ichksum); return 1; } image = image_ptr(); printf("Emulating macro %s at address %d\n", argv[2], macaddr); fflush(stdout); if ( send_macro_immediate(image, macaddr) != 0 ) return 1; return 0; } /*---------------------------------------------------------------------+ | Read heyu options from the command line and store pointers in | | structure opt_st. Return the number of argv[] tokens used for the | | options, or -1 if invalid usage or invalid option. | | Options supported: | | -v Verbose mode. | | -c Configuration file. | | -s Schedule file. | | -0 ... -9 Subdirectory of $HOME/.heyu/ or /etc/heyu/ | | where config file is stored, e.g., | | -2 => $HOME/.heyu/2/x10config | | -tr Sync with rising AC slope. | | -tf Sync with falling AC slope. | +---------------------------------------------------------------------*/ int heyu_getopt( int argc, char **argv, struct opt_st *optptr ) { int j, ntokens; optptr->configp = NULL; optptr->schedp = NULL; optptr->subp = NULL; optptr->verbose = 0; optptr->linesync = NO_SYNC; if ( argc < 2 ) return -1; ntokens = 0; for ( j = 1; j < argc; j++ ) { if ( *argv[j] != '-' ) break; if ( strncmp(argv[j], "-v", 2) == 0 ) { if ( strlen(argv[j]) == 2 ) { optptr->verbose = 1; ntokens++; } else { fprintf(stderr, "Invalid option '%s'\n", argv[j]); return -1; } } else if ( strncmp(argv[j], "-c", 2) == 0 ) { if ( (int)strlen(argv[j]) > 2 ) { optptr->configp = argv[j] + 1; ntokens++; } else if ( (j + 1) < argc && *argv[j + 1] != '-' ) { optptr->configp = argv[j + 1]; ntokens += 2; j++; } else { fprintf(stderr, "Invalid option usage '%s'\n", argv[j]); return -1; } } else if ( strncmp(argv[j], "-s", 2) == 0 ) { if ( (int)strlen(argv[j]) > 2 ) { optptr->schedp = argv[j] + 2; ntokens++; } else if ( (j + 1) < argc && *(argv[j + 1]) != '-' ) { optptr->schedp = argv[j + 1]; ntokens += 2; j++; } else { fprintf(stderr, "Invalid option usage '%s'\n", argv[j]); return -1; } } else if ( strncmp(argv[j], "-tr", 3) == 0 ) { if ( strlen(argv[j]) == 3 ) { optptr->linesync = RISE_SYNC; ntokens++; } else { fprintf(stderr, "Invalid option '%s'\n", argv[j]); return -1; } } else if ( strncmp(argv[j], "-tf", 3) == 0 ) { if ( strlen(argv[j]) == 3 ) { optptr->linesync = FALL_SYNC; ntokens++; } else { fprintf(stderr, "Invalid option '%s'\n", argv[j]); return -1; } } else if ( strlen(argv[j]) == 2 && isdigit((int)(*(argv[j] + 1))) ) { optptr->subp = argv[j] + 1; ntokens++; } else { fprintf(stderr, "Invalid parameter '%s'\n", argv[j]); return -1; } } if ( optptr->subp && optptr->configp ) { fprintf(stderr, "Options -%s and -c may not both be specified.\n", optptr->subp); return -1; } return ntokens; } /*---------------------------------------------------------------------+ | Return the line synchronization mode specified by CL option. | +---------------------------------------------------------------------*/ int line_sync_mode ( void ) { return optptr->linesync; } /*---------------------------------------------------------------------+ | Return the current system time as seconds after 0:00:00 hours legal | | (wall clock) time. | +---------------------------------------------------------------------*/ long int systime_now ( void ) { time_t now; struct tm *tms; time(&now); tms = localtime(&now); return ( 3600 * tms->tm_hour + 60 * tms->tm_min + tms->tm_sec); } #if 0 /*---------------------------------------------------------------------+ | Return the time of either Dawn or Dusk today as seconds after | | 0:00:00 legal (wall clock) time. | | If argument type is DAWN_EVENT, the time of Dawn is returned, | | returned, otherwise the time of Dusk. | | If LATITUDE or LONGITUDE have not been specified in the config | | file, or if no Dawn or Dusk today, silently return -1. | +---------------------------------------------------------------------*/ long int dawndusk_today ( unsigned char type ) { int dawn, dusk, retcode; long jd; time_t now; struct tm *tms; if ( configp->loc_flag ^ (LATITUDE | LONGITUDE) ) return -1; time(&now); tms = stdtime(&now); jd = greg2jd(tms->tm_year + 1900, tms->tm_mon + 1, tms->tm_mday); retcode = suntimes(configp->latitude, configp->longitude, std_tzone, jd, configp->sunmode, &dawn, &dusk, NULL, NULL); if ( retcode != NORMAL_SUN ) return -1; /* Convert to Legal Time */ tms->tm_hour = tms->tm_sec = 0; tms->tm_min = (type == DAWN_EVENT) ? dawn : dusk; tms->tm_isdst = 0; mktime(tms); return (3600 * tms->tm_hour + 60 * tms->tm_min + tms->tm_sec); } #endif /*---------------------------------------------------------------------+ | Locate the macro address (if any) corresponding to the argument | | trigger hcode|ucode and function (1 = on, 0 = off). Return 1 if | | found and pass back the macro address through the argument list. | | Otherwise return 0. | +---------------------------------------------------------------------*/ int locate_triggered_macro ( unsigned char *imagep, char hc, int unit, char *trig_func, int *macaddr) { unsigned char *cp; unsigned char trigaddr, funcmask; trigaddr = hc2code(hc) << 4 | unit2code(unit); funcmask = (!strcmp(strlower(trig_func), "off")) ? 0x00 : 0x80; cp = imagep + (imagep[0] << 8) + imagep[1]; while (*cp != 0xffu && *(cp+1) != 0xffu && cp < (imagep + PROMSIZE) ) { if ( *cp == trigaddr && (*(cp+1) & 0x80u) == funcmask ) { *macaddr = (*(cp+1) & 0x0fu) << 8 | *(cp+2); return 1; } cp += 3; } return 0; } /*---------------------------------------------------------------------+ | Execute commands in a uploaded macro image as if triggered. | | argv[2] = Hu address, argv[3] = "on" or "off" | +---------------------------------------------------------------------*/ int c_trigger ( int argc, char *argv[] ) { int macaddr; int ichksum, xchksum; unsigned char *imagep; unsigned char *image_ptr(void); char maclabel[MACRO_LEN + 1]; unsigned int aflags, bitmap; char hc, unit; if ( invalidate_for_cm10a() != 0 ) return 1; switch ( get_upload_expire() ) { case NO_RECORD_FILE : fprintf(stderr, "No schedule has been uploaded by Heyu.\n"); return 1; default : break; } if ( argc != 4 || (strcmp(argv[3], "on") != 0 && strcmp(argv[3], "off") != 0) ) { fprintf(stderr, "Usage: %s trigger Hu on|off\n", argv[0]); return 1; } aflags = parse_addr(argv[2], &hc, &bitmap); if ( !(aflags & A_VALID) || (aflags & (A_PLUS | A_MINUS | A_MULT)) || bitmap == 0 ) { fprintf(stderr, "Invalid Housecode|Unit address %s\n", argv[2]); return 1; } unit = code2unit(single_bmap_unit(bitmap )); if ( load_image(&ichksum) == 0 ) { fprintf(stderr, "Unable to load x10image file.\n"); return 1; } imagep = image_ptr(); if ( locate_triggered_macro(imagep, hc, unit, argv[3], &macaddr) == 0 ) { fprintf(stderr, "No trigger for '%c%d %s' found in file %s.\n", hc, unit, argv[3], pathspec(IMAGE_FILE)); return 1; } if ( lookup_macro(macaddr, maclabel, &xchksum) == 0 ) { fprintf(stderr, "Unknown macro at triggered address 0x%03x\n", macaddr); return 1; } if ( xchksum != ichksum ) { fprintf(stderr, "Mismatch between x10image and x10macroxref files.\n"); return 1; } printf("Emulating triggered macro %s\n", maclabel); fflush(stdout); if ( send_macro_immediate(imagep, macaddr) != 0 ) return 1; return 0; }