/*
 * /src/NTP/REPOSITORY/v4/libparse/clk_meinberg.c,v 3.23 1997/01/19 12:44:38 kardel Exp
 *  
 * clk_meinberg.c,v 3.23 1997/01/19 12:44:38 kardel Exp
 *
 * Meinberg clock support
 *
 * Copyright (C) 1992,1993,1994,1995,1996 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(PARSE) || defined(PARSEPPS)) && defined(CLOCK_MEINBERG)

#include <sys/types.h>
#include <sys/time.h>
#include <sys/errno.h>
#include "ntp_fp.h"
#include "ntp_unixtime.h"
#include "ntp_calendar.h"

#include "parse.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 else ' ' for DCF U/A 31
 *                   '#' if not PZF sychronisation available else ' ' for PZF 535
 * <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
 *                   'A' in future versions probably for leap second insert
 *                       warning
 *
 * For the university of Erlangen a special format was implemented to support
 * LEAP announcement and anouncement of alternate antenna.
 *
 * Version for UNI-ERLANGEN Software is: PZFUERL V4.6 (Meinberg)
 *
 * The use of this software release (or higher) is *ABSOLUTELY*
 * recommended (ask for PZFUERL version as some minor HW fixes have
 * been introduced) due to the LEAP second support and UTC indication.
 * The standard timecode does not indicate when the timecode is in
 * UTC (by front panel configuration) thus we have no chance to find
 * the correct utc offset. For the standard format do not ever use
 * UTC display as this is not detectable in the time code !!!
 *
 *     <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 U/A 31
 *                   '#' if not PZF sychronisation available else ' ' for PZF 535
 * <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 ' ' for DCF U/A 31
 *                   '#' if not PZF sychronisation available else ' ' for PZF 535
 * <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 (reminiscent of PZF535) usually ' '
 * <L>		   = 'L' on 23:59:60
 */

static struct format meinberg_fmt[] =
{
  {
    {
      { 3, 2},  {  6, 2}, {  9, 2},
      { 18, 2}, { 21, 2}, { 24, 2},
      { 14, 1}, { 27, 4}, { 29, 1},
    },
    "\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},
    },
    "\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}
    },
    "\2  .  .  ;  ;   :  :  ;    :  ;        ;   .         .       ",
    0
  }
};

static u_long cvt_meinberg();
static u_long cvt_mgps();

clockformat_t clock_meinberg[] =
{
  {
    (unsigned long (*)())0,	/* no input handling */
    cvt_meinberg,		/* Meinberg conversion */
    syn_simple,			/* easy time stamps for RS232 (fallback) */
    pps_simple,			/* easy PPS monitoring */
    (u_long (*)())0,		/* no time code synthesizer monitoring */
    (void *)&meinberg_fmt[0],	/* conversion configuration */
    "Meinberg Standard",	/* Meinberg simple format - beware */
    32,				/* string buffer */
    F_START|F_END|SYNC_START|SYNC_ONE, /* paket START/END delimiter, START synchronisation, PPS ONE sampling */
    0,				/* no private data (complete pakets) */
    { 0, 0},
    '\2',
    '\3',
    '\0'
  },
  {
    (unsigned long (*)())0,	/* no input handling */
    cvt_meinberg,		/* Meinberg conversion */
    syn_simple,			/* easy time stamps for RS232 (fallback) */
    pps_simple,			/* easy PPS monitoring */
    (u_long (*)())0,		/* no time code synthesizer monitoring */
    (void *)&meinberg_fmt[1],	/* conversion configuration */
    "Meinberg Extended",	/* Meinberg enhanced format */
    32,				/* string buffer */
    F_START|F_END|SYNC_START|SYNC_ONE,	/* paket START/END delimiter, START synchronisation, PPS ONE sampling  */
    0,				/* no private data (complete pakets) */
    { 0, 0},
    '\2',
    '\3',
    '\0'
  },
  {
    (unsigned long (*)())0,	/* no input handling */
    cvt_mgps,			/* Meinberg GPS166 conversion */
    syn_simple,			/* easy time stamps for RS232 (fallback) */
    pps_simple,			/* easy PPS monitoring */
    (u_long (*)())0,		/* no time code synthesizer monitoring */
    (void *)&meinberg_fmt[2],	/* conversion configuration */
    "Meinberg GPS Extended",	/* Meinberg FAU GPS format */
    70,				/* string buffer */
    F_START|F_END|SYNC_START|SYNC_ONE,	/* paket START/END delimiter, START synchronisation, PPS ONE sampling  */
    0,				/* no private data (complete pakets) */
    { 0, 0},
    '\2',
    '\3',
    '\0'
  }
};

/*
 * cvt_meinberg
 *
 * convert simple type format
 */
static u_long
cvt_meinberg(buffer, size, format, clock)
  register char          *buffer;
  register int            size;
  register struct format *format;
  register clocktime_t   *clock;
{
  if (!Strok(buffer, format->fixed_string))
    {
      return CVT_NONE;
    }
  else
    {
      if (Stoi(&buffer[format->field_offsets[O_DAY].offset], &clock->day,
	       format->field_offsets[O_DAY].length) ||
	  Stoi(&buffer[format->field_offsets[O_MONTH].offset], &clock->month,
	       format->field_offsets[O_MONTH].length) ||
	  Stoi(&buffer[format->field_offsets[O_YEAR].offset], &clock->year,
	       format->field_offsets[O_YEAR].length) ||
	  Stoi(&buffer[format->field_offsets[O_HOUR].offset], &clock->hour,
	       format->field_offsets[O_HOUR].length) ||
	  Stoi(&buffer[format->field_offsets[O_MIN].offset], &clock->minute,
	       format->field_offsets[O_MIN].length) ||
	  Stoi(&buffer[format->field_offsets[O_SEC].offset], &clock->second,
	       format->field_offsets[O_SEC].length))
	{
	  return CVT_FAIL|CVT_BADFMT;
	}
      else
	{
	  char *f = &buffer[format->field_offsets[O_FLAGS].offset];
	  
	  clock->flags = 0;
	  clock->usecond = 0;

	  /*
	   * 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->utcoffset = 0; /* UTC */
	      clock->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 standard time code
	       * ROMS !!!!
	       */
	      switch (buffer[format->field_offsets[O_ZONE].offset])
		{
		case ' ':
		  clock->utcoffset = -1*60*60; /* MET */
		  break;
		  
		case 'S':
		  clock->utcoffset = -2*60*60; /* MED */
		  break;
		  
		default:
		  return CVT_FAIL|CVT_BADFMT;
		}
	    }
	  
	  /*
	   * gather status flags
	   */
	  if (buffer[format->field_offsets[O_ZONE].offset] == 'S')
	    clock->flags    |= PARSEB_DST;
	  
	  if (f[0] == '#')
	    clock->flags |= PARSEB_POWERUP;

	  if (f[1] == '*')
	    clock->flags |= PARSEB_NOSYNC;

	  if (f[3] == '!')
	    clock->flags |= PARSEB_ANNOUNCE;

	  /*
	   * oncoming leap second
	   * 'a' code not confirmed - earth is not
	   * expected to speed up
	   */
	  if (f[3] == 'A')
	    clock->flags |= PARSEB_LEAPADD;
	  
	  if (f[3] == 'a')
	    clock->flags |= PARSEB_LEAPDEL;

	  
	  if (format->flags & MBG_EXTENDED)
	    {
	      clock->flags |= PARSEB_S_LEAP;
	      clock->flags |= PARSEB_S_ANTENNA;
	      
	      /*
	       * DCF77 does not encode the direction -
	       * so we take the current default -
	       * earth slowing down
	       */
	      clock->flags &= ~PARSEB_LEAPDEL;

	      if (f[4] == 'A')
		clock->flags |= PARSEB_LEAPADD;

	      if (f[5] == 'R')
		clock->flags |= PARSEB_ALTERNATE;
	    }
	  return CVT_OK;
	}
    }
}

/*
 * cvt_mgps
 *
 * convert Meinberg GPS format
 */
static u_long
cvt_mgps(buffer, size, format, clock)
  register char          *buffer;
  register int            size;
  register struct format *format;
  register clocktime_t   *clock;
{
  if (!Strok(buffer, format->fixed_string))
    {
      return CVT_NONE;
    }
  else
    {
      if (Stoi(&buffer[format->field_offsets[O_DAY].offset], &clock->day,
	       format->field_offsets[O_DAY].length) ||
	  Stoi(&buffer[format->field_offsets[O_MONTH].offset], &clock->month,
	       format->field_offsets[O_MONTH].length) ||
	  Stoi(&buffer[format->field_offsets[O_YEAR].offset], &clock->year,
	       format->field_offsets[O_YEAR].length) ||
	  Stoi(&buffer[format->field_offsets[O_HOUR].offset], &clock->hour,
	       format->field_offsets[O_HOUR].length) ||
	  Stoi(&buffer[format->field_offsets[O_MIN].offset], &clock->minute,
	       format->field_offsets[O_MIN].length) ||
	  Stoi(&buffer[format->field_offsets[O_SEC].offset], &clock->second,
	       format->field_offsets[O_SEC].length))
	{
	  return CVT_FAIL|CVT_BADFMT;
	}
      else
	{
	  long h;
	  char *f = &buffer[format->field_offsets[O_FLAGS].offset];
	  
	  clock->flags = PARSEB_S_LEAP|PARSEB_S_POSITION;
	      
	  clock->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->utcoffset,
		       format->field_offsets[O_UTCMOFFSET].length))
		{
		  return CVT_FAIL|CVT_BADFMT;
		}

	      clock->utcoffset += TIMES60(h);
	      clock->utcoffset  = TIMES60(clock->utcoffset);

	      if (buffer[format->field_offsets[O_UTCSOFFSET].offset] != '-')
		{
		  clock->utcoffset = -clock->utcoffset;
		}
	    }
	  
	  /*
	   * gather status flags
	   */
	  if (buffer[format->field_offsets[O_ZONE].offset] == 'S')
	    clock->flags    |= PARSEB_DST;
	  
	  if (clock->utcoffset == 0)
	    clock->flags |= PARSEB_UTC;
	  
	  /*
	   * no sv's seen - no time & position
	   */
	  if (f[0] == '#')
	    clock->flags |= PARSEB_POWERUP;
	  
	  /*
	   * at least one sv seen - time (for last position)
	   */
	  if (f[1] == '*')
	    clock->flags |= PARSEB_NOSYNC;
	  else
	    if (!(clock->flags & PARSEB_POWERUP))
	      clock->flags |= PARSEB_POSITION;
	  
	  /*
	   * oncoming zone switch
	   */
	  if (f[3] == '!')
	    clock->flags |= PARSEB_ANNOUNCE;
	  
	  /*
	   * oncoming leap second
	   * 'a' code not confirmed - earth is not
	   * expected to speed up
	   */
	  if (f[4] == 'A')
	    clock->flags |= PARSEB_LEAPADD;
	  
	  if (f[4] == 'a')
	    clock->flags |= PARSEB_LEAPDEL;

	  /*
	   * f[5] == ' '
	   */
	  
	  /*
	   * this is the leap second
	   */
	  if (f[6] == 'L')
	    clock->flags |= PARSEB_LEAPSECOND;

	  return CVT_OK;
	}
    }
}

#else /* not (REFCLOCK && (PARSE || PARSEPPS) && CLOCK_MEINBERG) */
int clk_meinberg_bs;
#endif /* not (REFCLOCK && (PARSE || PARSEPPS) && CLOCK_MEINBERG) */

/*
 * History:
 *
 * clk_meinberg.c,v
 * Revision 3.23  1997/01/19 12:44:38  kardel
 * 3-5.88.1 reconcilation
 *
 * Revision 3.22  1996/12/01 16:04:14  kardel
 * freeze for 5.86.12.2 PARSE-Patch
 *
 * Revision 3.21  1996/11/24 20:09:44  kardel
 * RELEASE_5_86_12_2 reconcilation
 *
 * Revision 3.20  1996/10/05 13:30:20  kardel
 * general update
 *
 * Revision 3.19  1995/09/09  17:27:57  kardel
 * fixed UTC offset correction
 *
 * Revision 3.18  1995/03/26  16:10:25  kardel
 * datagrams updated
 *
 * Revision 3.17  1994/10/03  21:59:20  kardel
 * 3.4e cleanup/integration
 *
 * Revision 3.16  1994/10/03  10:04:05  kardel
 * 3.4e reconcilation
 *
 * Revision 3.15  1994/05/30  10:19:59  kardel
 * LONG cleanup
 *
 * Revision 3.14  1994/02/20  13:04:37  kardel
 * parse add/delete second support
 *
 * Revision 3.13  1994/02/02  17:45:21  kardel
 * rcs ids fixed
 *
 * Revision 3.11  1994/01/25  19:05:10  kardel
 * 94/01/23 reconcilation
 *
 * Revision 3.10  1994/01/23  17:21:54  kardel
 * 1994 reconcilation
 *
 * Revision 3.9  1993/10/30  09:44:38  kardel
 * conditional compilation flag cleanup
 *
 * Revision 3.8  1993/10/22  14:27:48  kardel
 * Oct. 22nd 1993 reconcilation
 *
 * Revision 3.7  1993/10/09  15:01:30  kardel
 * file structure unified
 *
 * Revision 3.6  1993/10/03  19:10:43  kardel
 * restructured I/O handling
 *
 * Revision 3.5  1993/09/27  21:08:04  kardel
 * utcoffset now in seconds
 *
 * Revision 3.4  1993/09/26  23:40:22  kardel
 * new parse driver logic
 *
 * Revision 3.3  1993/08/18  09:29:32  kardel
 * GPS format is somewhat variable length - variable length part holds position
 *
 * Revision 3.2  1993/07/09  11:37:16  kardel
 * Initial restructured version + GPS support
 *
 * Revision 3.1  1993/07/06  10:00:17  kardel
 * DCF77 driver goes generic...
 *
 */
