/* Ce programme a été élaboré à partir du code de M. Olivier LEBRUN réutilisé en grande partie pour réaliser un encodeur OWL CM180 (Micro+) /* ainsi qu'à partir du code (+ carte électronique à relier au compteur) de M. Pascal CARDON pour la partie téléinfo /* Onlinux a fourni des trames du OWL CM180 me permettant de faire les algo d'encodage (il a développer un code de décodage des trames) /* Je remercie les auteurs. Ci-dessous les liens vers leur site internet. /*======================================================================================================================= ONLINUX : Decode and parse the Oregon Scientific V3 radio data transmitted by OWL CM180 Energy sensor (433.92MHz) References : http://blog.onlinux.fr https://github.com/onlinux/OWL-CMR180 http://domotique.web2diz.net/?p=11 http://www.domotique-info.fr/2014/05/recuperer-teleinformation-arduino/ http://connectingstuff.net/blog/encodage-protocoles-oregon-scientific-sur-arduino/ /*======================================================================================================================= /* * connectingStuff, Oregon Scientific v2.1 Emitter * http://connectingstuff.net/blog/encodage-protocoles-oregon-scientific-sur-arduino/ * * Copyright (C) 2013 olivier.lebrun@gmail.com * /*======================================================================================================================= my_teleinfo (c) 2012-2013 by Script name : my_teleinfo http://www.domotique-info.fr/2014/05/recuperer-teleinformation-arduino/ VERSIONS HISTORY Version 1.00 30/11/2013 + Original version Version 1.10 03/05/2015 + Manu : Small ajustment to variabilise the PIN numbers for Transmiter and Teleinfo See ref here : http://domotique.web2diz.net/?p=11# montage électronique conforme à http://www.domotique-info.fr/2014/05/recuperer-teleinformation-arduino/ ======================================================================================================================*/ #include // PIN SIETTINGS // const byte TELEINFO_PIN = 8; //Connexion TELEINFO const byte TX_PIN = 3; //emetteur 433 MHZ // PIN SIETTINGS // const unsigned long TIME = 488; const unsigned long TWOTIME = TIME*2; #define SEND_HIGH() digitalWrite(TX_PIN, HIGH) #define SEND_LOW() digitalWrite(TX_PIN, LOW) byte OregonMessageBuffer[13]; // OWL180 //********************************************************* SoftwareSerial* mySerial; char HHPHC; int ISOUSC; // intensité souscrite int IINST; // intensité instantanée en A int IMAX; // intensité maxi en A int PAPP; // puissance apparente en VA unsigned long HCHC; // compteur Heures Creuses en W unsigned long HCHP; // compteur Heures Pleines en W String PTEC; // Régime actuel : HPJB, HCJB, HPJW, HCJW, HPJR, HCJR String ADCO; // adresse compteur String OPTARIF; // option tarifaire String MOTDETAT; // status word String pgmVersion; // TeleInfo program version boolean ethernetIsOK; boolean teleInfoReceived; char chksum(char *buff, uint8_t len); boolean handleBuffer(char *bufferTeleinfo, int sequenceNumnber); char version[17] = "TeleInfo V 1.00"; unsigned long PAPP_arrondi; // PAPP*497/500/16 arrondi unsigned long chksum_CM180; unsigned long long HCP; //********** debug // char buffer[100];// à virer *************** //********************************************************************** /** * \brief Send logical "0" over RF * \details azero bit be represented by an off-to-on transition * \ of the RF signal at the middle of a clock period. * \ Remenber, the Oregon v2.1 protocol add an inverted bit first */ inline void sendZero(void) { SEND_LOW(); delayMicroseconds(TIME); SEND_HIGH(); delayMicroseconds(TIME); } /** * \brief Send logical "1" over RF * \details a one bit be represented by an on-to-off transition * \ of the RF signal at the middle of a clock period. * \ Remenber, the Oregon v2.1 protocol add an inverted bit first */ inline void sendOne(void) { SEND_HIGH(); delayMicroseconds(TIME); SEND_LOW(); delayMicroseconds(TIME); } /** * \brief Send a buffer over RF * \param data Data to send * \param size size of data to send */ void sendData(byte *data, byte size) { for(byte i = 0; i < size; ++i) { (bitRead(data[i], 0)) ? sendOne() : sendZero(); (bitRead(data[i], 1)) ? sendOne() : sendZero(); (bitRead(data[i], 2)) ? sendOne() : sendZero(); (bitRead(data[i], 3)) ? sendOne() : sendZero(); (bitRead(data[i], 4)) ? sendOne() : sendZero(); (bitRead(data[i], 5)) ? sendOne() : sendZero(); (bitRead(data[i], 6)) ? sendOne() : sendZero(); (bitRead(data[i], 7)) ? sendOne() : sendZero(); } } /** * \brief Send an Oregon message * \param data The Oregon message */ void sendOregon(byte *data, byte size) { sendPreamble(); sendData(data,size); sendPostamble(); } /** * \brief Send preamble * \details The preamble consists of 10 X "1" bits (minimum) */ inline void sendPreamble(void) { for(byte i = 0; i < 10; ++i) //OWL CM180 { sendOne(); } } /** * \brief Send postamble */ inline void sendPostamble(void) { for(byte i = 0; i <4 ; ++i) //OWL CM180 { sendZero() ; } SEND_LOW(); delayMicroseconds(TIME); } //================================================================================================================= // Basic constructor //================================================================================================================= void TeleInfo(String version) { // Serial.begin(1200,SERIAL_7E1); mySerial = new SoftwareSerial(TELEINFO_PIN, 9); // RX, TX mySerial->begin(1200); pgmVersion = version; // variables initializations ADCO = "270622224349"; OPTARIF = "----"; ISOUSC = 0; HCHC = 0L; // compteur Heures Creuses en W HCHP = 0L; // compteur Heures Pleines en W PTEC = "----"; // Régime actuel : HPJB, HCJB, HPJW, HCJW, HPJR, HCJR HHPHC = '-'; IINST = 0; // intensité instantanée en A IMAX = 0; // intensité maxi en A PAPP = 0; // puissance apparente en VA MOTDETAT = "------"; } //================================================================================================================= // Capture des trames de Teleinfo //================================================================================================================= boolean readTeleInfo(boolean ethernetIsConnected) { #define startFrame 0x02 #define endFrame 0x03 #define startLine 0x0A #define endLine 0x0D #define maxFrameLen 280 int comptChar=0; // variable de comptage des caractères reçus char charIn=0; // variable de mémorisation du caractère courant en réception char bufferTeleinfo[21] = ""; int bufferLen = 0; int checkSum; ethernetIsOK = ethernetIsConnected; int sequenceNumnber= 0; // number of information group //--- wait for starting frame character while (charIn != startFrame) { // "Start Text" STX (002 h) is the beginning of the frame if (mySerial->available()) charIn = mySerial->read()& 0x7F; // Serial.read() vide buffer au fur et à mesure } // fin while (tant que) pas caractère 0x02 // while (charIn != endFrame and comptChar<=maxFrameLen) while (charIn != endFrame) { // tant que des octets sont disponibles en lecture : on lit les caractères // if (Serial.available()) if (mySerial->available()) { charIn = mySerial->read()& 0x7F; // incrémente le compteur de caractère reçus comptChar++; if (charIn == startLine) bufferLen = 0; bufferTeleinfo[bufferLen] = charIn; // on utilise une limite max pour éviter String trop long en cas erreur réception // ajoute le caractère reçu au String pour les N premiers caractères if (charIn == endLine) { checkSum = bufferTeleinfo[bufferLen -1]; if (chksum(bufferTeleinfo, bufferLen) == checkSum) {// we clear the 1st character strncpy(&bufferTeleinfo[0], &bufferTeleinfo[1], bufferLen -3); bufferTeleinfo[bufferLen -3] = 0x00; sequenceNumnber++; if (! handleBuffer(bufferTeleinfo, sequenceNumnber)) { Serial.println(F("Sequence error ...")); return false; } } else { Serial.println(F("Checksum error ...")); return false; } } else bufferLen++; } if (comptChar > maxFrameLen) { Serial.println(F("Overflow error ...")); return false; } } return true; } //================================================================================================================= // Frame parsing //================================================================================================================= //void handleBuffer(char *bufferTeleinfo, uint8_t len) boolean handleBuffer(char *bufferTeleinfo, int sequenceNumnber) { // create a pointer to the first char after the space char* resultString = strchr(bufferTeleinfo,' ') + 1; boolean sequenceIsOK; switch(sequenceNumnber) { case 1: if (sequenceIsOK = bufferTeleinfo[0]=='A') ADCO = String(resultString); break; case 2: if (sequenceIsOK = bufferTeleinfo[0]=='O') OPTARIF = String(resultString); break; case 3: if (sequenceIsOK = bufferTeleinfo[1]=='S') ISOUSC = atol(resultString); break; case 4: if (sequenceIsOK = bufferTeleinfo[3]=='C') HCHC = atol(resultString); break; case 5: if (sequenceIsOK = bufferTeleinfo[3]=='P') HCHP = atol(resultString); break; case 6: if (sequenceIsOK = bufferTeleinfo[1]=='T') PTEC = String(resultString); break; case 7: if (sequenceIsOK = bufferTeleinfo[1]=='I') IINST =atol(resultString); break; case 8: if (sequenceIsOK = bufferTeleinfo[1]=='M') IMAX =atol(resultString); break; case 9: if (sequenceIsOK = bufferTeleinfo[1]=='A') PAPP =atol(resultString); break; case 10: if (sequenceIsOK = bufferTeleinfo[1]=='H') HHPHC = resultString[0]; break; case 11: if (sequenceIsOK = bufferTeleinfo[1]=='O') MOTDETAT = String(resultString); break; } #ifdef debug if(!sequenceIsOK) { Serial.print(F("Out of sequence ...")); Serial.println(bufferTeleinfo); } #endif return sequenceIsOK; } //================================================================================================================= // Calculates teleinfo Checksum //================================================================================================================= char chksum(char *buff, uint8_t len) { int i; char sum = 0; for (i=1; i<(len-2); i++) sum = sum + buff[i]; sum = (sum & 0x3F) + 0x20; return(sum); } //================================================================================================================= // This function displays the TeleInfo Internal counters // It's usefull for debug purpose //================================================================================================================= void displayTeleInfo() { /* ADCO 270622224349 B OPTARIF HC.. < ISOUSC 30 9 HCHC 014460852 $ HCHP 012506372 - PTEC HP.. IINST 002 Y IMAX 035 G PAPP 00520 ( HHPHC C . MOTDETAT 000000 B */ Serial.print(F(" ")); Serial.println(); Serial.print(F("ADCO ")); Serial.println(ADCO); Serial.print(F("OPTARIF ")); Serial.println(OPTARIF); Serial.print(F("ISOUSC ")); Serial.println(ISOUSC); Serial.print(F("HCHC ")); Serial.println(HCHC); Serial.print(F("HCHP ")); Serial.println(HCHP); Serial.print(F("PTEC ")); Serial.println(PTEC); Serial.print(F("IINST ")); Serial.println(IINST); Serial.print(F("IMAX ")); Serial.println(IMAX); Serial.print(F("PAPP ")); Serial.println(PAPP); Serial.print(F("HHPHC ")); Serial.println(HHPHC); Serial.print(F("MOTDETAT ")); Serial.println(MOTDETAT); } void encodeur_OWL_CM180() { if (PTEC.substring(1,2)=="C") { HCP=(HCHC*223666LL)/1000LL; } else { HCP=(HCHP*223666LL)/1000LL; } OregonMessageBuffer[0] =0x62; // imposé OregonMessageBuffer[1] =0x80; // GH G= non décodé par RFXCOLM, H = Count //OregonMessageBuffer[2] =0x3C; // IJ ID compteur : "L IJ 2" soit (L & 1110 )*16*16*16+I*16*16+J*16+2 // si heure creuse compteur 3D, si HP compteur 3C if (PTEC.substring(1,2)=="C") { OregonMessageBuffer[2] =0x3D; // Serial.print(F("HEURE CREUSE 0x3D")); //débug ******************************* } else { OregonMessageBuffer[2] =0x3C; } //OregonMessageBuffer[3] =0xE1; // KL K sert pour puissance instantanée, L sert pour identifiant compteur PAPP_arrondi=long(long(PAPP)*497/500/16); // améliore un peu la précision de la puissance apparente encodée (le CM180 transmet la PAPP * 497/500/16) if ((float(PAPP)*497/500/16-PAPP_arrondi)>0.5) { ++PAPP_arrondi; } OregonMessageBuffer[3]=(PAPP_arrondi&0x0F)<<4; //OregonMessageBuffer[4] =0x00; // MN puissance instantée = (P MN K)*16 soit : (P*16*16*16 + M*16*16 +N*16+K)*16*500/497. attention RFXCOM corrige cette valeur en multipliant par 16 puis 500/497. OregonMessageBuffer[4]=(PAPP_arrondi>>4)&0xFF; //OregonMessageBuffer[5] =0xCD; // OP Total conso : YZ WX UV ST QR O : Y*16^10 + Z*16^9..R*16 + O OregonMessageBuffer[5] =((PAPP_arrondi>>12)&0X0F)+((HCP&0x0F)<<4); //OregonMessageBuffer[6] =0x97; // QR sert total conso OregonMessageBuffer[6] =(HCP>>4)&0xFF; //OregonMessageBuffer[7] =0xCE; // ST sert total conso OregonMessageBuffer[7] =(HCP>>12)&0xFF; // ST sert total conso //OregonMessageBuffer[8] =0x12; // UV sert total conso OregonMessageBuffer[8] =(HCP>>20)&0xFF; // UV sert total conso //OregonMessageBuffer[9] =0x00; // WX sert total conso OregonMessageBuffer[9] =(HCP>>28)&0xFF; //OregonMessageBuffer[10] =0x00; //YZ sert total conso OregonMessageBuffer[10] =(HCP>>36)&0xFF; chksum_CM180= 0; for (byte i=0; i<11; i++) { chksum_CM180 += long(OregonMessageBuffer[i]&0x0F) + long(OregonMessageBuffer[i]>>4) ; } chksum_CM180 -=2; // = =b*16^2 + d*16+ a ou [b d a] //OregonMessageBuffer[11] =0xD0; //ab sert CHECKSUM somme(nibbles ci-dessuus)=b*16^2 + d*16+ a + 2 OregonMessageBuffer[11] =((chksum_CM180&0x0F)<<4) + ((chksum_CM180>>8)&0x0F); //OregonMessageBuffer[12] =0xF6; //cd d sert checksum, a non décodé par RFXCOM OregonMessageBuffer[12] =(int(chksum_CM180>>4)&0x0F); //C = 0 mais inutilisé } //************************************************************************************ void setup() { Serial.begin(115200); // pour la console, enlever les barres de commentaires ci dessous pour displayTeleInfo() TeleInfo(version); } void loop() { teleInfoReceived=readTeleInfo(true); if (teleInfoReceived) { encodeur_OWL_CM180(); mySerial->end(); //NECESSAIRE !! arrête les interruptions de softwareserial (lecture du port téléinfo) pour émission des trames OWL sendOregon(OregonMessageBuffer, sizeof(OregonMessageBuffer)); // Send the Message over RF mySerial->begin(1200); //NECESSAIRE !! relance les interuptions pour la lecture du port téléinfo displayTeleInfo(); // console pour voir les trames téléinfo // ajout d'un delais de 12s apres chaque trame envoyés pour éviter d'envoyer // en permanence des informations à domoticz et de créer des interférances delay(12000); } }