Skip to content

Instantly share code, notes, and snippets.

@nekman
Last active November 28, 2022 08:56
Show Gist options
  • Save nekman/8853dc4e6ae3bb603175959a17e673fa to your computer and use it in GitHub Desktop.
Save nekman/8853dc4e6ae3bb603175959a17e673fa to your computer and use it in GitHub Desktop.
SlimmeLezer+ with Aidon 6534 (communication module 6442 S) energy meter

SlimmeLezer+ with Aidon 6534 (communication module 6442 S) energy meter

Bought a SlimmeLezer+ to read data from the energy meter Aidon 6534.

Installed it according to the guide. Got power from the HAN-port using the provided serial cable, but I also needed to connect it to the USB-C to get it to work well.

Once setup and connected, I used the web interface to discover that it could not parse the data from the energy meter. The following error message was displayed in the log:

[dsmr:265]: /ADN9 6534 ^
Invalid identification string

image

Reached out to the energy company (Jönköping Energi), but according to them they where using the latest standard EFS 2.0 that they upgraded in June 2022. Still, the SlimmeLezer+ (or some component used by SlimmeLezer+ could not understand this standard).

Luckily for me, other people had discovered the issue and also provided a fix for it!

I followed the steps and started with downloading ESPHome.

Once installed i opened a new dashboard using:

esphome dashboard config/

Followed the steps in the link but slightly modified the p1reader.yaml to use some parts of the original SlimmeLezer+ config (because, for some reason I did not get the original p1reader.yaml to work).

(Below you can find the modified version that worked for me).

Added the changes and continued to following the steps from the link. Connected the SlimmeLezer+ using USB-C and installed it using "Plug into this computer". Downloaded the .bin file and installed it from https://web.esphome.io/

When the installation was done, I could read the data from the Aidon 6534 energy meter!

image

---
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: "elt_hack.p1reader"
version: "0.1"
includes:
- p1reader.h
wifi:
networks:
- ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: ${device_name}
ap_timeout: 15s
captive_portal:
# Enable logging
logger:
baud_rate: 0
# Enable Home Assistant API
api:
services:
service: set_dsmr_key
variables:
private_key: string
then:
- logger.log:
format: Setting private key %s. Set to empty string to disable
args: [private_key.c_str()]
- globals.set:
id: has_key
value: !lambda "return private_key.length() == 32;"
- lambda: |-
if (private_key.length() == 32)
private_key.copy(id(stored_decryption_key), 32);
id(dsmr_instance).set_decryption_key(private_key);
ota:
dsmr:
id: dsmr_instance
web_server:
port: 80
globals:
- id: has_key
type: bool
restore_value: yes
initial_value: "false"
- id: stored_decryption_key
type: char[32]
restore_value: yes
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->cumulativeActiveImport,
meter_sensor->cumulativeActiveExport,
meter_sensor->cumulativeReactiveImport,
meter_sensor->cumulativeReactiveExport,
meter_sensor->momentaryActiveImport,
meter_sensor->momentaryActiveExport,
meter_sensor->momentaryReactiveImport,
meter_sensor->momentaryReactiveExport,
meter_sensor->momentaryActiveImportL1,
meter_sensor->momentaryActiveExportL1,
meter_sensor->momentaryActiveImportL2,
meter_sensor->momentaryActiveExportL2,
meter_sensor->momentaryActiveImportL3,
meter_sensor->momentaryActiveExportL3,
meter_sensor->momentaryReactiveImportL1,
meter_sensor->momentaryReactiveExportL1,
meter_sensor->momentaryReactiveImportL2,
meter_sensor->momentaryReactiveExportL2,
meter_sensor->momentaryReactiveImportL3,
meter_sensor->momentaryReactiveExportL3,
meter_sensor->voltageL1,
meter_sensor->voltageL2,
meter_sensor->voltageL3,
meter_sensor->currentL1,
meter_sensor->currentL2,
meter_sensor->currentL3
};
sensors:
- name: "Cumulative Active Import"
unit_of_measurement: kWh
accuracy_decimals: 3
state_class: "total_increasing"
device_class: "energy"
- name: "Cumulative Active Export"
unit_of_measurement: kWh
accuracy_decimals: 3
state_class: "total_increasing"
device_class: "energy"
- name: "Cumulative Reactive Import"
unit_of_measurement: kvarh
accuracy_decimals: 3
state_class: "total_increasing"
- name: "Cumulative Reactive Export"
unit_of_measurement: kvarh
accuracy_decimals: 3
state_class: "total_increasing"
- name: "Momentary Active Import"
unit_of_measurement: W
accuracy_decimals: 2
state_class: "measurement"
device_class: "power"
- name: "Momentary Active Export"
unit_of_measurement: W
accuracy_decimals: 2
state_class: "measurement"
device_class: "power"
- name: "Momentary Reactive Import"
unit_of_measurement: var
accuracy_decimals: 2
state_class: "measurement"
- name: "Momentary Reactive Export"
unit_of_measurement: var
accuracy_decimals: 2
state_class: "measurement"
- name: "Momentary Active Import Phase 1"
unit_of_measurement: W
accuracy_decimals: 2
state_class: "measurement"
device_class: "power"
- name: "Momentary Active Export Phase 1"
unit_of_measurement: W
accuracy_decimals: 2
state_class: "measurement"
device_class: "power"
- name: "Momentary Active Import Phase 2"
unit_of_measurement: W
accuracy_decimals: 2
state_class: "measurement"
device_class: "power"
- name: "Momentary Active Export Phase 2"
unit_of_measurement: W
accuracy_decimals: 2
state_class: "measurement"
device_class: "power"
- name: "Momentary Active Import Phase 3"
unit_of_measurement: W
accuracy_decimals: 2
state_class: "measurement"
device_class: "power"
- name: "Momentary Active Export Phase 3"
unit_of_measurement: W
accuracy_decimals: 2
state_class: "measurement"
device_class: "power"
- name: "Momentary Reactive Import Phase 1"
unit_of_measurement: var
accuracy_decimals: 2
state_class: "measurement"
- name: "Momentary Reactive Export Phase 1"
unit_of_measurement: var
accuracy_decimals: 2
state_class: "measurement"
- name: "Momentary Reactive Import Phase 2"
unit_of_measurement: var
accuracy_decimals: 2
state_class: "measurement"
- name: "Momentary Reactive Export Phase 2"
unit_of_measurement: var
accuracy_decimals: 2
state_class: "measurement"
- name: "Momentary Reactive Import Phase 3"
unit_of_measurement: var
accuracy_decimals: 2
state_class: "measurement"
- name: "Momentary Reactive Export Phase 3"
unit_of_measurement: var
accuracy_decimals: 2
state_class: "measurement"
- 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"
//-------------------------------------------------------------------------------------
// ESPHome P1 Electricity Meter custom sensor
// Copyright 2020 Pär Svanström
//
// History
// 0.1.0 2020-11-05: 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 cumulativeReactiveImport;
double cumulativeReactiveExport;
double momentaryActiveImport;
double momentaryActiveExport;
double momentaryReactiveImport;
double momentaryReactiveExport;
double momentaryActiveImportL1;
double momentaryActiveExportL1;
double momentaryActiveImportL2;
double momentaryActiveExportL2;
double momentaryActiveImportL3;
double momentaryActiveExportL3;
double momentaryReactiveImportL1;
double momentaryReactiveExportL1;
double momentaryReactiveImportL2;
double momentaryReactiveExportL2;
double momentaryReactiveImportL3;
double momentaryReactiveExportL3;
double voltageL1;
double voltageL2;
double voltageL3;
double currentL1;
double currentL2;
double currentL3;
bool crcOk = false;
};
class P1Reader : public Component, public UARTDevice {
const char* DELIMITERS = "()*:";
const char* DATA_ID = "1-0";
char buffer[BUF_SIZE];
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 *momentaryReactiveImport = new Sensor();
Sensor *momentaryReactiveExport = new Sensor();
Sensor *momentaryActiveImportL1 = new Sensor();
Sensor *momentaryActiveExportL1 = new Sensor();
Sensor *momentaryActiveImportL2 = new Sensor();
Sensor *momentaryActiveExportL2 = new Sensor();
Sensor *momentaryActiveImportL3 = new Sensor();
Sensor *momentaryActiveExportL3 = new Sensor();
Sensor *momentaryReactiveImportL1 = new Sensor();
Sensor *momentaryReactiveExportL1 = new Sensor();
Sensor *momentaryReactiveImportL2 = new Sensor();
Sensor *momentaryReactiveExportL2 = new Sensor();
Sensor *momentaryReactiveImportL3 = new Sensor();
Sensor *momentaryReactiveExportL3 = 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 {
readP1Message();
}
private:
uint16_t crc16_update(uint16_t crc, uint8_t a) {
int i;
crc ^= a;
for (i = 0; i < 8; ++i) {
if (crc & 1) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc = (crc >> 1);
}
}
return crc;
}
void parseRow(ParsedMessage* parsed, char* obisCode, char* value) {
if (strncmp(obisCode, "1.8.0", 5) == 0) {
parsed->cumulativeActiveImport = atof(value);
} else if (strncmp(obisCode, "2.8.0", 5) == 0) {
parsed->cumulativeActiveExport = atof(value);
} else if (strncmp(obisCode, "3.8.0", 5) == 0) {
parsed->cumulativeReactiveImport = atof(value);
} else if (strncmp(obisCode, "4.8.0", 5) == 0) {
parsed->cumulativeReactiveExport = atof(value);
} else if (strncmp(obisCode, "1.7.0", 5) == 0) {
parsed->momentaryActiveImport = atof(value);
} else if (strncmp(obisCode, "2.7.0", 5) == 0) {
parsed->momentaryActiveExport = atof(value);
} else if (strncmp(obisCode, "3.7.0", 5) == 0) {
parsed->momentaryReactiveImport = atof(value);
} else if (strncmp(obisCode, "4.7.0", 5) == 0) {
parsed->momentaryReactiveExport = atof(value);
} else if (strncmp(obisCode, "21.7.0", 6) == 0) {
parsed->momentaryActiveImportL1 = atof(value);
} else if (strncmp(obisCode, "22.7.0", 6) == 0) {
parsed->momentaryActiveExportL1 = atof(value);
} else if (strncmp(obisCode, "41.7.0", 6) == 0) {
parsed->momentaryActiveImportL2 = atof(value);
} else if (strncmp(obisCode, "42.7.0", 6) == 0) {
parsed->momentaryActiveExportL2 = atof(value);
} else if (strncmp(obisCode, "61.7.0", 6) == 0) {
parsed->momentaryActiveImportL3 = atof(value);
} else if (strncmp(obisCode, "62.7.0", 6) == 0) {
parsed->momentaryActiveExportL3 = atof(value);
} else if (strncmp(obisCode, "23.7.0", 6) == 0) {
parsed->momentaryReactiveImportL1 = atof(value);
} else if (strncmp(obisCode, "24.7.0", 6) == 0) {
parsed->momentaryReactiveExportL1 = atof(value);
} else if (strncmp(obisCode, "43.7.0", 6) == 0) {
parsed->momentaryReactiveImportL2 = atof(value);
} else if (strncmp(obisCode, "44.7.0", 6) == 0) {
parsed->momentaryReactiveExportL2 = atof(value);
} else if (strncmp(obisCode, "63.7.0", 6) == 0) {
parsed->momentaryReactiveImportL3 = atof(value);
} else if (strncmp(obisCode, "64.7.0", 6) == 0) {
parsed->momentaryReactiveExportL3 = atof(value);
} else if (strncmp(obisCode, "32.7.0", 6) == 0) {
parsed->voltageL1 = atof(value);
} else if (strncmp(obisCode, "52.7.0", 6) == 0) {
parsed->voltageL2 = atof(value);
} else if (strncmp(obisCode, "72.7.0", 6) == 0) {
parsed->voltageL3 = atof(value);
} else if (strncmp(obisCode, "31.7.0", 6) == 0) {
parsed->currentL1 = atof(value);
} else if (strncmp(obisCode, "51.7.0", 6) == 0) {
parsed->currentL2 = atof(value);
} else if (strncmp(obisCode, "71.7.0", 6) == 0) {
parsed->currentL3 = atof(value);
}
}
void publishSensors(ParsedMessage* parsed) {
cumulativeActiveImport->publish_state(parsed->cumulativeActiveImport);
cumulativeActiveExport->publish_state(parsed->cumulativeActiveExport);
cumulativeReactiveImport->publish_state(parsed->cumulativeReactiveImport);
cumulativeReactiveExport->publish_state(parsed->cumulativeReactiveExport);
momentaryActiveImport->publish_state(parsed->momentaryActiveImport);
momentaryActiveExport->publish_state(parsed->momentaryActiveExport);
momentaryReactiveImport->publish_state(parsed->momentaryReactiveImport);
momentaryReactiveExport->publish_state(parsed->momentaryReactiveExport);
momentaryActiveImportL1->publish_state(parsed->momentaryActiveImportL1);
momentaryActiveExportL1->publish_state(parsed->momentaryActiveExportL1);
momentaryActiveImportL2->publish_state(parsed->momentaryActiveImportL2);
momentaryActiveExportL2->publish_state(parsed->momentaryActiveExportL2);
momentaryActiveImportL3->publish_state(parsed->momentaryActiveImportL3);
momentaryActiveExportL3->publish_state(parsed->momentaryActiveExportL3);
momentaryReactiveImportL1->publish_state(parsed->momentaryReactiveImportL1);
momentaryReactiveExportL1->publish_state(parsed->momentaryReactiveExportL1);
momentaryReactiveImportL2->publish_state(parsed->momentaryReactiveImportL2);
momentaryReactiveExportL2->publish_state(parsed->momentaryReactiveExportL2);
momentaryReactiveImportL3->publish_state(parsed->momentaryReactiveImportL3);
momentaryReactiveExportL3->publish_state(parsed->momentaryReactiveExportL3);
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);
}
void readP1Message() {
if (available()) {
uint16_t crc = 0x0000;
ParsedMessage parsed = ParsedMessage();
bool telegramEnded = false;
while (available()) {
int len = Serial.readBytesUntil('\n', buffer, BUF_SIZE);
if (len > 0) {
ESP_LOGD("data", "%s", buffer);
// put newline back as it is required for CRC calculation
buffer[len] = '\n';
buffer[len + 1] = '\0';
// if we've reached the CRC checksum, calculate last CRC and compare
if (buffer[0] == '!') {
crc = crc16_update(crc, buffer[0]);
int crcFromMsg = (int) strtol(&buffer[1], NULL, 16);
parsed.crcOk = crc == crcFromMsg;
ESP_LOGI("crc", "Telegram read. CRC: %04X = %04X. PASS = %s", crc, crcFromMsg, parsed.crcOk ? "YES": "NO");
telegramEnded = true;
// otherwise pass the row through the CRC calculation
} else {
for (int i = 0; i < len + 1; i++) {
crc = crc16_update(crc, buffer[i]);
}
}
// if this is a row containing information
if (strchr(buffer, '(') != NULL) {
char* dataId = strtok(buffer, DELIMITERS);
char* obisCode = strtok(NULL, DELIMITERS);
// ...and this row is a data row, then parse row
if (strncmp(DATA_ID, dataId, strlen(DATA_ID)) == 0) {
char* value = strtok(NULL, DELIMITERS);
char* unit = strtok(NULL, DELIMITERS);
parseRow(&parsed, obisCode, value);
}
}
}
// clean buffer
memset(buffer, 0, BUF_SIZE - 1);
if (!telegramEnded && !available()) {
// wait for more data
delay(40);
}
}
// if the CRC pass, publish sensor values
if (parsed.crcOk) {
publishSensors(&parsed);
}
}
}
};
# ESPHome secrets
wifi_ssid: "<SSID>"
wifi_password: "<PASSWORD FOR SSID>"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment