├── images └── wiring.png ├── README.md ├── p1reader.yaml └── p1reader.h /images/wiring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forsberg/esphome-p1reader/HEAD/images/wiring.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esphome-p1reader 2 | ESPHome custom component for reading P1 data from electricity meters. Designed for Swedish meters that implements the specification defined in the [Swedish Energy Industry Recommendation For Customer Interfaces](https://www.energiforetagen.se/forlag/elnat/branschrekommendation-for-lokalt-kundgranssnitt-for-elmatare/) version 1.3 and above. 3 | 4 | Please note that the project currently doesn't support the Aidon meter from Tekniska Verken since that meter outputs the data in a binary format according to an earlier version (1.2) of the above mentioned recommendation. 5 | 6 | ## Verified meter hardware / supplier 7 | * [Sagemcom T211](https://www.ellevio.se/globalassets/uploads/2020/nya-elmatare/ellevio_produktblad_fas3_t211_web2.pdf) / Ellevio 8 | 9 | ## Hardware 10 | I have used an ESP-12 based NodeMCU for my circuit. Most ESP-12/ESP-32 based controllers would probably work. The P1 port on the meter provides 5V up to 250mA which makes it possible to power the circuit directly from the P1 port. 11 | 12 | ### Parts 13 | - 1 NodeMCU or equivalent ESP-12 / ESP-32 microcontroller 14 | - 1 BC547 NPN transistor 15 | - 1 4.7kOhm Resistor 16 | - 1 10kOhm Resistor 17 | - 1 RJ12 6P6C port 18 | - 1 RJ12 to RJ12 cable (6 wires) 19 | 20 | ### Wiring 21 | The circuit is very simple, basically the 5V TX output on the P1 connector is converted to 3.3V and inverted by the transistor and connected to the UART0 RX pin on the microcontroller. The RTS (request to send) pin is pulled high so that data is sent continously and GND and 5V is taken from the P1 connector to drive the microcontroller. 22 | 23 | ![Wiring Diagram](images/wiring.png) 24 | 25 | ## Installation 26 | Clone the repository and update the [p1reader.yaml](p1reader.yaml) with your own settings (wifi SSID and password and API password). 27 | 28 | Prepare the microcontroller with ESPHome before you connect it to the circuit: 29 | - Install the `esphome` [command line tool](https://esphome.io/guides/getting_started_command_line.html) 30 | - Plug in the microcontroller to your USB port and run `esphome p1reader.yaml run` to flash the firmware 31 | - Remove the USB connection and connect the microcontroller to the rest of the circuit and plug it into the P1 port. 32 | - If everything works, your Home Assistant will now auto detect your new ESPHome integration. 33 | 34 | You can check the logs by issuing `esphome p1reader.yaml logs` (or use the super awesome ESPHome dashboard available as a Hass.io add-on or standalone). The logs should output data similar to this every 10 seconds: 35 | ``` 36 | [23:03:23][D][data:291]: [1.8.0]: 00001587.242 kWh 37 | [23:03:23][D][data:291]: [2.8.0]: 00000000.000 kWh 38 | [23:03:23][D][data:291]: [3.8.0]: 00000005.420 kvarh 39 | [23:03:23][D][data:291]: [4.8.0]: 00000269.077 kvarh 40 | [23:03:23][D][data:291]: [1.7.0]: 0000.702 kW 41 | [23:03:23][D][data:291]: [2.7.0]: 0000.000 kW 42 | [23:03:23][D][data:291]: [3.7.0]: 0000.041 kvar 43 | [23:03:23][D][data:291]: [4.7.0]: 0000.340 kvar 44 | [23:03:23][D][data:291]: [21.7.0]: 0000.351 kW 45 | [23:03:23][D][data:291]: [41.7.0]: 0000.187 kW 46 | [23:03:23][D][data:291]: [61.7.0]: 0000.163 kW 47 | [23:03:23][D][data:291]: [22.7.0]: 0000.000 kW 48 | [23:03:23][D][data:291]: [42.7.0]: 0000.000 kW 49 | [23:03:23][D][data:291]: [62.7.0]: 0000.000 kW 50 | [23:03:23][D][data:291]: [23.7.0]: 0000.000 kvar 51 | [23:03:23][D][data:291]: [43.7.0]: 0000.041 kvar 52 | [23:03:23][D][data:291]: [63.7.0]: 0000.000 kvar 53 | [23:03:23][D][data:291]: [24.7.0]: 0000.259 kvar 54 | [23:03:23][D][data:291]: [44.7.0]: 0000.000 kvar 55 | [23:03:23][D][data:291]: [64.7.0]: 0000.080 kvar 56 | [23:03:23][D][data:291]: [32.7.0]: 233.8 V 57 | [23:03:23][D][data:291]: [52.7.0]: 235.0 V 58 | [23:03:23][D][data:291]: [72.7.0]: 234.9 V 59 | [23:03:23][D][data:291]: [31.7.0]: 001.9 A 60 | [23:03:23][D][data:291]: [51.7.0]: 000.8 A 61 | [23:03:23][D][data:291]: [71.7.0]: 000.8 A 62 | [23:03:23][D][crc:273]: CRC: 27DE = 27DE. PASS = YES 63 | ``` 64 | 65 | The last row contains the CRC check. If you constantly get invalid CRC there might be something wrong with the serial communication. 66 | 67 | ## Technical documentation 68 | Specification overview: 69 | https://www.tekniskaverken.se/siteassets/tekniska-verken/elnat/aidonfd-rj12-han-interface-se-v13a.cleaned.pdf 70 | 71 | OBIS codes: 72 | https://tech.enectiva.cz/en/installation-instructions/others/obis-codes-meaning/ 73 | 74 | P1 hardware info (in Dutch): 75 | http://domoticx.com/p1-poort-slimme-meter-hardware/ 76 | -------------------------------------------------------------------------------- /p1reader.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: electricity_meter 3 | platform: ESP8266 4 | board: nodemcu 5 | includes: 6 | - p1reader.h 7 | 8 | wifi: 9 | ssid: 10 | password: 11 | use_address: 12 | 13 | # Enable fallback hotspot (captive portal) in case wifi connection fails 14 | ap: 15 | ssid: "electricity_meter" 16 | password: 17 | 18 | captive_portal: 19 | 20 | # Enable logging 21 | logger: 22 | level: DEBUG 23 | baud_rate: 0 # disable logging over uart 24 | 25 | # Enable Home Assistant API 26 | api: 27 | password: 28 | 29 | ota: 30 | password: 31 | 32 | uart: 33 | id: uart_bus 34 | tx_pin: TX 35 | rx_pin: RX 36 | baud_rate: 115200 37 | 38 | sensor: 39 | - platform: custom 40 | lambda: |- 41 | auto meter_sensor = new P1Reader(id(uart_bus)); 42 | App.register_component(meter_sensor); 43 | return { 44 | meter_sensor->cumulativeActiveImport, 45 | meter_sensor->cumulativeActiveExport, 46 | meter_sensor->cumulativeReactiveImport, 47 | meter_sensor->cumulativeReactiveExport, 48 | meter_sensor->momentaryActiveImport, 49 | meter_sensor->momentaryActiveExport, 50 | meter_sensor->momentaryReactiveImport, 51 | meter_sensor->momentaryReactiveExport, 52 | meter_sensor->momentaryActiveImportL1, 53 | meter_sensor->momentaryActiveExportL1, 54 | meter_sensor->momentaryActiveImportL2, 55 | meter_sensor->momentaryActiveExportL2, 56 | meter_sensor->momentaryActiveImportL3, 57 | meter_sensor->momentaryActiveExportL3, 58 | meter_sensor->momentaryReactiveImportL1, 59 | meter_sensor->momentaryReactiveExportL1, 60 | meter_sensor->momentaryReactiveImportL2, 61 | meter_sensor->momentaryReactiveExportL2, 62 | meter_sensor->momentaryReactiveImportL3, 63 | meter_sensor->momentaryReactiveExportL3, 64 | meter_sensor->voltageL1, 65 | meter_sensor->voltageL2, 66 | meter_sensor->voltageL3, 67 | meter_sensor->currentL1, 68 | meter_sensor->currentL2, 69 | meter_sensor->currentL3 70 | }; 71 | sensors: 72 | - name: "Cumulative Active Import" 73 | unit_of_measurement: kWh 74 | accuracy_decimals: 3 75 | - name: "Cumulative Active Export" 76 | unit_of_measurement: kWh 77 | accuracy_decimals: 3 78 | - name: "Cumulative Reactive Import" 79 | unit_of_measurement: kvarh 80 | accuracy_decimals: 3 81 | - name: "Cumulative Reactive Export" 82 | unit_of_measurement: kvarh 83 | accuracy_decimals: 3 84 | - name: "Momentary Active Import" 85 | unit_of_measurement: kW 86 | accuracy_decimals: 3 87 | - name: "Momentary Active Export" 88 | unit_of_measurement: kW 89 | accuracy_decimals: 3 90 | - name: "Momentary Reactive Import" 91 | unit_of_measurement: kvar 92 | accuracy_decimals: 3 93 | - name: "Momentary Reactive Export" 94 | unit_of_measurement: kvar 95 | accuracy_decimals: 3 96 | - name: "Momentary Active Import Phase 1" 97 | unit_of_measurement: kW 98 | accuracy_decimals: 3 99 | - name: "Momentary Active Export Phase 1" 100 | unit_of_measurement: kW 101 | accuracy_decimals: 3 102 | - name: "Momentary Active Import Phase 2" 103 | unit_of_measurement: kW 104 | accuracy_decimals: 3 105 | - name: "Momentary Active Export Phase 2" 106 | unit_of_measurement: kW 107 | accuracy_decimals: 3 108 | - name: "Momentary Active Import Phase 3" 109 | unit_of_measurement: kW 110 | accuracy_decimals: 3 111 | - name: "Momentary Active Export Phase 3" 112 | unit_of_measurement: kW 113 | accuracy_decimals: 3 114 | - name: "Momentary Reactive Import Phase 1" 115 | unit_of_measurement: kvar 116 | accuracy_decimals: 3 117 | - name: "Momentary Reactive Export Phase 1" 118 | unit_of_measurement: kvar 119 | accuracy_decimals: 3 120 | - name: "Momentary Reactive Import Phase 2" 121 | unit_of_measurement: kvar 122 | accuracy_decimals: 3 123 | - name: "Momentary Reactive Export Phase 2" 124 | unit_of_measurement: kvar 125 | accuracy_decimals: 3 126 | - name: "Momentary Reactive Import Phase 3" 127 | unit_of_measurement: kvar 128 | accuracy_decimals: 3 129 | - name: "Momentary Reactive Export Phase 3" 130 | unit_of_measurement: kvar 131 | accuracy_decimals: 3 132 | - name: "Voltage Phase 1" 133 | unit_of_measurement: V 134 | accuracy_decimals: 3 135 | - name: "Voltage Phase 2" 136 | unit_of_measurement: V 137 | accuracy_decimals: 3 138 | - name: "Voltage Phase 3" 139 | unit_of_measurement: V 140 | accuracy_decimals: 3 141 | - name: "Current Phase 1" 142 | unit_of_measurement: A 143 | accuracy_decimals: 3 144 | - name: "Current Phase 2" 145 | unit_of_measurement: A 146 | accuracy_decimals: 3 147 | - name: "Current Phase 3" 148 | unit_of_measurement: A 149 | accuracy_decimals: 3 150 | -------------------------------------------------------------------------------- /p1reader.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------------- 2 | // ESPHome P1 Electricity Meter custom sensor 3 | // Copyright 2020 Pär Svanström 4 | // 5 | // History 6 | // 0.1.0 2020-11-05: Initial release 7 | // 8 | // MIT License 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 10 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | // 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: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // 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 18 | // IN THE SOFTWARE. 19 | //------------------------------------------------------------------------------------- 20 | 21 | #include "esphome.h" 22 | 23 | #define BUF_SIZE 50 24 | 25 | class ParsedMessage { 26 | public: 27 | double cumulativeActiveImport; 28 | double cumulativeActiveExport; 29 | 30 | double cumulativeReactiveImport; 31 | double cumulativeReactiveExport; 32 | 33 | double momentaryActiveImport; 34 | double momentaryActiveExport; 35 | 36 | double momentaryReactiveImport; 37 | double momentaryReactiveExport; 38 | 39 | double momentaryActiveImportL1; 40 | double momentaryActiveExportL1; 41 | 42 | double momentaryActiveImportL2; 43 | double momentaryActiveExportL2; 44 | 45 | double momentaryActiveImportL3; 46 | double momentaryActiveExportL3; 47 | 48 | double momentaryReactiveImportL1; 49 | double momentaryReactiveExportL1; 50 | 51 | double momentaryReactiveImportL2; 52 | double momentaryReactiveExportL2; 53 | 54 | double momentaryReactiveImportL3; 55 | double momentaryReactiveExportL3; 56 | 57 | double voltageL1; 58 | double voltageL2; 59 | double voltageL3; 60 | 61 | double currentL1; 62 | double currentL2; 63 | double currentL3; 64 | 65 | bool crcOk = false; 66 | }; 67 | 68 | class P1Reader : public Component, public UARTDevice { 69 | const char* DELIMITERS = "()*:"; 70 | const char* DATA_ID = "1-0"; 71 | char buffer[BUF_SIZE]; 72 | 73 | public: 74 | Sensor *cumulativeActiveImport = new Sensor(); 75 | Sensor *cumulativeActiveExport = new Sensor(); 76 | 77 | Sensor *cumulativeReactiveImport = new Sensor(); 78 | Sensor *cumulativeReactiveExport = new Sensor(); 79 | 80 | Sensor *momentaryActiveImport = new Sensor(); 81 | Sensor *momentaryActiveExport = new Sensor(); 82 | 83 | Sensor *momentaryReactiveImport = new Sensor(); 84 | Sensor *momentaryReactiveExport = new Sensor(); 85 | 86 | Sensor *momentaryActiveImportL1 = new Sensor(); 87 | Sensor *momentaryActiveExportL1 = new Sensor(); 88 | 89 | Sensor *momentaryActiveImportL2 = new Sensor(); 90 | Sensor *momentaryActiveExportL2 = new Sensor(); 91 | 92 | Sensor *momentaryActiveImportL3 = new Sensor(); 93 | Sensor *momentaryActiveExportL3 = new Sensor(); 94 | 95 | Sensor *momentaryReactiveImportL1 = new Sensor(); 96 | Sensor *momentaryReactiveExportL1 = new Sensor(); 97 | 98 | Sensor *momentaryReactiveImportL2 = new Sensor(); 99 | Sensor *momentaryReactiveExportL2 = new Sensor(); 100 | 101 | Sensor *momentaryReactiveImportL3 = new Sensor(); 102 | Sensor *momentaryReactiveExportL3 = new Sensor(); 103 | 104 | Sensor *voltageL1 = new Sensor(); 105 | Sensor *voltageL2 = new Sensor(); 106 | Sensor *voltageL3 = new Sensor(); 107 | 108 | Sensor *currentL1 = new Sensor(); 109 | Sensor *currentL2 = new Sensor(); 110 | Sensor *currentL3 = new Sensor(); 111 | 112 | P1Reader(UARTComponent *parent) : UARTDevice(parent) {} 113 | 114 | void setup() override { } 115 | 116 | void loop() override { 117 | readP1Message(); 118 | } 119 | 120 | private: 121 | uint16_t crc16_update(uint16_t crc, uint8_t a) { 122 | int i; 123 | crc ^= a; 124 | for (i = 0; i < 8; ++i) { 125 | if (crc & 1) { 126 | crc = (crc >> 1) ^ 0xA001; 127 | } else { 128 | crc = (crc >> 1); 129 | } 130 | } 131 | return crc; 132 | } 133 | 134 | void parseRow(ParsedMessage* parsed, char* obisCode, char* value) { 135 | if (strncmp(obisCode, "1.8.0", 5) == 0) { 136 | parsed->cumulativeActiveImport = atof(value); 137 | 138 | } else if (strncmp(obisCode, "2.8.0", 5) == 0) { 139 | parsed->cumulativeActiveExport = atof(value); 140 | 141 | } else if (strncmp(obisCode, "3.8.0", 5) == 0) { 142 | parsed->cumulativeReactiveImport = atof(value); 143 | 144 | } else if (strncmp(obisCode, "4.8.0", 5) == 0) { 145 | parsed->cumulativeReactiveExport = atof(value); 146 | 147 | } else if (strncmp(obisCode, "1.7.0", 5) == 0) { 148 | parsed->momentaryActiveImport = atof(value); 149 | 150 | } else if (strncmp(obisCode, "2.7.0", 5) == 0) { 151 | parsed->momentaryActiveExport = atof(value); 152 | 153 | } else if (strncmp(obisCode, "3.7.0", 5) == 0) { 154 | parsed->momentaryReactiveImport = atof(value); 155 | 156 | } else if (strncmp(obisCode, "4.7.0", 5) == 0) { 157 | parsed->momentaryReactiveExport = atof(value); 158 | 159 | } else if (strncmp(obisCode, "21.7.0", 6) == 0) { 160 | parsed->momentaryActiveImportL1 = atof(value); 161 | 162 | } else if (strncmp(obisCode, "22.7.0", 6) == 0) { 163 | parsed->momentaryActiveExportL1 = atof(value); 164 | 165 | } else if (strncmp(obisCode, "41.7.0", 6) == 0) { 166 | parsed->momentaryActiveImportL2 = atof(value); 167 | 168 | } else if (strncmp(obisCode, "42.7.0", 6) == 0) { 169 | parsed->momentaryActiveExportL2 = atof(value); 170 | 171 | } else if (strncmp(obisCode, "61.7.0", 6) == 0) { 172 | parsed->momentaryActiveImportL3 = atof(value); 173 | 174 | } else if (strncmp(obisCode, "62.7.0", 6) == 0) { 175 | parsed->momentaryActiveExportL3 = atof(value); 176 | 177 | } else if (strncmp(obisCode, "23.7.0", 6) == 0) { 178 | parsed->momentaryReactiveImportL1 = atof(value); 179 | 180 | } else if (strncmp(obisCode, "24.7.0", 6) == 0) { 181 | parsed->momentaryReactiveExportL1 = atof(value); 182 | 183 | } else if (strncmp(obisCode, "43.7.0", 6) == 0) { 184 | parsed->momentaryReactiveImportL2 = atof(value); 185 | 186 | } else if (strncmp(obisCode, "44.7.0", 6) == 0) { 187 | parsed->momentaryReactiveExportL2 = atof(value); 188 | 189 | } else if (strncmp(obisCode, "63.7.0", 6) == 0) { 190 | parsed->momentaryReactiveImportL3 = atof(value); 191 | 192 | } else if (strncmp(obisCode, "64.7.0", 6) == 0) { 193 | parsed->momentaryReactiveExportL3 = atof(value); 194 | 195 | } else if (strncmp(obisCode, "32.7.0", 6) == 0) { 196 | parsed->voltageL1 = atof(value); 197 | 198 | } else if (strncmp(obisCode, "52.7.0", 6) == 0) { 199 | parsed->voltageL2 = atof(value); 200 | 201 | } else if (strncmp(obisCode, "72.7.0", 6) == 0) { 202 | parsed->voltageL3 = atof(value); 203 | 204 | } else if (strncmp(obisCode, "31.7.0", 6) == 0) { 205 | parsed->currentL1 = atof(value); 206 | 207 | } else if (strncmp(obisCode, "51.7.0", 6) == 0) { 208 | parsed->currentL2 = atof(value); 209 | 210 | } else if (strncmp(obisCode, "71.7.0", 6) == 0) { 211 | parsed->currentL3 = atof(value); 212 | } 213 | } 214 | 215 | void publishSensors(ParsedMessage* parsed) { 216 | cumulativeActiveImport->publish_state(parsed->cumulativeActiveImport); 217 | cumulativeActiveExport->publish_state(parsed->cumulativeActiveExport); 218 | 219 | cumulativeReactiveImport->publish_state(parsed->cumulativeReactiveImport); 220 | cumulativeReactiveExport->publish_state(parsed->cumulativeReactiveExport); 221 | 222 | momentaryActiveImport->publish_state(parsed->momentaryActiveImport); 223 | momentaryActiveExport->publish_state(parsed->momentaryActiveExport); 224 | 225 | momentaryReactiveImport->publish_state(parsed->momentaryReactiveImport); 226 | momentaryReactiveExport->publish_state(parsed->momentaryReactiveExport); 227 | 228 | momentaryActiveImportL1->publish_state(parsed->momentaryActiveImportL1); 229 | momentaryActiveExportL1->publish_state(parsed->momentaryActiveExportL1); 230 | 231 | momentaryActiveImportL2->publish_state(parsed->momentaryActiveImportL2); 232 | momentaryActiveExportL2->publish_state(parsed->momentaryActiveExportL2); 233 | 234 | momentaryActiveImportL3->publish_state(parsed->momentaryActiveImportL3); 235 | momentaryActiveExportL3->publish_state(parsed->momentaryActiveExportL3); 236 | 237 | momentaryReactiveImportL1->publish_state(parsed->momentaryReactiveImportL1); 238 | momentaryReactiveExportL1->publish_state(parsed->momentaryReactiveExportL1); 239 | 240 | momentaryReactiveImportL2->publish_state(parsed->momentaryReactiveImportL2); 241 | momentaryReactiveExportL2->publish_state(parsed->momentaryReactiveExportL2); 242 | 243 | momentaryReactiveImportL3->publish_state(parsed->momentaryReactiveImportL3); 244 | momentaryReactiveExportL3->publish_state(parsed->momentaryReactiveExportL3); 245 | 246 | voltageL1->publish_state(parsed->voltageL1); 247 | voltageL2->publish_state(parsed->voltageL2); 248 | voltageL3->publish_state(parsed->voltageL3); 249 | 250 | currentL1->publish_state(parsed->currentL1); 251 | currentL2->publish_state(parsed->currentL2); 252 | currentL3->publish_state(parsed->currentL3); 253 | } 254 | 255 | void readP1Message() { 256 | if (available()) { 257 | uint16_t crc = 0x0000; 258 | ParsedMessage parsed = ParsedMessage(); 259 | 260 | while (available()) { 261 | int len = readBytesUntil('\n', buffer, BUF_SIZE); 262 | 263 | if (len > 0) { 264 | // put newline back as it is required for CRC calculation 265 | buffer[len] = '\n'; 266 | buffer[len + 1] = '\0'; 267 | 268 | // if we've reached the CRC checksum, calculate last CRC and compare 269 | if (buffer[0] == '!') { 270 | crc = crc16_update(crc, buffer[0]); 271 | int crcFromMsg = (int) strtol(&buffer[1], NULL, 16); 272 | parsed.crcOk = crc == crcFromMsg; 273 | ESP_LOGD("crc", "CRC: %04X = %04X. PASS = %s", crc, crcFromMsg, parsed.crcOk ? "YES": "NO"); 274 | 275 | // otherwise pass the row through the CRC calculation 276 | } else { 277 | for (int i = 0; i < len + 1; i++) { 278 | crc = crc16_update(crc, buffer[i]); 279 | } 280 | } 281 | 282 | // if this is a row containing information 283 | if (strchr(buffer, '(') != NULL) { 284 | char* dataId = strtok(buffer, DELIMITERS); 285 | char* obisCode = strtok(NULL, DELIMITERS); 286 | 287 | // ...and this row is a data row, then parse row 288 | if (strncmp(DATA_ID, dataId, strlen(DATA_ID)) == 0) { 289 | char* value = strtok(NULL, DELIMITERS); 290 | char* unit = strtok(NULL, DELIMITERS); 291 | ESP_LOGD("data", "[%s]: %s %s", obisCode, value, unit); 292 | parseRow(&parsed, obisCode, value); 293 | } 294 | } 295 | } 296 | // clean buffer 297 | memset(buffer, 0, BUF_SIZE - 1); 298 | } 299 | 300 | // if the CRC pass, publish sensor values 301 | if (parsed.crcOk) { 302 | publishSensors(&parsed); 303 | } 304 | } 305 | } 306 | }; 307 | --------------------------------------------------------------------------------