/** * DTZ Logger * Version 3.91 * 09.05.2022 P. Rebesky * author Creator P.Rebesky * Copyright (©): 2020-2022 by Peter Rebesky * This code can use in private cases only. Every business or companies using of this codes or codes parts is required an approval of us (me) * Every private using can exchange some parts of code or modify some code-lines. This code is allowed change for private use only. * This software is basicly owned by Peter Rebesky and any comercial using is forbidden without approval of us (me). **/ #include //ESP8266 Core WiFi Library #include //ESP8266 Core WiFi Library #include #include //Local WebServer #include "configDTZ.h" #include "strings.h" #include // LittleFS is declared #include #include #include // https://github.com/tzapu/WiFiManager #include #include //ADC_MODE(ADC_VCC); #define BoardTaster 0 #define LED 14 #define Receive485 12 #define ON 1 #define OFF 0 #define FLASHTIME 300 #define MESZ 0 // summer-time = 1 else winter-time = 0 #define MaxBuffer 700 // maximum buffer for received bytes #define polynom 0x1021 // x16 + x12 + x5 + 1 0x1021 #define TimeOf2021 1609459260 #define Watt 0x1b #define Watt/h 0x1e #define var 0x1d #define var/h 0x20 #define Ampere 0x21 #define Voltage 0x23 #define Herz 0x2c unsigned int year = 0; byte month = 0; int day = 0; int years=0; byte hour = 0; byte minute =0; byte second =0; // int showProtocol = 1; int ShowProtocolTime = 0; uint32_t timestamp = 0; uint32_t timeDummy = 0; uint32_t SecOfYear =0; uint32_t ProtocolCounter =0; byte oldsecond = 0; uint32_t previousMillis = 0; uint32_t secondMillis =0; String act_time; String act_date; String TransmitResult; String MeterType = NOT_DETECT; const long utcOffsetInSeconds = 3600; uint32_t FromGridS = 0; uint32_t FromGridA = 0; uint32_t FromGridB = 0; uint32_t IntoGrid = 0; int U_L1 =0; int U_L2 =0; int U_L3 =0; int I_L1 =0; int I_L2 =0; int I_L3 =0; int Frequency = 0; int Consumtion = 0; float DispValue; int dtznumber = 0; int ValueScale; uint32_t meterID0=0; uint32_t meterID1=0; uint32_t meterID2=0; uint16_t crc_ccitt = 0xffff; uint16_t crc = 0xffff; int x=0; int errorCounter = 0; int pointer = 0; int singleValue = 0; uint32_t value0 = 0; uint32_t value1 = 0; uint32_t value2 = 0; uint32_t valueD = 0; char dummy0 = 0; char dummy1 = 0; int flashCounter = 0; int zaehler =0; int pointerEOT = MaxBuffer-2; int sendProtocols = 0; bool sendingOK = false; char receivedByte[2]; char extractValue[14]; char dataDTZ[MaxBuffer]; WiFiManager wm; // global wm instance //WiFiManagerParameter custom_field; // global param ( for non blocking w params ) WiFiServer server(80); ESP8266WiFiMulti WiFiMulti; // Define NTP Client to get time WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds); configDTZ cDTZ; //*********************** use WIFI manager by Tzapu https://github.com/tzapu/WiFiManager ************************************ bool setupWIFI() { WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP pinMode(LED, OUTPUT); // set as output digitalWrite(LED,ON); // show setup-mode on LED delay(1000); // Serial.println("\n Starting"); std::vector menu = {"wifi","info","sep","restart","exit"}; wm.setMenu(menu); // set dark theme wm.setClass("invert"); //set static ip // wm.setSTAStaticIPConfig(IPAddress(192,168,0,10),IPAddress(192,168,0,1),IPAddress(255,255,255,0),IPAddress(8,8,8,8)); // set static ip,gw,sn,dns // wm.setShowStaticFields(true); // force show static ip fields // wm.setShowDnsFields(true); // force show dns field always wm.setConfigPortalTimeout(500); // auto close configportal after n seconds checkButton(); // if pressed then reset WIFI config to start new change ssid bool res; res = wm.autoConnect("StromLog_Setup"); // anonymous ap, first parameter is name of access point if(!res) { return false; } else { return true; } } //************************* press button for set a new SSID ********************************** void checkButton(){ // check for button press if ( digitalRead(BoardTaster) == LOW ) { // poor mans debounce/press-hold delay(50); if( digitalRead(BoardTaster) == LOW ){ digitalWrite(LED,OFF); // LED OFF delay(5000); // reset delay hold if( digitalRead(BoardTaster) == LOW ){ digitalWrite(LED,ON); // show button pressed on LED wm.resetSettings(); delay(100); wm.resetSettings(); for(int i=0;i<50;i++){ if(i%2)digitalWrite(LED,ON); else digitalWrite(LED,OFF); delay(50); } ESP.restart(); } } } } //*************************** function by WIFI manager ******************************** String getParam(String name){ //read parameter from server, for customhmtl input String value; if(wm.server->hasArg(name)) { value = wm.server->arg(name); } return value; } //*************************** function by WIFI manager ******************************** void saveParamCallback(){ Serial.println("[CALLBACK] saveParamCallback fired"); Serial.println("PARAM customfieldid = " + getParam("customfieldid")); } //*************************** function for get time & date as timestamp ******************************** bool syncron_NTP_Time() { timeClient.begin(); if(timeClient.update()) { timeDummy = timeClient.getEpochTime(); if(timeDummy !=0){ timestamp = timeDummy + (MESZ * 3600); // 3600 means add 1 hour return true; } } return false; } //*************************** create a string from values for data and time ******************************** void getTimeDate2String(){ act_time = ""; act_date = ""; if(hour < 10) act_time += "0"; // if value < 10 then show a zero before act_time += String(hour); //combine string for time act_time += ":"; if(minute < 10) act_time += "0"; act_time += String(minute); act_time += ":"; if(second < 10) act_time += "0"; act_time += String(second); if (day < 10) act_date += "0"; // if value < 10 then show a zero before act_date += String(day); //combine string for time act_date += "."; if(month < 10) act_date += "0"; act_date += String(month); act_date += "."; if(year < 10) act_date += "0"; act_date += String(year); } //**************************** intern clock ******************************* bool internClock(uint32_t currentMillis) { if (currentMillis < secondMillis) secondMillis = 0; // handle if currentMillis are overflows, max 32 bits if (currentMillis - secondMillis >=1000){ secondMillis = currentMillis; second++; timestamp++; if (second >=60){ second=0, minute++;} if (minute >=60){ minute=0, hour++;} if (hour >=24){ hour=0; day++;} return true; } else return false; } //***************************** show DTZ-Data on WEB-Browser ****************************** void check4Client() { // Check if a client has connected String argument; String PostValues; if (WiFi.status() == WL_CONNECTED) { WiFiClient client = server.available(); if (!client) { return; } // Wait until the client sends some data unsigned long timeout = millis() + 3000; while (!client.available() && millis() < timeout) { delay(1); } if (millis() > timeout) { // Serial.println("timeout"); client.flush(); client.stop(); return; } // Read the first line of the reply from server and print it to Serial and copy it into variable "argument" if(client.available()){ String methode; String line; methode = client.readStringUntil(' '); if (methode == "GET") line = client.readStringUntil('\n'); else { line = client.readString(); int postBegin = line.indexOf("\r\n\r\n"); PostValues = line.substring(postBegin+4, line.length()); } int argEnd = line.indexOf("HTTP"); argument = line.substring(0, argEnd-1); // Match the request client.flush(); String s; if (argument == "/save") { cDTZ.savePostValues(PostValues); s = HTTP_ANSWER; s += HTTP_HEAD_DTZ; s += ""; s += cDTZ.getNameDTZ(); s += ""; s += HTTP_BODYSTYLE_DTZ; s += ""; s += HTTP_DIV_FIELD; s += "

Ihre Eingaben wurden gespeichert.

"; s += "

"; s += "\n"; } else if (argument == "/setup?") { s = HTTP_ANSWER; s += HTTP_HEAD_DTZ; s += ""; s += cDTZ.getNameDTZ(); s += ""; s += HTTP_BODYSTYLE_DTZ; s += ""; s += HTTP_DIV_FIELD; s += "

Grundeinstellungen

"; s += "
"; s += "


"; s += "


"; s += "


"; s += "


"; s += "


"; // s += "


"; // s += "


"; s += "


"; s += "
Show Protokoll an LED:"; else s += ">"; s += "

"; s += "

"; s += "
StromLog-Version: "; s += LoggerVersion; s += "

Hilfe & Impressum

"; s += "\n"; } else if (argument == "/showsetup") { // showConfigOnSerial(); s = HTTP_ANSWER; s += "none"; } else if (argument == "/favicon.ico") { s = HTTP_ANSWER; s += "none"; } else if (argument.substring(0,13) == "/searchLogger") { s = HTTP_ANSWER; int argBegin = argument.indexOf("?"); if (argBegin != -1){ int idEx = argument.substring(argBegin+1,argument.length()).toInt(); if(idEx == cDTZ.getUserID() || cDTZ.getUserID() == 0){ s += "{\"DTZLOGGER\":\"" + LoggerVersion + "\"}"; } } } else if (argument == "/" || argument == "/back" || argument == "/back?") { // Prepare the response s = HTTP_ANSWER; s += HTTP_HEAD_DTZ_REFRESH; s += ""; s += cDTZ.getNameDTZ(); s += ""; s += HTTP_BODYSTYLE_DTZ; s += ""; s += HTTP_DIV_FIELD; s += "

StromLog-Status

"; s += "

Zeit:

"; s += "Datum:

"; s += "Stromlog-IP:

"; s += "

"; s += TransmitResult; s += "
"; s += "

"; s += cDTZ.getNameDTZ(); s += "

"; s += "

Hersteller-ID:

"; s += "Zähler-ID:

"; s += "Bezug in kWh:

"; s += "Einspeisung in kWh:

"; s += "Summen-Leistung:

"; if(U_L1>0 || U_L2>0 || U_L3>0){ s += "Spannung L1:

"; s += "Spannung L2:

"; s += "Spannung L3:

"; s += "Strom L1:

"; s += "Strom L2:

"; s += "Strom L3:

"; s += "Netzfrequenz:

"; } s += "Received protocols:

"; if (ProtocolCounter == 0) ProtocolCounter++; // if zero then exeption!!! easy way solution add 1 s += "Protocol errors:

"; if(sendingOK == false)s += "🔴"; // show a red bubble when StromLog not connect on server at the first time s += "Saved protocols:"; s += "

"; s += "

"; s += HTTP_SETUP_BUTTON; s += "

Hilfe & Impressum

"; s += "\n"; } else if (argument == "/memory") { s = HTTP_ANSWER; s += "Free space: "; s += cDTZ.FREESPACE; s += "
"; if(cDTZ.saveCounter > 0){ for(int i=1;i<=cDTZ.saveCounter;i++){ s += cDTZ.readSavedValues(i); s += "
"; } } else s += "No saved data.
"; s += "\n"; } else if (argument == "/sendProtocols"){ sendProtocols = 10; s = HTTP_ANSWER; s += "10 RAW Protocols will be send to server now.
"; s += "\n"; } else if (argument == "/currentData"){ s = HTTP_ANSWERJSON; s += getValuesAsJSON(); } else if (argument == "/allData"){ s = HTTP_ANSWERJSON; s += prepareSendValuesAsJSON(); } else s = "HTTP/1.1 403 OK\r\nContent-Type: text/html\r\n\r\n"; // Send the response to the client client.print(s); delay(1); } } } //*********** prepare values for sending to the PHP-script ***************/ String prepareSendValues(){ String Data = "timestamp="; Data += timestamp; Data += "&usid="; Data += cDTZ.getUserID(); Data += "&bez1="; Data += FromGridS; Data += "&bez2="; Data += FromGridA; Data += "&bez3="; Data += FromGridB; Data += "&eeg1="; Data += IntoGrid; Data += "&pwa="; Data += Consumtion; Data += "&ul1="; Data += U_L1; Data += "&ul2="; Data += U_L2; Data += "&ul3="; Data += U_L3; Data += "&il1="; Data += I_L1; Data += "&il2="; Data += I_L2; Data += "&il3="; Data += I_L3; Data += "&frq="; Data += Frequency; Data += "&ip="; Data += WiFi.localIP().toString(); Data += "&dtzid="; sprintf(extractValue,"%08X",meterID2); Data += extractValue; sprintf(extractValue,"%08X",meterID1); Data += extractValue; sprintf(extractValue,"%08X",meterID0); Data += extractValue; Data += "&dtzname="; Data += cDTZ.getNameDTZ(); Data += "&dtzma="; Data += MeterType; Data += "&end"; return Data; } //*********** prepare values for sending to influx-db *******************/ String prepareSendValuesInFlux(){ String Data = "StromLog"; Data += ",usid="; Data += cDTZ.getUserID(); Data += ",ip="; Data += WiFi.localIP().toString(); Data += ",dtzid="; sprintf(extractValue,"%08X",meterID2); Data += extractValue; sprintf(extractValue,"%08X",meterID1); Data += extractValue; sprintf(extractValue,"%08X",meterID0); Data += extractValue; Data += ",dtzname="; Data += cDTZ.getNameDTZ(); Data += " pwa="; Data += Consumtion; // here is the space as an separator between immutable and changeable values Data += ",bez1="; DispValue = FromGridS; Data += DispValue / 10000; Data += ",bez2="; DispValue = FromGridA; Data += DispValue / 10000; Data += ",bez3="; DispValue = FromGridB; Data += DispValue / 10000; Data += ",eeg1="; DispValue = IntoGrid; Data += DispValue / 10000; Data += ",ul1="; DispValue = U_L1; Data += DispValue / 10; Data += ",ul2="; DispValue = U_L2; Data += DispValue / 10; Data += ",ul3="; DispValue = U_L3; Data += DispValue / 10; Data += ",il1="; DispValue = I_L1; Data += DispValue / 100; Data += ",il2="; DispValue = I_L2; Data += DispValue / 100; Data += ",il3="; DispValue = I_L3; Data += DispValue / 100; Data += ",frq="; DispValue = Frequency; Data += DispValue /10; Data += ",timestamp="; Data += timestamp; // -3600; // i dont know why, but db shows an hour more, also sub an hour // Data += ",end"; return Data; } //*********** build an JSON code of values and return it *****************/ String getValuesAsJSON(){ String json = "{\"timestamp\":"; json += timestamp; json += ",\"meterData\":"; json += "{\"consumption\":{\"value\":"; json += Consumtion; json += ",\"unit\":\"W\"},"; json += "\"FromGridSum\":{\"value\":"; json+= FromGridS; json += ",\"unit\":\"dWh\"},"; json += "\"FromGridA\":{\"value\":"; json+= FromGridA; json += ",\"unit\":\"dWh\"},"; json += "\"FromGridB\":{\"value\":"; json+= FromGridB; json += ",\"unit\":\"dWh\"},"; json += "\"FeedIn\":{\"value\":"; json+= IntoGrid; json += ",\"unit\":\"dWh\"}"; json += "},\"electricsData\":{"; json += "\"voltageL1\":{\"value\":"; json+= U_L1; json += ",\"unit\":\"dV\"},"; json += "\"voltageL2\":{\"value\":"; json+= U_L2; json += ",\"unit\":\"dV\"},"; json += "\"voltageL3\":{\"value\":"; json+= U_L3; json += ",\"unit\":\"dV\"},"; json += "\"currentL1\":{\"value\":"; json+= I_L1; json += ",\"unit\":\"dI\"},"; json += "\"currentL2\":{\"value\":"; json+= I_L2; json += ",\"unit\":\"dI\"},"; json += "\"currentL3\":{\"value\":"; json+= I_L3; json += ",\"unit\":\"dI\"},"; json += "\"freq\":{\"value\":"; json+= Frequency; json += ",\"unit\":\"dHz\"}"; json += "}}"; return json; } //*********** prepare values for sending as JSON *************************/ String prepareSendValuesAsJSON(){ String Data = "{\"userid\":"; Data += cDTZ.getUserID(); Data += ",\"meter\":{"; Data += "\"localip\":\""+ WiFi.localIP().toString()+"\","; Data += "\"metername\":\""+cDTZ.getNameDTZ()+"\","; Data += "\"manufracturer\":\""+MeterType+"\","; Data += "\"meterid\":\""; sprintf(extractValue,"%08X",meterID2); Data += extractValue; sprintf(extractValue,"%08X",meterID1); Data += extractValue; sprintf(extractValue,"%08X",meterID0); Data += extractValue; Data += "\"},\"currentData\":"; Data += getValuesAsJSON(); Data += "}"; return Data; } //************ just for debug only ***************************************/ String sendProtokollDebug(){ String Data = "protDebug=1&usid="; Data += cDTZ.getUserID(); String bufferDTZ; for(int i=0;i 0){ // sending data-values to a php-script r = send2dbasePOST(prepareSendValues(),true); } else {r = send2dbasePOST(prepareSendValuesInFlux(),true);} // send data-values into inFlux-db } else {r = send2dbasePOST(prepareSendValuesAsJSON(),true);} // send data-values as JSON return r; } //*********** send DTZ-Datas to PHP script on server per POST *******************/ bool send2dbasePOST(String content, bool saveIfError){ bool r=false; bool wrongAccess = false; if (WiFiMulti.run() == WL_CONNECTED) { WiFiClient client; HTTPClient http; String target = "http://"; target += cDTZ.getTargetURL(); if (http.begin(client,target)){ if(cDTZ.getUser() == "sendJSON") http.addHeader("Content-Type","application/json;charset=utf-8"); else http.addHeader("Content-Type","application/x-www-form-urlencoded"); int httpCode = 0; TransmitResult = ""; httpCode = http.POST(content); if (httpCode >= 100) { if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_NO_CONTENT){ TransmitResult = http.getString(); if (TransmitResult == "OK
" || httpCode == HTTP_CODE_NO_CONTENT){ r = true; // if data saved successful than result=true else false saveIfError = false; } else { TransmitResult = TransmitResult; r = false; saveIfError = false; wrongAccess = true; } } else {TransmitResult = "[HTTP] Unable to connect! "; TransmitResult +=httpCode;} } else {TransmitResult ="[http] missing return-code"; r = false;} } else {TransmitResult ="[HTTP] Service not found!"; r = false;} http.end(); } if(r == false && saveIfError == true && wrongAccess == false && sendingOK == true) cDTZ.saveValues(content); // save values data into littleFS to send later again return r; } //****************** read saved data and send 2 server **************************** void sendSavedData(){ if(cDTZ.saveCounter > 0){ if(send2dbasePOST(cDTZ.readSavedValues(1),false) == true){ if(cDTZ.saveCounter > 1){ for(int i=2;i<=cDTZ.saveCounter;i++){ delay(500); send2dbasePOST(cDTZ.readSavedValues(i),false); yield(); cDTZ.delFile(i); if(i > 15) {cDTZ.delFile(0); return;} // error handling } cDTZ.delFile(0); flashCounter=0; } else { cDTZ.delFile(1); cDTZ.saveCounter = 0; flashCounter=0; } } if(TransmitResult == "Wrong MeterID")cDTZ.delFile(0); if(TransmitResult == "Wrong user or password")cDTZ.delFile(0); } } //****************** read and decode values according OBIS-code ******************* void readValuesOBIS(){ int scaler=1; DecodeOneValue(SearchOBIS(OBISoutGridS,7),4); //7 bytes check and drop 4 values until target if(ValueScale < 0) scaler = ValueScale * -10; else scaler = ValueScale / 10; FromGridS = singleValue*scaler; scaler=1; DecodeOneValue(SearchOBIS(OBISoutGridA,7),4); // the return-value ValueScale contents potency *10*x / singleValue = value of OBIS if(ValueScale < 0) scaler = ValueScale * -10; else scaler = ValueScale / 10; FromGridA = singleValue*scaler; scaler=1; DecodeOneValue(SearchOBIS(OBISoutGridB,7),4); // the return-value ValueScale contents potency *10*x / singleValue = value of OBIS if(ValueScale < 0) scaler = ValueScale * -10; else scaler = ValueScale / 10; FromGridB = singleValue*scaler; scaler=1; DecodeOneValue(SearchOBIS(OBISintoGrid,7),4); // the return-value ValueScale contents potency *10*x / singleValue = value of OBIS if(ValueScale < 0) scaler = ValueScale * -10; else scaler = ValueScale / 10; IntoGrid = singleValue*scaler; scaler=1; DecodeOneValue(SearchOBIS(OBIS_Watt,7),4); if (singleValue == 0){ DecodeOneValue(SearchOBIS(OBIS_WattA,7),4); // the return-value ValueScale contents potency *10*x / singleValue = value of OBIS if(singleValue ==0){ DecodeOneValue(SearchOBIS(OBIS_WattD,7),4); // singleValue /= 10; // Serial.println(singleValue); //debug // Serial.println(ValueScale); //debug } } Consumtion = singleValue / ValueScale; DecodeOneValue(SearchOBIS(OBIS_U_L1,7),4); U_L1 = singleValue; // the return-value ValueScale contents potency *10*x / singleValue = value of OBIS DecodeOneValue(SearchOBIS(OBIS_U_L2,7),4); U_L2 = singleValue; DecodeOneValue(SearchOBIS(OBIS_U_L3,7),4); U_L3 = singleValue; DecodeOneValue(SearchOBIS(OBIS_I_L1,7),4); I_L1 = singleValue; DecodeOneValue(SearchOBIS(OBIS_I_L2,7),4); I_L2 = singleValue; DecodeOneValue(SearchOBIS(OBIS_I_L3,7),4); I_L3 = singleValue; DecodeOneValue(SearchOBIS(OBIS_Freq,7),4); Frequency = singleValue; DecodeOneValue(SearchOBIS(OBIS_DTZ_ID,7),4); meterID0 = value0; meterID1 = value1; meterID2 = value2; if (meterID0 == 0){ DecodeOneValue(SearchOBIS(OBIS_ServerID,7),4); meterID0 = value0; meterID1 = value1; meterID2 = value2; } DecodeOneValue(SearchOBIS(OBIS_Manuf,7),4); if (value0 !=0 ){ MeterType = ""; int i3= value0 & 0x00ff; int i2 = (value0 >>8) & 0x00ff; int i1 = (value0 >>16) & 0x00ff; MeterType += (char)i1; MeterType += (char)i2; MeterType += (char)i3; } } /*********************************************************** * convert a unix-timestamp into single bytes as value * function works up to 1972 until 19.01.2038 ***********************************************************/ void Timestamp2DateTime(){ int dummy = 0; bool leapYear = false; // default: don't be a leap-year timeDummy = timestamp - 31536000 *2; // sub two years (1970 and 1971) add the years later years= (timeDummy / 126230400) *4; // get the periodes of four years cycle; 3Y+leapY = 126230400 seconds timeDummy = timeDummy % 126230400; // get the rest of timestamp without the cycles like above this line if (timeDummy >= 31622400) { timeDummy -= 31622400; // sub the first leap-year if it's over years++; // add the leapyear years += timeDummy / 31536000; // get the rest of years between 0 until 2 years } year = years + 1972; // add start-year +2: now, we know the actual year and can figure out if is a leap-year // if (year % 4 == 0) leapYear = true; // if year divison by four without some rest, then is a leap-year...yeeeh -> it works until 2036 only if ( bitRead(year,0)==0 && bitRead(year,1)==0) { // it's very faster than %-arithmetic leapYear = true; SecOfYear = 31622400; // set the counts of seconds leap-year // Serial.print("LeapYear "); } else SecOfYear = 31536000; // or don't be a leap-year -> regulary seconds day = (timeDummy % SecOfYear) / 86400; // get the days of actual year month = 0; // set zero for (int i=0; day>=0; i++ ) { // do it until the day(s) are negativ, this loop has be exicute one time -> to set month at ones dummy = day; // store the value befor result is negativ for break, because the last one was the day of actual month day = day - DaysPerMonth[i]; // sub the days of month until it's negativ, according byte-array if (i == 1 && leapYear == true) {day -= 1;} // sub the 29. of february on leapyears ;-) month++; // add one month on every loop if (i > 11) return; // if this function wild ;-) then break (max. months are 0 - 11) } day = dummy+1; // add one, because the start was on 1. january (start of timstamp = 01.01.1970 and we have to add it) /****** get time ******/ hour = (timeDummy % 86400) / 3600; // the value of timeDummy contents the rest of hours, minutes and seconds only minute = (timeDummy % 3600) / 60; // it's shorter to take reduced timeDummy than the whole timestamp second = timeDummy % 60; } //****************************** show received a protocol on red LED *************************** void showProtocolOnLED(){ if (cDTZ.showProtocol==1 && flashCounter <= 0){ if (ShowProtocolTime > 0){ // Flash LED when protocol received digitalWrite(LED,ON); ShowProtocolTime --; } else digitalWrite(LED,OFF); // if time over turn off LED } } //****************************** compare search-string with buffer ***************************** int CheckOBIS(char *checkCode, int i, int searchLength){ int result = 0; for (int y=0;y<=searchLength;y++){ if(*(checkCode+y) != dataDTZ[i+y]) return -1; // check chars if identical } return result; } //****************************** search OBIS-code signation ***************************** int SearchOBIS(char *searchCode, int slength){ int x = 0; do { if (*searchCode == dataDTZ[x]){ if (CheckOBIS(searchCode, x, slength)==0)break; } x++; } while (x < MaxBuffer); if(x > MaxBuffer - 10)return 0; // nothing of OBIS founded else return x+slength+1; // pointer on the first value after OBIS-String } //****************** drop values until target value ******************* int dropValues(int actPointer, int drops){ int d=0; int c=0; if(drops > 0){ for(int i=0; i 10) break; d=dataDTZ[actPointer]; // get value type and length c=d&0x70; if(c == 0x70){ // jump over list-elements too actPointer++; drops += d&0x0f; // Serial.println(drops); //debug } else { d=d&0x0f; // extract lenght actPointer += d; // add length to drop value } } } return actPointer; } //************** get scale-factor for value *********************************************/ int getScalerActValue(int actPointer){ int actual_sc = dataDTZ[actPointer]; int scaler = 1; if(actual_sc == 0x52){ scaler = dataDTZ[actPointer+1]; switch (scaler){ case 253: scaler=1000; break; case 254: scaler=100; break; case 255: scaler=10; break; case 0: scaler=1; break; case 1: scaler=-10; break; case 2: scaler=-100; break; case 3: scaler=-1000; break; default: scaler=1; break; } } return scaler; } //************** decode one value from data / this days works till 32 bits only ********** int DecodeOneValue(int actPointer, int drops){ singleValue = 0; value2 = 0; value1 = 0; value0 = 0; if(actPointer == 0) return 0; actPointer = dropValues(actPointer,drops); if (dataDTZ[actPointer] == 01) return actPointer+1; // check if data not in used if (dataDTZ[actPointer] == 00) return actPointer; // check if data-block is end if (dataDTZ[actPointer] == 77) return actPointer; // check if new data-block begin ValueScale = getScalerActValue(actPointer-2); // get scaler of actual value char actual_TL = dataDTZ[actPointer]; int actual_Type = actual_TL & 0xf0; int actual_Length = actual_TL & 0x0f; if(actual_Length == 9 && actual_Type == 0x50 ){ // get only the last 4 bytes if eigth bytes delivered as integer actual_Length -= 4; actPointer +=4; } for (int i=1; i=8){ value2 = value2 << 8; valueD = value1; valueD = valueD >> 24; value2 += valueD; } if (i >= 4){ value1 = value1 << 8; valueD = value0; valueD = valueD >> 24; value1 += valueD; } value0 = value0 << 8; value0 += dataDTZ[actPointer+i]; } extractValue[i-1] = dataDTZ[actPointer+i]; extractValue[i]=0; } if(actual_Type == 0x50){ switch(actual_Length){ // handle negative values and set the rest of int32 case 4: if(bitRead(singleValue,13)){ singleValue |= 0xff000000;} break; case 3: if(bitRead(singleValue,15)){ singleValue |= 0xffff0000;} break; case 2: if(bitRead(singleValue, 7)){ singleValue |= 0xffffff00;} break; default: break; } } return actPointer+actual_Length; } //************ reflected uint16_t for CRC calculation ******************** // this function turns the bits -> MSB will be LSB from 0111001 to 1001110 // it's needed for counting crc reflected input and reflected output uint16_t refUint(uint16_t reflector, int bits){ // bits = how many bits should be reflected uint16_t shift=0; int i=0; do { shift <<= 1; if(reflector & 0x0001) shift |= 1; reflector >>= 1; i++; } while (i < bits); return shift; } //******************* calculate CRC16-CCITT ****************************** uint16_t crc_one_byte(uint16_t crcR, uint16_t oneByte, uint16_t poly){ oneByte = refUint(oneByte,8); // reflected input oneByte <<= 8; // shift left eight times for(int i=0; i<8; i++){ if((crcR ^ oneByte) & 0x8000) crcR =(crcR << 1)^ poly; else crcR <<= 1; oneByte <<= 1; } return crcR; } //*********************** decode SML-data from holley DTZ541 or generetic SML ************* void decodeDataSML(){ if (pointer > MaxBuffer / 4) { if (dataDTZ[pointer-1] == 0x1a && dataDTZ[pointer-2] == 0x1b){ // check of end-code add fill-byte-check if(dataDTZ[pointer-3] == 0x1b && dataDTZ[pointer-4] == 0x1b){ // really is end-code pointerEOT = pointer; // save pointer of data-buffer-end ProtocolCounter ++; // count the protocols ShowProtocolTime = 200; } } if(pointerEOT == pointer) { crc_ccitt = refUint(crc,16)^0xffff; // reflected CRC-output and finish it with xor by 0xffff } } pointer ++; if(pointer > pointerEOT+2) { // protcol end and now arriveds the CRC-bytes only uint16_t check_crc = dataDTZ[pointerEOT+2]; // load the first value of crc check_crc <<= 8; // do it to a MSB check_crc |= dataDTZ[pointerEOT+1]; // add the lower part //**************** debug crc check ********************************** // Serial.print(check_crc); // debug // Serial.print(":"); // debug // Serial.println(crc_ccitt); // debug //**************** debug end **************************************** if(check_crc == crc_ccitt){ // compare crc-strings readValuesOBIS(); //************** protokoll debug only ****************/ if(sendProtocols > 0){ send2dbasePOST(sendProtokollDebug(),false); //debug sendProtocols --; } //************** protokoll debug end *****************/ } else { errorCounter ++; // meterManufractur = 0; // show and mark that an error occurs (then manufracture is'nt displayed) } pointerEOT = MaxBuffer-2; pointer = 0; // clear for new begin of protocol crc = 0xFFFF; // set crc to begin by 0xffff } } //*********************** decode one value of s0 protocol ************ uint32_t getOneValueS0(int startpoint, int endpoint, int valueType){ int32_t result= -1; if(dataDTZ[startpoint] == 0x28){ // if begin by "(" -> then OK if(valueType == 0){ // meternumber String meterNumber = ""; for(int i=1; i < endpoint; i++){ if(dataDTZ[startpoint+i] == 0x29) endpoint = 1; // break if appeare ) meterNumber += dataDTZ[startpoint+i]; } int numberLength = meterNumber.length(); if (numberLength <= 10) meterID0 = meterNumber.toInt(); else { meterID0 = meterNumber.substring(8).toInt(); valueType = 1; endpoint = 9; } } if(valueType == 1){ //meter manufracture int oneChar = 0; meterID2 = 0x000000A0; for(int i=1; i < endpoint; i++){ oneChar = dataDTZ[startpoint+i]; if(oneChar == 0x29) endpoint = 1; // break if appeare ) if(oneChar >= 0x30 && oneChar <= 0x39){ oneChar &= 0x0f; if(i<4 && oneChar!=0){ meterID2 = meterID2 << 4; meterID2 += oneChar; } meterID1 = meterID1 << 4; meterID1 += oneChar; } else { meterID1 = meterID1 << 8; meterID1 += oneChar; } } } if(valueType == 0x50){ String valueSing = "0"; int i; int dot=0; bool negative = false; for(i=1; i < endpoint; i++){ if(dataDTZ[startpoint+i] == 0x2a) endpoint = 1; // break if appeare * if(dataDTZ[startpoint+i] == 0x2e) dot=i; if(dataDTZ[startpoint+i] == 0x2d) negative = true; // check if negativ value if(dataDTZ[startpoint+i] >= 0x30 && dataDTZ[startpoint+i] <= 0x39) valueSing += dataDTZ[startpoint+i]; } result = valueSing.toInt(); if (dot > 0){ dot=i-dot-2; if(dot == 3 && dataDTZ[startpoint+i] == 0x41) result = result/10; } if(negative) result *= -1; // Serial.println(result); // debug } } return result; } //*********************** decode data from Logarex LX13BE ************ void decodeDataS0(){ if (dataDTZ[pointer] == 0x0a && dataDTZ[pointer-1] == 0x0d && dataDTZ[pointer-2] == 0x21){ // check of row-end int32_t result= -1; getOneValueS0(SearchOBIS("96.1.0*255",9),17,0); // Zählernummer and manufracture result = getOneValueS0(SearchOBIS("1.8.0*255",8),12,0x50); // Bezug fromGrid if(result > 0)FromGridS=result; result= -1; result = getOneValueS0(SearchOBIS("1.8.1*255",8),12,0x50); // Bezug fromGrid if(result > 0)FromGridA=result; result= -1; result = getOneValueS0(SearchOBIS("1.8.2*255",8),12,0x50); // Bezug fromGrid if(result > 0)FromGridB=result; result= -1; result = getOneValueS0(SearchOBIS("2.8.0*255",8),12,0x50); // EEG intoGrid if(result > 0)IntoGrid=result; result= -1; result = getOneValueS0(SearchOBIS("16.7.0*255",9),10,0x50); // P in W if(result != -1)Consumtion=result; result= -1; result = getOneValueS0(SearchOBIS("32.7.0*255",9),10,0x50); // UL1 if(result != -1)U_L1=result; result = getOneValueS0(SearchOBIS("52.7.0*255",9),10,0x50); // UL2 if(result != -1)U_L2=result; result = getOneValueS0(SearchOBIS("72.7.0*255",9),10,0x50); // UL3 if(result != -1)U_L3=result; result = getOneValueS0(SearchOBIS("31.7.0*255",9),10,0x50); // IL1 if(result != -1)I_L1=result; result = getOneValueS0(SearchOBIS("51.7.0*255",9),10,0x50); // IL2 if(result != -1)I_L2=result; result = getOneValueS0(SearchOBIS("71.7.0*255",9),10,0x50); // IL3 if(result != -1)I_L3=result; result = getOneValueS0(SearchOBIS("14.7.0*255",9),5,0x50); // Freq if(result != -1)Frequency=result; // Serial.println(result); // debug if (dataDTZ[pointer-2] == 0x21){ // check of protokol-end ProtocolCounter ++; // count the protocols ShowProtocolTime = 200; //************** protokoll debug only ****************/ if(sendProtocols > 0){ send2dbasePOST(sendProtokollDebug(),false); //debug sendProtocols --; } //************** protokoll debug end *****************/ } pointer = 0; // clear for new begin of protocol } pointer ++; } //******************automatic detect meter-type ************************** void detectMeter(){ if (dataDTZ[pointer] == 0x0a && dataDTZ[pointer-1] == 0x0d){ // check of end-code int poi=SearchOBIS("/",0); if (poi > 0){ MeterType =""; int serialSpeed = dataDTZ[poi+3] & 0x0f; if (serialSpeed > 4 && serialSpeed < 9){ for(int i=poi; iMaxBuffer-10) break; } cDTZ.dtz_Type = 2; pointer = 0; // clear for new begin of protocol } } } if (dataDTZ[pointer] == 0x1a && dataDTZ[pointer-1] == 0x1b){ // check of end-code add fill-byte-check if(dataDTZ[pointer-2] == 0x1b && dataDTZ[pointer-3] == 0x1b){ // really is end-code if (SearchOBIS(ProtBegin,4) !=0 ){ // check if protocol is complete if (SearchOBIS("HLY",2) !=0 ){ cDTZ.dtz_Type = 1; MeterType = DTZ541; } else if (SearchOBIS("ISK",2) !=0 ){ cDTZ.dtz_Type = 3; MeterType = ISKRA; } else if (SearchOBIS("EMH",2) !=0 ){ cDTZ.dtz_Type = 4; MeterType = EMH; } else { cDTZ.dtz_Type = 10; MeterType = GENERETIC; } } pointer = 0; // clear for new begin of protocol } } pointer ++; } //*********************** setup esp on start ***************************** void setup() { pinMode(BoardTaster, INPUT_PULLUP); // set as input pinMode(LED, OUTPUT); // set as output digitalWrite(LED,OFF); pinMode(Receive485, OUTPUT); digitalWrite(Receive485, OFF); // set pin on low-level for chip MAX485 to receive flashCounter = 30; // time to show flash on LED for infos Serial.begin(9600); // DTZ liefert 9600 baud 8-N-1 delay(2000); wifi_station_set_hostname("StromLog-SML"); // set hostname into WLAN network bool result = setupWIFI(); if (result) { // Serial.print("connected to ssid: "); // Serial.print(WiFi.SSID()); // Serial.print(" with IP:"); // Serial.println(WiFi.localIP()); // Serial.println("Start Server now"); cDTZ.begin(); // start filesystem and properties of user // showConfigOnSerial(); server.begin(); syncron_NTP_Time(); Timestamp2DateTime(); delay(500); } else { // Serial.println("Failed to connect!"); // Serial.println("Please take a reset,"); // Serial.println("or hold the button for 6s!"); for (int i=0 ; i<20;i++){ if (digitalRead(LED) == HIGH)digitalWrite(LED,OFF); else digitalWrite(LED,ON); delay(200); } ESP.restart(); } } //************************* esp loop function *************************** void loop() { yield(); checkButton(); // check if user pressed button for set new SSID uint32_t currentMillis = millis(); if(internClock(currentMillis)== true) { // every second will run this component if (flashCounter > 0){ // Flash LED when errors occurs or for information if (digitalRead(LED) == HIGH)digitalWrite(LED,OFF); else digitalWrite(LED,ON); flashCounter --; } else digitalWrite(LED,OFF); // if flashtime over turn off LED if (hour == 11 && minute==2 && second==(cDTZ.getUserID()%40)) { // update timestamp // clearValues(); // clear data if (syncron_NTP_Time() == true) Timestamp2DateTime(); else if(!WiFi.isConnected()) ESP.restart(); // check if connected yet } if (timestamp < TimeOf2021 && second == 50) { if (syncron_NTP_Time() == true) Timestamp2DateTime(); // get timestamp again if sync was failure, perhaps after power down } if (cDTZ.getUserID() !=0){ if (second == (cDTZ.getUserID()%40)+3) { // is called every minute at the n seconds if(cDTZ.saveCounter > 0)sendSavedData(); if (minute == 0 || minute == 15 || minute == 30 || minute == 45) { // and every quarter hour // if (minute % 2 == 0) { // and every n-th minute if (setContentAndSend()){ TransmitResult = "Letzter Datentransfer: "; clearValues(); // clear data sendingOK = true; } else{ flashCounter=300; // show an error n seconds TransmitResult += "🔴"; } getTimeDate2String(); TransmitResult += "
"; TransmitResult += act_time; TransmitResult += " Uhr - "; TransmitResult += act_date; } } } } if (Serial.available()>0) { // read the incoming byte from uart Serial.readBytes(receivedByte,1); dataDTZ[pointer] = receivedByte[0]; // Serial.print(dataDTZ[pointer]); // debug crc = crc_one_byte(crc, dataDTZ[pointer], polynom); if (pointer > MaxBuffer-2){ pointer =0;} //zur sicherheit, dass pointer nicht überläuft switch (cDTZ.dtz_Type){ case 0: detectMeter(); break; case 1: decodeDataSML(); break; case 2: decodeDataS0(); break; case 3: decodeDataSML(); break; case 4: decodeDataSML(); break; case 10: decodeDataSML(); break; default: detectMeter(); break; } } showProtocolOnLED(); check4Client(); // thats it's if with the browser show data on port 80 by html }