diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a4d785 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +*.code-workspace diff --git a/P1Meter.ino b/P1Meter.ino index 7b72bff..f3f0d5d 100644 --- a/P1Meter.ino +++ b/P1Meter.ino @@ -1,22 +1,68 @@ -// #include +#include +#include #include #include #include #include #include #include +#include +#include #include "CRC16.h" +#include +#include +#include +#include -//===Change values from here=== -const char* ssid = "WIFISSID"; -const char* password = "PASSWORD"; -const char* hostName = "ESPP1Meter"; -const char* domoticzIP = "192.168.1.35"; -const int domoticzPort = 8090; -const int domoticzGasIdx = 291; -const int domoticzEneryIdx = 294; -const bool outputOnSerial = true; -//===Change values to here=== +// 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 @@ -25,277 +71,512 @@ 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 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; -#define SERIAL_RX D5 // pin for SoftwareSerial RX -SoftwareSerial mySerial(SERIAL_RX, -1, true, MAXLINELENGTH); // (RX, TX. inverted, buffer) +// P1 Serial settings +#define SERIAL_RX D1 // pin for SoftwareSerial RX +SoftwareSerial mySerial(SERIAL_RX, -1, false); // (RX, TX, inverted) -unsigned int currentCRC=0; - -void SendToDomoLog(char* message) +void setup() { - char url[512]; - sprintf(url, "http://%s:%d/json.htm?type=command¶m=addlogmessage&message=%s", domoticzIP, domoticzPort, message); -} - -void setup() { + // Init Serial Serial.begin(115200); - Serial.println("Booting"); + 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) { + 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); - // Port defaults to 8266 - // ArduinoOTA.setPort(8266); + // Init ArduinoOTA + //// Port defaults to 8266 + ArduinoOTA.setPort(8266); - // Hostname defaults to esp8266-[ChipID] + //// Hostname defaults to esp8266-[ChipID] ArduinoOTA.setHostname(hostName); - // No authentication by default + //// 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"); + 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(); - Serial.println("Ready"); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); + + // 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(); +} -bool SendToDomo(int idx, int nValue, char* sValue) +// 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 url[255]; - sprintf(url, "http://%s:%d/json.htm?type=command¶m=udevice&idx=%d&nvalue=%d&svalue=%s", domoticzIP, domoticzPort, idx, nValue, sValue); - Serial.printf("[HTTP] GET... URL: %s\n",url); - http.begin(url); //HTTP - int httpCode = http.GET(); + 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) { + 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() { - //sends over the gas setting to domoticz - if(prevGAS!=mGAS) + if (prevGAS != mGAS) { char sValue[10]; sprintf(sValue, "%d", mGAS); - if(SendToDomo(domoticzGasIdx, 0, sValue)) - prevGAS=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); - SendToDomo(domoticzEneryIdx, 0, sValue); + Send(energyIdx, 0, sValue); } - -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)) { +// 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; } -int FindCharInArrayRev(char array[], char c, int len) { - for (int i = len - 1; i >= 0; i--) { - if (array[i] == c) { +// 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; + if (valOld > 0 && ((valNew - valOld > maxDiffer) && (valOld - valNew > maxDiffer))) + return valOld; + return valNew; } -long getValue(char* buffer, int maxlen) { +// 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; + 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; + 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)) { + if (strncpy(res, buffer + s + 1, l)) + { + if (isNumber(res, l)) + { return (1000 * atof(res)); } } return 0; } -bool decodeTelegram(int len) { +// 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) + if (startChar >= 0) { //start found. Reset CRC calculation - currentCRC=CRC16(0x0000,(unsigned char *) telegram+startChar, len-startChar); - if(outputOnSerial) + currentCRC = CRC16(0x0000, (unsigned char *)telegram + startChar, len - startChar); + if (outputOnSerial) { - for(int cnt=startChar; cnt=0) + else if (endChar >= 0) { - //add to crc calc - currentCRC=CRC16(currentCRC,(unsigned char*)telegram+endChar, 1); + //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) + messageCRC[4] = 0; //thanks to HarmOtten (issue 5) + if (outputOnSerial) { - for(int cnt=0; cntGND on ESP, RTS->3.3V on ESP and RxD->any digital pin on ESP. In this sketch I use D5