Last active
June 28, 2018 17:52
-
-
Save timkoers/667563ebb2b13adc4c30339b4bc9ddcf to your computer and use it in GitHub Desktop.
This is the code for (hopefully) simulating a P1 smart meter.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef CRC16_H | |
#define CRC16_H | |
// https://github.com/jantenhove/P1-Meter-ESP8266 | |
#if defined(ARDUINO) && ARDUINO >= 100 | |
#include "Arduino.h" | |
#else | |
#include "WProgram.h" | |
#endif | |
unsigned int CRC16(unsigned int crc, unsigned char *buf, int len) | |
{ | |
for (int pos = 0; pos < len; pos++) | |
{ | |
crc ^= (unsigned int)buf[pos]; // XOR byte into least sig. byte of crc | |
for (int i = 8; i != 0; i--) { // Loop over each bit | |
if ((crc & 0x0001) != 0) { // If the LSB is set | |
crc >>= 1; // Shift right and XOR 0xA001 | |
crc ^= 0xA001; | |
} | |
else // Else LSB is not set | |
crc >>= 1; // Just shift right | |
} | |
} | |
return crc; | |
} | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Tim Koers - 2018 | |
#include <WiFi.h> | |
#include <ESPmDNS.h> | |
#include <WiFiClientSecure.h> | |
#include <PubSubClient.h> | |
#include <SPIFFS.h> | |
#include <ArduinoOTA.h> | |
#include "CRC16.h" | |
/** | |
"/ISk5\2MT174-001\r\n"\ | |
"1-3:0.2.8(50)\r\n"\ // DSMR version | |
"0-0:1.0.0(%2d%2d%2d%2d%2d%2dW)\r\n"\ // Date-time timestamp of the P1 message (101209113020W) (2010, december 9th, 11h30m20s) -> (yymmhhmmss) | |
"0-0:96.1.1(4B384547303034303436333935353037)\r\n"\ // Equipment identifier | |
"1-0:1.8.1(%f*kWh)\r\n"\ // Active Energy Import (+A) Low tarrif | |
"1-0:1.8.2(%f*kWh)\r\n"\ // Active Energy Import (+A) Low normal/high | |
"1-0:2.8.1(%f*kWh)\r\n"\ // Active Energy Export (-A) Low tarrif | |
"1-0:2.8.2(%f*kWh)\r\n"\ // Active Energy Export (-A) Normal/High tarrif | |
"0-0:96.14.0(%4d)\r\n"\ // Tarrif indicator | |
"1-0:1.7.0(%f*kW)\r\n"\ // Actual electricity power delivered (+P) | |
"1-0:2.7.0(%f*kW)\r\n"\ // Actual electricity power received (-P) | |
"0-0:96.7.21(00000)\r\n"\ // Number of power failures in any phase | |
"0-0:96.7.9(00000)\r\n"\ // Number of long power failures in any phase | |
"1-0:99.97.0(0)(0-0:96.7.19)\r\n"\ // Power failure event log | |
"1-0:32.32.0(00000)\r\n"\ // Number of voltage sags in L1 | |
"1-0:52.32.0(00000)\r\n"\ // Number of voltage sags in L2 | |
"1-0:72.32.0(00000)\r\n"\ // Number of voltage sags in L3 | |
"1-0:32.36.0(00000)\r\n"\ // Number of voltage swells in L1 | |
"1-0:52.36.0(00000)\r\n"\ // Number of voltage swells in L2 | |
"1-0:72.36.0(00000)\r\n"\ // Number of voltage swells in L3 | |
"0-0:96.13.0(0)\r\n"\ // Other devices on M-Bus | |
"1-0:32.7.0(%f*V)\r\n"\ // Instantaneous voltage L1 | |
"1-0:52.7.0(%f*V)\r\n"\ // Instantaneous voltage L2 | |
"1-0:72.7.0(%f*V)\r\n"\ // Instantaneous voltage L3 | |
"1-0:31.7.0(%d*A)\r\n"\ // Instantaneous current L1 | |
"1-0:51.7.0(%d*A)\r\n"\ // Instantaneous current L2 | |
"1-0:71.7.0(%d*A)\r\n"\ // Instantaneous current L3 | |
"1-0:21.7.0(%f*kW)\r\n"\ // Instantaneous active power L1 (+P) | |
"1-0:41.7.0(%f*kW)\r\n"\ // Instantaneous active power L2 (+P) | |
"1-0:61.7.0(%f*kW)\r\n"\ // Instantaneous active power L3 (+P) | |
"1-0:22.7.0(%f*kW))\r\n"\ // Instantaneous active power L1 (-P) | |
"1-0:42.7.0(%f*kW))\r\n"\ // Instantaneous active power L2 (-P) | |
"1-0:62.7.0(%f*kW))\r\n"\ // Instantaneous active power L3 (-P) | |
"!EF2F\r\n" | |
*/ | |
#define SENDING_INTERVAL 1000 | |
#define ENDING_LINE "!%4x\r\n" | |
char *DSMR_5_0_2_TELEGRAM[] = {"/ISk5\\2MT174-001\r\n", | |
"1-3:0.2.8(50)\r\n", | |
"0-0:1.0.0(%02d%02d%02d%02d%02d%02dW)\r\n", | |
"1-0:1.8.1(%07.3f*kWh)\r\n", | |
"1-0:1.8.2(%07.3f*kWh)\r\n", | |
"1-0:2.8.1(%07.3f*kWh)\r\n", | |
"1-0:2.8.2(%07.3f*kWh)\r\n", | |
"0-0:96.14.0(%04d)\r\n", | |
"1-0:1.7.0(%f*kW)\r\n", | |
"1-0:2.7.0(%f*kW)\r\n", | |
"0-0:96.7.21(00000)\r\n", | |
"0-0:96.7.9(00000)\r\n", | |
"1-0:99.97.0(0)(0-0:96.7.19)\r\n", | |
"1-0:32.32.0(00000)\r\n", | |
"1-0:52.32.0(00000)\r\n", | |
"1-0:72.32.0(00000)\r\n", | |
"1-0:32.36.0(00000)\r\n", | |
"1-0:52.36.0(00000)\r\n", | |
"1-0:72.36.0(00000)\r\n", | |
"0-0:96.13.0(0)\r\n", | |
"1-0:32.7.0(%03.1f*V)\r\n", | |
"1-0:52.7.0(%03.1f*V)\r\n", | |
"1-0:72.7.0(%03.1f*V)\r\n", | |
"1-0:31.7.0(%03.2f*A)\r\n", | |
"1-0:51.7.0(%03.2f*A)\r\n", | |
"1-0:71.7.0(%03.2f*A)\r\n", | |
"!\r\n" | |
}; | |
//"1-0:21.7.0(%f*kW)\r\n"\ | |
//"1-0:41.7.0(%f*kW)\r\n"\ | |
//"1-0:61.7.0(%f*kW)\r\n"\ | |
//"1-0:22.7.0(%f*kW))\r\n"\ | |
//"1-0:42.7.0(%f*kW))\r\n"\ | |
//"1-0:62.7.0(%f*kW))\r\n"\ | |
//"0-1:24.1.0(003)\r\n"\ // Device type | |
//"0-1:96.1.0(0)\r\n"\ // Equipment identifier (Gas) | |
//"0-1:24.2.1(101209112500W)(12785.123*m3)\r\n"\ // Last 5-minute value gas delivered to client in m3 (time value)(gas delivered) | |
long previousMessage; | |
float previousActiveEnergyImport; // 1.8.0 | |
float previousActiveEnergyExport; // 2.8.0 | |
float activeEnergyImport; // 1.8.0 | |
float activeEnergyImportLowTarrif; // 1.8.1 | |
float activeEnergyImportNormalorHighTarrif; // 1.8.2 | |
float activeEnergyExport; // 2.8.0 | |
float previousActiveEnergyExportLowTarrif; // 2.8.1 | |
float previousActiveEnergyExportNormalorHighTarrif; // 2.8.2 | |
float activeEnergyExportLowTarrif; // 2.8.1 | |
float activeEnergyExportNormalorHighTarrif; // 2.8.2 | |
float voltagePhaseOne; // 32.7.0 | |
float voltagePhaseTwo; // 52.7.0 | |
float voltagePhaseThree; //72.7.0 | |
float currentPhaseOne; // 31.7.0 | |
float currentPhaseTwo; // 51.7.0 | |
float currentPhaseThree; // 71.7.0 | |
float activeImportPowerPhaseOne; // 21.7.0 | |
float activeImportPowerPhaseTwo; // 41.7.0 | |
float activeImportPowerPhaseThree; // 61.7.0 | |
float activeExportPowerPhaseOne; // 22.7.0 | |
float activeExportPowerPhaseTwo; // 42.7.0 | |
float activeExportPowerPhaseThree; // 62.7.0 | |
float actualPowerDeliveredToClient; // 1.7.0 (kW) | |
float actualPowerReceivedFromClient; // 2.7.0 (kW) | |
const int timeZone = 1; // Central European Time | |
//const int timeZone = -5; // Eastern Standard Time (USA) | |
//const int timeZone = -4; // Eastern Daylight Time (USA) | |
//const int timeZone = -8; // Pacific Standard Time (USA) | |
//const int timeZone = -7; // Pacific Daylight Time (USA) | |
char* p1Buffer; | |
long sendingInterval; | |
long p1Length; | |
// https://remotemonitoringsystems.ca/time-zone-abbreviations.php | |
const char* NTP_SERVER = "de.pool.ntp.org"; | |
const char* TZ_INFO = "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00"; | |
struct tm local; | |
void setup(){ | |
Serial.begin(115200); | |
pinMode(LED_BUILTIN, OUTPUT); | |
configTzTime(TZ_INFO, NTP_SERVER); | |
getLocalTime(&local, 1000); // wait up to 10sec to sync | |
while (local.tm_year == 1970) { | |
getLocalTime(&local, 1000); // wait up to 10sec to sync | |
} | |
Serial.println(&local, "%B %d %Y %H:%M:%S (%A)"); | |
} | |
void loop(){ | |
getLocalTime(&local); | |
if ((millis() - sendingInterval) > SENDING_INTERVAL && p1Length > 0) { | |
sendingInterval = millis(); | |
char outputBuffer[27][32]; | |
if (snprintf(outputBuffer[2], 27, DSMR_5_0_2_TELEGRAM[2], (local.tm_year - 100), local.tm_mon + 1, local.tm_mday, local.tm_hour, local.tm_min, local.tm_sec) <= 0) { | |
client.publish(debug_topic, "Failed to set line 2"); | |
} | |
if (snprintf(outputBuffer[3], 30, DSMR_5_0_2_TELEGRAM[3], activeEnergyImportLowTarrif) <= 0) { | |
client.publish(debug_topic, "Failed to set line 3"); | |
} | |
if (snprintf(outputBuffer[4], 26, DSMR_5_0_2_TELEGRAM[4], activeEnergyImportNormalorHighTarrif) <= 0) { | |
client.publish(debug_topic, "Failed to set line 4"); | |
} | |
if (snprintf(outputBuffer[5], 26, DSMR_5_0_2_TELEGRAM[5], activeEnergyExportLowTarrif) <= 0) { | |
client.publish(debug_topic, "Failed to set line 5"); | |
} | |
if (snprintf(outputBuffer[6], 26, DSMR_5_0_2_TELEGRAM[6], activeEnergyExportNormalorHighTarrif) <= 0) { | |
client.publish(debug_topic, "Failed to set line 6"); | |
} | |
if (snprintf(outputBuffer[7], 20, DSMR_5_0_2_TELEGRAM[7], (local.tm_hour > 23 && local.tm_hour < 7) ? 1 : 2) <= 0) { | |
client.publish(debug_topic, "Failed to set line 7"); | |
} | |
if (snprintf(outputBuffer[8], 25, DSMR_5_0_2_TELEGRAM[8], actualPowerDeliveredToClient) <= 0) { | |
client.publish(debug_topic, "Failed to set line 6"); | |
} | |
if (snprintf(outputBuffer[9], 25, DSMR_5_0_2_TELEGRAM[9], actualPowerReceivedFromClient) <= 0) { | |
client.publish(debug_topic, "Failed to set line 9"); | |
} | |
if (snprintf(outputBuffer[20], 23, DSMR_5_0_2_TELEGRAM[20], voltagePhaseOne) <= 0) { | |
client.publish(debug_topic, "Failed to set line 20"); | |
} | |
if (snprintf(outputBuffer[21], 23, DSMR_5_0_2_TELEGRAM[21], voltagePhaseTwo) <= 0) { | |
client.publish(debug_topic, "Failed to set line 21"); | |
} | |
if (snprintf(outputBuffer[22], 23, DSMR_5_0_2_TELEGRAM[22], voltagePhaseThree) <= 0) { | |
client.publish(debug_topic, "Failed to set line 22"); | |
} | |
if (snprintf(outputBuffer[23], 21, DSMR_5_0_2_TELEGRAM[23], currentPhaseOne) <= 0) { | |
client.publish(debug_topic, "Failed to set line 23"); | |
} | |
if (snprintf(outputBuffer[24], 21, DSMR_5_0_2_TELEGRAM[24], currentPhaseTwo) <= 0) { | |
client.publish(debug_topic, "Failed to set line 24"); | |
} | |
if (snprintf(outputBuffer[25], 21, DSMR_5_0_2_TELEGRAM[25], currentPhaseThree) <= 0) { | |
client.publish(debug_topic, "Failed to set line 25"); | |
} | |
memcpy(outputBuffer[0], DSMR_5_0_2_TELEGRAM[0], 19); | |
memcpy(outputBuffer[1], DSMR_5_0_2_TELEGRAM[1], 17); | |
memcpy(outputBuffer[10], DSMR_5_0_2_TELEGRAM[10], 22); | |
memcpy(outputBuffer[11], DSMR_5_0_2_TELEGRAM[11], 21); | |
memcpy(outputBuffer[12], DSMR_5_0_2_TELEGRAM[12], 31); | |
memcpy(outputBuffer[13], DSMR_5_0_2_TELEGRAM[13], 22); | |
memcpy(outputBuffer[14], DSMR_5_0_2_TELEGRAM[14], 22); | |
memcpy(outputBuffer[15], DSMR_5_0_2_TELEGRAM[15], 22); | |
memcpy(outputBuffer[16], DSMR_5_0_2_TELEGRAM[16], 22); | |
memcpy(outputBuffer[17], DSMR_5_0_2_TELEGRAM[17], 22); | |
memcpy(outputBuffer[18], DSMR_5_0_2_TELEGRAM[18], 22); | |
memcpy(outputBuffer[19], DSMR_5_0_2_TELEGRAM[19], 18); | |
// Calculate the CRC | |
unsigned int currentCRC = 0; | |
for (int i = 0; i < sizeof(outputBuffer); i++) { | |
currentCRC = CRC16(currentCRC, (unsigned char*)outputBuffer[i], strlen((char*)outputBuffer[i])); | |
} | |
if (snprintf(outputBuffer[26], 9, ENDING_LINE, currentCRC) <= 0) { | |
client.publish(debug_topic, "Failed to add CRC"); | |
} | |
for (int i = 0; i < 27; i++) { | |
Serial.write(outputBuffer[i]); | |
} | |
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); | |
} | |
} | |
void fillInValues(){ | |
previousActiveEnergyImport = activeEnergyImport; | |
previousActiveEnergyExport = activeEnergyExport; | |
char* lines = strtok(p1Buffer, "\r\n"); | |
char* line = lines; | |
bool reachedEnd = false; | |
while (!reachedEnd) { | |
if (sscanf(line, "1-0:1.8.0*255(%f*kWh)", &activeEnergyImport) > 0) { | |
} | |
if (sscanf(line, "1-0:1.8.1*255(%f*kWh)", &activeEnergyImportLowTarrif) > 0) { | |
} | |
if (sscanf(line, "1-0:1.8.2*255(%f*kWh)", &activeEnergyImportNormalorHighTarrif) > 0) { | |
} | |
if (sscanf(line, "1-0:2.8.0*255(%f*kWh)", &activeEnergyExport) > 0) { | |
} | |
if (sscanf(line, "1-0:2.8.1*255(%f*kWh)", &activeEnergyExportLowTarrif) > 0) { | |
} | |
if (sscanf(line, "1-0:2.8.2*255(%f*kWh)", &activeEnergyExportNormalorHighTarrif) > 0) { | |
reachedEnd = true; | |
} | |
/* | |
if (sscanf(line, "1-0:1.7.0*255(%f*kW)", &actualElectricityPowerDelivered) > 0) { | |
client.publish(debug_topic, "Failed to find 1.7.0"); | |
} | |
if (sscanf(line, "1-0:2.7.0*255(%f*kW)", &actualElectricityPowerReceived) > 0) { | |
client.publish(debug_topic, "Failed to find 2.7.0"); | |
}*/ | |
if (sscanf(line, "1-0:32.7.0*255(%f*V)", &voltagePhaseOne) > 0) { | |
} | |
if (sscanf(line, "1-0:52.7.0*255(%f*V)", &voltagePhaseTwo) > 0) { | |
} | |
if (sscanf(line, "1-0:72.7.0*255(%f*V)", &voltagePhaseThree) > 0) { | |
} | |
if (sscanf(line, "1-0:31.7.0*255(%f*A)", ¤tPhaseOne) > 0) { | |
} | |
if (sscanf(line, "1-0:51.7.0*255(%f*A)", ¤tPhaseTwo) > 0) { | |
} | |
if (sscanf(line, "1-0:71.7.0*255(%f*A)", ¤tPhaseThree) > 0) { | |
} | |
line++; | |
} | |
if (previousMessage != 0) { | |
long timeDifference = ((millis() - previousMessage) / 1000); | |
float importWattHour = ((activeEnergyImport - previousActiveEnergyImport) * 1000.0f); | |
float exportWattHour = ((activeEnergyExport - previousActiveEnergyExport) * 1000.0f); | |
actualPowerDeliveredToClient = (importWattHour / 1000.0f) / (timeDifference / 3600.0f); // Needs to be in kW | |
actualPowerReceivedFromClient = (exportWattHour / 1000.0f) / (timeDifference / 3600.0f); // Needs to be in kW | |
} | |
previousMessage = millis(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment