From ef6ce72d310b183f919f2b4ca38cfa8a7c4a9524 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:05:35 -0700 Subject: [PATCH 01/34] Add a class for EV charger --- .../solaredge_modbus_multi/hub.py | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 8323dc3c..112bffde 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -55,6 +55,12 @@ class DeviceInitFailed(SolarEdgeException): pass +class DeviceIsCharger(SolarEdgeException): + """Raised when an inverter device matches a charger model""" + + pass + + class ModbusReadError(SolarEdgeException): """Raised when a modbus read fails (generic)""" @@ -1867,3 +1873,183 @@ def battery_rating_adjust(self) -> int: @property def battery_energy_reset_cycles(self) -> int: return self.hub.battery_energy_reset_cycles + + +class SolarEdgeCharger: + """Class that defines a SolarEdge EV Charger.""" + + def __init__(self, device_id: int, hub: SolarEdgeModbusMultiHub) -> None: + self.charger_unit_id = device_id + self.hub = hub + self.decoded_common = [] + self.decoded_model = [] + self.has_parent = False + + async def init_device(self) -> None: + """Set up data about the device from modbus.""" + + try: + charger_data = await self.hub.modbus_read_holding_registers( + unit=self.charger_unit_id, address=40000, rcount=69 + ) + + decoder = BinaryPayloadDecoder.fromRegisters( + charger_data.registers, byteorder=Endian.BIG + ) + + self.decoded_common = OrderedDict( + [ + ("C_SunSpec_ID", decoder.decode_32bit_uint()), + ("C_SunSpec_DID", decoder.decode_16bit_uint()), + ("C_SunSpec_Length", decoder.decode_16bit_uint()), + ( + "C_Manufacturer", + parse_modbus_string(decoder.decode_string(32)), + ), + ("C_Model", parse_modbus_string(decoder.decode_string(32))), + ("C_Option", parse_modbus_string(decoder.decode_string(16))), + ("C_Version", parse_modbus_string(decoder.decode_string(16))), + ( + "C_SerialNumber", + parse_modbus_string(decoder.decode_string(32)), + ), + ("C_Device_address", decoder.decode_16bit_uint()), + ] + ) + + for name, value in iter(self.decoded_common.items()): + _LOGGER.debug( + ( + f"C{self.charger_unit_id}: " + f"{name} {hex(value) if isinstance(value, int) else value}" + f"{type(value)}" + ), + ) + + self.hub.inverter_common[self.charger_unit_id] = self.decoded_common + + except ModbusIOError: + raise DeviceInvalid(f"No response from charger ID {self.charger_unit_id}") + + except ModbusIllegalAddress: + raise DeviceInvalid(f"ID {self.charger_unit_id} is not SunSpec.") + + if ( + self.decoded_common["C_SunSpec_ID"] == SunSpecNotImpl.UINT32 + or self.decoded_common["C_SunSpec_DID"] == SunSpecNotImpl.UINT16 + or self.decoded_common["C_SunSpec_ID"] != 0x53756E53 + or self.decoded_common["C_SunSpec_DID"] != 0x0001 + or self.decoded_common["C_SunSpec_Length"] != 65 + ): + raise DeviceInvalid(f"ID {self.charger_unit_id} is not SunSpec.") + + self.manufacturer = self.decoded_common["C_Manufacturer"] + self.model = self.decoded_common["C_Model"] + self.option = self.decoded_common["C_Option"] + self.serial = self.decoded_common["C_SerialNumber"] + self.device_address = self.decoded_common["C_Device_address"] + self.name = f"{self.hub.hub_id.capitalize()} C{self.charger_unit_id}" + self.uid_base = f"{self.model}_{self.serial}" + + async def read_modbus_data(self) -> None: + """Read and update dynamic modbus registers.""" + + try: + charger_data = await self.hub.modbus_read_holding_registers( + unit=self.charger_unit_id, address=40044, rcount=16 + ) + + decoder = BinaryPayloadDecoder.fromRegisters( + charger_data.registers, byteorder=Endian.BIG + ) + + self.decoded_common["C_Version"] = parse_modbus_string( + decoder.decode_string(16) + ) + + charger_data = await self.hub.modbus_read_holding_registers( + unit=self.charger_unit_id, address=40069, rcount=40 + ) + + decoder = BinaryPayloadDecoder.fromRegisters( + charger_data.registers, byteorder=Endian.BIG + ) + + self.decoded_model = OrderedDict( + [ + ("C_SunSpec_DID", decoder.decode_16bit_uint()), + ("C_SunSpec_Length", decoder.decode_16bit_uint()), + ("AC_Current", decoder.decode_16bit_uint()), + ("AC_Current_A", decoder.decode_16bit_uint()), + ("AC_Current_B", decoder.decode_16bit_uint()), + ("AC_Current_C", decoder.decode_16bit_uint()), + ("AC_Current_SF", decoder.decode_16bit_int()), + ("AC_Voltage_AB", decoder.decode_16bit_uint()), + ("AC_Voltage_BC", decoder.decode_16bit_uint()), + ("AC_Voltage_CA", decoder.decode_16bit_uint()), + ("AC_Voltage_AN", decoder.decode_16bit_uint()), + ("AC_Voltage_BN", decoder.decode_16bit_uint()), + ("AC_Voltage_CN", decoder.decode_16bit_uint()), + ("AC_Voltage_SF", decoder.decode_16bit_int()), + ("AC_Power", decoder.decode_16bit_int()), + ("AC_Power_SF", decoder.decode_16bit_int()), + ("AC_Frequency", decoder.decode_16bit_uint()), + ("AC_Frequency_SF", decoder.decode_16bit_int()), + ("AC_VA", decoder.decode_16bit_int()), + ("AC_VA_SF", decoder.decode_16bit_int()), + ("AC_var", decoder.decode_16bit_int()), + ("AC_var_SF", decoder.decode_16bit_int()), + ("AC_PF", decoder.decode_16bit_int()), + ("AC_PF_SF", decoder.decode_16bit_int()), + ("AC_Energy_WH", decoder.decode_32bit_uint()), + ("AC_Energy_WH_SF", decoder.decode_16bit_uint()), + ("I_DC_Current", decoder.decode_16bit_uint()), + ("I_DC_Current_SF", decoder.decode_16bit_int()), + ("I_DC_Voltage", decoder.decode_16bit_uint()), + ("I_DC_Voltage_SF", decoder.decode_16bit_int()), + ("I_DC_Power", decoder.decode_16bit_int()), + ("I_DC_Power_SF", decoder.decode_16bit_int()), + ("I_Temp_Cab", decoder.decode_16bit_int()), + ("I_Temp_Sink", decoder.decode_16bit_int()), + ("I_Temp_Trns", decoder.decode_16bit_int()), + ("I_Temp_Other", decoder.decode_16bit_int()), + ("I_Temp_SF", decoder.decode_16bit_int()), + ("I_Status", decoder.decode_16bit_int()), + ("I_Status_Vendor", decoder.decode_16bit_int()), + ] + ) + + if ( + self.decoded_model["C_SunSpec_DID"] == SunSpecNotImpl.UINT16 + or self.decoded_model["C_SunSpec_DID"] not in [101, 102, 103] + or self.decoded_model["C_SunSpec_Length"] != 50 + ): + raise DeviceInvalid(f"Charger {self.charger_unit_id} not usable.") + + except ModbusIOError: + raise ModbusReadError(f"No response from charger ID {self.charger_unit_id}") + + @property + def online(self) -> bool: + """Device is online.""" + return self.hub.online + + @property + def fw_version(self) -> str | None: + if "C_Version" in self.decoded_common: + return self.decoded_common["C_Version"] + + return None + + @property + def device_info(self) -> DeviceInfo: + """Return the device info.""" + return DeviceInfo( + identifiers={(DOMAIN, self.uid_base)}, + name=self.name, + manufacturer=self.manufacturer, + model=self.model, + serial_number=self.serial, + sw_version=self.fw_version, + hw_version=self.option, + ) From 7838619913cd270de12875f1d0c016459e47d987 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 26 Apr 2026 11:55:01 -0700 Subject: [PATCH 02/34] Charger -> EVSE --- .../solaredge_modbus_multi/hub.py | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 656ad89c..5f1664c3 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -63,8 +63,8 @@ class DeviceInitFailed(SolarEdgeException): pass -class DeviceIsCharger(SolarEdgeException): - """Raised when an inverter device matches a charger model""" +class DeviceIsEVSE(SolarEdgeException): + """Raised when an inverter device matches a EVSE model""" pass @@ -2622,11 +2622,11 @@ def last_update(self) -> datetime.datetime | None: return self._last_update_timestamp -class SolarEdgeCharger: - """Class that defines a SolarEdge EV Charger.""" +class SolarEdgeEVSE: + """Class that defines a SolarEdge EVSE.""" def __init__(self, device_id: int, hub: SolarEdgeModbusMultiHub) -> None: - self.charger_unit_id = device_id + self.evse_unit_id = device_id self.hub = hub self.decoded_common = [] self.decoded_model = [] @@ -2636,12 +2636,12 @@ async def init_device(self) -> None: """Set up data about the device from modbus.""" try: - charger_data = await self.hub.modbus_read_holding_registers( - unit=self.charger_unit_id, address=40000, rcount=69 + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=40000, rcount=69 ) decoder = BinaryPayloadDecoder.fromRegisters( - charger_data.registers, byteorder=Endian.BIG + evse_data.registers, byteorder=Endian.BIG ) self.decoded_common = OrderedDict( @@ -2667,19 +2667,17 @@ async def init_device(self) -> None: for name, value in iter(self.decoded_common.items()): _LOGGER.debug( ( - f"C{self.charger_unit_id}: " + f"C{self.evse_unit_id}: " f"{name} {hex(value) if isinstance(value, int) else value}" f"{type(value)}" ), ) - self.hub.inverter_common[self.charger_unit_id] = self.decoded_common - except ModbusIOError: - raise DeviceInvalid(f"No response from charger ID {self.charger_unit_id}") + raise DeviceInvalid(f"No response from evse ID {self.evse_unit_id}") except ModbusIllegalAddress: - raise DeviceInvalid(f"ID {self.charger_unit_id} is not SunSpec.") + raise DeviceInvalid(f"ID {self.evse_unit_id} is not SunSpec.") if ( self.decoded_common["C_SunSpec_ID"] == SunSpecNotImpl.UINT32 @@ -2688,26 +2686,26 @@ async def init_device(self) -> None: or self.decoded_common["C_SunSpec_DID"] != 0x0001 or self.decoded_common["C_SunSpec_Length"] != 65 ): - raise DeviceInvalid(f"ID {self.charger_unit_id} is not SunSpec.") + raise DeviceInvalid(f"ID {self.evse_unit_id} is not SunSpec.") self.manufacturer = self.decoded_common["C_Manufacturer"] self.model = self.decoded_common["C_Model"] self.option = self.decoded_common["C_Option"] self.serial = self.decoded_common["C_SerialNumber"] self.device_address = self.decoded_common["C_Device_address"] - self.name = f"{self.hub.hub_id.capitalize()} C{self.charger_unit_id}" + self.name = f"{self.hub.hub_id.capitalize()} C{self.evse_unit_id}" self.uid_base = f"{self.model}_{self.serial}" async def read_modbus_data(self) -> None: """Read and update dynamic modbus registers.""" try: - charger_data = await self.hub.modbus_read_holding_registers( - unit=self.charger_unit_id, address=40044, rcount=16 + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=40044, rcount=16 ) decoder = BinaryPayloadDecoder.fromRegisters( - charger_data.registers, byteorder=Endian.BIG + evse_data.registers, byteorder=Endian.BIG ) self.decoded_common["C_Version"] = parse_modbus_string( @@ -2715,7 +2713,7 @@ async def read_modbus_data(self) -> None: ) except ModbusIOError: - raise ModbusReadError(f"No response from charger ID {self.charger_unit_id}") + raise ModbusReadError(f"No response from EVSE ID {self.evse_unit_id}") @property def online(self) -> bool: From a857eebb02edc5706ed3f4bd7f41cf46732dc518 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 26 Apr 2026 11:59:52 -0700 Subject: [PATCH 03/34] Update SolarEdgeEVSE to current methods --- .../solaredge_modbus_multi/hub.py | 105 ++++++++++++++---- 1 file changed, 81 insertions(+), 24 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 5f1664c3..eddbb757 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2640,30 +2640,88 @@ async def init_device(self) -> None: unit=self.evse_unit_id, address=40000, rcount=69 ) - decoder = BinaryPayloadDecoder.fromRegisters( - evse_data.registers, byteorder=Endian.BIG - ) - self.decoded_common = OrderedDict( [ - ("C_SunSpec_ID", decoder.decode_32bit_uint()), - ("C_SunSpec_DID", decoder.decode_16bit_uint()), - ("C_SunSpec_Length", decoder.decode_16bit_uint()), ( - "C_Manufacturer", - parse_modbus_string(decoder.decode_string(32)), - ), - ("C_Model", parse_modbus_string(decoder.decode_string(32))), - ("C_Option", parse_modbus_string(decoder.decode_string(16))), - ("C_Version", parse_modbus_string(decoder.decode_string(16))), - ( - "C_SerialNumber", - parse_modbus_string(decoder.decode_string(32)), - ), - ("C_Device_address", decoder.decode_16bit_uint()), + "C_SunSpec_ID", + ModbusClientMixin.convert_from_registers( + evse_data.registers[0:2], + data_type=ModbusClientMixin.DATATYPE.UINT32, + ), + ) ] ) + uint16_fields = [ + "C_SunSpec_DID", + "C_SunSpec_Length", + "C_Device_address", + ] + uint16_data = evse_data.registers[2:4] + [evse_data.registers[68]] + self.decoded_common.update( + OrderedDict( + zip( + uint16_fields, + ModbusClientMixin.convert_from_registers( + uint16_data, + data_type=ModbusClientMixin.DATATYPE.UINT16, + ), + ) + ) + ) + + self.decoded_common.update( + OrderedDict( + [ + ( + "C_Manufacturer", # string(32) + int_list_to_string( + ModbusClientMixin.convert_from_registers( + evse_data.registers[4:20], + data_type=ModbusClientMixin.DATATYPE.UINT16, + ) + ), + ), + ( + "C_Model", # string(32) + int_list_to_string( + ModbusClientMixin.convert_from_registers( + evse_data.registers[20:36], + data_type=ModbusClientMixin.DATATYPE.UINT16, + ) + ), + ), + ( + "C_Option", # string(16) + int_list_to_string( + ModbusClientMixin.convert_from_registers( + evse_data.registers[36:44], + data_type=ModbusClientMixin.DATATYPE.UINT16, + ) + ), + ), + ( + "C_Version", # string(16) + int_list_to_string( + ModbusClientMixin.convert_from_registers( + evse_data.registers[44:52], + data_type=ModbusClientMixin.DATATYPE.UINT16, + ) + ), + ), + ( + "C_SerialNumber", # string(32) + int_list_to_string( + ModbusClientMixin.convert_from_registers( + evse_data.registers[52:68], + data_type=ModbusClientMixin.DATATYPE.UINT16, + ) + ), + ), + ] + ) + ) + for name, value in iter(self.decoded_common.items()): _LOGGER.debug( ( @@ -2704,12 +2762,11 @@ async def read_modbus_data(self) -> None: unit=self.evse_unit_id, address=40044, rcount=16 ) - decoder = BinaryPayloadDecoder.fromRegisters( - evse_data.registers, byteorder=Endian.BIG - ) - - self.decoded_common["C_Version"] = parse_modbus_string( - decoder.decode_string(16) + self.decoded_common["C_Version"] = int_list_to_string( + ModbusClientMixin.convert_from_registers( + evse_data.registers[0:8], + data_type=ModbusClientMixin.DATATYPE.UINT16, + ) ) except ModbusIOError: From 8e9a972588c6dcff1fb09c28cd120e66f53e7889 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 26 Apr 2026 12:40:55 -0700 Subject: [PATCH 04/34] Add evse detection regex --- custom_components/solaredge_modbus_multi/const.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/custom_components/solaredge_modbus_multi/const.py b/custom_components/solaredge_modbus_multi/const.py index b1c28bc0..1d67513e 100644 --- a/custom_components/solaredge_modbus_multi/const.py +++ b/custom_components/solaredge_modbus_multi/const.py @@ -30,6 +30,11 @@ re.IGNORECASE, ) +DETECT_EVSE_REGEX = re.compile( + r"^(?:SE-EV-SA)", # Add additional prefixes with |OTHER-PREFIX + re.IGNORECASE, +) + class ModbusExceptions: """An enumeration of the valid modbus exceptions.""" From 5872194435938e68eda672e3f3a59520e3124559 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 26 Apr 2026 12:46:08 -0700 Subject: [PATCH 05/34] Raise DeviceIsEVSE if model matches known evse --- custom_components/solaredge_modbus_multi/hub.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index eddbb757..5936a7c0 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -26,6 +26,7 @@ from .const import ( BATTERY_REG_BASE, + DETECT_EVSE_REGEX, DOMAIN, METER_REG_BASE, PYMODBUS_REQUIRED_VERSION, @@ -961,6 +962,9 @@ async def init_device(self) -> None: f"ID {self.inverter_unit_id} is not a SunSpec inverter." ) + if DETECT_EVSE_REGEX.match(self.decoded_common["C_Model"]): + raise DeviceIsEVSE(f"Detected EVSE model: {self.decoded_common['C_Model']}") + if ( self.decoded_common["C_SunSpec_ID"] == SunSpecNotImpl.UINT32 or self.decoded_common["C_SunSpec_DID"] == SunSpecNotImpl.UINT16 From 1153742dce31d1d3cbd98d6eca0bf9ffe4993cd9 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 26 Apr 2026 13:04:10 -0700 Subject: [PATCH 06/34] Handle DeviceIsEVSE exception during init --- custom_components/solaredge_modbus_multi/hub.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 5936a7c0..b13bbdf9 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -188,6 +188,7 @@ def __init__( self.inverters = [] self.meters = [] self.batteries = [] + self.evses = [] self.inverter_common = {} self.mmppt_common = {} self.has_write = None @@ -285,6 +286,14 @@ async def _async_init_solaredge(self) -> None: _LOGGER.error(f"Inverter at {self.hub_host} ID {inverter_unit_id}: {e}") raise HubInitFailed(f"{e}") + except DeviceIsEVSE as e: + _LOGGER.debug( + f"Device model matches EVSE at {self.hub_host} ID {inverter_unit_id}: {e}" + ) + new_evse = SolarEdgeEVSE(inverter_unit_id, self) + await new_evse.init_device() + self.evses.append(new_evse) + if self._detect_meters: for meter_id in METER_REG_BASE: try: @@ -963,7 +972,7 @@ async def init_device(self) -> None: ) if DETECT_EVSE_REGEX.match(self.decoded_common["C_Model"]): - raise DeviceIsEVSE(f"Detected EVSE model: {self.decoded_common['C_Model']}") + raise DeviceIsEVSE(f"Model {self.decoded_common['C_Model']}") if ( self.decoded_common["C_SunSpec_ID"] == SunSpecNotImpl.UINT32 From 6c013a9d35e95f19b5827883b10390d28dd4b784 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 26 Apr 2026 13:10:00 -0700 Subject: [PATCH 07/34] Add version entity for evse devices --- custom_components/solaredge_modbus_multi/sensor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/custom_components/solaredge_modbus_multi/sensor.py b/custom_components/solaredge_modbus_multi/sensor.py index c748baff..49e72545 100644 --- a/custom_components/solaredge_modbus_multi/sensor.py +++ b/custom_components/solaredge_modbus_multi/sensor.py @@ -239,6 +239,9 @@ async def async_setup_entry( entities.append(SolarEdgeBatterySOE(battery, config_entry, coordinator)) entities.append(SolarEdgeBatteryStatus(battery, config_entry, coordinator)) + for evse in hub.evses: + entities.append(Version(evse, config_entry, coordinator)) + if entities: async_add_entities(entities) From ea767b5e99291ff5b8fc2c0887cc38ae2879cff0 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 26 Apr 2026 13:21:45 -0700 Subject: [PATCH 08/34] Use ID prefix E for EVSE devices --- custom_components/solaredge_modbus_multi/hub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index b13bbdf9..375ed049 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2764,7 +2764,7 @@ async def init_device(self) -> None: self.option = self.decoded_common["C_Option"] self.serial = self.decoded_common["C_SerialNumber"] self.device_address = self.decoded_common["C_Device_address"] - self.name = f"{self.hub.hub_id.capitalize()} C{self.evse_unit_id}" + self.name = f"{self.hub.hub_id.capitalize()} E{self.evse_unit_id}" self.uid_base = f"{self.model}_{self.serial}" async def read_modbus_data(self) -> None: From 397f1fe348328542ffb529d4da3f6d3cb43e2f08 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 26 Apr 2026 13:24:49 -0700 Subject: [PATCH 09/34] Add EVSE devices to diagnostics --- .../solaredge_modbus_multi/diagnostics.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/custom_components/solaredge_modbus_multi/diagnostics.py b/custom_components/solaredge_modbus_multi/diagnostics.py index feed3da1..ff6522c8 100644 --- a/custom_components/solaredge_modbus_multi/diagnostics.py +++ b/custom_components/solaredge_modbus_multi/diagnostics.py @@ -15,6 +15,7 @@ REDACT_INVERTER = {"identifiers", "C_SerialNumber", "serial_number"} REDACT_METER = {"identifiers", "C_SerialNumber", "serial_number", "via_device"} REDACT_BATTERY = {"identifiers", "B_SerialNumber", "serial_number", "via_device"} +REDACT_EVSE = {"identifiers", "C_SerialNumber", "serial_number"} def format_values(format_input) -> Any: @@ -87,4 +88,14 @@ async def async_get_config_entry_diagnostics( } data.update(async_redact_data(battery, REDACT_BATTERY)) + for evse in hub.evses: + evse: dict[str, Any] = { + f"evse_unit_id_{evse.evse_unit_id}": { + "device_info": evse.device_info, + "common": evse.decoded_common, + "model": format_values(evse.decoded_model), + } + } + data.update(async_redact_data(evse, REDACT_EVSE)) + return data From a13713d63366e59c640ea56734eaff24100b8762 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 26 Apr 2026 21:39:13 -0700 Subject: [PATCH 10/34] Try state address for Keba P30 --- .../solaredge_modbus_multi/hub.py | 21 +++++++++++++++++++ .../solaredge_modbus_multi/sensor.py | 19 +++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 375ed049..6510b1e6 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2782,6 +2782,27 @@ async def read_modbus_data(self) -> None: ) ) + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=1000, rcount=2 + ) + + self.decoded_model.update( + OrderedDict( + [ + ( + "E_State", + ModbusClientMixin.convert_from_registers( + evse_data.registers[0:2], + data_type=ModbusClientMixin.DATATYPE.UINT32, + ), + ), + ] + ) + ) + + except ModbusIllegalAddress: + _LOGGER.error(f"E{self.evse_unit_id}: E_State NOT available") + except ModbusIOError: raise ModbusReadError(f"No response from EVSE ID {self.evse_unit_id}") diff --git a/custom_components/solaredge_modbus_multi/sensor.py b/custom_components/solaredge_modbus_multi/sensor.py index 49e72545..cf338e09 100644 --- a/custom_components/solaredge_modbus_multi/sensor.py +++ b/custom_components/solaredge_modbus_multi/sensor.py @@ -241,6 +241,7 @@ async def async_setup_entry( for evse in hub.evses: entities.append(Version(evse, config_entry, coordinator)) + entities.append(SolarEdgeEvseState(evse, config_entry, coordinator)) if entities: async_add_entities(entities) @@ -2528,3 +2529,21 @@ def available(self) -> bool: @property def native_value(self) -> datetime.datetime | None: return self._platform.last_update + + +class SolarEdgeEvseState(SolarEdgeSensorBase): + @property + def unique_id(self) -> str: + return f"{self._platform.uid_base}_charging_state" + + @property + def name(self) -> str: + return "Charging State" + + @property + def available(self) -> bool: + return super().available and "E_State" in self._platform.decoded_model + + @property + def native_value(self): + return self._platform.decoded_model["E_State"] From f51eda11fd818905f457b45230d6650b3ddd698d Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 17:19:04 -0700 Subject: [PATCH 11/34] Bump version for pre-release --- custom_components/solaredge_modbus_multi/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/manifest.json b/custom_components/solaredge_modbus_multi/manifest.json index 9eb6acb4..8907358d 100644 --- a/custom_components/solaredge_modbus_multi/manifest.json +++ b/custom_components/solaredge_modbus_multi/manifest.json @@ -10,5 +10,5 @@ "issue_tracker": "https://github.com/WillCodeForCats/solaredge-modbus-multi/issues", "loggers": ["custom_components.solaredge_modbus_multi"], "requirements": ["pymodbus>=3.8.3", "awesomeversion>=25.5.0"], - "version": "3.2.4" + "version": "3.3.0-evse.0" } From bdd6d32a0484f1dbbfd9fa9b26624d673744ea69 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 17:30:31 -0700 Subject: [PATCH 12/34] Missing calls to read data for evses --- custom_components/solaredge_modbus_multi/hub.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 6510b1e6..a3f02d6e 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -368,6 +368,8 @@ async def _async_init_solaredge(self) -> None: await meter.read_modbus_data() for battery in self.batteries: await battery.read_modbus_data() + for evse in self.evses: + await evse.read_modbus_data() timestamp = dt.now() for inverter in self.inverters: @@ -458,6 +460,8 @@ async def async_refresh_modbus_data(self) -> bool: await meter.read_modbus_data() for battery in self.batteries: await battery.read_modbus_data() + for evse in self.evses: + await evse.read_modbus_data() except ModbusReadError as e: self.disconnect() From ae6c008192d73a9bb410ea9a93d9d2b4bb25e796 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 17:31:45 -0700 Subject: [PATCH 13/34] Skip meter and battery detection if DeviceIsEVSE --- custom_components/solaredge_modbus_multi/hub.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index a3f02d6e..d346e0be 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -293,6 +293,9 @@ async def _async_init_solaredge(self) -> None: new_evse = SolarEdgeEVSE(inverter_unit_id, self) await new_evse.init_device() self.evses.append(new_evse) + + # Skip meter and battery detection if DeviceIsEVSE + continue if self._detect_meters: for meter_id in METER_REG_BASE: From d05ff763964f3ce58ff9fdff01db868f4c3ab8fa Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 17:32:04 -0700 Subject: [PATCH 14/34] Debug ident for evse should be E not C --- custom_components/solaredge_modbus_multi/hub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index d346e0be..8bd107a4 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2745,7 +2745,7 @@ async def init_device(self) -> None: for name, value in iter(self.decoded_common.items()): _LOGGER.debug( ( - f"C{self.evse_unit_id}: " + f"E{self.evse_unit_id}: " f"{name} {hex(value) if isinstance(value, int) else value}" f"{type(value)}" ), From 2e5c131dfc885c1388bc73db871ce2ac98af7542 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 17:32:19 -0700 Subject: [PATCH 15/34] Format with ruff --- custom_components/solaredge_modbus_multi/hub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 8bd107a4..d55005f9 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -293,7 +293,7 @@ async def _async_init_solaredge(self) -> None: new_evse = SolarEdgeEVSE(inverter_unit_id, self) await new_evse.init_device() self.evses.append(new_evse) - + # Skip meter and battery detection if DeviceIsEVSE continue From 12aaa39a90bd9bb2cbd2343aec323a4253b113d4 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 18:24:51 -0700 Subject: [PATCH 16/34] Bump version for pre-release --- custom_components/solaredge_modbus_multi/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/manifest.json b/custom_components/solaredge_modbus_multi/manifest.json index 8907358d..21e7a759 100644 --- a/custom_components/solaredge_modbus_multi/manifest.json +++ b/custom_components/solaredge_modbus_multi/manifest.json @@ -10,5 +10,5 @@ "issue_tracker": "https://github.com/WillCodeForCats/solaredge-modbus-multi/issues", "loggers": ["custom_components.solaredge_modbus_multi"], "requirements": ["pymodbus>=3.8.3", "awesomeversion>=25.5.0"], - "version": "3.3.0-evse.0" + "version": "3.3.0-evse.1" } From 48dd0da405a7dea66838558856d1c9a001a83d58 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 18:56:53 -0700 Subject: [PATCH 17/34] Fix incorrect use of update() --- .../solaredge_modbus_multi/hub.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index d55005f9..96ef6af8 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2793,18 +2793,16 @@ async def read_modbus_data(self) -> None: unit=self.evse_unit_id, address=1000, rcount=2 ) - self.decoded_model.update( - OrderedDict( - [ - ( - "E_State", - ModbusClientMixin.convert_from_registers( - evse_data.registers[0:2], - data_type=ModbusClientMixin.DATATYPE.UINT32, - ), + self.decoded_model = OrderedDict( + [ + ( + "E_State", + ModbusClientMixin.convert_from_registers( + evse_data.registers[0:2], + data_type=ModbusClientMixin.DATATYPE.UINT32, ), - ] - ) + ), + ] ) except ModbusIllegalAddress: From cd5b094736df930e69eb44113871dd41c8b19026 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 19:09:48 -0700 Subject: [PATCH 18/34] Add read for cable state --- .../solaredge_modbus_multi/hub.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 96ef6af8..1a9c9238 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2805,6 +2805,24 @@ async def read_modbus_data(self) -> None: ] ) + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=1004, rcount=2 + ) + + self.decoded_model.update( + OrderedDict( + [ + ( + "E_Cable_State", + ModbusClientMixin.convert_from_registers( + evse_data.registers[0:2], + data_type=ModbusClientMixin.DATATYPE.UINT32, + ), + ), + ] + ) + ) + except ModbusIllegalAddress: _LOGGER.error(f"E{self.evse_unit_id}: E_State NOT available") From 3974e63a2cbf271cac59461ea00db2c052756f5b Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 19:10:01 -0700 Subject: [PATCH 19/34] Add read for type and features --- custom_components/solaredge_modbus_multi/hub.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 1a9c9238..6b0e55d6 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2823,6 +2823,23 @@ async def read_modbus_data(self) -> None: ) ) + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=1016, rcount=2 + ) + + self.decoded_model.update( + OrderedDict( + [ + ( + "E_Type_Features", + ModbusClientMixin.convert_from_registers( + evse_data.registers[0:2], + data_type=ModbusClientMixin.DATATYPE.UINT32, + ), + ), + ] + ) + ) except ModbusIllegalAddress: _LOGGER.error(f"E{self.evse_unit_id}: E_State NOT available") From c3f3ce5fcb2aa8fbd2affbae49b7254cf0741adc Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 19:10:18 -0700 Subject: [PATCH 20/34] Add debug output for decoded_model --- custom_components/solaredge_modbus_multi/hub.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 6b0e55d6..3681262c 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2840,6 +2840,16 @@ async def read_modbus_data(self) -> None: ] ) ) + + for name, value in iter(self.decoded_model.items()): + if isinstance(value, float): + display_value = float_to_hex(value) + else: + display_value = hex(value) if isinstance(value, int) else value + _LOGGER.debug( + f"E{self.evse_unit_id}: {name} {display_value} {type(value)}" + ) + except ModbusIllegalAddress: _LOGGER.error(f"E{self.evse_unit_id}: E_State NOT available") From d16002a7b3526800473b1a73d03d2a58dbf079ec Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 19:10:29 -0700 Subject: [PATCH 21/34] Update error message for illegal address --- custom_components/solaredge_modbus_multi/hub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 3681262c..6673f9ad 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2851,7 +2851,7 @@ async def read_modbus_data(self) -> None: ) except ModbusIllegalAddress: - _LOGGER.error(f"E{self.evse_unit_id}: E_State NOT available") + _LOGGER.error(f"E{self.evse_unit_id}: EVSE register(s) NOT available") except ModbusIOError: raise ModbusReadError(f"No response from EVSE ID {self.evse_unit_id}") From 944098aa82c263776f4d963d0a0fe129125bde39 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 3 May 2026 19:10:59 -0700 Subject: [PATCH 22/34] Bump version for pre-release --- custom_components/solaredge_modbus_multi/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/manifest.json b/custom_components/solaredge_modbus_multi/manifest.json index 21e7a759..cffd74bd 100644 --- a/custom_components/solaredge_modbus_multi/manifest.json +++ b/custom_components/solaredge_modbus_multi/manifest.json @@ -10,5 +10,5 @@ "issue_tracker": "https://github.com/WillCodeForCats/solaredge-modbus-multi/issues", "loggers": ["custom_components.solaredge_modbus_multi"], "requirements": ["pymodbus>=3.8.3", "awesomeversion>=25.5.0"], - "version": "3.3.0-evse.1" + "version": "3.3.0-evse.2" } From 60e2d542400b425f2e28e9ea9e7f515d8253331d Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 4 May 2026 22:17:44 -0700 Subject: [PATCH 23/34] Blind read sunspec ranges to see if we can see any differences --- .../solaredge_modbus_multi/hub.py | 61 ++++++------------- .../solaredge_modbus_multi/sensor.py | 1 - 2 files changed, 19 insertions(+), 43 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 6673f9ad..06f270c0 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2789,56 +2789,33 @@ async def read_modbus_data(self) -> None: ) ) + # blind read sunspec ranges to see if we can see any differences evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=1000, rcount=2 + unit=self.evse_unit_id, address=40069, rcount=64 ) - - self.decoded_model = OrderedDict( - [ - ( - "E_State", - ModbusClientMixin.convert_from_registers( - evse_data.registers[0:2], - data_type=ModbusClientMixin.DATATYPE.UINT32, - ), - ), - ] + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=40133, rcount=64 ) - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=1004, rcount=2 + unit=self.evse_unit_id, address=40197, rcount=64 ) - - self.decoded_model.update( - OrderedDict( - [ - ( - "E_Cable_State", - ModbusClientMixin.convert_from_registers( - evse_data.registers[0:2], - data_type=ModbusClientMixin.DATATYPE.UINT32, - ), - ), - ] - ) + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=40261, rcount=64 ) - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=1016, rcount=2 + unit=self.evse_unit_id, address=40325, rcount=64 ) - - self.decoded_model.update( - OrderedDict( - [ - ( - "E_Type_Features", - ModbusClientMixin.convert_from_registers( - evse_data.registers[0:2], - data_type=ModbusClientMixin.DATATYPE.UINT32, - ), - ), - ] - ) + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=40389, rcount=64 + ) + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=40453, rcount=64 + ) + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=40517, rcount=64 + ) + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=40581, rcount=64 ) for name, value in iter(self.decoded_model.items()): diff --git a/custom_components/solaredge_modbus_multi/sensor.py b/custom_components/solaredge_modbus_multi/sensor.py index 3f62dfe1..00943c01 100644 --- a/custom_components/solaredge_modbus_multi/sensor.py +++ b/custom_components/solaredge_modbus_multi/sensor.py @@ -240,7 +240,6 @@ async def async_setup_entry( for evse in hub.evses: entities.append(Version(evse, config_entry, coordinator)) - entities.append(SolarEdgeEvseState(evse, config_entry, coordinator)) if entities: async_add_entities(entities) From 4209c933f6c8b31bde3b3e610a3d22937416ba4a Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 4 May 2026 22:18:02 -0700 Subject: [PATCH 24/34] Bump version for pre-release --- custom_components/solaredge_modbus_multi/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/manifest.json b/custom_components/solaredge_modbus_multi/manifest.json index cffd74bd..adc3cae9 100644 --- a/custom_components/solaredge_modbus_multi/manifest.json +++ b/custom_components/solaredge_modbus_multi/manifest.json @@ -10,5 +10,5 @@ "issue_tracker": "https://github.com/WillCodeForCats/solaredge-modbus-multi/issues", "loggers": ["custom_components.solaredge_modbus_multi"], "requirements": ["pymodbus>=3.8.3", "awesomeversion>=25.5.0"], - "version": "3.3.0-evse.2" + "version": "3.3.0-evse.3" } From 2a3def0ee16222e1efb5e8098747691d230090f8 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Tue, 5 May 2026 11:39:56 -0700 Subject: [PATCH 25/34] Replace OrderedDict with dict --- custom_components/solaredge_modbus_multi/hub.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index a69eb615..bbc431da 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2659,7 +2659,7 @@ async def init_device(self) -> None: unit=self.evse_unit_id, address=40000, rcount=69 ) - self.decoded_common = OrderedDict( + self.decoded_common = dict( [ ( "C_SunSpec_ID", @@ -2678,7 +2678,7 @@ async def init_device(self) -> None: ] uint16_data = evse_data.registers[2:4] + [evse_data.registers[68]] self.decoded_common.update( - OrderedDict( + dict( zip( uint16_fields, ModbusClientMixin.convert_from_registers( @@ -2690,7 +2690,7 @@ async def init_device(self) -> None: ) self.decoded_common.update( - OrderedDict( + dict( [ ( "C_Manufacturer", # string(32) From a7ba919a2b90e9b8c5df6b8a6dd822220fd3ca29 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Tue, 5 May 2026 15:42:57 -0700 Subject: [PATCH 26/34] Change debug in modbus read to unit instead of I --- .../solaredge_modbus_multi/hub.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index bbc431da..46932201 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -568,7 +568,7 @@ async def modbus_read_holding_registers(self, unit, address, rcount): sig = inspect.signature(self._client.read_holding_registers) _LOGGER.debug( - f"I{self._rr_unit}: modbus_read_holding_registers " + f"unit={self._rr_unit}: modbus_read_holding_registers " f"address={self._rr_address} count={self._rr_count}" ) @@ -581,38 +581,40 @@ async def modbus_read_holding_registers(self, unit, address, rcount): address=self._rr_address, count=self._rr_count, slave=self._rr_unit ) - _LOGGER.debug(f"I{self._rr_unit}: result is error: {result.isError()} ") + _LOGGER.debug(f"unit={self._rr_unit}: result is error: {result.isError()} ") if result.isError(): - _LOGGER.debug(f"I{self._rr_unit}: error result: {type(result)} ") + _LOGGER.debug(f"unit={self._rr_unit}: error result: {type(result)} ") if type(result) is ModbusIOException: raise ModbusIOError(result) if type(result) is ExceptionResponse: if result.exception_code == ModbusExceptions.IllegalAddress: - _LOGGER.debug(f"I{unit} Read IllegalAddress: {result}") + _LOGGER.debug(f"unit={self._rr_unit} Read IllegalAddress: {result}") raise ModbusIllegalAddress(result) if result.exception_code == ModbusExceptions.IllegalFunction: - _LOGGER.debug(f"I{unit} Read IllegalFunction: {result}") + _LOGGER.debug( + f"unit={self._rr_unit} Read IllegalFunction: {result}" + ) raise ModbusIllegalFunction(result) if result.exception_code == ModbusExceptions.IllegalValue: - _LOGGER.debug(f"I{unit} Read IllegalValue: {result}") + _LOGGER.debug(f"unit={self._rr_unit} Read IllegalValue: {result}") raise ModbusIllegalValue(result) raise ModbusReadError(result) _LOGGER.debug( - f"I{self._rr_unit}: Registers received={len(result.registers)} " + f"unit={self._rr_unit}: Registers received={len(result.registers)} " f"requested={self._rr_count} address={self._rr_address} " f"result={result}" ) if len(result.registers) != rcount: raise ModbusReadError( - f"I{self._rr_unit}: Registers received != requested : " + f"unit={self._rr_unit}: Registers received != requested : " f"{len(result.registers)} != {self._rr_count} at {self._rr_address}" ) From 6d3681eb96dc795617c8716a6d7ca93413647173 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Tue, 5 May 2026 15:50:19 -0700 Subject: [PATCH 27/34] Also apply #979 to evse class --- custom_components/solaredge_modbus_multi/hub.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 4fd75fad..d2396d5b 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2649,8 +2649,8 @@ class SolarEdgeEVSE: def __init__(self, device_id: int, hub: SolarEdgeModbusMultiHub) -> None: self.evse_unit_id = device_id self.hub = hub - self.decoded_common = [] - self.decoded_model = [] + self.decoded_common = {} + self.decoded_model = {} self.has_parent = False async def init_device(self) -> None: From e84348bf84e2ca140f9a64af366ebc51a8d88147 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Tue, 5 May 2026 15:50:59 -0700 Subject: [PATCH 28/34] Bump version for pre-release --- custom_components/solaredge_modbus_multi/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/manifest.json b/custom_components/solaredge_modbus_multi/manifest.json index adc3cae9..f5d0f54f 100644 --- a/custom_components/solaredge_modbus_multi/manifest.json +++ b/custom_components/solaredge_modbus_multi/manifest.json @@ -10,5 +10,5 @@ "issue_tracker": "https://github.com/WillCodeForCats/solaredge-modbus-multi/issues", "loggers": ["custom_components.solaredge_modbus_multi"], "requirements": ["pymodbus>=3.8.3", "awesomeversion>=25.5.0"], - "version": "3.3.0-evse.3" + "version": "3.3.0-evse.4" } From 16fe17da7a440272d8fea00a0de28e3f17ed2558 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 17 May 2026 09:57:47 -0700 Subject: [PATCH 29/34] Blind read at 50000 --- .../solaredge_modbus_multi/hub.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 73703a2b..731d1308 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2832,31 +2832,34 @@ async def read_modbus_data(self) -> None: # blind read sunspec ranges to see if we can see any differences evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=40069, rcount=64 + unit=self.evse_unit_id, address=50000, rcount=69 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=40133, rcount=64 + unit=self.evse_unit_id, address=50069, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=40197, rcount=64 + unit=self.evse_unit_id, address=50133, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=40261, rcount=64 + unit=self.evse_unit_id, address=50197, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=40325, rcount=64 + unit=self.evse_unit_id, address=50261, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=40389, rcount=64 + unit=self.evse_unit_id, address=50325, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=40453, rcount=64 + unit=self.evse_unit_id, address=50389, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=40517, rcount=64 + unit=self.evse_unit_id, address=50453, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=40581, rcount=64 + unit=self.evse_unit_id, address=50517, rcount=64 + ) + evse_data = await self.hub.modbus_read_holding_registers( + unit=self.evse_unit_id, address=50581, rcount=64 ) for name, value in iter(self.decoded_model.items()): From b11c02c817fb9aa12119bf91857ab4607f8ee692 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 17 May 2026 09:58:14 -0700 Subject: [PATCH 30/34] Bump version for pre-release --- custom_components/solaredge_modbus_multi/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/manifest.json b/custom_components/solaredge_modbus_multi/manifest.json index f5d0f54f..cecabb92 100644 --- a/custom_components/solaredge_modbus_multi/manifest.json +++ b/custom_components/solaredge_modbus_multi/manifest.json @@ -10,5 +10,5 @@ "issue_tracker": "https://github.com/WillCodeForCats/solaredge-modbus-multi/issues", "loggers": ["custom_components.solaredge_modbus_multi"], "requirements": ["pymodbus>=3.8.3", "awesomeversion>=25.5.0"], - "version": "3.3.0-evse.4" + "version": "3.3.0-evse.5" } From da45ac16a5b8ecd3fda7d71a04419aa006f105a7 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 17 May 2026 13:44:03 -0700 Subject: [PATCH 31/34] Blind read at 0 --- .../solaredge_modbus_multi/hub.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 731d1308..b29081ff 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2832,34 +2832,34 @@ async def read_modbus_data(self) -> None: # blind read sunspec ranges to see if we can see any differences evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=50000, rcount=69 + unit=self.evse_unit_id, address=0, rcount=69 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=50069, rcount=64 + unit=self.evse_unit_id, address=69, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=50133, rcount=64 + unit=self.evse_unit_id, address=133, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=50197, rcount=64 + unit=self.evse_unit_id, address=197, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=50261, rcount=64 + unit=self.evse_unit_id, address=261, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=50325, rcount=64 + unit=self.evse_unit_id, address=325, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=50389, rcount=64 + unit=self.evse_unit_id, address=389, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=50453, rcount=64 + unit=self.evse_unit_id, address=453, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=50517, rcount=64 + unit=self.evse_unit_id, address=517, rcount=64 ) evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=50581, rcount=64 + unit=self.evse_unit_id, address=581, rcount=64 ) for name, value in iter(self.decoded_model.items()): From 9d692724acc1a7eeaf946e17e4ef1eed2ab1a769 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 17 May 2026 13:44:27 -0700 Subject: [PATCH 32/34] Bump version for pre-release --- custom_components/solaredge_modbus_multi/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/solaredge_modbus_multi/manifest.json b/custom_components/solaredge_modbus_multi/manifest.json index cecabb92..9c19f98c 100644 --- a/custom_components/solaredge_modbus_multi/manifest.json +++ b/custom_components/solaredge_modbus_multi/manifest.json @@ -10,5 +10,5 @@ "issue_tracker": "https://github.com/WillCodeForCats/solaredge-modbus-multi/issues", "loggers": ["custom_components.solaredge_modbus_multi"], "requirements": ["pymodbus>=3.8.3", "awesomeversion>=25.5.0"], - "version": "3.3.0-evse.5" + "version": "3.3.0-evse.6" } From e882d87a270b4cd060f3c7dc2dc55381f2632098 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 17 May 2026 15:38:42 -0700 Subject: [PATCH 33/34] Remove evse_data reads We have tried several different register ranges and none of them return data that changes. Only the model and version are available. --- .../solaredge_modbus_multi/hub.py | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index b29081ff..c2a05560 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -2830,38 +2830,6 @@ async def read_modbus_data(self) -> None: ) ) - # blind read sunspec ranges to see if we can see any differences - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=0, rcount=69 - ) - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=69, rcount=64 - ) - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=133, rcount=64 - ) - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=197, rcount=64 - ) - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=261, rcount=64 - ) - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=325, rcount=64 - ) - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=389, rcount=64 - ) - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=453, rcount=64 - ) - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=517, rcount=64 - ) - evse_data = await self.hub.modbus_read_holding_registers( - unit=self.evse_unit_id, address=581, rcount=64 - ) - for name, value in iter(self.decoded_model.items()): if isinstance(value, float): display_value = float_to_hex(value) From 24b93b6e9625e94ed65892bc1ae4cb619d5dc292 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Sun, 17 May 2026 15:38:52 -0700 Subject: [PATCH 34/34] Remove unused sensor SolarEdgeEvseState --- .../solaredge_modbus_multi/sensor.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/sensor.py b/custom_components/solaredge_modbus_multi/sensor.py index 5d1fdbcf..34141115 100644 --- a/custom_components/solaredge_modbus_multi/sensor.py +++ b/custom_components/solaredge_modbus_multi/sensor.py @@ -2593,21 +2593,3 @@ def entity_registry_enabled_default(self) -> bool: @property def native_value(self) -> datetime.datetime | None: return self._platform.last_update - - -class SolarEdgeEvseState(SolarEdgeSensorBase): - @property - def unique_id(self) -> str: - return f"{self._platform.uid_base}_charging_state" - - @property - def name(self) -> str: - return "Charging State" - - @property - def available(self) -> bool: - return super().available and "E_State" in self._platform.decoded_model - - @property - def native_value(self): - return self._platform.decoded_model["E_State"]