Skip to content

Instantly share code, notes, and snippets.

@KrzysztofHajdamowicz
Created May 30, 2025 07:12
Show Gist options
  • Save KrzysztofHajdamowicz/f5cdde881a96a7dfcd71a29dec8bb500 to your computer and use it in GitHub Desktop.
Save KrzysztofHajdamowicz/f5cdde881a96a7dfcd71a29dec8bb500 to your computer and use it in GitHub Desktop.
Home-Assistant, Eastron SDM72, SDM630 modbus config generator
#!/usr/bin/env python3
# Edit line 324 to adjust list od modbus addresses used in your setup!
import yaml
import re
# Template data as a string
template_yaml = """
- name: Meter_METERID_CHANGEME_volts_L1/N
slave: METERID_CHANGEME
address: 0
input_type: input
precision: 2
data_type: float32
unit_of_measurement: V
device_class: voltage
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_volts_L2/N
slave: METERID_CHANGEME
address: 2
input_type: input
precision: 2
data_type: float32
unit_of_measurement: V
device_class: voltage
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_volts_L3/N
slave: METERID_CHANGEME
address: 4
input_type: input
precision: 2
data_type: float32
unit_of_measurement: V
device_class: voltage
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_volts_average
slave: METERID_CHANGEME
address: 42
input_type: input
precision: 2
data_type: float32
unit_of_measurement: V
device_class: voltage
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L1_current
address: 6
input_type: input
slave: METERID_CHANGEME
precision: 3
data_type: float32
unit_of_measurement: A
device_class: current
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L2_current
address: 8
input_type: input
slave: METERID_CHANGEME
precision: 3
data_type: float32
unit_of_measurement: A
device_class: current
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L3_current
address: 10
input_type: input
slave: METERID_CHANGEME
precision: 3
data_type: float32
unit_of_measurement: A
device_class: current
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L1_power
address: 12
input_type: input
slave: METERID_CHANGEME
precision: 3
scale: 1
data_type: float32
unit_of_measurement: W
device_class: power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L2_power
address: 14
input_type: input
slave: METERID_CHANGEME
precision: 3
data_type: float32
unit_of_measurement: W
device_class: power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L3_power
address: 16
input_type: input
slave: METERID_CHANGEME
precision: 3
data_type: float32
unit_of_measurement: W
device_class: power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L1_power_apparent
address: 18
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: VA
device_class: apparent_power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L2_power_apparent
address: 20
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: VA
device_class: apparent_power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L3_power_apparent
address: 22
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: VA
device_class: apparent_power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L1_power_reactive
address: 24
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: var
device_class: reactive_power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L2_power_reactive
address: 26
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: var
device_class: reactive_power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L3_power_reactive
address: 28
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: var
device_class: reactive_power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L1_power_factor
address: 30
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
device_class: power_factor
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L2_power_factor
address: 32
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
device_class: power_factor
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_L3_power_factor
address: 34
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
device_class: power_factor
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_total_current
address: 48
input_type: input
slave: METERID_CHANGEME
precision: 3
data_type: float32
unit_of_measurement: A
device_class: current
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_total_power
address: 52
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: W
device_class: power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_total_power_apparent
address: 56
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: VA
device_class: apparent_power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_total_power_reactive
address: 60
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: var
device_class: reactive_power
state_class: measurement
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_frequency
address: 70
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: Hz
state_class: measurement
device_class: frequency
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_import_active
address: 72
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: kWh
device_class: energy
state_class: total_increasing
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_export_active
address: 74
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: kWh
device_class: energy
state_class: total_increasing
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_import_reactive
address: 78
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: kVArh
device_class: energy
state_class: total_increasing
unique_id: UUID_CHANGEME
- name: Meter_METERID_CHANGEME_export_reactive
address: 80
input_type: input
slave: METERID_CHANGEME
precision: 2
data_type: float32
unit_of_measurement: kVArh
device_class: energy
state_class: total_increasing
unique_id: UUID_CHANGEME
"""
def generate_meter_config(meter_id):
"""Generate configuration for a specific meter ID"""
# Load the template
template_data = yaml.safe_load(template_yaml)
# Process each sensor in the template
for sensor in template_data:
# Replace METERID_CHANGEME with actual meter ID
sensor['name'] = sensor['name'].replace('METERID_CHANGEME', str(meter_id))
# Update slave ID to match meter ID
sensor['slave'] = meter_id
# Generate unique_id based on meter ID and address
address = sensor['address']
sensor['unique_id'] = f"modbus_{meter_id}_address_{address}"
return template_data
def generate_all_meters():
"""Generate configurations for all meters (1-8 and 200)"""
all_configs = {}
# Generate for meters 1-8 and 200
meter_ids = list(range(1, 9)) + [200]
for meter_id in meter_ids:
config = generate_meter_config(meter_id)
all_configs[f'meter_{meter_id}'] = config
return all_configs
def generate_flat_config():
"""Generate a flat list of all sensors for all meters"""
flat_config = []
# Generate for meters 1-8 and 200
meter_ids = list(range(1, 9)) + [200]
for meter_id in meter_ids:
config = generate_meter_config(meter_id)
flat_config.extend(config)
return flat_config
def save_configs(configs, combined=False):
"""Save configurations to YAML files"""
if combined:
# Generate flat configuration for Home Assistant
flat_config = generate_flat_config()
with open('all_meters_config.yaml', 'w') as f:
yaml.dump(flat_config, f, default_flow_style=False, sort_keys=False)
print("Generated combined configuration: all_meters_config.yaml")
else:
# Save each meter in a separate file
for meter_name, config in configs.items():
filename = f'{meter_name}_config.yaml'
with open(filename, 'w') as f:
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
print(f"Generated configuration: {filename}")
def main():
# Generate configurations for all meters
all_configs = generate_all_meters()
# Save as separate files
save_configs(all_configs, combined=False)
# Save as a combined flat file for Home Assistant
save_configs(all_configs, combined=True)
# Print a sample to verify
print("\nSample from combined configuration:")
flat_sample = generate_flat_config()[:4] # First 4 sensors
print(yaml.dump(flat_sample, default_flow_style=False))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment