/* dcfdecode 0.3b by Hasw */
/* License: GPL */

#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/time.h>
#include <time.h>

#define PORT "/dev/ttyS1"

/* My Tobit Realtime DCF77 clock is using RTS to get 
 * power, another possibility is DTR (TIOCM_DTR) */

#define POWER TIOCM_RTS

/* The clock signal is using CTS, the DATA line is 
 * using RI, other possibilities are: 
 * TIOCM_DSR and TIOCM_CAR (DCD) */

#define CLOCK TIOCM_CTS
#define DATA TIOCM_RNG

/*
 * 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
 */

struct tm dcftime;
int fd;

int readport(void) {
	int status;

	if(ioctl(fd, TIOCMGET, &status) < 0) {
		perror("ioctl(): TIOCMGET error\n");
		exit(1);
	}
	return status;
}

void waitfor(int clk) {
	while((readport() & CLOCK) != clk) {
		usleep(200);
	}
}

int dcf2bcd(unsigned char dcf[60], int offset) {
	return(dcf[offset] + dcf[offset+1]*2 + dcf[offset+2]*4 + dcf[offset+3]*8);
}

int parity(unsigned char dcf[60], int offset, int length) {
	int x, count = 0;
	for(x = offset; x <= (offset+length); x++) {
		count += dcf[x] > 0 ? 1 : 0;
		if(count == 2) count = 0;
	}
	return count;
}
			
struct tm *decode(void) {
	unsigned char dcf[60];
	int x = 0;
	
	while(x <= 58) {
		waitfor(CLOCK);
		dcf[x] = ((readport() & DATA) == DATA) ? 1 : 0;
		printf("%d", dcf[x]);
		waitfor(0);
		x++;
	}
	
	// Bit 20 must be 1
	if(dcf[20] != 1) {
		return(NULL);
	}
	// Hour parity
	// if(parity(dcf, 29, 4) != dcf[35]) return NULL;
	// Min. parity
	// if(parity(dcf, 21, 5) != dcf[28]) return NULL;
	// Date parity
	// if(parity(dcf, 36, 20) != dcf[58]) return NULL;
	
	// Time zone
	if(!(dcf[17]^dcf[18])) return NULL;
	dcftime.tm_isdst = -1;
	if((dcf[17] == 0) && (dcf[18] == 1)) {
		dcftime.tm_isdst = 0;
	}
	if((dcf[17] == 1) && (dcf[18] == 0)) {
		dcftime.tm_isdst = 1;
	}
	
	// Time
	dcftime.tm_hour = dcf2bcd(dcf, 29) + dcf[33]*10 + dcf[34]*20;
	dcftime.tm_min = dcf2bcd(dcf, 21) + dcf[25]*10 + dcf[26]*20 + dcf[27]*40;
	dcftime.tm_sec = 0;

	// Date
	dcftime.tm_mday = dcf2bcd(dcf, 36) + dcf[40]*10 + dcf[41]*20;
	dcftime.tm_mon = dcf2bcd(dcf, 45) - 1 + dcf[50]*10;
	dcftime.tm_year = 100 + dcf[50] + dcf[51]*2 + dcf[52]*4;
	
	printf(" Decoding finished\n");
	return(&dcftime);
}

double diff(struct timeval *t1, struct timeval *t2) {
	int udiff, mdiff;
	
	udiff = (t2->tv_usec > t1->tv_usec) ? t2->tv_usec - t1->tv_usec : t1->tv_usec - t2->tv_usec;
	mdiff = t2->tv_sec - t1->tv_sec;
	
	return(mdiff + 0.000001 * udiff);
}

void waitforstart(void) {
	struct timeval old, new;
	double tdiff = 0;
	
	printf("Waiting for minute start");
	if(gettimeofday(&old, 0L) < 0) {
		perror("gettimeofday() fails");
		exit(1);
	}
	
	while((tdiff < 1.8) || (tdiff > 2.5)) {
		waitfor(CLOCK);
		if(gettimeofday(&new, 0L) < 0) {
			perror("gettimeofday() fails");
			exit(1);
		}
		tdiff = diff(&old, &new);
		printf(".");
		if(gettimeofday(&old, 0L) < 0) {
			perror("gettimeofday() fails");
			exit(1);
		}
		waitfor(0);
	}
	printf("Found\n");
}

int main(int argc, char *argv[]) {
	int status;
	struct timeval newtime;
	struct tm *dcftime = NULL;
	
	if((argc == 2) && (strcmp(argv[1], "--help") == 0)) {
		printf("dcfdecode 0.3b by Hasw\n\n");
		printf("dcfdecode -s -> Sets the system time to DCF77 clock.\n\n");
		exit(0);
	}
	
	fd = open(PORT, O_RDWR | O_NOCTTY | O_NDELAY);
	if(fd < 0) { 
		fprintf(stderr, "Can't open %s for R/W", PORT);
		exit(1);
	}
	
	// Enable
	status = readport();
	status |= POWER;
	if(ioctl(fd, TIOCMSET, &status) < 0) {
		perror("ioctl(): TIOCMSET fails, can't enable power");
		exit(1);
	}
	sleep(5);
	
	while(dcftime == NULL) {
		waitforstart();
		dcftime = decode();
	}
	
	newtime.tv_usec = 0;
	newtime.tv_sec = mktime(dcftime);

	if((argc == 2) && (strcmp(argv[1], "-s") == 0)) {
		printf("Setting system time: ");
		if(settimeofday(&newtime, 0L) < 0) {
			perror("settimeofday() fails");
			exit(1);
		}
	}
	
	printf("%02d:%02d:%02d %02d.%02d.%04d\n", dcftime->tm_hour, dcftime->tm_min, dcftime->tm_sec, \
											  dcftime->tm_mday, dcftime->tm_mon + 1, dcftime->tm_year + 1900);
	// Disable
	status &= ~POWER;
	if(ioctl(fd, TIOCMSET, &status) < 0) {
		perror("ioctl(): TIOCMSET fails, can't disable power");
		exit(1);
	} 
	
	close(fd);
	return(0);
}

