├── README.md ├── docs ├── IMG_20200115_192406.jpg ├── IMG_20200115_192436.jpg ├── breadboard.jpg └── p1_meter_schema.png ├── dsmr_p1_sensor.h └── read_p1.yaml /README.md: -------------------------------------------------------------------------------- 1 | # Note 2 | This component isn't needed anymore because esphome supports the DSMR protocol now in the standard library: [DMSR Component](https://esphome.io/components/sensor/dsmr.html) 3 | 4 | I leave the page online for reference and the hardware info. 5 | 6 | # CustomP1UartComponent 7 | 8 | This is my first custom component for EspHome. It can be used to read DSMR data from the P1 port of dutch smart meters with an ESP module and pubish the result in [Home Assistant](https://www.home-assistant.io/). 9 | 10 | The work is based on these projects: 11 | - https://esphome.io/custom/uart.html (The project where the custom component is for) 12 | - https://github.com/rspaargaren/DSMR_ESPHOME (We shared thoughts on how to read the P1 port) 13 | - https://github.com/matthijskooijman/arduino-dsmr (The library that i use to parse the telegrams) 14 | - https://github.com/brandond/esphome-tuya_pir (Example how to read data from UART) 15 | - http://domoticx.com/p1-poort-slimme-meter-uitlezen-hardware/ (Information about hardware requirements. Examples for inverters.) 16 | 17 | ## Hardware 18 | 19 | I used a Wemos D1 mini to connect to the P1 port but it will probably work with most ESP boards. You need some kind of [hardware inverter](https://en.wikipedia.org/wiki/Inverter_(logic_gate)) because the UART component doesn't support inverting the signal with a software setting. 20 | Port 5 from the P1 connector goes to pin 1 (A1) from the 7404. Output pin 2 from the 7404 goes to port D2 on the Wemos. 21 | Port D5 from the Wemos is connected to port 2 from the p1 connector. This is used to request a message from the meter. 22 | R1 is needed for my Iskra meter. It won't send any telegrams when it's not there. 23 | 24 | ## Schema 25 | ![Schema](https://raw.githubusercontent.com/nldroid/CustomP1UartComponent/master/docs/p1_meter_schema.png) 26 | 27 | ## Example bread board 28 | ![Bread board](https://raw.githubusercontent.com/nldroid/CustomP1UartComponent/master/docs/breadboard.jpg) 29 | 30 | ## Software 31 | Just add the .h file in your config folder and see the .yaml file for usage 32 | 33 | ## Limitations 34 | The software is only usable for meters with [8N1](https://en.wikipedia.org/wiki/8-N-1) serial communication. This is the case for newer dsmr protocols. Older procols use 7E1. You can change the software and shift the char one bit (c &= ~(1 << 7);). 35 | Real old dsmr protocols don't have a CRC at the end of a telegram and the dsmr parser that i use, doesn't support these old protocols. 36 | 37 | ## Experimental board with case 38 | ![Board](docs/IMG_20200115_192406.jpg) 39 | ![case](docs/IMG_20200115_192436.jpg) 40 | -------------------------------------------------------------------------------- /docs/IMG_20200115_192406.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nldroid/CustomP1UartComponent/76d7f15833edb8df741fd055fe2d00d17c637ccc/docs/IMG_20200115_192406.jpg -------------------------------------------------------------------------------- /docs/IMG_20200115_192436.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nldroid/CustomP1UartComponent/76d7f15833edb8df741fd055fe2d00d17c637ccc/docs/IMG_20200115_192436.jpg -------------------------------------------------------------------------------- /docs/breadboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nldroid/CustomP1UartComponent/76d7f15833edb8df741fd055fe2d00d17c637ccc/docs/breadboard.jpg -------------------------------------------------------------------------------- /docs/p1_meter_schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nldroid/CustomP1UartComponent/76d7f15833edb8df741fd055fe2d00d17c637ccc/docs/p1_meter_schema.png -------------------------------------------------------------------------------- /dsmr_p1_sensor.h: -------------------------------------------------------------------------------- 1 | #include "esphome.h" 2 | #include "dsmr.h" 3 | 4 | using namespace esphome; 5 | 6 | #define P1_MAXTELEGRAMLENGTH 1500 7 | #define DELAY_MS 60000 // Delay in miliseconds before reading another telegram 8 | #define WAIT_FOR_DATA_MS 2000 9 | 10 | // Use data structure according to: https://github.com/matthijskooijman/arduino-dsmr 11 | 12 | using MyData = ParsedData < 13 | /* FixedValue */ energy_delivered_tariff1, 14 | /* FixedValue */ energy_delivered_tariff2, 15 | /* FixedValue */ energy_returned_tariff1, 16 | /* FixedValue */ energy_returned_tariff2, 17 | /* FixedValue */ power_delivered, 18 | /* FixedValue */ power_returned, 19 | /* FixedValue */ voltage_l1, 20 | /* FixedValue */ voltage_l2, 21 | /* FixedValue */ voltage_l3, 22 | /* FixedValue */ current_l1, 23 | /* FixedValue */ current_l2, 24 | /* FixedValue */ current_l3, 25 | /* FixedValue */ power_delivered_l1, 26 | /* FixedValue */ power_delivered_l2, 27 | /* FixedValue */ power_delivered_l3, 28 | /* FixedValue */ power_returned_l1, 29 | /* FixedValue */ power_returned_l2, 30 | /* FixedValue */ power_returned_l3, 31 | /* uint16_t */ gas_device_type, 32 | /* uint8_t */ gas_valve_position, 33 | /* TimestampedFixedValue */ gas_delivered 34 | >; 35 | 36 | class CustomP1UartComponent : public Component, public uart::UARTDevice { 37 | protected: 38 | char telegram[P1_MAXTELEGRAMLENGTH]; 39 | char c; 40 | int telegramlen; 41 | bool headerfound; 42 | bool footerfound; 43 | unsigned long lastread; 44 | int bytes_read; 45 | 46 | bool data_available() { 47 | // See if there's data available. 48 | unsigned long currentMillis = millis(); 49 | unsigned long previousMillis = currentMillis; 50 | 51 | while (currentMillis - previousMillis < WAIT_FOR_DATA_MS) { // wait in miliseconds 52 | currentMillis = millis(); 53 | if (available()) { 54 | return true; 55 | } 56 | } 57 | return false; 58 | } 59 | 60 | bool read_message() { 61 | //ESP_LOGD("DmsrCustom","Read message"); 62 | headerfound = false; 63 | footerfound = false; 64 | telegramlen = 0; 65 | bytes_read = 0; 66 | unsigned long currentMillis = millis(); 67 | unsigned long previousMillis = currentMillis; 68 | 69 | if (available()) { // Check to be sure 70 | // Messages come in batches. Read until footer. 71 | while (!footerfound && currentMillis - previousMillis < 5000) { // Loop while there's no footer found with a maximum of 5 seconds 72 | currentMillis = millis(); 73 | // Loop while there's data to read 74 | while (available()) { // Loop while there's data 75 | if (telegramlen >= P1_MAXTELEGRAMLENGTH) { // Buffer overflow 76 | headerfound = false; 77 | footerfound = false; 78 | ESP_LOGD("DmsrCustom","Error: Message larger than buffer"); 79 | } 80 | bytes_read++; 81 | c = read(); 82 | if (c == 47) { // header: forward slash 83 | // ESP_LOGD("DmsrCustom","Header found"); 84 | headerfound = true; 85 | telegramlen = 0; 86 | } 87 | if (headerfound) { 88 | telegram[telegramlen] = c; 89 | telegramlen++; 90 | if (c == 33) { // footer: exclamation mark 91 | ESP_LOGD("DmsrCustom","Footer found"); 92 | footerfound = true; 93 | } else { 94 | if (footerfound && c == 10) { // last \n after footer 95 | // Parse message 96 | MyData data; 97 | // ESP_LOGD("DmsrCustom","Trying to parse"); 98 | ParseResult res = P1Parser::parse(&data, telegram, telegramlen, false); // Parse telegram accoring to data definition. Ignore unknown values. 99 | if (res.err) { 100 | // Parsing error, show it 101 | Serial.println(res.fullError(telegram, telegram + telegramlen)); 102 | } else { 103 | publish_sensors(data); 104 | return true; // break out function 105 | } 106 | } 107 | } 108 | } 109 | } // While data available 110 | } // !footerfound 111 | } 112 | return false; 113 | } 114 | 115 | void publish_sensors(MyData data){ 116 | if(data.energy_delivered_tariff1_present)s_energy_delivered_tariff1->publish_state(data.energy_delivered_tariff1); 117 | if(data.energy_delivered_tariff2_present)s_energy_delivered_tariff2->publish_state(data.energy_delivered_tariff2); 118 | if(data.energy_returned_tariff1_present)s_energy_returned_tariff1->publish_state(data.energy_returned_tariff1); 119 | if(data.energy_returned_tariff2_present)s_energy_returned_tariff2->publish_state(data.energy_returned_tariff2); 120 | if(data.power_delivered_present)s_power_delivered->publish_state(data.power_delivered); 121 | if(data.power_returned_present)s_power_returned->publish_state(data.power_returned); 122 | if(data.voltage_l1_present)s_voltage_l1->publish_state(data.voltage_l1); 123 | if(data.voltage_l2_present)s_voltage_l2->publish_state(data.voltage_l2); 124 | if(data.voltage_l3_present)s_voltage_l3->publish_state(data.voltage_l3); 125 | if(data.current_l1_present)s_current_l1->publish_state(data.current_l1); 126 | if(data.current_l2_present)s_current_l2->publish_state(data.current_l2); 127 | if(data.current_l3_present)s_current_l3->publish_state(data.current_l3); 128 | if(data.power_delivered_l1_present)s_power_delivered_l1->publish_state(data.power_delivered_l1); 129 | if(data.power_delivered_l2_present)s_power_delivered_l2->publish_state(data.power_delivered_l2); 130 | if(data.power_delivered_l3_present)s_power_delivered_l3->publish_state(data.power_delivered_l3); 131 | if(data.power_returned_l1_present)s_power_returned_l1->publish_state(data.power_returned_l1); 132 | if(data.power_returned_l2_present)s_power_returned_l2->publish_state(data.power_returned_l2); 133 | if(data.power_returned_l3_present)s_power_returned_l3->publish_state(data.power_returned_l3); 134 | if(data.gas_device_type_present)s_gas_device_type->publish_state(data.gas_device_type); 135 | if(data.gas_valve_position_present)s_gas_valve_position->publish_state(data.gas_valve_position); 136 | if(data.gas_delivered_present)s_gas_delivered->publish_state(data.gas_delivered); 137 | }; 138 | 139 | public: 140 | CustomP1UartComponent(UARTComponent *parent) : UARTDevice(parent) {} 141 | Sensor *s_energy_delivered_tariff1 = new Sensor(); 142 | Sensor *s_energy_delivered_tariff2 = new Sensor(); 143 | Sensor *s_energy_returned_tariff1 = new Sensor(); 144 | Sensor *s_energy_returned_tariff2 = new Sensor(); 145 | Sensor *s_electricity_tariff = new Sensor(); 146 | Sensor *s_power_delivered = new Sensor(); 147 | Sensor *s_power_returned = new Sensor(); 148 | Sensor *s_electricity_threshold = new Sensor(); 149 | Sensor *s_voltage_l1 = new Sensor(); 150 | Sensor *s_voltage_l2 = new Sensor(); 151 | Sensor *s_voltage_l3 = new Sensor(); 152 | Sensor *s_current_l1 = new Sensor(); 153 | Sensor *s_current_l2 = new Sensor(); 154 | Sensor *s_current_l3 = new Sensor(); 155 | Sensor *s_power_delivered_l1 = new Sensor(); 156 | Sensor *s_power_delivered_l2 = new Sensor(); 157 | Sensor *s_power_delivered_l3 = new Sensor(); 158 | Sensor *s_power_returned_l1 = new Sensor(); 159 | Sensor *s_power_returned_l2 = new Sensor(); 160 | Sensor *s_power_returned_l3 = new Sensor(); 161 | Sensor *s_gas_device_type = new Sensor(); 162 | Sensor *s_gas_valve_position = new Sensor(); 163 | Sensor *s_gas_delivered = new Sensor(); 164 | 165 | void setup() override { 166 | lastread = 0; 167 | pinMode(D5, OUTPUT); // Set D5 as output pin 168 | digitalWrite(D5,LOW); // Set low, don't request message from P1 port 169 | } 170 | 171 | void loop() override { 172 | unsigned long now = millis(); 173 | 174 | if (now - lastread > DELAY_MS || lastread == 0) { 175 | lastread = now; 176 | digitalWrite(D5,HIGH); // Set high, request new message from P1 port 177 | if (data_available()) { // Check for x seconds if there's data available 178 | bool have_message = read_message(); 179 | if (have_message) { 180 | digitalWrite(D5,LOW); // Set low, stop requesting messages from P1 port 181 | } // If No message was read, keep output port high and retry later 182 | } else { 183 | ESP_LOGD("DmsrCustom","No data available. Is P1 port connected?"); 184 | } 185 | } 186 | } 187 | 188 | }; 189 | -------------------------------------------------------------------------------- /read_p1.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: esp_dsmr 3 | platform: ESP8266 4 | board: d1_mini 5 | includes: 6 | - dsmr_p1_uart.h 7 | libraries: 8 | - "Dsmr" 9 | 10 | wifi: 11 | ssid: !secret wifi_ssid 12 | password: !secret wifi_password 13 | 14 | # Enable fallback hotspot (captive portal) in case wifi connection fails 15 | ap: 16 | ssid: "esp_dsmr_uart Fallback Hotspot" 17 | password: "" 18 | 19 | captive_portal: 20 | 21 | # Enable logging 22 | logger: 23 | level: DEBUG 24 | esp8266_store_log_strings_in_flash: False 25 | 26 | # Enable Home Assistant API 27 | api: 28 | password: "" 29 | 30 | ota: 31 | password: "" 32 | 33 | uart: 34 | - rx_pin: D2 35 | baud_rate: 115200 36 | id: uart_bus 37 | 38 | sensor: 39 | - platform: custom 40 | lambda: |- 41 | auto dsmr_p1_sensor = new CustomP1UartComponent(id(uart_bus)); 42 | App.register_component(dsmr_p1_sensor); 43 | return {dsmr_p1_sensor->s_energy_delivered_tariff1, dsmr_p1_sensor->s_energy_delivered_tariff2, dsmr_p1_sensor->s_energy_returned_tariff1, dsmr_p1_sensor->s_energy_returned_tariff2, dsmr_p1_sensor->s_power_delivered, dsmr_p1_sensor->s_power_returned, dsmr_p1_sensor->s_voltage_l1, dsmr_p1_sensor->s_voltage_l2, dsmr_p1_sensor->s_voltage_l3, dsmr_p1_sensor->s_current_l1, dsmr_p1_sensor->s_current_l2, dsmr_p1_sensor->s_current_l3, dsmr_p1_sensor->s_power_delivered_l1, dsmr_p1_sensor->s_power_delivered_l2, dsmr_p1_sensor->s_power_delivered_l3, dsmr_p1_sensor->s_power_returned_l1, dsmr_p1_sensor->s_power_returned_l2, dsmr_p1_sensor->s_power_returned_l3, dsmr_p1_sensor->s_gas_device_type, dsmr_p1_sensor->s_gas_valve_position, dsmr_p1_sensor->s_gas_delivered}; 44 | 45 | sensors: 46 | - name: "Consumption Low Tarif Sensor" 47 | unit_of_measurement: kWh 48 | accuracy_decimals: 3 49 | - name: "Consumption High Tarif Sensor" 50 | unit_of_measurement: kWh 51 | accuracy_decimals: 3 52 | - name: "Return Low Tarif Sensor" 53 | unit_of_measurement: kWh 54 | accuracy_decimals: 3 55 | - name: "Return High Tarif Sensor" 56 | unit_of_measurement: kWh 57 | accuracy_decimals: 3 58 | - name: "Actual Consumption Sensor" 59 | unit_of_measurement: W 60 | accuracy_decimals: 3 61 | filters: 62 | - multiply: 1000 63 | - name: "Actual Delivery Sensor" 64 | unit_of_measurement: W 65 | accuracy_decimals: 3 66 | filters: 67 | - multiply: 1000 68 | - name: "Instant Voltage L1 Sensor" 69 | unit_of_measurement: V 70 | accuracy_decimals: 3 71 | - name: "Instant Voltage L2 Sensor" 72 | unit_of_measurement: V 73 | accuracy_decimals: 3 74 | - name: "Instant Voltage L3 Sensor" 75 | unit_of_measurement: V 76 | accuracy_decimals: 3 77 | - name: "Instant Current L1 Sensor" 78 | unit_of_measurement: A 79 | accuracy_decimals: 3 80 | - name: "Instant Current L2 Sensor" 81 | unit_of_measurement: A 82 | accuracy_decimals: 3 83 | - name: "Instant Current L3 Sensor" 84 | unit_of_measurement: A 85 | accuracy_decimals: 3 86 | - name: "Power Delivered L1 Sensor" 87 | unit_of_measurement: W 88 | accuracy_decimals: 3 89 | filters: 90 | - multiply: 1000 91 | - name: "Power Delivered L2 Sensor" 92 | unit_of_measurement: W 93 | accuracy_decimals: 3 94 | filters: 95 | - multiply: 1000 96 | - name: "Power Delivered L3 Sensor" 97 | unit_of_measurement: W 98 | accuracy_decimals: 3 99 | filters: 100 | - multiply: 1000 101 | - name: "Power Returned L1 Sensor" 102 | unit_of_measurement: W 103 | accuracy_decimals: 3 104 | filters: 105 | - multiply: 1000 106 | - name: "Power Returned L2 Sensor" 107 | unit_of_measurement: W 108 | accuracy_decimals: 3 109 | filters: 110 | - multiply: 1000 111 | - name: "Power Returned L3 Sensor" 112 | unit_of_measurement: W 113 | accuracy_decimals: 3 114 | filters: 115 | - multiply: 1000 116 | - name: "Gas device type Sensor" 117 | - name: "Gas valve position Sensor" 118 | - name: "Gas Meter M3 Sensor" 119 | unit_of_measurement: m3 120 | accuracy_decimals: 3 121 | 122 | --------------------------------------------------------------------------------