I’ve recently configured a custom sensor to read DSMR P1 data from Romande Energie through SlimmeLezer+ device.
To do so, I’ve reused some C code from Forsbergs
Find below the edited C code and the associated ESPhome configuration file.
Hope this will be useful for HA Swiss community.
p1reader.h
//-------------------------------------------------------------------------------------
// ESPHome P1 Electricity Meter custom sensor
// Copyright 2022 Sylvain Baron
//
// History
// 0.1.0 2022-09-01: Initial release
//
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-------------------------------------------------------------------------------------
#include "esphome.h"
#define BUF_SIZE 60
class ParsedMessage {
public:
double cumulativeActiveImport;
double cumulativeActiveExport;
double momentaryActiveImport;
double momentaryActiveExport;
double cumulativeActiveImportTarif1;
double cumulativeActiveImportTarif2;
double cumulativeActiveExportTarif1;
double cumulativeActiveExportTarif2;
double voltageL1;
double voltageL2;
double voltageL3;
double currentL1;
double currentL2;
double currentL3;
};
class P1Reader : public Component, public UARTDevice {
char buffer[BUF_SIZE];
const int8_t OUTSIDE_FRAME = 0;
const int8_t FOUND_FRAME = 1;
int8_t parseHDLCState = OUTSIDE_FRAME;
public:
Sensor *cumulativeActiveImport = new Sensor();
Sensor *cumulativeActiveExport = new Sensor();
Sensor *cumulativeReactiveImport = new Sensor();
Sensor *cumulativeReactiveExport = new Sensor();
Sensor *momentaryActiveImport = new Sensor();
Sensor *momentaryActiveExport = new Sensor();
Sensor *cumulativeActiveImportTarif1 = new Sensor();
Sensor *cumulativeActiveImportTarif2 = new Sensor();
Sensor *cumulativeActiveExportTarif1 = new Sensor();
Sensor *cumulativeActiveExportTarif2 = new Sensor();
Sensor *voltageL1 = new Sensor();
Sensor *voltageL2 = new Sensor();
Sensor *voltageL3 = new Sensor();
Sensor *currentL1 = new Sensor();
Sensor *currentL2 = new Sensor();
Sensor *currentL3 = new Sensor();
P1Reader(UARTComponent *parent) : UARTDevice(parent) {}
void setup() override { }
void loop() override {
readHDLCMessage();
}
private:
void parseRow(ParsedMessage* parsed, char* obisCode, double value) {
ESP_LOGD("parseRow", "OBIS %s value %f", obisCode, value);
if (strncmp(obisCode, "1.8.0", 5) == 0) {
parsed->cumulativeActiveImport = value;
}
else if (strncmp(obisCode, "2.8.0", 5) == 0) {
parsed->cumulativeActiveExport = value;
}
else if (strncmp(obisCode, "1.7.0", 5) == 0) {
parsed->momentaryActiveImport = value;
}
else if (strncmp(obisCode, "2.7.0", 5) == 0) {
parsed->momentaryActiveExport = value;
}
else if (strncmp(obisCode, "1.8.1", 5) == 0) {
parsed->cumulativeActiveImportTarif1 = value;
}
else if (strncmp(obisCode, "1.8.2", 5) == 0) {
parsed->cumulativeActiveImportTarif2 = value;
}
else if (strncmp(obisCode, "2.8.1", 5) == 0) {
parsed->cumulativeActiveExportTarif1 = value;
}
else if (strncmp(obisCode, "2.8.2", 5) == 0) {
parsed->cumulativeActiveExportTarif2 = value;
}
else if (strncmp(obisCode, "32.7.0", 6) == 0) {
parsed->voltageL1 = value;
}
else if (strncmp(obisCode, "52.7.0", 6) == 0) {
parsed->voltageL2 = value;
}
else if (strncmp(obisCode, "72.7.0", 6) == 0) {
parsed->voltageL3 = value;
}
else if (strncmp(obisCode, "31.7.0", 6) == 0) {
parsed->currentL1 = value;
}
else if (strncmp(obisCode, "51.7.0", 6) == 0) {
parsed->currentL2 = value;
}
else if (strncmp(obisCode, "71.7.0", 6) == 0) {
parsed->currentL3 = value;
}
}
void publishSensors(ParsedMessage* parsed) {
momentaryActiveImport->publish_state(parsed->momentaryActiveImport);
momentaryActiveExport->publish_state(parsed->momentaryActiveExport);
cumulativeActiveImport->publish_state(parsed->cumulativeActiveImport);
cumulativeActiveExport->publish_state(parsed->cumulativeActiveExport);
cumulativeActiveImportTarif1->publish_state(parsed->cumulativeActiveImportTarif1);
cumulativeActiveImportTarif2->publish_state(parsed->cumulativeActiveImportTarif2);
cumulativeActiveExportTarif1->publish_state(parsed->cumulativeActiveExportTarif1);
cumulativeActiveExportTarif2->publish_state(parsed->cumulativeActiveExportTarif2);
voltageL1->publish_state(parsed->voltageL1);
voltageL2->publish_state(parsed->voltageL2);
voltageL3->publish_state(parsed->voltageL3);
currentL1->publish_state(parsed->currentL1);
currentL2->publish_state(parsed->currentL2);
currentL3->publish_state(parsed->currentL3);
}
/* Reads messages formatted according to "Branschrekommendation v1.2", which
at the time of writing (20210207) is used by Tekniska Verken's Aidon 6442SE
meters. This is a binary format, with a HDLC Frame.
This code is in no way a generic HDLC Frame parser, but it does the job
of decoding this particular data stream.
*/
void readHDLCMessage() {
if (available())
{
uint8_t data = 0;
uint16_t crc = 0x0000;
uint32_t uvalue = 0;
char obis[7];
ParsedMessage parsed = ParsedMessage();
while (parseHDLCState == OUTSIDE_FRAME) {
data = read();
if (data == 0x7e) {
ESP_LOGD("hdlc", "Found start of frame");
parseHDLCState = FOUND_FRAME;
break;
int8_t next = peek();
//ESP_LOGD("hdlc", "Next is %d", next);
if (next == 0x7e) {
read(); // We were actually at the end flag, consume the start flag of the next frame.
}
else if (next == -1) {
ESP_LOGE("hdlc", "No char available after flag, out of sync. Returning");
parseHDLCState = OUTSIDE_FRAME;
return;
}
}
}
if (parseHDLCState == FOUND_FRAME)
{
// Read various static HDLC Frame information we don't care about
int len = Serial.readBytes(buffer, 12);
if (len != 12) {
ESP_LOGE("hdlc", "Expected 12 bytes, got %d bytes - out of sync. Returning", len);
parseHDLCState = OUTSIDE_FRAME;
return;
}
//ESP_LOGD("hdlc", "Got %d HDLC bytes, now reading 4 Invoke ID And Priority bytes", len);
len = Serial.readBytes(buffer, 4);
if (len != 4) {
ESP_LOGE("hdlc", "Expected 4 bytes - out of sync, returning.", buffer[0], buffer[1]);
parseHDLCState = OUTSIDE_FRAME;
return;
}
data = read(); // Expect length of date time field, usually 0
//ESP_LOGD("hdlc", "Length of date time field is %d", data);
Serial.readBytes(buffer, data);
data = read();
//ESP_LOGD("hdlc", "Expect 0x02 (Structure tag), got %02X", data);
if (data != 0x02) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
data = read();
if (data != 0x0F) {
ESP_LOGE("hdlc", "Expected 15 bytes, got %d bytes - out of sync. Returning", data);
parseHDLCState = OUTSIDE_FRAME;
return;
}
// read serial number (96.1.0)
if (Serial.readBytes(buffer, 2) != 2) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
if (buffer[0] != 0x09) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
uint8_t str_length = buffer[1];
if (Serial.readBytes(buffer, str_length) != str_length) {
ESP_LOGE("hdlc", "Unable to read %d bytes of serial number", str_length);
parseHDLCState = OUTSIDE_FRAME;
return;
}
// read momentary Active Import (1.7.0)
strncpy(obis, "1.7.0", 7);
data = read();
if (data != 0x06) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 4);
uvalue = buffer[3] | buffer[2] << 8 | buffer[1] << 16 | buffer[0] << 24;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue);
// read momentary Active Export (2.7.0)
strncpy(obis, "2.7.0", 7);
data = read();
if (data != 0x06) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 4);
uvalue = buffer[3] | buffer[2] << 8 | buffer[1] << 16 | buffer[0] << 24;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue);
// read Cumulative active import energy (1.8.0)
strncpy(obis, "1.8.0", 7);
data = read();
if (data != 0x06) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 4);
uvalue = buffer[3] | buffer[2] << 8 | buffer[1] << 16 | buffer[0] << 24;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 1000); // convert in kW
// read Cumulative active export energy (2.8.0)
strncpy(obis, "2.8.0", 7);
data = read();
if (data != 0x06) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 4);
uvalue = buffer[3] | buffer[2] << 8 | buffer[1] << 16 | buffer[0] << 24;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 1000); // convert in kW
// read phase voltage L1 (32.7.0)
strncpy(obis, "32.7.0", 7);
data = read();
if (data != 0x12) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 2);
uvalue = buffer[1] | buffer[0] << 8;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 10); // convert to V
// read phase voltage L2 (52.7.0)
strncpy(obis, "52.7.0", 7);
data = read();
if (data != 0x12) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 2);
uvalue = buffer[1] | buffer[0] << 8;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 10); // convert to V
// read phase voltage L3 (72.7.0)
strncpy(obis, "72.7.0", 7);
data = read();
if (data != 0x12) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 2);
uvalue = buffer[1] | buffer[0] << 8;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 10); // convert to V
// read phase current L1 (31.7.0)
strncpy(obis, "31.7.0", 7);
data = read();
if (data != 0x12) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 2);
uvalue = buffer[1] | buffer[0] << 8;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 10); // convert to A
// read phase current L2 (51.7.0)
strncpy(obis, "51.7.0", 7);
data = read();
if (data != 0x12) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 2);
uvalue = buffer[1] | buffer[0] << 8;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 10); // convert to A
// read phase current L3 (71.7.0)
strncpy(obis, "71.7.0", 7);
data = read();
if (data != 0x12) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 2);
uvalue = buffer[1] | buffer[0] << 8;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 10); // convert to A
// read Cumulative active import energy tarif 1 (1.8.1)
strncpy(obis, "1.8.1", 7);
data = read();
if (data != 0x06) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 4);
uvalue = buffer[3] | buffer[2] << 8 | buffer[1] << 16 | buffer[0] << 24;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 1000); // convert in kW
// read Cumulative active import energy tarif 2 (1.8.2)
strncpy(obis, "1.8.2", 7);
data = read();
if (data != 0x06) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 4);
uvalue = buffer[3] | buffer[2] << 8 | buffer[1] << 16 | buffer[0] << 24;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 1000); // convert in kW
// read Cumulative active export energy tarif 1 (2.8.1)
strncpy(obis, "2.8.1", 7);
data = read();
if (data != 0x06) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 4);
uvalue = buffer[3] | buffer[2] << 8 | buffer[1] << 16 | buffer[0] << 24;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 1000); // convert in kW
// read Cumulative active export energy tarif 2 (2.8.2)
strncpy(obis, "2.8.2", 7);
data = read();
if (data != 0x06) {
parseHDLCState = OUTSIDE_FRAME;
return;
}
Serial.readBytes(buffer, 4);
uvalue = buffer[3] | buffer[2] << 8 | buffer[1] << 16 | buffer[0] << 24;
//ESP_LOGD("hdlc", "Value for %s is %u", obis, uvalue);
parseRow(&parsed, obis, uvalue / 1000); // convert in kW
publishSensors(&parsed);
while (true)
{
data = read();
//ESP_LOGD("hdlc", "Read char %02X", data);
if (data == 0x7e) {
ESP_LOGD("hdlc", "Found end of frame");
parseHDLCState = OUTSIDE_FRAME;
return;
}
}
}
}
}
};
p1reader.yaml
---
substitutions:
device_name: p1reader
device_description: "DIY P1 module to read your smart meter"
esphome:
name: ${device_name}
comment: "${device_description}"
platform: ESP8266
# esp8266_restore_from_flash: true
board: d1_mini
name_add_mac_suffix: false
project:
name: "Sylvain_hack.p1reader"
version: "0.1"
includes:
- p1reader.h
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Optional manual IP
# manual_ip:
# static_ip: 192.168.50.132
# gateway: 192.168.50.1
# subnet: 255.255.255.0
# MAC Address: C8:C9:A3:7C:19:C7
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: ${device_name}
ap_timeout: 15s
password: !secret fallback_password
captive_portal:
# Enable logging
logger:
# level: VERBOSE
level: INFO
# baud_rate: 0 # disable logging over uart
baud_rate: 0
# Enable Home Assistant API
api:
password: !secret hass_api_password
ota:
password: !secret ota_password
#web_server:
# port: 80
uart:
id: uart_bus
tx_pin: D8
rx_pin: D7
baud_rate: 115200
rx_buffer_size: 256
data_bits: 8
parity: NONE
stop_bits: 1
sensor:
- platform: custom
lambda: |-
auto meter_sensor = new P1Reader(id(uart_bus));
App.register_component(meter_sensor);
return {
meter_sensor->momentaryActiveImport,
meter_sensor->momentaryActiveExport,
meter_sensor->cumulativeActiveImport,
meter_sensor->cumulativeActiveExport,
meter_sensor->cumulativeActiveImportTarif1,
meter_sensor->cumulativeActiveImportTarif2,
meter_sensor->cumulativeActiveExportTarif1,
meter_sensor->cumulativeActiveExportTarif2,
meter_sensor->voltageL1,
meter_sensor->voltageL2,
meter_sensor->voltageL3,
meter_sensor->currentL1,
meter_sensor->currentL2,
meter_sensor->currentL3
};
sensors:
- name: "Momentary Import"
unit_of_measurement: W
state_class: measurement
device_class: power
- name: "Momentary Export"
unit_of_measurement: W
state_class: measurement
device_class: power
- name: "Total Import"
unit_of_measurement: kWh
state_class: total_increasing
device_class: energy
- name: "Total Export"
unit_of_measurement: kWh
state_class: total_increasing
device_class: energy
- name: "Total Import T1"
unit_of_measurement: kWh
state_class: total_increasing
device_class: energy
- name: "Total Import T2"
unit_of_measurement: kWh
state_class: total_increasing
device_class: energy
- name: "Total Export T1"
unit_of_measurement: kWh
state_class: total_increasing
device_class: energy
- name: "Total Export T2"
unit_of_measurement: kWh
state_class: total_increasing
device_class: energy
- name: "Voltage Phase 1"
unit_of_measurement: V
accuracy_decimals: 1
state_class: measurement
device_class: voltage
- name: "Voltage Phase 2"
unit_of_measurement: V
accuracy_decimals: 1
state_class: measurement
device_class: voltage
- name: "Voltage Phase 3"
unit_of_measurement: V
accuracy_decimals: 1
state_class: measurement
device_class: voltage
- name: "Current Phase 1"
unit_of_measurement: A
accuracy_decimals: 1
state_class: measurement
device_class: current
- name: "Current Phase 2"
unit_of_measurement: A
accuracy_decimals: 1
state_class: measurement
device_class: current
- name: "Current Phase 3"
unit_of_measurement: A
accuracy_decimals: 1
state_class: measurement
device_class: current