diff --git a/SOFAR-HYD-3PH-AND-G3.json b/SOFAR-HYD-3PH-AND-G3.json index 609a2a4..386fa84 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,10 +44,35 @@ "rs485_config_stop_bit", "rs485_config_parity_bit" ], - "append": [1] + "append": [ + 1 + ] } ], "registers": [ + { + "name": "serial_number", + "register": "0x0445", + "read_type": "string", + "registers": 7, + "ha": { + "name": "Serial Number", + "icon": "mdi:identifier", + "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", @@ -54,7 +81,6 @@ "ha": { "name": "HW Version", "icon": "mdi:numeric", - "entity_category": "diagnostic", "value_template": "{{ value_json.hw_version }}" }, "refresh": 86400 @@ -67,7 +93,6 @@ "ha": { "name": "Software Version", "icon": "mdi:numeric", - "entity_category": "diagnostic", "value_template": "{{ value_json.sw_version_com }}" }, "refresh": 86400 @@ -80,7 +105,6 @@ "ha": { "name": "DSP1 Software Version", "icon": "mdi:numeric", - "entity_category": "diagnostic", "value_template": "{{ value_json.sw_version_master }}" }, "refresh": 86400 @@ -93,7 +117,6 @@ "ha": { "name": "DSP2 Software Version", "icon": "mdi:numeric", - "entity_category": "diagnostic", "value_template": "{{ value_json.sw_version_slave }}" }, "refresh": 86400 @@ -659,7 +682,6 @@ "ha": { "name": "Time of use", "icon": "mdi:home-clock", - "entity_category": "diagnostic", "value_template": "{{ value_json.time_of_use }}" }, "refresh": 3600 @@ -672,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 @@ -685,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 @@ -701,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", @@ -719,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 @@ -732,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 @@ -745,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 @@ -766,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 @@ -779,8 +794,10 @@ "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, "min": -999999, "max": 999999, "write": true, @@ -836,10 +853,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", @@ -851,7 +946,6 @@ "name": "PV1 Voltage", "icon": "mdi:alpha-v-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_1_voltage }}" } }, @@ -866,7 +960,6 @@ "name": "PV1 Current", "icon": "mdi:alpha-a-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_1_current }}" } }, @@ -881,7 +974,6 @@ "name": "PV1 Power", "icon": "mdi:solar-power", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_1_power }}" } }, @@ -896,7 +988,6 @@ "name": "PV2 Voltage", "icon": "mdi:alpha-v-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_2_voltage }}" } }, @@ -911,7 +1002,6 @@ "name": "PV2 Current", "icon": "mdi:alpha-a-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_2_current }}" } }, @@ -926,7 +1016,6 @@ "name": "PV2 Power", "icon": "mdi:solar-power", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_2_power }}" } }, @@ -945,7 +1034,6 @@ "name": "PV Total Power", "icon": "mdi:solar-power", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.pv_total_power }}" } }, @@ -961,7 +1049,6 @@ "icon": "mdi:lightning-bolt", "state_class": "measurement", "unit_of_measurement": "W", - "entity_category": "diagnostic", "value_template": "{{ value_json.active_power }}" } }, @@ -976,7 +1063,6 @@ "icon": "mdi:home-lightning-bolt-outline", "state_class": "measurement", "unit_of_measurement": "W", - "entity_category": "diagnostic", "value_template": "{{ value_json.load_power }}" } }, @@ -992,7 +1078,6 @@ "icon": "mdi:lightning-bolt", "state_class": "measurement", "unit_of_measurement": "W", - "entity_category": "diagnostic", "value_template": "{{ value_json.grid }}" } }, @@ -1003,7 +1088,6 @@ "name": "Insulation resistance", "icon": "mdi:omega", "unit_of_measurement": "Ohms", - "entity_category": "diagnostic", "value_template": "{{ value_json.insulation_resistance }}" }, "refresh": 3600 @@ -1019,7 +1103,6 @@ "icon": "mdi:sine-wave", "state_class": "measurement", "unit_of_measurement": "Hz", - "entity_category": "diagnostic", "value_template": "{{ value_json.ongrid_frequency }}" }, "refresh": 60 @@ -1035,7 +1118,6 @@ "name": "On-grid Voltage", "icon": "mdi:alpha-v-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.ongrid_voltage }}" }, "refresh": 60 @@ -1049,7 +1131,6 @@ "name": "Internal Temperature", "icon": "mdi:temperature-celsius", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.inverter_temp_internal }}" } }, @@ -1062,7 +1143,6 @@ "name": "Heatsink Temperature", "icon": "mdi:temperature-celsius", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.inverter_temp_heatsink }}" } }, @@ -1077,7 +1157,6 @@ "name": "Off-Grid Power", "icon": "mdi:lightning-bolt", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.offgrid_active_power }}" } }, @@ -1092,7 +1171,6 @@ "name": "Off-Grid Frequency", "icon": "mdi:sine-wave", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.offgrid_frequency }}" }, "refresh": 60 @@ -1108,7 +1186,6 @@ "name": "Off-Grid Voltage", "icon": "mdi:alpha-v-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.offgrid_voltage }}" }, "refresh": 60 @@ -1125,7 +1202,6 @@ "name": "Battery Current", "icon": "mdi:alpha-a-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_current }}" } }, @@ -1141,7 +1217,6 @@ "name": "Battery Power", "icon": "mdi:battery-charging", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_power }}" } }, @@ -1156,7 +1231,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 @@ -1172,7 +1246,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 @@ -1188,7 +1261,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 @@ -1204,7 +1276,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 @@ -1220,7 +1291,6 @@ "name": "Import Today", "icon": "mdi:transmission-tower-import", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.today_import }}" }, "refresh": 60 @@ -1236,7 +1306,6 @@ "name": "Import Total", "icon": "mdi:transmission-tower-import", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.total_import }}" }, "refresh": 3600 @@ -1252,7 +1321,6 @@ "name": "Export Today", "icon": "mdi:transmission-tower-export", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.today_export }}" }, "refresh": 60 @@ -1268,7 +1336,6 @@ "name": "Export Total", "icon": "mdi:transmission-tower-export", "state_class": "total_increasing", - "entity_category": "diagnostic", "value_template": "{{ value_json.total_export }}" }, "refresh": 3600 @@ -1284,7 +1351,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 @@ -1300,7 +1366,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 @@ -1316,7 +1381,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 @@ -1332,7 +1396,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 @@ -1348,7 +1411,6 @@ "name": "Battery Voltage", "icon": "mdi:alpha-v-box", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_voltage }}" } }, @@ -1362,7 +1424,6 @@ "name": "Battery Temperature", "icon": "mdi:temperature-celsius", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_temp }}" } }, @@ -1375,7 +1436,6 @@ "name": "Battery SOC", "icon": "mdi:battery-80", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_soc }}" }, "min": 0, @@ -1390,7 +1450,6 @@ "name": "Battery SOH", "icon": "mdi:battery-heart-variant", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_soh }}" }, "refresh": 3600 @@ -1404,7 +1463,6 @@ "name": "Battery Cycles", "icon": "mdi:battery-sync", "state_class": "measurement", - "entity_category": "diagnostic", "value_template": "{{ value_json.battery_cycles }}" }, "refresh": 3600 @@ -1589,5 +1647,1092 @@ "untested": true, "refresh": 86400 } - ] + ], + "error_codes": { + "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/SOFAR-HYD-ES-AND-ME3000-SP.json b/SOFAR-HYD-ES-AND-ME3000-SP.json index b187e12..fa9114d 100644 --- a/SOFAR-HYD-ES-AND-ME3000-SP.json +++ b/SOFAR-HYD-ES-AND-ME3000-SP.json @@ -1,5 +1,83 @@ { "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", + "register": "0x020d", + "desc": "Charge/Discharge power (-3000 to 3000W) - used with Charge/Discharge modes", + "type": "I16", + "function": "int", + "min": -3000, + "max": 3000, + "signed": true, + "passive": true, + "write": true, + "write_functioncode": "42", + "notify_on_change": true, + "write_addresses": { + "standby": "0x0100", + "discharge": "0x0101", + "charge": "0x0102", + "auto": "0x0103" + }, + "write_values": { + "standby": 21845, + "auto": 21845 + }, + "ha": { + "command_topic": "sofar/rw/charge_discharge_power", + "entity_category": "config", + "name": "Charge/Discharge Power", + "min": -3000, + "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", @@ -9,105 +87,615 @@ "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 + "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": "battery_power", - "register": "0x20d", - "function": "multiply", - "factor": 10 + "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": "grid_freq", "register": "0x20c", "function": "divide", - "factor": 100 + "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" + "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 + "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_cycles", - "register": "0x22c" + "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" + "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 + "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 + "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 + "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 + "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 + "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 + "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 + "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" + "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" + "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 + "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 + "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 31302d1..832b96a 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") + 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) - 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']: @@ -189,6 +198,29 @@ 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 = 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: + 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 + 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 +238,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', []): @@ -246,25 +282,37 @@ 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 + 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 - 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 @@ -276,15 +324,24 @@ def update_state(self): continue raw_value = None logging.debug('Reading %s', register['name']) + + 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) - else: - 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('register', False): + 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 + #raw_value = self.aggregate_datetime_bitmap(register) if raw_value is None: logging.error(f"Value for {register['name']}: is none") continue @@ -293,12 +350,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: @@ -462,8 +521,30 @@ 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}({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): - """ Read value from register with a retry mechanism """ + """ Write value to register """ with self.mutex: retry = self.write_retry logging.info( @@ -598,7 +679,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 @@ -612,55 +695,67 @@ def is_valid_serial_number(self, serial_number): return serial_number[0] == 'S' and serial_number[1:].isalnum() return False - def determine_serial_number(self): - """ Determine the serial number from the inverter """ - serial_number = None - - # Try first location: 0x2001 ... 0x2007 + def read_ascii(self, start, count): 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 + regs = self.instrument.read_registers(start, count, functioncode=3) except Exception as e: - logging.info( - f"Failed to validate serial number from first location: {str(e)}") + logging.debug(f"Error reading registers at {hex(start)}: {e}") + return None - # 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)}") + chars = [] + for val in regs: + hi = (val >> 8) & 0xFF + lo = val & 0xFF - # 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)}") + # 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.""" + + # 1) First location: 0x2001–0x2007 (14 chars) + 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 = 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 = 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 + 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') + serial_number = self.raw_data.get('serial_number') model = None if len(serial_number) == 14: code = serial_number[1:3] @@ -763,7 +858,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: @@ -868,6 +963,64 @@ 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 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 """ @@ -878,6 +1031,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.format_history_event(raw_value) elif register['function'] == 'bit_field': length = len(register['fields']) fields = []