diff options
Diffstat (limited to 'libparse/clk_meinberg.c')
-rw-r--r-- | libparse/clk_meinberg.c | 747 |
1 files changed, 747 insertions, 0 deletions
diff --git a/libparse/clk_meinberg.c b/libparse/clk_meinberg.c new file mode 100644 index 0000000..87c7fde --- /dev/null +++ b/libparse/clk_meinberg.c @@ -0,0 +1,747 @@ +/* + * /src/NTP/ntp-4/libparse/clk_meinberg.c,v 4.8 1999/11/28 09:13:50 kardel RELEASE_19991128_A + * + * clk_meinberg.c,v 4.8 1999/11/28 09:13:50 kardel RELEASE_19991128_A + * + * Meinberg clock support + * + * Copyright (C) 1995-1999 by Frank Kardel <kardel@acm.org> + * Copyright (C) 1992-1994 by Frank Kardel, Friedrich-Alexander Universität Erlangen-Nürnberg, Germany + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#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 <stdio.h> +#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) + * + * <STX>D:<dd>.<mm>.<yy>;T:<w>;U:<hh>:<mm>:<ss>;<S><F><D><A><ETX> + * 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 + * <STX> = '\002' ASCII start of text + * <ETX> = '\003' ASCII end of text + * <dd>,<mm>,<yy> = day, month, year(2 digits!!) + * <w> = day of week (sunday= 0) + * <hh>,<mm>,<ss> = hour, minute, second + * <S> = '#' if never synced since powerup for DCF C51 + * = '#' if not PZF sychronisation available for PZF 535/509 + * = ' ' if ok + * <F> = '*' if time comes from internal quartz + * = ' ' if completely synched + * <D> = 'S' if daylight saving time is active + * = 'U' if time is represented in UTC + * = ' ' if no special condition exists + * <A> = '!' 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) + * + * <STX><dd>.<mm>.<yy>; <w>; <hh>:<mm>:<ss>; <U><S><F><D><A><L><R><ETX> + * 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 + * <STX> = '\002' ASCII start of text + * <ETX> = '\003' ASCII end of text + * <dd>,<mm>,<yy> = day, month, year(2 digits!!) + * <w> = day of week (sunday= 0) + * <hh>,<mm>,<ss> = hour, minute, second + * <U> = 'U' UTC time display + * <S> = '#' if never synced since powerup else ' ' for DCF C51 + * '#' if not PZF sychronisation available else ' ' for PZF 535/509 + * <F> = '*' if time comes from internal quartz else ' ' + * <D> = 'S' if daylight saving time is active else ' ' + * <A> = '!' during the hour preceeding an daylight saving time + * start/end change + * <L> = 'A' LEAP second announcement + * <R> = 'R' alternate antenna + * + * Meinberg GPS166 receiver + * + * You must get the Uni-Erlangen firmware for the GPS receiver support + * to work to full satisfaction ! + * + * <STX><dd>.<mm>.<yy>; <w>; <hh>:<mm>:<ss>; <+/-><00:00>; <U><S><F><D><A><L><R><L>; <position...><ETX> + * + * 000000000111111111122222222223333333333444444444455555555556666666 + * 123456789012345678901234567890123456789012345678901234567890123456 + * \x0209.07.93; 5; 08:48:26; +00:00; #*S!A L; 49.5736N 11.0280E 373m\x03 + * + * + * <STX> = '\002' ASCII start of text + * <ETX> = '\003' ASCII end of text + * <dd>,<mm>,<yy> = day, month, year(2 digits!!) + * <w> = day of week (sunday= 0) + * <hh>,<mm>,<ss> = hour, minute, second + * <+/->,<00:00> = offset to UTC + * <S> = '#' if never synced since powerup else ' ' + * <F> = '*' if position is not confirmed else ' ' + * <D> = 'S' if daylight saving time is active else ' ' + * <A> = '!' during the hour preceeding an daylight saving time + * start/end change + * <L> = 'A' LEAP second announcement + * <R> = 'R' alternate antenna (reminiscent of PZF535) usually ' ' + * <L> = '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 */ +/*+-------------------------------------------------------------*/ + +unsigned long +mbg_csum( + unsigned char *p, + unsigned int n + ) +{ + unsigned long sum = 0; + short i; + + for ( i = 0; i < n; i++ ) + sum += *p++; + + return( sum ); +} /* csum */ + +void +get_mbg_header( + unsigned char **bufpp, + GPS_MSG_HDR *headerp + ) +{ + headerp->gps_cmd = get_lsb_short(bufpp); + headerp->gps_len = get_lsb_short(bufpp); + headerp->gps_data_csum = get_lsb_short(bufpp); + headerp->gps_hdr_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 u_long cvt_meinberg P((unsigned char *, int, struct format *, clocktime_t *, void *)); +static u_long cvt_mgps P((unsigned char *, int, struct format *, clocktime_t *, void *)); +static u_long mbg_input P((parse_t *, unsigned int, timestamp_t *)); +static u_long gps_input P((parse_t *, unsigned int, timestamp_t *)); + +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 pakets) */ + }, + { + 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 pakets) */ + }, + { + gps_input, /* no input handling */ + cvt_mgps, /* Meinberg GPS166 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 pakets) */ + } +}; + +/* + * 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_ANTENNA; + + /* + * 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_ALTERNATE; + } + return CVT_OK; + } +} + + +/* + * mbg_input + * + * grep data from input stream + */ +static u_long +mbg_input( + parse_t *parseio, + unsigned int ch, + timestamp_t *tstamp + ) +{ + unsigned int rtc; + + parseprintf(DD_PARSE, ("mbg_input(0x%lx, 0x%x, ...)\n", (long)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); + } +} + + +/* + * 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; + } + } +} + +/* + * gps_input + * + * grep binary data from input stream + */ +static u_long +gps_input( + parse_t *parseio, + unsigned int 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%lx, 0x%x, ...)\n", (long)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+1; + 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+1; + 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.gps_cmd, (int)header.gps_len, (int)header.gps_data_csum, + (int)header.gps_hdr_csum)); + + + calc_csum = mbg_csum( (unsigned char *) parseio->parse_dtime.parse_msg + 1, (unsigned short)6 ); + + if ( calc_csum != header.gps_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.gps_len == 0) || /* no data to wait for */ + (header.gps_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.gps_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.gps_len)); + + msg_buf->len = header.gps_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) */ +int clk_meinberg_bs; +#endif /* not (REFCLOCK && CLOCK_PARSE && CLOCK_MEINBERG) */ + +/* + * History: + * + * clk_meinberg.c,v + * 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 + * + */ |