/*
 * /src/NTP/REPOSITORY/v3/kernel/testdcf.c,v 3.1 1993/07/06 10:01:46 kardel Exp
 *  
 * testdcf.c,v 3.1 1993/07/06 10:01:46 kardel Exp
 *
 * simple DCF77 100/200ms pulse test program (via 50Baud serial line)
 *
 * Copyright (c) 1993
 * Frank Kardel, Friedrich-Alexander Universitaet Erlangen-Nuernberg
 *                                    
 * 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.
 *
 * This program may not be sold or used for profit without prior
 * written consent of the author.
 */

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stropts.h>
#include <sys/time.h>

/*
 * state flags
 */
#define DCFB_ANNOUNCE           0x0001 /* switch time zone warning (DST switch) */
#define DCFB_POWERUP            0x0002 /* no synchronisation */
#define DCFB_NOSYNC             0x0004 /* timecode currently not confirmed */
#define DCFB_DST                0x0008 /* DST in effect */
#define DCFB_UTC		0x0010 /* UTC time */
#define DCFB_LEAP		0x0020 /* LEAP warning (1 hour prior to occurence) */
#define DCFB_ALTERNATE		0x0040 /* alternate antenna used */

#define DCFB_S_LEAP		0x0100 /* supports LEAP */
#define DCFB_S_LOCATION		0x0200 /* supports LOCATION information */
#define DCFB_S_PPS     		0x0400 /* supports PPS time stamping */

#define DCFB_TIMECODE		0x1000 /* valid time code sample */
#define DCFB_PPS		0x2000 /* valid PPS sample */

#define DCF_TCINFO		(DCFB_ANNOUNCE|DCFB_POWERUP|DCFB_NOSYNC|DCFB_DST|\
				 DCFB_UTC|DCFB_LEAP|DCFB_ALTERNATE|DCFB_S_LEAP|\
				 DCFB_S_LOCATION|DCFB_TIMECODE)

#define DCF_POWERUP(x)          ((x) & DCFB_POWERUP)
#define DCF_NOSYNC(x)           (((x) & (DCFB_POWERUP|DCFB_NOSYNC)) == DCFB_NOSYNC)
#define DCF_SYNC(x)             (((x) & (DCFB_POWERUP|DCFB_NOSYNC)) == 0)
#define DCF_ANNOUNCE(x)         ((x) & DCFB_ANNOUNCE)
#define DCF_DST(x)              ((x) & DCFB_DST)
#define DCF_UTC(x)		((x) & DCFB_UTC)
#define DCF_LEAP(x)		(DCF_SYNC(x) && ((x) & DCFB_LEAP))
#define DCF_ALTERNATE(x)	((x) & DCFB_ALTERNATE)
#define DCF_S_LEAP(x)		((x) & DCFB_S_LEAP)
#define DCF_S_LOCATION(x)	((x) & DCFB_S_LOCATION)
#define DCF_S_PPS(x)		((x) & DCFB_S_PPS)
#define DCF_TIMECODE(x)		((x) & DCFB_TIMECODE)
#define DCF_PPS(x)		((x) & DCFB_PPS)

struct clocktime		/* clock time broken up from time code */
{
  long wday;
  long day;
  long month;
  long year;
  long hour;
  long minute;
  long second;
  long usecond;
  long utcoffset;	/* in minutes */
  long flags;		/* current clock status */
};

typedef struct clocktime clocktime_t;

#define TIMES10(_X_) (((_X_) << 3) + ((_X_) << 1))

/*
 * parser related return/error codes
 */
#define CVT_MASK	0x0000000F /* conversion exit code */
#define   CVT_NONE	0x00000001 /* format not applicable */
#define   CVT_FAIL	0x00000002 /* conversion failed - error code returned */
#define   CVT_OK	0x00000004 /* conversion succeeded */
#define CVT_BADFMT	0x00000010 /* general format error - (unparsable) */
#define CVT_BADDATE     0x00000020 /* date field incorrect */
#define CVT_BADTIME	0x00000040 /* time field incorrect */

/*
 * DCF77 raw time code
 *
 * From "Zur Zeit", Physikalisch-Technische Bundesanstalt (PTB), Braunschweig
 * und Berlin, Maerz 1989
 *
 * Timecode transmission:
 * AM:
 *	time marks are send every second except for the second before the
 *	next minute mark
 *	time marks consist of a reduction of transmitter power to 25%
 *	of the nominal level
 *	the falling edge is the time indication (on time)
 *	time marks of a 100ms duration constitute a logical 0
 *	time marks of a 200ms duration constitute a logical 1
 * FM:
 *	see the spec. (basically a (non-)inverted psuedo random phase shift)
 *
 * Encoding:
 * Second	Contents
 * 0  - 10	AM: free, FM: 0
 * 11 - 14	free
 * 15		R     - alternate antenna
 * 16		A1    - expect zone change (1 hour before)
 * 17 - 18	Z1,Z2 - time zone
 *		 0  0 illegal
 *		 0  1 MEZ  (MET)
 *		 1  0 MESZ (MED, MET DST)
 *		 1  1 illegal
 * 19		A2    - expect leap insertion/deletion (1 hour before)
 * 20		S     - start of time code (1)
 * 21 - 24	M1    - BCD (lsb first) Minutes
 * 25 - 27	M10   - BCD (lsb first) 10 Minutes
 * 28		P1    - Minute Parity (even)
 * 29 - 32	H1    - BCD (lsb first) Hours
 * 33 - 34      H10   - BCD (lsb first) 10 Hours
 * 35		P2    - Hour Parity (even)
 * 36 - 39	D1    - BCD (lsb first) Days
 * 40 - 41	D10   - BCD (lsb first) 10 Days
 * 42 - 44	DW    - BCD (lsb first) day of week (1: Monday -> 7: Sunday)
 * 45 - 49	MO    - BCD (lsb first) Month
 * 50           MO0   - 10 Months
 * 51 - 53	Y1    - BCD (lsb first) Years
 * 54 - 57	Y10   - BCD (lsb first) 10 Years
 * 58 		P3    - Date Parity (even)
 * 59		      - usually missing (minute indication), except for leap insertion
 */

static struct rawdcfcode 
{
  char offset;			/* start bit */
} rawdcfcode[] =
{
  {  0 }, { 15 }, { 16 }, { 17 }, { 19 }, { 20 }, { 21 }, { 25 }, { 28 }, { 29 },
  { 33 }, { 35 }, { 36 }, { 40 }, { 42 }, { 45 }, { 49 }, { 50 }, { 54 }, { 58 }, { 59 }
};

#define DCF_M	0
#define DCF_R	1
#define DCF_A1	2
#define DCF_Z	3
#define DCF_A2	4
#define DCF_S	5
#define DCF_M1	6
#define DCF_M10	7
#define DCF_P1	8
#define DCF_H1	9
#define DCF_H10	10
#define DCF_P2	11
#define DCF_D1	12
#define DCF_D10	13
#define DCF_DW	14
#define DCF_MO	15
#define DCF_MO0	16
#define DCF_Y1	17
#define DCF_Y10	18
#define DCF_P3	19

static struct partab
{
  char offset;			/* start bit of parity field */
} partab[] =
{
  { 21 }, { 29 }, { 36 }, { 59 }
};

#define DCF_P_P1	0
#define DCF_P_P2	1
#define DCF_P_P3	2

#define DCF_Z_MET 0x2
#define DCF_Z_MED 0x1

static unsigned long ext_bf(buf, idx)
  register char *buf;
  register int   idx;
{
  register unsigned long sum = 0;
  register int i, first;

  first = rawdcfcode[idx].offset;
  
  for (i = rawdcfcode[idx+1].offset - 1; i >= first; i--)
    {
      sum <<= 1;
      sum |= (buf[i] != '-');
    }
  return sum;
}

static unsigned pcheck(buf, idx)
  register char *buf;
  register int   idx;
{
  register int i,last;
  register unsigned psum = 1;

  last = partab[idx+1].offset;

  for (i = partab[idx].offset; i < last; i++)
    psum ^= (buf[i] != '-');

  return psum;
}

static unsigned long convert_rawdcf(buffer, size, clock)
  register unsigned char   *buffer;
  register int              size;
  register clocktime_t     *clock;
{
  register unsigned char *s = buffer;
  register int i;

  if (size < 57)
    {
      printf("%-30s", "*** INCOMPLETE");
      return CVT_NONE;
    }
  
  /*
   * check Minute, Start and Parity bits
   */
  if ((ext_bf(buffer, DCF_M) == 0) &&
      (ext_bf(buffer, DCF_S) == 1) &&
      pcheck(buffer, DCF_P_P1) &&
      pcheck(buffer, DCF_P_P2) &&
      pcheck(buffer, DCF_P_P3))
    {
      /*
       * buffer OK
       */

      clock->flags  = DCFB_S_LOCATION|DCFB_S_LEAP;
      clock->usecond= 0;
      clock->second = 0;
      clock->minute = ext_bf(buffer, DCF_M10);
      clock->minute = TIMES10(clock->minute) + ext_bf(buffer, DCF_M1);
      clock->hour   = ext_bf(buffer, DCF_H10);
      clock->hour   = TIMES10(clock->hour) + ext_bf(buffer, DCF_H1);
      clock->day    = ext_bf(buffer, DCF_D10);
      clock->day    = TIMES10(clock->day) + ext_bf(buffer, DCF_D1);
      clock->month  = ext_bf(buffer, DCF_MO0);
      clock->month  = TIMES10(clock->month) + ext_bf(buffer, DCF_MO);
      clock->year   = ext_bf(buffer, DCF_Y10);
      clock->year   = TIMES10(clock->year) + ext_bf(buffer, DCF_Y1);
      clock->wday   = ext_bf(buffer, DCF_DW);

      switch (ext_bf(buffer, DCF_Z))
	{
	case DCF_Z_MET:
	  clock->utcoffset = -60;
	  break;

	case DCF_Z_MED:
	  clock->flags     |= DCFB_DST;
	  clock->utcoffset  = -120;
	  break;

	default:
          printf("%-30s", "*** BAD TIME ZONE");
	  return CVT_FAIL|CVT_BADFMT;
	}

      if (ext_bf(buffer, DCF_A1))
	clock->flags |= DCFB_ANNOUNCE;

      if (ext_bf(buffer, DCF_A2))
	clock->flags |= DCFB_LEAP;

      if (ext_bf(buffer, DCF_R))
	clock->flags |= DCFB_ALTERNATE;

      return CVT_OK;
    }
  else
    {
      /*
       * bad format - not for us
       */
      printf("%-30s", "*** BAD FORMAT (invalid/parity)");
      return CVT_FAIL|CVT_BADFMT;
    }
}

char type(c)
unsigned char c;
{
  c ^= 0xFF;
  return (c > 0xF);
}

static char *wday[8] =
{
  "??",
  "Mo",
  "Tu",
  "We",
  "Th",
  "Fr",
  "Sa",
  "Su"
};

static char pat[] = "-\\|/";

#define LINES (24-2)	/* error lines after which the two headlines are repeated */

main(argc, argv)
  int argc;
  char **argv;
{
  printf("  DCF77 monitor - Copyright 1993, Frank Kardel\n\n");

  if ((argc != 2) && (argc != 3))
    {
      fprintf(stderr, "usage: %s [-f|-t|-ft|-tf] <device>\n", argv[0]);
      exit(1);
    }
  else
    {
      unsigned char c;
      char *file;
      int fd;
      int offset = 15;
      int trace = 0;
      int errs = LINES+1;

      /*
       * SIMPLE(!) argument "parser"
       */
      if (argc == 3)
	{
	  if (strcmp(argv[1], "-f") == 0)
	    offset = 0;
	  if (strcmp(argv[1], "-t") == 0)
	    trace = 1;
	  if ((strcmp(argv[1], "-ft") == 0) ||
	      (strcmp(argv[1], "-tf") == 0))
	    {
              offset = 0;
	      trace = 1;
	    }
	  file = argv[2];
	}
      else
	{
	  file = argv[1];
	}

      fd = open(file, O_RDONLY);
      if (fd == -1)
	{
	  perror(file);
	  exit(1);
	}
      else
	{
	  int i, on = TIOCM_RTS;
          struct timeval t, tt, tlast;
	  char buf[61];
	  clocktime_t clock;
	  struct termios term;
	  int rtc = CVT_NONE;

	  if (ioctl(fd, TCGETS, &term) == -1)
	    {
	      perror("TIOCGET");
	      exit(1);
	    }

	  bzero(term.c_cc, sizeof(term.c_cc));
	  term.c_cc[VMIN] = 1;
	  term.c_cflag = B50|CS8|CREAD|CLOCAL;
	  term.c_iflag = 0;
	  term.c_oflag = 0;
	  term.c_lflag = 0;

	  if (ioctl(fd, TCSETS, &term) == -1)
	    {
	      perror("TCSETS");
	      exit(1);
	    }

	  while (ioctl(fd, I_POP, 0) == 0)
	    ;
	  if (ioctl(fd, TIOCMBIC, &on) == -1)
	    {
	      perror("TIOCM_RTS");
	      exit (1);
	    }
	  clock.hour = 0;
	  clock.minute = 0;
	  clock.day = 0;
	  clock.wday = 0;
	  clock.month = 0;
	  clock.year = 0;
	  clock.flags = 0;
	  buf[60] = '\0';
	  for ( i = 0; i < 60; i++)
	    buf[i] = '.';

	  gettimeofday(&tlast, 0L);
	  i = 0;
	  while (read(fd, &c, 1) == 1)
	    {
	      gettimeofday(&t, 0L);
	      tt = t;
              t.tv_sec -= tlast.tv_sec;
	      t.tv_usec -= tlast.tv_usec;
	      if (t.tv_usec < 0)
		{
		  t.tv_usec += 1000000;
		  t.tv_sec  -= 1;
		}

	      if (errs > LINES)
		{
		  printf("  %s", &"PTB private....RADMLSMin....PHour..PMDay..DayMonthYear....P\n"[offset]);
		  printf("  %s", &"---------------RADMLS1248124P124812P1248121241248112481248P\n"[offset]);
		  errs = 0;
		}

	      if (t.tv_sec > 1 ||
		  t.tv_sec == 1 &&
		  t.tv_usec > 500000)
		{
	          printf("%c %.*s ", pat[i % (sizeof(pat)-1)], 59 - offset, &buf[offset]);

                  if ((rtc = convert_rawdcf(buf, i, &clock)) != CVT_OK)
		    {
		      printf("\n");
		      clock.hour = 0;
		      clock.minute = 0;
		      clock.day = 0;
		      clock.wday = 0;
		      clock.month = 0;
		      clock.year = 0;
		      clock.flags = 0;
		      errs++;
		    }

		  if (((c^0xFF)+1) & (c^0xFF))
		   buf[0] = '?';
		  else
		   buf[0] = type(c) ? '#' : '-';

		  for ( i = 1; i < 60; i++)
		    buf[i] = '.';

		  i = 0;
		}
	      else
		{
		  if (((c^0xFF)+1) & (c^0xFF))
		   buf[i] = '?';
		  else
		   buf[i] = type(c) ? '#' : '-';

	          printf("%c %.*s ", pat[i % (sizeof(pat)-1)], 59 - offset, &buf[offset]);
		}

	      if (rtc == CVT_OK)
		{
		  printf("%s, %2d:%02d:%02d, %d.%02d.%02d, <%s%s%s%s>",
			wday[clock.wday],
			clock.hour, clock.minute, i, clock.day, clock.month,
			clock.year,
			(clock.flags & DCFB_ALTERNATE) ? "R" : "_",
			(clock.flags & DCFB_ANNOUNCE) ? "A" : "_",
			(clock.flags & DCFB_DST) ? "D" : "_",
			(clock.flags & DCFB_LEAP) ? "" : "_"
			);
		  if (trace && (i == 0))
		    {
		      printf("\n");
		      errs++;
		    }
	        }

	      printf("\r");

	      if (i < 60)
		{
		  i++;
	        }

	      tlast = tt;

	      fflush(stdout);
	    }
	  close(fd);
	}
    }
  return 0;
}
