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 <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPClient.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <Button2.h>
#include <Timer.h>
#include "CRC16.h"
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Fonts/Org_01.h>
#include <Adafruit_PCD8544.h>
//===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&param=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&param=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&param=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<len-startChar;cnt++)
for (int cnt = startChar; cnt < len - startChar; cnt++)
Serial.print(telegram[cnt]);
}
//Serial.println("Start found!");
}
Serial.println("Start found!");
telegramsTotal++;
}
else if(endChar>=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; cnt<len;cnt++)
for (int cnt = 0; cnt < len; cnt++)
Serial.print(telegram[cnt]);
}
}
validCRCFound = (strtol(messageCRC, NULL, 16) == currentCRC);
if(validCRCFound)
Serial.println("\nVALID CRC FOUND!");
if (validCRCFound)
{
Serial.println("\nVALID CRC FOUND!");
}
else
Serial.println("\n===INVALID CRC FOUND!===");
{
Serial.println("\nINVALID CRC FOUND!");
telegramsInvalid++;
}
currentCRC = 0;
}
else
{
currentCRC=CRC16(currentCRC, (unsigned char*)telegram, len);
if(outputOnSerial)
currentCRC = CRC16(currentCRC, (unsigned char *)telegram, len);
if (outputOnSerial)
{
for(int cnt=0; cnt<len;cnt++)
for (int cnt = 0; cnt < len; cnt++)
Serial.print(telegram[cnt]);
}
}
long val =0;
long val2=0;
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);
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)
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)
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)
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)
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)
if (strncmp(telegram, "0-1:24.2.1", strlen("0-1:24.2.1")) == 0)
mGAS = getValue(telegram, len);
return validCRCFound;
}
void readTelegram() {
if (mySerial.available()) {
// Read P1 telegram
void readTelegram()
{
if (mySerial.available())
{
memset(telegram, 0, sizeof(telegram));
while (mySerial.available()) {
while (mySerial.available())
{
int len = mySerial.readBytesUntil('\n', telegram, MAXLINELENGTH);
telegram[len] = '\n';
telegram[len+1] = 0;
telegram[len + 1] = 0;
yield();
if(decodeTelegram(len+1))
if (decodeTelegram(len + 1))
{
UpdateElectricity();
UpdateGas();
UpdateElectricity();
UpdateGas();
}
}
}
}
}
void loop()
{
if (hour() == 0 && minute() == 0 && second() == 0)
{
reboot();
}
void loop() {
readTelegram();
button.loop();
t.update();
ArduinoOTA.handle();
}

View File

@ -1,15 +1,23 @@
# 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)
### 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
- Place all files from this repository in a directory. Open the .ino file.
- Adjust WIFI, Domoticz and debug settings at the top of the file
- Compile and flash
* 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
* Place all files from this repository in a directory. Open the .ino file.
* Adjust WIFI, Domoticz and debug settings at the top of the file
* Compile and flash
### 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
![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