Xref: utzoo sci.electronics:3131 sci.astro:2243 comp.dcom.modems:2008 comp.misc:2620
Path: utzoo!dciem!nrcaer!cognos!bruce
From: bruce@cognos.uucp (Dwayne Bruce)
Newsgroups: sci.electronics,sci.astro,comp.dcom.modems,comp.misc
Subject: Re: N.B.S. Time Service
Message-ID: <3204@cognos.UUCP>
Date: 16 Jun 88 22:55:51 GMT
Article-I.D.: cognos.3204
References: <455@trane.UUCP>
Reply-To: ml@gandalf.UUCP (Marcus Leech)
Followup-To: comp.misc
Organization: Gandalf Inc., Ottawa, Canada
Lines: 283

-----
Well kids, after reading about the NBS telephone time service, and having
  read about an expensive commerical system from Precision Standard Time, Inc.,
  I just had to tell you about the system I cooked up.
We've been keeping our VAXen in sync with a simple system that uses the
   AFSK time code produced by CHU.  CHU is Canada's provider of brodcast time,
   and it has a signal that can be heard regularly all over the western
   hemisphere.  During seconds 31..39 of each minute, they transmit a time
   code in 103-type AFSK.  I built a modem, plugged it into an HF receiver, and
   wrote a piece of code to handle the output. The code is enclosed.
If you want to know more about the hardware, consult the article I wrote in
   the April, 1988 edition of "The Canadian Amateur" (published by the
   Canadian Amateur Radio Federation).
Anyway, here's the code.  It runs under UNIX and VMS.
-------------------------
/* CHU      Marcus Leech  VE3MDL   Aug 1987 */
/* This program reads and understands the timecode format
 *  produced by the CHU broadcast signal.  This signal provides
 *  an atomic time standard to most places in North and South
 *  America on 3.330 7.335 and 14.670 MHZ, mode A3. During
 *  seconds 31 thru 39 the time is broadcast using Bell 103 AFSK.
 *  The time code looks like this:
 *  6.d|d.d|h.h|m.m|s.s|6.d|d.d|h.h|m.m|s.s
 *  The time code is repeated twice for error detection. Each . represents
 *    one nibble boundary.  The nibbles get transmitted reversed, so you
 *    have to flip them to make sense of the time code.  Each nibble is a BCD
 *    digit.
 * It takes one argument, the input device, and some optional flags:
 *  chu [-adst] 
 *  -
 *  a adjust  attempt to adjust the system clock to CHU time
 *  d daemon  run continuously as a daemon
 *  s show    show semi-raw timecode
 *  t tune    show chars that wouldn't get considered for time code
 *
 * When used as a system-time-adjuster, you need to set the system time
 *  to within 2mins of the correct time. The program will drag the
 *  clock into exact synchronism.
 */
#include 
#ifndef VMS
#include 
#include 
#include 
#else VMS
#include 
#include 
#endif VMS
/* Macros to fetch individual nibbles. */
#define hinib(x) (((x) >> 4) & 0x0F)
#define lonib(x) ((x) & 0x0F)
/* Macro to restore correct ordering within a byte. */
#define byte(h,l) (((l) << 4)|(h))
#define ADJINT 27
#define TWEAK 50 + 12     /* Fudge factor in centiseconds. */
                          /* CHU code is skewed by 0.5 seconds. */
#define iabs(x) ((x < 0) ? -x : x)
char sample1[5];  /* The first time code. */
char sample2[5];  /* The second (error checking) time code. */
main (argc, argv)
int argc;
char **argv;
{
    char c, *p;
#ifndef VMS
    struct sgttyb sgb;            /* For fiddling with tty line params. */
#endif VMS
    int line;                     /* Fd for the tty line.*/
    int i;
    int adjcnt;                     /* Number of samples before we adjust.*/
    struct tm *localtime(), *ltp, *gmtime();  /* To break out the time. */
    time_t now, time();
#ifdef VMS
    long t0[2], t1[2], t2[2];
#endif VMS
    int amm, ass, mmss, diff;
    char *devstr;
    int adjust;
    int show;
    int tune;
    int daemon;

    setbuf (stdout, NULL);
    adjust = show = tune = 0;
    devstr = "/dev/null";
    for (i = 1; i < argc; i++)
    {
        if (argv[i][0] == '-')
        {
            p = &argv[i][1];
            while (*p)
            {
                switch (*p)
                {
                case 'a':
                    adjust++;
                    break;
                case 't':
                    tune++;
                    break;
                case 's':
                    show++;
                    break;
                case 'd':
                    daemon++;
                    break;
                default:
                    fprintf (stdout, "unknown flag '%c'\n", *p);
                    exit (1);
                }
                *p++;
            }
        }
        else
        {
            strcpy (devstr, argv[i]);
        }
    }
    line = open (devstr, 0);
#ifndef VMS
    /* Set up 8-bit datapath, at 30CPS. */
    gtty (line, &sgb);
    sgb.sg_ispeed = sgb.sg_ospeed = B300;
    sgb.sg_flags |= RAW;
    stty (line, &sgb);
#endif VMS
    adjcnt = 0;
    if (!daemon)
    {
        adjcnt = 19;
    }
    /* Read forever, waiting for the synchronizing BCD 6 digit to appear. */
    for (;;)
    {
        read (line, &c, 1);
        /* We have a syncronizing digit. Grab two samples and compare. */
        if (lonib(c) == 6)
        {
            /* Get first sample. */
            sample1[0] = byte(hinib(c),lonib(c));
            for (i = 1; i < 5; i++)
            {
                read (line, &c, 1);
                sample1[i] = byte(hinib(c),lonib(c));
            }
            /* Get second sample. */
            for (i = 0; i < 5; i++)
            {
                read (line, &c, 1);
                sample2[i] = byte(hinib(c),lonib(c));
            }
            /* If samples match, we have a valid time code. */
            if (compare (sample1, sample2, 5) == 0)
            {
                /* Show the code (if -s).  The high-order nibble in the
                 *  first byte is the synch digit, so it gets masked out
                 *  for printing.
                 */
                if (show)
                {
                    fprintf (stdout, "TC: ");
                    for (i = 0; i < 5; i++)
                    {
                        fprintf (stdout, "%02x", sample1[i]);
                    }
                    fprintf (stdout, "\n");
                }
                if (adjcnt++ >= ADJINT)
                {
                    adjcnt = 0;
                    /* Fetch UTC (GMT). */
                    time (&now);
                    ltp = gmtime (&now);
                    /* Convert time code minutes and seconds into
                     *   binary.
                     */
                    amm = (hinib(sample1[3]) * 10) + lonib(sample1[3]);
                    ass = (hinib(sample1[4]) * 10) + lonib(sample1[4]);
                    /* Convert minutes and seconds portion of system time. */
                    mmss = (ltp->tm_min * 60) + ltp->tm_sec;
                    /* Compute the difference. */
                    diff = ((amm * 60) + ass) - mmss;
                    /* Adjust the system time. */
                    now += (long)diff;
                    if (iabs(diff) > 120)
                    {
                        fprintf (stdout, "%02d-%02d-%02d %02d:%02d:%02d ",
                            ltp->tm_year, ltp->tm_mon + 1, ltp->tm_mday,
                            ltp->tm_hour, ltp->tm_min, ltp->tm_sec);
                        fprintf (stdout, "TERROR %d\n", diff);
                        if (!daemon)
                        {
                            break;
                        }
                        continue;
                    }
                    /* Only do it if there IS a (reasonable) difference. */
                    if ((diff != 0) && (adjust))
                    {
                        ltp = localtime (&now);
                        fprintf (stdout, "%02d-%02d-%02d %02d:%02d:%02d ",
                            ltp->tm_year, ltp->tm_mon + 1, ltp->tm_mday,
                            ltp->tm_hour, ltp->tm_min, ltp->tm_sec);
                        fprintf (stdout, "ADJUST %d\n", diff);
                        /* What we'd REALLY like here is a system call of
                         * the form:   stime (*time, ticks)
                         * that would allow you to set the system tick
                         * counter to  in centiseconds.  Too bad.
                         *-->stime (&now, TWEAK);<--
                         */
#ifndef VMS
#ifndef EUNICE
                        stime (&now);
#endif EUNICE
#else VMS
                        t1[0] = 100000 * ((diff * 100) + TWEAK);
                        t1[1] = 0;
                        if (diff < 0)
                        {
                            t1[1] = -1;
                        }
                        sys$gettim (t0);
                        lib$addx (t0, t1, t2);
                        sys$setime (t2);
#endif VMS
                    }
                    ltp = localtime (&now);
                    fprintf (stdout, "%02d-%02d-%02d %02d:%02d:%02d TVALID\n",
                        ltp->tm_year, ltp->tm_mon + 1, ltp->tm_mday,
                        ltp->tm_hour, ltp->tm_min, ltp->tm_sec);
                    if (!daemon)
                    {
                        break;
                    }
                }
            }
        }
        else if (tune)
        {
                fprintf (stdout, "FT: %c (%02x)\n", c, (unsigned)c);
        }
    }
}
/* Compare two byte-arrays (s1, s2) of length cnt. */
compare (s1, s2, cnt)
char *s1;
char *s2;
int cnt;
{
    int i;
    for (i = 0; i < cnt; i++)
    {
        if (*s1++ != *s2++)
        {
            return (1);
        }
    }
    return (0);
}
#ifdef VMS
struct tm *
gmtime (tod)
time_t *tod;
{
    int hh;
    int mm;
    char s;
    int secdiff, sgn;
    time_t utc;
    char *p, *getenv ();
    p = getenv ("UTCDISP");
    sscanf (p, "%c %02d:%02d", &s, &hh, &mm);
    utc = *tod;
    secdiff = (hh * 3600) + (mm * 60);
    sgn = (s == '+') ? 1 : -1;
    secdiff *= sgn;
    utc += secdiff;
    return (localtime (&utc));
}
#endif VMS



-- ml@gandalf utzoo!dciem!nrcaer!gandalf!ml