diff options
Diffstat (limited to 'util/tg2.c')
-rw-r--r-- | util/tg2.c | 2506 |
1 files changed, 2506 insertions, 0 deletions
diff --git a/util/tg2.c b/util/tg2.c new file mode 100644 index 0000000..6d52ef4 --- /dev/null +++ b/util/tg2.c @@ -0,0 +1,2506 @@ +/* + * tg.c generate WWV or IRIG signals for test + */ +/* + * This program can generate audio signals that simulate the WWV/H + * broadcast timecode. Alternatively, it can generate the IRIG-B + * timecode commonly used to synchronize laboratory equipment. It is + * intended to test the WWV/H driver (refclock_wwv.c) and the IRIG + * driver (refclock_irig.c) in the NTP driver collection. + * + * Besides testing the drivers themselves, this program can be used to + * synchronize remote machines over audio transmission lines or program + * feeds. The program reads the time on the local machine and sets the + * initial epoch of the signal generator within one millisecond. + * Alernatively, the initial epoch can be set to an arbitrary time. This + * is useful when searching for bugs and testing for correct response to + * a leap second in UTC. Note however, the ultimate accuracy is limited + * by the intrinsic frequency error of the codec sample clock, which can + # reach well over 100 PPM. + * + * The default is to route generated signals to the line output + * jack; the s option on the command line routes these signals to the + * internal speaker as well. The v option controls the speaker volume + * over the range 0-255. The signal generator by default uses WWV + * format; the h option switches to WWVH format and the i option + * switches to IRIG-B format. + * + * Once started the program runs continuously. The default initial epoch + * for the signal generator is read from the computer system clock when + * the program starts. The y option specifies an alternate epoch using a + * string yydddhhmmss, where yy is the year of century, ddd the day of + * year, hh the hour of day and mm the minute of hour. For instance, + * 1946Z on 1 January 2006 is 060011946. The l option lights the leap + * warning bit in the WWV/H timecode, so is handy to check for correct + * behavior at the next leap second epoch. The remaining options are + * specified below under the Parse Options heading. Most of these are + * for testing. + * + * During operation the program displays the WWV/H timecode (9 digits) + * or IRIG timecode (20 digits) as each new string is constructed. The + * display is followed by the BCD binary bits as transmitted. Note that + * the transmissionorder is low-order first as the frame is processed + * left to right. For WWV/H The leap warning L preceeds the first bit. + * For IRIG the on-time marker M preceeds the first (units) bit, so its + * code is delayed one bit and the next digit (tens) needs only three + * bits. + * + * The program has been tested with the Sun Blade 1500 running Solaris + * 10, but not yet with other machines. It uses no special features and + * should be readily portable to other hardware and operating systems. + * + * $Log: tg.c,v $ + * Revision 1.28 2007/02/12 23:57:45 dmw + * v0.23 2007-02-12 dmw: + * - Changed statistics to include calculated error + * of frequency, based on number of added or removed + * cycles over time. + * + * Revision 1.27 2007/02/09 02:28:59 dmw + * v0.22 2007-02-08 dmw: + * - Changed default for rate correction to "enabled", "-j" switch now disables. + * - Adjusted help message accordingly. + * - Added "2007" to modifications note at end of help message. + * + * Revision 1.26 2007/02/08 03:36:17 dmw + * v0.21 2007-02-07 dmw: + * - adjusted strings for shorten and lengthen to make + * fit on smaller screen. + * + * Revision 1.25 2007/02/01 06:08:09 dmw + * v0.20 2007-02-01 dmw: + * - Added periodic display of running time along with legend on IRIG-B, allows tracking how + * close IRIG output is to actual clock time. + * + * Revision 1.24 2007/01/31 19:24:11 dmw + * v0.19 2007-01-31 dmw: + * - Added tracking of how many seconds have been adjusted, + * how many cycles added (actually in milliseconds), how + * many cycles removed, print periodically if verbose is + * active. + * - Corrected lack of lengthen or shorten of minute & hour + * pulses for WWV format. + * + * Revision 1.23 2007/01/13 07:09:12 dmw + * v0.18 2007-01-13 dmw: + * - added -k option, which allows force of long or short + * cycles, to test against IRIG-B decoder. + * + * Revision 1.22 2007/01/08 16:27:23 dmw + * v0.17 2007-01-08 dmw: + * - Changed -j option to **enable** rate correction, not disable. + * + * Revision 1.21 2007/01/08 06:22:36 dmw + * v0.17 2007-01-08 dmw: + * - Run stability check versus ongoing system clock (assume NTP correction) + * and adjust time code rate to try to correct, if gets too far out of sync. + * Disable this algorithm with -j option. + * + * Revision 1.20 2006/12/19 04:59:04 dmw + * v0.16 2006-12-18 dmw + * - Corrected print of setting of output frequency, always + * showed 8000 samples/sec, now as specified on command line. + * - Modified to reflect new employer Norscan. + * + * Revision 1.19 2006/12/19 03:45:38 dmw + * v0.15 2006-12-18 dmw: + * - Added count of number of seconds to output then exit, + * default zero for forever. + * + * Revision 1.18 2006/12/18 05:43:36 dmw + * v0.14 2006-12-17 dmw: + * - Corrected WWV(H) signal to leave "tick" sound off of 29th and 59th second of minute. + * - Adjusted verbose output format for WWV(H). + * + * Revision 1.17 2006/12/18 02:31:33 dmw + * v0.13 2006-12-17 dmw: + * - Put SPARC code back in, hopefully will work, but I don't have + * a SPARC to try it on... + * - Reworked Verbose mode, different flag to initiate (x not v) + * and actually implement turn off of verbosity when this flag used. + * - Re-claimed v flag for output level. + * - Note that you must define OSS_MODS to get OSS to compile, + * otherwise will expect to compile using old SPARC options, as + * it used to be. + * + * Revision 1.16 2006/10/26 19:08:43 dmw + * v0.12 2006-10-26 dmw: + * - Reversed output binary dump for IRIG, makes it easier to read the numbers. + * + * Revision 1.15 2006/10/24 15:57:09 dmw + * v0.11 2006-10-24 dmw: + * - another tweak. + * + * Revision 1.14 2006/10/24 15:55:53 dmw + * v0.11 2006-10-24 dmw: + * - Curses a fix to the fix to the fix of the usaeg. + * + * Revision 1.13 2006/10/24 15:53:25 dmw + * v0.11 (still) 2006-10-24 dmw: + * - Messed with usage message that's all. + * + * Revision 1.12 2006/10/24 15:50:05 dmw + * v0.11 2006-10-24 dmw: + * - oops, needed to note "hours" in usage of that offset. + * + * Revision 1.11 2006/10/24 15:49:09 dmw + * v0.11 2006-10-24 dmw: + * - Added ability to offset actual time sent, from the UTC time + * as per the computer. + * + * Revision 1.10 2006/10/24 03:25:55 dmw + * v0.10 2006-10-23 dmw: + * - Corrected polarity of correction of offset when going into or out of DST. + * - Ensure that zero offset is always positive (pet peeve). + * + * Revision 1.9 2006/10/24 00:00:35 dmw + * v0.9 2006-10-23 dmw: + * - Shift time offset when DST in or out. + * + * Revision 1.8 2006/10/23 23:49:28 dmw + * v0.8 2006-10-23 dmw: + * - made offset of zero default positive. + * + * Revision 1.7 2006/10/23 23:44:13 dmw + * v0.7 2006-10-23 dmw: + * - Added unmodulated and inverted unmodulated output. + * + * Revision 1.6 2006/10/23 18:10:37 dmw + * v0.6 2006-10-23 dmw: + * - Cleaned up usage message. + * - Require at least one option, or prints usage message and exits. + * + * Revision 1.5 2006/10/23 16:58:10 dmw + * v0.5 2006-10-23 dmw: + * - Finally added a usage message. + * - Added leap second pending and DST change pending into IEEE 1344. + * - Default code type is now IRIG-B with IEEE 1344. + * + * Revision 1.4 2006/10/23 03:27:25 dmw + * v0.4 2006-10-22 dmw: + * - Added leap second addition and deletion. + * - Added DST changing forward and backward. + * - Changed date specification to more conventional year, month, and day of month + * (rather than day of year). + * + * Revision 1.3 2006/10/22 21:04:12 dmw + * v0.2 2006-10-22 dmw: + * - Corrected format of legend line. + * + * Revision 1.2 2006/10/22 21:01:07 dmw + * v0.1 2006-10-22 dmw: + * - Added some more verbose output (as is my style) + * - Corrected frame format - there were markers in the + * middle of frames, now correctly as "zero" bits. + * - Added header line to show fields of output. + * - Added straight binary seconds, were not implemented + * before. + * - Added IEEE 1344 with parity. + * + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#undef VERSION /* avoid conflict below */ +#endif + +#ifdef HAVE_SYS_SOUNDCARD_H +#include <sys/soundcard.h> +#else +# ifdef HAVE_SYS_AUDIOIO_H +# include <sys/audioio.h> +# else +# include <sys/audio.h> +# endif +#endif + +#include "ntp_stdlib.h" /* for strlcat(), strlcpy() */ + +#include <math.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <sys/ioctl.h> +#include <sys/time.h> + +#define VERSION (0) +#define ISSUE (23) +#define ISSUE_DATE "2007-02-12" + +#define SECOND (8000) /* one second of 125-us samples */ +#define BUFLNG (400) /* buffer size */ +#define DEVICE "/dev/audio" /* default audio device */ +#define WWV (0) /* WWV encoder */ +#define IRIG (1) /* IRIG-B encoder */ +#define OFF (0) /* zero amplitude */ +#define LOW (1) /* low amplitude */ +#define HIGH (2) /* high amplitude */ +#define DATA0 (200) /* WWV/H 0 pulse */ +#define DATA1 (500) /* WWV/H 1 pulse */ +#define PI (800) /* WWV/H PI pulse */ +#define M2 (2) /* IRIG 0 pulse */ +#define M5 (5) /* IRIG 1 pulse */ +#define M8 (8) /* IRIG PI pulse */ + +#define NUL (0) + +#define SECONDS_PER_MINUTE (60) +#define SECONDS_PER_HOUR (3600) + +#define OUTPUT_DATA_STRING_LENGTH (200) + +/* Attempt at unmodulated - "high" */ +int u6000[] = { + 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, /* 0- 9 */ + 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, /* 10-19 */ + 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, /* 20-29 */ + 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, /* 30-39 */ + 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, /* 40-49 */ + 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, /* 50-59 */ + 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, /* 60-69 */ + 247, 247, 247, 247, 247, 247, 247, 247, 247, 247}; /* 70-79 */ + +/* Attempt at unmodulated - "low" */ +int u3000[] = { + 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, /* 0- 9 */ + 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, /* 10-19 */ + 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, /* 20-29 */ + 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, /* 30-39 */ + 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, /* 40-49 */ + 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, /* 50-59 */ + 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, /* 60-69 */ + 119, 119, 119, 119, 119, 119, 119, 119, 119, 119}; /* 70-79 */ + +/* + * Companded sine table amplitude 3000 units + */ +int c3000[] = {1, 48, 63, 70, 78, 82, 85, 89, 92, 94, /* 0-9 */ + 96, 98, 99, 100, 101, 101, 102, 103, 103, 103, /* 10-19 */ + 103, 103, 103, 103, 102, 101, 101, 100, 99, 98, /* 20-29 */ + 96, 94, 92, 89, 85, 82, 78, 70, 63, 48, /* 30-39 */ + 129, 176, 191, 198, 206, 210, 213, 217, 220, 222, /* 40-49 */ + 224, 226, 227, 228, 229, 229, 230, 231, 231, 231, /* 50-59 */ + 231, 231, 231, 231, 230, 229, 229, 228, 227, 226, /* 60-69 */ + 224, 222, 220, 217, 213, 210, 206, 198, 191, 176}; /* 70-79 */ +/* + * Companded sine table amplitude 6000 units + */ +int c6000[] = {1, 63, 78, 86, 93, 98, 101, 104, 107, 110, /* 0-9 */ + 112, 113, 115, 116, 117, 117, 118, 118, 119, 119, /* 10-19 */ + 119, 119, 119, 118, 118, 117, 117, 116, 115, 113, /* 20-29 */ + 112, 110, 107, 104, 101, 98, 93, 86, 78, 63, /* 30-39 */ + 129, 191, 206, 214, 221, 226, 229, 232, 235, 238, /* 40-49 */ + 240, 241, 243, 244, 245, 245, 246, 246, 247, 247, /* 50-59 */ + 247, 247, 247, 246, 246, 245, 245, 244, 243, 241, /* 60-69 */ + 240, 238, 235, 232, 229, 226, 221, 214, 206, 191}; /* 70-79 */ + +/* + * Decoder operations at the end of each second are driven by a state + * machine. The transition matrix consists of a dispatch table indexed + * by second number. Each entry in the table contains a case switch + * number and argument. + */ +struct progx { + int sw; /* case switch number */ + int arg; /* argument */ +}; + +/* + * Case switch numbers + */ +#define DATA (0) /* send data (0, 1, PI) */ +#define COEF (1) /* send BCD bit */ +#define DEC (2) /* decrement to next digit and send PI */ +#define MIN (3) /* minute pulse */ +#define LEAP (4) /* leap warning */ +#define DUT1 (5) /* DUT1 bits */ +#define DST1 (6) /* DST1 bit */ +#define DST2 (7) /* DST2 bit */ +#define DECZ (8) /* decrement to next digit and send zero */ +#define DECC (9) /* decrement to next digit and send bit */ +#define NODEC (10) /* no decerement to next digit, send PI */ +#define DECX (11) /* decrement to next digit, send PI, but no tick */ +#define DATAX (12) /* send data (0, 1, PI), but no tick */ + +/* + * WWV/H format (100-Hz, 9 digits, 1 m frame) + */ +struct progx progx[] = { + {MIN, 800}, /* 0 minute sync pulse */ + {DATA, DATA0}, /* 1 */ + {DST2, 0}, /* 2 DST2 */ + {LEAP, 0}, /* 3 leap warning */ + {COEF, 1}, /* 4 1 year units */ + {COEF, 2}, /* 5 2 */ + {COEF, 4}, /* 6 4 */ + {COEF, 8}, /* 7 8 */ + {DEC, DATA0}, /* 8 */ + {DATA, PI}, /* 9 p1 */ + {COEF, 1}, /* 10 1 minute units */ + {COEF, 2}, /* 11 2 */ + {COEF, 4}, /* 12 4 */ + {COEF, 8}, /* 13 8 */ + {DEC, DATA0}, /* 14 */ + {COEF, 1}, /* 15 10 minute tens */ + {COEF, 2}, /* 16 20 */ + {COEF, 4}, /* 17 40 */ + {COEF, 8}, /* 18 80 (not used) */ + {DEC, PI}, /* 19 p2 */ + {COEF, 1}, /* 20 1 hour units */ + {COEF, 2}, /* 21 2 */ + {COEF, 4}, /* 22 4 */ + {COEF, 8}, /* 23 8 */ + {DEC, DATA0}, /* 24 */ + {COEF, 1}, /* 25 10 hour tens */ + {COEF, 2}, /* 26 20 */ + {COEF, 4}, /* 27 40 (not used) */ + {COEF, 8}, /* 28 80 (not used) */ + {DECX, PI}, /* 29 p3 */ + {COEF, 1}, /* 30 1 day units */ + {COEF, 2}, /* 31 2 */ + {COEF, 4}, /* 32 4 */ + {COEF, 8}, /* 33 8 */ + {DEC, DATA0}, /* 34 not used */ + {COEF, 1}, /* 35 10 day tens */ + {COEF, 2}, /* 36 20 */ + {COEF, 4}, /* 37 40 */ + {COEF, 8}, /* 38 80 */ + {DEC, PI}, /* 39 p4 */ + {COEF, 1}, /* 40 100 day hundreds */ + {COEF, 2}, /* 41 200 */ + {COEF, 4}, /* 42 400 (not used) */ + {COEF, 8}, /* 43 800 (not used) */ + {DEC, DATA0}, /* 44 */ + {DATA, DATA0}, /* 45 */ + {DATA, DATA0}, /* 46 */ + {DATA, DATA0}, /* 47 */ + {DATA, DATA0}, /* 48 */ + {DATA, PI}, /* 49 p5 */ + {DUT1, 8}, /* 50 DUT1 sign */ + {COEF, 1}, /* 51 10 year tens */ + {COEF, 2}, /* 52 20 */ + {COEF, 4}, /* 53 40 */ + {COEF, 8}, /* 54 80 */ + {DST1, 0}, /* 55 DST1 */ + {DUT1, 1}, /* 56 0.1 DUT1 fraction */ + {DUT1, 2}, /* 57 0.2 */ + {DUT1, 4}, /* 58 0.4 */ + {DATAX, PI}, /* 59 p6 */ + {DATA, DATA0}, /* 60 leap */ +}; + +/* + * IRIG format frames (1000 Hz, 1 second for 10 frames of data) + */ + +/* + * IRIG format frame 10 - MS straight binary seconds + */ +struct progx progu[] = { + {COEF, 2}, /* 0 0x0 0200 seconds */ + {COEF, 4}, /* 1 0x0 0400 */ + {COEF, 8}, /* 2 0x0 0800 */ + {DECC, 1}, /* 3 0x0 1000 */ + {COEF, 2}, /* 4 0x0 2000 */ + {COEF, 4}, /* 6 0x0 4000 */ + {COEF, 8}, /* 7 0x0 8000 */ + {DECC, 1}, /* 8 0x1 0000 */ + {COEF, 2}, /* 9 0x2 0000 - but only 86,401 / 0x1 5181 seconds in a day, so always zero */ + {NODEC, M8}, /* 9 PI */ +}; + +/* + * IRIG format frame 8 - MS control functions + */ +struct progx progv[] = { + {COEF, 2}, /* 0 CF # 19 */ + {COEF, 4}, /* 1 CF # 20 */ + {COEF, 8}, /* 2 CF # 21 */ + {DECC, 1}, /* 3 CF # 22 */ + {COEF, 2}, /* 4 CF # 23 */ + {COEF, 4}, /* 6 CF # 24 */ + {COEF, 8}, /* 7 CF # 25 */ + {DECC, 1}, /* 8 CF # 26 */ + {COEF, 2}, /* 9 CF # 27 */ + {DEC, M8}, /* 10 PI */ +}; + +/* + * IRIG format frames 7 & 9 - LS control functions & LS straight binary seconds + */ +struct progx progw[] = { + {COEF, 1}, /* 0 CF # 10, 0x0 0001 seconds */ + {COEF, 2}, /* 1 CF # 11, 0x0 0002 */ + {COEF, 4}, /* 2 CF # 12, 0x0 0004 */ + {COEF, 8}, /* 3 CF # 13, 0x0 0008 */ + {DECC, 1}, /* 4 CF # 14, 0x0 0010 */ + {COEF, 2}, /* 6 CF # 15, 0x0 0020 */ + {COEF, 4}, /* 7 CF # 16, 0x0 0040 */ + {COEF, 8}, /* 8 CF # 17, 0x0 0080 */ + {DECC, 1}, /* 9 CF # 18, 0x0 0100 */ + {NODEC, M8}, /* 10 PI */ +}; + +/* + * IRIG format frames 2 to 6 - minutes, hours, days, hundreds days, 2 digit years (also called control functions bits 1-9) + */ +struct progx progy[] = { + {COEF, 1}, /* 0 1 units, CF # 1 */ + {COEF, 2}, /* 1 2 units, CF # 2 */ + {COEF, 4}, /* 2 4 units, CF # 3 */ + {COEF, 8}, /* 3 8 units, CF # 4 */ + {DECZ, M2}, /* 4 zero bit, CF # 5 / unused, default zero in years */ + {COEF, 1}, /* 5 10 tens, CF # 6 */ + {COEF, 2}, /* 6 20 tens, CF # 7*/ + {COEF, 4}, /* 7 40 tens, CF # 8*/ + {COEF, 8}, /* 8 80 tens, CF # 9*/ + {DEC, M8}, /* 9 PI */ +}; + +/* + * IRIG format first frame, frame 1 - seconds + */ +struct progx progz[] = { + {MIN, M8}, /* 0 PI (on-time marker for the second at zero cross of 1st cycle) */ + {COEF, 1}, /* 1 1 units */ + {COEF, 2}, /* 2 2 */ + {COEF, 4}, /* 3 4 */ + {COEF, 8}, /* 4 8 */ + {DECZ, M2}, /* 5 zero bit */ + {COEF, 1}, /* 6 10 tens */ + {COEF, 2}, /* 7 20 */ + {COEF, 4}, /* 8 40 */ + {DEC, M8}, /* 9 PI */ +}; + +/* LeapState values. */ +#define LEAPSTATE_NORMAL (0) +#define LEAPSTATE_DELETING (1) +#define LEAPSTATE_INSERTING (2) +#define LEAPSTATE_ZERO_AFTER_INSERT (3) + + +/* + * Forward declarations + */ +void WWV_Second(int, int); /* send second */ +void WWV_SecondNoTick(int, int); /* send second with no tick */ +void digit(int); /* encode digit */ +void peep(int, int, int); /* send cycles */ +void poop(int, int, int, int); /* Generate unmodulated from similar tables */ +void delay(int); /* delay samples */ +int ConvertMonthDayToDayOfYear (int, int, int); /* Calc day of year from year month & day */ +void Help (void); /* Usage message */ +void ReverseString(char *); + +/* + * Extern declarations, don't know why not in headers + */ +//float round ( float ); + +/* + * Global variables + */ +char buffer[BUFLNG]; /* output buffer */ +int bufcnt = 0; /* buffer counter */ +int fd; /* audio codec file descriptor */ +int tone = 1000; /* WWV sync frequency */ +int HourTone = 1500; /* WWV hour on-time frequency */ +int encode = IRIG; /* encoder select */ +int leap = 0; /* leap indicator */ +int DstFlag = 0; /* winter/summer time */ +int dut1 = 0; /* DUT1 correction (sign, magnitude) */ +int utc = 0; /* option epoch */ +int IrigIncludeYear = FALSE; /* Whether to send year in first control functions area, between P5 and P6. */ +int IrigIncludeIeee = FALSE; /* Whether to send IEEE 1344 control functions extensions between P6 and P8. */ +int StraightBinarySeconds = 0; +int ControlFunctions = 0; +int Debug = FALSE; +int Verbose = TRUE; +char *CommandName; + +#ifndef HAVE_SYS_SOUNDCARD_H +int level = AUDIO_MAX_GAIN / 8; /* output level */ +int port = AUDIO_LINE_OUT; /* output port */ +#endif + +int TotalSecondsCorrected = 0; +int TotalCyclesAdded = 0; +int TotalCyclesRemoved = 0; + + +/* + * Main program + */ +int +main( + int argc, /* command line options */ + char **argv /* poiniter to list of tokens */ + ) +{ +#ifndef HAVE_SYS_SOUNDCARD_H + audio_info_t info; /* Sun audio structure */ + int rval; /* For IOCTL calls */ +#endif + + struct timeval TimeValue; /* System clock at startup */ + time_t SecondsPartOfTime; /* Sent to gmtime() for calculation of TimeStructure (can apply offset). */ + time_t BaseRealTime; /* Base realtime so can determine seconds since starting. */ + time_t NowRealTime; /* New realtime to can determine seconds as of now. */ + unsigned SecondsRunningRealTime; /* Difference between NowRealTime and BaseRealTime. */ + unsigned SecondsRunningSimulationTime; /* Time that the simulator has been running. */ + int SecondsRunningDifference; /* Difference between what real time says we have been running */ + /* and what simulator says we have been running - will slowly */ + /* change because of clock drift. */ + int ExpectedRunningDifference = 0; /* Stable value that we've obtained from check at initial start-up. */ + unsigned StabilityCount; /* Used to check stability of difference while starting */ +#define RUN_BEFORE_STABILITY_CHECK (30) // Must run this many seconds before even checking stability. +#define MINIMUM_STABILITY_COUNT (10) // Number of consecutive differences that need to be within initial stability band to say we are stable. +#define INITIAL_STABILITY_BAND ( 2) // Determining initial stability for consecutive differences within +/- this value. +#define RUNNING_STABILITY_BAND ( 5) // When running, stability is defined as difference within +/- this value. + + struct tm *TimeStructure = NULL; /* Structure returned by gmtime */ + char device[200]; /* audio device */ + char code[200]; /* timecode */ + int temp; + int arg = 0; + int sw = 0; + int ptr = 0; + + int Year; + int Month; + int DayOfMonth; + int Hour; + int Minute; + int Second = 0; + int DayOfYear; + + int BitNumber; +#ifdef HAVE_SYS_SOUNDCARD_H + int AudioFormat; + int MonoStereo; /* 0=mono, 1=stereo */ +#define MONO (0) +#define STEREO (1) + int SampleRate; + int SampleRateDifference; +#endif + int SetSampleRate; + char FormatCharacter = '3'; /* Default is IRIG-B with IEEE 1344 extensions */ + char AsciiValue; + int HexValue; + int OldPtr = 0; + int FrameNumber = 0; + + /* Time offset for IEEE 1344 indication. */ + float TimeOffset = 0.0; + int OffsetSignBit = 0; + int OffsetOnes = 0; + int OffsetHalf = 0; + + int TimeQuality = 0; /* Time quality for IEEE 1344 indication. */ + char ParityString[200]; /* Partial output string, to calculate parity on. */ + int ParitySum = 0; + int ParityValue; + char *StringPointer; + + /* Flags to indicate requested leap second addition or deletion by command line option. */ + /* Should be mutually exclusive - generally ensured by code which interprets command line option. */ + int InsertLeapSecond = FALSE; + int DeleteLeapSecond = FALSE; + + /* Date and time of requested leap second addition or deletion. */ + int LeapYear = 0; + int LeapMonth = 0; + int LeapDayOfMonth = 0; + int LeapHour = 0; + int LeapMinute = 0; + int LeapDayOfYear = 0; + + /* State flag for the insertion and deletion of leap seconds, esp. deletion, */ + /* where the logic gets a bit tricky. */ + int LeapState = LEAPSTATE_NORMAL; + + /* Flags for indication of leap second pending and leap secod polarity in IEEE 1344 */ + int LeapSecondPending = FALSE; + int LeapSecondPolarity = FALSE; + + /* Date and time of requested switch into or out of DST by command line option. */ + int DstSwitchYear = 0; + int DstSwitchMonth = 0; + int DstSwitchDayOfMonth = 0; + int DstSwitchHour = 0; + int DstSwitchMinute = 0; + int DstSwitchDayOfYear = 0; + + /* Indicate when we have been asked to switch into or out of DST by command line option. */ + int DstSwitchFlag = FALSE; + + /* To allow predict for DstPendingFlag in IEEE 1344 */ + int DstSwitchPendingYear = 0; /* Default value isn't valid, but I don't care. */ + int DstSwitchPendingDayOfYear = 0; + int DstSwitchPendingHour = 0; + int DstSwitchPendingMinute = 0; + + /* /Flag for indication of a DST switch pending in IEEE 1344 */ + int DstPendingFlag = FALSE; + + /* Attempt at unmodulated */ + int Unmodulated = FALSE; + int UnmodulatedInverted = FALSE; + + /* Offset to actual time value sent. */ + float UseOffsetHoursFloat; + int UseOffsetSecondsInt = 0; + float UseOffsetSecondsFloat; + + /* String to allow us to put out reversed data - so can read the binary numbers. */ + char OutputDataString[OUTPUT_DATA_STRING_LENGTH]; + + /* Number of seconds to send before exiting. Default = 0 = forever. */ + int SecondsToSend = 0; + int CountOfSecondsSent = 0; /* Counter of seconds */ + + /* Flags to indicate whether to add or remove a cycle for time adjustment. */ + int AddCycle = FALSE; // We are ahead, add cycle to slow down and get back in sync. + int RemoveCycle = FALSE; // We are behind, remove cycle to slow down and get back in sync. + int RateCorrection; // Aggregate flag for passing to subroutines. + int EnableRateCorrection = TRUE; + + float RatioError; + + + CommandName = argv[0]; + + if (argc < 1) + { + Help (); + exit (-1); + } + + /* + * Parse options + */ + strlcpy(device, DEVICE, sizeof(device)); + Year = 0; + SetSampleRate = SECOND; + +#if HAVE_SYS_SOUNDCARD_H + while ((temp = getopt(argc, argv, "a:b:c:df:g:hHi:jk:l:o:q:r:stu:xy:z?")) != -1) { +#else + while ((temp = getopt(argc, argv, "a:b:c:df:g:hHi:jk:l:o:q:r:stu:v:xy:z?")) != -1) { +#endif + switch (temp) { + + case 'a': /* specify audio device (/dev/audio) */ + strlcpy(device, optarg, sizeof(device)); + break; + + case 'b': /* Remove (delete) a leap second at the end of the specified minute. */ + sscanf(optarg, "%2d%2d%2d%2d%2d", &LeapYear, &LeapMonth, &LeapDayOfMonth, + &LeapHour, &LeapMinute); + InsertLeapSecond = FALSE; + DeleteLeapSecond = TRUE; + break; + + case 'c': /* specify number of seconds to send output for before exiting, 0 = forever */ + sscanf(optarg, "%d", &SecondsToSend); + break; + + case 'd': /* set DST for summer (WWV/H only) / start with DST active (IRIG) */ + DstFlag++; + break; + + case 'f': /* select format: i=IRIG-98 (default) 2=IRIG-2004 3-IRIG+IEEE-1344 w=WWV(H) */ + sscanf(optarg, "%c", &FormatCharacter); + break; + + case 'g': /* Date and time to switch back into / out of DST active. */ + sscanf(optarg, "%2d%2d%2d%2d%2d", &DstSwitchYear, &DstSwitchMonth, &DstSwitchDayOfMonth, + &DstSwitchHour, &DstSwitchMinute); + DstSwitchFlag = TRUE; + break; + + case 'h': + case 'H': + case '?': + Help (); + exit(-1); + break; + + case 'i': /* Insert (add) a leap second at the end of the specified minute. */ + sscanf(optarg, "%2d%2d%2d%2d%2d", &LeapYear, &LeapMonth, &LeapDayOfMonth, + &LeapHour, &LeapMinute); + InsertLeapSecond = TRUE; + DeleteLeapSecond = FALSE; + break; + + case 'j': + EnableRateCorrection = FALSE; + break; + + case 'k': + sscanf (optarg, "%d", &RateCorrection); + EnableRateCorrection = FALSE; + if (RateCorrection < 0) + { + RemoveCycle = TRUE; + AddCycle = FALSE; + + if (Verbose) + printf ("\n> Forcing rate correction removal of cycle...\n"); + } + else + { + if (RateCorrection > 0) + { + RemoveCycle = FALSE; + AddCycle = TRUE; + + if (Verbose) + printf ("\n> Forcing rate correction addition of cycle...\n"); + } + } + break; + + case 'l': /* use time offset from UTC */ + sscanf(optarg, "%f", &UseOffsetHoursFloat); + UseOffsetSecondsFloat = UseOffsetHoursFloat * (float) SECONDS_PER_HOUR; + UseOffsetSecondsInt = (int) (UseOffsetSecondsFloat + 0.5); + break; + + case 'o': /* Set IEEE 1344 time offset in hours - positive or negative, to the half hour */ + sscanf(optarg, "%f", &TimeOffset); + if (TimeOffset >= -0.2) + { + OffsetSignBit = 0; + + if (TimeOffset > 0) + { + OffsetOnes = TimeOffset; + + if ( (TimeOffset - floor(TimeOffset)) >= 0.4) + OffsetHalf = 1; + else + OffsetHalf = 0; + } + else + { + OffsetOnes = 0; + OffsetHalf = 0; + } + } + else + { + OffsetSignBit = 1; + OffsetOnes = -TimeOffset; + + if ( (ceil(TimeOffset) - TimeOffset) >= 0.4) + OffsetHalf = 1; + else + OffsetHalf = 0; + } + + /*printf ("\nGot TimeOffset = %3.1f, OffsetSignBit = %d, OffsetOnes = %d, OffsetHalf = %d...\n", + TimeOffset, OffsetSignBit, OffsetOnes, OffsetHalf); + */ + break; + + case 'q': /* Hex quality code 0 to 0x0F - 0 = maximum, 0x0F = no lock */ + sscanf(optarg, "%x", &TimeQuality); + TimeQuality &= 0x0F; + /*printf ("\nGot TimeQuality = 0x%1X...\n", TimeQuality); + */ + break; + + case 'r': /* sample rate (nominally 8000, integer close to 8000 I hope) */ + sscanf(optarg, "%d", &SetSampleRate); + break; + + case 's': /* set leap warning bit (WWV/H only) */ + leap++; + break; + + case 't': /* select WWVH sync frequency */ + tone = 1200; + break; + + case 'u': /* set DUT1 offset (-7 to +7) */ + sscanf(optarg, "%d", &dut1); + if (dut1 < 0) + dut1 = abs(dut1); + else + dut1 |= 0x8; + break; + +#ifndef HAVE_SYS_SOUNDCARD_H + case 'v': /* set output level (0-255) */ + sscanf(optarg, "%d", &level); + break; +#endif + + case 'x': /* Turn off verbose output. */ + Verbose = FALSE; + break; + + case 'y': /* Set initial date and time */ + sscanf(optarg, "%2d%2d%2d%2d%2d%2d", &Year, &Month, &DayOfMonth, + &Hour, &Minute, &Second); + utc++; + break; + + case 'z': /* Turn on Debug output (also turns on Verbose below) */ + Debug = TRUE; + break; + + default: + printf("Invalid option \"%c\", aborting...\n", temp); + exit (-1); + break; + } + } + + if (Debug) + Verbose = TRUE; + + if (InsertLeapSecond || DeleteLeapSecond) + { + LeapDayOfYear = ConvertMonthDayToDayOfYear (LeapYear, LeapMonth, LeapDayOfMonth); + + if (Debug) + { + printf ("\nHave request for leap second %s at year %4d day %3d at %2.2dh%2.2d....\n",\ + DeleteLeapSecond ? "DELETION" : (InsertLeapSecond ? "ADDITION" : "( error ! )" ), + LeapYear, LeapDayOfYear, LeapHour, LeapMinute); + } + } + + if (DstSwitchFlag) + { + DstSwitchDayOfYear = ConvertMonthDayToDayOfYear (DstSwitchYear, DstSwitchMonth, DstSwitchDayOfMonth); + + /* Figure out time of minute previous to DST switch, so can put up warning flag in IEEE 1344 */ + DstSwitchPendingYear = DstSwitchYear; + DstSwitchPendingDayOfYear = DstSwitchDayOfYear; + DstSwitchPendingHour = DstSwitchHour; + DstSwitchPendingMinute = DstSwitchMinute - 1; + if (DstSwitchPendingMinute < 0) + { + DstSwitchPendingMinute = 59; + DstSwitchPendingHour--; + if (DstSwitchPendingHour < 0) + { + DstSwitchPendingHour = 23; + DstSwitchPendingDayOfYear--; + if (DstSwitchPendingDayOfYear < 1) + { + DstSwitchPendingYear--; + } + } + } + + if (Debug) + { + printf ("\nHave DST switch request for year %4d day %3d at %2.2dh%2.2d,", + DstSwitchYear, DstSwitchDayOfYear, DstSwitchHour, DstSwitchMinute); + printf ("\n so will have warning at year %4d day %3d at %2.2dh%2.2d.\n", + DstSwitchPendingYear, DstSwitchPendingDayOfYear, DstSwitchPendingHour, DstSwitchPendingMinute); + } + } + + switch (tolower(FormatCharacter)) { + case 'i': + printf ("\nFormat is IRIG-1998 (no year coded)...\n\n"); + encode = IRIG; + IrigIncludeYear = FALSE; + IrigIncludeIeee = FALSE; + break; + + case '2': + printf ("\nFormat is IRIG-2004 (BCD year coded)...\n\n"); + encode = IRIG; + IrigIncludeYear = TRUE; + IrigIncludeIeee = FALSE; + break; + + case '3': + printf ("\nFormat is IRIG with IEEE-1344 (BCD year coded, and more control functions)...\n\n"); + encode = IRIG; + IrigIncludeYear = TRUE; + IrigIncludeIeee = TRUE; + break; + + case '4': + printf ("\nFormat is unmodulated IRIG with IEEE-1344 (BCD year coded, and more control functions)...\n\n"); + encode = IRIG; + IrigIncludeYear = TRUE; + IrigIncludeIeee = TRUE; + + Unmodulated = TRUE; + UnmodulatedInverted = FALSE; + break; + + case '5': + printf ("\nFormat is inverted unmodulated IRIG with IEEE-1344 (BCD year coded, and more control functions)...\n\n"); + encode = IRIG; + IrigIncludeYear = TRUE; + IrigIncludeIeee = TRUE; + + Unmodulated = TRUE; + UnmodulatedInverted = TRUE; + break; + + case 'w': + printf ("\nFormat is WWV(H)...\n\n"); + encode = WWV; + break; + + default: + printf ("\n\nUnexpected format value of \'%c\', cannot parse, aborting...\n\n", FormatCharacter); + exit (-1); + break; + } + + /* + * Open audio device and set options + */ + fd = open(device, O_WRONLY); + if (fd <= 0) { + printf("Unable to open audio device \"%s\", aborting: %s\n", device, strerror(errno)); + exit(1); + } + +#ifdef HAVE_SYS_SOUNDCARD_H + /* First set coding type */ + AudioFormat = AFMT_MU_LAW; + if (ioctl(fd, SNDCTL_DSP_SETFMT, &AudioFormat)==-1) + { /* Fatal error */ + printf ("\nUnable to set output format, aborting...\n\n"); + exit(-1); + } + + if (AudioFormat != AFMT_MU_LAW) + { + printf ("\nUnable to set output format for mu law, aborting...\n\n"); + exit(-1); + } + + /* Next set number of channels */ + MonoStereo = MONO; /* Mono */ + if (ioctl(fd, SNDCTL_DSP_STEREO, &MonoStereo)==-1) + { /* Fatal error */ + printf ("\nUnable to set mono/stereo, aborting...\n\n"); + exit(-1); + } + + if (MonoStereo != MONO) + { + printf ("\nUnable to set mono/stereo for mono, aborting...\n\n"); + exit(-1); + } + + /* Now set sample rate */ + SampleRate = SetSampleRate; + if (ioctl(fd, SNDCTL_DSP_SPEED, &SampleRate)==-1) + { /* Fatal error */ + printf ("\nUnable to set sample rate to %d, returned %d, aborting...\n\n", SetSampleRate, SampleRate); + exit(-1); + } + + SampleRateDifference = SampleRate - SetSampleRate; + + if (SampleRateDifference < 0) + SampleRateDifference = - SampleRateDifference; + + /* Fixed allowable sample rate error 0.1% */ + if (SampleRateDifference > (SetSampleRate/1000)) + { + printf ("\nUnable to set sample rate to %d, result was %d, more than 0.1 percent, aborting...\n\n", SetSampleRate, SampleRate); + exit(-1); + } + else + { + /* printf ("\nAttempt to set sample rate to %d, actual %d...\n\n", SetSampleRate, SampleRate); */ + } +#else + rval = ioctl(fd, AUDIO_GETINFO, &info); + if (rval < 0) { + printf("\naudio control %s", strerror(errno)); + exit(0); + } + info.play.port = port; + info.play.gain = level; + info.play.sample_rate = SetSampleRate; + info.play.channels = 1; + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_ULAW; + printf("\nport %d gain %d rate %d chan %d prec %d encode %d\n", + info.play.port, info.play.gain, info.play.sample_rate, + info.play.channels, info.play.precision, + info.play.encoding); + ioctl(fd, AUDIO_SETINFO, &info); +#endif + + /* + * Unless specified otherwise, read the system clock and + * initialize the time. + */ + gettimeofday(&TimeValue, NULL); // Now always read the system time to keep "real time" of operation. + NowRealTime = BaseRealTime = SecondsPartOfTime = TimeValue.tv_sec; + SecondsRunningSimulationTime = 0; // Just starting simulation, running zero seconds as of now. + StabilityCount = 0; // No stability yet. + + if (utc) + { + DayOfYear = ConvertMonthDayToDayOfYear (Year, Month, DayOfMonth); + } + else + { + /* Apply offset to time. */ + if (UseOffsetSecondsInt >= 0) + SecondsPartOfTime += (time_t) UseOffsetSecondsInt; + else + SecondsPartOfTime -= (time_t) (-UseOffsetSecondsInt); + + TimeStructure = gmtime(&SecondsPartOfTime); + Minute = TimeStructure->tm_min; + Hour = TimeStructure->tm_hour; + DayOfYear = TimeStructure->tm_yday + 1; + Year = TimeStructure->tm_year % 100; + Second = TimeStructure->tm_sec; + + /* + * Delay the first second so the generator is accurately + * aligned with the system clock within one sample (125 + * microseconds ). + */ + delay(SECOND - TimeValue.tv_usec * 8 / 1000); + } + + StraightBinarySeconds = Second + (Minute * SECONDS_PER_MINUTE) + (Hour * SECONDS_PER_HOUR); + + memset(code, 0, sizeof(code)); + switch (encode) { + + /* + * For WWV/H and default time, carefully set the signal + * generator seconds number to agree with the current time. + */ + case WWV: + printf("WWV time signal, starting point:\n"); + printf(" Year = %02d, Day of year = %03d, Time = %02d:%02d:%02d, Minute tone = %d Hz, Hour tone = %d Hz.\n", + Year, DayOfYear, Hour, Minute, Second, tone, HourTone); + snprintf(code, sizeof(code), "%01d%03d%02d%02d%01d", + Year / 10, DayOfYear, Hour, Minute, Year % 10); + if (Verbose) + { + printf("\n Year = %2.2d, Day of year = %3d, Time = %2.2d:%2.2d:%2.2d, Code = %s", + Year, DayOfYear, Hour, Minute, Second, code); + + if ((EnableRateCorrection) || (RemoveCycle) || (AddCycle)) + printf (", CountOfSecondsSent = %d, TotalCyclesAdded = %d, TotalCyclesRemoved = %d\n", CountOfSecondsSent, TotalCyclesAdded, TotalCyclesRemoved); + else + printf ("\n"); + } + + ptr = 8; + for (BitNumber = 0; BitNumber <= Second; BitNumber++) { + if (progx[BitNumber].sw == DEC) + ptr--; + } + break; + + /* + * For IRIG the signal generator runs every second, so requires + * no additional alignment. + */ + case IRIG: + printf ("IRIG-B time signal, starting point:\n"); + printf (" Year = %02d, Day of year = %03d, Time = %02d:%02d:%02d, Straight binary seconds (SBS) = %05d / 0x%04X.\n", + Year, DayOfYear, Hour, Minute, Second, StraightBinarySeconds, StraightBinarySeconds); + printf ("\n"); + if (Verbose) + { + printf ("Codes: \".\" = marker/position indicator, \"-\" = zero dummy bit, \"0\" = zero bit, \"1\" = one bit.\n"); + if ((EnableRateCorrection) || (AddCycle) || (RemoveCycle)) + { + printf (" \"o\" = short zero, \"*\" = long zero, \"x\" = short one, \"+\" = long one.\n"); + } + printf ("Numerical values are time order reversed in output to make it easier to read.\n"); + /* 111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999 */ + /* 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 */ + printf ("\n"); + printf ("Legend of output codes:\n"); + //printf ("\n"); + //printf ("| StraightBinSecs | IEEE_1344_Control | Year | Day_of_Year | Hours | Minutes |Seconds |\n"); + //printf ("| --------------- | ----------------- | ---- | ----------- | ----- | ------- |------- |\n"); + //printf ("| | | | | | | |\n"); + } + break; + } + + /* + * Run the signal generator to generate new timecode strings + * once per minute for WWV/H and once per second for IRIG. + */ + for (CountOfSecondsSent=0; ((SecondsToSend==0) || (CountOfSecondsSent<SecondsToSend)); CountOfSecondsSent++) + { + if ((encode == IRIG) && (((Second % 20) == 0) || (CountOfSecondsSent == 0))) + { + printf ("\n"); + + printf (" Year = %02d, Day of year = %03d, Time = %02d:%02d:%02d, Straight binary seconds (SBS) = %05d / 0x%04X.\n", + Year, DayOfYear, Hour, Minute, Second, StraightBinarySeconds, StraightBinarySeconds); + if ((EnableRateCorrection) || (RemoveCycle) || (AddCycle)) + { + printf (" CountOfSecondsSent = %d, TotalCyclesAdded = %d, TotalCyclesRemoved = %d\n", CountOfSecondsSent, TotalCyclesAdded, TotalCyclesRemoved); + if ((CountOfSecondsSent != 0) && ((TotalCyclesAdded != 0) || (TotalCyclesRemoved != 0))) + { + RatioError = ((float) (TotalCyclesAdded - TotalCyclesRemoved)) / (1000.0 * (float) CountOfSecondsSent); + printf (" Adjusted by %2.1f%%, apparent send frequency is %4.2f Hz not %d Hz.\n\n", + RatioError*100.0, (1.0+RatioError)*((float) SetSampleRate), SetSampleRate); + } + } + else + printf ("\n"); + + /* printf ("|Seconds | Minutes | Hours | Day_of_Year | Year | IEEE_1344_Control | StraightBinSecs |\n"); + printf ("|------- | ------- | ----- | ----------- | ---- | ----------------- |-------------------|\n"); + printf ("| | | | | | | |\n");*/ + printf ("| StraightBinSecs | IEEE_1344_Control | Year | Day_of_Year | Hours | Minutes |Seconds |\n"); + printf ("| --------------- | ----------------- | ---- | ----------- | ----- | ------- |------- |\n"); + printf ("| | | | | | | |\n"); + } + + if (RemoveCycle) + { + RateCorrection = -1; + TotalSecondsCorrected ++; + } + else + { + if (AddCycle) + { + TotalSecondsCorrected ++; + RateCorrection = +1; + } + else + RateCorrection = 0; + } + + /* + * Crank the state machine to propagate carries to the + * year of century. Note that we delayed up to one + * second for alignment after reading the time, so this + * is the next second. + */ + + if (LeapState == LEAPSTATE_NORMAL) + { + /* If on the second of a leap (second 59 in the specified minute), then add or delete a second */ + if ((Year == LeapYear) && (DayOfYear == LeapDayOfYear) && (Hour == LeapHour) && (Minute == LeapMinute)) + { + /* To delete a second, which means we go from 58->60 instead of 58->59->00. */ + if ((DeleteLeapSecond) && (Second == 58)) + { + LeapState = LEAPSTATE_DELETING; + + if (Debug) + printf ("\n<--- Ready to delete a leap second...\n"); + } + else + { /* Delete takes precedence over insert. */ + /* To add a second, which means we go from 59->60->00 instead of 59->00. */ + if ((InsertLeapSecond) && (Second == 59)) + { + LeapState = LEAPSTATE_INSERTING; + + if (Debug) + printf ("\n<--- Ready to insert a leap second...\n"); + } + } + } + } + + switch (LeapState) + { + case LEAPSTATE_NORMAL: + Second = (Second + 1) % 60; + break; + + case LEAPSTATE_DELETING: + Second = 0; + LeapState = LEAPSTATE_NORMAL; + + if (Debug) + printf ("\n<--- Deleting a leap second...\n"); + break; + + case LEAPSTATE_INSERTING: + Second = 60; + LeapState = LEAPSTATE_ZERO_AFTER_INSERT; + + if (Debug) + printf ("\n<--- Inserting a leap second...\n"); + break; + + case LEAPSTATE_ZERO_AFTER_INSERT: + Second = 0; + LeapState = LEAPSTATE_NORMAL; + + if (Debug) + printf ("\n<--- Inserted a leap second, now back to zero...\n"); + break; + + default: + printf ("\n\nLeap second state invalid value of %d, aborting...", LeapState); + exit (-1); + break; + } + + /* Check for second rollover, increment minutes and ripple upward if required. */ + if (Second == 0) { + Minute++; + if (Minute >= 60) { + Minute = 0; + Hour++; + } + + /* Check for activation of DST switch. */ + /* If DST is active, this would mean that at the appointed time, we de-activate DST, */ + /* which translates to going backward an hour (repeating the last hour). */ + /* If DST is not active, this would mean that at the appointed time, we activate DST, */ + /* which translates to going forward an hour (skipping the next hour). */ + if (DstSwitchFlag) + { + /* The actual switch happens on the zero'th second of the actual minute specified. */ + if ((Year == DstSwitchYear) && (DayOfYear == DstSwitchDayOfYear) && (Hour == DstSwitchHour) && (Minute == DstSwitchMinute)) + { + if (DstFlag == 0) + { /* DST flag is zero, not in DST, going to DST, "spring ahead", so increment hour by two instead of one. */ + Hour++; + DstFlag = 1; + + /* Must adjust offset to keep consistent with UTC. */ + /* Here we have to increase offset by one hour. If it goes from negative to positive, then we fix that. */ + if (OffsetSignBit == 0) + { /* Offset is positive */ + if (OffsetOnes == 0x0F) + { + OffsetSignBit = 1; + OffsetOnes = (OffsetHalf == 0) ? 8 : 7; + } + else + OffsetOnes++; + } + else + { /* Offset is negative */ + if (OffsetOnes == 0) + { + OffsetSignBit = 0; + OffsetOnes = (OffsetHalf == 0) ? 1 : 0; + } + else + OffsetOnes--; + } + + if (Debug) + printf ("\n<--- DST activated, spring ahead an hour, new offset !...\n"); + } + else + { /* DST flag is non zero, in DST, going out of DST, "fall back", so no increment of hour. */ + Hour--; + DstFlag = 0; + + /* Must adjust offset to keep consistent with UTC. */ + /* Here we have to reduce offset by one hour. If it goes negative, then we fix that. */ + if (OffsetSignBit == 0) + { /* Offset is positive */ + if (OffsetOnes == 0) + { + OffsetSignBit = 1; + OffsetOnes = (OffsetHalf == 0) ? 1 : 0; + } + else + OffsetOnes--; + } + else + { /* Offset is negative */ + if (OffsetOnes == 0x0F) + { + OffsetSignBit = 0; + OffsetOnes = (OffsetHalf == 0) ? 8 : 7; + } + else + OffsetOnes++; + } + + if (Debug) + printf ("\n<--- DST de-activated, fall back an hour!...\n"); + } + + DstSwitchFlag = FALSE; /* One time deal, not intended to run this program past two switches... */ + } + } + + if (Hour >= 24) { + /* Modified, just in case dumb case where activating DST advances 23h59:59 -> 01h00:00 */ + Hour = Hour % 24; + DayOfYear++; + } + + /* + * At year rollover check for leap second. + */ + if (DayOfYear >= (Year & 0x3 ? 366 : 367)) { + if (leap) { + WWV_Second(DATA0, RateCorrection); + if (Verbose) + printf("\nLeap!"); + leap = 0; + } + DayOfYear = 1; + Year++; + } + if (encode == WWV) { + snprintf(code, sizeof(code), + "%01d%03d%02d%02d%01d", Year / 10, + DayOfYear, Hour, Minute, Year % 10); + if (Verbose) + printf("\n Year = %2.2d, Day of year = %3d, Time = %2.2d:%2.2d:%2.2d, Code = %s", + Year, DayOfYear, Hour, Minute, Second, code); + + if ((EnableRateCorrection) || (RemoveCycle) || (AddCycle)) + { + printf (", CountOfSecondsSent = %d, TotalCyclesAdded = %d, TotalCyclesRemoved = %d\n", CountOfSecondsSent, TotalCyclesAdded, TotalCyclesRemoved); + if ((CountOfSecondsSent != 0) && ((TotalCyclesAdded != 0) || (TotalCyclesRemoved != 0))) + { + RatioError = ((float) (TotalCyclesAdded - TotalCyclesRemoved)) / (1000.0 * (float) CountOfSecondsSent); + printf (" Adjusted by %2.1f%%, apparent send frequency is %4.2f Hz not %d Hz.\n\n", + RatioError*100.0, (1.0+RatioError)*((float) SetSampleRate), SetSampleRate); + } + } + else + printf ("\n"); + + ptr = 8; + } + } /* End of "if (Second == 0)" */ + + /* After all that, if we are in the minute just prior to a leap second, warn of leap second pending */ + /* and of the polarity */ + if ((Year == LeapYear) && (DayOfYear == LeapDayOfYear) && (Hour == LeapHour) && (Minute == LeapMinute)) + { + LeapSecondPending = TRUE; + LeapSecondPolarity = DeleteLeapSecond; + } + else + { + LeapSecondPending = FALSE; + LeapSecondPolarity = FALSE; + } + + /* Notification through IEEE 1344 happens during the whole minute previous to the minute specified. */ + /* The time of that minute has been previously calculated. */ + if ((Year == DstSwitchPendingYear) && (DayOfYear == DstSwitchPendingDayOfYear) && + (Hour == DstSwitchPendingHour) && (Minute == DstSwitchPendingMinute)) + { + DstPendingFlag = TRUE; + } + else + { + DstPendingFlag = FALSE; + } + + + StraightBinarySeconds = Second + (Minute * SECONDS_PER_MINUTE) + (Hour * SECONDS_PER_HOUR); + + if (encode == IRIG) { + if (IrigIncludeIeee) + { + if ((OffsetOnes == 0) && (OffsetHalf == 0)) + OffsetSignBit = 0; + + ControlFunctions = (LeapSecondPending == 0 ? 0x00000 : 0x00001) | (LeapSecondPolarity == 0 ? 0x00000 : 0x00002) + | (DstPendingFlag == 0 ? 0x00000 : 0x00004) | (DstFlag == 0 ? 0x00000 : 0x00008) + | (OffsetSignBit == 0 ? 0x00000 : 0x00010) | ((OffsetOnes & 0x0F) << 5) | (OffsetHalf == 0 ? 0x00000 : 0x00200) + | ((TimeQuality & 0x0F) << 10); + /* if (Verbose) + printf ("\nDstFlag = %d, OffsetSignBit = %d, OffsetOnes = %d, OffsetHalf = %d, TimeQuality = 0x%1.1X ==> ControlFunctions = 0x%5.5X...", + DstFlag, OffsetSignBit, OffsetOnes, OffsetHalf, TimeQuality, ControlFunctions); + */ + } + else + ControlFunctions = 0; + + /* + YearDay HourMin Sec + snprintf(code, sizeof(code), "%04x%04d%06d%02d%02d%02d", + 0, Year, DayOfYear, Hour, Minute, Second); + */ + if (IrigIncludeYear) { + snprintf(ParityString, sizeof(ParityString), + "%04X%02d%04d%02d%02d%02d", + ControlFunctions & 0x7FFF, Year, + DayOfYear, Hour, Minute, Second); + } else { + snprintf(ParityString, sizeof(ParityString), + "%04X%02d%04d%02d%02d%02d", + ControlFunctions & 0x7FFF, + 0, DayOfYear, Hour, Minute, Second); + } + + if (IrigIncludeIeee) + { + ParitySum = 0; + for (StringPointer=ParityString; *StringPointer!=NUL; StringPointer++) + { + switch (toupper(*StringPointer)) + { + case '1': + case '2': + case '4': + case '8': + ParitySum += 1; + break; + + case '3': + case '5': + case '6': + case '9': + case 'A': + case 'C': + ParitySum += 2; + break; + + case '7': + case 'B': + case 'D': + case 'E': + ParitySum += 3; + break; + + case 'F': + ParitySum += 4; + break; + } + } + + if ((ParitySum & 0x01) == 0x01) + ParityValue = 0x01; + else + ParityValue = 0; + } + else + ParityValue = 0; + + ControlFunctions |= ((ParityValue & 0x01) << 14); + + if (IrigIncludeYear) { + snprintf(code, sizeof(code), + /* YearDay HourMin Sec */ + "%05X%05X%02d%04d%02d%02d%02d", + StraightBinarySeconds, + ControlFunctions, Year, DayOfYear, + Hour, Minute, Second); + } else { + snprintf(code, sizeof(code), + /* YearDay HourMin Sec */ + "%05X%05X%02d%04d%02d%02d%02d", + StraightBinarySeconds, + ControlFunctions, 0, DayOfYear, + Hour, Minute, Second); + } + + if (Debug) + printf("\nCode string: %s, ParityString = %s, ParitySum = 0x%2.2X, ParityValue = %d, DstFlag = %d...\n", code, ParityString, ParitySum, ParityValue, DstFlag); + + ptr = strlen(code)-1; + OldPtr = 0; + } + + /* + * Generate data for the second + */ + switch (encode) { + + /* + * The IRIG second consists of 20 BCD digits of width- + * modulateod pulses at 2, 5 and 8 ms and modulated 50 + * percent on the 1000-Hz carrier. + */ + case IRIG: + /* Initialize the output string */ + OutputDataString[0] = '\0'; + + for (BitNumber = 0; BitNumber < 100; BitNumber++) { + FrameNumber = (BitNumber/10) + 1; + switch (FrameNumber) + { + case 1: + /* bits 0 to 9, first frame */ + sw = progz[BitNumber % 10].sw; + arg = progz[BitNumber % 10].arg; + break; + + case 2: + case 3: + case 4: + case 5: + case 6: + /* bits 10 to 59, second to sixth frame */ + sw = progy[BitNumber % 10].sw; + arg = progy[BitNumber % 10].arg; + break; + + case 7: + /* bits 60 to 69, seventh frame */ + sw = progw[BitNumber % 10].sw; + arg = progw[BitNumber % 10].arg; + break; + + case 8: + /* bits 70 to 79, eighth frame */ + sw = progv[BitNumber % 10].sw; + arg = progv[BitNumber % 10].arg; + break; + + case 9: + /* bits 80 to 89, ninth frame */ + sw = progw[BitNumber % 10].sw; + arg = progw[BitNumber % 10].arg; + break; + + case 10: + /* bits 90 to 99, tenth frame */ + sw = progu[BitNumber % 10].sw; + arg = progu[BitNumber % 10].arg; + break; + + default: + /* , Unexpected values of FrameNumber */ + printf ("\n\nUnexpected value of FrameNumber = %d, cannot parse, aborting...\n\n", FrameNumber); + exit (-1); + break; + } + + switch(sw) { + + case DECC: /* decrement pointer and send bit. */ + ptr--; + case COEF: /* send BCD bit */ + AsciiValue = toupper(code[ptr]); + HexValue = isdigit(AsciiValue) ? AsciiValue - '0' : (AsciiValue - 'A')+10; + /* if (Debug) { + if (ptr != OldPtr) { + if (Verbose) + printf("\n(%c->%X)", AsciiValue, HexValue); + OldPtr = ptr; + } + } + */ + // OK, adjust all unused bits in hundreds of days. + if ((FrameNumber == 5) && ((BitNumber % 10) > 1)) + { + if (RateCorrection < 0) + { // Need to remove cycles to catch up. + if ((HexValue & arg) != 0) + { + if (Unmodulated) + { + poop(M5, 1000, HIGH, UnmodulatedInverted); + poop(M5-1, 1000, LOW, UnmodulatedInverted); + + TotalCyclesRemoved += 1; + } + else + { + peep(M5, 1000, HIGH); + peep(M5-1, 1000, LOW); + + TotalCyclesRemoved += 1; + } + strlcat(OutputDataString, "x", OUTPUT_DATA_STRING_LENGTH); + } + else + { + if (Unmodulated) + { + poop(M2, 1000, HIGH, UnmodulatedInverted); + poop(M8-1, 1000, LOW, UnmodulatedInverted); + + TotalCyclesRemoved += 1; + } + else + { + peep(M2, 1000, HIGH); + peep(M8-1, 1000, LOW); + + TotalCyclesRemoved += 1; + } + strlcat(OutputDataString, "o", OUTPUT_DATA_STRING_LENGTH); + } + } // End of true clause for "if (RateCorrection < 0)" + else + { // Else clause for "if (RateCorrection < 0)" + if (RateCorrection > 0) + { // Need to add cycles to slow back down. + if ((HexValue & arg) != 0) + { + if (Unmodulated) + { + poop(M5, 1000, HIGH, UnmodulatedInverted); + poop(M5+1, 1000, LOW, UnmodulatedInverted); + + TotalCyclesAdded += 1; + } + else + { + peep(M5, 1000, HIGH); + peep(M5+1, 1000, LOW); + + TotalCyclesAdded += 1; + } + strlcat(OutputDataString, "+", OUTPUT_DATA_STRING_LENGTH); + } + else + { + if (Unmodulated) + { + poop(M2, 1000, HIGH, UnmodulatedInverted); + poop(M8+1, 1000, LOW, UnmodulatedInverted); + + TotalCyclesAdded += 1; + } + else + { + peep(M2, 1000, HIGH); + peep(M8+1, 1000, LOW); + + TotalCyclesAdded += 1; + } + strlcat(OutputDataString, "*", OUTPUT_DATA_STRING_LENGTH); + } + } // End of true clause for "if (RateCorrection > 0)" + else + { // Else clause for "if (RateCorrection > 0)" + // Rate is OK, just do what you feel! + if ((HexValue & arg) != 0) + { + if (Unmodulated) + { + poop(M5, 1000, HIGH, UnmodulatedInverted); + poop(M5, 1000, LOW, UnmodulatedInverted); + } + else + { + peep(M5, 1000, HIGH); + peep(M5, 1000, LOW); + } + strlcat(OutputDataString, "1", OUTPUT_DATA_STRING_LENGTH); + } + else + { + if (Unmodulated) + { + poop(M2, 1000, HIGH, UnmodulatedInverted); + poop(M8, 1000, LOW, UnmodulatedInverted); + } + else + { + peep(M2, 1000, HIGH); + peep(M8, 1000, LOW); + } + strlcat(OutputDataString, "0", OUTPUT_DATA_STRING_LENGTH); + } + } // End of else clause for "if (RateCorrection > 0)" + } // End of else claues for "if (RateCorrection < 0)" + } // End of true clause for "if ((FrameNumber == 5) && (BitNumber == 8))" + else + { // Else clause for "if ((FrameNumber == 5) && (BitNumber == 8))" + if ((HexValue & arg) != 0) + { + if (Unmodulated) + { + poop(M5, 1000, HIGH, UnmodulatedInverted); + poop(M5, 1000, LOW, UnmodulatedInverted); + } + else + { + peep(M5, 1000, HIGH); + peep(M5, 1000, LOW); + } + strlcat(OutputDataString, "1", OUTPUT_DATA_STRING_LENGTH); + } + else + { + if (Unmodulated) + { + poop(M2, 1000, HIGH, UnmodulatedInverted); + poop(M8, 1000, LOW, UnmodulatedInverted); + } + else + { + peep(M2, 1000, HIGH); + peep(M8, 1000, LOW); + } + strlcat(OutputDataString, "0", OUTPUT_DATA_STRING_LENGTH); + } + } // end of else clause for "if ((FrameNumber == 5) && (BitNumber == 8))" + break; + + case DECZ: /* decrement pointer and send zero bit */ + ptr--; + if (Unmodulated) + { + poop(M2, 1000, HIGH, UnmodulatedInverted); + poop(M8, 1000, LOW, UnmodulatedInverted); + } + else + { + peep(M2, 1000, HIGH); + peep(M8, 1000, LOW); + } + strlcat(OutputDataString, "-", OUTPUT_DATA_STRING_LENGTH); + break; + + case DEC: /* send marker/position indicator IM/PI bit */ + ptr--; + case NODEC: /* send marker/position indicator IM/PI bit but no decrement pointer */ + case MIN: /* send "second start" marker/position indicator IM/PI bit */ + if (Unmodulated) + { + poop(arg, 1000, HIGH, UnmodulatedInverted); + poop(10 - arg, 1000, LOW, UnmodulatedInverted); + } + else + { + peep(arg, 1000, HIGH); + peep(10 - arg, 1000, LOW); + } + strlcat(OutputDataString, ".", OUTPUT_DATA_STRING_LENGTH); + break; + + default: + printf ("\n\nUnknown state machine value \"%d\", unable to continue, aborting...\n\n", sw); + exit (-1); + break; + } + if (ptr < 0) + break; + } + ReverseString ( OutputDataString ); + if (Verbose) + { + printf("%s", OutputDataString); + if (RateCorrection > 0) + printf(" fast\n"); + else + { + if (RateCorrection < 0) + printf (" slow\n"); + else + printf ("\n"); + } + } + break; + + /* + * The WWV/H second consists of 9 BCD digits of width- + * modulateod pulses 200, 500 and 800 ms at 100-Hz. + */ + case WWV: + sw = progx[Second].sw; + arg = progx[Second].arg; + switch(sw) { + + case DATA: /* send data bit */ + WWV_Second(arg, RateCorrection); + if (Verbose) + { + if (arg == DATA0) + printf ("0"); + else + { + if (arg == DATA1) + printf ("1"); + else + { + if (arg == PI) + printf ("P"); + else + printf ("?"); + } + } + } + break; + + case DATAX: /* send data bit */ + WWV_SecondNoTick(arg, RateCorrection); + if (Verbose) + { + if (arg == DATA0) + printf ("0"); + else + { + if (arg == DATA1) + printf ("1"); + else + { + if (arg == PI) + printf ("P"); + else + printf ("?"); + } + } + } + break; + + case COEF: /* send BCD bit */ + if (code[ptr] & arg) { + WWV_Second(DATA1, RateCorrection); + if (Verbose) + printf("1"); + } else { + WWV_Second(DATA0, RateCorrection); + if (Verbose) + printf("0"); + } + break; + + case LEAP: /* send leap bit */ + if (leap) { + WWV_Second(DATA1, RateCorrection); + if (Verbose) + printf("L"); + } else { + WWV_Second(DATA0, RateCorrection); + if (Verbose) + printf("0"); + } + break; + + case DEC: /* send data bit */ + ptr--; + WWV_Second(arg, RateCorrection); + if (Verbose) + { + if (arg == DATA0) + printf ("0"); + else + { + if (arg == DATA1) + printf ("1"); + else + { + if (arg == PI) + printf ("P"); + else + printf ("?"); + } + } + } + break; + + case DECX: /* send data bit with no tick */ + ptr--; + WWV_SecondNoTick(arg, RateCorrection); + if (Verbose) + { + if (arg == DATA0) + printf ("0"); + else + { + if (arg == DATA1) + printf ("1"); + else + { + if (arg == PI) + printf ("P"); + else + printf ("?"); + } + } + } + break; + + case MIN: /* send minute sync */ + if (Minute == 0) + { + peep(arg, HourTone, HIGH); + + if (RateCorrection < 0) + { + peep( 990 - arg, HourTone, OFF); + TotalCyclesRemoved += 10; + + if (Debug) + printf ("\n* Shorter Second: "); + } + else + { + if (RateCorrection > 0) + { + peep(1010 - arg, HourTone, OFF); + + TotalCyclesAdded += 10; + + if (Debug) + printf ("\n* Longer Second: "); + } + else + { + peep(1000 - arg, HourTone, OFF); + } + } + + if (Verbose) + printf("H"); + } + else + { + peep(arg, tone, HIGH); + + if (RateCorrection < 0) + { + peep( 990 - arg, tone, OFF); + TotalCyclesRemoved += 10; + + if (Debug) + printf ("\n* Shorter Second: "); + } + else + { + if (RateCorrection > 0) + { + peep(1010 - arg, tone, OFF); + + TotalCyclesAdded += 10; + + if (Debug) + printf ("\n* Longer Second: "); + } + else + { + peep(1000 - arg, tone, OFF); + } + } + + if (Verbose) + printf("M"); + } + break; + + case DUT1: /* send DUT1 bits */ + if (dut1 & arg) + { + WWV_Second(DATA1, RateCorrection); + if (Verbose) + printf("1"); + } + else + { + WWV_Second(DATA0, RateCorrection); + if (Verbose) + printf("0"); + } + break; + + case DST1: /* send DST1 bit */ + ptr--; + if (DstFlag) + { + WWV_Second(DATA1, RateCorrection); + if (Verbose) + printf("1"); + } + else + { + WWV_Second(DATA0, RateCorrection); + if (Verbose) + printf("0"); + } + break; + + case DST2: /* send DST2 bit */ + if (DstFlag) + { + WWV_Second(DATA1, RateCorrection); + if (Verbose) + printf("1"); + } + else + { + WWV_Second(DATA0, RateCorrection); + if (Verbose) + printf("0"); + } + break; + } + } + + if (EnableRateCorrection) + { + SecondsRunningSimulationTime++; + + gettimeofday(&TimeValue, NULL); + NowRealTime = TimeValue.tv_sec; + + if (NowRealTime >= BaseRealTime) // Just in case system time corrects backwards, do not blow up. + { + SecondsRunningRealTime = (unsigned) (NowRealTime - BaseRealTime); + SecondsRunningDifference = SecondsRunningSimulationTime - SecondsRunningRealTime; + + if (Debug) + { + printf ("> NowRealTime = 0x%8.8X, BaseRealtime = 0x%8.8X, SecondsRunningRealTime = 0x%8.8X, SecondsRunningSimulationTime = 0x%8.8X.\n", + (unsigned) NowRealTime, (unsigned) BaseRealTime, SecondsRunningRealTime, SecondsRunningSimulationTime); + printf ("> SecondsRunningDifference = 0x%8.8X, ExpectedRunningDifference = 0x%8.8X.\n", + SecondsRunningDifference, ExpectedRunningDifference); + } + + if (SecondsRunningSimulationTime > RUN_BEFORE_STABILITY_CHECK) + { + if (StabilityCount < MINIMUM_STABILITY_COUNT) + { + if (StabilityCount == 0) + { + ExpectedRunningDifference = SecondsRunningDifference; + StabilityCount++; + if (Debug) + printf ("> Starting stability check.\n"); + } + else + { // Else for "if (StabilityCount == 0)" + if ((ExpectedRunningDifference+INITIAL_STABILITY_BAND > SecondsRunningDifference) + && (ExpectedRunningDifference-INITIAL_STABILITY_BAND < SecondsRunningDifference)) + { // So far, still within stability band, increment count. + StabilityCount++; + if (Debug) + printf ("> StabilityCount = %d.\n", StabilityCount); + } + else + { // Outside of stability band, start over. + StabilityCount = 0; + if (Debug) + printf ("> Out of stability band, start over.\n"); + } + } // End of else for "if (StabilityCount == 0)" + } // End of true clause for "if (StabilityCount < MINIMUM_STABILITY_COUNT))" + else + { // Else clause for "if (StabilityCount < MINIMUM_STABILITY_COUNT))" - OK, so we are supposed to be stable. + if (AddCycle) + { + if (ExpectedRunningDifference >= SecondsRunningDifference) + { + if (Debug) + printf ("> Was adding cycles, ExpectedRunningDifference >= SecondsRunningDifference, can stop it now.\n"); + + AddCycle = FALSE; + RemoveCycle = FALSE; + } + else + { + if (Debug) + printf ("> Was adding cycles, not done yet.\n"); + } + } + else + { + if (RemoveCycle) + { + if (ExpectedRunningDifference <= SecondsRunningDifference) + { + if (Debug) + printf ("> Was removing cycles, ExpectedRunningDifference <= SecondsRunningDifference, can stop it now.\n"); + + AddCycle = FALSE; + RemoveCycle = FALSE; + } + else + { + if (Debug) + printf ("> Was removing cycles, not done yet.\n"); + } + } + else + { + if ((ExpectedRunningDifference+RUNNING_STABILITY_BAND > SecondsRunningDifference) + && (ExpectedRunningDifference-RUNNING_STABILITY_BAND < SecondsRunningDifference)) + { // All is well, within tolerances. + if (Debug) + printf ("> All is well, within tolerances.\n"); + } + else + { // Oops, outside tolerances. Else clause of "if ((ExpectedRunningDifference...SecondsRunningDifference)" + if (ExpectedRunningDifference > SecondsRunningDifference) + { + if (Debug) + printf ("> ExpectedRunningDifference > SecondsRunningDifference, running behind real time.\n"); + + // Behind real time, have to add a cycle to slow down and get back in sync. + AddCycle = FALSE; + RemoveCycle = TRUE; + } + else + { // Else clause of "if (ExpectedRunningDifference < SecondsRunningDifference)" + if (ExpectedRunningDifference < SecondsRunningDifference) + { + if (Debug) + printf ("> ExpectedRunningDifference < SecondsRunningDifference, running ahead of real time.\n"); + + // Ahead of real time, have to remove a cycle to speed up and get back in sync. + AddCycle = TRUE; + RemoveCycle = FALSE; + } + else + { + if (Debug) + printf ("> Oops, outside tolerances, but doesn't fit the profiles, how can this be?\n"); + } + } // End of else clause of "if (ExpectedRunningDifference > SecondsRunningDifference)" + } // End of else clause of "if ((ExpectedRunningDifference...SecondsRunningDifference)" + } // End of else clause of "if (RemoveCycle)". + } // End of else clause of "if (AddCycle)". + } // End of else clause for "if (StabilityCount < MINIMUM_STABILITY_COUNT))" + } // End of true clause for "if ((SecondsRunningSimulationTime > RUN_BEFORE_STABILITY_CHECK)" + } // End of true clause for "if (NowRealTime >= BaseRealTime)" + else + { + if (Debug) + printf ("> Hmm, time going backwards?\n"); + } + } // End of true clause for "if (EnableRateCorrection)" + + fflush (stdout); + } + + +printf ("\n\n>> Completed %d seconds, exiting...\n\n", SecondsToSend); +return (0); +} + + +/* + * Generate WWV/H 0 or 1 data pulse. + */ +void WWV_Second( + int code, /* DATA0, DATA1, PI */ + int Rate /* <0 -> do a short second, 0 -> normal second, >0 -> long second */ + ) +{ + /* + * The WWV data pulse begins with 5 ms of 1000 Hz follwed by a + * guard time of 25 ms. The data pulse is 170, 570 or 770 ms at + * 100 Hz corresponding to 0, 1 or position indicator (PI), + * respectively. Note the 100-Hz data pulses are transmitted 6 + * dB below the 1000-Hz sync pulses. Originally the data pulses + * were transmited 10 dB below the sync pulses, but the station + * engineers increased that to 6 dB because the Heath GC-1000 + * WWV/H radio clock worked much better. + */ + peep(5, tone, HIGH); /* send seconds tick */ + peep(25, tone, OFF); + peep(code - 30, 100, LOW); /* send data */ + + /* The quiet time is shortened or lengthened to get us back on time */ + if (Rate < 0) + { + peep( 990 - code, 100, OFF); + + TotalCyclesRemoved += 10; + + if (Debug) + printf ("\n* Shorter Second: "); + } + else + { + if (Rate > 0) + { + peep(1010 - code, 100, OFF); + + TotalCyclesAdded += 10; + + if (Debug) + printf ("\n* Longer Second: "); + } + else + peep(1000 - code, 100, OFF); + } +} + +/* + * Generate WWV/H 0 or 1 data pulse, with no tick, for 29th and 59th seconds + */ +void WWV_SecondNoTick( + int code, /* DATA0, DATA1, PI */ + int Rate /* <0 -> do a short second, 0 -> normal second, >0 -> long second */ + ) +{ + /* + * The WWV data pulse begins with 5 ms of 1000 Hz follwed by a + * guard time of 25 ms. The data pulse is 170, 570 or 770 ms at + * 100 Hz corresponding to 0, 1 or position indicator (PI), + * respectively. Note the 100-Hz data pulses are transmitted 6 + * dB below the 1000-Hz sync pulses. Originally the data pulses + * were transmited 10 dB below the sync pulses, but the station + * engineers increased that to 6 dB because the Heath GC-1000 + * WWV/H radio clock worked much better. + */ + peep(30, tone, OFF); /* send seconds non-tick */ + peep(code - 30, 100, LOW); /* send data */ + + /* The quiet time is shortened or lengthened to get us back on time */ + if (Rate < 0) + { + peep( 990 - code, 100, OFF); + + TotalCyclesRemoved += 10; + + if (Debug) + printf ("\n* Shorter Second: "); + } + else + { + if (Rate > 0) + { + peep(1010 - code, 100, OFF); + + TotalCyclesAdded += 10; + + if (Debug) + printf ("\n* Longer Second: "); + } + else + peep(1000 - code, 100, OFF); + } +} + +/* + * Generate cycles of 100 Hz or any multiple of 100 Hz. + */ +void peep( + int pulse, /* pulse length (ms) */ + int freq, /* frequency (Hz) */ + int amp /* amplitude */ + ) +{ + int increm; /* phase increment */ + int i, j; + + if (amp == OFF || freq == 0) + increm = 10; + else + increm = freq / 100; + j = 0; + for (i = 0 ; i < pulse * 8; i++) { + switch (amp) { + + case HIGH: + buffer[bufcnt++] = ~c6000[j]; + break; + + case LOW: + buffer[bufcnt++] = ~c3000[j]; + break; + + default: + buffer[bufcnt++] = ~0; + } + if (bufcnt >= BUFLNG) { + write(fd, buffer, BUFLNG); + bufcnt = 0; + } + j = (j + increm) % 80; + } +} + + +/* + * Generate unmodulated from similar tables. + */ +void poop( + int pulse, /* pulse length (ms) */ + int freq, /* frequency (Hz) */ + int amp, /* amplitude */ + int inverted /* is upside down */ + ) +{ + int increm; /* phase increment */ + int i, j; + + if (amp == OFF || freq == 0) + increm = 10; + else + increm = freq / 100; + j = 0; + for (i = 0 ; i < pulse * 8; i++) { + switch (amp) { + + case HIGH: + if (inverted) + buffer[bufcnt++] = ~u3000[j]; + else + buffer[bufcnt++] = ~u6000[j]; + break; + + case LOW: + if (inverted) + buffer[bufcnt++] = ~u6000[j]; + else + buffer[bufcnt++] = ~u3000[j]; + break; + + default: + buffer[bufcnt++] = ~0; + } + if (bufcnt >= BUFLNG) { + write(fd, buffer, BUFLNG); + bufcnt = 0; + } + j = (j + increm) % 80; + } +} + +/* + * Delay for initial phasing + */ +void delay ( + int Delay /* delay in samples */ + ) +{ + int samples; /* samples remaining */ + + samples = Delay; + memset(buffer, 0, BUFLNG); + while (samples >= BUFLNG) { + write(fd, buffer, BUFLNG); + samples -= BUFLNG; + } + write(fd, buffer, samples); +} + + +/* Calc day of year from year month & day */ +/* Year - 0 means 2000, 100 means 2100. */ +/* Month - 1 means January, 12 means December. */ +/* DayOfMonth - 1 is first day of month */ +int +ConvertMonthDayToDayOfYear (int YearValue, int MonthValue, int DayOfMonthValue) + { + int ReturnValue; + int LeapYear; + int MonthCounter; + + /* Array of days in a month. Note that here January is zero. */ + /* NB: have to add 1 to days in February in a leap year! */ + int DaysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + + LeapYear = FALSE; + if ((YearValue % 4) == 0) + { + if ((YearValue % 100) == 0) + { + if ((YearValue % 400) == 0) + { + LeapYear = TRUE; + } + } + else + { + LeapYear = TRUE; + } + } + + if (Debug) + printf ("\nConvertMonthDayToDayOfYear(): Year %d %s a leap year.\n", YearValue+2000, LeapYear ? "is" : "is not"); + + /* Day of month given us starts in this algorithm. */ + ReturnValue = DayOfMonthValue; + + /* Add in days in month for each month past January. */ + for (MonthCounter=1; MonthCounter<MonthValue; MonthCounter++) + { + ReturnValue += DaysInMonth [ MonthCounter - 1 ]; + } + + /* Add a day for leap years where we are past February. */ + if ((LeapYear) && (MonthValue > 2)) + { + ReturnValue++; + } + + if (Debug) + printf ("\nConvertMonthDayToDayOfYear(): %4.4d-%2.2d-%2.2d represents day %3d of year.\n", + YearValue+2000, MonthValue, DayOfMonthValue, ReturnValue); + + return (ReturnValue); + } + + +void +Help ( void ) + { + printf ("\n\nTime Code Generation - IRIG-B or WWV, v%d.%d, %s dmw", VERSION, ISSUE, ISSUE_DATE); + printf ("\n\nRCS Info:"); + printf ( "\n $Header: /home/dmw/src/IRIG_generation/ntp-4.2.2p3/util/RCS/tg.c,v 1.28 2007/02/12 23:57:45 dmw Exp $"); + printf ("\n\nUsage: %s [option]*", CommandName); + printf ("\n\nOptions: -a device_name Output audio device name (default /dev/audio)"); + printf ( "\n -b yymmddhhmm Remove leap second at end of minute specified"); + printf ( "\n -c seconds_to_send Number of seconds to send (default 0 = forever)"); + printf ( "\n -d Start with IEEE 1344 DST active"); + printf ( "\n -f format_type i = Modulated IRIG-B 1998 (no year coded)"); + printf ( "\n 2 = Modulated IRIG-B 2002 (year coded)"); + printf ( "\n 3 = Modulated IRIG-B w/IEEE 1344 (year & control funcs) (default)"); + printf ( "\n 4 = Unmodulated IRIG-B w/IEEE 1344 (year & control funcs)"); + printf ( "\n 5 = Inverted unmodulated IRIG-B w/IEEE 1344 (year & control funcs)"); + printf ( "\n w = WWV(H)"); + printf ( "\n -g yymmddhhmm Switch into/out of DST at beginning of minute specified"); + printf ( "\n -i yymmddhhmm Insert leap second at end of minute specified"); + printf ( "\n -j Disable time rate correction against system clock (default enabled)"); + printf ( "\n -k nn Force rate correction for testing (+1 = add cycle, -1 = remove cycle)"); + printf ( "\n -l time_offset Set offset of time sent to UTC as per computer, +/- float hours"); + printf ( "\n -o time_offset Set IEEE 1344 time offset, +/-, to 0.5 hour (default 0)"); + printf ( "\n -q quality_code_hex Set IEEE 1344 quality code (default 0)"); + printf ( "\n -r sample_rate Audio sample rate (default 8000)"); + printf ( "\n -s Set leap warning bit (WWV[H] only)"); + printf ( "\n -t sync_frequency WWV(H) on-time pulse tone frequency (default 1200)"); + printf ( "\n -u DUT1_offset Set WWV(H) DUT1 offset -7 to +7 (default 0)"); +#ifndef HAVE_SYS_SOUNDCARD_H + printf ( "\n -v initial_output_level Set initial output level (default %d, must be 0 to 255)", AUDIO_MAX_GAIN/8); +#endif + printf ( "\n -x Turn off verbose output (default on)"); + printf ( "\n -y yymmddhhmmss Set initial date and time as specified (default system time)"); + printf ("\n\nThis software licenced under the GPL, modifications performed 2006 & 2007 by Dean Weiten"); + printf ( "\nContact: Dean Weiten, Norscan Instruments Ltd., Winnipeg, MB, Canada, ph (204)-233-9138, E-mail dmw@norscan.com"); + printf ("\n\n"); + } + +/* Reverse string order for nicer print. */ +void +ReverseString(char *str) + { + int StringLength; + int IndexCounter; + int CentreOfString; + char TemporaryCharacter; + + + StringLength = strlen(str); + CentreOfString = (StringLength/2)+1; + for (IndexCounter = StringLength; IndexCounter >= CentreOfString; IndexCounter--) + { + TemporaryCharacter = str[IndexCounter-1]; + str[IndexCounter-1] = str[StringLength-IndexCounter]; + str[StringLength-IndexCounter] = TemporaryCharacter; + } + } + |