diff --git a/custom_components/givenergy_local/__init__.py b/custom_components/givenergy_local/__init__.py index 9b75c4e..f2759db 100755 --- a/custom_components/givenergy_local/__init__.py +++ b/custom_components/givenergy_local/__init__.py @@ -14,6 +14,7 @@ Platform.BINARY_SENSOR, Platform.NUMBER, Platform.SENSOR, + Platform.SELECT, Platform.SWITCH, ] diff --git a/custom_components/givenergy_local/coordinator.py b/custom_components/givenergy_local/coordinator.py index 48540f8..afee3e5 100644 --- a/custom_components/givenergy_local/coordinator.py +++ b/custom_components/givenergy_local/coordinator.py @@ -90,7 +90,10 @@ async def _async_update_data(self) -> Plant: """Fetch data from the inverter.""" if not self.client.connected: await self.client.connect() - self.require_full_refresh = True + await self.client.detect_plant() + self.require_full_refresh = False + self.last_full_refresh = datetime.utcnow() + return self.client.plant if self.last_full_refresh < (datetime.utcnow() - _FULL_REFRESH_INTERVAL): self.require_full_refresh = True diff --git a/custom_components/givenergy_local/givenergy_modbus/client/client.py b/custom_components/givenergy_local/givenergy_modbus/client/client.py index 5800aad..75b277b 100644 --- a/custom_components/givenergy_local/givenergy_modbus/client/client.py +++ b/custom_components/givenergy_local/givenergy_modbus/client/client.py @@ -75,6 +75,35 @@ async def connect(self) -> None: self.connected = True _logger.info("Connection established to %s:%d", self.host, self.port) + async def detect_plant(self, timeout: int = 1, retries: int = 3) -> None: + """Detect inverter capabilities that influence how subsequent requests are made.""" + _logger.info("Detectig plant") + + # Refresh the core set of registers that work across all inverters + await self.refresh_plant(True, timeout=timeout, retries=retries) + + # Use that to detect the number of batteries + self.plant.detect_batteries() + _logger.info("Batteries detected: %d", self.plant.number_batteries) + + # Some devices support additional registers + # When unsupported, devices appear to simple ignore requests + possible_additional_holding_registers = [180] + for hr in possible_additional_holding_registers: + try: + reqs = commands.refresh_additional_holding_registers(hr) + await self.execute(reqs, timeout=timeout, retries=retries) + _logger.info( + "Detected additional holding register support (base_register=%d)", + hr, + ) + self.plant.additional_holding_registers.append(hr) + except asyncio.TimeoutError: + _logger.debug( + "Inverter did not respond to additional holder register query (base_register=%d)", + hr, + ) + async def close(self) -> None: """Disconnect from the remote host and clean up tasks and queues.""" if not self.connected: @@ -124,10 +153,6 @@ async def refresh_plant( full_refresh, self.plant.number_batteries, max_batteries ) await self.execute(reqs, timeout=timeout, retries=retries) - - if full_refresh: - self.plant.detect_batteries() - return self.plant async def watch_plant( @@ -142,7 +167,6 @@ async def watch_plant( """Refresh data about the Plant.""" await self.connect() await self.refresh_plant(True, max_batteries=max_batteries) - self.plant.detect_batteries() while True: if handler: handler() diff --git a/custom_components/givenergy_local/givenergy_modbus/client/commands.py b/custom_components/givenergy_local/givenergy_modbus/client/commands.py index 203a030..e753b03 100644 --- a/custom_components/givenergy_local/givenergy_modbus/client/commands.py +++ b/custom_components/givenergy_local/givenergy_modbus/client/commands.py @@ -49,8 +49,25 @@ class RegisterMap: BATTERY_PAUSE_MODE = 318 +def refresh_additional_holding_registers( + base_register: int, +) -> list[TransparentRequest]: + """Requests one specific set of holding registers. + + This is intended to be used in cases where registers may or may not be present, + depending on device capabilities.""" + return [ + ReadHoldingRegistersRequest( + base_register=base_register, register_count=60, slave_address=0x32 + ) + ] + + def refresh_plant_data( - complete: bool, number_batteries: int = 1, max_batteries: int = 5 + complete: bool, + number_batteries: int = 1, + max_batteries: int = 5, + additional_holding_registers: Optional[list[int]] = None, ) -> list[TransparentRequest]: """Refresh plant data.""" requests: list[TransparentRequest] = [ @@ -77,16 +94,16 @@ def refresh_plant_data( base_register=120, register_count=60, slave_address=0x32 ) ) - requests.append( - ReadHoldingRegistersRequest( - base_register=300, register_count=60, slave_address=0x32 - ) - ) requests.append( ReadInputRegistersRequest( base_register=120, register_count=60, slave_address=0x32 ) ) + + if additional_holding_registers: + for hr in additional_holding_registers: + requests.extend(refresh_additional_holding_registers(hr)) + number_batteries = max_batteries for i in range(number_batteries): requests.append( diff --git a/custom_components/givenergy_local/givenergy_modbus/model/plant.py b/custom_components/givenergy_local/givenergy_modbus/model/plant.py index cfb16d0..cb2c536 100644 --- a/custom_components/givenergy_local/givenergy_modbus/model/plant.py +++ b/custom_components/givenergy_local/givenergy_modbus/model/plant.py @@ -24,6 +24,7 @@ class Plant(GivEnergyBaseModel): """Representation of a complete GivEnergy plant.""" register_caches: dict[int, RegisterCache] = {} + additional_holding_registers: list[int] = [] inverter_serial_number: str = "" data_adapter_serial_number: str = "" number_batteries: int = 0 @@ -93,7 +94,6 @@ def detect_batteries(self) -> None: assert Battery.from_orm(self.register_caches[i + 0x32]).is_valid() except (KeyError, AssertionError): break - _logger.debug("Updating connected battery count to %d", i) self.number_batteries = i @property diff --git a/custom_components/givenergy_local/select.py b/custom_components/givenergy_local/select.py index 1d9f353..bdb9cb8 100644 --- a/custom_components/givenergy_local/select.py +++ b/custom_components/givenergy_local/select.py @@ -1,4 +1,5 @@ """Select platform.""" + from __future__ import annotations from homeassistant.components.select import SelectEntity, SelectEntityDescription @@ -10,18 +11,16 @@ set_battery_pause_mode, ) -from .givenergy_modbus.model.inverter import BatteryPauseMode, Generation, Model - from . import GivEnergyUpdateCoordinator from .const import DOMAIN, Icon from .entity import InverterEntity - +from .givenergy_modbus.model.inverter import BatteryPauseMode _BATTERY_PAUSE_MODE_OPTIONS = { - BatteryPauseMode.DISABLED: "DISABLED", - BatteryPauseMode.PAUSE_CHARGE: "PAUSE_CHARGE", - BatteryPauseMode.PAUSE_DISCHARGE: "PAUSE_DISCHARGE", - BatteryPauseMode.PAUSE_BOTH: "PAUSE_BOTH", + BatteryPauseMode.DISABLED: "Not Paused", + BatteryPauseMode.PAUSE_CHARGE: "Pause Charge", + BatteryPauseMode.PAUSE_DISCHARGE: "Pause Discharge", + BatteryPauseMode.PAUSE_BOTH: "Pause Charge & Discharge", } @@ -42,10 +41,7 @@ async def async_setup_entry( coordinator: GivEnergyUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] entities: list[SelectEntity] = [] - is_gen1 = coordinator.data.inverter.generation == Generation.GEN1 - is_aio = coordinator.data.inverter.model == Model.ALL_IN_ONE - - if not is_gen1 or is_aio: + if coordinator.data.inverter.battery_pause_mode is not None: entities += BatteryPauseModeSelect( coordinator, config_entry,