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