#include #include #include #include #include #include #include #include #include #include #include "CRC16.h" #include #include #include #include // Function prototypes void reboot(); time_t NTPgetTime(); void drawMenu(); // Time related settings Timer t; WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 10000); // Display settings //// Software SPI (slower updates, more flexible pin options): //// pin 7 - Serial clock out (SCLK) //// pin 6 - Serial data out (DIN) //// pin 5 - Data/Command select (D/C) //// pin 4 - LCD chip select (CS) //// pin 3 - LCD reset (RST) Adafruit_PCD8544 display = Adafruit_PCD8544(D7, D6, D5, D4, D3); int tab = 0; int tabCount = 3; // LED settings #define ledPin D8 int ledBrightness = 192; void ledOff(); int ledTimeout; // Button settings #define buttonPin D2 Button2 button = Button2(buttonPin); // Wifi Settings const char *ssid = "*"; const char *password = "*"; const char *hostName = "ESPP1Meter"; // Server settings const char *apiIP = "192.168.2.6"; const int apiPort = 3000; const char *apiKey = "*"; const int gasIdx = 291; const int energyIdx = 294; const bool outputOnSerial = false; // Statistics variables unsigned long telegramsTotal = 0; unsigned long telegramsInvalid = 0; unsigned long postedTotal = 0; unsigned long postedFailed = 0; // Vars to store meter readings long mEVLT = 0; //Meter reading Electrics - consumption low tariff long mEVHT = 0; //Meter reading Electrics - consumption high tariff long mEOLT = 0; //Meter reading Electrics - return low tariff long mEOHT = 0; //Meter reading Electrics - return high tariff long mEAV = 0; //Meter reading Electrics - Actual consumption long mEAT = 0; //Meter reading Electrics - Actual return long mGAS = 0; //Meter reading Gas long prevGAS = 0; #define MAXLINELENGTH 128 // longest normal line is 47 char (+3 for \r\n\0) char telegram[MAXLINELENGTH]; unsigned int currentCRC = 0; // P1 Serial settings #define SERIAL_RX D1 // pin for SoftwareSerial RX SoftwareSerial mySerial(SERIAL_RX, -1, false); // (RX, TX, inverted) void setup() { // Init Serial Serial.begin(115200); delay(50); // Init LED pinMode(ledPin, OUTPUT); analogWrite(ledPin, ledBrightness); // Init Button button.setReleasedHandler(press); // Init Display display.begin(); display.setContrast(50); display.setFont(&Org_01); drawMenu(); // Init WiFi WiFi.mode(WIFI_STA); display.println("Connecting to:"); display.print(" "); display.println(ssid); display.display(); WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("Connection Failed! Rebooting..."); drawMenu(); display.println("Connection Failed!"); display.display(); delay(5000); ESP.restart(); } // Init P1 Serial mySerial.begin(115200); // Init ArduinoOTA //// Port defaults to 8266 ArduinoOTA.setPort(8266); //// Hostname defaults to esp8266-[ChipID] ArduinoOTA.setHostname(hostName); //// No authentication by default // ArduinoOTA.setPassword((const char *)"123"); ArduinoOTA.onStart([]() { drawMenu(); display.println("OTA Update"); display.display(); Serial.println("Start"); }); ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { drawMenu(); display.println("OTA Update"); display.printf("Progress: %u%%\r", (progress / (total / 100))); display.display(); Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { drawMenu(); display.println("OTA Update"); display.printf("Error[%u]: ", error); display.display(); Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("End Failed"); }); ArduinoOTA.begin(); // Init Time timeClient.begin(); time_t test = NTPgetTime(); while (test < 10000) { test = NTPgetTime(); } setTime(NTPgetTime()); getExternalTime NtpTime = &NTPgetTime; setSyncProvider(NtpTime); setSyncInterval(3600); display.println("Time set"); display.display(); delay(1000); // Exit setup drawTab(2); ledTimeout = t.after(5000, ledOff, (void *)0); } // Reboot the ESP void reboot() { drawMenu(); display.println("Scheduled reboot"); display.display(); delay(5000); ESP.restart(); } // Update Time time_t NTPgetTime() { timeClient.update(); return timeClient.getEpochTime(); } // Turn of the backlight LEDs void ledOff(void *context) { tab = 0; drawTab(tab); analogWrite(ledPin, 0); } // Button action void press(Button2 &btn) { analogWrite(ledPin, ledBrightness); t.stop(ledTimeout); ledTimeout = t.after(10000, ledOff, (void *)0); if ((tab + 1) == tabCount) { tab = 0; } else { tab++; } drawTab(tab); } // Draw top bar void drawMenu() { display.clearDisplay(); display.setCursor(0, 4); display.println("P1 Reader"); display.drawLine(0, 6, 84, 6, 1); display.display(); display.setCursor(0, 12); } void drawTab(int no) { drawMenu(); switch (no) { // Network case 0: drawMenu(); // SSID display.println("Connected to:"); display.print(" "); display.println(ssid); // Own IP display.println("IP address: "); display.print(" "); display.println(WiFi.localIP()); // Target server display.println("Target host: "); display.print(" "); display.println(String(apiIP) + ":" + String(apiPort)); break; // Stats case 1: drawMenu(); // Telegrams display.println("Telegrams"); display.print(" Total: "); display.println(String(telegramsTotal)); display.print(" Invalid: "); display.println(String(telegramsInvalid)); // Posted display.println("Posted"); display.print(" Total: "); display.println(String(postedTotal)); display.print(" Failed: "); display.println(String(postedFailed)); break; // Time case 2: display.println("Current time:"); // Time display.print(" "); if (hour() < 10) { display.print("0"); } display.print(hour()); display.print(":"); if (minute() < 10) { display.print("0"); } display.println(minute()); // Date display.print(" "); if (day() < 10) { display.print("0"); } display.print(day()); display.print("-"); if (month() < 10) { display.print("0"); } display.print(month()); display.print("-"); display.println(year()); break; } display.display(); } // Send data to server bool Send(int idx, int nValue, char *sValue) { HTTPClient http; bool retVal = false; char httpRequestData[255]; // "http://%s:%d/json.htm?type=command¶m=udevice&idx=%d&nvalue=%d&svalue=%s" // "api_key=tPmAT5Ab3j7F9&sensor=BME280&value1=24.25&value2=49.54&value3=1005.14" sprintf(httpRequestData, "api_key=%s&type=%d&nval=%d&sval=%s", apiKey, idx, nValue, sValue); Serial.printf("[HTTP] GET... URL: %s\n", httpRequestData); http.begin(String("http://" + String(apiIP) + ":" + String(apiPort))); //HTTP http.addHeader("Content-Type", "application/x-www-form-urlencoded"); postedTotal++; int httpCode = http.POST(httpRequestData); // httpCode will be negative on error if (httpCode > 0) { // HTTP header has been send and Server response header has been handled Serial.printf("[HTTP] GET... code: %d\n", httpCode); // file found at server if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); retVal = true; } } else { postedFailed++; Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); return retVal; } // Update Gas info void UpdateGas() { if (prevGAS != mGAS) { char sValue[10]; sprintf(sValue, "%d", mGAS); if (Send(gasIdx, 0, sValue)) prevGAS = mGAS; } } // Update Electricity info void UpdateElectricity() { char sValue[255]; sprintf(sValue, "%d;%d;%d;%d;%d;%d", mEVLT, mEVHT, mEOLT, mEOHT, mEAV, mEAT); Send(energyIdx, 0, sValue); } // Check if value is number bool isNumber(char *res, int len) { for (int i = 0; i < len; i++) { if (((res[i] < '0') || (res[i] > '9')) && (res[i] != '.' && res[i] != 0)) { return false; } } return true; } // Find char in array int FindCharInArrayRev(char array[], char c, int len) { for (int i = len - 1; i >= 0; i--) { if (array[i] == c) { return i; } } return -1; } // Check if value is valid long getValidVal(long valNew, long valOld, long maxDiffer) { //check if the incoming value is valid if (valOld > 0 && ((valNew - valOld > maxDiffer) && (valOld - valNew > maxDiffer))) return valOld; return valNew; } // Get value from char buffer long getValue(char *buffer, int maxlen) { int s = FindCharInArrayRev(buffer, '(', maxlen - 2); if (s < 8) return 0; if (s > 32) s = 32; int l = FindCharInArrayRev(buffer, '*', maxlen - 2) - s - 1; if (l < 4) return 0; if (l > 12) return 0; char res[16]; memset(res, 0, sizeof(res)); if (strncpy(res, buffer + s + 1, l)) { if (isNumber(res, l)) { return (1000 * atof(res)); } } return 0; } // Decode P1 telegram bool decodeTelegram(int len) { //need to check for start int startChar = FindCharInArrayRev(telegram, '/', len); int endChar = FindCharInArrayRev(telegram, '!', len); bool validCRCFound = false; if (startChar >= 0) { //start found. Reset CRC calculation currentCRC = CRC16(0x0000, (unsigned char *)telegram + startChar, len - startChar); if (outputOnSerial) { for (int cnt = startChar; cnt < len - startChar; cnt++) Serial.print(telegram[cnt]); } Serial.println("Start found!"); telegramsTotal++; } else if (endChar >= 0) { //add to crc calc currentCRC = CRC16(currentCRC, (unsigned char *)telegram + endChar, 1); char messageCRC[5]; strncpy(messageCRC, telegram + endChar + 1, 4); messageCRC[4] = 0; //thanks to HarmOtten (issue 5) if (outputOnSerial) { for (int cnt = 0; cnt < len; cnt++) Serial.print(telegram[cnt]); } validCRCFound = (strtol(messageCRC, NULL, 16) == currentCRC); if (validCRCFound) { Serial.println("\nVALID CRC FOUND!"); } else { Serial.println("\nINVALID CRC FOUND!"); telegramsInvalid++; } currentCRC = 0; } else { currentCRC = CRC16(currentCRC, (unsigned char *)telegram, len); if (outputOnSerial) { for (int cnt = 0; cnt < len; cnt++) Serial.print(telegram[cnt]); } } long val = 0; long val2 = 0; // 1-0:1.8.1(000992.992*kWh) // 1-0:1.8.1 = Elektra verbruik laag tarief (DSMR v4.0) if (strncmp(telegram, "1-0:1.8.1", strlen("1-0:1.8.1")) == 0) mEVLT = getValue(telegram, len); // 1-0:1.8.2(000560.157*kWh) // 1-0:1.8.2 = Elektra verbruik hoog tarief (DSMR v4.0) if (strncmp(telegram, "1-0:1.8.2", strlen("1-0:1.8.2")) == 0) mEVHT = getValue(telegram, len); // 1-0:2.8.1(000348.890*kWh) // 1-0:2.8.1 = Elektra opbrengst laag tarief (DSMR v4.0) if (strncmp(telegram, "1-0:2.8.1", strlen("1-0:2.8.1")) == 0) mEOLT = getValue(telegram, len); // 1-0:2.8.2(000859.885*kWh) // 1-0:2.8.2 = Elektra opbrengst hoog tarief (DSMR v4.0) if (strncmp(telegram, "1-0:2.8.2", strlen("1-0:2.8.2")) == 0) mEOHT = getValue(telegram, len); // 1-0:1.7.0(00.424*kW) Actueel verbruik // 1-0:2.7.0(00.000*kW) Actuele teruglevering // 1-0:1.7.x = Electricity consumption actual usage (DSMR v4.0) if (strncmp(telegram, "1-0:1.7.0", strlen("1-0:1.7.0")) == 0) mEAV = getValue(telegram, len); if (strncmp(telegram, "1-0:2.7.0", strlen("1-0:2.7.0")) == 0) mEAT = getValue(telegram, len); // 0-1:24.2.1(150531200000S)(00811.923*m3) // 0-1:24.2.1 = Gas (DSMR v4.0) on Kaifa MA105 meter if (strncmp(telegram, "0-1:24.2.1", strlen("0-1:24.2.1")) == 0) mGAS = getValue(telegram, len); return validCRCFound; } // Read P1 telegram void readTelegram() { if (mySerial.available()) { memset(telegram, 0, sizeof(telegram)); while (mySerial.available()) { int len = mySerial.readBytesUntil('\n', telegram, MAXLINELENGTH); telegram[len] = '\n'; telegram[len + 1] = 0; yield(); if (decodeTelegram(len + 1)) { UpdateElectricity(); UpdateGas(); } } } } void loop() { if (hour() == 0 && minute() == 0 && second() == 0) { reboot(); } readTelegram(); button.loop(); t.update(); ArduinoOTA.handle(); }