
/* $Id: DCF77ISR.cpp 10 2011-05-11 16:26:05Z scjurgen@yahoo.com $

  A library to read the DCF77 time code:
  what you need to do:
  + hook the signal of the DCF77 board to an external interrupt pin (on arduino pin2 or pin3, activating interrupt 0 or 1)
  + define some parameters that suit your needs BEFORE including this file. 
     I make some use of defines so the code will be reduced singificantly without too many branches and memory print.
     The callbacks are also define statements in order to reduce possible stack usage for push/pop stuff
     Note that these are called in an interrupt (in the ISR version of this library).
  + this is not defined as a class to make setting up external interrupts easier, also I suppose it will not be replicated (i.e. using 2 DCF77 objects)
  
  It contains 
  - error redundancy check (transmission and logic)
  - and some heuristic correction
  - signal quality output (0..10) 0=ok, 10=very disturbed
  - signal present output (0,1)
  - drift correction for millis()
  - callback for realtime clock (or whatever you like to set) (called in ISR, beware of interrupt stuff) N.B, this are not actual callbacks but define macros (which could call a function)
  - updated time and date public variables (volatile)
  
  
  the signal quality output should be used as follows: if the values are bad, try to change the antenna, wait 2 seconds before changing again and observe values
  
  Written by Jurgen Schwietering
  http://www.schwietering.com/jayduino
  scjurgen@yahoo.com

	This library is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This library 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.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.

****/

#ifndef _DCF77ISR_h
#define _DCF77ISR_h

#include <wprogram.h>

/**
assign these values before including the file (or it will use standard settings)
	#define partialOk true // accept partial results
	#define MAXMOVINGAVERAGE 3 // in seconds [default 3]
	#define MINDRIFTCORRECTION 3 // maxdrift allowed before correcting the drift in msecs [default 3]
*/	
// specify your settings before including this file, this will result in removing unnecessary code

#ifndef DCF77_LED_REPLICAPIN 
	#define DCF77_LED_REPLICAPIN -1 // -1 to disable LED replica, note that an active LED can influence the DCF77 module in weired ways
#endif

#ifndef DCF77_PARTIALOK
	#define DCF77_PARTIALOK true // accept partial results
#endif

#ifndef DCF77_MAXMOVINGAVERAGE
	#define DCF77_MAXMOVINGAVERAGE 3 // in seconds, set to 0 to disable
#endif

#ifndef DCF77_MINDRIFTCORRECTION
	#define DCF77_MINDRIFTCORRECTION 3 // maxdrift allowed before correcting the drift in msecs
#endif

#ifndef DCF77_Pin
	#define DCF77_Pin 2
#endif	

#ifndef DCF77_INTNR
	#define DCF77_INTNR 0
#endif	

#ifndef DCF77_MINUTECALLBACK
	#define DCF77_MINUTECALLBACK(hour,min,day,month,year,weekday)) 
#endif

#ifndef DCF77_SECONDCALLBACK
	#define DCF77_SECONDCALLBACK(second)
#endif

#ifndef DCF77_MSECSVARIATION
	#define DCF77_MSECSVARIATION 35
#endif

namespace DCF77ISR {
	typedef enum {TZ_CET, TZ_CEST} TIMEZONE;

#if DCF77_MAXMOVINGAVERAGE >0	
	byte goodCntSec;
	byte badCntSec; // bad could be also more than 255, but at that point you have big problems, so we don't count more
	byte badCntMvgAvg[DCF77_MAXMOVINGAVERAGE];
	byte goodCntMvgAvg[DCF77_MAXMOVINGAVERAGE];
	byte avgCurPos;
	byte avgBad;
	byte avgGood;
#endif	

	int lastError=0;

	
	unsigned long skipDtAdd=0;
	bool skipped=false;
	unsigned long lastT;
	typedef enum {MA_NONE, MA_ASSIGNED, MA_SOME, MA_BAD}MINUTEACTION;
	MINUTEACTION assigned=MA_NONE;

	byte hour, min, day, month, weekday;
	unsigned short year;
	byte newSecond;
	
	#if DCF77_LED_REPLICAPIN!=-1
		int ISRActivePin=12;
	#endif

	int blinkPin=13;
	int seconds=0;
	int previousSecond=0;
	int minutes=0;
	int hours=0;
	
	long startMin=0;
	char bitAssigned[61]="";
	int assignCnt=0;
	int assignDoubleCnt=0;  // assigned 2 times, but same value since minuteleap
	int doubtAssigned=0;    // assigned 2 times, different value since minuteleap
	int goodCnt=0;          // good assignments since minuteleap
	int badCnt=0;           // bad interrupts since minuteleap
	int cntIsr=0;           // number of interrupts since minuteleap
	
	bool possiblyValidMinute=false;
	
	int driftPerMinute=0;
	static  unsigned char bcdMulti[8]={1,2,4,8,10,20,40,80};
	
	int bcdbitsToInt(char *data, int size)
	{
		unsigned char val=0;
		unsigned char pos=0;
		while(size--)
		{
			if (*data++=='1')
				val+=bcdMulti[pos];
			pos++;
		}
		return val;
	}

	// even parity
	bool checkParity(char *data, int size, char par)
	{
		unsigned char val=0;
		if (par=='1') par=1;
			else
		if (par=='0') par=0;
			else return false;
		while(size--)
		{
				switch(*data)
				{
					case '1': val^=1; break;
					case 'o': // double assigned
					case '0': break; // normal assigned 0
					default: return false; // not assigned or doubtly assigned
				}
				data++;
		}
		return val==par;
	}
	
	// this should be called 2 times per second, except on holidays, I mean the 59th second
	// there are interrupts if the signal is dirty
	
	void dcfInterrupt()
	{
		cntIsr++;
		unsigned long t,dt;
		int val;
		t=millis();
		val=digitalRead(DCF77_Pin);
		#if DCF77_LED_REPLICAPIN!=-1
			digitalWrite(ISRActivePin,val);
		#endif
		dt=t-lastT;
		lastT=t;
		if (dt<20) // skip spurious stuff
		{
			badCnt++;
			skipped=true;
			skipDtAdd+=dt;
			return;
		}
		
		if (skipped) // if we skipped previous correct value
		{
			dt+=skipDtAdd;
			skipped=false;
			skipDtAdd=0;
		}
	
		if ((dt>1720) && (dt < 1930)) 
		// this is the minute gap, the onbeat of this is the exact minute start hh:mm:00
		// the values collected are now validated
		{
			//Serial.println("M");
			//printf("good=%d bad=%d assigned=%d double=%d doubt=%d drift=%d\n", goodCnt, badCnt, assignCnt, assignDoubleCnt, doubtAssigned,driftPerMinute);
			startMin=t;
			//printf("!%s\n", bitAssigned);
			//printf("*** start min\n");
			assigned=MA_NONE;
			if (possiblyValidMinute) // our minute start 60 seconds ago was ok
			{
				#if DCF77_PARTIALOK	// assign if we accept partial results
					int assgnCnt=0;
					if (!checkParity(bitAssigned+29,6,bitAssigned[35]))
					{
						DebugPrint("parity hour error");
					}
					else
					{
						hour=bcdbitsToInt(bitAssigned+29,6);
						assgnCnt++;
					}
					if (!checkParity(bitAssigned+21,7,bitAssigned[28]))
					{
						DebugPrint("parity minute error");
						assigned = MA_SOME;
					} 
					else 
					{
						min=bcdbitsToInt(bitAssigned+21,7);
						assgnCnt++;
					}
					if (!checkParity(bitAssigned+36,58-36,bitAssigned[58]))
					{
						DebugPrint("parity date error");
					} 
					else // assign if we accept partial results
					{
						hour=bcdbitsToInt(bitAssigned+29,6);
						day=bcdbitsToInt(bitAssigned+36,6); 
						month=bcdbitsToInt(bitAssigned+45,5); 
						year=bcdbitsToInt(bitAssigned+50,8)+2000; 
						weekday=bcdbitsToInt(bitAssigned+42,3);
						assgnCnt++;
					}
					if (assgnCnt==3) // all assigned, all is good
					{
						assigned = MA_ASSIGNED;
						DCF77_MINUTECALLBACK(hour, min, day, month, year, weekday);
					}
					else
						assigned = MA_SOME;
				#else // assign only if all is good
					if (!checkParity(bitAssigned+29,6,bitAssigned[35]))
					{
						DebugPrint("parity hour error");
						assigned = MA_BAD;
						return;
					}
					if (!checkParity(bitAssigned+21,7,bitAssigned[28]))
					{
						DebugPrint("parity minute error");
						assigned = MA_BAD;
						return;
					}
					if (!checkParity(bitAssigned+36,58-36,bitAssigned[58]))
					{
						DebugPrint("parity date error");
						assigned = MA_BAD;
						return;
					}
					hour=bcdbitsToInt(bitAssigned+29,6);
					min=bcdbitsToInt(bitAssigned+21,7);
					day=bcdbitsToInt(bitAssigned+36,6); 
					month=bcdbitsToInt(bitAssigned+45,5); 
					year=bcdbitsToInt(bitAssigned+50,8)+2000; 
					weekday=bcdbitsToInt(bitAssigned+42,3);
					assigned = MA_ASSIGNED;
					DCF77_MINUTECALLBACK(hour, min, day, month, year, weekday);
				#endif
			}
			else
			{
				DebugPrint("skipping minute\n");
				assigned = MA_BAD;
			}
			possiblyValidMinute=true;
	
			// reset bits and stuff
		}
		else if (dt>2050) // big gap, something bad going on (signal lost i.e.)
		{
			possiblyValidMinute=false; 
		}
		else
		{
			lastError=999;
			// let's see which gap we are in
			long dtStart=t-startMin;
			if (dtStart > 60000) // no resync of minute
			{
				possiblyValidMinute=false;
				startMin+=60000;
				dtStart-=60000;
				DebugPrint("no minute gap... waiting for clean signal\n");
			}
			int second=(dtStart+100)/1000; // rough rough, correct seconds, because if negative drift we will repeat a second
			int msec=dtStart%1000;

			if (val==0) // we should be near the 0.1 or near the 0.2 sec mark
			{
				if ((msec >= 100-DCF77_MSECSVARIATION) && (msec<=100+DCF77_MSECSVARIATION)) // around 0.1 sec
				{
					lastError=msec-100;
					if (second < 60)
					{
						goodCnt++;
						assignCnt++;
						if (bitAssigned[second]!='-')
						{
							assignDoubleCnt++;
							bitAssigned[second]='o';
						}
						else
							bitAssigned[second]='0';
					}
				}
				else
				if ((msec >= 200-DCF77_MSECSVARIATION) && (msec<=200+DCF77_MSECSVARIATION)) // around 0.2 sec
				{
					lastError=msec-200;
					if (second < 60)
					{
						goodCnt++;
						assignCnt++;
						if (bitAssigned[second]!='-')
						{
							assignDoubleCnt++;
							doubtAssigned++;
							bitAssigned[second]='?';
						}
						else
							bitAssigned[second]='1';
					}
				}
				else
				{
					badCnt++;
					// spurious stuff
				}
				//printf("# ");
			}
			else
			if (val==1) // we should be near the 0
			{
				if (msec >950) // some rounding so we don't need odd calculations near 0.0
					msec-=1000;	// value pushed to -50..0
				if (msec < 30) // correct only if we are very near to 0
				{
					// correct startMin for drift
					if (msec < -DCF77_MINDRIFTCORRECTION)
					{
						startMin+=msec;
						driftPerMinute+=msec;
						DebugPrint(" - drift ");
					}
					if (msec > +DCF77_MINDRIFTCORRECTION)
					{
						startMin+=msec;
						driftPerMinute+=msec;
						DebugPrint(" + drift ");
					}
					lastError=msec;
					goodCnt++;
				}
				else
				{
					badCnt++;
					lastError=998;
				}
				DCF77_SECONDCALLBACK(second);
			}
		}
	}
	
	
	void setup()
	{
		pinMode(DCF77_Pin, INPUT);
		digitalWrite(DCF77_Pin, HIGH);
		attachInterrupt(DCF77_INTNR, dcfInterrupt, CHANGE);
	}
	
	void discard()
	{
		// detach interrupt
		detachInterrupt(0);
	}
	
	void ResetMinuteStuff()
	{
		goodCnt=0;
		badCnt=0;
		assignCnt=0;
		assignDoubleCnt=0;
		doubtAssigned=0;
		driftPerMinute=0;
		for (int i=0; i < sizeof(bitAssigned); i++)
		{
			bitAssigned[i]='-';
		}
		bitAssigned[60]=0;
		assigned=MA_NONE;
	}

}; // end of namespace

#endif //_DCF77ISR_h



