Created
June 5, 2025 05:26
-
-
Save KrzysztofHajdamowicz/3cd91264e8187b323e0a2a674300de2d to your computer and use it in GitHub Desktop.
ESPHome + 9x Eastron SDM120, Eastron SDM630 Utility Meter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| esphome: | |
| name: "poc-modbus" | |
| friendly_name: PoC-Modbus | |
| platformio_options: | |
| build_flags: | |
| - -DCONFIG_ARDUINO_LOOP_STACK_SIZE=32768 | |
| external_components: | |
| - source: github://pr#8032 | |
| components: [ modbus, uart, modbus_controller ] | |
| esp32: | |
| board: esp32dev | |
| framework: | |
| type: esp-idf | |
| # Enable logging | |
| logger: | |
| wifi: | |
| ssid: !secret wifi_ssid | |
| password: !secret wifi_password | |
| # Enable fallback hotspot (captive portal) in case wifi connection fails | |
| ap: | |
| ssid: "Poc-Modbus Fallback Hotspot" | |
| password: "..." | |
| captive_portal: | |
| uart: | |
| id: uart_bus | |
| tx_pin: GPIO25 | |
| rx_pin: GPIO27 | |
| baud_rate: 19200 | |
| stop_bits: 1 | |
| rx_full_threshold: 1 | |
| modbus: | |
| id: modbus_1 | |
| uart_id: uart_bus | |
| send_wait_time: 600ms | |
| turnaround_time: 200ms | |
| flow_control_pin: GPIO26 | |
| globals: | |
| - id: current_meter_polling_index # Current position in polling carousel | |
| type: int | |
| initial_value: '0' | |
| - id: ha_connected_initial # Tracks if HA has connected at least once since boot | |
| type: bool | |
| initial_value: 'false' | |
| - id: modbus_polling_active # Controls if the polling lambda should run | |
| type: bool | |
| initial_value: 'false' | |
| ota: | |
| - platform: esphome | |
| password: "..." | |
| on_begin: | |
| then: | |
| - logger.log: "OTA Start. Disabling modbus polling" | |
| - globals.set: | |
| id: modbus_polling_active | |
| value: 'true' | |
| # Enable Home Assistant API | |
| api: | |
| encryption: | |
| key: "..." | |
| # This will be triggered when Home Assistant (or any other API client) connects | |
| on_client_connected: | |
| then: | |
| - lambda: |- | |
| if (!id(ha_connected_initial)) { // Only act fully on the very first connection | |
| ESP_LOGI("main", "Home Assistant client connected for the first time. Scheduling Modbus polling activation."); | |
| id(ha_connected_initial) = true; // Mark that HA has connected | |
| // Execute the script to enable polling after a delay | |
| // The script itself will check if polling is already active | |
| id(start_modbus_polling_delayed).execute(); | |
| } else { | |
| ESP_LOGI("main", "Home Assistant client reconnected. Polling state remains as is."); | |
| } | |
| script: | |
| - id: start_modbus_polling_delayed | |
| mode: single # Ensures this script only runs one instance at a time | |
| then: | |
| - lambda: |- | |
| // Only proceed if polling is not already active | |
| if (id(modbus_polling_active)) { | |
| ESP_LOGI("script.start_modbus_polling_delayed", "Modbus polling is already active. Exiting script."); | |
| return; | |
| } | |
| ESP_LOGI("script.start_modbus_polling_delayed", "Starting delay before activating Modbus polling."); | |
| - delay: 15s # Wait for 15 seconds to settle HA connection handshake etc | |
| - lambda: |- | |
| ESP_LOGI("script.start_modbus_polling_delayed", "Delay complete. Activating Modbus polling now."); | |
| id(modbus_polling_active) = true; | |
| interval: | |
| - interval: 2s # Poll one meter every 2 seconds. Adjust as needed. | |
| # For 9 meters, this means each meter updates every 9 * 2s = 18 seconds. | |
| then: | |
| - lambda: |- | |
| if (!id(modbus_polling_active)) { | |
| if (id(ha_connected_initial)) { | |
| ESP_LOGD("meter_polling_interval", "Modbus polling is not yet active (waiting for delayed start)."); | |
| } else { | |
| ESP_LOGD("meter_polling_interval", "Waiting for Home Assistant to connect and polling to be activated."); | |
| } | |
| return; // Don't poll if not yet active | |
| } | |
| // Array of meter component IDs (defined via packages) | |
| ESP_LOGD("meter_polling_interval", "Updating meter at index: %d", id(current_meter_polling_index)); | |
| switch (id(current_meter_polling_index)) { | |
| case 0: id(sdm_meter_instance_200).update(); break; | |
| case 1: id(sdm_meter_instance_1).update(); break; | |
| case 2: id(sdm_meter_instance_2).update(); break; | |
| case 3: id(sdm_meter_instance_3).update(); break; | |
| case 4: id(sdm_meter_instance_4).update(); break; | |
| case 5: id(sdm_meter_instance_5).update(); break; | |
| case 6: id(sdm_meter_instance_6).update(); break; | |
| // case 6: id(sdm_meter_instance_7).update(); break; | |
| // case 7: id(sdm_meter_instance_8).update(); break; | |
| } | |
| id(current_meter_polling_index)++; | |
| if (id(current_meter_polling_index) >= 7) { // 9 is the total number of meters | |
| id(current_meter_polling_index) = 0; | |
| } | |
| packages: | |
| wifi: !include includes/packages/wifi.yaml | |
| board_temp: !include includes/packages/internal_temp.yaml | |
| uptime: !include includes/packages/uptime_timestamp.yaml | |
| time: !include includes/packages/time.yaml | |
| restart: !include includes/packages/restart.yaml | |
| meter200: !include | |
| file: includes/packages/sdm_meter_template_3ph.yaml | |
| vars: | |
| address: "200" | |
| meter_component_id: sdm_meter_instance_200 | |
| meter1: !include | |
| file: includes/packages/sdm_meter_template_1ph.yaml | |
| vars: | |
| address: "1" | |
| meter_component_id: sdm_meter_instance_1 | |
| meter2: !include | |
| file: includes/packages/sdm_meter_template_1ph.yaml | |
| vars: | |
| address: "2" | |
| meter_component_id: sdm_meter_instance_2 | |
| meter3: !include | |
| file: includes/packages/sdm_meter_template_1ph.yaml | |
| vars: | |
| address: "3" | |
| meter_component_id: sdm_meter_instance_3 | |
| meter4: !include | |
| file: includes/packages/sdm_meter_template_1ph.yaml | |
| vars: | |
| address: "4" | |
| meter_component_id: sdm_meter_instance_4 | |
| meter5: !include | |
| file: includes/packages/sdm_meter_template_1ph.yaml | |
| vars: | |
| address: "5" | |
| meter_component_id: sdm_meter_instance_5 | |
| meter6: !include | |
| file: includes/packages/sdm_meter_template_1ph.yaml | |
| vars: | |
| address: "6" | |
| meter_component_id: sdm_meter_instance_6 | |
| meter7: !include | |
| file: includes/packages/sdm_meter_template_1ph.yaml | |
| vars: | |
| address: "7" | |
| meter_component_id: sdm_meter_instance_7 | |
| meter8: !include | |
| file: includes/packages/sdm_meter_template_1ph.yaml | |
| vars: | |
| address: "8" | |
| meter_component_id: sdm_meter_instance_8 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| substitutions: | |
| address: "1" | |
| meter_component_id: sdm_meter_instance_${address} | |
| sensor: | |
| - platform: sdm_meter | |
| modbus_id: modbus_1 | |
| address: ${address} | |
| id: ${meter_component_id} | |
| update_interval: "never" | |
| phase_a: | |
| voltage: | |
| name: "Meter ${address} L1 Voltage" | |
| current: | |
| name: "Meter ${address} L1 Current" | |
| active_power: | |
| name: "Meter ${address} L1 Active Power" | |
| apparent_power: | |
| name: "Meter ${address} L1 Apparent Power" | |
| reactive_power: | |
| name: "Meter ${address} L1 Reactive Power" | |
| power_factor: | |
| name: "Meter ${address} L1 Power Factor" | |
| phase_angle: | |
| name: "Meter ${address} L1 Phase Angle" | |
| frequency: | |
| name: "Meter ${address} Grid Frequency" | |
| total_power: | |
| name: "Meter ${address} Total Active Power" | |
| import_active_energy: | |
| name: "Meter ${address} Energy Import Active" | |
| export_active_energy: | |
| name: "Meter ${address} Energy Export Active" | |
| import_reactive_energy: | |
| name: "Meter ${address} Energy Import Reactive" | |
| export_reactive_energy: | |
| name: "Meter ${address} Energy Export Reactive" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| substitutions: | |
| address: "1" | |
| meter_component_id: sdm_meter_instance_${address} | |
| sensor: | |
| - platform: sdm_meter | |
| modbus_id: modbus_1 | |
| address: ${address} | |
| id: ${meter_component_id} | |
| update_interval: "never" | |
| phase_a: | |
| voltage: | |
| name: "Meter ${address} L1 Voltage" | |
| current: | |
| name: "Meter ${address} L1 Current" | |
| active_power: | |
| name: "Meter ${address} L1 Active Power" | |
| apparent_power: | |
| name: "Meter ${address} L1 Apparent Power" | |
| reactive_power: | |
| name: "Meter ${address} L1 Reactive Power" | |
| power_factor: | |
| name: "Meter ${address} L1 Power Factor" | |
| phase_angle: | |
| name: "Meter ${address} L1 Phase Angle" | |
| phase_b: | |
| voltage: | |
| name: "Meter ${address} L2 Voltage" | |
| disabled_by_default: true | |
| current: | |
| name: "Meter ${address} L2 Current" | |
| disabled_by_default: true | |
| active_power: | |
| name: "Meter ${address} L2 Active Power" | |
| disabled_by_default: true | |
| apparent_power: | |
| name: "Meter ${address} L2 Apparent Power" | |
| disabled_by_default: true | |
| reactive_power: | |
| name: "Meter ${address} L2 Reactive Power" | |
| disabled_by_default: true | |
| power_factor: | |
| name: "Meter ${address} L2 Power Factor" | |
| disabled_by_default: true | |
| phase_angle: | |
| name: "Meter ${address} L2 Phase Angle" | |
| disabled_by_default: true | |
| phase_c: | |
| voltage: | |
| name: "Meter ${address} L3 Voltage" | |
| disabled_by_default: true | |
| current: | |
| name: "Meter ${address} L3 Current" | |
| disabled_by_default: true | |
| active_power: | |
| name: "Meter ${address} L3 Active Power" | |
| disabled_by_default: true | |
| apparent_power: | |
| name: "Meter ${address} L3 Apparent Power" | |
| disabled_by_default: true | |
| reactive_power: | |
| name: "Meter ${address} L3 Reactive Power" | |
| disabled_by_default: true | |
| power_factor: | |
| name: "Meter ${address} L3 Power Factor" | |
| disabled_by_default: true | |
| phase_angle: | |
| name: "Meter ${address} L3 Phase Angle" | |
| disabled_by_default: true | |
| frequency: | |
| name: "Meter ${address} Grid Frequency" | |
| total_power: | |
| name: "Meter ${address} Total Active Power" | |
| import_active_energy: | |
| name: "Meter ${address} Energy Import Active" | |
| export_active_energy: | |
| name: "Meter ${address} Energy Export Active" | |
| import_reactive_energy: | |
| name: "Meter ${address} Energy Import Reactive" | |
| export_reactive_energy: | |
| name: "Meter ${address} Energy Export Reactive" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment