diff --git a/config-default.yaml b/config-default.yaml index 0c38eee..af6cc0d 100644 --- a/config-default.yaml +++ b/config-default.yaml @@ -1,222 +1,222 @@ ###################################################################### # PvMonit - By David Mercereau : http://david.mercereau.info/contact/ # Licence BEERWARE # Version 1.0 ###################################################################### ############################## # # NE MODIFIER PAS CE FICHIER ! # copier config-default.yaml dans config.yaml et modifier config.yaml (supprimer tout ce qui ne vous intéresse pas de modifier) # ############################## # Niveau d'affichage des messages printMessage: 0 # 0=0 5=debug printMessageLogfile: false # path or fase # URL data urlDataXml: http://pvmonit.chezvous/data-xml.php # Utiliser un domaine qui pointe vers l'ip, not localhost or 127.0.0.1 tmpFileDataXml: /tmp/PvMonit_data-xml.php.tmp dir: bin: /opt/PvMonit/bin/ bin_enabled: /opt/PvMonit/bin-enabled/ lcd: /opt/PvMonit/lcd/ domo: /opt/PvMonit/domo/ data: ppv_total: false # production total des régulateurs (utilisé si vous avez plusieurs régulateur) conso_calc: false # Calculé avec : la puissance instantané (P du BMV) - ppv_total ppv_total at true for use this cache: dir: /tmp/PvMonit_cache # in tmpfs file_prefix: time: 60 # in second # Methode de récupération des données VE DIRECT (par USB - vedirect OU serial par Arduino) vedirect: by: usb # usb OR arduino usb: # Binaire de vedirect.py USB bin: /usr/bin/sudo /usr/bin/python /opt/PvMonit/bin/vedirect.py arduino: # Fichier de data YAML enregistré par le script vedirectOnArduinoRemote.py cohérence avec config-vedirectOnArduinoRemote.yaml data_file: /tmp/PvMonit_getSerialArduino.data.yaml data_file_expir: 300 # Expiration serial: port: /dev/ttyAMA0 # ttyAMA0 pour le serial via GPIO, ttyUSB0 pour le port USB... timeout: 0 # Débit du serial 0 qui va vers l'Arduino (doit être cohérent entre les 2, diffère selon la distance de câble) # Débit Longueur (m) # 2400 60 # 4 800 30 # 9 600 15 # 19 200 7,6 # https://fr.wikipedia.org/wiki/RS-232#Limites baudRate: 4800 whileSleep: 0.001 whileSleepAfterStop: 3 # donnée récolté (voir la doc victron sur le protocole VE.Direct) data_ok: mppt: - CS - PPV - V - ERR - I - VPV - H19 - H20 - H21 - H22 - H23 bmv: - V - VS - VM - DM - I - T - P - CE - SOC - TTG - AR - H1 - H2 - H3 - H4 - H5 - H6 - H7 - H8 - H9 - H10 - H11 - H12 - H13 - h14 - H15 - H16 - H17 - H18 phoenix: - P - CS - MODE - AC_OUT_V - AC_OUT_I - WARN # Numéro de série (champs SER#) en correspondance avec des nom buvables deviceCorrespondance: HQXXXXXXXX: MpttGarage HQYYYYYYYY: MpttToit # Plafont de consommation en W impossible à dépasser (techniquement, sinon c'est une erreur de sonde) consoPlafond: 1500 # Tension standard du réseau (110V ou 230V) tensionNorme: 230 ### Export vers Emoncms emoncms: # Test la connexion internet testInternetHost: emoncms.org testInternetPort: 80 # emoncms URL du post.json & API key urlInputJsonPost: https://emoncms.org/input/post.json apiKey: XXXXXXXXXXXXXXXXXXXXXXXX # Répertoire de collecte de données dataCollecte: /tmp/PvMonit_collecteData # Dossier ou ranger les erreurs dataCollecteError: /tmp/PvMonit_collecteDataError # Attente entre deux requête OK sleepOk: 1 # Attente entre deux requête échoué sleepNok: 3 # Fichier de lock pour éviter les doublons lockFile: /tmp/PvMonit_sendToEmoncms.lock ### Page Web : www: # Délais de raffraichissement de la page (en seconde) 300000 = 5 minutes refreshTime: 300000 # Max de la jauge voltage batterie (en V) vbatMax: 30 # Max de la jauge puissance PV (en W) PpvMax: 500 # max Jauge puissance PV (en W) # Max de la jauge puissance PV total (si plusieurs régulateur) (en W) PpvtMax: 500 # max Jauge puissance PV (en W) dataPrimaire: - V - PPV - ERR - CS - SOC - AR - P - TTG - MODE - AC_OUT_V - AC_OUT_I - WARN - PPVT - CONSO dataPrimaireSmallScreen: - SOC - P - PPVT menu: -
  • EmonCMS (historique)
  • -
  • PvMonit projet
  • -
  • Soutenir l'auteur
  • # Ecran LCD (avec script PvMonit/lcd lcd: rafraichissement: 0.1 # en seconde pour les boutons dataUpdate: 45 # en seconde pour le rafraichissement des données onTimer: 60 # en seconde le temps que l'écran reste allumé si par défaut éteind estCeLaNuitTimer: 600 # détection de la nuit tout les x secondes dataPrint: - SOC - P - PPVT onAt: 8 # heure d'allumage du LCD offAt: 21 # heure d'extinction du LCD # Domotique (avec script PvMonit/domo) domo: dataCheckTime: 30 i2c: adress: 0x04 device: 1 heartbeatTime: 1 # Fréquence du bâtement de coeur (en seconde) fileExpir: 40 # Age en seconde après laquel le fichier n'est plus considéré comme ok fileCheckTime: 10 # Fréquece de la vérificatio du XML (en seconde) fileCheckError: 3 # nombre d'erreurs sur le xml avant de sortir du mode automatique / stopper le heartbeat # temps avant erreur = $checkTime * $checkError fileDownloadRetry: 3 valueUse: - SOC - P - PPVT - CS #- CONSO relay: - dataFreq: 3 # Délais de récupération des données depuis l'arduino (en secondes) - nb: 5 + dataFreq: 30 # Délais de récupération des données depuis l'arduino (en secondes) + nb: 5 # 14 max ! scriptDir: /opt/PvMonit/domo/relay.script.d/ - scriptExecInterval: 5 # Interval d'execution des script de relais + scriptExecInterval: 40 # Interval d'execution des script de relais spoolTimeout: 10 # timeout sur un ordre dans la file d'attente - relayCorrespondance: # A commencer par 0 + relayCorrespondance: # A commencer par 0 0: Pompe de relevage 1: Box Internet 2: Téléphone 3: Disque dur externe 4: Chargeur outil électroportatif # Sonde courant ! #5: Chauffe eau #6: Batterie de vélo életrique #7: Surpresseur diff --git a/domo/INSTALL.md b/domo/INSTALL.md new file mode 100644 index 0000000..1be7aaf --- /dev/null +++ b/domo/INSTALL.md @@ -0,0 +1,3 @@ +pip3 install smbus2 wget + +14 relay max pour cette version diff --git a/domo/domo.py b/domo/domo.py index d7bd013..f1b85fa 100644 --- a/domo/domo.py +++ b/domo/domo.py @@ -1,177 +1,208 @@ import yaml import time from smbus2 import SMBus import os from lxml import etree from urllib.request import urlopen import wget from past.builtins import execfile import re import time ## for debug : import pprint with open('../config-default.yaml') as f1: config = yaml.load(f1, Loader=yaml.FullLoader) with open('../config.yaml') as f2: config_perso = yaml.load(f2, Loader=yaml.FullLoader) def configGet(key1, key2=None, key3=None, key4=None): if key4 != None: try: return config_perso[key1][key2][key3][key4] except: return config[key1][key2][key3][key4] elif key3 != None: try: return config_perso[key1][key2][key3] except: return config[key1][key2][key3] elif key2 != None: try: return config_perso[key1][key2] except: return config[key1][key2] else: try: return config_perso[key1] except: return config[key1] # Cherche a savoir si le MPPT est en Absorption ou en Float (en fin de charge) def MpptAbsOrFlo(cs): patternAbsFlo = re.compile(r"Absorption|Float") if patternAbsFlo.match(cs): return True; else : return False; # Function for log def logMsg(level, msg): if level <= configGet('printMessage') : print(time.strftime ('%m/%d/%Y %H:%M') ," - ",msg) return -1 # i2c write def writeNumber(value): bus.write_byte(configGet('domo', 'i2c', 'adress'), value) return -1 def download_data(): # téléchargement des données logMsg(3, 'Download data') with open(configGet('tmpFileDataXml'), 'wb') as tmpxml: tmpxml.write(urlopen(configGet('urlDataXml')).read()) return time.time() logMsg(1, 'Lancement du script domo.py') heartLastCheck=0 relayDataLastCheck=0 scriptExecLast=0 xmlLastCheck=0 xmlfileCheckError=0 xmlData = {} spoolAction=None spoolActionSend=False + +bus=SMBus(configGet('domo', 'i2c', 'device')); + logMsg(5, "Début de la boucle") while 1: # XML data recup t=int(time.time()) if xmlLastCheck+configGet('domo', 'dataCheckTime') < t: if not os.path.isfile(configGet('tmpFileDataXml')): logMsg(1, "Le fichier XML de donnée " + configGet('tmpFileDataXml') + " n'existe pas.") download_data() xmlfileCheckError=xmlfileCheckError+1 elif os.path.getmtime(configGet('tmpFileDataXml'))+configGet('domo', 'fileExpir') < t : logMsg(1, "Le fichier data est périmé !") download_data() xmlfileCheckError=xmlfileCheckError+1 else: logMsg(3, "Récupération des données XML (état de l'installation solaire") xmlfileCheckError=0 tree = etree.parse(configGet('tmpFileDataXml')) datacount=0 for datas in tree.xpath("/devices/device/datas/data"): if datas.get("id") in configGet('domo', 'valueUse'): datacount = datacount + 1 for data in datas.getchildren(): if data.tag == "value": xmlData[datas.get("id")]=data.text logMsg(5, pprint.pprint(xmlData)) xmlLastCheck=t # S'il y a trop d'erreur : if xmlfileCheckError >= configGet('domo', 'fileCheckError'): logMsg(1, 'Trop d\'erreur, on patiente 10 secondes') time.sleep(10) xmlfileCheckError=0 else: + ######################### # Le heartbeat + ######################### if heartLastCheck+configGet('domo', 'heartbeatTime') < t: - #DEVSMBSTOP bus=SMBus(configGet('domo', 'i2c', 'device')); - #DEVSMBSTOP writeNumber(int(ord("H"))) + writeNumber(int(ord("H"))) logMsg(5, 'Heardbeat envoyé') heartLastCheck=t + ######################### # Data Relay + ######################### if relayDataLastCheck+configGet('domo', 'relay', 'dataFreq') < t: logMsg(4, 'On récupère les données des relay (via i2c arduino)') # A FAIRE # Simulation monsieur l'arbitre #// Etat : #// - 0 : off force #// - 1 : off auto #// - 2 : on auto #// - 3 : on force #// Mode #// - 0 : Null #// - 1 : Off #// - 2 : Auto #// - 3 : On - relayEtat = [1, 1, 0, 3, 1] - relayMod = [2, 2, 1, 3, 2] - #logMsg(5, pprint.pprint(relayEtat)) + + time.sleep(0.3) + # Requête i2c pour demande de data (état et mode des relay) + i2cResults = bus.read_i2c_block_data(configGet('domo', 'i2c', 'adress'), int(ord('D')), configGet('domo', 'relay', 'nb')*2+1) + # Remise à 0 + relayEtat=[] + relayMod=[] + x=0 + dataOrdre=1 + logEtat="" + logMod="" + for i2cDatas in i2cResults: + # Si les données sont présentes + if i2cDatas != 255: + if i2cDatas == 29: # C'est le sépartateur : https://fr.wikibooks.org/wiki/Les_ASCII_de_0_%C3%A0_127/La_table_ASCII + dataOrdre=2 + x=0 + elif dataOrdre == 1: + relayEtat.insert(x,i2cDatas) + logEtat=logEtat+","+str(i2cDatas) + else: + relayMod.insert(x,i2cDatas) + logMod=logMod+","+str(i2cDatas) + x=x+1 + logMsg(5, "DATA reçu : Etat " + logEtat) + logMsg(5, "DATA reçu : Mod " + logMod) relayDataLastCheck=t + ######################### # On joue les scripts + ######################### if scriptExecLast+configGet('domo', 'relay', 'scriptExecInterval') < t: logMsg(4, 'On joue les script des relay en mode auto') relayId=0 print(relayEtat[0]) for mod in relayMod: # Si la file d'attente des actions est vide on que le relay est en automatique if spoolAction == None and mod == 2: scriptFile=configGet('dir','domo') + configGet('domo','relay', 'scriptDir') + "/" + str(relayId) + ".py" logMsg(4, 'Lecture du script ' + scriptFile) if not os.path.isfile(scriptFile): logMsg(2, 'Erreur, pas de script ' + scriptFile) else: returnEtat=None execfile(scriptFile) if returnEtat != None and returnEtat != relayEtat[relayId]: logMsg(2, 'Un changement d\'état vers ' + str(returnEtat) + ' de est demandé pour le relay ' + str(relayId)) spoolAction=[relayId, returnEtat, t] else: logMsg(4, 'Pas de changement d\'état demandé pour le relay ' + str(relayId)) relayId=relayId+1 scriptExecLast=t # Traitement de la file d'attente if spoolAction != None and spoolActionSend == False: logMsg(3, 'Traitement du spool, envoi de l\'ordre') logMsg(5, pprint.pprint(spoolAction)) spoolActionSend=t # A FAIRE # Lancer l'ordre sur l'aduino # Vérifier que l'arduino a bien exécuté l'ordre if spoolAction != None and spoolActionSend != False: # Est-ce que le relay est dans l'état attendu par l'ordre if relayEtat[spoolAction[0]] == spoolAction[1]: logMsg(5, 'Le relay ' + spoolAction[0] + ' n\'est pas encore dans l\'état attendu ' + spoolAction[1]) #if spoolAction != None and spoolActionSend+configGet('domo', 'spoolTimeout') < t: # spoolAction= # !! Faut faire un truc vide le spoolAction quand c'est fait... hummmm ... # Pour être gentil avec le système time.sleep(0.01) diff --git a/firmware/arduinoRelayi2c.ino b/firmware/arduinoRelayi2c.ino new file mode 100644 index 0000000..817d7c0 --- /dev/null +++ b/firmware/arduinoRelayi2c.ino @@ -0,0 +1,356 @@ + +#include // bibliothèque de rjbatista +#include + +// Documentation afficheur + bouton +// http://electroniqueamateur.blogspot.com/2017/07/afficheur-8-chiffres-8-leds-8-boutons.html + +// Documentation module relais : +// http://wiki.mchobby.be/index.php?title=Module_Relais + +#define DEBUG 1 +//#define MASTERSIMU 1 + +//Slave Address for the Communication +#define SLAVE_ADDRESS 0x04 + +// Config : + +// Afficheur TM1638 lib : +// DIO 3, CLK 2 , STB 4: +TM1638 afficheur(3, 2, 4); + +// Relay number +byte relayNb=8; +// Relay pin number +byte relayPin[] = {5, 6, 7, 8, 9, 10, 11, 12}; +// ####### Relay +// Etat : +// - 0 : off force +// - 1 : off auto +// - 2 : on auto +// - 3 : on force +byte relayEtat[] = {0, 0, 0, 0, 0, 0, 0, 0}; +// Mode +// - 0 : Null +// - 1 : Off +// - 2 : Auto +// - 3 : On +byte relayMod[] = {0, 0, 0, 0, 0, 0, 0, 0}; + +// Police d'afficheur +const byte MES_FONTS[] = { + 0b00000000, // 0 null + 0b00001000, // 1 Down for off force + 0b01000000, // 2 Middle for on/off auto + 0b00000001 // 3 UP for on force +}; + + +String RelayOrderCode = "RO"; +bool MasterPresent = false; +bool RelayChange = true; +int HeartbeatCheckFreq=40; +int HeartbeatCheckCount=0; +int RelayChangeFreq=120; +int RelayChangeCount=0; + +int changeEtat(int id) { + switch (relayMod[id]) { + // Null + case 0: + afficheur.setLED(TM1638_COLOR_NONE, id); + digitalWrite(relayPin[id],HIGH); + relayEtat[id]=0; + break; + // Off + case 1: + afficheur.setLED(TM1638_COLOR_NONE, id); + digitalWrite(relayPin[id],HIGH); + relayEtat[id]=0; + break; + // Auto + case 2: + afficheur.setLED(TM1638_COLOR_NONE, id); + digitalWrite(relayPin[id],HIGH); + relayEtat[id]=1; + break; + // On + case 3: + afficheur.setLED(TM1638_COLOR_RED, id); + digitalWrite(relayPin[id],LOW); + relayEtat[id]=3; + break; + } + #ifdef DEBUG + Serial.println((String)"Etat pass to "+relayEtat[id]+" for "+id+" relay"); + #endif +} + + + + + +int changeMod(int id, int newMod) { + switch (newMod) { + case 0: + afficheur.setDisplayDigit(0, id, false, MES_FONTS); + break; + case 1: + afficheur.setDisplayDigit(1, id, false, MES_FONTS); + break; + case 2: + afficheur.setDisplayDigit(2, id, false, MES_FONTS); + break; + case 3: + afficheur.setDisplayDigit(3, id, false, MES_FONTS); + break; + } + #ifdef DEBUG + Serial.println((String)"Mod change to "+newMod+" for "+id+" relay"); + #endif + relayMod[id]=newMod; +} + +int i2cReceiveData[50]; + +// callback for received data +void receiveData(int byteCount) { + int i = 0; + #ifdef DEBUG + Serial.print("Donnée Reçu :"); + #endif + while (Wire.available()) { + Serial.print(" "); + i2cReceiveData[i] = Wire.read(); + #ifdef DEBUG + Serial.print(i2cReceiveData[i]); + #endif + i++; + } + #ifdef DEBUG + Serial.println(); + #endif + /* + #ifdef DEBUG + Serial.print("Conséquence : "); + Serial.println(i2cReceiveData[0]); + #endif + */ + // Heartbeat + if(i2cReceiveData[0]=='H') { + #ifdef DEBUG + Serial.println("Hearbeat de nouveau reçu !"); + #endif + HeartbeatCheckCount=0; + // Si on récupère le master : On remet les relay en auto off (1) s'il sont à 0 + if (MasterPresent == false) { + for (int i = 0; i < relayNb; i++) { + if (relayMod[i] == 0) { + changeMod(i, 2); + } + } + RelayChange = true; + MasterPresent = true; + } + } + if(i2cReceiveData[0]== 79) { // Réception d'un O (pour Ordre) + #ifdef DEBUG + Serial.println("Ordre du Pi de changement d'état pour le relay " + String(i2cReceiveData[1]) + " à " + String(i2cReceiveData[2])); + #endif + relayEtat[i2cReceiveData[1]] = i2cReceiveData[2]; + // Passer l'état à Auto On + if (i2cReceiveData[2] == 2) { + afficheur.setLED(TM1638_COLOR_RED, i2cReceiveData[1]); + digitalWrite(relayPin[i2cReceiveData[1]],LOW); + // Passer l'état à Auto Off + } else if (i2cReceiveData[2] == 1) { + afficheur.setLED(TM1638_COLOR_NONE, i2cReceiveData[1]); + digitalWrite(relayPin[i2cReceiveData[1]],HIGH); + } else { + Serial.println("Erreur, ordre incorrect"); + } + } +} + +// callback for sending data +void sendData() { + // ------------------ Data Send + if(i2cReceiveData[0] = "D") { + Serial.println("Data send : "); + // Relay etat + for (byte i = 0; i < relayNb; i = i + 1) { + Wire.write(relayEtat[i]); + Serial.print(relayEtat[i]); + } + Serial.print(" - "); + Wire.write(29); // Séparrateur de groupe : https://fr.wikibooks.org/wiki/Les_ASCII_de_0_%C3%A0_127/La_table_ASCII + // Relay mode + for (byte i = 0; i < relayNb; i = i + 1) { + Wire.write(relayMod[i]); + Serial.print(relayMod[i]); + } + Serial.println(); + } +} + +// Setup : +void setup() { + #ifdef DEBUG + // Mise en route du serial + Serial.begin(9600); + Serial.println("Debug Actif sur le serial"); + #endif + // Déclaration des PIN pour les relays + for (byte i = 0; i < relayNb; i = i + 1) { + pinMode(relayPin[i], OUTPUT); + digitalWrite(relayPin[i],HIGH); + } + + Wire.begin(SLAVE_ADDRESS); + // define callbacks for i2c communication + Wire.onReceive(receiveData); + Wire.onRequest(sendData); + + // Pour le debug : + #ifdef DEBUG + #ifdef MASTERSIMU + for (int i = 0; i < relayNb; i++) { + if (relayEtat[i] != 3 || relayEtat[i] != 0) { + changeMod(i, 2); + } + } + #endif + #endif + +} + + +int relayOrdreId; +int relayOrdreEtat; + + +// Loop : +void loop() { + + // pour le debug + #ifdef DEBUG + #ifdef MASTERSIMU + MasterPresent = true; + HeartbeatCheckCount=0; + #endif + #endif + + // Lecture / réception données + HeartbeatCheckCount=HeartbeatCheckCount+1; + if ( Serial.available() ) { + + String lu = Serial.readStringUntil('\n'); + + // Ordre sur les relay + if(lu.substring(0,2) == RelayOrderCode) { + if (relayNb <= 10) { + relayOrdreId=lu.substring(3,4).toInt(); + relayOrdreEtat=lu.substring(5,6).toInt(); + } else { + relayOrdreId=lu.substring(3,5).toInt(); + relayOrdreEtat=lu.substring(6,7).toInt(); + } + #ifdef DEBUG + // Exemple d'ordre : RO:2=1 (le relay 2 passe à l'état 1) + Serial.print("Reception d'un ordre pour les relay : "); + Serial.print(relayOrdreId); + Serial.print(" à passer en état "); + Serial.print(relayOrdreEtat); + #endif + switch (relayOrdreEtat) { + // Auto Off + case 1: + afficheur.setLED(TM1638_COLOR_NONE, relayOrdreId); + digitalWrite(relayPin[relayOrdreId],HIGH); + relayEtat[relayOrdreId]=1; + break; + // Auto On + case 2: + afficheur.setLED(TM1638_COLOR_RED, relayOrdreId); + digitalWrite(relayPin[relayOrdreId],LOW); + relayEtat[relayOrdreId]=2; + break; + } + } + + + + + } + + if (HeartbeatCheckCount > HeartbeatCheckFreq && MasterPresent == true) { + #ifdef DEBUG + Serial.println("Hearbeat non reçu, l'arduino est débranché"); + #endif + HeartbeatCheckCount=0; + MasterPresent = false; + // Si on pert le master : On éteind les relays qui était en auto + for (int i = 0; i < relayNb; i++) { + if (relayMod[i] == 2) { + changeMod(i, 0); + } + } + RelayChange = true; + } + + // Bouton Action + byte etatBoutons; + etatBoutons = afficheur.getButtons(); + for (int i = 0; i < relayNb; i++) { + if (bitRead(etatBoutons, i)) { + #ifdef DEBUG + Serial.print("Action button : "); + Serial.println(i); + #endif + switch (relayMod[i]) { + case 0: + changeMod(i, 1); + break; + case 1: + if (MasterPresent == true) { + changeMod(i, 2); + } else { + changeMod(i, 3); + } + break; + case 2: + changeMod(i, 3); + break; + case 3: + if (MasterPresent == true) { + changeMod(i, 1); + } else { + changeMod(i, 0); + } + break; + } + RelayChange = true; + delay(500); // Evite le rebond des bouttons + } + + } + + // Changement d'etat des relay + RelayChangeCount=RelayChangeCount+1; + if (RelayChangeCount > RelayChangeFreq) { + if (RelayChange == true) { + RelayChangeCount=0; + #ifdef DEBUG + Serial.println((String)"Change relay etat"); + #endif + for (int i = 0; i < relayNb; i++) { + changeEtat(i); + } + RelayChange = false; + } else { + RelayChangeCount=0; + } + } + delay(50); +}