Skip to content

Instantly share code, notes, and snippets.

@timkoers
Last active June 28, 2018 17:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timkoers/667563ebb2b13adc4c30339b4bc9ddcf to your computer and use it in GitHub Desktop.
Save timkoers/667563ebb2b13adc4c30339b4bc9ddcf to your computer and use it in GitHub Desktop.
This is the code for (hopefully) simulating a P1 smart meter.
#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
// 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)", &currentPhaseOne) > 0) {
}
if (sscanf(line, "1-0:51.7.0*255(%f*A)", &currentPhaseTwo) > 0) {
}
if (sscanf(line, "1-0:71.7.0*255(%f*A)", &currentPhaseThree) > 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