From 6dbe924028d1765f6dda80d21a56923131e6333d Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Wed, 31 Dec 2025 12:20:14 +0000 Subject: [PATCH 01/20] Update json files --- SOFAR-HYD-3PH-AND-G3.json | 24 + SOFAR-HYD-ES-AND-ME3000-SP.json | 814 +++++++++++++++++++++++++++----- 2 files changed, 727 insertions(+), 111 deletions(-) diff --git a/SOFAR-HYD-3PH-AND-G3.json b/SOFAR-HYD-3PH-AND-G3.json index 609a2a4..5a8ac28 100644 --- a/SOFAR-HYD-3PH-AND-G3.json +++ b/SOFAR-HYD-3PH-AND-G3.json @@ -46,6 +46,30 @@ } ], "registers": [ + { + "name": "serial_number", + "register": "0x0445", + "read_type": "string", + "registers": 7, + "ha": { + "name": "Serial Number", + "icon": "mdi:identifier", + "entity_category": "diagnostic", + "value_template": "{{ value_json.serial_number }}" + }, + "refresh": 86400 + }, + { + "name": "model", + "value": "HYD-6000-EP", + "read_type": "static", + "ha": { + "name": "Model", + "icon": "mdi:tag-text", + "value_template": "{{ value_json.model }}" + }, + "refresh": 86400 + }, { "name": "hw_version", "register": "0x044D", diff --git a/SOFAR-HYD-ES-AND-ME3000-SP.json b/SOFAR-HYD-ES-AND-ME3000-SP.json index b187e12..c055a4c 100644 --- a/SOFAR-HYD-ES-AND-ME3000-SP.json +++ b/SOFAR-HYD-ES-AND-ME3000-SP.json @@ -1,113 +1,705 @@ { - "registers": [ - { - "name": "running_state", - "register": "0x200", - "function": "mode", - "modes": { - "0": "Standby", - "2": "Charging", - "4": "Discharging", - "6": "Fault" - } - }, - { - "name": "grid_voltage", - "register": "0x0206", - "function": "divide", - "factor": 10 - }, - { - "name": "battery_power", - "register": "0x20d", - "function": "multiply", - "factor": 10 - }, - { - "name": "grid_freq", - "register": "0x20c", - "function": "divide", - "factor": 100 - }, - { - "name": "batterySOC", - "register": "0x210" - }, - { - "name": "battery_voltage", - "register": "0x20e", - "function": "divide", - "factor": 100 - }, - { - "name": "battery_cycles", - "register": "0x22c" - }, - { - "name": "battery_temp", - "register": "0x211" - }, - { - "name": "grid_power", - "register": "0x212", - "function": "multiply", - "factor": 10 - }, - { - "name": "consumption", - "register": "0x213", - "function": "multiply", - "factor": 10 - }, - { - "name": "solarPV", - "register": "0x215", - "function": "multiply", - "factor": 10 - }, - { - "name": "today_generation", - "register": "0x218", - "function": "divide", - "factor": 100 - }, - { - "name": "today_exported", - "register": "0x219", - "function": "divide", - "factor": 100 - }, - { - "name": "today_purchase", - "register": "0x21a", - "function": "divide", - "factor": 100 - }, - { - "name": "today_consumption", - "register": "0x21b", - "function": "divide", - "factor": 100 - }, - { - "name": "inverter_temp", - "register": "0x238" - }, - { - "name": "inverterHS_temp", - "register": "0x239" - }, - { - "name": "solarPVAmps", - "register": "0x236", - "function": "divide", - "factor": 100 - }, - { - "name": "battery_current", - "register": "0x207", - "function": "divide", - "factor": 100 - } - ] + "registers": [ + { + "name": "serial_number", + "read_type": "static", + "value": "ME3000SP", + "ha": { + "name": "Serial Number", + "icon": "mdi:identifier", + "value_template": "{{ value_json.serial_number }}" + }, + "refresh": 86400 + }, + { + "name": "operating_mode", + "desc": "Operating mode control - determines charge/discharge behavior", + "type": "U16", + "function": "mode", + "modes": { + "0x0100": "Standby", + "0x0101": "Discharge", + "0x0102": "Charge", + "0x0103": "Auto" + }, + "write": true, + "mode_params": { + "0x0100": 21845, + "0x0101": 0, + "0x0102": 0, + "0x0103": 21845 + }, + "ha": { + "command_topic": "sofar/rw/operating_mode", + "entity_category": "config", + "name": "Operating Mode", + "options": [ + "Standby", + "Discharge", + "Charge", + "Auto" + ], + "value_template": "{{ value_json.operating_mode }}", + "icon": "mdi:auto-mode", + "control": "select" + } + }, + { + "name": "charge_discharge_power", + "desc": "Charge/Discharge power (0-3000W) - used with Charge/Discharge modes", + "type": "U16", + "min": 0, + "max": 3000, + "write": true, + "passive": true, + "linked_register": "operating_mode", + "ha": { + "command_topic": "sofar/rw/charge_discharge_power", + "entity_category": "config", + "name": "Charge/Discharge Power", + "min": 0, + "max": 3000, + "step": 100, + "initial": 0, + "mode": "slider", + "value_template": "{{ value_json.charge_discharge_power }}", + "icon": "mdi:battery-charging", + "unit_of_measurement": "W", + "control": "number" + } + }, + { + "name": "running_state", + "register": "0x200", + "function": "mode", + "modes": { + "0": "Standby", + "2": "Charging", + "4": "Discharging", + "6": "Fault" + }, + "ha": { + "name": "Running State", + "icon": "mdi:power-standby", + "value_template": "{{ value_json.running_state }}" + } + }, + { + "name": "model", + "value": "ME-3000SP", + "read_type": "static", + "ha": { + "name": "Model", + "icon": "mdi:tag-text", + "value_template": "{{ value_json.model }}" + }, + "refresh": 86400 + }, + { + "name": "hw_version", + "value": "TBD", + "read_type": "static", + "ha": { + "name": "HW Version", + "icon": "mdi:numeric", + "value_template": "{{ value_json.hw_version }}" + }, + "refresh": 86400 + }, + { + "name": "sw_version_com", + "read_type": "static", + "value": "TBD", + "ha": { + "name": "Software Version", + "icon": "mdi:numeric", + "value_template": "{{ value_json.sw_version_com }}" + }, + "refresh": 86400 + }, + { + "name": "fault_list_1", + "register": "0x0201", + "ha": { + "name": "Fault List 1", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_1 }}" + } + }, + { + "name": "fault_list_2", + "register": "0x0202", + "ha": { + "name": "Fault List 2", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_2 }}" + } + }, + { + "name": "fault_list_3", + "register": "0x0203", + "ha": { + "name": "Fault List 3", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_3 }}" + } + }, + { + "name": "fault_list_4", + "register": "0x0204", + "ha": { + "name": "Fault List 4", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_4 }}" + } + }, + { + "name": "fault_list_5", + "register": "0x0205", + "ha": { + "name": "Fault List 5", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_5 }}" + } + }, + { + "name": "grid_voltage", + "register": "0x0206", + "function": "divide", + "factor": 10, + "ha": { + "device_class": "voltage", + "unit_of_measurement": "V", + "name": "Grid Voltage", + "icon": "mdi:alpha-v-box", + "state_class": "measurement", + "value_template": "{{ value_json.grid_voltage }}" + } + }, + { + "name": "grid_current", + "register": "0x0207", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "current", + "unit_of_measurement": "A", + "name": "Grid Current", + "icon": "mdi:alpha-a-box", + "state_class": "measurement", + "value_template": "{{ value_json.grid_current }}" + } + }, + { + "name": "battery_power", + "register": "0x20d", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "Battery Power", + "icon": "mdi:battery-charging", + "state_class": "measurement", + "value_template": "{{ value_json.battery_power }}" + } + }, + { + "name": "grid_freq", + "register": "0x20c", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "frequency", + "unit_of_measurement": "Hz", + "name": "Grid Frequency", + "icon": "mdi:sine-wave", + "state_class": "measurement", + "value_template": "{{ value_json.grid_freq }}" + } + }, + { + "name": "batterySOC", + "register": "0x210", + "ha": { + "device_class": "battery", + "unit_of_measurement": "%", + "name": "Battery SOC", + "icon": "mdi:battery-80", + "state_class": "measurement", + "value_template": "{{ value_json.batterySOC }}" + } + }, + { + "name": "battery_voltage", + "register": "0x20e", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "voltage", + "unit_of_measurement": "V", + "name": "Battery Voltage", + "icon": "mdi:alpha-v-box", + "state_class": "measurement", + "value_template": "{{ value_json.battery_voltage }}" + } + }, + { + "name": "battery_current", + "register": "0x020F", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "current", + "unit_of_measurement": "A", + "name": "Battery Current", + "icon": "mdi:alpha-a-box", + "state_class": "measurement", + "value_template": "{{ value_json.battery_current }}" + } + }, + { + "name": "battery_temp", + "register": "0x211", + "ha": { + "device_class": "temperature", + "unit_of_measurement": "°C", + "name": "Battery Temperature", + "icon": "mdi:temperature-celsius", + "state_class": "measurement", + "value_template": "{{ value_json.battery_temp }}" + } + }, + { + "name": "grid_power", + "register": "0x212", + "function": "multiply", + "factor": 10, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "Grid Power", + "icon": "mdi:lightning-bolt", + "state_class": "measurement", + "value_template": "{{ value_json.grid_power }}" + } + }, + { + "name": "consumption", + "register": "0x213", + "function": "multiply", + "factor": 10, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "Load Power", + "icon": "mdi:home-lightning-bolt-outline", + "state_class": "measurement", + "value_template": "{{ value_json.consumption }}" + } + }, + { + "name": "inverter_power", + "register": "0x0214", + "function": "multiply", + "factor": 10, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "Inverter Power", + "icon": "mdi:lightning-bolt", + "state_class": "measurement", + "value_template": "{{ value_json.inverter_power }}" + } + }, + { + "name": "solarPV", + "register": "0x215", + "function": "multiply", + "factor": 10, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "Solar PV Power", + "icon": "mdi:solar-power", + "state_class": "measurement", + "value_template": "{{ value_json.solarPV }}" + } + }, + { + "name": "eps_output_voltage", + "register": "0x0216", + "function": "divide", + "factor": 10, + "ha": { + "device_class": "voltage", + "unit_of_measurement": "V", + "name": "EPS Output Voltage", + "icon": "mdi:alpha-v-box", + "state_class": "measurement", + "value_template": "{{ value_json.eps_output_voltage }}" + } + }, + { + "name": "eps_output_power", + "register": "0x0217", + "function": "multiply", + "factor": 10, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "EPS Output Power", + "icon": "mdi:lightning-bolt", + "state_class": "measurement", + "value_template": "{{ value_json.eps_output_power }}" + } + }, + { + "name": "today_generation", + "register": "0x218", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Solar Generation Today", + "icon": "mdi:solar-power-variant", + "state_class": "total_increasing", + "value_template": "{{ value_json.today_generation }}" + } + }, + { + "name": "today_exported", + "register": "0x219", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Export Today", + "icon": "mdi:transmission-tower-export", + "state_class": "total_increasing", + "value_template": "{{ value_json.today_exported }}" + } + }, + { + "name": "today_purchase", + "register": "0x21a", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Import Today", + "icon": "mdi:transmission-tower-import", + "state_class": "total_increasing", + "value_template": "{{ value_json.today_purchase }}" + } + }, + { + "name": "today_consumption", + "register": "0x21b", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Consumption Today", + "icon": "mdi:home-lightning-bolt-outline", + "state_class": "total_increasing", + "value_template": "{{ value_json.today_consumption }}" + } + }, + { + "name": "total_generation_hi", + "register": "0x021C", + "ha": { + "name": "Total Generation Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.total_generation_hi }}" + } + }, + { + "name": "total_generation_lo", + "register": "0x021D", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Total Generation", + "icon": "mdi:solar-power-variant", + "state_class": "total_increasing", + "value_template": "{{ value_json.total_generation_lo }}" + } + }, + { + "name": "total_export_hi", + "register": "0x021E", + "ha": { + "name": "Total Export Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.total_export_hi }}" + } + }, + { + "name": "total_export_lo", + "register": "0x021F", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Total Export", + "icon": "mdi:transmission-tower-export", + "state_class": "total_increasing", + "value_template": "{{ value_json.total_export_lo }}" + } + }, + { + "name": "total_import_hi", + "register": "0x0220", + "ha": { + "name": "Total Import Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.total_import_hi }}" + } + }, + { + "name": "total_import_lo", + "register": "0x0221", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Total Import", + "icon": "mdi:transmission-tower-import", + "state_class": "total_increasing", + "value_template": "{{ value_json.total_import_lo }}" + } + }, + { + "name": "total_consumption_hi", + "register": "0x0222", + "ha": { + "name": "Total Consumption Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.total_consumption_hi }}" + } + }, + { + "name": "total_consumption_lo", + "register": "0x0223", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Total Consumption", + "icon": "mdi:home-lightning-bolt-outline", + "state_class": "total_increasing", + "value_template": "{{ value_json.total_consumption_lo }}" + } + }, + { + "name": "battery_charge_today", + "register": "0x0224", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Battery Charge Today", + "icon": "mdi:battery-charging-80", + "state_class": "total_increasing", + "value_template": "{{ value_json.battery_charge_today }}" + } + }, + { + "name": "battery_discharge_today", + "register": "0x0225", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Battery Discharge Today", + "icon": "mdi:battery-minus", + "state_class": "total_increasing", + "value_template": "{{ value_json.battery_discharge_today }}" + } + }, + { + "name": "battery_total_charge_hi", + "register": "0x0226", + "ha": { + "name": "Battery Total Charge Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.battery_total_charge_hi }}" + } + }, + { + "name": "battery_total_charge_lo", + "register": "0x0227", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Battery Total Charge", + "icon": "mdi:battery-charging-80", + "state_class": "total_increasing", + "value_template": "{{ value_json.battery_total_charge_lo }}" + } + }, + { + "name": "battery_total_discharge_hi", + "register": "0x0228", + "ha": { + "name": "Battery Total Discharge Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.battery_total_discharge_hi }}" + } + }, + { + "name": "battery_total_discharge_lo", + "register": "0x0229", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Battery Total Discharge", + "icon": "mdi:battery-minus", + "state_class": "total_increasing", + "value_template": "{{ value_json.battery_total_discharge_lo }}" + } + }, + { + "name": "countdown_time", + "register": "0x022A", + "ha": { + "name": "Countdown Time", + "unit_of_measurement": "s", + "icon": "mdi:timer", + "value_template": "{{ value_json.countdown_time }}" + } + }, + { + "name": "inverter_alarm_info", + "register": "0x022B", + "ha": { + "name": "Inverter Alarm Information", + "icon": "mdi:alert-circle", + "value_template": "{{ value_json.inverter_alarm_info }}" + } + }, + { + "name": "battery_cycles", + "register": "0x22c", + "ha": { + "device_class": "battery", + "unit_of_measurement": "cycles", + "name": "Battery Cycles", + "icon": "mdi:battery-sync", + "state_class": "total_increasing", + "value_template": "{{ value_json.battery_cycles }}" + } + }, + { + "name": "inverter_bus_voltage", + "register": "0x022D", + "function": "divide", + "factor": 10, + "ha": { + "device_class": "voltage", + "unit_of_measurement": "V", + "name": "Inverter Bus Voltage", + "icon": "mdi:alpha-v-box", + "state_class": "measurement", + "value_template": "{{ value_json.inverter_bus_voltage }}" + } + }, + { + "name": "llc_bus_voltage", + "register": "0x022E", + "function": "divide", + "factor": 10, + "ha": { + "device_class": "voltage", + "unit_of_measurement": "V", + "name": "LLC Bus Voltage", + "icon": "mdi:alpha-v-box", + "state_class": "measurement", + "value_template": "{{ value_json.llc_bus_voltage }}" + } + }, + { + "name": "buck_current", + "register": "0x022F", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "current", + "unit_of_measurement": "A", + "name": "Buck Current", + "icon": "mdi:alpha-a-box", + "state_class": "measurement", + "value_template": "{{ value_json.buck_current }}" + } + }, + { + "name": "battery_health_soh", + "register": "0x0237", + "ha": { + "device_class": "battery", + "unit_of_measurement": "%", + "name": "Battery Health (SOH)", + "icon": "mdi:battery-heart", + "state_class": "measurement", + "value_template": "{{ value_json.battery_health_soh }}" + } + }, + { + "name": "inverter_temp", + "register": "0x238", + "ha": { + "device_class": "temperature", + "unit_of_measurement": "°C", + "name": "Inverter Temperature", + "icon": "mdi:temperature-celsius", + "state_class": "measurement", + "value_template": "{{ value_json.inverter_temp }}" + } + }, + { + "name": "inverterHS_temp", + "register": "0x239", + "ha": { + "device_class": "temperature", + "unit_of_measurement": "°C", + "name": "Heatsink Temperature", + "icon": "mdi:temperature-celsius", + "state_class": "measurement", + "value_template": "{{ value_json.inverterHS_temp }}" + } + }, + { + "name": "solarPVAmps", + "register": "0x236", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "current", + "unit_of_measurement": "A", + "name": "Solar PV Current", + "icon": "mdi:alpha-a-box", + "state_class": "measurement", + "value_template": "{{ value_json.solarPVAmps }}" + } + }, + { + "name": "battery_current", + "register": "0x207", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "current", + "unit_of_measurement": "A", + "name": "Battery Current", + "icon": "mdi:alpha-a-box", + "state_class": "measurement", + "value_template": "{{ value_json.battery_current }}" + } + } + ] } \ No newline at end of file From 225eadfea6759fd48ee8a0e59360e616f86582a2 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Wed, 31 Dec 2025 17:20:47 +0000 Subject: [PATCH 02/20] Add historical event information --- SOFAR-HYD-3PH-AND-G3.json | 452 +++++++++++++++++++++++++++++++++----- sofar2mqtt-v2.py | 85 ++++++- 2 files changed, 476 insertions(+), 61 deletions(-) diff --git a/SOFAR-HYD-3PH-AND-G3.json b/SOFAR-HYD-3PH-AND-G3.json index 5a8ac28..d5af0dc 100644 --- a/SOFAR-HYD-3PH-AND-G3.json +++ b/SOFAR-HYD-3PH-AND-G3.json @@ -21,7 +21,9 @@ "battery_config_cell_type", "battery_config_eps_buffer" ], - "append": [1] + "append": [ + 1 + ] }, { "name": "eps_config", @@ -42,7 +44,9 @@ "rs485_config_stop_bit", "rs485_config_parity_bit" ], - "append": [1] + "append": [ + 1 + ] } ], "registers": [ @@ -54,7 +58,6 @@ "ha": { "name": "Serial Number", "icon": "mdi:identifier", - "entity_category": "diagnostic", "value_template": "{{ value_json.serial_number }}" }, "refresh": 86400 @@ -78,7 +81,6 @@ "ha": { "name": "HW Version", "icon": "mdi:numeric", - "entity_category": "diagnostic", "value_template": "{{ value_json.hw_version }}" }, "refresh": 86400 @@ -91,7 +93,6 @@ "ha": { "name": "Software Version", "icon": "mdi:numeric", - "entity_category": "diagnostic", "value_template": "{{ value_json.sw_version_com }}" }, "refresh": 86400 @@ -104,7 +105,6 @@ "ha": { "name": "DSP1 Software Version", "icon": "mdi:numeric", - "entity_category": "diagnostic", "value_template": "{{ value_json.sw_version_master }}" }, "refresh": 86400 @@ -117,7 +117,6 @@ "ha": { "name": "DSP2 Software Version", "icon": "mdi:numeric", - "entity_category": "diagnostic", "value_template": "{{ value_json.sw_version_slave }}" }, "refresh": 86400 @@ -683,7 +682,6 @@ "ha": { "name": "Time of use", "icon": "mdi:home-clock", - "entity_category": "diagnostic", "value_template": "{{ value_json.time_of_use }}" }, "refresh": 3600 @@ -696,7 +694,6 @@ "ha": { "name": "Time of use - Start time", "icon": "mdi:clock-digital", - "entity_category": "diagnostic", "value_template": "{{ value_json.time_of_use_charge_start_time }}" }, "refresh": 3600 @@ -709,7 +706,6 @@ "ha": { "name": "Time of use - End time", "icon": "mdi:clock-digital", - "entity_category": "diagnostic", "value_template": "{{ value_json.time_of_use_charge_end_time }}" }, "refresh": 3600 @@ -725,7 +721,6 @@ "ha": { "name": "Time of use - Target SOC", "icon": "mdi:percent-outline", - "entity_category": "diagnostic", "device_class": "battery", "unit_of_measurement": "%", "state_class": "measurement", @@ -743,7 +738,6 @@ "device_class": "power", "unit_of_measurement": "W", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.time_of_use_charge_power }}" }, "refresh": 3600 @@ -756,7 +750,6 @@ "ha": { "name": "Time of use - Start date", "icon": "mdi:calendar", - "entity_category": "diagnostic", "value_template": "{{ value_json.time_of_use_start_date }}" }, "refresh": 3600 @@ -769,7 +762,6 @@ "ha": { "name": "Time of use - End date", "icon": "mdi:calendar", - "entity_category": "diagnostic", "value_template": "{{ value_json.time_of_use_end_date }}" }, "refresh": 3600 @@ -790,7 +782,6 @@ "ha": { "name": "Time of use - Days of week", "icon": "mdi:calendar-today", - "entity_category": "diagnostic", "value_template": "{{ value_json.time_of_use_dow }}" }, "refresh": 3600 @@ -860,10 +851,88 @@ "ha": { "name": "State", "icon": "mdi:power-standby", - "entity_category": "diagnostic", + "entity_category": "None", "value_template": "{{ value_json.state }}" } }, + { + "name": "history_event_list_1", + "register": "0x1480", + "function": "history_event_map", + "ha": { + "name": "History Event List 1", + "icon": "mdi:calendar-alert", + "value_template": "{{ value_json.history_event_list_1 }}" + }, + "notify_on_change": true + }, + { + "name": "history_event_list_1_datetime", + "aggregate_datetime_bitmap": { + "year_month": "0x1481", + "day_hour": "0x1482", + "minute_second": "0x1483" + }, + "ha": { + "name": "History Event List 1 - DateTime", + "icon": "mdi:calendar-clock", + "value_template": "{{ value_json.history_event_list_1_datetime }}" + } + }, + { + "name": "fault_list_1", + "register": "0x0405", + "ha": { + "name": "Fault List 1", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_1 }}" + } + }, + { + "name": "fault_list_2", + "register": "0x0406", + "ha": { + "name": "Fault List 2", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_2 }}" + } + }, + { + "name": "fault_list_3", + "register": "0x0407", + "ha": { + "name": "Fault List 3", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_3 }}" + } + }, + { + "name": "fault_list_4", + "register": "0x0408", + "ha": { + "name": "Fault List 4", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_4 }}" + } + }, + { + "name": "fault_list_5", + "register": "0x0409", + "ha": { + "name": "Fault List 5", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_5 }}" + } + }, + { + "name": "countdown", + "register": "0x0417", + "ha": { + "name": "Countdown", + "icon": "mdi:counter", + "value_template": "{{ value_json.countdown }}" + } + }, { "name": "pv_1_voltage", "register": "0x0584", @@ -875,7 +944,6 @@ "name": "PV1 Voltage", "icon": "mdi:alpha-v-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_1_voltage }}" } }, @@ -890,7 +958,6 @@ "name": "PV1 Current", "icon": "mdi:alpha-a-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_1_current }}" } }, @@ -905,7 +972,6 @@ "name": "PV1 Power", "icon": "mdi:solar-power", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_1_power }}" } }, @@ -920,7 +986,6 @@ "name": "PV2 Voltage", "icon": "mdi:alpha-v-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_2_voltage }}" } }, @@ -935,7 +1000,6 @@ "name": "PV2 Current", "icon": "mdi:alpha-a-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_2_current }}" } }, @@ -950,7 +1014,6 @@ "name": "PV2 Power", "icon": "mdi:solar-power", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_2_power }}" } }, @@ -969,7 +1032,6 @@ "name": "PV Total Power", "icon": "mdi:solar-power", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_total_power }}" } }, @@ -985,7 +1047,6 @@ "icon": "mdi:lightning-bolt", "state_class": "measurement", "unit_of_measurement": "W", - "entity_category": "diagnostic", "value_template": "{{ value_json.active_power }}" } }, @@ -1000,7 +1061,6 @@ "icon": "mdi:home-lightning-bolt-outline", "state_class": "measurement", "unit_of_measurement": "W", - "entity_category": "diagnostic", "value_template": "{{ value_json.load_power }}" } }, @@ -1016,7 +1076,6 @@ "icon": "mdi:lightning-bolt", "state_class": "measurement", "unit_of_measurement": "W", - "entity_category": "diagnostic", "value_template": "{{ value_json.grid }}" } }, @@ -1027,7 +1086,6 @@ "name": "Insulation resistance", "icon": "mdi:omega", "unit_of_measurement": "Ohms", - "entity_category": "diagnostic", "value_template": "{{ value_json.insulation_resistance }}" }, "refresh": 3600 @@ -1043,7 +1101,6 @@ "icon": "mdi:sine-wave", "state_class": "measurement", "unit_of_measurement": "Hz", - "entity_category": "diagnostic", "value_template": "{{ value_json.ongrid_frequency }}" }, "refresh": 60 @@ -1059,7 +1116,6 @@ "name": "On-grid Voltage", "icon": "mdi:alpha-v-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.ongrid_voltage }}" }, "refresh": 60 @@ -1073,7 +1129,6 @@ "name": "Internal Temperature", "icon": "mdi:temperature-celsius", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.inverter_temp_internal }}" } }, @@ -1086,7 +1141,6 @@ "name": "Heatsink Temperature", "icon": "mdi:temperature-celsius", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.inverter_temp_heatsink }}" } }, @@ -1101,7 +1155,6 @@ "name": "Off-Grid Power", "icon": "mdi:lightning-bolt", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.offgrid_active_power }}" } }, @@ -1116,7 +1169,6 @@ "name": "Off-Grid Frequency", "icon": "mdi:sine-wave", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.offgrid_frequency }}" }, "refresh": 60 @@ -1132,7 +1184,6 @@ "name": "Off-Grid Voltage", "icon": "mdi:alpha-v-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.offgrid_voltage }}" }, "refresh": 60 @@ -1149,7 +1200,6 @@ "name": "Battery Current", "icon": "mdi:alpha-a-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_current }}" } }, @@ -1165,7 +1215,6 @@ "name": "Battery Power", "icon": "mdi:battery-charging", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_power }}" } }, @@ -1180,7 +1229,6 @@ "name": "Solar Generation Today", "icon": "mdi:solar-power-variant", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.today_generation }}" }, "refresh": 60 @@ -1196,7 +1244,6 @@ "name": "Solar Generation Total", "icon": "mdi:solar-power-variant", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.total_generation }}" }, "refresh": 3600 @@ -1212,7 +1259,6 @@ "name": "Consumption Today", "icon": "mdi:home-lightning-bolt-outline", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.today_consumption }}" }, "refresh": 60 @@ -1228,7 +1274,6 @@ "name": "Consumption Total", "icon": "mdi:home-lightning-bolt-outline", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.total_consumption }}" }, "refresh": 3600 @@ -1244,7 +1289,6 @@ "name": "Import Today", "icon": "mdi:transmission-tower-import", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.today_import }}" }, "refresh": 60 @@ -1260,7 +1304,6 @@ "name": "Import Total", "icon": "mdi:transmission-tower-import", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.total_import }}" }, "refresh": 3600 @@ -1276,7 +1319,6 @@ "name": "Export Today", "icon": "mdi:transmission-tower-export", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.today_export }}" }, "refresh": 60 @@ -1292,7 +1334,6 @@ "name": "Export Total", "icon": "mdi:transmission-tower-export", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.total_export }}" }, "refresh": 3600 @@ -1308,7 +1349,6 @@ "name": "Battery Charge Today", "icon": "mdi:battery-plus-variant", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.today_battery_charge }}" }, "refresh": 60 @@ -1324,7 +1364,6 @@ "name": "Battery Charge Total", "icon": "mdi:battery-plus-variant", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.total_battery_charge }}" }, "refresh": 3600 @@ -1340,7 +1379,6 @@ "name": "Battery Discharge Today", "icon": "mdi:battery-minus-variant", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.today_battery_discharge }}" }, "refresh": 60 @@ -1356,7 +1394,6 @@ "name": "Battery Discharge Total", "icon": "mdi:battery-minus-variant", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.total_battery_discharge }}" }, "refresh": 3600 @@ -1372,7 +1409,6 @@ "name": "Battery Voltage", "icon": "mdi:alpha-v-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_voltage }}" } }, @@ -1386,7 +1422,6 @@ "name": "Battery Temperature", "icon": "mdi:temperature-celsius", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_temp }}" } }, @@ -1399,7 +1434,6 @@ "name": "Battery SOC", "icon": "mdi:battery-80", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_soc }}" }, "min": 0, @@ -1414,7 +1448,6 @@ "name": "Battery SOH", "icon": "mdi:battery-heart-variant", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_soh }}" }, "refresh": 3600 @@ -1428,7 +1461,6 @@ "name": "Battery Cycles", "icon": "mdi:battery-sync", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_cycles }}" }, "refresh": 3600 @@ -1613,5 +1645,317 @@ "untested": true, "refresh": 86400 } - ] + ], + "error_codes": { + "1": "GridOVP", + "2": "GridUVP", + "3": "GridOFP", + "4": "GridUFP", + "5": "GFCI", + "6": "OVRT", + "7": "LVRT", + "8": "IslandFault", + "9": "GridOVPInstant1", + "10": "GridOVPInstant2", + "11": "VGridLineFault", + "12": "InvOVP", + "17": "HwADFaultIGrid", + "18": "HwADFaultDCI(AC)", + "19": "HwADFaultVGrid(DC)", + "20": "HwADFaultVGrid(AC)", + "21": "GFCIDeviceFault(DC)", + "22": "GFCIDeviceFault(AC)", + "23": "HwADFaultDCV", + "24": "HwADFaultIdc", + "25": "HwADFaultDCI(DC)", + "26": "HwADFaultIdcBranch", + "29": "ConsistentFault_GFCI", + "30": "ConsistentFault_Vgrid", + "31": "ConsistentFault_DCI", + "33": "SpiCommFault(DC)", + "34": "SpiCommFault(AC)", + "35": "SChip_Fault", + "36": "MChip_Fault", + "37": "HwAuxPowerFault", + "38": "InverterSoftStartFail", + "41": "RelayFail", + "42": "IsoFault", + "43": "PEConnectFault", + "44": "PvConfigError", + "45": "CTDisconnect", + "46": "ReversalConnection", + "47": "ParallelFault", + "48": "SNTypeFault", + "49": "TempFault_Bat", + "50": "TempFault_HeatSink1", + "51": "TempFault_HeatSink2", + "52": "TempFault_HeatSink3", + "53": "TempFault_HeatSink4", + "54": "TempFault_HeatSink5", + "55": "TempFault_HeatSink6", + "57": "TempFault_Env1", + "58": "TempFault_Env2", + "59": "TempFault_Inv1", + "60": "TempFault_Inv2", + "61": "TempFault_Inv3", + "65": "VbusRmsUnbalance", + "66": "VbusInstantUnbalance", + "67": "BusUVP", + "68": "BusZVP", + "69": "PVOVP", + "70": "BatOVP", + "71": "LLCBusOVP", + "72": "SwBusRmsOVP", + "73": "SwBusInstantOVP", + "74": "FlyingCapOVP", + "81": "SwBatOCP", + "82": "DciOCP", + "83": "SwOCPInstant", + "84": "SwBuckBoostOCP", + "85": "SwAcRmsOCP", + "86": "SwPvOCPInstant", + "87": "IpvUnbalance", + "88": "IacUnbalance", + "89": "SwPvOCP", + "97": "HwLLCBusOVP", + "98": "HwBusOVP", + "99": "HwBuckBoostOCP", + "100": "HwBatOCP", + "102": "HwPVOCP", + "103": "HwACOCP", + "110": "Overload1", + "111": "Overload2", + "112": "Overload3", + "113": "OverTempDerating", + "114": "FreqDerating", + "115": "FreqLoading", + "116": "VoltDerating", + "117": "VoltLoading", + "124": "BatLowVoltageAlarm", + "125": "BatLowVoltageShut", + "129": "unrecoverHwAcOCP", + "130": "unrecoverBusOVP", + "131": "unrecoverHwBusOVP", + "132": "unrecoverIpvUnbalance", + "133": "unrecoverEPSBatOCP", + "134": "unrecoverAcOCPInstant", + "135": "unrecoverIacUnbalance", + "137": "unrecoverPvConfigError", + "138": "unrecoverPVOCPInstant", + "139": "unrecoverHwPVOCP", + "140": "unrecoverRelayFail", + "141": "unrecoverVbusUnbalance", + "142": "unrecoverSpdFail(DC)", + "143": "unrecoverSpdFail(AC)", + "145": "USBFault", + "146": "WifiFault", + "147": "BluetoothFault", + "148": "RTCFault", + "149": "CommEEPROMFault", + "150": "FlashFault", + "152": "SafetyVerFault", + "153": "SciCommLose(DC)", + "154": "SciCommLose(AC)", + "155": "SciCommLose(Fuse)", + "156": "SoftVerError", + "157": "BMS1CommunicatonFault", + "158": "BMS2CommunicatonFault", + "159": "BMS3CommunicatonFault", + "160": "BMS4CommunicatonFault", + "161": "ForceShutdown", + "162": "RemoteShutdown", + "163": "Drms0Shutdown", + "165": "RemoteDerating", + "166": "LogicInterfaceDerating", + "167": "AlarmAntiRefluxing", + "169": "FanFault1", + "170": "FanFault2", + "171": "FanFault3", + "172": "FanFault4", + "173": "FanFault5", + "174": "FanFault6", + "176": "MeterCommLose", + "177": "BMS OVP", + "178": "BMS UVP", + "179": "BMS OTP", + "180": "BMS UTP", + "181": "BMS OCP", + "182": "BMS Short", + "193": "StringFuse_Fault0", + "194": "StringFuse_Fault1", + "195": "StringFuse_Fault2", + "196": "StringFuse_Fault3", + "197": "StringFuse_Fault4", + "198": "StringFuse_Fault5", + "199": "StringFuse_Fault6", + "200": "StringFuse_Fault7", + "201": "StringFuse_Fault8", + "202": "StringFuse_Fault9", + "203": "StringFuse_Fault10", + "204": "StringFuse_Fault11", + "205": "StringFuse_Fault12", + "206": "StringFuse_Fault13", + "207": "StringFuse_Fault14", + "208": "StringFuse_Fault15", + "209": "StringFuse_Fault16", + "210": "StringFuse_Fault17", + "211": "StringFuse_Fault18", + "212": "StringFuse_Fault19", + "213": "StringFuse_Fault20", + "214": "StringFuse_Fault21", + "215": "StringFuse_Fault22", + "216": "StringFuse_Fault23", + "217": "StringFuse_Fault24", + "218": "StringFuse_Fault25", + "219": "StringFuse_Fault26", + "220": "StringFuse_Fault27", + "221": "StringFuse_Fault28", + "222": "StringFuse_Fault29", + "223": "StringFuse_Fault30", + "224": "StringFuse_Fault31", + "225": "InputFuse_Fault0", + "226": "InputFuse_Fault1", + "227": "InputFuse_Fault2", + "228": "InputFuse_Fault3", + "229": "InputFuse_Fault4", + "230": "InputFuse_Fault5", + "231": "InputFuse_Fault6", + "232": "InputFuse_Fault7", + "233": "InputFuse_Fault8", + "234": "InputFuse_Fault9", + "235": "InputFuse_Fault10", + "236": "InputFuse_Fault11", + "237": "InputFuse_Fault12", + "238": "InputFuse_Fault13", + "239": "InputFuse_Fault14", + "240": "InputFuse_Fault15", + "241": "CombinerOverVoltageGroup1", + "242": "CombinerOverVoltageGroup2", + "243": "CombinerOverVoltageGroup3", + "244": "CombinerOverVoltageGroup4", + "245": "CombinerOverVoltageGroup5", + "246": "CombinerOverVoltageGroup6", + "247": "CombinerOverVoltageGroup7", + "248": "CombinerOverVoltageGroup8", + "249": "CombinerOverVoltageGroup9", + "250": "CombinerOverVoltageGroup10", + "251": "CombinerOverVoltageGroup11", + "252": "CombinerOverVoltageGroup12", + "253": "CombinerOverVoltageGroup13", + "254": "CombinerOverVoltageGroup14", + "255": "CombinerOverVoltageGroup15", + "256": "CombinerOverVoltageGroup16", + "257": "CombinerUnderVoltageGroup1", + "258": "CombinerUnderVoltageGroup2", + "259": "CombinerUnderVoltageGroup3", + "260": "CombinerUnderVoltageGroup4", + "261": "CombinerUnderVoltageGroup5", + "262": "CombinerUnderVoltageGroup6", + "263": "CombinerUnderVoltageGroup7", + "264": "CombinerUnderVoltageGroup8", + "265": "CombinerUnderVoltageGroup9", + "266": "CombinerUnderVoltageGroup10", + "267": "CombinerUnderVoltageGroup11", + "268": "CombinerUnderVoltageGroup12", + "269": "CombinerUnderVoltageGroup13", + "270": "CombinerUnderVoltageGroup14", + "271": "CombinerUnderVoltageGroup15", + "272": "CombinerUnderVoltageGroup16", + "273": "CombinerOverCurrent1", + "274": "CombinerOverCurrent2", + "275": "CombinerOverCurrent3", + "276": "CombinerOverCurrent4", + "277": "CombinerOverCurrent5", + "278": "CombinerOverCurrent6", + "279": "CombinerOverCurrent7", + "280": "CombinerOverCurrent8", + "281": "CombinerOverCurrent9", + "282": "CombinerOverCurrent10", + "283": "CombinerOverCurrent11", + "284": "CombinerOverCurrent12", + "285": "CombinerOverCurrent13", + "286": "CombinerOverCurrent14", + "287": "CombinerOverCurrent15", + "288": "CombinerOverCurrent16", + "289": "CombinerOverCurrent17", + "290": "CombinerOverCurrent18", + "291": "CombinerOverCurrent19", + "292": "CombinerOverCurrent20", + "293": "CombinerOverCurrent21", + "294": "CombinerOverCurrent22", + "295": "CombinerOverCurrent23", + "296": "CombinerOverCurrent24", + "297": "CombinerOverCurrent25", + "298": "CombinerOverCurrent26", + "299": "CombinerOverCurrent27", + "300": "CombinerOverCurrent28", + "301": "CombinerOverCurrent29", + "302": "CombinerOverCurrent30", + "303": "CombinerOverCurrent31", + "304": "CombinerOverCurrent32", + "337": "CombinerRefluxFault1", + "338": "CombinerRefluxFault2", + "339": "CombinerRefluxFault3", + "340": "CombinerRefluxFault4", + "341": "CombinerRefluxFault5", + "342": "CombinerRefluxFault6", + "343": "CombinerRefluxFault7", + "344": "CombinerRefluxFault8", + "345": "CombinerRefluxFault9", + "346": "CombinerRefluxFault10", + "347": "CombinerRefluxFault11", + "348": "CombinerRefluxFault12", + "349": "CombinerRefluxFault13", + "350": "CombinerRefluxFault14", + "351": "CombinerRefluxFault15", + "352": "CombinerRefluxFault16", + "353": "CombinerRefluxFault17", + "354": "CombinerRefluxFault18", + "355": "CombinerRefluxFault19", + "356": "CombinerRefluxFault20", + "357": "CombinerRefluxFault21", + "358": "CombinerRefluxFault22", + "359": "CombinerRefluxFault23", + "360": "CombinerRefluxFault24", + "361": "CombinerRefluxFault25", + "362": "CombinerRefluxFault26", + "363": "CombinerRefluxFault27", + "364": "CombinerRefluxFault28", + "365": "CombinerRefluxFault29", + "366": "CombinerRefluxFault30", + "367": "CombinerRefluxFault31", + "368": "CombinerRefluxFault32", + "401": "AFCI0", + "402": "AFCI1", + "403": "AFCI2", + "404": "AFCI3", + "405": "AFCI4", + "406": "AFCI5", + "407": "AFCI6", + "408": "AFCI7", + "409": "AFCI8", + "410": "AFCI9", + "411": "AFCI10", + "412": "AFCI11", + "413": "AFCI12", + "414": "AFCI13", + "415": "AFCI14", + "416": "AFCI15", + "417": "AFCI16", + "418": "AFCI17", + "419": "AFCI18", + "420": "AFCI19", + "421": "AFCI20", + "422": "AFCI21", + "423": "AFCI22", + "424": "AFCI23", + "425": "AFCI24", + "426": "AFCI25", + "427": "AFCI26", + "428": "AFCI27", + "429": "AFCI28", + "430": "AFCI29", + "431": "AFCI30", + "432": "AFCI31" + } } \ No newline at end of file diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index 31302d1..b81f961 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -250,6 +250,18 @@ def setup_instrument(self): self.instrument.close_port_after_each_call = False self.instrument.clear_buffers_before_each_transaction = True + def aggregate_datetime_bitmap(self, register): + additional_registers = register.get('aggregate_datetime_bitmap', {}) + ts = self.read_event_timestamp( + additional_registers.get("year_month",""), + additional_registers.get("day_hour",""), + additional_registers.get("minute_second","") + ) + if ts is None: + return "No valid timestamp available" + else: + return ts.strftime("%Y-%m-%d %H:%M:%S") + def combine_aggregate_registers(self, register): """ Combine registers from the 'aggregate' field using the arithmetic function in 'agg_function' """ raw_value = 0 @@ -278,13 +290,19 @@ def update_state(self): logging.debug('Reading %s', register['name']) if 'aggregate' in register: raw_value = self.combine_aggregate_registers(register) - else: + if register.get('read_type') == 'static': + raw_value = register['value'] + if register.get('register', False): raw_value = self.read_register( int(register['register'], 16), register.get('read_type', 'register'), register.get('signed', False), register.get('registers', 1) ) + + if 'aggregate_datetime_bitmap' in register: + continue + #raw_value = self.aggregate_datetime_bitmap(register) if raw_value is None: logging.error(f"Value for {register['name']}: is none") continue @@ -293,12 +311,14 @@ def update_state(self): # Inverter will return maximum 16-bit integer value when data not available (eg. grid usage when grid down) if value == 65535: value = 0 - if 'min' in register and value < register['min']: - logging.error( - f"Value for {register['name']}: {str(value)} is lower than min allowed value: {register['min']}") - if 'max' in register and value > register['max']: - logging.error( - f"Value for {register['name']}: {str(raw_value)} is greater than max allowed value: {register['max']}") + if 'min' in register: + if int(value) < register.get('min'): + logging.error( + f"Value for {register['name']}: {str(value)} is lower than min allowed value: {register['min']}") + if 'max' in register: + if int(value) > register.get('max', 0): + logging.error( + f"Value for {register['name']}: {str(raw_value)} is greater than max allowed value: {register['max']}") logging.debug(f"Read {register['name']} {value}") if not self.raw_data.get(register.get('name')) == raw_value: @@ -868,6 +888,55 @@ def get_register(self, register_name): logging.error( f"Register {register_name} not found in configuration") return register + + def read_event_timestamp(self, + reg_yM, + reg_dH, + reg_mS): + """ + Reads inverter history timestamp registers and returns a datetime object. + + Register format: + reg_yM (0x1481): + High byte = year (last two digits) + Low byte = month + + reg_dH (0x1482): + High byte = day + Low byte = hour + + reg_mS (0x1483): + High byte = minute + Low byte = second + """ + logging.info(f"Reading event timestamp from registers: yM={reg_yM}, dH={reg_dH}, mS={reg_mS}") + if reg_yM == "" or reg_dH == "" or reg_mS == "": return None + + # Read raw register values + yM = self.instrument.read_register(int(reg_yM, 16), 0, 3) + dH = self.instrument.read_register(int(reg_dH, 16), 0, 3) + mS = self.instrument.read_register(int(reg_mS, 16), 0, 3) + + logging.info(f"Read event timestamp registers: yM={yM}, dH={dH}, mS={mS}") + + # Extract bytes + year_2digit = (yM >> 8) & 0xFF + month = yM & 0xFF + + day = (dH >> 8) & 0xFF + hour = dH & 0xFF + + minute = (mS >> 8) & 0xFF + second = mS & 0xFF + + # Convert 2‑digit year to full year (assume 2000–2099) + year_full = 2000 + year_2digit + + # Build datetime + timestamp = datetime.datetime(year_full, month, day, hour, minute, second) + + return timestamp + def translate_from_raw_value(self, register, raw_value): """ Translate raw value to a normalized value using the function and factor """ @@ -878,6 +947,8 @@ def translate_from_raw_value(self, register, raw_value): return raw_value / register['factor'] elif register['function'] == 'mode': return register['modes'].get(str(raw_value), raw_value) + elif register['function'] == 'history_event_map': + return self.config.get('error_codes', {}).get(str(raw_value), raw_value) elif register['function'] == 'bit_field': length = len(register['fields']) fields = [] From 8c3bbed2fa420bbed1d49188f9c1244dd09b7f96 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Wed, 31 Dec 2025 18:12:43 +0000 Subject: [PATCH 03/20] Initial attempt at write support --- SOFAR-HYD-ES-AND-ME3000-SP.json | 1407 ++++++++++++++++--------------- sofar2mqtt-v2.py | 44 +- 2 files changed, 746 insertions(+), 705 deletions(-) diff --git a/SOFAR-HYD-ES-AND-ME3000-SP.json b/SOFAR-HYD-ES-AND-ME3000-SP.json index c055a4c..b45a483 100644 --- a/SOFAR-HYD-ES-AND-ME3000-SP.json +++ b/SOFAR-HYD-ES-AND-ME3000-SP.json @@ -1,705 +1,706 @@ { - "registers": [ - { - "name": "serial_number", - "read_type": "static", - "value": "ME3000SP", - "ha": { - "name": "Serial Number", - "icon": "mdi:identifier", - "value_template": "{{ value_json.serial_number }}" - }, - "refresh": 86400 - }, - { - "name": "operating_mode", - "desc": "Operating mode control - determines charge/discharge behavior", - "type": "U16", - "function": "mode", - "modes": { - "0x0100": "Standby", - "0x0101": "Discharge", - "0x0102": "Charge", - "0x0103": "Auto" - }, - "write": true, - "mode_params": { - "0x0100": 21845, - "0x0101": 0, - "0x0102": 0, - "0x0103": 21845 - }, - "ha": { - "command_topic": "sofar/rw/operating_mode", - "entity_category": "config", - "name": "Operating Mode", - "options": [ - "Standby", - "Discharge", - "Charge", - "Auto" - ], - "value_template": "{{ value_json.operating_mode }}", - "icon": "mdi:auto-mode", - "control": "select" - } - }, - { - "name": "charge_discharge_power", - "desc": "Charge/Discharge power (0-3000W) - used with Charge/Discharge modes", - "type": "U16", - "min": 0, - "max": 3000, - "write": true, - "passive": true, - "linked_register": "operating_mode", - "ha": { - "command_topic": "sofar/rw/charge_discharge_power", - "entity_category": "config", - "name": "Charge/Discharge Power", - "min": 0, - "max": 3000, - "step": 100, - "initial": 0, - "mode": "slider", - "value_template": "{{ value_json.charge_discharge_power }}", - "icon": "mdi:battery-charging", - "unit_of_measurement": "W", - "control": "number" - } - }, - { - "name": "running_state", - "register": "0x200", - "function": "mode", - "modes": { - "0": "Standby", - "2": "Charging", - "4": "Discharging", - "6": "Fault" - }, - "ha": { - "name": "Running State", - "icon": "mdi:power-standby", - "value_template": "{{ value_json.running_state }}" - } - }, - { - "name": "model", - "value": "ME-3000SP", - "read_type": "static", - "ha": { - "name": "Model", - "icon": "mdi:tag-text", - "value_template": "{{ value_json.model }}" - }, - "refresh": 86400 - }, - { - "name": "hw_version", - "value": "TBD", - "read_type": "static", - "ha": { - "name": "HW Version", - "icon": "mdi:numeric", - "value_template": "{{ value_json.hw_version }}" - }, - "refresh": 86400 - }, - { - "name": "sw_version_com", - "read_type": "static", - "value": "TBD", - "ha": { - "name": "Software Version", - "icon": "mdi:numeric", - "value_template": "{{ value_json.sw_version_com }}" - }, - "refresh": 86400 - }, - { - "name": "fault_list_1", - "register": "0x0201", - "ha": { - "name": "Fault List 1", - "icon": "mdi:alert", - "value_template": "{{ value_json.fault_list_1 }}" - } - }, - { - "name": "fault_list_2", - "register": "0x0202", - "ha": { - "name": "Fault List 2", - "icon": "mdi:alert", - "value_template": "{{ value_json.fault_list_2 }}" - } - }, - { - "name": "fault_list_3", - "register": "0x0203", - "ha": { - "name": "Fault List 3", - "icon": "mdi:alert", - "value_template": "{{ value_json.fault_list_3 }}" - } - }, - { - "name": "fault_list_4", - "register": "0x0204", - "ha": { - "name": "Fault List 4", - "icon": "mdi:alert", - "value_template": "{{ value_json.fault_list_4 }}" - } - }, - { - "name": "fault_list_5", - "register": "0x0205", - "ha": { - "name": "Fault List 5", - "icon": "mdi:alert", - "value_template": "{{ value_json.fault_list_5 }}" - } - }, - { - "name": "grid_voltage", - "register": "0x0206", - "function": "divide", - "factor": 10, - "ha": { - "device_class": "voltage", - "unit_of_measurement": "V", - "name": "Grid Voltage", - "icon": "mdi:alpha-v-box", - "state_class": "measurement", - "value_template": "{{ value_json.grid_voltage }}" - } - }, - { - "name": "grid_current", - "register": "0x0207", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "current", - "unit_of_measurement": "A", - "name": "Grid Current", - "icon": "mdi:alpha-a-box", - "state_class": "measurement", - "value_template": "{{ value_json.grid_current }}" - } - }, - { - "name": "battery_power", - "register": "0x20d", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "power", - "unit_of_measurement": "W", - "name": "Battery Power", - "icon": "mdi:battery-charging", - "state_class": "measurement", - "value_template": "{{ value_json.battery_power }}" - } - }, - { - "name": "grid_freq", - "register": "0x20c", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "frequency", - "unit_of_measurement": "Hz", - "name": "Grid Frequency", - "icon": "mdi:sine-wave", - "state_class": "measurement", - "value_template": "{{ value_json.grid_freq }}" - } - }, - { - "name": "batterySOC", - "register": "0x210", - "ha": { - "device_class": "battery", - "unit_of_measurement": "%", - "name": "Battery SOC", - "icon": "mdi:battery-80", - "state_class": "measurement", - "value_template": "{{ value_json.batterySOC }}" - } - }, - { - "name": "battery_voltage", - "register": "0x20e", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "voltage", - "unit_of_measurement": "V", - "name": "Battery Voltage", - "icon": "mdi:alpha-v-box", - "state_class": "measurement", - "value_template": "{{ value_json.battery_voltage }}" - } - }, - { - "name": "battery_current", - "register": "0x020F", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "current", - "unit_of_measurement": "A", - "name": "Battery Current", - "icon": "mdi:alpha-a-box", - "state_class": "measurement", - "value_template": "{{ value_json.battery_current }}" - } - }, - { - "name": "battery_temp", - "register": "0x211", - "ha": { - "device_class": "temperature", - "unit_of_measurement": "°C", - "name": "Battery Temperature", - "icon": "mdi:temperature-celsius", - "state_class": "measurement", - "value_template": "{{ value_json.battery_temp }}" - } - }, - { - "name": "grid_power", - "register": "0x212", - "function": "multiply", - "factor": 10, - "ha": { - "device_class": "power", - "unit_of_measurement": "W", - "name": "Grid Power", - "icon": "mdi:lightning-bolt", - "state_class": "measurement", - "value_template": "{{ value_json.grid_power }}" - } - }, - { - "name": "consumption", - "register": "0x213", - "function": "multiply", - "factor": 10, - "ha": { - "device_class": "power", - "unit_of_measurement": "W", - "name": "Load Power", - "icon": "mdi:home-lightning-bolt-outline", - "state_class": "measurement", - "value_template": "{{ value_json.consumption }}" - } - }, - { - "name": "inverter_power", - "register": "0x0214", - "function": "multiply", - "factor": 10, - "ha": { - "device_class": "power", - "unit_of_measurement": "W", - "name": "Inverter Power", - "icon": "mdi:lightning-bolt", - "state_class": "measurement", - "value_template": "{{ value_json.inverter_power }}" - } - }, - { - "name": "solarPV", - "register": "0x215", - "function": "multiply", - "factor": 10, - "ha": { - "device_class": "power", - "unit_of_measurement": "W", - "name": "Solar PV Power", - "icon": "mdi:solar-power", - "state_class": "measurement", - "value_template": "{{ value_json.solarPV }}" - } - }, - { - "name": "eps_output_voltage", - "register": "0x0216", - "function": "divide", - "factor": 10, - "ha": { - "device_class": "voltage", - "unit_of_measurement": "V", - "name": "EPS Output Voltage", - "icon": "mdi:alpha-v-box", - "state_class": "measurement", - "value_template": "{{ value_json.eps_output_voltage }}" - } - }, - { - "name": "eps_output_power", - "register": "0x0217", - "function": "multiply", - "factor": 10, - "ha": { - "device_class": "power", - "unit_of_measurement": "W", - "name": "EPS Output Power", - "icon": "mdi:lightning-bolt", - "state_class": "measurement", - "value_template": "{{ value_json.eps_output_power }}" - } - }, - { - "name": "today_generation", - "register": "0x218", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Solar Generation Today", - "icon": "mdi:solar-power-variant", - "state_class": "total_increasing", - "value_template": "{{ value_json.today_generation }}" - } - }, - { - "name": "today_exported", - "register": "0x219", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Export Today", - "icon": "mdi:transmission-tower-export", - "state_class": "total_increasing", - "value_template": "{{ value_json.today_exported }}" - } - }, - { - "name": "today_purchase", - "register": "0x21a", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Import Today", - "icon": "mdi:transmission-tower-import", - "state_class": "total_increasing", - "value_template": "{{ value_json.today_purchase }}" - } - }, - { - "name": "today_consumption", - "register": "0x21b", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Consumption Today", - "icon": "mdi:home-lightning-bolt-outline", - "state_class": "total_increasing", - "value_template": "{{ value_json.today_consumption }}" - } - }, - { - "name": "total_generation_hi", - "register": "0x021C", - "ha": { - "name": "Total Generation Hi", - "icon": "mdi:counter", - "value_template": "{{ value_json.total_generation_hi }}" - } - }, - { - "name": "total_generation_lo", - "register": "0x021D", - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Total Generation", - "icon": "mdi:solar-power-variant", - "state_class": "total_increasing", - "value_template": "{{ value_json.total_generation_lo }}" - } - }, - { - "name": "total_export_hi", - "register": "0x021E", - "ha": { - "name": "Total Export Hi", - "icon": "mdi:counter", - "value_template": "{{ value_json.total_export_hi }}" - } - }, - { - "name": "total_export_lo", - "register": "0x021F", - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Total Export", - "icon": "mdi:transmission-tower-export", - "state_class": "total_increasing", - "value_template": "{{ value_json.total_export_lo }}" - } - }, - { - "name": "total_import_hi", - "register": "0x0220", - "ha": { - "name": "Total Import Hi", - "icon": "mdi:counter", - "value_template": "{{ value_json.total_import_hi }}" - } - }, - { - "name": "total_import_lo", - "register": "0x0221", - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Total Import", - "icon": "mdi:transmission-tower-import", - "state_class": "total_increasing", - "value_template": "{{ value_json.total_import_lo }}" - } - }, - { - "name": "total_consumption_hi", - "register": "0x0222", - "ha": { - "name": "Total Consumption Hi", - "icon": "mdi:counter", - "value_template": "{{ value_json.total_consumption_hi }}" - } - }, - { - "name": "total_consumption_lo", - "register": "0x0223", - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Total Consumption", - "icon": "mdi:home-lightning-bolt-outline", - "state_class": "total_increasing", - "value_template": "{{ value_json.total_consumption_lo }}" - } - }, - { - "name": "battery_charge_today", - "register": "0x0224", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Battery Charge Today", - "icon": "mdi:battery-charging-80", - "state_class": "total_increasing", - "value_template": "{{ value_json.battery_charge_today }}" - } - }, - { - "name": "battery_discharge_today", - "register": "0x0225", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Battery Discharge Today", - "icon": "mdi:battery-minus", - "state_class": "total_increasing", - "value_template": "{{ value_json.battery_discharge_today }}" - } - }, - { - "name": "battery_total_charge_hi", - "register": "0x0226", - "ha": { - "name": "Battery Total Charge Hi", - "icon": "mdi:counter", - "value_template": "{{ value_json.battery_total_charge_hi }}" - } - }, - { - "name": "battery_total_charge_lo", - "register": "0x0227", - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Battery Total Charge", - "icon": "mdi:battery-charging-80", - "state_class": "total_increasing", - "value_template": "{{ value_json.battery_total_charge_lo }}" - } - }, - { - "name": "battery_total_discharge_hi", - "register": "0x0228", - "ha": { - "name": "Battery Total Discharge Hi", - "icon": "mdi:counter", - "value_template": "{{ value_json.battery_total_discharge_hi }}" - } - }, - { - "name": "battery_total_discharge_lo", - "register": "0x0229", - "ha": { - "device_class": "energy", - "unit_of_measurement": "kWh", - "name": "Battery Total Discharge", - "icon": "mdi:battery-minus", - "state_class": "total_increasing", - "value_template": "{{ value_json.battery_total_discharge_lo }}" - } - }, - { - "name": "countdown_time", - "register": "0x022A", - "ha": { - "name": "Countdown Time", - "unit_of_measurement": "s", - "icon": "mdi:timer", - "value_template": "{{ value_json.countdown_time }}" - } - }, - { - "name": "inverter_alarm_info", - "register": "0x022B", - "ha": { - "name": "Inverter Alarm Information", - "icon": "mdi:alert-circle", - "value_template": "{{ value_json.inverter_alarm_info }}" - } - }, - { - "name": "battery_cycles", - "register": "0x22c", - "ha": { - "device_class": "battery", - "unit_of_measurement": "cycles", - "name": "Battery Cycles", - "icon": "mdi:battery-sync", - "state_class": "total_increasing", - "value_template": "{{ value_json.battery_cycles }}" - } - }, - { - "name": "inverter_bus_voltage", - "register": "0x022D", - "function": "divide", - "factor": 10, - "ha": { - "device_class": "voltage", - "unit_of_measurement": "V", - "name": "Inverter Bus Voltage", - "icon": "mdi:alpha-v-box", - "state_class": "measurement", - "value_template": "{{ value_json.inverter_bus_voltage }}" - } - }, - { - "name": "llc_bus_voltage", - "register": "0x022E", - "function": "divide", - "factor": 10, - "ha": { - "device_class": "voltage", - "unit_of_measurement": "V", - "name": "LLC Bus Voltage", - "icon": "mdi:alpha-v-box", - "state_class": "measurement", - "value_template": "{{ value_json.llc_bus_voltage }}" - } - }, - { - "name": "buck_current", - "register": "0x022F", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "current", - "unit_of_measurement": "A", - "name": "Buck Current", - "icon": "mdi:alpha-a-box", - "state_class": "measurement", - "value_template": "{{ value_json.buck_current }}" - } - }, - { - "name": "battery_health_soh", - "register": "0x0237", - "ha": { - "device_class": "battery", - "unit_of_measurement": "%", - "name": "Battery Health (SOH)", - "icon": "mdi:battery-heart", - "state_class": "measurement", - "value_template": "{{ value_json.battery_health_soh }}" - } - }, - { - "name": "inverter_temp", - "register": "0x238", - "ha": { - "device_class": "temperature", - "unit_of_measurement": "°C", - "name": "Inverter Temperature", - "icon": "mdi:temperature-celsius", - "state_class": "measurement", - "value_template": "{{ value_json.inverter_temp }}" - } - }, - { - "name": "inverterHS_temp", - "register": "0x239", - "ha": { - "device_class": "temperature", - "unit_of_measurement": "°C", - "name": "Heatsink Temperature", - "icon": "mdi:temperature-celsius", - "state_class": "measurement", - "value_template": "{{ value_json.inverterHS_temp }}" - } - }, - { - "name": "solarPVAmps", - "register": "0x236", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "current", - "unit_of_measurement": "A", - "name": "Solar PV Current", - "icon": "mdi:alpha-a-box", - "state_class": "measurement", - "value_template": "{{ value_json.solarPVAmps }}" - } - }, - { - "name": "battery_current", - "register": "0x207", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "current", - "unit_of_measurement": "A", - "name": "Battery Current", - "icon": "mdi:alpha-a-box", - "state_class": "measurement", - "value_template": "{{ value_json.battery_current }}" - } - } - ] + "registers": [ + { + "name": "serial_number", + "read_type": "static", + "value": "ME3000SP", + "ha": { + "name": "Serial Number", + "icon": "mdi:identifier", + "value_template": "{{ value_json.serial_number }}" + }, + "refresh": 86400 + }, + { + "name": "working_mode", + "register": "0x1200", + "desc": "Working mode", + "type": "U16", + "function": "mode", + "modes": { + "0": "Auto", + "1": "Time of use", + "2": "Timing mode", + "3": "Passive mode" + }, + "write": true, + "notify_on_change": true, + "ha": { + "command_topic": "sofar/rw/working_mode", + "entity_category": "config", + "name": "Mode", + "options": [ + "Auto", + "Time of use", + "Timing mode", + "Passive mode" + ], + "value_template": "{{ value_json.working_mode }}", + "icon": "mdi:auto-mode", + "control": "select" + } + }, + { + "name": "charge_discharge_power", + "desc": "Charge/Discharge power (-3000 to 3000W) - used with Charge/Discharge modes", + "type": "U16", + "min": -3000, + "max": 3000, + "write": true, + "write_functioncode": "42", + "write_addresses": { + "standby": "0x0100", + "discharge": "0x0101", + "charge": "0x0102", + "auto": "0x0103" + }, + "ha": { + "command_topic": "sofar/rw/charge_discharge_power", + "entity_category": "config", + "name": "Charge/Discharge Power", + "min": 0, + "max": 3000, + "step": 100, + "initial": 0, + "mode": "slider", + "value_template": "{{ value_json.charge_discharge_power }}", + "icon": "mdi:battery-charging", + "unit_of_measurement": "W", + "control": "number" + } + }, + { + "name": "running_state", + "register": "0x200", + "function": "mode", + "modes": { + "0": "Standby", + "2": "Charging", + "4": "Discharging", + "6": "Fault" + }, + "ha": { + "name": "Running State", + "icon": "mdi:power-standby", + "value_template": "{{ value_json.running_state }}" + } + }, + { + "name": "model", + "value": "ME-3000SP", + "read_type": "static", + "ha": { + "name": "Model", + "icon": "mdi:tag-text", + "value_template": "{{ value_json.model }}" + }, + "refresh": 86400 + }, + { + "name": "hw_version", + "value": "TBD", + "read_type": "static", + "ha": { + "name": "HW Version", + "icon": "mdi:numeric", + "value_template": "{{ value_json.hw_version }}" + }, + "refresh": 86400 + }, + { + "name": "sw_version_com", + "read_type": "static", + "value": "TBD", + "ha": { + "name": "Software Version", + "icon": "mdi:numeric", + "value_template": "{{ value_json.sw_version_com }}" + }, + "refresh": 86400 + }, + { + "name": "fault_list_1", + "register": "0x0201", + "ha": { + "name": "Fault List 1", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_1 }}" + } + }, + { + "name": "fault_list_2", + "register": "0x0202", + "ha": { + "name": "Fault List 2", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_2 }}" + } + }, + { + "name": "fault_list_3", + "register": "0x0203", + "ha": { + "name": "Fault List 3", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_3 }}" + } + }, + { + "name": "fault_list_4", + "register": "0x0204", + "ha": { + "name": "Fault List 4", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_4 }}" + } + }, + { + "name": "fault_list_5", + "register": "0x0205", + "ha": { + "name": "Fault List 5", + "icon": "mdi:alert", + "value_template": "{{ value_json.fault_list_5 }}" + } + }, + { + "name": "grid_voltage", + "register": "0x0206", + "function": "divide", + "factor": 10, + "ha": { + "device_class": "voltage", + "unit_of_measurement": "V", + "name": "Grid Voltage", + "icon": "mdi:alpha-v-box", + "state_class": "measurement", + "value_template": "{{ value_json.grid_voltage }}" + } + }, + { + "name": "grid_current", + "register": "0x0207", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "current", + "unit_of_measurement": "A", + "name": "Grid Current", + "icon": "mdi:alpha-a-box", + "state_class": "measurement", + "value_template": "{{ value_json.grid_current }}" + } + }, + { + "name": "battery_power", + "register": "0x20d", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "Battery Power", + "icon": "mdi:battery-charging", + "state_class": "measurement", + "value_template": "{{ value_json.battery_power }}" + } + }, + { + "name": "grid_freq", + "register": "0x20c", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "frequency", + "unit_of_measurement": "Hz", + "name": "Grid Frequency", + "icon": "mdi:sine-wave", + "state_class": "measurement", + "value_template": "{{ value_json.grid_freq }}" + } + }, + { + "name": "batterySOC", + "register": "0x210", + "ha": { + "device_class": "battery", + "unit_of_measurement": "%", + "name": "Battery SOC", + "icon": "mdi:battery-80", + "state_class": "measurement", + "value_template": "{{ value_json.batterySOC }}" + } + }, + { + "name": "battery_voltage", + "register": "0x20e", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "voltage", + "unit_of_measurement": "V", + "name": "Battery Voltage", + "icon": "mdi:alpha-v-box", + "state_class": "measurement", + "value_template": "{{ value_json.battery_voltage }}" + } + }, + { + "name": "battery_current", + "register": "0x020F", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "current", + "unit_of_measurement": "A", + "name": "Battery Current", + "icon": "mdi:alpha-a-box", + "state_class": "measurement", + "value_template": "{{ value_json.battery_current }}" + } + }, + { + "name": "battery_temp", + "register": "0x211", + "ha": { + "device_class": "temperature", + "unit_of_measurement": "°C", + "name": "Battery Temperature", + "icon": "mdi:temperature-celsius", + "state_class": "measurement", + "value_template": "{{ value_json.battery_temp }}" + } + }, + { + "name": "grid_power", + "register": "0x212", + "function": "multiply", + "factor": 10, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "Grid Power", + "icon": "mdi:lightning-bolt", + "state_class": "measurement", + "value_template": "{{ value_json.grid_power }}" + } + }, + { + "name": "consumption", + "register": "0x213", + "function": "multiply", + "factor": 10, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "Load Power", + "icon": "mdi:home-lightning-bolt-outline", + "state_class": "measurement", + "value_template": "{{ value_json.consumption }}" + } + }, + { + "name": "inverter_power", + "register": "0x0214", + "function": "multiply", + "factor": 10, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "Inverter Power", + "icon": "mdi:lightning-bolt", + "state_class": "measurement", + "value_template": "{{ value_json.inverter_power }}" + } + }, + { + "name": "solarPV", + "register": "0x215", + "function": "multiply", + "factor": 10, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "Solar PV Power", + "icon": "mdi:solar-power", + "state_class": "measurement", + "value_template": "{{ value_json.solarPV }}" + } + }, + { + "name": "eps_output_voltage", + "register": "0x0216", + "function": "divide", + "factor": 10, + "ha": { + "device_class": "voltage", + "unit_of_measurement": "V", + "name": "EPS Output Voltage", + "icon": "mdi:alpha-v-box", + "state_class": "measurement", + "value_template": "{{ value_json.eps_output_voltage }}" + } + }, + { + "name": "eps_output_power", + "register": "0x0217", + "function": "multiply", + "factor": 10, + "ha": { + "device_class": "power", + "unit_of_measurement": "W", + "name": "EPS Output Power", + "icon": "mdi:lightning-bolt", + "state_class": "measurement", + "value_template": "{{ value_json.eps_output_power }}" + } + }, + { + "name": "today_generation", + "register": "0x218", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Solar Generation Today", + "icon": "mdi:solar-power-variant", + "state_class": "total_increasing", + "value_template": "{{ value_json.today_generation }}" + } + }, + { + "name": "today_exported", + "register": "0x219", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Export Today", + "icon": "mdi:transmission-tower-export", + "state_class": "total_increasing", + "value_template": "{{ value_json.today_exported }}" + } + }, + { + "name": "today_purchase", + "register": "0x21a", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Import Today", + "icon": "mdi:transmission-tower-import", + "state_class": "total_increasing", + "value_template": "{{ value_json.today_purchase }}" + } + }, + { + "name": "today_consumption", + "register": "0x21b", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Consumption Today", + "icon": "mdi:home-lightning-bolt-outline", + "state_class": "total_increasing", + "value_template": "{{ value_json.today_consumption }}" + } + }, + { + "name": "total_generation_hi", + "register": "0x021C", + "ha": { + "name": "Total Generation Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.total_generation_hi }}" + } + }, + { + "name": "total_generation_lo", + "register": "0x021D", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Total Generation", + "icon": "mdi:solar-power-variant", + "state_class": "total_increasing", + "value_template": "{{ value_json.total_generation_lo }}" + } + }, + { + "name": "total_export_hi", + "register": "0x021E", + "ha": { + "name": "Total Export Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.total_export_hi }}" + } + }, + { + "name": "total_export_lo", + "register": "0x021F", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Total Export", + "icon": "mdi:transmission-tower-export", + "state_class": "total_increasing", + "value_template": "{{ value_json.total_export_lo }}" + } + }, + { + "name": "total_import_hi", + "register": "0x0220", + "ha": { + "name": "Total Import Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.total_import_hi }}" + } + }, + { + "name": "total_import_lo", + "register": "0x0221", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Total Import", + "icon": "mdi:transmission-tower-import", + "state_class": "total_increasing", + "value_template": "{{ value_json.total_import_lo }}" + } + }, + { + "name": "total_consumption_hi", + "register": "0x0222", + "ha": { + "name": "Total Consumption Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.total_consumption_hi }}" + } + }, + { + "name": "total_consumption_lo", + "register": "0x0223", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Total Consumption", + "icon": "mdi:home-lightning-bolt-outline", + "state_class": "total_increasing", + "value_template": "{{ value_json.total_consumption_lo }}" + } + }, + { + "name": "battery_charge_today", + "register": "0x0224", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Battery Charge Today", + "icon": "mdi:battery-charging-80", + "state_class": "total_increasing", + "value_template": "{{ value_json.battery_charge_today }}" + } + }, + { + "name": "battery_discharge_today", + "register": "0x0225", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Battery Discharge Today", + "icon": "mdi:battery-minus", + "state_class": "total_increasing", + "value_template": "{{ value_json.battery_discharge_today }}" + } + }, + { + "name": "battery_total_charge_hi", + "register": "0x0226", + "ha": { + "name": "Battery Total Charge Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.battery_total_charge_hi }}" + } + }, + { + "name": "battery_total_charge_lo", + "register": "0x0227", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Battery Total Charge", + "icon": "mdi:battery-charging-80", + "state_class": "total_increasing", + "value_template": "{{ value_json.battery_total_charge_lo }}" + } + }, + { + "name": "battery_total_discharge_hi", + "register": "0x0228", + "ha": { + "name": "Battery Total Discharge Hi", + "icon": "mdi:counter", + "value_template": "{{ value_json.battery_total_discharge_hi }}" + } + }, + { + "name": "battery_total_discharge_lo", + "register": "0x0229", + "ha": { + "device_class": "energy", + "unit_of_measurement": "kWh", + "name": "Battery Total Discharge", + "icon": "mdi:battery-minus", + "state_class": "total_increasing", + "value_template": "{{ value_json.battery_total_discharge_lo }}" + } + }, + { + "name": "countdown_time", + "register": "0x022A", + "ha": { + "name": "Countdown Time", + "unit_of_measurement": "s", + "icon": "mdi:timer", + "value_template": "{{ value_json.countdown_time }}" + } + }, + { + "name": "inverter_alarm_info", + "register": "0x022B", + "ha": { + "name": "Inverter Alarm Information", + "icon": "mdi:alert-circle", + "value_template": "{{ value_json.inverter_alarm_info }}" + } + }, + { + "name": "battery_cycles", + "register": "0x22c", + "ha": { + "device_class": "battery", + "unit_of_measurement": "cycles", + "name": "Battery Cycles", + "icon": "mdi:battery-sync", + "state_class": "total_increasing", + "value_template": "{{ value_json.battery_cycles }}" + } + }, + { + "name": "inverter_bus_voltage", + "register": "0x022D", + "function": "divide", + "factor": 10, + "ha": { + "device_class": "voltage", + "unit_of_measurement": "V", + "name": "Inverter Bus Voltage", + "icon": "mdi:alpha-v-box", + "state_class": "measurement", + "value_template": "{{ value_json.inverter_bus_voltage }}" + } + }, + { + "name": "llc_bus_voltage", + "register": "0x022E", + "function": "divide", + "factor": 10, + "ha": { + "device_class": "voltage", + "unit_of_measurement": "V", + "name": "LLC Bus Voltage", + "icon": "mdi:alpha-v-box", + "state_class": "measurement", + "value_template": "{{ value_json.llc_bus_voltage }}" + } + }, + { + "name": "buck_current", + "register": "0x022F", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "current", + "unit_of_measurement": "A", + "name": "Buck Current", + "icon": "mdi:alpha-a-box", + "state_class": "measurement", + "value_template": "{{ value_json.buck_current }}" + } + }, + { + "name": "battery_health_soh", + "register": "0x0237", + "ha": { + "device_class": "battery", + "unit_of_measurement": "%", + "name": "Battery Health (SOH)", + "icon": "mdi:battery-heart", + "state_class": "measurement", + "value_template": "{{ value_json.battery_health_soh }}" + } + }, + { + "name": "inverter_temp", + "register": "0x238", + "ha": { + "device_class": "temperature", + "unit_of_measurement": "°C", + "name": "Inverter Temperature", + "icon": "mdi:temperature-celsius", + "state_class": "measurement", + "value_template": "{{ value_json.inverter_temp }}" + } + }, + { + "name": "inverterHS_temp", + "register": "0x239", + "ha": { + "device_class": "temperature", + "unit_of_measurement": "°C", + "name": "Heatsink Temperature", + "icon": "mdi:temperature-celsius", + "state_class": "measurement", + "value_template": "{{ value_json.inverterHS_temp }}" + } + }, + { + "name": "solarPVAmps", + "register": "0x236", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "current", + "unit_of_measurement": "A", + "name": "Solar PV Current", + "icon": "mdi:alpha-a-box", + "state_class": "measurement", + "value_template": "{{ value_json.solarPVAmps }}" + } + }, + { + "name": "battery_current", + "register": "0x207", + "function": "divide", + "factor": 100, + "ha": { + "device_class": "current", + "unit_of_measurement": "A", + "name": "Battery Current", + "icon": "mdi:alpha-a-box", + "state_class": "measurement", + "value_template": "{{ value_json.battery_current }}" + } + } + ] } \ No newline at end of file diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index b81f961..6c92275 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -189,6 +189,31 @@ def on_message(self, client, userdata, message, properties=None): logging.info( f"Received a request for {register['name']} but energy_storage_mode is not in Passive mode. Ignoring") continue + if register['name'] == 'charge_discharge_power': + if int(self.raw_data.get('working_mode', None)) == 3: + logging.info( + f"Received a request for {register['name']} but working_mode is not in Passive mode. Ignoring") + continue + if 'write_addresses' in register: + write_register = None + write_functioncode = None + if new_raw_value == 0: + write_register = register['write_addresses'].get("standby", None) + elif new_raw_value < 0: + write_register = register['write_addresses'].get("discharge", None) + elif new_raw_value > 0: + write_register = register['write_addresses'].get("charge", None) + if write_register is None: + logging.error( + f"No write address found for value: {new_raw_value} on register: {register['name']}. Ignoring") + continue + if write_functioncode in register: + write_functioncode = register.get('write_functioncode', '16') + else: + logging.info( + f"Mapping value: {new_raw_value} to write register: {write_register} for register: {register['name']}") + self.write_register_special(write_register, write_functioncode, abs(new_raw_value)) + continue if register['name'] in self.raw_data: retry = self.write_retry while retry > 0: @@ -206,6 +231,7 @@ def on_message(self, client, userdata, message, properties=None): else: logging.error( f"No current read value for {register['name']} skipping write operation. Please try again.") + if not found: for block in self.config.get('write_register_blocks', []): @@ -299,7 +325,7 @@ def update_state(self): register.get('signed', False), register.get('registers', 1) ) - + if 'aggregate_datetime_bitmap' in register: continue #raw_value = self.aggregate_datetime_bitmap(register) @@ -482,8 +508,22 @@ def main(self): self.iteration += 1 self.terminate(status_code=0) + def write_register_special(self, registeraddress, functioncode, value): + with self.mutex: + logging.info( + f"Writing {registeraddress}({int(registeraddress, 16)}) functioncode: {functioncode} with {value}({value})") + self.instrument._generic_command( + functioncode, + int(registeraddress, 16), + value, + number_of_decimals=0, + number_of_registers=1, + signed=False, + payloadformat= self.instrument._Payloadformat.REGISTER, + ) + def write_register(self, register, value): - """ Read value from register with a retry mechanism """ + """ Write value to register """ with self.mutex: retry = self.write_retry logging.info( From 40933bf4f5d2c8fa0d74abf3a12f22efff7c465f Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Thu, 1 Jan 2026 17:42:03 +0000 Subject: [PATCH 04/20] Increase timeout for ME3000, output a failure pattern when a value is None --- sofar2mqtt-v2.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index 6c92275..5fd0eac 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -272,7 +272,7 @@ def setup_instrument(self): self.instrument.serial.bytesize = 8 self.instrument.serial.parity = serial.PARITY_NONE self.instrument.serial.stopbits = 1 - self.instrument.serial.timeout = 0.1 # seconds + self.instrument.serial.timeout = 0.5 # seconds self.instrument.close_port_after_each_call = False self.instrument.clear_buffers_before_each_transaction = True @@ -658,7 +658,9 @@ def read_register(self, registeraddress, read_type, signed, registers=1): self.failures = self.failures + 1 self.failure_pattern += "f" self.failed.append(registeraddress) - if value: + if value is None: + self.failure_pattern += "n" + else: self.failure_pattern += "." return value From 08641a070a394af1ca5ee621108464e37af0cdb0 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Thu, 1 Jan 2026 17:56:42 +0000 Subject: [PATCH 05/20] Assume device is an ME3000SP if the serial number lookup doesnt work --- sofar2mqtt-v2.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index 5fd0eac..0085727 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -68,22 +68,31 @@ def __init__(self, daemon, retry, retry_delay, write_retry, write_retry_delay, r self.client = mqtt.Client( client_id=f"sofar2mqtt-{socket.gethostname()}", userdata=None, protocol=mqtt.MQTTv5, transport="tcp") if not self.raw_data['serial_number']: - logging.error("Failed to determine serial number. Exiting") - self.terminate(status_code=1) + logging.error("Failed to determine serial number, assuming device is an ME3000SP") + self.raw_data['serial_number'] = "E1" # ME3000SP self.raw_data['model'] = self.determine_model() self.raw_data['protocol'] = self.determine_modbus_protocol() + protocol_file = self.raw_data.get('protocol', False) - if self.raw_data.get('protocol') == "SOFAR-1-40KTL.json": - logging.error("Unsupported protocol detected. Exiting") + if protocol_file == "SOFAR-1-40KTL.json": + logging.error(f"Sorry {self.raw_data['model']} is not currently supported. Exiting") + self.terminate(status_code=1) + if not protocol_file: + logging.error(f"Unknown protocol for model: {self.raw_data['model']}. Exiting") self.terminate(status_code=1) - - protocol_file = self.raw_data.get('protocol') if not os.path.isfile(protocol_file): logging.error( f"Protocol file {protocol_file} does not exist. Exiting") self.terminate(status_code=1) self.config = load_config(protocol_file) + + if 'registers' in self.config: + if 'serial_number' in self.config['registers']: + serial_number = self.config['registers']['serial_number']['value'] + logging.error(f"Using static serial number from config file. Serial Number: {serial_number}") + self.raw_data['serial_number'] = serial_number + self.write_registers = [] untested = False for register in self.config['registers']: @@ -825,7 +834,7 @@ def determine_modbus_protocol(self): "SOFAR ESI 2.5...5.0K": "SOFAR-HYD-3PH-AND-G3.json" } - modbus_protocol = protocol_mapping.get(self.raw_data.get('model')) + modbus_protocol = protocol_mapping.get(self.raw_data.get('model'), False) if modbus_protocol: logging.info(f"Modbus protocol determined: {modbus_protocol}") else: From 5e69647f3efdc3c58f9b0063e49289fc02a700fe Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Thu, 1 Jan 2026 17:58:30 +0000 Subject: [PATCH 06/20] Make this min -3000 --- SOFAR-HYD-ES-AND-ME3000-SP.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SOFAR-HYD-ES-AND-ME3000-SP.json b/SOFAR-HYD-ES-AND-ME3000-SP.json index b45a483..e5777e4 100644 --- a/SOFAR-HYD-ES-AND-ME3000-SP.json +++ b/SOFAR-HYD-ES-AND-ME3000-SP.json @@ -58,7 +58,7 @@ "command_topic": "sofar/rw/charge_discharge_power", "entity_category": "config", "name": "Charge/Discharge Power", - "min": 0, + "min": -3000, "max": 3000, "step": 100, "initial": 0, From 6f42470bf478bf9a15f2a1b7b133d39c78d54d77 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Thu, 1 Jan 2026 18:01:37 +0000 Subject: [PATCH 07/20] Use the functioncode --- sofar2mqtt-v2.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index 0085727..a7561a3 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -205,7 +205,7 @@ def on_message(self, client, userdata, message, properties=None): continue if 'write_addresses' in register: write_register = None - write_functioncode = None + write_functioncode = register.get('write_functioncode', '16') if new_raw_value == 0: write_register = register['write_addresses'].get("standby", None) elif new_raw_value < 0: @@ -216,13 +216,10 @@ def on_message(self, client, userdata, message, properties=None): logging.error( f"No write address found for value: {new_raw_value} on register: {register['name']}. Ignoring") continue - if write_functioncode in register: - write_functioncode = register.get('write_functioncode', '16') - else: - logging.info( - f"Mapping value: {new_raw_value} to write register: {write_register} for register: {register['name']}") - self.write_register_special(write_register, write_functioncode, abs(new_raw_value)) - continue + logging.info( + f"Mapping value: {new_raw_value} to write register: {write_register} for register: {register['name']}") + self.write_register_special(write_register, write_functioncode, abs(new_raw_value)) + continue if register['name'] in self.raw_data: retry = self.write_retry while retry > 0: From f45cc8a7e6ad51c8a53ad76c2582cfa831733d02 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Thu, 1 Jan 2026 18:03:06 +0000 Subject: [PATCH 08/20] Notify when this changes --- SOFAR-HYD-ES-AND-ME3000-SP.json | 1 + 1 file changed, 1 insertion(+) diff --git a/SOFAR-HYD-ES-AND-ME3000-SP.json b/SOFAR-HYD-ES-AND-ME3000-SP.json index e5777e4..cb1f055 100644 --- a/SOFAR-HYD-ES-AND-ME3000-SP.json +++ b/SOFAR-HYD-ES-AND-ME3000-SP.json @@ -48,6 +48,7 @@ "max": 3000, "write": true, "write_functioncode": "42", + "notify_on_change": true, "write_addresses": { "standby": "0x0100", "discharge": "0x0101", From 1e0bab68b6384cc81f43436069c03f90d3961593 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Thu, 1 Jan 2026 19:26:57 +0000 Subject: [PATCH 09/20] Add missing function --- SOFAR-HYD-ES-AND-ME3000-SP.json | 1 + 1 file changed, 1 insertion(+) diff --git a/SOFAR-HYD-ES-AND-ME3000-SP.json b/SOFAR-HYD-ES-AND-ME3000-SP.json index cb1f055..efb806b 100644 --- a/SOFAR-HYD-ES-AND-ME3000-SP.json +++ b/SOFAR-HYD-ES-AND-ME3000-SP.json @@ -44,6 +44,7 @@ "name": "charge_discharge_power", "desc": "Charge/Discharge power (-3000 to 3000W) - used with Charge/Discharge modes", "type": "U16", + "function": "int", "min": -3000, "max": 3000, "write": true, From f96d8ed8bae5806589a85b22645ea6be34ae80cf Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Thu, 1 Jan 2026 19:27:09 +0000 Subject: [PATCH 10/20] Improve error handling --- sofar2mqtt-v2.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index a7561a3..bdbc354 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -237,7 +237,10 @@ def on_message(self, client, userdata, message, properties=None): else: logging.error( f"No current read value for {register['name']} skipping write operation. Please try again.") - + else: + logging.error(f"Function provided {register['function']} is not known for register {register['name']} skipping write operation. Check the JSON is configured correctly.") + else: + logging.error(f"No function was provided for register {register['name']} skipping write operation. Check the JSON is configured correctly.") if not found: for block in self.config.get('write_register_blocks', []): From db4b3660fde13e14e3fd2b7487b36b6718a760d1 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Thu, 1 Jan 2026 19:29:58 +0000 Subject: [PATCH 11/20] Fix serial number workaround --- sofar2mqtt-v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index bdbc354..1beca5d 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -69,7 +69,7 @@ def __init__(self, daemon, retry, retry_delay, write_retry, write_retry_delay, r client_id=f"sofar2mqtt-{socket.gethostname()}", userdata=None, protocol=mqtt.MQTTv5, transport="tcp") if not self.raw_data['serial_number']: logging.error("Failed to determine serial number, assuming device is an ME3000SP") - self.raw_data['serial_number'] = "E1" # ME3000SP + self.raw_data['serial_number'] = "E1234567890123" # ME3000SP self.raw_data['model'] = self.determine_model() self.raw_data['protocol'] = self.determine_modbus_protocol() protocol_file = self.raw_data.get('protocol', False) From bd30b17a3686cdc25d51885264bdb2011f2fddf5 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Thu, 1 Jan 2026 19:37:12 +0000 Subject: [PATCH 12/20] Use the actual charge/discharge power --- SOFAR-HYD-ES-AND-ME3000-SP.json | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/SOFAR-HYD-ES-AND-ME3000-SP.json b/SOFAR-HYD-ES-AND-ME3000-SP.json index efb806b..f895761 100644 --- a/SOFAR-HYD-ES-AND-ME3000-SP.json +++ b/SOFAR-HYD-ES-AND-ME3000-SP.json @@ -42,11 +42,14 @@ }, { "name": "charge_discharge_power", + "register": "0x020d", "desc": "Charge/Discharge power (-3000 to 3000W) - used with Charge/Discharge modes", - "type": "U16", + "type": "I16", "function": "int", "min": -3000, "max": 3000, + "signed": true, + "passive": true, "write": true, "write_functioncode": "42", "notify_on_change": true, @@ -193,20 +196,6 @@ "value_template": "{{ value_json.grid_current }}" } }, - { - "name": "battery_power", - "register": "0x20d", - "function": "divide", - "factor": 100, - "ha": { - "device_class": "power", - "unit_of_measurement": "W", - "name": "Battery Power", - "icon": "mdi:battery-charging", - "state_class": "measurement", - "value_template": "{{ value_json.battery_power }}" - } - }, { "name": "grid_freq", "register": "0x20c", From c938c0126c45f047fe2bb9c27d253f164ddc9b9b Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Sat, 3 Jan 2026 22:01:28 +0000 Subject: [PATCH 13/20] Add special write values for standby/auto 0x5555 --- SOFAR-HYD-ES-AND-ME3000-SP.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SOFAR-HYD-ES-AND-ME3000-SP.json b/SOFAR-HYD-ES-AND-ME3000-SP.json index f895761..fa9114d 100644 --- a/SOFAR-HYD-ES-AND-ME3000-SP.json +++ b/SOFAR-HYD-ES-AND-ME3000-SP.json @@ -59,6 +59,10 @@ "charge": "0x0102", "auto": "0x0103" }, + "write_values": { + "standby": 21845, + "auto": 21845 + }, "ha": { "command_topic": "sofar/rw/charge_discharge_power", "entity_category": "config", From 85796b777f8a312ed704f8b5470edbf45711368f Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Sat, 3 Jan 2026 22:02:28 +0000 Subject: [PATCH 14/20] Update write_register_special function --- sofar2mqtt-v2.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index 1beca5d..abcf6a7 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -69,7 +69,7 @@ def __init__(self, daemon, retry, retry_delay, write_retry, write_retry_delay, r client_id=f"sofar2mqtt-{socket.gethostname()}", userdata=None, protocol=mqtt.MQTTv5, transport="tcp") if not self.raw_data['serial_number']: logging.error("Failed to determine serial number, assuming device is an ME3000SP") - self.raw_data['serial_number'] = "E1234567890123" # ME3000SP + self.raw_data['serial_number'] = "XE101010101010" # ME3000SP self.raw_data['model'] = self.determine_model() self.raw_data['protocol'] = self.determine_modbus_protocol() protocol_file = self.raw_data.get('protocol', False) @@ -208,6 +208,7 @@ def on_message(self, client, userdata, message, properties=None): write_functioncode = register.get('write_functioncode', '16') if new_raw_value == 0: write_register = register['write_addresses'].get("standby", None) + new_raw_value = register['write_values'].get("standby", new_raw_value) elif new_raw_value < 0: write_register = register['write_addresses'].get("discharge", None) elif new_raw_value > 0: @@ -517,20 +518,28 @@ def main(self): self.iteration += 1 self.terminate(status_code=0) + def write_register_special(self, registeraddress, functioncode, value): with self.mutex: + reg_int = int(registeraddress, 16) + logging.info( - f"Writing {registeraddress}({int(registeraddress, 16)}) functioncode: {functioncode} with {value}({value})") - self.instrument._generic_command( - functioncode, - int(registeraddress, 16), - value, - number_of_decimals=0, - number_of_registers=1, - signed=False, - payloadformat= self.instrument._Payloadformat.REGISTER, + f"Writing {registeraddress}({reg_int}) functioncode: {functioncode} with {value}" ) + # Payload = [cmd][param] (each uint16) + payload = struct.pack(">HH", reg_int, value) + + logging.info("Payload (no CRC): %s", payload.hex(" ")) + + response = self.instrument._perform_command( + functioncode, # 0x42 + payload + ) + + logging.info("Raw response: %s", response.hex(" ")) + return response + def write_register(self, register, value): """ Write value to register """ with self.mutex: @@ -731,7 +740,7 @@ def determine_serial_number(self): def determine_model(self): """ Determine the model of the inverter based on the serial number """ - serial_number = self.raw_data.get('serial_number') + serial_number = self.raw_data.get('serial_number') model = None if len(serial_number) == 14: code = serial_number[1:3] From d2765842c0b38248a002a70d4351633ed0f7efa9 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Sat, 3 Jan 2026 22:21:23 +0000 Subject: [PATCH 15/20] Fix bug in determine_serial_number --- sofar2mqtt-v2.py | 83 ++++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index abcf6a7..d158785 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -693,51 +693,52 @@ def is_valid_serial_number(self, serial_number): return False def determine_serial_number(self): - """ Determine the serial number from the inverter """ - serial_number = None - - # Try first location: 0x2001 ... 0x2007 - try: - serial_number = ''.join([self.read_register( - register, 'string', False, 1) for register in range(0x2001, 0x2008)]) - if self.is_valid_serial_number(serial_number): - logging.info( - f"Valid Serial number found at first location: {serial_number}") - return serial_number - except Exception as e: - logging.info( - f"Failed to validate serial number from first location: {str(e)}") - - # Try second location: 0x0445 ... 0x044B (14 digits) - try: - serial_number = ''.join([self.read_register( - register, 'string', False, 1) for register in range(0x0445, 0x044C)]) - if self.is_valid_serial_number(serial_number): - logging.info( - f"Valid Serial number found at second location: {serial_number}") - return serial_number - except Exception as e: - logging.info( - f"Failed to validate serial number from second location: {str(e)}") - - # Try third location: 0x0445 ... 0x044C and 0x0470...0x0471 (20 digits) - try: - serial_number_part1 = ''.join([self.read_register( - register, 'string', False, 1) for register in range(0x0445, 0x044C)]) - serial_number_part2 = ''.join([self.read_register( - register, 'string', False, 1) for register in range(0x0470, 0x0472)]) - serial_number = serial_number_part1 + serial_number_part2 - if self.is_valid_serial_number(serial_number): - logging.info( - f"Valid Serial number found at third location: {serial_number}") - return serial_number - except Exception as e: - logging.info( - f"Failed to validate serial number from third location: {str(e)}") + """Determine the serial number from the inverter.""" + + def read_ascii(registers): + chars = [] + for reg in registers: + val = self.read_register(reg, "string", False, 1) + if val is None: + return None + val = val.replace("\x00", "").strip() + if len(val) > 1: + return None + chars.append(val) + return "".join(chars) + + # 1) First location: 0x2001–0x2007 (14 chars) + serial = read_ascii(range(0x2001, 0x2008)) + if serial and self.is_valid_serial_number(serial): + logging.info(f"Valid Serial number found at first location: {serial}") + return serial + + # 2) Second location: 0x0445–0x044B (14 chars) + serial = read_ascii(range(0x0445, 0x044C)) + if serial and self.is_valid_serial_number(serial): + logging.info(f"Valid Serial number found at second location: {serial}") + return serial + + # 3) Third location: 0x0445–0x044C (16 chars) + 0x0470–0x0471 (4 chars) + part1 = read_ascii(range(0x0445, 0x044C)) + part2 = read_ascii(range(0x0470, 0x0472)) + + if part1 is not None and part2 is not None: + # Sofar rule: if part2 is empty → 14-digit serial + if part2.strip("") == "": + if self.is_valid_serial_number(part1): + logging.info(f"Valid 14-digit Serial number found at third location: {part1}") + return part1 + else: + serial = part1 + part2 + if self.is_valid_serial_number(serial): + logging.info(f"Valid 20-digit Serial number found at third location: {serial}") + return serial logging.error("Failed to determine serial number") return None + def determine_model(self): """ Determine the model of the inverter based on the serial number """ serial_number = self.raw_data.get('serial_number') From 20a3e61ec02bbb3cd53f0cf0de848d40047a4d82 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Sat, 3 Jan 2026 22:36:01 +0000 Subject: [PATCH 16/20] Full fix for serial number reading --- sofar2mqtt-v2.py | 49 +++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index d158785..0647e4a 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -68,8 +68,8 @@ def __init__(self, daemon, retry, retry_delay, write_retry, write_retry_delay, r self.client = mqtt.Client( client_id=f"sofar2mqtt-{socket.gethostname()}", userdata=None, protocol=mqtt.MQTTv5, transport="tcp") if not self.raw_data['serial_number']: - logging.error("Failed to determine serial number, assuming device is an ME3000SP") - self.raw_data['serial_number'] = "XE101010101010" # ME3000SP + logging.error("Failed to determine serial number, Exiting") + self.terminate(status_code=1) self.raw_data['model'] = self.determine_model() self.raw_data['protocol'] = self.determine_modbus_protocol() protocol_file = self.raw_data.get('protocol', False) @@ -692,36 +692,47 @@ def is_valid_serial_number(self, serial_number): return serial_number[0] == 'S' and serial_number[1:].isalnum() return False + def read_ascii(self, start, count): + try: + regs = self.instrument.read_registers(start, count, functioncode=3) + except Exception as e: + logging.debug(f"Error reading registers at {hex(start)}: {e}") + return None + + chars = [] + for val in regs: + hi = (val >> 8) & 0xFF + lo = val & 0xFF + + # Sofar stores ASCII in HIGH BYTE first for this model + for b in (hi, lo): + if b == 0: + continue + c = chr(b) + if c.isprintable(): + chars.append(c) + + return "".join(chars) + def determine_serial_number(self): """Determine the serial number from the inverter.""" - - def read_ascii(registers): - chars = [] - for reg in registers: - val = self.read_register(reg, "string", False, 1) - if val is None: - return None - val = val.replace("\x00", "").strip() - if len(val) > 1: - return None - chars.append(val) - return "".join(chars) - + # 1) First location: 0x2001–0x2007 (14 chars) - serial = read_ascii(range(0x2001, 0x2008)) + serial = self.read_ascii(0x2001, 7) if serial and self.is_valid_serial_number(serial): logging.info(f"Valid Serial number found at first location: {serial}") return serial # 2) Second location: 0x0445–0x044B (14 chars) - serial = read_ascii(range(0x0445, 0x044C)) + serial = self.read_ascii(0x0445, 7) if serial and self.is_valid_serial_number(serial): logging.info(f"Valid Serial number found at second location: {serial}") return serial # 3) Third location: 0x0445–0x044C (16 chars) + 0x0470–0x0471 (4 chars) - part1 = read_ascii(range(0x0445, 0x044C)) - part2 = read_ascii(range(0x0470, 0x0472)) + part1 = self.read_ascii(0x0445, 8) # 16 chars + part2 = self.read_ascii(0x0470, 2) # 4 chars + serial = part1 + part2 if part1 is not None and part2 is not None: # Sofar rule: if part2 is empty → 14-digit serial From b7bd779696028c9a2c5e0009384d7b03b9efe7fc Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Sat, 3 Jan 2026 22:39:42 +0000 Subject: [PATCH 17/20] Notify on change of desired power --- SOFAR-HYD-3PH-AND-G3.json | 1 + 1 file changed, 1 insertion(+) diff --git a/SOFAR-HYD-3PH-AND-G3.json b/SOFAR-HYD-3PH-AND-G3.json index d5af0dc..a5cae84 100644 --- a/SOFAR-HYD-3PH-AND-G3.json +++ b/SOFAR-HYD-3PH-AND-G3.json @@ -796,6 +796,7 @@ "function": "int", "signed": true, "passive": true, + "notify_on_change": true, "min": -999999, "max": 999999, "write": true, From 2c312ee74e361ce4b01fbce8b09994c4ca0cf860 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Sun, 4 Jan 2026 00:13:45 +0000 Subject: [PATCH 18/20] Fix bug in desired_power --- SOFAR-HYD-3PH-AND-G3.json | 16 ++++++++++++++++ sofar2mqtt-v2.py | 31 +++++++++++++++++-------------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/SOFAR-HYD-3PH-AND-G3.json b/SOFAR-HYD-3PH-AND-G3.json index a5cae84..6339924 100644 --- a/SOFAR-HYD-3PH-AND-G3.json +++ b/SOFAR-HYD-3PH-AND-G3.json @@ -794,6 +794,7 @@ "desc3": "Reading 0x1189 will always return 0. Min is typically -6000 or the value set to 0x1189. Max is typically +6000 or the value set to 0x1189", "type": "I32", "function": "int", + "read": false, "signed": true, "passive": true, "notify_on_change": true, @@ -1204,6 +1205,21 @@ "value_template": "{{ value_json.battery_current }}" } }, + { + "name": "battery_current_2", + "register": "0x0669", + "function": "divide", + "factor": 100, + "signed": true, + "ha": { + "device_class": "current", + "unit_of_measurement": "A", + "name": "Battery Current 2", + "icon": "mdi:alpha-a-box", + "state_class": "measurement", + "value_template": "{{ value_json.battery_current_2 }}" + } + }, { "name": "battery_power", "register": "0x0606", diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index 0647e4a..8fc0d20 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -301,18 +301,18 @@ def aggregate_datetime_bitmap(self, register): def combine_aggregate_registers(self, register): """ Combine registers from the 'aggregate' field using the arithmetic function in 'agg_function' """ raw_value = 0 - for register_name in register['aggregate']: - if register_name in self.raw_data: + for agg_register_name in register['aggregate']: + if agg_register_name in self.raw_data: if raw_value == 0: - raw_value = self.raw_data[register_name] + raw_value = self.raw_data[agg_register_name] else: if register['agg_function'] == 'add': - raw_value += self.raw_data[register_name] + raw_value += self.raw_data[agg_register_name] elif register['agg_function'] == 'subtract': - raw_value -= self.raw_data[register_name] + raw_value -= self.raw_data[agg_register_name] elif register['agg_function'] == 'avg': raw_value = int( - (raw_value + self.raw_data[register_name]) / 2) + (raw_value + self.raw_data[agg_register_name]) / 2) self.raw_data[register['name']] = raw_value return raw_value @@ -324,17 +324,20 @@ def update_state(self): continue raw_value = None logging.debug('Reading %s', register['name']) - if 'aggregate' in register: - raw_value = self.combine_aggregate_registers(register) + if register.get('read_type') == 'static': + logging.info(f"Using static value for {register['name']}") raw_value = register['value'] + if 'aggregate' in register: + raw_value = self.combine_aggregate_registers(register) if register.get('register', False): - raw_value = self.read_register( - int(register['register'], 16), - register.get('read_type', 'register'), - register.get('signed', False), - register.get('registers', 1) - ) + if register.get('read', True): + raw_value = self.read_register( + int(register['register'], 16), + register.get('read_type', 'register'), + register.get('signed', False), + register.get('registers', 1) + ) if 'aggregate_datetime_bitmap' in register: continue From b6b54ec2c4e4e1a32a8e17c010513e3abc67a800 Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Sun, 4 Jan 2026 00:34:33 +0000 Subject: [PATCH 19/20] Remove testing register --- SOFAR-HYD-3PH-AND-G3.json | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/SOFAR-HYD-3PH-AND-G3.json b/SOFAR-HYD-3PH-AND-G3.json index 6339924..32cf908 100644 --- a/SOFAR-HYD-3PH-AND-G3.json +++ b/SOFAR-HYD-3PH-AND-G3.json @@ -1205,21 +1205,6 @@ "value_template": "{{ value_json.battery_current }}" } }, - { - "name": "battery_current_2", - "register": "0x0669", - "function": "divide", - "factor": 100, - "signed": true, - "ha": { - "device_class": "current", - "unit_of_measurement": "A", - "name": "Battery Current 2", - "icon": "mdi:alpha-a-box", - "state_class": "measurement", - "value_template": "{{ value_json.battery_current_2 }}" - } - }, { "name": "battery_power", "register": "0x0606", From 5992cb709d34df40132dfd455b46ee07d681d18b Mon Sep 17 00:00:00 2001 From: Richard Pearce Date: Sun, 4 Jan 2026 13:13:13 +0000 Subject: [PATCH 20/20] Include the error code descriptions and solutions from the user manual --- SOFAR-HYD-3PH-AND-G3.json | 1395 ++++++++++++++++++++++++++++--------- sofar2mqtt-v2.py | 11 +- 2 files changed, 1095 insertions(+), 311 deletions(-) diff --git a/SOFAR-HYD-3PH-AND-G3.json b/SOFAR-HYD-3PH-AND-G3.json index 32cf908..386fa84 100644 --- a/SOFAR-HYD-3PH-AND-G3.json +++ b/SOFAR-HYD-3PH-AND-G3.json @@ -1649,315 +1649,1090 @@ } ], "error_codes": { - "1": "GridOVP", - "2": "GridUVP", - "3": "GridOFP", - "4": "GridUFP", - "5": "GFCI", - "6": "OVRT", - "7": "LVRT", - "8": "IslandFault", - "9": "GridOVPInstant1", - "10": "GridOVPInstant2", - "11": "VGridLineFault", - "12": "InvOVP", - "17": "HwADFaultIGrid", - "18": "HwADFaultDCI(AC)", - "19": "HwADFaultVGrid(DC)", - "20": "HwADFaultVGrid(AC)", - "21": "GFCIDeviceFault(DC)", - "22": "GFCIDeviceFault(AC)", - "23": "HwADFaultDCV", - "24": "HwADFaultIdc", - "25": "HwADFaultDCI(DC)", - "26": "HwADFaultIdcBranch", - "29": "ConsistentFault_GFCI", - "30": "ConsistentFault_Vgrid", - "31": "ConsistentFault_DCI", - "33": "SpiCommFault(DC)", - "34": "SpiCommFault(AC)", - "35": "SChip_Fault", - "36": "MChip_Fault", - "37": "HwAuxPowerFault", - "38": "InverterSoftStartFail", - "41": "RelayFail", - "42": "IsoFault", - "43": "PEConnectFault", - "44": "PvConfigError", - "45": "CTDisconnect", - "46": "ReversalConnection", - "47": "ParallelFault", - "48": "SNTypeFault", - "49": "TempFault_Bat", - "50": "TempFault_HeatSink1", - "51": "TempFault_HeatSink2", - "52": "TempFault_HeatSink3", - "53": "TempFault_HeatSink4", - "54": "TempFault_HeatSink5", - "55": "TempFault_HeatSink6", - "57": "TempFault_Env1", - "58": "TempFault_Env2", - "59": "TempFault_Inv1", - "60": "TempFault_Inv2", - "61": "TempFault_Inv3", - "65": "VbusRmsUnbalance", - "66": "VbusInstantUnbalance", - "67": "BusUVP", - "68": "BusZVP", - "69": "PVOVP", - "70": "BatOVP", - "71": "LLCBusOVP", - "72": "SwBusRmsOVP", - "73": "SwBusInstantOVP", - "74": "FlyingCapOVP", - "81": "SwBatOCP", - "82": "DciOCP", - "83": "SwOCPInstant", - "84": "SwBuckBoostOCP", - "85": "SwAcRmsOCP", - "86": "SwPvOCPInstant", - "87": "IpvUnbalance", - "88": "IacUnbalance", - "89": "SwPvOCP", - "97": "HwLLCBusOVP", - "98": "HwBusOVP", - "99": "HwBuckBoostOCP", - "100": "HwBatOCP", - "102": "HwPVOCP", - "103": "HwACOCP", - "110": "Overload1", - "111": "Overload2", - "112": "Overload3", - "113": "OverTempDerating", - "114": "FreqDerating", - "115": "FreqLoading", - "116": "VoltDerating", - "117": "VoltLoading", - "124": "BatLowVoltageAlarm", - "125": "BatLowVoltageShut", - "129": "unrecoverHwAcOCP", - "130": "unrecoverBusOVP", - "131": "unrecoverHwBusOVP", - "132": "unrecoverIpvUnbalance", - "133": "unrecoverEPSBatOCP", - "134": "unrecoverAcOCPInstant", - "135": "unrecoverIacUnbalance", - "137": "unrecoverPvConfigError", - "138": "unrecoverPVOCPInstant", - "139": "unrecoverHwPVOCP", - "140": "unrecoverRelayFail", - "141": "unrecoverVbusUnbalance", - "142": "unrecoverSpdFail(DC)", - "143": "unrecoverSpdFail(AC)", - "145": "USBFault", - "146": "WifiFault", - "147": "BluetoothFault", - "148": "RTCFault", - "149": "CommEEPROMFault", - "150": "FlashFault", - "152": "SafetyVerFault", - "153": "SciCommLose(DC)", - "154": "SciCommLose(AC)", - "155": "SciCommLose(Fuse)", - "156": "SoftVerError", - "157": "BMS1CommunicatonFault", - "158": "BMS2CommunicatonFault", - "159": "BMS3CommunicatonFault", - "160": "BMS4CommunicatonFault", - "161": "ForceShutdown", - "162": "RemoteShutdown", - "163": "Drms0Shutdown", - "165": "RemoteDerating", - "166": "LogicInterfaceDerating", - "167": "AlarmAntiRefluxing", - "169": "FanFault1", - "170": "FanFault2", - "171": "FanFault3", - "172": "FanFault4", - "173": "FanFault5", - "174": "FanFault6", - "176": "MeterCommLose", - "177": "BMS OVP", - "178": "BMS UVP", - "179": "BMS OTP", - "180": "BMS UTP", - "181": "BMS OCP", - "182": "BMS Short", - "193": "StringFuse_Fault0", - "194": "StringFuse_Fault1", - "195": "StringFuse_Fault2", - "196": "StringFuse_Fault3", - "197": "StringFuse_Fault4", - "198": "StringFuse_Fault5", - "199": "StringFuse_Fault6", - "200": "StringFuse_Fault7", - "201": "StringFuse_Fault8", - "202": "StringFuse_Fault9", - "203": "StringFuse_Fault10", - "204": "StringFuse_Fault11", - "205": "StringFuse_Fault12", - "206": "StringFuse_Fault13", - "207": "StringFuse_Fault14", - "208": "StringFuse_Fault15", - "209": "StringFuse_Fault16", - "210": "StringFuse_Fault17", - "211": "StringFuse_Fault18", - "212": "StringFuse_Fault19", - "213": "StringFuse_Fault20", - "214": "StringFuse_Fault21", - "215": "StringFuse_Fault22", - "216": "StringFuse_Fault23", - "217": "StringFuse_Fault24", - "218": "StringFuse_Fault25", - "219": "StringFuse_Fault26", - "220": "StringFuse_Fault27", - "221": "StringFuse_Fault28", - "222": "StringFuse_Fault29", - "223": "StringFuse_Fault30", - "224": "StringFuse_Fault31", - "225": "InputFuse_Fault0", - "226": "InputFuse_Fault1", - "227": "InputFuse_Fault2", - "228": "InputFuse_Fault3", - "229": "InputFuse_Fault4", - "230": "InputFuse_Fault5", - "231": "InputFuse_Fault6", - "232": "InputFuse_Fault7", - "233": "InputFuse_Fault8", - "234": "InputFuse_Fault9", - "235": "InputFuse_Fault10", - "236": "InputFuse_Fault11", - "237": "InputFuse_Fault12", - "238": "InputFuse_Fault13", - "239": "InputFuse_Fault14", - "240": "InputFuse_Fault15", - "241": "CombinerOverVoltageGroup1", - "242": "CombinerOverVoltageGroup2", - "243": "CombinerOverVoltageGroup3", - "244": "CombinerOverVoltageGroup4", - "245": "CombinerOverVoltageGroup5", - "246": "CombinerOverVoltageGroup6", - "247": "CombinerOverVoltageGroup7", - "248": "CombinerOverVoltageGroup8", - "249": "CombinerOverVoltageGroup9", - "250": "CombinerOverVoltageGroup10", - "251": "CombinerOverVoltageGroup11", - "252": "CombinerOverVoltageGroup12", - "253": "CombinerOverVoltageGroup13", - "254": "CombinerOverVoltageGroup14", - "255": "CombinerOverVoltageGroup15", - "256": "CombinerOverVoltageGroup16", - "257": "CombinerUnderVoltageGroup1", - "258": "CombinerUnderVoltageGroup2", - "259": "CombinerUnderVoltageGroup3", - "260": "CombinerUnderVoltageGroup4", - "261": "CombinerUnderVoltageGroup5", - "262": "CombinerUnderVoltageGroup6", - "263": "CombinerUnderVoltageGroup7", - "264": "CombinerUnderVoltageGroup8", - "265": "CombinerUnderVoltageGroup9", - "266": "CombinerUnderVoltageGroup10", - "267": "CombinerUnderVoltageGroup11", - "268": "CombinerUnderVoltageGroup12", - "269": "CombinerUnderVoltageGroup13", - "270": "CombinerUnderVoltageGroup14", - "271": "CombinerUnderVoltageGroup15", - "272": "CombinerUnderVoltageGroup16", - "273": "CombinerOverCurrent1", - "274": "CombinerOverCurrent2", - "275": "CombinerOverCurrent3", - "276": "CombinerOverCurrent4", - "277": "CombinerOverCurrent5", - "278": "CombinerOverCurrent6", - "279": "CombinerOverCurrent7", - "280": "CombinerOverCurrent8", - "281": "CombinerOverCurrent9", - "282": "CombinerOverCurrent10", - "283": "CombinerOverCurrent11", - "284": "CombinerOverCurrent12", - "285": "CombinerOverCurrent13", - "286": "CombinerOverCurrent14", - "287": "CombinerOverCurrent15", - "288": "CombinerOverCurrent16", - "289": "CombinerOverCurrent17", - "290": "CombinerOverCurrent18", - "291": "CombinerOverCurrent19", - "292": "CombinerOverCurrent20", - "293": "CombinerOverCurrent21", - "294": "CombinerOverCurrent22", - "295": "CombinerOverCurrent23", - "296": "CombinerOverCurrent24", - "297": "CombinerOverCurrent25", - "298": "CombinerOverCurrent26", - "299": "CombinerOverCurrent27", - "300": "CombinerOverCurrent28", - "301": "CombinerOverCurrent29", - "302": "CombinerOverCurrent30", - "303": "CombinerOverCurrent31", - "304": "CombinerOverCurrent32", - "337": "CombinerRefluxFault1", - "338": "CombinerRefluxFault2", - "339": "CombinerRefluxFault3", - "340": "CombinerRefluxFault4", - "341": "CombinerRefluxFault5", - "342": "CombinerRefluxFault6", - "343": "CombinerRefluxFault7", - "344": "CombinerRefluxFault8", - "345": "CombinerRefluxFault9", - "346": "CombinerRefluxFault10", - "347": "CombinerRefluxFault11", - "348": "CombinerRefluxFault12", - "349": "CombinerRefluxFault13", - "350": "CombinerRefluxFault14", - "351": "CombinerRefluxFault15", - "352": "CombinerRefluxFault16", - "353": "CombinerRefluxFault17", - "354": "CombinerRefluxFault18", - "355": "CombinerRefluxFault19", - "356": "CombinerRefluxFault20", - "357": "CombinerRefluxFault21", - "358": "CombinerRefluxFault22", - "359": "CombinerRefluxFault23", - "360": "CombinerRefluxFault24", - "361": "CombinerRefluxFault25", - "362": "CombinerRefluxFault26", - "363": "CombinerRefluxFault27", - "364": "CombinerRefluxFault28", - "365": "CombinerRefluxFault29", - "366": "CombinerRefluxFault30", - "367": "CombinerRefluxFault31", - "368": "CombinerRefluxFault32", - "401": "AFCI0", - "402": "AFCI1", - "403": "AFCI2", - "404": "AFCI3", - "405": "AFCI4", - "406": "AFCI5", - "407": "AFCI6", - "408": "AFCI7", - "409": "AFCI8", - "410": "AFCI9", - "411": "AFCI10", - "412": "AFCI11", - "413": "AFCI12", - "414": "AFCI13", - "415": "AFCI14", - "416": "AFCI15", - "417": "AFCI16", - "418": "AFCI17", - "419": "AFCI18", - "420": "AFCI19", - "421": "AFCI20", - "422": "AFCI21", - "423": "AFCI22", - "424": "AFCI23", - "425": "AFCI24", - "426": "AFCI25", - "427": "AFCI26", - "428": "AFCI27", - "429": "AFCI28", - "430": "AFCI29", - "431": "AFCI30", - "432": "AFCI31" + "1": { + "name": "GridOVP", + "description": "The grid voltage is too high", + "solution": "If the alarm occurs occasionally, the possible cause is that the electric grid is abnormal occasionally. Inverter will automatically return to normal operating status when the electric grid’s back to normal. If the alarm occurs frequently, check whether the grid voltage/frequency is within the acceptable range. If yes, please check the AC circuit breaker and AC wiring of the inverter. If the grid voltage/frequency is NOT within the acceptable range and AC wiring is correct, but the alarm occurs repeatedly, contact technical support to change the grid protection points after obtaining approval from the local electrical grid operator." + }, + "2": { + "name": "GridUVP", + "description": "The grid voltage is too low" + }, + "3": { + "name": "GridOFP", + "description": "The grid frequency is too high" + }, + "4": { + "name": "GridUFP", + "description": "The grid frequency is too low" + }, + "5": { + "name": "GFCI", + "description": "Charge Leakage Fault", + "solution": "Internal faults of inverter, switch OFF inverter, wait for 5 minutes, then switch ON inverter. Check whether the problem is solved. If no, please contact technical support." + }, + "6": { + "name": "OVRT", + "description": "OVRT function is faulty" + }, + "7": { + "name": "LVRT", + "description": "LVRT function is faulty" + }, + "8": { + "name": "IslandFault", + "description": "Island protection error" + }, + "9": { + "name": "GridOVPInstant1", + "description": "Transient overvoltage of grid voltage 1" + }, + "10": { + "name": "GridOVPInstant2", + "description": "Transient overvoltage of grid voltage 2" + }, + "11": { + "name": "VGridLineFault", + "description": "Power grid line voltage error" + }, + "12": { + "name": "InvOVP", + "description": "Inverter voltage overvoltage" + }, + "17": { + "name": "HwADFaultIGrid", + "description": "Power grid current sampling error" + }, + "18": { + "name": "HwADFaultDCI(AC)", + "description": "Wrong sampling of DC component of grid current" + }, + "19": { + "name": "HwADFaultVGrid(DC)", + "description": "Power grid voltage sampling error (DC)" + }, + "20": { + "name": "HwADFaultVGrid(AC)", + "description": "Power grid voltage sampling error (AC)" + }, + "21": { + "name": "GFCIDeviceFault(DC)", + "description": "Leakage current sampling error (DC)" + }, + "22": { + "name": "GFCIDeviceFault(AC)", + "description": "Leakage current sampling error (AC)" + }, + "23": { + "name": "HwADFaultDCV", + "description": "Error in DC component sampling of load voltage" + }, + "24": { + "name": "HwADFaultIdc", + "description": "DC input current sampling error" + }, + "25": { + "name": "HwADFaultDCI(DC)" + }, + "26": { + "name": "HwADFaultIdcBranch" + }, + "29": { + "name": "ConsistentFault_GFCI", + "description": "Leakage current consistency error" + }, + "30": { + "name": "ConsistentFault_Vgrid", + "description": "Grid voltage consistency error" + }, + "31": { + "name": "ConsistentFault_DCI" + }, + "33": { + "name": "SpiCommFault(DC)", + "description": "SPI communication error (DC)" + }, + "34": { + "name": "SpiCommFault(AC)", + "description": "SPI communication error (AC)" + }, + "35": { + "name": "SChip_Fault", + "description": "Chip error (DC)" + }, + "36": { + "name": "MChip_Fault", + "description": "Chip error (AC)" + }, + "37": { + "name": "HwAuxPowerFault", + "description": "Auxiliary power error" + }, + "38": { + "name": "InverterSoftStartFail" + }, + "41": { + "name": "RelayFail", + "description": "Relay detection failure" + }, + "42": { + "name": "IsoFault", + "description": "Low insulation impedance", + "solution": "Check the insulation resistance between the photovoltaic array and ground. If there is a short circuit, repair the fault in time." + }, + "43": { + "name": "PEConnectFault", + "description": "Ground fault", + "solution": "Check AC output PE wire for grounding." + }, + "44": { + "name": "PvConfigError", + "description": "Error setting input mode", + "solution": "Check the PV input mode (parallel/independent). If incorrect, change the PV input mode." + }, + "45": { + "name": "CTDisconnect", + "description": "CT error", + "solution": "Check whether the CT wiring is correct." + }, + "46": { + "name": "ReversalConnection" + }, + "47": { + "name": "ParallelFault" + }, + "48": { + "name": "SNTypeFault" + }, + "49": { + "name": "TempFault_Bat", + "description": "Battery temperature protection", + "solution": "Ensure inverter is installed in a cool, ventilated place, not in direct sunlight, and ambient temperature is below inverter limits." + }, + "50": { + "name": "TempFault_HeatSink1", + "description": "Radiator 1 temperature protection" + }, + "51": { + "name": "TempFault_HeatSink2", + "description": "Radiator 2 temperature protection" + }, + "52": { + "name": "TempFault_HeatSink3", + "description": "Radiator 3 temperature protection" + }, + "53": { + "name": "TempFault_HeatSink4", + "description": "Radiator 4 temperature protection" + }, + "54": { + "name": "TempFault_HeatSink5", + "description": "Radiator 5 temperature protection" + }, + "55": { + "name": "TempFault_HeatSink6", + "description": "Radiator 6 temperature protection" + }, + "57": { + "name": "TempFault_Env1", + "description": "Ambient temperature 1 protection" + }, + "58": { + "name": "TempFault_Env2", + "description": "Ambient temperature 2 protection" + }, + "59": { + "name": "TempFault_Inv1", + "description": "Module 1 temperature protection" + }, + "60": { + "name": "TempFault_Inv2", + "description": "Module 2 temperature protection" + }, + "61": { + "name": "TempFault_Inv3", + "description": "Module 3 temperature protection" + }, + "65": { + "name": "VbusRmsUnbalance", + "description": "Unbalanced bus voltage RMS", + "solution": "Internal fault. Switch OFF inverter, wait 5 minutes, then switch ON. If unresolved, contact technical support." + }, + "66": { + "name": "VbusInstantUnbalance", + "description": "Transient value of bus voltage unbalanced", + "solution": "Internal fault. Switch OFF inverter, wait 5 minutes, then switch ON. If unresolved, contact technical support." + }, + "67": { + "name": "BusUVP", + "description": "Busbar undervoltage during grid-connection" + }, + "68": { + "name": "BusZVP", + "description": "Bus voltage low" + }, + "69": { + "name": "PVOVP", + "description": "PV over-voltage", + "solution": "Check whether PV series voltage (Voc) exceeds inverter max input voltage. Reduce number of modules in series if needed." + }, + "70": { + "name": "BatOVP", + "description": "Battery over-voltage", + "solution": "Check whether the battery overvoltage setting matches battery specifications." + }, + "71": { + "name": "LLCBusOVP", + "description": "LLC BUS overvoltage protection", + "solution": "Internal fault. Switch OFF inverter, wait 5 minutes, then switch ON. If unresolved, contact technical support." + }, + "72": { + "name": "SwBusRmsOVP", + "description": "Inverter bus voltage RMS software overvoltage" + }, + "73": { + "name": "SwBusInstantOVP", + "description": "Inverter bus voltage instantaneous value software overvoltage" + }, + "74": { + "name": "FlyingCapOVP" + }, + "81": { + "name": "SwBatOCP", + "description": "Battery overcurrent software protection" + }, + "82": { + "name": "DciOCP", + "description": "DCI overcurrent protection" + }, + "83": { + "name": "SwOCPInstant", + "description": "Output instantaneous current protection" + }, + "84": { + "name": "SwBuckBoostOCP", + "description": "BuckBoost software overflow" + }, + "85": { + "name": "SwAcRmsOCP", + "description": "Output effective value current protection" + }, + "86": { + "name": "SwPvOCPInstant", + "description": "PV overcurrent software protection" + }, + "87": { + "name": "IpvUnbalance", + "description": "PV flows in uneven parallel" + }, + "88": { + "name": "IacUnbalance", + "description": "Unbalanced output current" + }, + "89": { + "name": "SwPvOCP" + }, + "97": { + "name": "HwLLCBusOVP", + "description": "LLC bus hardware overvoltage" + }, + "98": { + "name": "HwBusOVP", + "description": "Inverter bus hardware overvoltage" + }, + "99": { + "name": "HwBuckBoostOCP", + "description": "BuckBoost hardware overflows" + }, + "100": { + "name": "HwBatOCP", + "description": "Battery hardware overflows" + }, + "102": { + "name": "HwPVOCP", + "description": "PV hardware overflows" + }, + "103": { + "name": "HwACOCP", + "description": "AC output hardware overflows" + }, + "110": { + "name": "Overload1", + "description": "Overload protection 1", + "solution": "Check whether the inverter is operating under overload." + }, + "111": { + "name": "Overload2", + "description": "Overload protection 2" + }, + "112": { + "name": "Overload3", + "description": "Overload protection 3" + }, + "113": { + "name": "OverTempDerating", + "description": "Internal temperature is too high", + "solution": "Ensure inverter is installed in a cool, ventilated place, not in direct sunlight, and ambient temperature is below limits." + }, + "114": { + "name": "FreqDerating", + "description": "AC frequency is too high", + "solution": "Ensure grid frequency and voltage are within acceptable range." + }, + "115": { + "name": "FreqLoading", + "description": "AC frequency is too low" + }, + "116": { + "name": "VoltDerating", + "description": "AC voltage is too high" + }, + "117": { + "name": "VoltLoading", + "description": "AC voltage is too low" + }, + "124": { + "name": "BatLowVoltageAlarm", + "description": "Battery low voltage protection", + "solution": "Check whether the battery voltage of the inverter is too low." + }, + "125": { + "name": "BatLowVoltageShut", + "description": "Battery low voltage shutdown" + }, + "129": { + "name": "unrecoverHwAcOCP", + "description": "Output hardware overcurrent permanent failure", + "solution": "Internal fault. Switch OFF inverter, wait 5 minutes, then switch ON. If unresolved, contact technical support." + }, + "130": { + "name": "unrecoverBusOVP", + "description": "Permanent Bus overvoltage failure" + }, + "131": { + "name": "unrecoverHwBusOVP", + "description": "Permanent Bus hardware overvoltage failure" + }, + "132": { + "name": "unrecoverIpvUnbalance", + "description": "PV uneven flow permanent failure" + }, + "133": { + "name": "unrecoverEPSBatOCP", + "description": "Permanent battery overcurrent failure in EPS mode" + }, + "134": { + "name": "unrecoverAcOCPInstant", + "description": "Output transient overcurrent permanent failure" + }, + "135": { + "name": "unrecoverIacUnbalance", + "description": "Permanent failure of unbalanced output current" + }, + "137": { + "name": "unrecoverPvConfigError", + "description": "Input mode setting error permanent failure", + "solution": "Check the PV input mode (parallel/independent). If incorrect, change the PV input mode." + }, + "138": { + "name": "unrecoverPVOCPInstant", + "description": "Input overcurrent permanent fault" + }, + "139": { + "name": "unrecoverHwPVOCP", + "description": "Input hardware overcurrent permanent failure" + }, + "140": { + "name": "unrecoverRelayFail", + "description": "Permanent relay failure" + }, + "141": { + "name": "unrecoverVbusUnbalance", + "description": "Bus voltage unbalanced permanent failure" + }, + "145": { + "name": "USBFault", + "description": "USB fault", + "solution": "Check the USB port of the inverter." + }, + "146": { + "name": "WifiFault", + "description": "Wifi fault", + "solution": "Check the Wifi port of the inverter." + }, + "147": { + "name": "BluetoothFault", + "description": "Bluetooth fault", + "solution": "Check the Bluetooth connection of the inverter." + }, + "148": { + "name": "RTCFault", + "description": "RTC clock failure", + "solution": "Internal fault. Switch OFF inverter, wait 5 minutes, then switch ON. If unresolved, contact technical support." + }, + "149": { + "name": "CommEEPROMFault", + "description": "Communication board EEPROM error", + "solution": "Internal fault. Switch OFF inverter, wait 5 minutes, then switch ON. If unresolved, contact technical support." + }, + "150": { + "name": "FlashFault", + "description": "Communication board FLASH error" + }, + "151": { + "name": "FlashFault", + "description": "Communication board FLASH error" + }, + "152": { + "name": "SafetyVerFault" + }, + "153": { + "name": "SciCommLose(DC)", + "description": "SCI communication error (DC)" + }, + "154": { + "name": "SciCommLose(AC)", + "description": "SCI communication error (AC)" + }, + "155": { + "name": "SciCommLose(Fuse)", + "description": "SCI communication error (Fuse)" + }, + "156": { + "name": "SoftVerError", + "description": "Inconsistent software versions", + "solution": "Contact technical support for software upgrades." + }, + "157": { + "name": "BMS1CommunicatonFault", + "description": "Communication failure of lithium battery", + "solution": "Ensure battery is compatible with inverter. CAN communication recommended. Check communication line or port for faults." + }, + "158": { + "name": "BMS2CommunicatonFault" + }, + "159": { + "name": "BMS3CommunicatonFault" + }, + "160": { + "name": "BMS4CommunicatonFault" + }, + "161": { + "name": "ForceShutdown", + "description": "Force shutdown", + "solution": "The inverter has performed a forced shutdown." + }, + "162": { + "name": "RemoteShutdown", + "description": "Remote shutdown", + "solution": "The inverter has performed a remote shutdown." + }, + "163": { + "name": "Drms0Shutdown", + "description": "Drms0 shutdown", + "solution": "The inverter has performed a Drms0 shutdown." + }, + "165": { + "name": "RemoteDerating", + "description": "Remote derating", + "solution": "The inverter is performing remote load reduction." + }, + "166": { + "name": "LogicInterfaceDerating", + "description": "Logic interface derating", + "solution": "The inverter is derated by the execution logic interface." + }, + "167": { + "name": "AlarmAntiRefluxing", + "description": "Anti reflux derating", + "solution": "The inverter is preventing countercurrent load drop." + }, + "169": { + "name": "FanFault1" + }, + "170": { + "name": "FanFault2" + }, + "171": { + "name": "FanFault3" + }, + "172": { + "name": "FanFault4" + }, + "173": { + "name": "FanFault5" + }, + "174": { + "name": "FanFault6" + }, + "176": { + "name": "MeterCommLose" + }, + "177": { + "name": "BMS OVP", + "description": "BMS over-voltage alarm", + "solution": "Internal battery failure. Close inverter and battery, wait 5 minutes, then restart. If unresolved, contact technical support." + }, + "178": { + "name": "BMS UVP", + "description": "BMS under-voltage alarm" + }, + "179": { + "name": "BMS OTP", + "description": "BMS high temperature warning" + }, + "180": { + "name": "BMS UTP", + "description": "BMS low temperature alarm" + }, + "181": { + "name": "BMS OCP", + "description": "Warning of overload in charge and discharge of BMS" + }, + "182": { + "name": "BMS Short", + "description": "BMS short circuit alarm", + "solution": "Maintenance required." + }, + "183": { + "name": "StringFuse_Fault0" + }, + "184": { + "name": "StringFuse_Fault1" + }, + "185": { + "name": "StringFuse_Fault2" + }, + "186": { + "name": "StringFuse_Fault3" + }, + "187": { + "name": "StringFuse_Fault4" + }, + "188": { + "name": "StringFuse_Fault5" + }, + "189": { + "name": "StringFuse_Fault6" + }, + "190": { + "name": "StringFuse_Fault7" + }, + "191": { + "name": "StringFuse_Fault8" + }, + "192": { + "name": "StringFuse_Fault9" + }, + "193": { + "name": "StringFuse_Fault10" + }, + "194": { + "name": "StringFuse_Fault11" + }, + "195": { + "name": "StringFuse_Fault12" + }, + "196": { + "name": "StringFuse_Fault13" + }, + "197": { + "name": "StringFuse_Fault14" + }, + "198": { + "name": "StringFuse_Fault15" + }, + "199": { + "name": "StringFuse_Fault16" + }, + "200": { + "name": "StringFuse_Fault17" + }, + "201": { + "name": "StringFuse_Fault18" + }, + "202": { + "name": "StringFuse_Fault19" + }, + "203": { + "name": "StringFuse_Fault20" + }, + "204": { + "name": "StringFuse_Fault21" + }, + "205": { + "name": "StringFuse_Fault22" + }, + "206": { + "name": "StringFuse_Fault23" + }, + "207": { + "name": "StringFuse_Fault24" + }, + "208": { + "name": "StringFuse_Fault25" + }, + "209": { + "name": "StringFuse_Fault26" + }, + "210": { + "name": "StringFuse_Fault27" + }, + "211": { + "name": "StringFuse_Fault28" + }, + "212": { + "name": "StringFuse_Fault29" + }, + "213": { + "name": "StringFuse_Fault30" + }, + "214": { + "name": "StringFuse_Fault31" + }, + "215": { + "name": "InputFuse_Fault0" + }, + "216": { + "name": "InputFuse_Fault1" + }, + "217": { + "name": "InputFuse_Fault2" + }, + "218": { + "name": "InputFuse_Fault3" + }, + "219": { + "name": "InputFuse_Fault4" + }, + "220": { + "name": "InputFuse_Fault5" + }, + "221": { + "name": "InputFuse_Fault6" + }, + "222": { + "name": "InputFuse_Fault7" + }, + "223": { + "name": "InputFuse_Fault8" + }, + "224": { + "name": "InputFuse_Fault9" + }, + "225": { + "name": "InputFuse_Fault10" + }, + "226": { + "name": "InputFuse_Fault11" + }, + "227": { + "name": "InputFuse_Fault12" + }, + "228": { + "name": "InputFuse_Fault13" + }, + "229": { + "name": "InputFuse_Fault14" + }, + "230": { + "name": "InputFuse_Fault15" + }, + "231": { + "name": "CombinerOverVoltageGroup1" + }, + "232": { + "name": "CombinerOverVoltageGroup2" + }, + "233": { + "name": "CombinerOverVoltageGroup3" + }, + "234": { + "name": "CombinerOverVoltageGroup4" + }, + "235": { + "name": "CombinerOverVoltageGroup5" + }, + "236": { + "name": "CombinerOverVoltageGroup6" + }, + "237": { + "name": "CombinerOverVoltageGroup7" + }, + "238": { + "name": "CombinerOverVoltageGroup8" + }, + "239": { + "name": "CombinerOverVoltageGroup9" + }, + "240": { + "name": "CombinerOverVoltageGroup10" + }, + "241": { + "name": "CombinerOverVoltageGroup11" + }, + "242": { + "name": "CombinerOverVoltageGroup12" + }, + "243": { + "name": "CombinerOverVoltageGroup13" + }, + "244": { + "name": "CombinerOverVoltageGroup14" + }, + "245": { + "name": "CombinerOverVoltageGroup15" + }, + "246": { + "name": "CombinerOverVoltageGroup16" + }, + "247": { + "name": "CombinerUnderVoltageGroup1" + }, + "248": { + "name": "CombinerUnderVoltageGroup2" + }, + "249": { + "name": "CombinerUnderVoltageGroup3" + }, + "250": { + "name": "CombinerUnderVoltageGroup4" + }, + "251": { + "name": "CombinerUnderVoltageGroup5" + }, + "252": { + "name": "CombinerUnderVoltageGroup6" + }, + "253": { + "name": "CombinerUnderVoltageGroup7" + }, + "254": { + "name": "CombinerUnderVoltageGroup8" + }, + "255": { + "name": "CombinerUnderVoltageGroup9" + }, + "256": { + "name": "CombinerUnderVoltageGroup10" + }, + "257": { + "name": "CombinerUnderVoltageGroup11" + }, + "258": { + "name": "CombinerUnderVoltageGroup12" + }, + "259": { + "name": "CombinerUnderVoltageGroup13" + }, + "260": { + "name": "CombinerUnderVoltageGroup14" + }, + "261": { + "name": "CombinerUnderVoltageGroup15" + }, + "262": { + "name": "CombinerUnderVoltageGroup16" + }, + "263": { + "name": "CombinerOverCurrent1" + }, + "264": { + "name": "CombinerOverCurrent2" + }, + "265": { + "name": "CombinerOverCurrent3" + }, + "266": { + "name": "CombinerOverCurrent4" + }, + "267": { + "name": "CombinerOverCurrent5" + }, + "268": { + "name": "CombinerOverCurrent6" + }, + "269": { + "name": "CombinerOverCurrent7" + }, + "270": { + "name": "CombinerOverCurrent8" + }, + "271": { + "name": "CombinerOverCurrent9" + }, + "272": { + "name": "CombinerOverCurrent10" + }, + "273": { + "name": "CombinerOverCurrent11" + }, + "274": { + "name": "CombinerOverCurrent12" + }, + "275": { + "name": "CombinerOverCurrent13" + }, + "276": { + "name": "CombinerOverCurrent14" + }, + "277": { + "name": "CombinerOverCurrent15" + }, + "278": { + "name": "CombinerOverCurrent16" + }, + "279": { + "name": "CombinerOverCurrent17" + }, + "280": { + "name": "CombinerOverCurrent18" + }, + "281": { + "name": "CombinerOverCurrent19" + }, + "282": { + "name": "CombinerOverCurrent20" + }, + "283": { + "name": "CombinerOverCurrent21" + }, + "284": { + "name": "CombinerOverCurrent22" + }, + "285": { + "name": "CombinerOverCurrent23" + }, + "286": { + "name": "CombinerOverCurrent24" + }, + "287": { + "name": "CombinerOverCurrent25" + }, + "288": { + "name": "CombinerOverCurrent26" + }, + "289": { + "name": "CombinerOverCurrent27" + }, + "290": { + "name": "CombinerOverCurrent28" + }, + "291": { + "name": "CombinerOverCurrent29" + }, + "292": { + "name": "CombinerOverCurrent30" + }, + "293": { + "name": "CombinerOverCurrent31" + }, + "294": { + "name": "CombinerOverCurrent32" + }, + "301": { + "name": "CombinerOverCurrent29" + }, + "302": { + "name": "CombinerOverCurrent30" + }, + "303": { + "name": "CombinerOverCurrent31" + }, + "304": { + "name": "CombinerOverCurrent32" + }, + "337": { + "name": "CombinerRefluxFault1" + }, + "338": { + "name": "CombinerRefluxFault2" + }, + "339": { + "name": "CombinerRefluxFault3" + }, + "340": { + "name": "CombinerRefluxFault4" + }, + "341": { + "name": "CombinerRefluxFault5" + }, + "342": { + "name": "CombinerRefluxFault6" + }, + "343": { + "name": "CombinerRefluxFault7" + }, + "344": { + "name": "CombinerRefluxFault8" + }, + "345": { + "name": "CombinerRefluxFault9" + }, + "346": { + "name": "CombinerRefluxFault10" + }, + "347": { + "name": "CombinerRefluxFault11" + }, + "348": { + "name": "CombinerRefluxFault12" + }, + "349": { + "name": "CombinerRefluxFault13" + }, + "350": { + "name": "CombinerRefluxFault14" + }, + "351": { + "name": "CombinerRefluxFault15" + }, + "352": { + "name": "CombinerRefluxFault16" + }, + "353": { + "name": "CombinerRefluxFault17" + }, + "354": { + "name": "CombinerRefluxFault18" + }, + "355": { + "name": "CombinerRefluxFault19" + }, + "356": { + "name": "CombinerRefluxFault20" + }, + "357": { + "name": "CombinerRefluxFault21" + }, + "358": { + "name": "CombinerRefluxFault22" + }, + "359": { + "name": "CombinerRefluxFault23" + }, + "360": { + "name": "CombinerRefluxFault24" + }, + "361": { + "name": "CombinerRefluxFault25" + }, + "362": { + "name": "CombinerRefluxFault26" + }, + "363": { + "name": "CombinerRefluxFault27" + }, + "364": { + "name": "CombinerRefluxFault28" + }, + "365": { + "name": "CombinerRefluxFault29" + }, + "366": { + "name": "CombinerRefluxFault30" + }, + "367": { + "name": "CombinerRefluxFault31" + }, + "368": { + "name": "CombinerRefluxFault32" + }, + "401": { + "name": "AFCI0" + }, + "402": { + "name": "AFCI1" + }, + "403": { + "name": "AFCI2" + }, + "404": { + "name": "AFCI3" + }, + "405": { + "name": "AFCI4" + }, + "406": { + "name": "AFCI5" + }, + "407": { + "name": "AFCI6" + }, + "408": { + "name": "AFCI7" + }, + "409": { + "name": "AFCI8" + }, + "410": { + "name": "AFCI9" + }, + "411": { + "name": "AFCI10" + }, + "412": { + "name": "AFCI11" + }, + "413": { + "name": "AFCI12" + }, + "414": { + "name": "AFCI13" + }, + "415": { + "name": "AFCI14" + }, + "416": { + "name": "AFCI15" + }, + "417": { + "name": "AFCI16" + }, + "418": { + "name": "AFCI17" + }, + "419": { + "name": "AFCI18" + }, + "420": { + "name": "AFCI19" + }, + "421": { + "name": "AFCI20" + }, + "422": { + "name": "AFCI21" + }, + "423": { + "name": "AFCI22" + }, + "424": { + "name": "AFCI23" + }, + "425": { + "name": "AFCI24" + }, + "426": { + "name": "AFCI25" + }, + "427": { + "name": "AFCI26" + }, + "428": { + "name": "AFCI27" + }, + "429": { + "name": "AFCI28" + }, + "430": { + "name": "AFCI29" + }, + "431": { + "name": "AFCI30" + }, + "432": { + "name": "AFCI31" + } } } \ No newline at end of file diff --git a/sofar2mqtt-v2.py b/sofar2mqtt-v2.py index 8fc0d20..832b96a 100644 --- a/sofar2mqtt-v2.py +++ b/sofar2mqtt-v2.py @@ -1012,6 +1012,15 @@ def read_event_timestamp(self, return timestamp + def format_history_event(self, raw_value): + entry = self.config.get('error_codes', {}).get(str(raw_value), {}) + name = entry.get("name", "") + description = entry.get("description", "") + + if name and description: + return f"{name} – {description}" + return name or str(raw_value) + def translate_from_raw_value(self, register, raw_value): """ Translate raw value to a normalized value using the function and factor """ @@ -1023,7 +1032,7 @@ def translate_from_raw_value(self, register, raw_value): elif register['function'] == 'mode': return register['modes'].get(str(raw_value), raw_value) elif register['function'] == 'history_event_map': - return self.config.get('error_codes', {}).get(str(raw_value), raw_value) + return self.format_history_event(raw_value) elif register['function'] == 'bit_field': length = len(register['fields']) fields = []