1 /*        $NetBSD: refclock_ulink.c,v 1.6 2024/08/18 20:47:19 christos Exp $    */
2 
3 /*
4  * refclock_ulink - clock driver for Ultralink  WWVB receiver
5  */
6 
7 #ifdef HAVE_CONFIG_H
8 #include <config.h>
9 #endif
10 
11 #if defined(REFCLOCK) && defined(CLOCK_ULINK)
12 
13 #include <stdio.h>
14 #include <ctype.h>
15 
16 #include "ntpd.h"
17 #include "ntp_io.h"
18 #include "ntp_refclock.h"
19 #include "ntp_stdlib.h"
20 
21 /* This driver supports ultralink Model 320,325,330,331,332 WWVB radios
22  *
23  * this driver was based on the refclock_wwvb.c driver
24  * in the ntp distribution.
25  *
26  * Fudge Factors
27  *
28  * fudge flag1 0 don't poll clock
29  *             1 send poll character
30  *
31  * revision history:
32  *                  99/9/09 j.c.lang    original edit's
33  *                  99/9/11 j.c.lang    changed timecode parse to
34  *                                      match what the radio actually
35  *                                      sends.
36  *              99/10/11 j.c.lang       added support for continous
37  *                                      time code mode (dipsw2)
38  *                  99/11/26 j.c.lang   added support for 320 decoder
39  *                                      (taken from Dave Strout's
40  *                                      Model 320 driver)
41  *                  99/11/29 j.c.lang   added fudge flag 1 to control
42  *                                                clock polling
43  *                  99/12/15 j.c.lang   fixed 320 quality flag
44  *                  01/02/21 s.l.smith  fixed 33x quality flag
45  *                                                added more debugging stuff
46  *                                                updated 33x time code explanation
47  *                  04/01/23 frank migge          added support for 325 decoder
48  *                                      (tested with ULM325.F)
49  *
50  * Questions, bugs, ideas send to:
51  *        Joseph C. Lang
52  *        tcnojl1@earthlink.net
53  *
54  *        Dave Strout
55  *        dstrout@linuxfoundry.com
56  *
57  *      Frank Migge
58  *      frank.migge@oracle.com
59  *
60  *
61  * on the Ultralink model 33X decoder Dip switch 2 controls
62  * polled or continous timecode
63  * set fudge flag1 if using polled (needed for model 320 and 325)
64  * dont set fudge flag1 if dip switch 2 is set on model 33x decoder
65 */
66 
67 
68 /*
69  * Interface definitions
70  */
71 #define   DEVICE              "/dev/wwvb%d" /* device name and unit */
72 #define   SPEED232  B9600     /* uart speed (9600 baud) */
73 #define   PRECISION (-10)     /* precision assumed (about 10 ms) */
74 #define   REFID               "WWVB"    /* reference ID */
75 #define   DESCRIPTION         "Ultralink WWVB Receiver" /* WRU */
76 
77 #define   LEN33X              32        /* timecode length Model 33X and 325 */
78 #define LEN320                24        /* timecode length Model 320 */
79 
80 #define   SIGLCHAR33x         'S'       /* signal strength identifier char 325 */
81 #define   SIGLCHAR325         'R'       /* signal strength identifier char 33x */
82 
83 /*
84  *  unit control structure
85  */
86 struct ulinkunit {
87           u_char    tcswitch; /* timecode switch */
88           l_fp      laststamp;          /* last receive timestamp */
89 };
90 
91 /*
92  * Function prototypes
93  */
94 static    int       ulink_start         (int, struct peer *);
95 static    void      ulink_shutdown      (int, struct peer *);
96 static    void      ulink_receive       (struct recvbuf *);
97 static    void      ulink_poll          (int, struct peer *);
98 
99 /*
100  * Transfer vector
101  */
102 struct    refclock refclock_ulink = {
103           ulink_start,                  /* start up driver */
104           ulink_shutdown,               /* shut down driver */
105           ulink_poll,                   /* transmit poll message */
106           noentry,            /* not used  */
107           noentry,            /* not used  */
108           noentry,            /* not used  */
109           NOFLAGS
110 };
111 
112 
113 /*
114  * ulink_start - open the devices and initialize data for processing
115  */
116 static int
ulink_start(int unit,struct peer * peer)117 ulink_start(
118           int unit,
119           struct peer *peer
120           )
121 {
122           register struct ulinkunit *up;
123           struct refclockproc *pp;
124           int fd;
125           char device[20];
126 
127           /*
128            * Open serial port. Use CLK line discipline, if available.
129            */
130           snprintf(device, sizeof(device), DEVICE, unit);
131           fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_CLK);
132           if (fd <= 0)
133                     return (0);
134 
135           /*
136            * Allocate and initialize unit structure
137            */
138           up = emalloc(sizeof(struct ulinkunit));
139           memset(up, 0, sizeof(struct ulinkunit));
140           pp = peer->procptr;
141           pp->io.clock_recv = ulink_receive;
142           pp->io.srcclock = peer;
143           pp->io.datalen = 0;
144           pp->io.fd = fd;
145           if (!io_addclock(&pp->io)) {
146                     close(fd);
147                     pp->io.fd = -1;
148                     free(up);
149                     return (0);
150           }
151           pp->unitptr = up;
152 
153           /*
154            * Initialize miscellaneous variables
155            */
156           peer->precision = PRECISION;
157           pp->clockdesc = DESCRIPTION;
158           memcpy((char *)&pp->refid, REFID, 4);
159           return (1);
160 }
161 
162 
163 /*
164  * ulink_shutdown - shut down the clock
165  */
166 static void
ulink_shutdown(int unit,struct peer * peer)167 ulink_shutdown(
168           int unit,
169           struct peer *peer
170           )
171 {
172           register struct ulinkunit *up;
173           struct refclockproc *pp;
174 
175           pp = peer->procptr;
176           up = pp->unitptr;
177           if (pp->io.fd != -1)
178                     io_closeclock(&pp->io);
179           if (up != NULL)
180                     free(up);
181 }
182 
183 
184 /*
185  * ulink_receive - receive data from the serial interface
186  */
187 static void
ulink_receive(struct recvbuf * rbufp)188 ulink_receive(
189           struct recvbuf *rbufp
190           )
191 {
192           struct ulinkunit *up;
193           struct refclockproc *pp;
194           struct peer *peer;
195 
196           l_fp      trtmp;                        /* arrival timestamp */
197           int       quality = INT_MAX;  /* quality indicator */
198           int       temp;                         /* int temp */
199           char      syncchar;           /* synchronization indicator */
200           char      leapchar;           /* leap indicator */
201           char      modechar;           /* model 320 mode flag */
202         char        siglchar;           /* model difference between 33x/325 */
203           char      char_quality[2];    /* temp quality flag */
204 
205           /*
206            * Initialize pointers and read the timecode and timestamp
207            */
208           peer = rbufp->recv_peer;
209           pp = peer->procptr;
210           up = pp->unitptr;
211           temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
212 
213           /*
214            * Note we get a buffer and timestamp for both a <cr> and <lf>,
215            * but only the <cr> timestamp is retained.
216            */
217           if (temp == 0) {
218                     if (up->tcswitch == 0) {
219                               up->tcswitch = 1;
220                               up->laststamp = trtmp;
221                     } else
222                         up->tcswitch = 0;
223                     return;
224           }
225           pp->lencode = temp;
226           pp->lastrec = up->laststamp;
227           up->laststamp = trtmp;
228           up->tcswitch = 1;
229 #ifdef DEBUG
230           if (debug)
231                     printf("ulink: timecode %d %s\n", pp->lencode,
232                         pp->a_lastcode);
233 #endif
234 
235           /*
236            * We get down to business, check the timecode format and decode
237            * its contents. If the timecode has invalid length or is not in
238            * proper format, we declare bad format and exit.
239            */
240           syncchar = leapchar = modechar = siglchar = ' ';
241           switch (pp->lencode ) {
242           case LEN33X:
243 
244                     /*
245                  * First we check if the format is 33x or 325:
246                      *   <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5 (33x)
247                      *   <CR><LF>R5_1C00LYYYY+DDDUTCS HH:MM:SSL+5 (325)
248                      * simply by comparing if the signal level is 'S' or 'R'
249                  */
250 
251                  if (sscanf(pp->a_lastcode, "%c%*31c",
252                             &siglchar) == 1) {
253 
254                     if(siglchar == SIGLCHAR325) {
255 
256                        /*
257                         * decode for a Model 325 decoder.
258                         * Timecode format from January 23, 2004 datasheet is:
259                     *
260                         *   <CR><LF>R5_1C00LYYYY+DDDUTCS HH:MM:SSL+5
261                     *
262                         *   R      WWVB decodersignal readability R1 - R5
263                         *   5      R1 is unreadable, R5 is best
264                         *   space  a space (0x20)
265                         *   1      Data bit 0, 1, M (pos mark), or ? (unknown).
266                         *   C      Reception from either (C)olorado or (H)awaii
267                         *   00     Hours since last good WWVB frame sync. Will
268                         *          be 00-99
269                         *   space  Space char (0x20) or (0xa5) if locked to wwvb
270                         *   YYYY   Current year, 2000-2099
271                         *   +      Leap year indicator. '+' if a leap year,
272                         *          a space (0x20) if not.
273                         *   DDD    Day of year, 000 - 365.
274                         *   UTC    Timezone (always 'UTC').
275                         *   S      Daylight savings indicator
276                         *             S - standard time (STD) in effect
277                         *             O - during STD to DST day 0000-2400
278                         *             D - daylight savings time (DST) in effect
279                         *             I - during DST to STD day 0000-2400
280                         *   space  Space character (0x20)
281                         *   HH     Hours 00-23
282                         *   :      This is the REAL in sync indicator (: = insync)
283                         *   MM     Minutes 00-59
284                         *   :      : = in sync ? = NOT in sync
285                         *   SS     Seconds 00-59
286                         *   L      Leap second flag. Changes from space (0x20)
287                         *          to 'I' or 'D' during month preceding leap
288                         *          second adjustment. (I)nsert or (D)elete
289                         *   +5     UT1 correction (sign + digit ))
290                         */
291 
292                            if (sscanf(pp->a_lastcode,
293                           "%*2c %*2c%2c%*c%4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c",
294                               char_quality, &pp->year, &pp->day,
295                           &pp->hour, &syncchar, &pp->minute, &pp->second,
296                           &leapchar) == 8) {
297 
298                                 if (char_quality[0] == '0') {
299                                         quality = 0;
300                                 } else if (char_quality[0] == '0') {
301                                         quality = (char_quality[1] & 0x0f);
302                                 } else  {
303                                         quality = 99;
304                                 }
305 
306                               if (leapchar == 'I' ) leapchar = '+';
307                               if (leapchar == 'D' ) leapchar = '-';
308 
309                               /*
310                               #ifdef DEBUG
311                               if (debug) {
312                                  printf("ulink: char_quality %c %c\n",
313                                     char_quality[0], char_quality[1]);
314                                    printf("ulink: quality %d\n", quality);
315                                    printf("ulink: syncchar %x\n", syncchar);
316                                    printf("ulink: leapchar %x\n", leapchar);
317                           }
318                           #endif
319                           */
320 
321                        }
322 
323                     }
324                     if(siglchar == SIGLCHAR33x) {
325 
326                        /*
327                         * We got a Model 33X decoder.
328                         * Timecode format from January 29, 2001 datasheet is:
329                         *   <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5
330                         *   S      WWVB decoder sync indicator. S for in-sync(?)
331                         *          or N for noisy signal.
332                         *   9+     RF signal level in S-units, 0-9 followed by
333                         *          a space (0x20). The space turns to '+' if the
334                         *          level is over 9.
335                         *   D      Data bit 0, 1, 2 (position mark), or
336                         *          3 (unknown).
337                         *   space  Space character (0x20)
338                         *   00     Hours since last good WWVB frame sync. Will
339                         *          be 00-23 hrs, or '1d' to '7d'. Will be 'Lk'
340                     *          if currently in sync.
341                         *   space  Space character (0x20)
342                         *   YYYY   Current year, 1990-2089
343                         *   +      Leap year indicator. '+' if a leap year,
344                         *          a space (0x20) if not.
345                         *   DDD    Day of year, 001 - 366.
346                         *   UTC    Timezone (always 'UTC').
347                         *   S      Daylight savings indicator
348                         *             S - standard time (STD) in effect
349                         *             O - during STD to DST day 0000-2400
350                         *             D - daylight savings time (DST) in effect
351                         *             I - during DST to STD day 0000-2400
352                         *   space  Space character (0x20)
353                         *   HH     Hours 00-23
354                         *   :      This is the REAL in sync indicator (: = insync)
355                         *   MM     Minutes 00-59
356                         *   :      : = in sync ? = NOT in sync
357                         *   SS     Seconds 00-59
358                         *   L      Leap second flag. Changes from space (0x20)
359                         *          to '+' or '-' during month preceding leap
360                         *          second adjustment.
361                         *   +5     UT1 correction (sign + digit ))
362                         */
363 
364                            if (sscanf(pp->a_lastcode,
365                            "%*4c %2c %4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c",
366                                char_quality, &pp->year, &pp->day,
367                            &pp->hour, &syncchar, &pp->minute, &pp->second,
368                            &leapchar) == 8) {
369 
370                                  if (char_quality[0] == 'L') {
371                                         quality = 0;
372                                  } else if (char_quality[0] == '0') {
373                                         quality = (char_quality[1] & 0x0f);
374                                  } else  {
375                                         quality = 99;
376                                }
377 
378                            /*
379                            #ifdef DEBUG
380                        if (debug) {
381                               printf("ulink: char_quality %c %c\n",
382                                         char_quality[0], char_quality[1]);
383                               printf("ulink: quality %d\n", quality);
384                               printf("ulink: syncchar %x\n", syncchar);
385                               printf("ulink: leapchar %x\n", leapchar);
386                            }
387                            #endif
388                            */
389 
390                             }
391                     }
392                         break;
393                     }
394                     /*FALLTHROUGH*/
395 
396           case LEN320:
397 
398                   /*
399                      * Model 320 Decoder
400                      * The timecode format is:
401                      *
402                      *  <cr><lf>SQRYYYYDDD+HH:MM:SS.mmLT<cr>
403                      *
404                      * where:
405                      *
406                      * S = 'S' -- sync'd in last hour,
407                      *     '0'-'9' - hours x 10 since last update,
408                      *     '?' -- not in sync
409                      * Q = Number of correlating time-frames, from 0 to 5
410                      * R = 'R' -- reception in progress,
411                      *     'N' -- Noisy reception,
412                      *     ' ' -- standby mode
413                      * YYYY = year from 1990 to 2089
414                      * DDD = current day from 1 to 366
415                      * + = '+' if current year is a leap year, else ' '
416                      * HH = UTC hour 0 to 23
417                      * MM = Minutes of current hour from 0 to 59
418                      * SS = Seconds of current minute from 0 to 59
419                      * mm = 10's milliseconds of the current second from 00 to 99
420                      * L  = Leap second pending at end of month
421                      *     'I' = insert, 'D'= delete
422                      * T  = DST <-> STD transition indicators
423                      *
424            */
425 
426                     if (sscanf(pp->a_lastcode, "%c%1d%c%4d%3d%*c%2d:%2d:%2d.%2ld%c",
427                          &syncchar, &quality, &modechar, &pp->year, &pp->day,
428                  &pp->hour, &pp->minute, &pp->second,
429                               &pp->nsec, &leapchar) == 10) {
430                     pp->nsec *= 10000000; /* M320 returns 10's of msecs */
431                     if (leapchar == 'I' ) leapchar = '+';
432                     if (leapchar == 'D' ) leapchar = '-';
433                     if (syncchar != '?' ) syncchar = ':';
434                         break;
435                     }
436                     /*FALLTHROUGH*/
437           default:
438                     refclock_report(peer, CEVNT_BADREPLY);
439                     return;
440           }
441 
442           /*
443            * Decode quality indicator
444            * For the 325 & 33x series, the lower the number the "better"
445            * the time is. I used the dispersion as the measure of time
446            * quality. The quality indicator in the 320 is the number of
447            * correlating time frames (the more the better)
448            */
449 
450           /*
451            * The spec sheet for the 325 & 33x series states the clock will
452            * maintain +/-0.002 seconds accuracy when locked to WWVB. This
453            * is indicated by 'Lk' in the quality portion of the incoming
454            * string. When not in lock, a drift of +/-0.015 seconds should
455            * be allowed for.
456            * With the quality indicator decoding scheme above, the 'Lk'
457            * condition will produce a quality value of 0. If the quality
458            * indicator starts with '0' then the second character is the
459            * number of hours since we were last locked. If the first
460            * character is anything other than 'L' or '0' then we have been
461            * out of lock for more than 9 hours so we assume the worst and
462            * force a quality value that selects the 'default' maximum
463            * dispersion. The dispersion values below are what came with the
464            * driver. They're not unreasonable so they've not been changed.
465            */
466 
467           if (pp->lencode == LEN33X) {
468                     switch (quality) {
469                               case 0 :
470                                         pp->disp=.002;
471                                         break;
472                               case 1 :
473                                         pp->disp=.02;
474                                         break;
475                               case 2 :
476                                         pp->disp=.04;
477                                         break;
478                               case 3 :
479                                         pp->disp=.08;
480                                         break;
481                               default:
482                                         pp->disp=MAXDISPERSE;
483                                         break;
484                     }
485           } else {
486                     switch (quality) {
487                               case 5 :
488                                         pp->disp=.002;
489                                         break;
490                               case 4 :
491                                         pp->disp=.02;
492                                         break;
493                               case 3 :
494                                         pp->disp=.04;
495                                         break;
496                               case 2 :
497                                         pp->disp=.08;
498                                         break;
499                               case 1 :
500                                         pp->disp=.16;
501                                         break;
502                               default:
503                                         pp->disp=MAXDISPERSE;
504                                         break;
505                     }
506 
507           }
508 
509           /*
510            * Decode synchronization, and leap characters. If
511            * unsynchronized, set the leap bits accordingly and exit.
512            * Otherwise, set the leap bits according to the leap character.
513            */
514 
515           if (syncchar != ':')
516                     pp->leap = LEAP_NOTINSYNC;
517           else if (leapchar == '+')
518                     pp->leap = LEAP_ADDSECOND;
519           else if (leapchar == '-')
520                     pp->leap = LEAP_DELSECOND;
521           else
522                     pp->leap = LEAP_NOWARNING;
523 
524           /*
525            * Process the new sample in the median filter and determine the
526            * timecode timestamp.
527            */
528           if (!refclock_process(pp)) {
529                     refclock_report(peer, CEVNT_BADTIME);
530           }
531 
532 }
533 
534 /*
535  * ulink_poll - called by the transmit procedure
536  */
537 
538 static void
ulink_poll(int unit,struct peer * peer)539 ulink_poll(
540           int unit,
541           struct peer *peer
542           )
543 {
544         struct refclockproc *pp;
545         char pollchar;
546 
547         pp = peer->procptr;
548         pollchar = 'T';
549           if (pp->sloppyclockflag & CLK_FLAG1) {
550                   if (write(pp->io.fd, &pollchar, 1) != 1)
551                   refclock_report(peer, CEVNT_FAULT);
552           else
553                       pp->polls++;
554           }
555           else
556                       pp->polls++;
557 
558         if (pp->coderecv == pp->codeproc) {
559                 refclock_report(peer, CEVNT_TIMEOUT);
560                 return;
561         }
562         pp->lastref = pp->lastrec;
563           refclock_receive(peer);
564           record_clock_stats(&peer->srcadr, pp->a_lastcode);
565 
566 }
567 
568 #else
569 NONEMPTY_TRANSLATION_UNIT
570 #endif /* REFCLOCK */
571