# coding: utf-8
#===============================================================================
# Jonathan Zéhnné 2018
# Utilisation du DS3231 pour reveiller l'alimentation de la carte Mezzanine version B0 et plus.
# http://jumpifnotzero.free.fr/?./electronic/embedded_RPI/index.php
# Cette librairie n'est utilise QUE pour utiliser la sortie IRQ du DS3231.
# Elle n'a aucun sens en dehors de ça.
#===============================================================================
# Le programme de démo (en bas) reset le flag.
# et programme le reveil de la carte Mezzanine pour la minute suivante.
# Il faut couper rapidement le PI !!!

import os
import smbus

"""
Note importante :
Cette petite bibliotheque n'est pas complète et ne prétent pas remplacer des bib plus
complètes qu'on trouve à droite à gauche sur internet. Cependant, je n'en ai pas trouvé
Pour regler les alarmes. Celle-ci le fait.
On ne peut pas regler l'heure, les fonction du Linux le font très bien.
On peut :
- lire l'heure
- regler l'alarme 1 (mais vous pouvez aisément modifier pour l'alarme 2 !)
- lire des registres dans tous les sens.
- lire ou écrire des flag (pour arreter les alarmes par exemple !)
"""



class ds3231():
	# adresse de base du DS3231
	_addr = 0x68
	
	# declaration des flags :
	FORMAT12H = [0x02, 6]
	CENTURY = [0x05, 7]
	A1M1 = 	[0x07, 7]
	A1M2 = 	[0x08, 7]
	A1M3 = 	[0x09, 7]
	A1FORMAT12H = [0x09, 6]
	A1M4 = 	[0x0a, 7]
	#A2M1 = 	[0x07, 7] ce registre n'existe pas.
	A2M2 = 	[0x0b, 7]
	A2M3 = 	[0x0c, 7]
	A2FORMAT12H = [0x0c, 6]
	A2M4 = 	[0x0d, 7]
	EOSC = 	[0x0e, 7]
	BBSQW=	[0x0e, 6]
	CONV = 	[0x0e, 5]
	RS2 =	[0x0e, 4]
	RS1 =	[0x0e, 3]
	INTCN =	[0x0e, 2]
	A2IE =	[0x0e, 1]
	A1IE =	[0x0e, 0]
	OSF=	[0x0f, 8]
	EN32kHz=[0x0f, 3]
	BSY = 	[0x0f, 2]
	A2F = 	[0x0f, 1]
	A1F = 	[0x0f, 0]
	
	# declaration des registres :
	REG_SECONDS = [0x00, 0x7f, 60]
	REG_MINUTES = [0x01, 0x7f, 60]
	REG_HOURS 	= [0x02, 0x3f, 24]
	REG_DAY 	= [0x03, 0x07, 8]		# jour dans la semaine
	REG_DATE 	= [0x04, 0x3f, 32]		# jour dans le mois
	REG_MONTH 	= [0x05, 0x1f, 13]
	REG_YEAR 	= [0x06, 0xff, 101]
	A1REG_SECONDS 	= [0x07, 0x7f, 60]
	A1REG_MINUTES 	= [0x08, 0x7f, 60]
	A1REG_HOURS		= [0x09, 0x3f, 24]
	A1REG_DAY		= [0x0a, 0x3f, 32]
	A2REG_SECONDS	= [0x07, 0x7f, 60]
	A2REG_MINUTES	= [0x08, 0x7f, 60]
	A2REG_HOURS		= [0x09, 0x3f, 24]
	A2REG_DAY		= [0x0a, 0x3f, 32]
	REG_CONTROL =	[0x0e, 0xff, 0xff]
	REG_STATUS	=	[0x0f, 0xff, 0xff]
	AGINGOFFSET =	[0x10, 0xff, 0xff]
	TEMPMSB  =	[0x11, 0xff, 0xff]
	TEMPLSB	 =	[0x12, 0xc0, 0xff]
	

	def __init__(self):
		os.system("sudo rmmod rtc-ds1307")			# on libère la lib linux
		self._bus = smbus.SMBus(1)

	# ecrit 1 octet sur l'I2C	
	def _write(self, register, data):
		self._bus.write_byte_data(self._addr, register, data)
	
	# lit 1 octet sur l'I2C
	def _read(self, register):
		return self._bus.read_byte_data(self._addr, register)

	# décode du format BCD les n derniers chiffres
	def BcdToInt(self, bcd, n=2):
	    return int(('%x' % bcd)[-n:])

	# encode au format BCD les n derniers chiffes
	def IntToBcd(self, x, n=2):
		return int(str(x)[-n:], 0x10)
		
	# fixe le flag à 0 ou 1
	def setflag(self, flag, value):
		masque = 1 << flag[1]			# masque du type 0b00010000
		val = self._read(flag[0])		# on lit le registre (pour ne pas modifier les autres flags)
		if value:
			val = val | masque 			# set : le masque est du type 0b00010000
		else:
			val = val & (255-masque)	# reset : le masque est du type 0b11101111
		self._write(flag[0], val)
	
	# lit un flag. Si valeur <> 0 alors True
	def getflag(self, flag):
		val = self._read(flag[0])		# lecture
		return val & (1 << flag[1])		# masquage


	def _get_regdate(self, reg):
		val = self._read(reg[0])
		val &= reg[1]
		return self.BcdToInt(val)
	
	# lit et retourne les registres jour, heure, minute, seconde
	def getdate(self):
		jour = self._get_regdate(self.REG_DATE) # lecture jour du mois, pas de la semaine
		heure = self._get_regdate(self.REG_HOURS)
		minute = self._get_regdate(self.REG_MINUTES)
		seconde = self._get_regdate(self.REG_SECONDS)
		return jour, heure, minute, seconde
		

	def _set_regalarme(self, data, reg):
		if data > reg[2]:		# limite maximum
			return
		data &= reg[1]		# masque
		self._write(reg[0], self.IntToBcd(data))

	# lance une alarme. date est au format [jour, heure, minute, seconde]
	def set_alarm1(self, jour, heure, minute, seconde):
		self._set_regalarme(jour, self.A1REG_DAY)	# utilisé en tant que date (jour du mois)
		self._set_regalarme(heure, self.A1REG_HOURS)
		self._set_regalarme(minute, self.A1REG_MINUTES)
		self._set_regalarme(seconde, self.A1REG_SECONDS)
		
		self.setflag(self.A1M1, False)
		self.setflag(self.A1M2, False)
		self.setflag(self.A1M3, False)
		self.setflag(self.A1M4, False)
		self.setflag(self.INTCN, True)	# broche INT/SQW en mode INT
		self.setflag(self.A1IE, True)	# autorise interruption alarme
		self.setflag(self.A1F, False)	# réarmement manuel obligatoire.
	
	def reset_alarm1(self):
		self.setflag(self.INTCN, True)
		self.setflag(self.A1IE, False)
		self.setflag(self.A1F, False)

	# lit la température		
	def get_temperature(self):
		msb = self._read(self._TEMPMSB)
		lsb = self._read(self._TEMPLSB)
		return msb + ((lsb >> 6) * 0.25)
	
	
if __name__ == "__main__":
	import time
	
	# code pour reseter l'alarme à faire OBLIGATOIREMENT au démarrage
	# suivant, pour éviter de vider la pile :
	rtc = ds3231()
	rtc.reset_alarm1()
	
	# code pour programmer l'alarme suivante :
	jour, heure, minute, seconde = rtc.getdate()
	minute += 2						# programme dans 2 minutes max.
	seconde = 0
	print("alarme à : %s %s %s" %(heure, minute, seconde))
	rtc.set_alarm1(jour, heure, minute, seconde)		# reglage d'une alarme à la seconde 0 dans 2 minute
	
	
			