/src/ntp-dev/ntpd/refclock_ulink.c
Line | Count | Source (jump to first uncovered line) |
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 | 0 | #define SPEED232 B9600 /* uart speed (9600 baud) */ |
71 | 0 | #define PRECISION (-10) /* precision assumed (about 10 ms) */ |
72 | 0 | #define REFID "WWVB" /* reference ID */ |
73 | 0 | #define DESCRIPTION "Ultralink WWVB Receiver" /* WRU */ |
74 | | |
75 | 0 | #define LEN33X 32 /* timecode length Model 33X and 325 */ |
76 | 0 | #define LEN320 24 /* timecode length Model 320 */ |
77 | | |
78 | 0 | #define SIGLCHAR33x 'S' /* signal strength identifier char 325 */ |
79 | 0 | #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 |
115 | | ulink_start( |
116 | | int unit, |
117 | | struct peer *peer |
118 | | ) |
119 | 0 | { |
120 | 0 | register struct ulinkunit *up; |
121 | 0 | struct refclockproc *pp; |
122 | 0 | int fd; |
123 | 0 | char device[20]; |
124 | | |
125 | | /* |
126 | | * Open serial port. Use CLK line discipline, if available. |
127 | | */ |
128 | 0 | snprintf(device, sizeof(device), DEVICE, unit); |
129 | 0 | fd = refclock_open(device, SPEED232, LDISC_CLK); |
130 | 0 | if (fd <= 0) |
131 | 0 | return (0); |
132 | | |
133 | | /* |
134 | | * Allocate and initialize unit structure |
135 | | */ |
136 | 0 | up = emalloc(sizeof(struct ulinkunit)); |
137 | 0 | memset(up, 0, sizeof(struct ulinkunit)); |
138 | 0 | pp = peer->procptr; |
139 | 0 | pp->io.clock_recv = ulink_receive; |
140 | 0 | pp->io.srcclock = peer; |
141 | 0 | pp->io.datalen = 0; |
142 | 0 | pp->io.fd = fd; |
143 | 0 | if (!io_addclock(&pp->io)) { |
144 | 0 | close(fd); |
145 | 0 | pp->io.fd = -1; |
146 | 0 | free(up); |
147 | 0 | return (0); |
148 | 0 | } |
149 | 0 | pp->unitptr = up; |
150 | | |
151 | | /* |
152 | | * Initialize miscellaneous variables |
153 | | */ |
154 | 0 | peer->precision = PRECISION; |
155 | 0 | pp->clockdesc = DESCRIPTION; |
156 | 0 | memcpy((char *)&pp->refid, REFID, 4); |
157 | 0 | return (1); |
158 | 0 | } |
159 | | |
160 | | |
161 | | /* |
162 | | * ulink_shutdown - shut down the clock |
163 | | */ |
164 | | static void |
165 | | ulink_shutdown( |
166 | | int unit, |
167 | | struct peer *peer |
168 | | ) |
169 | 0 | { |
170 | 0 | register struct ulinkunit *up; |
171 | 0 | struct refclockproc *pp; |
172 | |
|
173 | 0 | pp = peer->procptr; |
174 | 0 | up = pp->unitptr; |
175 | 0 | if (pp->io.fd != -1) |
176 | 0 | io_closeclock(&pp->io); |
177 | 0 | if (up != NULL) |
178 | 0 | free(up); |
179 | 0 | } |
180 | | |
181 | | |
182 | | /* |
183 | | * ulink_receive - receive data from the serial interface |
184 | | */ |
185 | | static void |
186 | | ulink_receive( |
187 | | struct recvbuf *rbufp |
188 | | ) |
189 | 0 | { |
190 | 0 | struct ulinkunit *up; |
191 | 0 | struct refclockproc *pp; |
192 | 0 | struct peer *peer; |
193 | |
|
194 | 0 | l_fp trtmp; /* arrival timestamp */ |
195 | 0 | int quality = INT_MAX; /* quality indicator */ |
196 | 0 | int temp; /* int temp */ |
197 | 0 | char syncchar; /* synchronization indicator */ |
198 | 0 | char leapchar; /* leap indicator */ |
199 | 0 | char modechar; /* model 320 mode flag */ |
200 | 0 | char siglchar; /* model difference between 33x/325 */ |
201 | 0 | char char_quality[2]; /* temp quality flag */ |
202 | | |
203 | | /* |
204 | | * Initialize pointers and read the timecode and timestamp |
205 | | */ |
206 | 0 | peer = rbufp->recv_peer; |
207 | 0 | pp = peer->procptr; |
208 | 0 | up = pp->unitptr; |
209 | 0 | 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 | 0 | if (temp == 0) { |
216 | 0 | if (up->tcswitch == 0) { |
217 | 0 | up->tcswitch = 1; |
218 | 0 | up->laststamp = trtmp; |
219 | 0 | } else |
220 | 0 | up->tcswitch = 0; |
221 | 0 | return; |
222 | 0 | } |
223 | 0 | pp->lencode = temp; |
224 | 0 | pp->lastrec = up->laststamp; |
225 | 0 | up->laststamp = trtmp; |
226 | 0 | up->tcswitch = 1; |
227 | 0 | #ifdef DEBUG |
228 | 0 | if (debug) |
229 | 0 | printf("ulink: timecode %d %s\n", pp->lencode, |
230 | 0 | pp->a_lastcode); |
231 | 0 | #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 | 0 | syncchar = leapchar = modechar = siglchar = ' '; |
239 | 0 | switch (pp->lencode ) { |
240 | 0 | 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 | 0 | if (sscanf(pp->a_lastcode, "%c%*31c", |
250 | 0 | &siglchar) == 1) { |
251 | |
|
252 | 0 | 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 | 0 | if (sscanf(pp->a_lastcode, |
291 | 0 | "%*2c %*2c%2c%*c%4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c", |
292 | 0 | char_quality, &pp->year, &pp->day, |
293 | 0 | &pp->hour, &syncchar, &pp->minute, &pp->second, |
294 | 0 | &leapchar) == 8) { |
295 | | |
296 | 0 | if (char_quality[0] == '0') { |
297 | 0 | quality = 0; |
298 | 0 | } else if (char_quality[0] == '0') { |
299 | 0 | quality = (char_quality[1] & 0x0f); |
300 | 0 | } else { |
301 | 0 | quality = 99; |
302 | 0 | } |
303 | |
|
304 | 0 | if (leapchar == 'I' ) leapchar = '+'; |
305 | 0 | 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 | 0 | } |
320 | | |
321 | 0 | } |
322 | 0 | 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 | 0 | if (sscanf(pp->a_lastcode, |
363 | 0 | "%*4c %2c %4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c", |
364 | 0 | char_quality, &pp->year, &pp->day, |
365 | 0 | &pp->hour, &syncchar, &pp->minute, &pp->second, |
366 | 0 | &leapchar) == 8) { |
367 | | |
368 | 0 | if (char_quality[0] == 'L') { |
369 | 0 | quality = 0; |
370 | 0 | } else if (char_quality[0] == '0') { |
371 | 0 | quality = (char_quality[1] & 0x0f); |
372 | 0 | } else { |
373 | 0 | quality = 99; |
374 | 0 | } |
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 | 0 | } |
389 | 0 | } |
390 | 0 | break; |
391 | 0 | } |
392 | | |
393 | 0 | 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 | 0 | if (sscanf(pp->a_lastcode, "%c%1d%c%4d%3d%*c%2d:%2d:%2d.%2ld%c", |
424 | 0 | &syncchar, &quality, &modechar, &pp->year, &pp->day, |
425 | 0 | &pp->hour, &pp->minute, &pp->second, |
426 | 0 | &pp->nsec, &leapchar) == 10) { |
427 | 0 | pp->nsec *= 10000000; /* M320 returns 10's of msecs */ |
428 | 0 | if (leapchar == 'I' ) leapchar = '+'; |
429 | 0 | if (leapchar == 'D' ) leapchar = '-'; |
430 | 0 | if (syncchar != '?' ) syncchar = ':'; |
431 | |
|
432 | 0 | break; |
433 | 0 | } |
434 | | |
435 | 0 | default: |
436 | 0 | refclock_report(peer, CEVNT_BADREPLY); |
437 | 0 | return; |
438 | 0 | } |
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 | 0 | if (pp->lencode == LEN33X) { |
466 | 0 | switch (quality) { |
467 | 0 | case 0 : |
468 | 0 | pp->disp=.002; |
469 | 0 | break; |
470 | 0 | case 1 : |
471 | 0 | pp->disp=.02; |
472 | 0 | break; |
473 | 0 | case 2 : |
474 | 0 | pp->disp=.04; |
475 | 0 | break; |
476 | 0 | case 3 : |
477 | 0 | pp->disp=.08; |
478 | 0 | break; |
479 | 0 | default: |
480 | 0 | pp->disp=MAXDISPERSE; |
481 | 0 | break; |
482 | 0 | } |
483 | 0 | } else { |
484 | 0 | switch (quality) { |
485 | 0 | case 5 : |
486 | 0 | pp->disp=.002; |
487 | 0 | break; |
488 | 0 | case 4 : |
489 | 0 | pp->disp=.02; |
490 | 0 | break; |
491 | 0 | case 3 : |
492 | 0 | pp->disp=.04; |
493 | 0 | break; |
494 | 0 | case 2 : |
495 | 0 | pp->disp=.08; |
496 | 0 | break; |
497 | 0 | case 1 : |
498 | 0 | pp->disp=.16; |
499 | 0 | break; |
500 | 0 | default: |
501 | 0 | pp->disp=MAXDISPERSE; |
502 | 0 | break; |
503 | 0 | } |
504 | |
|
505 | 0 | } |
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 | 0 | if (syncchar != ':') |
514 | 0 | pp->leap = LEAP_NOTINSYNC; |
515 | 0 | else if (leapchar == '+') |
516 | 0 | pp->leap = LEAP_ADDSECOND; |
517 | 0 | else if (leapchar == '-') |
518 | 0 | pp->leap = LEAP_DELSECOND; |
519 | 0 | else |
520 | 0 | pp->leap = LEAP_NOWARNING; |
521 | | |
522 | | /* |
523 | | * Process the new sample in the median filter and determine the |
524 | | * timecode timestamp. |
525 | | */ |
526 | 0 | if (!refclock_process(pp)) { |
527 | 0 | refclock_report(peer, CEVNT_BADTIME); |
528 | 0 | } |
529 | |
|
530 | 0 | } |
531 | | |
532 | | /* |
533 | | * ulink_poll - called by the transmit procedure |
534 | | */ |
535 | | |
536 | | static void |
537 | | ulink_poll( |
538 | | int unit, |
539 | | struct peer *peer |
540 | | ) |
541 | 0 | { |
542 | 0 | struct refclockproc *pp; |
543 | 0 | char pollchar; |
544 | |
|
545 | 0 | pp = peer->procptr; |
546 | 0 | pollchar = 'T'; |
547 | 0 | if (pp->sloppyclockflag & CLK_FLAG1) { |
548 | 0 | if (write(pp->io.fd, &pollchar, 1) != 1) |
549 | 0 | refclock_report(peer, CEVNT_FAULT); |
550 | 0 | else |
551 | 0 | pp->polls++; |
552 | 0 | } |
553 | 0 | else |
554 | 0 | pp->polls++; |
555 | |
|
556 | 0 | if (pp->coderecv == pp->codeproc) { |
557 | 0 | refclock_report(peer, CEVNT_TIMEOUT); |
558 | 0 | return; |
559 | 0 | } |
560 | 0 | pp->lastref = pp->lastrec; |
561 | 0 | refclock_receive(peer); |
562 | 0 | record_clock_stats(&peer->srcadr, pp->a_lastcode); |
563 | |
|
564 | 0 | } |
565 | | |
566 | | #else |
567 | | int refclock_ulink_bs; |
568 | | #endif /* REFCLOCK */ |