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