/* $NetBSD: clk_meinberg.c,v 1.8 2024/08/18 20:47:17 christos Exp $ */ /* * /src/NTP/REPOSITORY/ntp4-dev/libparse/clk_meinberg.c,v 4.12.2.1 2005/09/25 10:22:35 kardel RELEASE_20050925_A * * clk_meinberg.c,v 4.12.2.1 2005/09/25 10:22:35 kardel RELEASE_20050925_A * * Meinberg clock support * * Copyright (c) 1995-2015 by Frank Kardel ntp.org> * Copyright (c) 1989-1994 by Frank Kardel, Friedrich-Alexander Universitaet Erlangen-Nuernberg, Germany * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #ifdef HAVE_CONFIG_H # include #endif #if defined(REFCLOCK) && defined(CLOCK_PARSE) && defined(CLOCK_MEINBERG) #include "ntp_fp.h" #include "ntp_unixtime.h" #include "ntp_calendar.h" #include "ntp_machine.h" #include "parse.h" #ifndef PARSESTREAM #include #else #include "sys/parsestreams.h" #endif #include "ntp_stdlib.h" #include "ntp_stdlib.h" #include "mbg_gps166.h" #include "binio.h" #include "ascii.h" /* * The Meinberg receiver every second sends a datagram of the following form * (Standard Format) * * D:
..;T:;U:::; * pos: 0 00 00 0 00 0 11 111 1 111 12 2 22 2 22 2 2 2 3 3 3 * 1 23 45 6 78 9 01 234 5 678 90 1 23 4 56 7 8 9 0 1 2 * = '\002' ASCII start of text * = '\003' ASCII end of text *
,, = day, month, year(2 digits!!) * = day of week (sunday= 0) * ,, = hour, minute, second * = '#' if never synced since powerup for DCF C51 * = '#' if not PZF sychronisation available for PZF 535/509 * = ' ' if ok * = '*' if time comes from internal quartz * = ' ' if completely synched * = 'S' if daylight saving time is active * = 'U' if time is represented in UTC * = ' ' if no special condition exists * = '!' during the hour preceeding an daylight saving time * start/end change * = 'A' leap second insert warning * = ' ' if no special condition exists * * Extended data format (PZFUERL for PZF type clocks) * *
..; ; ::; * pos: 0 00 0 00 0 00 11 1 11 11 1 11 2 22 22 2 2 2 2 2 3 3 3 * 1 23 4 56 7 89 01 2 34 56 7 89 0 12 34 5 6 7 8 9 0 1 2 * = '\002' ASCII start of text * = '\003' ASCII end of text *
,, = day, month, year(2 digits!!) * = day of week (sunday= 0) * ,, = hour, minute, second * = 'U' UTC time display * = '#' if never synced since powerup else ' ' for DCF C51 * '#' if not PZF sychronisation available else ' ' for PZF 535/509 * = '*' if time comes from internal quartz else ' ' * = 'S' if daylight saving time is active else ' ' * = '!' during the hour preceeding an daylight saving time * start/end change * = 'A' LEAP second announcement * = 'R' "call bit" used to signalize irregularities in the control facilities, * usually ' ', until 2003 indicated transmission via alternate antenna * * Meinberg GPS receivers * * For very old devices you must get the Uni-Erlangen firmware for the GPS receiver support * to work to full satisfaction ! * With newer GPS receiver types the Uni Erlangen string format can be configured at the device. * *
..; ; ::; <+/-><00:00>; ; * * 000000000111111111122222222223333333333444444444455555555556666666 * 123456789012345678901234567890123456789012345678901234567890123456 * \x0209.07.93; 5; 08:48:26; +00:00; #*S!A L; 49.5736N 11.0280E 373m\x03 * * * = '\002' ASCII start of text * = '\003' ASCII end of text *
,, = day, month, year(2 digits!!) * = day of week (sunday= 0) * ,, = hour, minute, second * <+/->,<00:00> = offset to UTC * = '#' if never synced since powerup else ' ' * = '*' if position is not confirmed else ' ' * = 'S' if daylight saving time is active else ' ' * = '!' during the hour preceeding an daylight saving time * start/end change * = 'A' LEAP second announcement * = 'R' "call bit" used to signalize irregularities in the control facilities, * usually ' ', until 2003 indicated transmission via alternate antenna * (reminiscent of PZF receivers) * = 'L' on 23:59:60 * * Binary messages have a lead in for a fixed header of SOH */ /*--------------------------------------------------------------*/ /* Name: csum() */ /* */ /* Purpose: Compute a checksum about a number of bytes */ /* */ /* Input: uchar *p address of the first byte */ /* short n the number of bytes */ /* */ /* Output: -- */ /* */ /* Ret val: the checksum */ /*+-------------------------------------------------------------*/ CSUM mbg_csum( unsigned char *p, unsigned int n ) { unsigned int sum = 0; unsigned int i; for ( i = 0; i < n; i++ ) sum += *p++; return (CSUM) sum; } /* csum */ void get_mbg_header( unsigned char **bufpp, GPS_MSG_HDR *headerp ) { headerp->cmd = (GPS_CMD) get_lsb_short(bufpp); headerp->len = get_lsb_uint16(bufpp); headerp->data_csum = (CSUM) get_lsb_short(bufpp); headerp->hdr_csum = (CSUM) get_lsb_short(bufpp); } static struct format meinberg_fmt[] = { { { { 3, 2}, { 6, 2}, { 9, 2}, { 18, 2}, { 21, 2}, { 24, 2}, { 14, 1}, { 27, 4}, { 29, 1}, }, (const unsigned char *)"\2D: . . ;T: ;U: . . ; \3", 0 }, { /* special extended FAU Erlangen extended format */ { { 1, 2}, { 4, 2}, { 7, 2}, { 14, 2}, { 17, 2}, { 20, 2}, { 11, 1}, { 25, 4}, { 27, 1}, }, (const unsigned char *)"\2 . . ; ; : : ; \3", MBG_EXTENDED }, { /* special extended FAU Erlangen GPS format */ { { 1, 2}, { 4, 2}, { 7, 2}, { 14, 2}, { 17, 2}, { 20, 2}, { 11, 1}, { 32, 7}, { 35, 1}, { 25, 2}, { 28, 2}, { 24, 1} }, (const unsigned char *)"\2 . . ; ; : : ; : ; ; . . ", 0 } }; static parse_cvt_fnc_t cvt_meinberg; static parse_cvt_fnc_t cvt_mgps; static parse_inp_fnc_t mbg_input; static parse_inp_fnc_t gps_input; struct msg_buf { unsigned short len; /* len to fill */ unsigned short phase; /* current input phase */ }; #define MBG_NONE 0 /* no data input */ #define MBG_HEADER 1 /* receiving header */ #define MBG_DATA 2 /* receiving data */ #define MBG_STRING 3 /* receiving standard data message */ clockformat_t clock_meinberg[] = { { mbg_input, /* normal input handling */ cvt_meinberg, /* Meinberg conversion */ pps_one, /* easy PPS monitoring */ 0, /* conversion configuration */ "Meinberg Standard", /* Meinberg simple format - beware */ 32, /* string buffer */ 0 /* no private data (complete packets) */ }, { mbg_input, /* normal input handling */ cvt_meinberg, /* Meinberg conversion */ pps_one, /* easy PPS monitoring */ 0, /* conversion configuration */ "Meinberg Extended", /* Meinberg enhanced format */ 32, /* string buffer */ 0 /* no private data (complete packets) */ }, { gps_input, /* no input handling */ cvt_mgps, /* Meinberg GPS receiver conversion */ pps_one, /* easy PPS monitoring */ (void *)&meinberg_fmt[2], /* conversion configuration */ "Meinberg GPS Extended", /* Meinberg FAU GPS format */ 512, /* string buffer */ sizeof(struct msg_buf) /* no private data (complete packets) */ } }; /* * parse_cvt_fnc_t cvt_meinberg * * convert simple type format */ static u_long cvt_meinberg( unsigned char *buffer, int size, struct format *unused, clocktime_t *clock_time, void *local ) { struct format *format; /* * select automagically correct data format */ if (Strok(buffer, meinberg_fmt[0].fixed_string)) { format = &meinberg_fmt[0]; } else { if (Strok(buffer, meinberg_fmt[1].fixed_string)) { format = &meinberg_fmt[1]; } else { return CVT_FAIL|CVT_BADFMT; } } /* * collect data */ if (Stoi(&buffer[format->field_offsets[O_DAY].offset], &clock_time->day, format->field_offsets[O_DAY].length) || Stoi(&buffer[format->field_offsets[O_MONTH].offset], &clock_time->month, format->field_offsets[O_MONTH].length) || Stoi(&buffer[format->field_offsets[O_YEAR].offset], &clock_time->year, format->field_offsets[O_YEAR].length) || Stoi(&buffer[format->field_offsets[O_HOUR].offset], &clock_time->hour, format->field_offsets[O_HOUR].length) || Stoi(&buffer[format->field_offsets[O_MIN].offset], &clock_time->minute, format->field_offsets[O_MIN].length) || Stoi(&buffer[format->field_offsets[O_SEC].offset], &clock_time->second, format->field_offsets[O_SEC].length)) { return CVT_FAIL|CVT_BADFMT; } else { unsigned char *f = &buffer[format->field_offsets[O_FLAGS].offset]; clock_time->usecond = 0; clock_time->flags = PARSEB_S_LEAP; if (clock_time->second == 60) clock_time->flags |= PARSEB_LEAPSECOND; /* * in the extended timecode format we have also the * indication that the timecode is in UTC * for compatibilty reasons we start at the USUAL * offset (POWERUP flag) and know that the UTC indication * is the character before the powerup flag */ if ((format->flags & MBG_EXTENDED) && (f[-1] == 'U')) { /* * timecode is in UTC */ clock_time->utcoffset = 0; /* UTC */ clock_time->flags |= PARSEB_UTC; } else { /* * only calculate UTC offset if MET/MED is in time code * or we have the old time code format, where we do not * know whether it is UTC time or MET/MED * pray that nobody switches to UTC in the *old* standard time code * ROMS !!!! The new ROMS have 'U' at the ZONE field - good. */ switch (buffer[format->field_offsets[O_ZONE].offset]) { case ' ': clock_time->utcoffset = -1*60*60; /* MET */ break; case 'S': clock_time->utcoffset = -2*60*60; /* MED */ break; case 'U': /* * timecode is in UTC */ clock_time->utcoffset = 0; /* UTC */ clock_time->flags |= PARSEB_UTC; break; default: return CVT_FAIL|CVT_BADFMT; } } /* * gather status flags */ if (buffer[format->field_offsets[O_ZONE].offset] == 'S') clock_time->flags |= PARSEB_DST; if (f[0] == '#') clock_time->flags |= PARSEB_POWERUP; if (f[1] == '*') clock_time->flags |= PARSEB_NOSYNC; if (f[3] == '!') clock_time->flags |= PARSEB_ANNOUNCE; /* * oncoming leap second * 'a' code not confirmed - earth is not * expected to speed up */ if (f[3] == 'A') clock_time->flags |= PARSEB_LEAPADD; if (f[3] == 'a') clock_time->flags |= PARSEB_LEAPDEL; if (format->flags & MBG_EXTENDED) { clock_time->flags |= PARSEB_S_CALLBIT; /* * DCF77 does not encode the direction - * so we take the current default - * earth slowing down */ clock_time->flags &= ~PARSEB_LEAPDEL; if (f[4] == 'A') clock_time->flags |= PARSEB_LEAPADD; if (f[5] == 'R') clock_time->flags |= PARSEB_CALLBIT; } return CVT_OK; } } /* * parse_inp_fnc_t mbg_input * * grab data from input stream */ static u_long mbg_input( parse_t *parseio, char ch, timestamp_t *tstamp ) { unsigned int rtc; parseprintf(DD_PARSE, ("mbg_input(0x%p, 0x%x, ...)\n", (void*)parseio, ch)); switch (ch) { case STX: parseprintf(DD_PARSE, ("mbg_input: STX seen\n")); parseio->parse_index = 1; parseio->parse_data[0] = ch; parseio->parse_dtime.parse_stime = *tstamp; /* collect timestamp */ return PARSE_INP_SKIP; case ETX: parseprintf(DD_PARSE, ("mbg_input: ETX seen\n")); if ((rtc = parse_addchar(parseio, ch)) == PARSE_INP_SKIP) return parse_end(parseio); else return rtc; default: return parse_addchar(parseio, ch); } } /* * parse_cvt_fnc_t cvt_mgps * * convert Meinberg GPS format */ static u_long cvt_mgps( unsigned char *buffer, int size, struct format *format, clocktime_t *clock_time, void *local ) { if (!Strok(buffer, format->fixed_string)) { return cvt_meinberg(buffer, size, format, clock_time, local); } else { if (Stoi(&buffer[format->field_offsets[O_DAY].offset], &clock_time->day, format->field_offsets[O_DAY].length) || Stoi(&buffer[format->field_offsets[O_MONTH].offset], &clock_time->month, format->field_offsets[O_MONTH].length) || Stoi(&buffer[format->field_offsets[O_YEAR].offset], &clock_time->year, format->field_offsets[O_YEAR].length) || Stoi(&buffer[format->field_offsets[O_HOUR].offset], &clock_time->hour, format->field_offsets[O_HOUR].length) || Stoi(&buffer[format->field_offsets[O_MIN].offset], &clock_time->minute, format->field_offsets[O_MIN].length) || Stoi(&buffer[format->field_offsets[O_SEC].offset], &clock_time->second, format->field_offsets[O_SEC].length)) { return CVT_FAIL|CVT_BADFMT; } else { long h; unsigned char *f = &buffer[format->field_offsets[O_FLAGS].offset]; clock_time->flags = PARSEB_S_LEAP|PARSEB_S_POSITION; clock_time->usecond = 0; /* * calculate UTC offset */ if (Stoi(&buffer[format->field_offsets[O_UTCHOFFSET].offset], &h, format->field_offsets[O_UTCHOFFSET].length)) { return CVT_FAIL|CVT_BADFMT; } else { if (Stoi(&buffer[format->field_offsets[O_UTCMOFFSET].offset], &clock_time->utcoffset, format->field_offsets[O_UTCMOFFSET].length)) { return CVT_FAIL|CVT_BADFMT; } clock_time->utcoffset += TIMES60(h); clock_time->utcoffset = TIMES60(clock_time->utcoffset); if (buffer[format->field_offsets[O_UTCSOFFSET].offset] != '-') { clock_time->utcoffset = -clock_time->utcoffset; } } /* * gather status flags */ if (buffer[format->field_offsets[O_ZONE].offset] == 'S') clock_time->flags |= PARSEB_DST; if (clock_time->utcoffset == 0) clock_time->flags |= PARSEB_UTC; /* * no sv's seen - no time & position */ if (f[0] == '#') clock_time->flags |= PARSEB_POWERUP; /* * at least one sv seen - time (for last position) */ if (f[1] == '*') clock_time->flags |= PARSEB_NOSYNC; else if (!(clock_time->flags & PARSEB_POWERUP)) clock_time->flags |= PARSEB_POSITION; /* * oncoming zone switch */ if (f[3] == '!') clock_time->flags |= PARSEB_ANNOUNCE; /* * oncoming leap second * 'a' code not confirmed - earth is not * expected to speed up */ if (f[4] == 'A') clock_time->flags |= PARSEB_LEAPADD; if (f[4] == 'a') clock_time->flags |= PARSEB_LEAPDEL; /* * f[5] == ' ' */ /* * this is the leap second */ if ((f[6] == 'L') || (clock_time->second == 60)) clock_time->flags |= PARSEB_LEAPSECOND; return CVT_OK; } } } /* * parse_inp_fnc_t gps_input * * grep binary data from input stream */ static u_long gps_input( parse_t *parseio, char ch, timestamp_t *tstamp ) { CSUM calc_csum; /* used to compare the incoming csums */ GPS_MSG_HDR header; struct msg_buf *msg_buf; msg_buf = (struct msg_buf *)parseio->parse_pdata; parseprintf(DD_PARSE, ("gps_input(0x%p, 0x%x, ...)\n", (void*)parseio, ch)); if (!msg_buf) return PARSE_INP_SKIP; if ( msg_buf->phase == MBG_NONE ) { /* not receiving yet */ switch (ch) { case SOH: parseprintf(DD_PARSE, ("gps_input: SOH seen\n")); msg_buf->len = sizeof( header ); /* prepare to receive msg header */ msg_buf->phase = MBG_HEADER; /* receiving header */ break; case STX: parseprintf(DD_PARSE, ("gps_input: STX seen\n")); msg_buf->len = 0; msg_buf->phase = MBG_STRING; /* prepare to receive ASCII ETX delimited message */ parseio->parse_index = 1; parseio->parse_data[0] = ch; break; default: return PARSE_INP_SKIP; /* keep searching */ } parseio->parse_dtime.parse_msglen = 1; /* reset buffer pointer */ parseio->parse_dtime.parse_msg[0] = ch; /* fill in first character */ parseio->parse_dtime.parse_stime = *tstamp; /* collect timestamp */ return PARSE_INP_SKIP; } /* SOH/STX has already been received */ /* save incoming character in both buffers if needbe */ if ((msg_buf->phase == MBG_STRING) && (parseio->parse_index < parseio->parse_dsize)) parseio->parse_data[parseio->parse_index++] = ch; parseio->parse_dtime.parse_msg[parseio->parse_dtime.parse_msglen++] = ch; if (parseio->parse_dtime.parse_msglen > sizeof(parseio->parse_dtime.parse_msg)) { msg_buf->phase = MBG_NONE; /* buffer overflow - discard */ parseio->parse_data[parseio->parse_index] = '\0'; memcpy(parseio->parse_ldata, parseio->parse_data, (unsigned)(parseio->parse_index+1)); parseio->parse_ldsize = parseio->parse_index; return PARSE_INP_DATA; } switch (msg_buf->phase) { case MBG_HEADER: case MBG_DATA: msg_buf->len--; if ( msg_buf->len ) /* transfer not complete */ return PARSE_INP_SKIP; parseprintf(DD_PARSE, ("gps_input: %s complete\n", (msg_buf->phase == MBG_DATA) ? "data" : "header")); break; case MBG_STRING: if ((ch == ETX) || (parseio->parse_index >= parseio->parse_dsize)) { msg_buf->phase = MBG_NONE; parseprintf(DD_PARSE, ("gps_input: string complete\n")); parseio->parse_data[parseio->parse_index] = '\0'; memcpy(parseio->parse_ldata, parseio->parse_data, (unsigned)(parseio->parse_index+1)); parseio->parse_ldsize = parseio->parse_index; parseio->parse_index = 0; return PARSE_INP_TIME; } else { return PARSE_INP_SKIP; } } /* cnt == 0, so the header or the whole message is complete */ if ( msg_buf->phase == MBG_HEADER ) { /* header complete now */ unsigned char *datap = parseio->parse_dtime.parse_msg + 1; get_mbg_header(&datap, &header); parseprintf(DD_PARSE, ("gps_input: header: cmd 0x%x, len %d, dcsum 0x%x, hcsum 0x%x\n", (int)header.cmd, (int)header.len, (int)header.data_csum, (int)header.hdr_csum)); calc_csum = mbg_csum( (unsigned char *) parseio->parse_dtime.parse_msg + 1, (unsigned short)6 ); if ( calc_csum != header.hdr_csum ) { parseprintf(DD_PARSE, ("gps_input: header checksum mismatch expected 0x%x, got 0x%x\n", (int)calc_csum, (int)mbg_csum( (unsigned char *) parseio->parse_dtime.parse_msg, (unsigned short)6 ))); msg_buf->phase = MBG_NONE; /* back to hunting mode */ return PARSE_INP_DATA; /* invalid header checksum received - pass up for detection */ } if ((header.len == 0) || /* no data to wait for */ (header.len >= (sizeof (parseio->parse_dtime.parse_msg) - sizeof(header) - 1))) /* blows anything we have space for */ { msg_buf->phase = MBG_NONE; /* back to hunting mode */ return (header.len == 0) ? PARSE_INP_DATA : PARSE_INP_SKIP; /* message complete/throwaway */ } parseprintf(DD_PARSE, ("gps_input: expecting %d bytes of data message\n", (int)header.len)); msg_buf->len = header.len;/* save number of bytes to wait for */ msg_buf->phase = MBG_DATA; /* flag header already complete */ return PARSE_INP_SKIP; } parseprintf(DD_PARSE, ("gps_input: message data complete\n")); /* Header and data have been received. The header checksum has been */ /* checked */ msg_buf->phase = MBG_NONE; /* back to hunting mode */ return PARSE_INP_DATA; /* message complete, must be evaluated */ } #else /* not (REFCLOCK && CLOCK_PARSE && CLOCK_MEINBERG) */ NONEMPTY_TRANSLATION_UNIT #endif /* not (REFCLOCK && CLOCK_PARSE && CLOCK_MEINBERG) */ /* * History: * * clk_meinberg.c,v * Revision 4.12.2.1 2005/09/25 10:22:35 kardel * cleanup buffer bounds * * Revision 4.12 2005/04/16 17:32:10 kardel * update copyright * * Revision 4.11 2004/11/14 15:29:41 kardel * support PPSAPI, upgrade Copyright to Berkeley style * * Revision 4.8 1999/11/28 09:13:50 kardel * RECON_4_0_98F * * Revision 4.7 1999/02/21 11:09:14 kardel * cleanup * * Revision 4.6 1998/06/14 21:09:36 kardel * Sun acc cleanup * * Revision 4.5 1998/06/13 15:18:54 kardel * fix mem*() to b*() function macro emulation * * Revision 4.4 1998/06/13 12:03:23 kardel * fix SYSV clock name clash * * Revision 4.3 1998/06/12 15:22:28 kardel * fix prototypes * * Revision 4.2 1998/05/24 16:14:42 kardel * support current Meinberg standard data formats * * Revision 4.1 1998/05/24 09:39:52 kardel * implementation of the new IO handling model * * Revision 4.0 1998/04/10 19:45:29 kardel * Start 4.0 release version numbering * * from V3 3.23 - log info deleted 1998/04/11 kardel * */