Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions custom_components/solaredge_modbus_multi/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
re.IGNORECASE,
)

STATUS_VENDOR4_VERSION = "3.20.0"


class ModbusExceptions:
"""An enumeration of the valid modbus exceptions."""
Expand Down Expand Up @@ -270,6 +272,34 @@ class SunSpecNotImpl(IntEnum):
256: "Arc Detected",
}

VENDOR4_STATUS: dict[int, dict[int, str]] = {
# controller: {error_code: "description"}
0x3: {
0x2: "Inverter Communication Error",
0x1C: "Battery without Firmware",
0x6B: "Battery Communication Error",
0x6E: "Meter Communication Error",
0xA5: "Modbus Routing Packets Debug",
},
0x18: {
0x14: "Inv. Power Error - No Code",
0x1C: "V-L1 Min1",
0x3D: "I-RCD Step",
0x40: "F-L1 Max1",
0x61: "Vin Buck Max",
0xA5: "Tz Over Current 3",
0xB5: "Vcap11 Surge",
0xB6: "Vcap12 Surge",
0xB9: "Vcap31 Surge",
0xBA: "Vcap33 Surge",
0xBE: "Swing Rdiff Overheat",
0xBF: "Swing Rcommon Overheat",
0xC7: "Rapid Shutdown (RSD) Error",
0xD6: "Multiple Seq. GB",
0x100: "Arc Detected",
},
}

SUNSPEC_DID = {
101: "Single Phase Inverter",
102: "Split Phase Inverter",
Expand Down
1 change: 1 addition & 0 deletions custom_components/solaredge_modbus_multi/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ async def async_get_config_entry_diagnostics(
"mmppt": format_values(inverter.decoded_mmppt),
"has_battery": inverter.has_battery,
"storage_control": format_values(inverter.decoded_storage_control),
"use_status_vendor4": inverter.use_status_vendor4,
}
}

Expand Down
40 changes: 40 additions & 0 deletions custom_components/solaredge_modbus_multi/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
import inspect
import logging

from awesomeversion import AwesomeVersion
from awesomeversion.exceptions import (
AwesomeVersionCompareException,
AwesomeVersionStrategyException,
)
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
Expand All @@ -28,6 +33,7 @@
DOMAIN,
METER_REG_BASE,
PYMODBUS_REQUIRED_VERSION,
STATUS_VENDOR4_VERSION,
ConfDefaultFlag,
ConfDefaultInt,
ConfDefaultStr,
Expand Down Expand Up @@ -844,6 +850,7 @@ def __init__(self, device_id: int, hub: SolarEdgeModbusMultiHub) -> None:
self.site_limit_control = None
self._grid_status = None
self._last_update_timestamp = None
self._use_status_vendor4 = False

async def init_device(self) -> None:
"""Set up data about the device from modbus."""
Expand Down Expand Up @@ -1036,6 +1043,16 @@ async def init_device(self) -> None:
self.name = f"{self.hub.hub_id.capitalize()} I{self.inverter_unit_id}"
self.uid_base = f"{self.model}_{self.serial}"

try:
this_ver = AwesomeVersion(self.decoded_common["C_Version"])
self._use_status_vendor4 = this_ver >= AwesomeVersion(
STATUS_VENDOR4_VERSION
)
except (AwesomeVersionCompareException, AwesomeVersionStrategyException) as e:
_LOGGER.error(
f"Error checking inverter version: {e}. Please report this issue."
)

if self.decoded_mmppt is not None:
for unit_index in range(self.decoded_mmppt["mmppt_Units"]):
self.mmppt_units.append(SolarEdgeMMPPTUnit(self, self.hub, unit_index))
Expand Down Expand Up @@ -1127,6 +1144,7 @@ async def read_modbus_data(self) -> None:
+ [inverter_data.registers[28]]
+ inverter_data.registers[30:40]
)

self.decoded_model.update(
dict(
zip(
Expand Down Expand Up @@ -1154,6 +1172,24 @@ async def read_modbus_data(self) -> None:
)
)

if self.use_status_vendor4:
inverter_data = await self.hub.modbus_read_holding_registers(
unit=self.inverter_unit_id, address=40119, rcount=2
)
self.decoded_model.update(
dict(
[
(
"I_Status_Vendor4",
ModbusClientMixin.convert_from_registers(
inverter_data.registers[0:2],
data_type=ModbusClientMixin.DATATYPE.UINT32,
),
),
]
)
)

if (
self.decoded_model["C_SunSpec_DID"] == SunSpecNotImpl.UINT16
or self.decoded_model["C_SunSpec_DID"] not in [101, 102, 103]
Expand Down Expand Up @@ -1913,6 +1949,10 @@ def is_mmppt(self) -> bool:
def last_update(self) -> datetime.datetime | None:
return self._last_update_timestamp

@property
def use_status_vendor4(self) -> bool:
return self._use_status_vendor4


class SolarEdgeMMPPTUnit:
"""Defines a SolarEdge inverter MMPPT unit."""
Expand Down
61 changes: 61 additions & 0 deletions custom_components/solaredge_modbus_multi/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
RRCR_STATUS,
SUNSPEC_DID,
SUNSPEC_SF_RANGE,
VENDOR4_STATUS,
VENDOR_STATUS,
BatteryLimit,
SunSpecAccum,
Expand All @@ -67,6 +68,8 @@ async def async_setup_entry(
entities.append(Version(inverter, config_entry, coordinator))
entities.append(SolarEdgeInverterStatus(inverter, config_entry, coordinator))
entities.append(StatusVendor(inverter, config_entry, coordinator))
if inverter.use_status_vendor4:
entities.append(StatusVendor4(inverter, config_entry, coordinator))
entities.append(ACCurrentSensor(inverter, config_entry, coordinator))
entities.append(ACCurrentSensor(inverter, config_entry, coordinator, "A"))
entities.append(ACCurrentSensor(inverter, config_entry, coordinator, "B"))
Expand Down Expand Up @@ -1399,6 +1402,10 @@ def unique_id(self) -> str:
def name(self) -> str:
return "Status Vendor"

@property
def entity_registry_enabled_default(self) -> bool:
return not self._platform.use_status_vendor4

@property
def native_value(self):
try:
Expand Down Expand Up @@ -1428,6 +1435,60 @@ def extra_state_attributes(self):
return None


class StatusVendor4(SolarEdgeSensorBase):
entity_category = EntityCategory.DIAGNOSTIC

@property
def unique_id(self) -> str:
return f"{self._platform.uid_base}_status_vendor4"

@property
def name(self) -> str:
return "Status Vendor 4"

@property
def available(self) -> bool:
return (
super().available
and "I_Status_Vendor4" in self._platform.decoded_model
and self._platform.decoded_model["I_Status_Vendor4"]
!= SunSpecNotImpl.UINT32
)

@property
def native_value(self):
try:
value = self._platform.decoded_model["I_Status_Vendor4"]
controller = (value >> 24) & 0xFF
error = value & 0xFFFF
return f"{controller:X}x{error:X}"
except TypeError:
return None

@property
def extra_state_attributes(self):
try:
value = self._platform.decoded_model["I_Status_Vendor4"]

controller = (value >> 24) & 0xFF
error = value & 0xFFFF
attrs = {
"controller": hex(controller),
"error_code": hex(error),
}

if controller in VENDOR4_STATUS and error in VENDOR4_STATUS[controller]:
attrs["description"] = VENDOR4_STATUS[controller][error]

return attrs

except KeyError:
return None

except TypeError:
return None


class SolarEdgeGlobalPowerControlBlock(SolarEdgeSensorBase):
@property
def available(self) -> bool:
Expand Down
Loading