Updated readme

This commit is contained in:
Arne van Iterson 2021-02-04 12:41:51 +01:00
parent 6bd04eda97
commit bd566a4b39
3 changed files with 413 additions and 122 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.vscode
*.code-workspace

View File

@ -1,22 +1,68 @@
// #include <TimeLib.h> #include <TimeLib.h>
#include <NTPClient.h>
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESP8266mDNS.h> #include <ESP8266mDNS.h>
#include <ESP8266HTTPClient.h> #include <ESP8266HTTPClient.h>
#include <WiFiUdp.h> #include <WiFiUdp.h>
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
#include <Button2.h>
#include <Timer.h>
#include "CRC16.h" #include "CRC16.h"
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Fonts/Org_01.h>
#include <Adafruit_PCD8544.h>
//===Change values from here=== // Function prototypes
const char* ssid = "WIFISSID"; void reboot();
const char* password = "PASSWORD"; 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"; const char *hostName = "ESPP1Meter";
const char* domoticzIP = "192.168.1.35";
const int domoticzPort = 8090; // Server settings
const int domoticzGasIdx = 291; const char *apiIP = "192.168.2.6";
const int domoticzEneryIdx = 294; const int apiPort = 3000;
const bool outputOnSerial = true; const char *apiKey = "*";
//===Change values to here=== 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 // Vars to store meter readings
long mEVLT = 0; //Meter reading Electrics - consumption low tariff long mEVLT = 0; //Meter reading Electrics - consumption low tariff
@ -28,135 +74,350 @@ long mEAT = 0; //Meter reading Electrics - Actual return
long mGAS = 0; //Meter reading Gas long mGAS = 0; //Meter reading Gas
long prevGAS = 0; long prevGAS = 0;
#define MAXLINELENGTH 128 // longest normal line is 47 char (+3 for \r\n\0) #define MAXLINELENGTH 128 // longest normal line is 47 char (+3 for \r\n\0)
char telegram[MAXLINELENGTH]; char telegram[MAXLINELENGTH];
#define SERIAL_RX D5 // pin for SoftwareSerial RX
SoftwareSerial mySerial(SERIAL_RX, -1, true, MAXLINELENGTH); // (RX, TX. inverted, buffer)
unsigned int currentCRC = 0; unsigned int currentCRC = 0;
void SendToDomoLog(char* message) // P1 Serial settings
{ #define SERIAL_RX D1 // pin for SoftwareSerial RX
char url[512]; SoftwareSerial mySerial(SERIAL_RX, -1, false); // (RX, TX, inverted)
sprintf(url, "http://%s:%d/json.htm?type=command&param=addlogmessage&message=%s", domoticzIP, domoticzPort, message);
}
void setup() { void setup()
{
// Init Serial
Serial.begin(115200); 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); WiFi.mode(WIFI_STA);
display.println("Connecting to:");
display.print(" ");
display.println(ssid);
display.display();
WiFi.begin(ssid, password); WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) { while (WiFi.waitForConnectResult() != WL_CONNECTED)
{
Serial.println("Connection Failed! Rebooting..."); Serial.println("Connection Failed! Rebooting...");
drawMenu();
display.println("Connection Failed!");
display.display();
delay(5000); delay(5000);
ESP.restart(); ESP.restart();
} }
// Init P1 Serial
mySerial.begin(115200); mySerial.begin(115200);
// Port defaults to 8266 // Init ArduinoOTA
// ArduinoOTA.setPort(8266); //// Port defaults to 8266
ArduinoOTA.setPort(8266);
// Hostname defaults to esp8266-[ChipID] //// Hostname defaults to esp8266-[ChipID]
ArduinoOTA.setHostname(hostName); ArduinoOTA.setHostname(hostName);
// No authentication by default //// No authentication by default
// ArduinoOTA.setPassword((const char *)"123"); // ArduinoOTA.setPassword((const char *)"123");
ArduinoOTA.onStart([]() { ArduinoOTA.onStart([]() {
drawMenu();
display.println("OTA Update");
display.display();
Serial.println("Start"); Serial.println("Start");
}); });
ArduinoOTA.onEnd([]() { ArduinoOTA.onEnd([]() {
Serial.println("\nEnd"); Serial.println("\nEnd");
}); });
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 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))); Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
}); });
ArduinoOTA.onError([](ota_error_t error) { ArduinoOTA.onError([](ota_error_t error) {
drawMenu();
display.println("OTA Update");
display.printf("Error[%u]: ", error);
display.display();
Serial.printf("Error[%u]: ", error); Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); if (error == OTA_AUTH_ERROR)
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); Serial.println("Auth Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); else if (error == OTA_BEGIN_ERROR)
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); Serial.println("Begin Failed");
else if (error == OTA_END_ERROR) Serial.println("End 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(); ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: "); // Init Time
Serial.println(WiFi.localIP()); timeClient.begin();
time_t test = NTPgetTime();
while (test < 10000)
{
test = NTPgetTime();
} }
setTime(NTPgetTime());
getExternalTime NtpTime = &NTPgetTime;
setSyncProvider(NtpTime);
setSyncInterval(3600);
bool SendToDomo(int idx, int nValue, char* sValue) 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; HTTPClient http;
bool retVal = false; bool retVal = false;
char url[255]; char httpRequestData[255];
sprintf(url, "http://%s:%d/json.htm?type=command&param=udevice&idx=%d&nvalue=%d&svalue=%s", domoticzIP, domoticzPort, idx, nValue, sValue);
Serial.printf("[HTTP] GET... URL: %s\n",url); // "http://%s:%d/json.htm?type=command&param=udevice&idx=%d&nvalue=%d&svalue=%s"
http.begin(url); //HTTP // "api_key=tPmAT5Ab3j7F9&sensor=BME280&value1=24.25&value2=49.54&value3=1005.14"
int httpCode = http.GET(); 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 // httpCode will be negative on error
if (httpCode > 0) if (httpCode > 0)
{ // HTTP header has been send and Server response header has been handled { // HTTP header has been send and Server response header has been handled
Serial.printf("[HTTP] GET... code: %d\n", httpCode); Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server // file found at server
if (httpCode == HTTP_CODE_OK) { if (httpCode == HTTP_CODE_OK)
{
String payload = http.getString(); String payload = http.getString();
retVal = true; retVal = true;
} }
} }
else else
{ {
postedFailed++;
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
} }
http.end(); http.end();
return retVal; return retVal;
} }
// Update Gas info
void UpdateGas() void UpdateGas()
{ {
//sends over the gas setting to domoticz
if (prevGAS != mGAS) if (prevGAS != mGAS)
{ {
char sValue[10]; char sValue[10];
sprintf(sValue, "%d", mGAS); sprintf(sValue, "%d", mGAS);
if(SendToDomo(domoticzGasIdx, 0, sValue)) if (Send(gasIdx, 0, sValue))
prevGAS = mGAS; prevGAS = mGAS;
} }
} }
// Update Electricity info
void UpdateElectricity() void UpdateElectricity()
{ {
char sValue[255]; char sValue[255];
sprintf(sValue, "%d;%d;%d;%d;%d;%d", mEVLT, mEVHT, mEOLT, mEOHT, mEAV, mEAT); sprintf(sValue, "%d;%d;%d;%d;%d;%d", mEVLT, mEVHT, mEOLT, mEOHT, mEAV, mEAT);
SendToDomo(domoticzEneryIdx, 0, sValue); Send(energyIdx, 0, sValue);
} }
// Check if value is number
bool isNumber(char* res, int len) { 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)) { for (int i = 0; i < len; i++)
{
if (((res[i] < '0') || (res[i] > '9')) && (res[i] != '.' && res[i] != 0))
{
return false; return false;
} }
} }
return true; return true;
} }
int FindCharInArrayRev(char array[], char c, int len) { // Find char in array
for (int i = len - 1; i >= 0; i--) { int FindCharInArrayRev(char array[], char c, int len)
if (array[i] == c) { {
for (int i = len - 1; i >= 0; i--)
{
if (array[i] == c)
{
return i; return i;
} }
} }
return -1; return -1;
} }
// Check if value is valid
long getValidVal(long valNew, long valOld, long maxDiffer) long getValidVal(long valNew, long valOld, long maxDiffer)
{ {
//check if the incoming value is valid //check if the incoming value is valid
@ -165,25 +426,35 @@ long getValidVal(long valNew, long valOld, long maxDiffer)
return valNew; 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); int s = FindCharInArrayRev(buffer, '(', maxlen - 2);
if (s < 8) return 0; if (s < 8)
if (s > 32) s = 32; return 0;
if (s > 32)
s = 32;
int l = FindCharInArrayRev(buffer, '*', maxlen - 2) - s - 1; int l = FindCharInArrayRev(buffer, '*', maxlen - 2) - s - 1;
if (l < 4) return 0; if (l < 4)
if (l > 12) return 0; return 0;
if (l > 12)
return 0;
char res[16]; char res[16];
memset(res, 0, sizeof(res)); memset(res, 0, sizeof(res));
if (strncpy(res, buffer + s + 1, l)) { if (strncpy(res, buffer + s + 1, l))
if (isNumber(res, l)) { {
if (isNumber(res, l))
{
return (1000 * atof(res)); return (1000 * atof(res));
} }
} }
return 0; return 0;
} }
bool decodeTelegram(int len) { // Decode P1 telegram
bool decodeTelegram(int len)
{
//need to check for start //need to check for start
int startChar = FindCharInArrayRev(telegram, '/', len); int startChar = FindCharInArrayRev(telegram, '/', len);
int endChar = FindCharInArrayRev(telegram, '!', len); int endChar = FindCharInArrayRev(telegram, '!', len);
@ -197,8 +468,8 @@ bool decodeTelegram(int len) {
for (int cnt = startChar; cnt < len - startChar; cnt++) for (int cnt = startChar; cnt < len - startChar; cnt++)
Serial.print(telegram[cnt]); Serial.print(telegram[cnt]);
} }
//Serial.println("Start found!"); Serial.println("Start found!");
telegramsTotal++;
} }
else if (endChar >= 0) else if (endChar >= 0)
{ {
@ -214,9 +485,14 @@ bool decodeTelegram(int len) {
} }
validCRCFound = (strtol(messageCRC, NULL, 16) == currentCRC); validCRCFound = (strtol(messageCRC, NULL, 16) == currentCRC);
if (validCRCFound) if (validCRCFound)
{
Serial.println("\nVALID CRC FOUND!"); Serial.println("\nVALID CRC FOUND!");
}
else else
Serial.println("\n===INVALID CRC FOUND!==="); {
Serial.println("\nINVALID CRC FOUND!");
telegramsInvalid++;
}
currentCRC = 0; currentCRC = 0;
} }
else else
@ -236,25 +512,21 @@ bool decodeTelegram(int len) {
if (strncmp(telegram, "1-0:1.8.1", strlen("1-0:1.8.1")) == 0) if (strncmp(telegram, "1-0:1.8.1", strlen("1-0:1.8.1")) == 0)
mEVLT = getValue(telegram, len); mEVLT = getValue(telegram, len);
// 1-0:1.8.2(000560.157*kWh) // 1-0:1.8.2(000560.157*kWh)
// 1-0:1.8.2 = Elektra verbruik hoog tarief (DSMR v4.0) // 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) if (strncmp(telegram, "1-0:1.8.2", strlen("1-0:1.8.2")) == 0)
mEVHT = getValue(telegram, len); mEVHT = getValue(telegram, len);
// 1-0:2.8.1(000348.890*kWh) // 1-0:2.8.1(000348.890*kWh)
// 1-0:2.8.1 = Elektra opbrengst laag tarief (DSMR v4.0) // 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) if (strncmp(telegram, "1-0:2.8.1", strlen("1-0:2.8.1")) == 0)
mEOLT = getValue(telegram, len); mEOLT = getValue(telegram, len);
// 1-0:2.8.2(000859.885*kWh) // 1-0:2.8.2(000859.885*kWh)
// 1-0:2.8.2 = Elektra opbrengst hoog tarief (DSMR v4.0) // 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) if (strncmp(telegram, "1-0:2.8.2", strlen("1-0:2.8.2")) == 0)
mEOHT = getValue(telegram, len); mEOHT = getValue(telegram, len);
// 1-0:1.7.0(00.424*kW) Actueel verbruik // 1-0:1.7.0(00.424*kW) Actueel verbruik
// 1-0:2.7.0(00.000*kW) Actuele teruglevering // 1-0:2.7.0(00.000*kW) Actuele teruglevering
// 1-0:1.7.x = Electricity consumption actual usage (DSMR v4.0) // 1-0:1.7.x = Electricity consumption actual usage (DSMR v4.0)
@ -264,7 +536,6 @@ bool decodeTelegram(int len) {
if (strncmp(telegram, "1-0:2.7.0", strlen("1-0:2.7.0")) == 0) if (strncmp(telegram, "1-0:2.7.0", strlen("1-0:2.7.0")) == 0)
mEAT = getValue(telegram, len); mEAT = getValue(telegram, len);
// 0-1:24.2.1(150531200000S)(00811.923*m3) // 0-1:24.2.1(150531200000S)(00811.923*m3)
// 0-1:24.2.1 = Gas (DSMR v4.0) on Kaifa MA105 meter // 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) if (strncmp(telegram, "0-1:24.2.1", strlen("0-1:24.2.1")) == 0)
@ -273,10 +544,14 @@ bool decodeTelegram(int len) {
return validCRCFound; return validCRCFound;
} }
void readTelegram() { // Read P1 telegram
if (mySerial.available()) { void readTelegram()
{
if (mySerial.available())
{
memset(telegram, 0, sizeof(telegram)); memset(telegram, 0, sizeof(telegram));
while (mySerial.available()) { while (mySerial.available())
{
int len = mySerial.readBytesUntil('\n', telegram, MAXLINELENGTH); int len = mySerial.readBytesUntil('\n', telegram, MAXLINELENGTH);
telegram[len] = '\n'; telegram[len] = '\n';
telegram[len + 1] = 0; telegram[len + 1] = 0;
@ -290,12 +565,18 @@ void readTelegram() {
} }
} }
void loop()
{
void loop() { if (hour() == 0 && minute() == 0 && second() == 0)
readTelegram(); {
ArduinoOTA.handle(); reboot();
} }
readTelegram();
button.loop();
t.update();
ArduinoOTA.handle();
}

View File

@ -1,15 +1,23 @@
# P1-Meter-ESP8266 # P1-Meter-ESP8266
## Original code by [jantenhove on GitHub](https://github.com/jantenhove/P1-Meter-ESP8266), I altered the code to add a screen and make it work with my own server. The original code works with Domoticz, be sure to check it out!
Software for the ESP2866 that sends P1 smart meter data to Domoticz (with CRC checking and OTA firmware updates) Software for the ESP2866 that sends P1 smart meter data to Domoticz (with CRC checking and OTA firmware updates)
### Installation instrucions ### Installation instrucions
- Make sure that your ESP8266 can be flashed from the Arduino environnment: https://github.com/esp8266/Arduino
- Install the SoftSerial library from: https://github.com/plerup/espsoftwareserial * Make sure that your ESP8266 can be flashed from the Arduino environnment: https://github.com/esp8266/Arduino
- Place all files from this repository in a directory. Open the .ino file. * Install the SoftSerial library from: https://github.com/plerup/espsoftwareserial
- Adjust WIFI, Domoticz and debug settings at the top of the file * Place all files from this repository in a directory. Open the .ino file.
- Compile and flash * Adjust WIFI, Domoticz and debug settings at the top of the file
* Compile and flash
### Connection of the P1 meter to the ESP8266 ### Connection of the P1 meter to the ESP8266
You need to connect the smart meter with a RJ11 connector. This is the pinout to use You need to connect the smart meter with a RJ11 connector. This is the pinout to use
![RJ11 P1 connetor](http://gejanssen.com/howto/Slimme-meter-uitlezen/RJ11-pinout.png) ![RJ11 P1 connetor](http://gejanssen.com/howto/Slimme-meter-uitlezen/RJ11-pinout.png)
Connect GND->GND on ESP, RTS->3.3V on ESP and RxD->any digital pin on ESP. In this sketch I use D5 Connect GND->GND on ESP, RTS->3.3V on ESP and RxD->any digital pin on ESP. In this sketch I use D5