2 * /src/NTP/ntp-4/libparse/clk_meinberg.c,v 4.8 1999/11/28 09:13:50 kardel RELEASE_19991128_A
4 * clk_meinberg.c,v 4.8 1999/11/28 09:13:50 kardel RELEASE_19991128_A
6 * Meinberg clock support
8 * Copyright (C) 1995-1999 by Frank Kardel <kardel@acm.org>
9 * Copyright (C) 1992-1994 by Frank Kardel, Friedrich-Alexander Universität Erlangen-Nürnberg, Germany
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 #if defined(REFCLOCK) && defined(CLOCK_PARSE) && defined(CLOCK_MEINBERG)
24 #include "ntp_unixtime.h"
25 #include "ntp_calendar.h"
27 #include "ntp_machine.h"
34 #include "sys/parsestreams.h"
37 #include "ntp_stdlib.h"
39 #include "ntp_stdlib.h"
41 #include "mbg_gps166.h"
46 * The Meinberg receiver every second sends a datagram of the following form
49 * <STX>D:<dd>.<mm>.<yy>;T:<w>;U:<hh>:<mm>:<ss>;<S><F><D><A><ETX>
50 * pos: 0 00 00 0 00 0 11 111 1 111 12 2 22 2 22 2 2 2 3 3 3
51 * 1 23 45 6 78 9 01 234 5 678 90 1 23 4 56 7 8 9 0 1 2
52 * <STX> = '\002' ASCII start of text
53 * <ETX> = '\003' ASCII end of text
54 * <dd>,<mm>,<yy> = day, month, year(2 digits!!)
55 * <w> = day of week (sunday= 0)
56 * <hh>,<mm>,<ss> = hour, minute, second
57 * <S> = '#' if never synced since powerup for DCF C51
58 * = '#' if not PZF sychronisation available for PZF 535/509
60 * <F> = '*' if time comes from internal quartz
61 * = ' ' if completely synched
62 * <D> = 'S' if daylight saving time is active
63 * = 'U' if time is represented in UTC
64 * = ' ' if no special condition exists
65 * <A> = '!' during the hour preceeding an daylight saving time
67 * = 'A' leap second insert warning
68 * = ' ' if no special condition exists
70 * Extended data format (PZFUERL for PZF type clocks)
72 * <STX><dd>.<mm>.<yy>; <w>; <hh>:<mm>:<ss>; <U><S><F><D><A><L><R><ETX>
73 * pos: 0 00 0 00 0 00 11 1 11 11 1 11 2 22 22 2 2 2 2 2 3 3 3
74 * 1 23 4 56 7 89 01 2 34 56 7 89 0 12 34 5 6 7 8 9 0 1 2
75 * <STX> = '\002' ASCII start of text
76 * <ETX> = '\003' ASCII end of text
77 * <dd>,<mm>,<yy> = day, month, year(2 digits!!)
78 * <w> = day of week (sunday= 0)
79 * <hh>,<mm>,<ss> = hour, minute, second
80 * <U> = 'U' UTC time display
81 * <S> = '#' if never synced since powerup else ' ' for DCF C51
82 * '#' if not PZF sychronisation available else ' ' for PZF 535/509
83 * <F> = '*' if time comes from internal quartz else ' '
84 * <D> = 'S' if daylight saving time is active else ' '
85 * <A> = '!' during the hour preceeding an daylight saving time
87 * <L> = 'A' LEAP second announcement
88 * <R> = 'R' alternate antenna
90 * Meinberg GPS166 receiver
92 * You must get the Uni-Erlangen firmware for the GPS receiver support
93 * to work to full satisfaction !
95 * <STX><dd>.<mm>.<yy>; <w>; <hh>:<mm>:<ss>; <+/-><00:00>; <U><S><F><D><A><L><R><L>; <position...><ETX>
97 * 000000000111111111122222222223333333333444444444455555555556666666
98 * 123456789012345678901234567890123456789012345678901234567890123456
99 * \x0209.07.93; 5; 08:48:26; +00:00; #*S!A L; 49.5736N 11.0280E 373m\x03
102 * <STX> = '\002' ASCII start of text
103 * <ETX> = '\003' ASCII end of text
104 * <dd>,<mm>,<yy> = day, month, year(2 digits!!)
105 * <w> = day of week (sunday= 0)
106 * <hh>,<mm>,<ss> = hour, minute, second
107 * <+/->,<00:00> = offset to UTC
108 * <S> = '#' if never synced since powerup else ' '
109 * <F> = '*' if position is not confirmed else ' '
110 * <D> = 'S' if daylight saving time is active else ' '
111 * <A> = '!' during the hour preceeding an daylight saving time
113 * <L> = 'A' LEAP second announcement
114 * <R> = 'R' alternate antenna (reminiscent of PZF535) usually ' '
115 * <L> = 'L' on 23:59:60
117 * Binary messages have a lead in for a fixed header of SOH
120 /*--------------------------------------------------------------*/
123 /* Purpose: Compute a checksum about a number of bytes */
125 /* Input: uchar *p address of the first byte */
126 /* short n the number of bytes */
130 /* Ret val: the checksum */
131 /*+-------------------------------------------------------------*/
139 unsigned long sum = 0;
142 for ( i = 0; i < n; i++ )
150 unsigned char **bufpp,
154 headerp->gps_cmd = get_lsb_short(bufpp);
155 headerp->gps_len = get_lsb_short(bufpp);
156 headerp->gps_data_csum = get_lsb_short(bufpp);
157 headerp->gps_hdr_csum = get_lsb_short(bufpp);
160 static struct format meinberg_fmt[] =
164 { 3, 2}, { 6, 2}, { 9, 2},
165 { 18, 2}, { 21, 2}, { 24, 2},
166 { 14, 1}, { 27, 4}, { 29, 1},
168 (const unsigned char *)"\2D: . . ;T: ;U: . . ; \3",
171 { /* special extended FAU Erlangen extended format */
173 { 1, 2}, { 4, 2}, { 7, 2},
174 { 14, 2}, { 17, 2}, { 20, 2},
175 { 11, 1}, { 25, 4}, { 27, 1},
177 (const unsigned char *)"\2 . . ; ; : : ; \3",
180 { /* special extended FAU Erlangen GPS format */
182 { 1, 2}, { 4, 2}, { 7, 2},
183 { 14, 2}, { 17, 2}, { 20, 2},
184 { 11, 1}, { 32, 7}, { 35, 1},
185 { 25, 2}, { 28, 2}, { 24, 1}
187 (const unsigned char *)"\2 . . ; ; : : ; : ; ; . . ",
192 static u_long cvt_meinberg P((unsigned char *, int, struct format *, clocktime_t *, void *));
193 static u_long cvt_mgps P((unsigned char *, int, struct format *, clocktime_t *, void *));
194 static u_long mbg_input P((parse_t *, unsigned int, timestamp_t *));
195 static u_long gps_input P((parse_t *, unsigned int, timestamp_t *));
199 unsigned short len; /* len to fill */
200 unsigned short phase; /* current input phase */
203 #define MBG_NONE 0 /* no data input */
204 #define MBG_HEADER 1 /* receiving header */
205 #define MBG_DATA 2 /* receiving data */
206 #define MBG_STRING 3 /* receiving standard data message */
208 clockformat_t clock_meinberg[] =
211 mbg_input, /* normal input handling */
212 cvt_meinberg, /* Meinberg conversion */
213 pps_one, /* easy PPS monitoring */
214 0, /* conversion configuration */
215 "Meinberg Standard", /* Meinberg simple format - beware */
216 32, /* string buffer */
217 0 /* no private data (complete pakets) */
220 mbg_input, /* normal input handling */
221 cvt_meinberg, /* Meinberg conversion */
222 pps_one, /* easy PPS monitoring */
223 0, /* conversion configuration */
224 "Meinberg Extended", /* Meinberg enhanced format */
225 32, /* string buffer */
226 0 /* no private data (complete pakets) */
229 gps_input, /* no input handling */
230 cvt_mgps, /* Meinberg GPS166 conversion */
231 pps_one, /* easy PPS monitoring */
232 (void *)&meinberg_fmt[2], /* conversion configuration */
233 "Meinberg GPS Extended", /* Meinberg FAU GPS format */
234 512, /* string buffer */
235 sizeof(struct msg_buf) /* no private data (complete pakets) */
242 * convert simple type format
246 unsigned char *buffer,
248 struct format *unused,
249 clocktime_t *clock_time,
253 struct format *format;
256 * select automagically correct data format
258 if (Strok(buffer, meinberg_fmt[0].fixed_string))
260 format = &meinberg_fmt[0];
264 if (Strok(buffer, meinberg_fmt[1].fixed_string))
266 format = &meinberg_fmt[1];
270 return CVT_FAIL|CVT_BADFMT;
277 if (Stoi(&buffer[format->field_offsets[O_DAY].offset], &clock_time->day,
278 format->field_offsets[O_DAY].length) ||
279 Stoi(&buffer[format->field_offsets[O_MONTH].offset], &clock_time->month,
280 format->field_offsets[O_MONTH].length) ||
281 Stoi(&buffer[format->field_offsets[O_YEAR].offset], &clock_time->year,
282 format->field_offsets[O_YEAR].length) ||
283 Stoi(&buffer[format->field_offsets[O_HOUR].offset], &clock_time->hour,
284 format->field_offsets[O_HOUR].length) ||
285 Stoi(&buffer[format->field_offsets[O_MIN].offset], &clock_time->minute,
286 format->field_offsets[O_MIN].length) ||
287 Stoi(&buffer[format->field_offsets[O_SEC].offset], &clock_time->second,
288 format->field_offsets[O_SEC].length))
290 return CVT_FAIL|CVT_BADFMT;
294 unsigned char *f = &buffer[format->field_offsets[O_FLAGS].offset];
296 clock_time->usecond = 0;
297 clock_time->flags = PARSEB_S_LEAP;
299 if (clock_time->second == 60)
300 clock_time->flags |= PARSEB_LEAPSECOND;
303 * in the extended timecode format we have also the
304 * indication that the timecode is in UTC
305 * for compatibilty reasons we start at the USUAL
306 * offset (POWERUP flag) and know that the UTC indication
307 * is the character before the powerup flag
309 if ((format->flags & MBG_EXTENDED) && (f[-1] == 'U'))
314 clock_time->utcoffset = 0; /* UTC */
315 clock_time->flags |= PARSEB_UTC;
320 * only calculate UTC offset if MET/MED is in time code
321 * or we have the old time code format, where we do not
322 * know whether it is UTC time or MET/MED
323 * pray that nobody switches to UTC in the *old* standard time code
324 * ROMS !!!! The new ROMS have 'U' at the ZONE field - good.
326 switch (buffer[format->field_offsets[O_ZONE].offset])
329 clock_time->utcoffset = -1*60*60; /* MET */
333 clock_time->utcoffset = -2*60*60; /* MED */
340 clock_time->utcoffset = 0; /* UTC */
341 clock_time->flags |= PARSEB_UTC;
345 return CVT_FAIL|CVT_BADFMT;
350 * gather status flags
352 if (buffer[format->field_offsets[O_ZONE].offset] == 'S')
353 clock_time->flags |= PARSEB_DST;
356 clock_time->flags |= PARSEB_POWERUP;
359 clock_time->flags |= PARSEB_NOSYNC;
362 clock_time->flags |= PARSEB_ANNOUNCE;
365 * oncoming leap second
366 * 'a' code not confirmed - earth is not
367 * expected to speed up
370 clock_time->flags |= PARSEB_LEAPADD;
373 clock_time->flags |= PARSEB_LEAPDEL;
376 if (format->flags & MBG_EXTENDED)
378 clock_time->flags |= PARSEB_S_ANTENNA;
381 * DCF77 does not encode the direction -
382 * so we take the current default -
385 clock_time->flags &= ~PARSEB_LEAPDEL;
388 clock_time->flags |= PARSEB_LEAPADD;
391 clock_time->flags |= PARSEB_ALTERNATE;
401 * grep data from input stream
412 parseprintf(DD_PARSE, ("mbg_input(0x%lx, 0x%x, ...)\n", (long)parseio, ch));
417 parseprintf(DD_PARSE, ("mbg_input: STX seen\n"));
419 parseio->parse_index = 1;
420 parseio->parse_data[0] = ch;
421 parseio->parse_dtime.parse_stime = *tstamp; /* collect timestamp */
422 return PARSE_INP_SKIP;
425 parseprintf(DD_PARSE, ("mbg_input: ETX seen\n"));
426 if ((rtc = parse_addchar(parseio, ch)) == PARSE_INP_SKIP)
427 return parse_end(parseio);
432 return parse_addchar(parseio, ch);
440 * convert Meinberg GPS format
444 unsigned char *buffer,
446 struct format *format,
447 clocktime_t *clock_time,
451 if (!Strok(buffer, format->fixed_string))
453 return cvt_meinberg(buffer, size, format, clock_time, local);
457 if (Stoi(&buffer[format->field_offsets[O_DAY].offset], &clock_time->day,
458 format->field_offsets[O_DAY].length) ||
459 Stoi(&buffer[format->field_offsets[O_MONTH].offset], &clock_time->month,
460 format->field_offsets[O_MONTH].length) ||
461 Stoi(&buffer[format->field_offsets[O_YEAR].offset], &clock_time->year,
462 format->field_offsets[O_YEAR].length) ||
463 Stoi(&buffer[format->field_offsets[O_HOUR].offset], &clock_time->hour,
464 format->field_offsets[O_HOUR].length) ||
465 Stoi(&buffer[format->field_offsets[O_MIN].offset], &clock_time->minute,
466 format->field_offsets[O_MIN].length) ||
467 Stoi(&buffer[format->field_offsets[O_SEC].offset], &clock_time->second,
468 format->field_offsets[O_SEC].length))
470 return CVT_FAIL|CVT_BADFMT;
475 unsigned char *f = &buffer[format->field_offsets[O_FLAGS].offset];
477 clock_time->flags = PARSEB_S_LEAP|PARSEB_S_POSITION;
479 clock_time->usecond = 0;
482 * calculate UTC offset
484 if (Stoi(&buffer[format->field_offsets[O_UTCHOFFSET].offset], &h,
485 format->field_offsets[O_UTCHOFFSET].length))
487 return CVT_FAIL|CVT_BADFMT;
491 if (Stoi(&buffer[format->field_offsets[O_UTCMOFFSET].offset], &clock_time->utcoffset,
492 format->field_offsets[O_UTCMOFFSET].length))
494 return CVT_FAIL|CVT_BADFMT;
497 clock_time->utcoffset += TIMES60(h);
498 clock_time->utcoffset = TIMES60(clock_time->utcoffset);
500 if (buffer[format->field_offsets[O_UTCSOFFSET].offset] != '-')
502 clock_time->utcoffset = -clock_time->utcoffset;
507 * gather status flags
509 if (buffer[format->field_offsets[O_ZONE].offset] == 'S')
510 clock_time->flags |= PARSEB_DST;
512 if (clock_time->utcoffset == 0)
513 clock_time->flags |= PARSEB_UTC;
516 * no sv's seen - no time & position
519 clock_time->flags |= PARSEB_POWERUP;
522 * at least one sv seen - time (for last position)
525 clock_time->flags |= PARSEB_NOSYNC;
527 if (!(clock_time->flags & PARSEB_POWERUP))
528 clock_time->flags |= PARSEB_POSITION;
531 * oncoming zone switch
534 clock_time->flags |= PARSEB_ANNOUNCE;
537 * oncoming leap second
538 * 'a' code not confirmed - earth is not
539 * expected to speed up
542 clock_time->flags |= PARSEB_LEAPADD;
545 clock_time->flags |= PARSEB_LEAPDEL;
552 * this is the leap second
554 if ((f[6] == 'L') || (clock_time->second == 60))
555 clock_time->flags |= PARSEB_LEAPSECOND;
565 * grep binary data from input stream
574 CSUM calc_csum; /* used to compare the incoming csums */
576 struct msg_buf *msg_buf;
578 msg_buf = (struct msg_buf *)parseio->parse_pdata;
580 parseprintf(DD_PARSE, ("gps_input(0x%lx, 0x%x, ...)\n", (long)parseio, ch));
583 return PARSE_INP_SKIP;
585 if ( msg_buf->phase == MBG_NONE )
586 { /* not receiving yet */
590 parseprintf(DD_PARSE, ("gps_input: SOH seen\n"));
592 msg_buf->len = sizeof( header ); /* prepare to receive msg header */
593 msg_buf->phase = MBG_HEADER; /* receiving header */
597 parseprintf(DD_PARSE, ("gps_input: STX seen\n"));
600 msg_buf->phase = MBG_STRING; /* prepare to receive ASCII ETX delimited message */
601 parseio->parse_index = 1;
602 parseio->parse_data[0] = ch;
606 return PARSE_INP_SKIP; /* keep searching */
609 parseio->parse_dtime.parse_msglen = 1; /* reset buffer pointer */
610 parseio->parse_dtime.parse_msg[0] = ch; /* fill in first character */
611 parseio->parse_dtime.parse_stime = *tstamp; /* collect timestamp */
612 return PARSE_INP_SKIP;
615 /* SOH/STX has already been received */
617 /* save incoming character in both buffers if needbe */
618 if ((msg_buf->phase == MBG_STRING) &&
619 (parseio->parse_index < parseio->parse_dsize))
620 parseio->parse_data[parseio->parse_index++] = ch;
622 parseio->parse_dtime.parse_msg[parseio->parse_dtime.parse_msglen++] = ch;
624 if (parseio->parse_dtime.parse_msglen > sizeof(parseio->parse_dtime.parse_msg))
626 msg_buf->phase = MBG_NONE; /* buffer overflow - discard */
627 parseio->parse_data[parseio->parse_index] = '\0';
628 memcpy(parseio->parse_ldata, parseio->parse_data, (unsigned)(parseio->parse_index+1));
629 parseio->parse_ldsize = parseio->parse_index+1;
630 return PARSE_INP_DATA;
633 switch (msg_buf->phase)
639 if ( msg_buf->len ) /* transfer not complete */
640 return PARSE_INP_SKIP;
642 parseprintf(DD_PARSE, ("gps_input: %s complete\n", (msg_buf->phase == MBG_DATA) ? "data" : "header"));
647 if ((ch == ETX) || (parseio->parse_index >= parseio->parse_dsize))
649 msg_buf->phase = MBG_NONE;
650 parseprintf(DD_PARSE, ("gps_input: string complete\n"));
651 parseio->parse_data[parseio->parse_index] = '\0';
652 memcpy(parseio->parse_ldata, parseio->parse_data, (unsigned)(parseio->parse_index+1));
653 parseio->parse_ldsize = parseio->parse_index+1;
654 parseio->parse_index = 0;
655 return PARSE_INP_TIME;
659 return PARSE_INP_SKIP;
663 /* cnt == 0, so the header or the whole message is complete */
665 if ( msg_buf->phase == MBG_HEADER )
666 { /* header complete now */
667 unsigned char *datap = parseio->parse_dtime.parse_msg + 1;
669 get_mbg_header(&datap, &header);
671 parseprintf(DD_PARSE, ("gps_input: header: cmd 0x%x, len %d, dcsum 0x%x, hcsum 0x%x\n",
672 (int)header.gps_cmd, (int)header.gps_len, (int)header.gps_data_csum,
673 (int)header.gps_hdr_csum));
676 calc_csum = mbg_csum( (unsigned char *) parseio->parse_dtime.parse_msg + 1, (unsigned short)6 );
678 if ( calc_csum != header.gps_hdr_csum )
680 parseprintf(DD_PARSE, ("gps_input: header checksum mismatch expected 0x%x, got 0x%x\n",
681 (int)calc_csum, (int)mbg_csum( (unsigned char *) parseio->parse_dtime.parse_msg, (unsigned short)6 )));
683 msg_buf->phase = MBG_NONE; /* back to hunting mode */
684 return PARSE_INP_DATA; /* invalid header checksum received - pass up for detection */
687 if ((header.gps_len == 0) || /* no data to wait for */
688 (header.gps_len >= (sizeof (parseio->parse_dtime.parse_msg) - sizeof(header) - 1))) /* blows anything we have space for */
690 msg_buf->phase = MBG_NONE; /* back to hunting mode */
691 return (header.gps_len == 0) ? PARSE_INP_DATA : PARSE_INP_SKIP; /* message complete/throwaway */
694 parseprintf(DD_PARSE, ("gps_input: expecting %d bytes of data message\n", (int)header.gps_len));
696 msg_buf->len = header.gps_len;/* save number of bytes to wait for */
697 msg_buf->phase = MBG_DATA; /* flag header already complete */
698 return PARSE_INP_SKIP;
701 parseprintf(DD_PARSE, ("gps_input: message data complete\n"));
703 /* Header and data have been received. The header checksum has been */
706 msg_buf->phase = MBG_NONE; /* back to hunting mode */
707 return PARSE_INP_DATA; /* message complete, must be evaluated */
710 #else /* not (REFCLOCK && CLOCK_PARSE && CLOCK_MEINBERG) */
712 #endif /* not (REFCLOCK && CLOCK_PARSE && CLOCK_MEINBERG) */
718 * Revision 4.8 1999/11/28 09:13:50 kardel
721 * Revision 4.7 1999/02/21 11:09:14 kardel
724 * Revision 4.6 1998/06/14 21:09:36 kardel
727 * Revision 4.5 1998/06/13 15:18:54 kardel
728 * fix mem*() to b*() function macro emulation
730 * Revision 4.4 1998/06/13 12:03:23 kardel
731 * fix SYSV clock name clash
733 * Revision 4.3 1998/06/12 15:22:28 kardel
736 * Revision 4.2 1998/05/24 16:14:42 kardel
737 * support current Meinberg standard data formats
739 * Revision 4.1 1998/05/24 09:39:52 kardel
740 * implementation of the new IO handling model
742 * Revision 4.0 1998/04/10 19:45:29 kardel
743 * Start 4.0 release version numbering
745 * from V3 3.23 - log info deleted 1998/04/11 kardel