From 1a9a08d5ca4d120fa7ae1a7fa13e93a995856636 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:45:59 -0700 Subject: [PATCH 01/13] Bump super-linter to v8 --- .github/workflows/linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 084220ae..9ecfa1ae 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -20,7 +20,7 @@ jobs: with: fetch-depth: 0 - name: Lint Code Base - uses: github/super-linter@v7 + uses: super-linter/super-linter@v8 env: VALIDATE_ALL_CODEBASE: false VALIDATE_JSCPD: false From 03838ee8f43b4e36403faa991d4aface474f959b Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:51:49 -0700 Subject: [PATCH 02/13] Remove noqa B950 --- custom_components/solaredge_modbus_multi/sensor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/sensor.py b/custom_components/solaredge_modbus_multi/sensor.py index 37a98d26..67844074 100644 --- a/custom_components/solaredge_modbus_multi/sensor.py +++ b/custom_components/solaredge_modbus_multi/sensor.py @@ -2120,8 +2120,8 @@ def native_value(self): ): _LOGGER.warning( ( - "Battery Export Energy went backwards: Current value " # noqa: B950 - f"{self._platform.decoded_model['B_Export_Energy_WH']} " # noqa: B950 + "Battery Export Energy went backwards: Current value " + f"{self._platform.decoded_model['B_Export_Energy_WH']} " f"is less than last value of {self._last}" ) ) @@ -2132,7 +2132,7 @@ def native_value(self): _LOGGER.debug( ( "B_Export_Energy went backwards: " - f"{self._platform.decoded_model['B_Export_Energy_WH']} " # noqa: B950 + f"{self._platform.decoded_model['B_Export_Energy_WH']} " f"< {self._last} cycle {self._count} of " f"{self._platform.battery_energy_reset_cycles}" ) @@ -2209,8 +2209,8 @@ def native_value(self): ): _LOGGER.warning( ( - "Battery Import Energy went backwards: Current value " # noqa: B950 - f"{self._platform.decoded_model['B_Import_Energy_WH']} " # noqa: B950 + "Battery Import Energy went backwards: Current value " + f"{self._platform.decoded_model['B_Import_Energy_WH']} " f"is less than last value of {self._last}" ) ) @@ -2221,7 +2221,7 @@ def native_value(self): _LOGGER.debug( ( "B_Import_Energy went backwards: " - f"{self._platform.decoded_model['B_Import_Energy_WH']} " # noqa: B950 + f"{self._platform.decoded_model['B_Import_Energy_WH']} " f"< {self._last} cycle {self._count} of " f"{self._platform.battery_energy_reset_cycles}" ) From 58ad4f86297b31d4cc20201799ece81cbf8ba591 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:53:40 -0700 Subject: [PATCH 03/13] Disable flake8 (replaced by ruff) --- .github/workflows/linter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 9ecfa1ae..4e937b94 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -28,5 +28,6 @@ jobs: VALIDATE_PYTHON_PYLINT: false VALIDATE_PYTHON_PYINK: false VALIDATE_PYTHON_BLACK: false + VALIDATE_PYTHON_FLAKE8: false DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 4ff7c1dcb3355404115a55e88e6ec977d848edd2 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:55:34 -0700 Subject: [PATCH 04/13] Disable isort (replaced by ruff) --- .github/workflows/linter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 4e937b94..b1263dd9 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -29,5 +29,6 @@ jobs: VALIDATE_PYTHON_PYINK: false VALIDATE_PYTHON_BLACK: false VALIDATE_PYTHON_FLAKE8: false + VALIDATE_PYTHON_ISORT: false DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b60fdd89c9f58b1cb05e1478d5b97fda854b7751 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:58:43 -0700 Subject: [PATCH 05/13] Create pyproject.toml --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..3320acbb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[tool.ruff] +line-length = 80 + +[tool.ruff.lint] +extend-select = ["I"] +ignore = ["E701"] From b73aff1878ab16fb80c045571d2a4c369f5a2711 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:58:47 -0700 Subject: [PATCH 06/13] Delete setup.cfg --- setup.cfg | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index dd6412ad..00000000 --- a/setup.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[flake8] -max-line-length = 80 -extend-select = B950 -extend-ignore = E203,E501,E701 - -[isort] -profile = black From a3beae2ffdbb4206a012cb1f774cb1b62578c12d Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:59:39 -0700 Subject: [PATCH 07/13] Format with ruff --- .../solaredge_modbus_multi/__init__.py | 32 +- .../solaredge_modbus_multi/binary_sensor.py | 7 +- .../solaredge_modbus_multi/button.py | 20 +- .../solaredge_modbus_multi/config_flow.py | 94 +++- .../solaredge_modbus_multi/diagnostics.py | 15 +- .../solaredge_modbus_multi/helpers.py | 4 +- .../solaredge_modbus_multi/hub.py | 219 ++++++-- .../solaredge_modbus_multi/number.py | 68 ++- .../solaredge_modbus_multi/repairs.py | 10 +- .../solaredge_modbus_multi/scanner.py | 8 +- .../solaredge_modbus_multi/select.py | 46 +- .../solaredge_modbus_multi/sensor.py | 530 +++++++++++++----- .../solaredge_modbus_multi/switch.py | 22 +- 13 files changed, 784 insertions(+), 291 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/__init__.py b/custom_components/solaredge_modbus_multi/__init__.py index 3a168559..7641e4c8 100644 --- a/custom_components/solaredge_modbus_multi/__init__.py +++ b/custom_components/solaredge_modbus_multi/__init__.py @@ -8,7 +8,12 @@ import voluptuous as vol from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SCAN_INTERVAL, Platform +from homeassistant.const import ( + CONF_HOST, + CONF_PORT, + CONF_SCAN_INTERVAL, + Platform, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.typing import ConfigType @@ -99,7 +104,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: solaredge_hub = hass.data[DOMAIN][entry.entry_id]["hub"] await solaredge_hub.shutdown() - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + unload_ok = await hass.config_entries.async_unload_platforms( + entry, PLATFORMS + ) if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) @@ -152,13 +159,17 @@ async def async_remove_config_entry_device( for device_id in this_device_ids: if device_id in known_devices: - _LOGGER.error(f"Unable to remove entry: device {device_id} is in use") + _LOGGER.error( + f"Unable to remove entry: device {device_id} is in use" + ) return False return True -async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: +async def async_migrate_entry( + hass: HomeAssistant, config_entry: ConfigEntry +) -> bool: """Migrate old entry.""" _LOGGER.debug( "Migrating from config version " @@ -213,7 +224,9 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> # and the "leader" modbus unit id can't be known programmatically. old_unique_id = config_entry.unique_id - new_unique_id = f"{config_entry_data[CONF_HOST]}:{config_entry_data[CONF_PORT]}" + new_unique_id = ( + f"{config_entry_data[CONF_HOST]}:{config_entry_data[CONF_PORT]}" + ) _LOGGER.warning( "Migrating config entry unique ID from %s to %s", @@ -235,7 +248,10 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> class SolarEdgeCoordinator(TimestampDataUpdateCoordinator): def __init__( - self, hass: HomeAssistant, hub: SolarEdgeModbusMultiHub, scan_interval: int + self, + hass: HomeAssistant, + hub: SolarEdgeModbusMultiHub, + scan_interval: int, ): super().__init__( hass, @@ -298,7 +314,9 @@ async def _refresh_modbus_data_with_retry( if not isinstance(ex, ex_type): raise ex if 0 < limit <= attempt: - _LOGGER.debug(f"No more data refresh attempts (maximum {limit})") + _LOGGER.debug( + f"No more data refresh attempts (maximum {limit})" + ) raise ex _LOGGER.debug(f"Failed data refresh attempt {attempt}") diff --git a/custom_components/solaredge_modbus_multi/binary_sensor.py b/custom_components/solaredge_modbus_multi/binary_sensor.py index 09846016..dec8964e 100644 --- a/custom_components/solaredge_modbus_multi/binary_sensor.py +++ b/custom_components/solaredge_modbus_multi/binary_sensor.py @@ -31,7 +31,9 @@ async def async_setup_entry( for inverter in hub.inverters: if hub.option_detect_extras and inverter.advanced_power_control: - entities.append(AdvPowerControlEnabled(inverter, config_entry, coordinator)) + entities.append( + AdvPowerControlEnabled(inverter, config_entry, coordinator) + ) entities.append(GridStatusOnOff(inverter, config_entry, coordinator)) @@ -108,7 +110,8 @@ class GridStatusOnOff(SolarEdgeBinarySensorBase): @property def available(self) -> bool: return ( - super().available and "I_Grid_Status" in self._platform.decoded_model.keys() + super().available + and "I_Grid_Status" in self._platform.decoded_model.keys() ) @property diff --git a/custom_components/solaredge_modbus_multi/button.py b/custom_components/solaredge_modbus_multi/button.py index e255cc92..80b16c1f 100644 --- a/custom_components/solaredge_modbus_multi/button.py +++ b/custom_components/solaredge_modbus_multi/button.py @@ -28,15 +28,21 @@ async def async_setup_entry( entities = [] for inverter in hub.inverters: - entities.append(SolarEdgeRefreshButton(inverter, config_entry, coordinator)) + entities.append( + SolarEdgeRefreshButton(inverter, config_entry, coordinator) + ) """ Power Control Block """ if hub.option_detect_extras and inverter.advanced_power_control: entities.append( - SolarEdgeCommitControlSettings(inverter, config_entry, coordinator) + SolarEdgeCommitControlSettings( + inverter, config_entry, coordinator + ) ) entities.append( - SolarEdgeDefaultControlSettings(inverter, config_entry, coordinator) + SolarEdgeDefaultControlSettings( + inverter, config_entry, coordinator + ) ) if entities: @@ -118,7 +124,9 @@ async def async_press(self) -> None: await self._platform.write_registers( address=61696, payload=ModbusClientMixin.convert_to_registers( - 1, data_type=ModbusClientMixin.DATATYPE.UINT16, word_order="little" + 1, + data_type=ModbusClientMixin.DATATYPE.UINT16, + word_order="little", ), ) await self.async_update() @@ -148,7 +156,9 @@ async def async_press(self) -> None: await self._platform.write_registers( address=61697, payload=ModbusClientMixin.convert_to_registers( - 1, data_type=ModbusClientMixin.DATATYPE.UINT16, word_order="little" + 1, + data_type=ModbusClientMixin.DATATYPE.UINT16, + word_order="little", ), ) await self.async_update() diff --git a/custom_components/solaredge_modbus_multi/config_flow.py b/custom_components/solaredge_modbus_multi/config_flow.py index ae1d28e2..458978ea 100644 --- a/custom_components/solaredge_modbus_multi/config_flow.py +++ b/custom_components/solaredge_modbus_multi/config_flow.py @@ -9,8 +9,17 @@ import homeassistant.helpers.config_validation as cv import voluptuous as vol from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry, ConfigFlowResult, OptionsFlow -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_SCAN_INTERVAL +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlowResult, + OptionsFlow, +) +from homeassistant.const import ( + CONF_HOST, + CONF_NAME, + CONF_PORT, + CONF_SCAN_INTERVAL, +) from homeassistant.core import callback from homeassistant.data_entry_flow import AbortFlow from homeassistant.exceptions import HomeAssistantError @@ -43,17 +52,23 @@ class ScanNoResponseError(HomeAssistantError): pass -def generate_config_schema(step_id: str, user_input: dict[str, Any]) -> vol.Schema: +def generate_config_schema( + step_id: str, user_input: dict[str, Any] +) -> vol.Schema: """Generate config flow or repair schema.""" schema: dict[vol.Marker, Any] = {} if step_id == "user": - schema |= {vol.Required(CONF_NAME, default=user_input[CONF_NAME]): cv.string} + schema |= { + vol.Required(CONF_NAME, default=user_input[CONF_NAME]): cv.string + } if step_id in ["reconfigure", "confirm", "user"]: schema |= { vol.Required(CONF_HOST, default=user_input[CONF_HOST]): cv.string, - vol.Required(CONF_PORT, default=user_input[CONF_PORT]): vol.Coerce(int), + vol.Required(CONF_PORT, default=user_input[CONF_PORT]): vol.Coerce( + int + ), vol.Required( f"{ConfName.DEVICE_LIST}", default=user_input[ConfName.DEVICE_LIST], @@ -83,14 +98,18 @@ def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: """Create the options flow for SolarEdge Modbus Multi.""" return SolaredgeModbusMultiOptionsFlowHandler() - async def _async_update_progress_bar(self, scanned: int, total: int) -> None: + async def _async_update_progress_bar( + self, scanned: int, total: int + ) -> None: try: progress = scanned / total if total > 0 else 0 self.async_update_progress(progress) except asyncio.CancelledError: pass - async def _async_scan_devices(self, user_input: dict[str, Any]) -> list[int]: + async def _async_scan_devices( + self, user_input: dict[str, Any] + ) -> list[int]: """Scanner job for async_create_task""" scanner = SolarEdgeDeviceScanner( host=user_input[CONF_HOST], @@ -180,7 +199,9 @@ async def async_step_scan_ask_host( elif not 1 <= user_input[CONF_PORT] <= 65535: errors[CONF_PORT] = "invalid_tcp_port" else: - new_unique_id = f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" + new_unique_id = ( + f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" + ) await self.async_set_unique_id(new_unique_id) self._abort_if_unique_id_configured() @@ -211,11 +232,15 @@ async def async_step_scan_ask_host( step_id="scan_ask_host", data_schema=vol.Schema( { - vol.Optional(CONF_NAME, default=user_input[CONF_NAME]): cv.string, - vol.Required(CONF_HOST, default=user_input[CONF_HOST]): cv.string, - vol.Required(CONF_PORT, default=user_input[CONF_PORT]): vol.Coerce( - int - ), + vol.Optional( + CONF_NAME, default=user_input[CONF_NAME] + ): cv.string, + vol.Required( + CONF_HOST, default=user_input[CONF_HOST] + ): cv.string, + vol.Required( + CONF_PORT, default=user_input[CONF_PORT] + ): vol.Coerce(int), }, ), errors=errors, @@ -342,11 +367,15 @@ async def async_step_manual( step_id="manual", data_schema=vol.Schema( { - vol.Optional(CONF_NAME, default=user_input[CONF_NAME]): cv.string, - vol.Required(CONF_HOST, default=user_input[CONF_HOST]): cv.string, - vol.Required(CONF_PORT, default=user_input[CONF_PORT]): vol.Coerce( - int - ), + vol.Optional( + CONF_NAME, default=user_input[CONF_NAME] + ): cv.string, + vol.Required( + CONF_HOST, default=user_input[CONF_HOST] + ): cv.string, + vol.Required( + CONF_PORT, default=user_input[CONF_PORT] + ): vol.Coerce(int), vol.Required( f"{ConfName.DEVICE_LIST}", default=user_input[ConfName.DEVICE_LIST], @@ -364,7 +393,9 @@ async def async_step_features_info( entry = self._pending_entry self._pending_entry = None - return self.async_create_entry(title=entry["title"], data=entry["data"]) + return self.async_create_entry( + title=entry["title"], data=entry["data"] + ) return self.async_show_form( step_id="features_info", @@ -404,7 +435,9 @@ async def async_step_reconfigure( user_input[ConfName.DEVICE_LIST] = device_list_from_string( user_input[ConfName.DEVICE_LIST] ) - this_unique_id = f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" + this_unique_id = ( + f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" + ) if this_unique_id != config_entry.unique_id: self._async_abort_entries_match( @@ -430,7 +463,9 @@ async def async_step_reconfigure( user_input = { CONF_HOST: config_entry.data.get(CONF_HOST), - CONF_PORT: config_entry.data.get(CONF_PORT, ConfDefaultInt.PORT), + CONF_PORT: config_entry.data.get( + CONF_PORT, ConfDefaultInt.PORT + ), ConfName.DEVICE_LIST: reconfig_device_list, } @@ -469,7 +504,9 @@ async def async_step_init( return await self.async_step_adv_pwr_ctl() else: - return self.async_create_entry(title="", data=user_input) + return self.async_create_entry( + title="", data=user_input + ) else: user_input = { @@ -477,19 +514,22 @@ async def async_step_init( CONF_SCAN_INTERVAL, ConfDefaultInt.SCAN_INTERVAL ), ConfName.KEEP_MODBUS_OPEN: self.config_entry.options.get( - ConfName.KEEP_MODBUS_OPEN, bool(ConfDefaultFlag.KEEP_MODBUS_OPEN) + ConfName.KEEP_MODBUS_OPEN, + bool(ConfDefaultFlag.KEEP_MODBUS_OPEN), ), ConfName.DETECT_METERS: self.config_entry.options.get( ConfName.DETECT_METERS, bool(ConfDefaultFlag.DETECT_METERS) ), ConfName.DETECT_BATTERIES: self.config_entry.options.get( - ConfName.DETECT_BATTERIES, bool(ConfDefaultFlag.DETECT_BATTERIES) + ConfName.DETECT_BATTERIES, + bool(ConfDefaultFlag.DETECT_BATTERIES), ), ConfName.DETECT_EXTRAS: self.config_entry.options.get( ConfName.DETECT_EXTRAS, bool(ConfDefaultFlag.DETECT_EXTRAS) ), ConfName.ADV_PWR_CONTROL: self.config_entry.options.get( - ConfName.ADV_PWR_CONTROL, bool(ConfDefaultFlag.ADV_PWR_CONTROL) + ConfName.ADV_PWR_CONTROL, + bool(ConfDefaultFlag.ADV_PWR_CONTROL), ), ConfName.SLEEP_AFTER_WRITE: self.config_entry.options.get( ConfName.SLEEP_AFTER_WRITE, ConfDefaultInt.SLEEP_AFTER_WRITE @@ -579,7 +619,9 @@ async def async_step_battery_options( ): cv.boolean, vol.Optional( f"{ConfName.BATTERY_ENERGY_RESET_CYCLES}", - default=user_input[ConfName.BATTERY_ENERGY_RESET_CYCLES], + default=user_input[ + ConfName.BATTERY_ENERGY_RESET_CYCLES + ], ): vol.Coerce(int), vol.Optional( f"{ConfName.BATTERY_RATING_ADJUST}", diff --git a/custom_components/solaredge_modbus_multi/diagnostics.py b/custom_components/solaredge_modbus_multi/diagnostics.py index fb4cbe12..cfb266d1 100644 --- a/custom_components/solaredge_modbus_multi/diagnostics.py +++ b/custom_components/solaredge_modbus_multi/diagnostics.py @@ -14,7 +14,12 @@ REDACT_CONFIG = {"unique_id", "host"} 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_BATTERY = { + "identifiers", + "B_SerialNumber", + "serial_number", + "via_device", +} REDACT_EVSE = {"identifiers", "C_SerialNumber", "serial_number"} @@ -44,7 +49,9 @@ async def async_get_config_entry_diagnostics( data: dict[str, Any] = { "pymodbus_version": hub.pymodbus_version, - "config_entry": async_redact_data(config_entry.as_dict(), REDACT_CONFIG), + "config_entry": async_redact_data( + config_entry.as_dict(), REDACT_CONFIG + ), "yaml": async_redact_data(hass.data[DOMAIN]["yaml"], REDACT_CONFIG), } @@ -60,7 +67,9 @@ async def async_get_config_entry_diagnostics( "is_mmppt": inverter.is_mmppt, "mmppt": format_values(inverter.decoded_mmppt), "has_battery": inverter.has_battery, - "storage_control": format_values(inverter.decoded_storage_control), + "storage_control": format_values( + inverter.decoded_storage_control + ), "use_status_vendor4": inverter.use_status_vendor4, } } diff --git a/custom_components/solaredge_modbus_multi/helpers.py b/custom_components/solaredge_modbus_multi/helpers.py index 92b4e0f0..4c900b80 100644 --- a/custom_components/solaredge_modbus_multi/helpers.py +++ b/custom_components/solaredge_modbus_multi/helpers.py @@ -29,7 +29,9 @@ def int_list_to_string(int_list: list[int]) -> str: 7fc8d3e02d9d9011c25c80149eb88318e7f50d0e/pymodbus/client/mixin.py#L719 """ byte_data = b"".join(i.to_bytes(2, "big") for i in int_list) - return byte_data.decode("utf-8", errors="ignore").replace("\x00", "").rstrip() + return ( + byte_data.decode("utf-8", errors="ignore").replace("\x00", "").rstrip() + ) def update_accum(self, accum_value: int) -> None: diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index d1a62c5e..0a361c68 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -152,7 +152,8 @@ def __init__( ConfName.KEEP_MODBUS_OPEN, bool(ConfDefaultFlag.KEEP_MODBUS_OPEN) ) self._adv_storage_control = entry_options.get( - ConfName.ADV_STORAGE_CONTROL, bool(ConfDefaultFlag.ADV_STORAGE_CONTROL) + ConfName.ADV_STORAGE_CONTROL, + bool(ConfDefaultFlag.ADV_STORAGE_CONTROL), ) self._adv_site_limit_control = entry_options.get( ConfName.ADV_SITE_LIMIT_CONTROL, @@ -294,7 +295,9 @@ async def _async_init_solaredge(self) -> None: except DeviceInvalid as e: # Inverters are mandatory, but if the Device ID is invalid or not responding # skip it and warn the user instead of failing the entire hub setup - _LOGGER.error(f"Inverter at {self.hub_host} ID {inverter_unit_id}: {e}") + _LOGGER.error( + f"Inverter at {self.hub_host} ID {inverter_unit_id}: {e}" + ) ir.async_create_issue( self._hass, DOMAIN, @@ -327,7 +330,9 @@ async def _async_init_solaredge(self) -> None: _LOGGER.debug( f"Looking for meter I{inverter_unit_id}M{meter_id}" ) - new_meter = SolarEdgeMeter(inverter_unit_id, meter_id, self) + new_meter = SolarEdgeMeter( + inverter_unit_id, meter_id, self + ) await new_meter.init_device() for meter in self.meters: @@ -406,7 +411,11 @@ async def _async_init_solaredge(self) -> None: for evse in self.evses: await evse.read_modbus_data() - except (ModbusReadError, ModbusIllegalFunction, ModbusIllegalValue) as e: + except ( + ModbusReadError, + ModbusIllegalFunction, + ModbusIllegalValue, + ) as e: await self.disconnect() raise HubInitFailed(f"Read error: {e}") @@ -489,7 +498,11 @@ async def async_refresh_modbus_data(self) -> bool: for evse in self.evses: await evse.read_modbus_data() - except (ModbusReadError, ModbusIllegalFunction, ModbusIllegalValue) as e: + except ( + ModbusReadError, + ModbusIllegalFunction, + ModbusIllegalValue, + ) as e: await self.disconnect() raise DataUpdateFailed(f"Update failed: {e}") @@ -593,24 +606,34 @@ async def modbus_read_holding_registers(self, unit, address, rcount): if "device_id" in sig.parameters: result = await self._client.read_holding_registers( - address=self._rr_address, count=self._rr_count, device_id=self._rr_unit + address=self._rr_address, + count=self._rr_count, + device_id=self._rr_unit, ) else: result = await self._client.read_holding_registers( - address=self._rr_address, count=self._rr_count, slave=self._rr_unit + address=self._rr_address, + count=self._rr_count, + slave=self._rr_unit, ) - _LOGGER.debug(f"unit={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"unit={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"unit={self._rr_unit} Read IllegalAddress: {result}") + _LOGGER.debug( + f"unit={self._rr_unit} Read IllegalAddress: {result}" + ) raise ModbusIllegalAddress(result) if result.exception_code == ModbusExceptions.IllegalFunction: @@ -620,7 +643,9 @@ async def modbus_read_holding_registers(self, unit, address, rcount): raise ModbusIllegalFunction(result) if result.exception_code == ModbusExceptions.IllegalValue: - _LOGGER.debug(f"unit={self._rr_unit} Read IllegalValue: {result}") + _LOGGER.debug( + f"unit={self._rr_unit} Read IllegalValue: {result}" + ) raise ModbusIllegalValue(result) raise ModbusReadError(result) @@ -720,7 +745,9 @@ async def write_registers(self, unit: int, address: int, payload) -> None: ) if result.exception_code == ModbusExceptions.IllegalValue: - _LOGGER.debug(f"Unit {self._wr_unit} Write IllegalValue: {result}") + _LOGGER.debug( + f"Unit {self._wr_unit} Write IllegalValue: {result}" + ) raise HomeAssistantError( "Value invalid for device at ID {self._wr_unit}." ) @@ -848,17 +875,25 @@ def coordinator_timeout(self) -> int: if not self.initalized: this_timeout = SolarEdgeTimeouts.Inverter * self.number_of_inverters this_timeout += SolarEdgeTimeouts.Init * self.number_of_inverters - this_timeout += (SolarEdgeTimeouts.Device * 2) * 3 # max 3 per inverter - this_timeout += (SolarEdgeTimeouts.Device * 2) * 2 # max 2 per inverter + this_timeout += ( + SolarEdgeTimeouts.Device * 2 + ) * 3 # max 3 per inverter + this_timeout += ( + SolarEdgeTimeouts.Device * 2 + ) * 2 # max 2 per inverter if self.option_detect_extras: - this_timeout += (SolarEdgeTimeouts.Read * 3) * self.number_of_inverters + this_timeout += ( + SolarEdgeTimeouts.Read * 3 + ) * self.number_of_inverters else: this_timeout = SolarEdgeTimeouts.Inverter * self.number_of_inverters this_timeout += SolarEdgeTimeouts.Device * self.number_of_meters this_timeout += SolarEdgeTimeouts.Device * self.number_of_batteries if self.option_detect_extras: - this_timeout += (SolarEdgeTimeouts.Read * 3) * self.number_of_inverters + this_timeout += ( + SolarEdgeTimeouts.Read * 3 + ) * self.number_of_inverters this_timeout = this_timeout / 1000 @@ -918,7 +953,9 @@ async def init_device(self) -> None: "C_SunSpec_Length", "C_Device_address", ] - uint16_data = inverter_data.registers[2:4] + [inverter_data.registers[68]] + uint16_data = inverter_data.registers[2:4] + [ + inverter_data.registers[68] + ] self.decoded_common.update( dict( zip( @@ -992,10 +1029,14 @@ async def init_device(self) -> None: ), ) - self.hub.inverter_common[self.inverter_unit_id] = self.decoded_common + self.hub.inverter_common[self.inverter_unit_id] = ( + self.decoded_common + ) except (ModbusIOError, ModbusIOException): - raise DeviceInvalid(f"No response from Device ID {self.inverter_unit_id}") + raise DeviceInvalid( + f"No response from Device ID {self.inverter_unit_id}" + ) except ModbusIllegalAddress: raise DeviceInvalid( @@ -1092,15 +1133,22 @@ async def init_device(self) -> None: self._use_status_vendor4 = this_ver >= AwesomeVersion( STATUS_VENDOR4_VERSION ) - except (AwesomeVersionCompareException, AwesomeVersionStrategyException) as e: + 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)) - _LOGGER.debug(f"I{self.inverter_unit_id} MMPPT Unit {unit_index}") + self.mmppt_units.append( + SolarEdgeMMPPTUnit(self, self.hub, unit_index) + ) + _LOGGER.debug( + f"I{self.inverter_unit_id} MMPPT Unit {unit_index}" + ) async def read_modbus_data(self) -> None: """Read and update dynamic modbus registers.""" @@ -1240,7 +1288,9 @@ async def read_modbus_data(self) -> None: or self.decoded_model["C_SunSpec_DID"] not in [101, 102, 103] or self.decoded_model["C_SunSpec_Length"] != 50 ): - raise DeviceInvalid(f"Inverter {self.inverter_unit_id} not usable.") + raise DeviceInvalid( + f"Inverter {self.inverter_unit_id} not usable." + ) except ModbusIOError: raise ModbusReadError( @@ -1265,7 +1315,9 @@ async def read_modbus_data(self) -> None: try: inverter_data = await self.hub.modbus_read_holding_registers( - unit=self.inverter_unit_id, address=40123, rcount=mmppt_registers + unit=self.inverter_unit_id, + address=40123, + rcount=mmppt_registers, ) if self.decoded_mmppt["mmppt_Units"] in [2, 3]: @@ -1316,7 +1368,8 @@ async def read_modbus_data(self) -> None: int_list_to_string( ModbusClientMixin.convert_from_registers( inverter_data.registers[ - 9 + unit_offset : 17 + unit_offset + 9 + unit_offset : 17 + + unit_offset ], data_type=ModbusClientMixin.DATATYPE.UINT16, ) @@ -1325,7 +1378,11 @@ async def read_modbus_data(self) -> None: ( "Tmp", ModbusClientMixin.convert_from_registers( - [inverter_data.registers[24 + unit_offset]], + [ + inverter_data.registers[ + 24 + unit_offset + ] + ], data_type=ModbusClientMixin.DATATYPE.INT16, ), ), @@ -1365,7 +1422,9 @@ async def read_modbus_data(self) -> None: "DCEvt", ] uint32_data = ( - inverter_data.registers[20 + unit_offset : 22 + unit_offset] + inverter_data.registers[ + 20 + unit_offset : 22 + unit_offset + ] + inverter_data.registers[ 22 + unit_offset : 24 + unit_offset ] @@ -1397,12 +1456,15 @@ async def read_modbus_data(self) -> None: """ Global Dynamic Power Control and Status """ if self.hub.option_detect_extras is True and ( - self.global_power_control is True or self.global_power_control is None + self.global_power_control is True + or self.global_power_control is None ): try: async with asyncio.timeout(SolarEdgeTimeouts.Read / 1000): - inverter_data = await self.hub.modbus_read_holding_registers( - unit=self.inverter_unit_id, address=61440, rcount=4 + inverter_data = ( + await self.hub.modbus_read_holding_registers( + unit=self.inverter_unit_id, address=61440, rcount=4 + ) ) self.decoded_model.update( @@ -1471,12 +1533,15 @@ async def read_modbus_data(self) -> None: """ Advanced Power Control """ """ Power Control Block """ if self.hub.option_detect_extras is True and ( - self.advanced_power_control is True or self.advanced_power_control is None + self.advanced_power_control is True + or self.advanced_power_control is None ): try: async with asyncio.timeout(SolarEdgeTimeouts.Read / 1000): - inverter_data = await self.hub.modbus_read_holding_registers( - unit=self.inverter_unit_id, address=61696, rcount=86 + inverter_data = ( + await self.hub.modbus_read_holding_registers( + unit=self.inverter_unit_id, address=61696, rcount=86 + ) ) int32_fields = [ @@ -1544,7 +1609,8 @@ async def read_modbus_data(self) -> None: "PwrVsFreqX_1", ] float32_data = ( - inverter_data.registers[10:66] + inverter_data.registers[70:86] + inverter_data.registers[10:66] + + inverter_data.registers[70:86] ) self.decoded_model.update( dict( @@ -1592,8 +1658,10 @@ async def read_modbus_data(self) -> None: ) async with asyncio.timeout(SolarEdgeTimeouts.Read / 1000): - inverter_data = await self.hub.modbus_read_holding_registers( - unit=self.inverter_unit_id, address=61782, rcount=84 + inverter_data = ( + await self.hub.modbus_read_holding_registers( + unit=self.inverter_unit_id, address=61782, rcount=84 + ) ) float32_fields = [ @@ -1662,7 +1730,8 @@ async def read_modbus_data(self) -> None: "PwrSoftStartTime", ] uint32_data = ( - inverter_data.registers[32:36] + inverter_data.registers[52:56] + inverter_data.registers[32:36] + + inverter_data.registers[52:56] ) self.decoded_model.update( dict( @@ -1792,7 +1861,9 @@ async def read_modbus_data(self) -> None: except KeyError: pass - _LOGGER.debug(f"I{self.inverter_unit_id}: Ext_Prod_Max NOT available") + _LOGGER.debug( + f"I{self.inverter_unit_id}: Ext_Prod_Max NOT available" + ) except ModbusIOError: raise ModbusReadError( @@ -1824,7 +1895,9 @@ async def read_modbus_data(self) -> None: except ModbusIllegalAddress: self._grid_status = False - _LOGGER.debug(f"I{self.inverter_unit_id}: Grid On/Off NOT available") + _LOGGER.debug( + f"I{self.inverter_unit_id}: Grid On/Off NOT available" + ) except ModbusIOException as e: _LOGGER.debug( @@ -1896,7 +1969,8 @@ async def read_modbus_data(self) -> None: "discharge_limit", ] float32_data = ( - inverter_data.registers[2:6] + inverter_data.registers[10:14] + inverter_data.registers[2:6] + + inverter_data.registers[10:14] ) self.decoded_storage_control.update( dict( @@ -1931,7 +2005,9 @@ async def read_modbus_data(self) -> None: if isinstance(value, float): display_value = float_to_hex(value) else: - display_value = hex(value) if isinstance(value, int) else value + display_value = ( + hex(value) if isinstance(value, int) else value + ) _LOGGER.debug( f"I{self.inverter_unit_id}: " f"{name} {display_value} {type(value)}" @@ -1993,7 +2069,10 @@ class SolarEdgeMMPPTUnit: """Defines a SolarEdge inverter MMPPT unit.""" def __init__( - self, inverter: SolarEdgeInverter, hub: SolarEdgeModbusMultiHub, unit: int + self, + inverter: SolarEdgeInverter, + hub: SolarEdgeModbusMultiHub, + unit: int, ) -> None: self.inverter = inverter self.hub = hub @@ -2003,7 +2082,9 @@ def __init__( @property def online(self) -> bool: """Device is online.""" - return self.hub.online and self.inverter.is_mmppt and self.inverter.online + return ( + self.hub.online and self.inverter.is_mmppt and self.inverter.online + ) @property def device_info(self) -> DeviceInfo: @@ -2159,7 +2240,9 @@ async def init_device(self) -> None: ) except ModbusIOError: - raise DeviceInvalid(f"No response from inverter ID {self.inverter_unit_id}") + raise DeviceInvalid( + f"No response from inverter ID {self.inverter_unit_id}" + ) except ModbusIllegalAddress: raise DeviceInvalid(f"Meter {self.meter_id}: unsupported address") @@ -2170,9 +2253,7 @@ async def init_device(self) -> None: self.fw_version = self.decoded_common["C_Version"] self.serial = self.decoded_common["C_SerialNumber"] self.device_address = self.decoded_common["C_Device_address"] - self.name = ( - f"{self.hub.hub_id.capitalize()} I{self.inverter_unit_id} M{self.meter_id}" - ) + self.name = f"{self.hub.hub_id.capitalize()} I{self.inverter_unit_id} M{self.meter_id}" inverter_model = self.inverter_common["C_Model"] inerter_serial = self.inverter_common["C_SerialNumber"] @@ -2392,7 +2473,9 @@ def __init__( async def init_device(self) -> None: try: battery_info = await self.hub.modbus_read_holding_registers( - unit=self.inverter_unit_id, address=self.start_address, rcount=68 + unit=self.inverter_unit_id, + address=self.start_address, + rcount=68, ) self.decoded_common = dict( @@ -2460,7 +2543,9 @@ async def init_device(self) -> None: if isinstance(value, float): display_value = float_to_hex(value) else: - display_value = hex(value) if isinstance(value, int) else value + display_value = ( + hex(value) if isinstance(value, int) else value + ) _LOGGER.debug( ( f"I{self.inverter_unit_id}B{self.battery_id}: " @@ -2469,26 +2554,30 @@ async def init_device(self) -> None: ) except ModbusIOError: - raise DeviceInvalid(f"No response from inverter ID {self.inverter_unit_id}") + raise DeviceInvalid( + f"No response from inverter ID {self.inverter_unit_id}" + ) except ModbusIllegalAddress: - raise DeviceInvalid(f"Battery {self.battery_id} unsupported address") + raise DeviceInvalid( + f"Battery {self.battery_id} unsupported address" + ) self.decoded_common["B_Manufacturer"] = self.decoded_common[ "B_Manufacturer" ].removesuffix(self.decoded_common["B_SerialNumber"]) - self.decoded_common["B_Model"] = self.decoded_common["B_Model"].removesuffix( - self.decoded_common["B_SerialNumber"] - ) + self.decoded_common["B_Model"] = self.decoded_common[ + "B_Model" + ].removesuffix(self.decoded_common["B_SerialNumber"]) # Remove ASCII control characters from descriptive strings ascii_ctrl_chars = dict.fromkeys(range(32)) self.decoded_common["B_Manufacturer"] = self.decoded_common[ "B_Manufacturer" ].translate(ascii_ctrl_chars) - self.decoded_common["B_Model"] = self.decoded_common["B_Model"].translate( - ascii_ctrl_chars - ) + self.decoded_common["B_Model"] = self.decoded_common[ + "B_Model" + ].translate(ascii_ctrl_chars) self.decoded_common["B_SerialNumber"] = self.decoded_common[ "B_SerialNumber" ].translate(ascii_ctrl_chars) @@ -2498,7 +2587,9 @@ async def init_device(self) -> None: == hex(SunSpecNotImpl.FLOAT32) or self.decoded_common["B_RatedEnergy"] <= 0 ): - raise DeviceInvalid(f"Battery {self.battery_id} not usable (rating <=0)") + raise DeviceInvalid( + f"Battery {self.battery_id} not usable (rating <=0)" + ) self.manufacturer = self.decoded_common["B_Manufacturer"] self.model = self.decoded_common["B_Model"] @@ -2825,16 +2916,22 @@ async def read_modbus_data(self) -> None: if isinstance(value, float): display_value = float_to_hex(value) else: - display_value = hex(value) if isinstance(value, int) else value + 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}: EVSE register(s) 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}") + raise ModbusReadError( + f"No response from EVSE ID {self.evse_unit_id}" + ) @property def online(self) -> bool: diff --git a/custom_components/solaredge_modbus_multi/number.py b/custom_components/solaredge_modbus_multi/number.py index eb9d734a..073a74fb 100644 --- a/custom_components/solaredge_modbus_multi/number.py +++ b/custom_components/solaredge_modbus_multi/number.py @@ -37,25 +37,41 @@ async def async_setup_entry( """Dynamic Power Control""" if hub.option_detect_extras and inverter.global_power_control: entities.append( - SolarEdgeActivePowerLimitSet(inverter, config_entry, coordinator) + SolarEdgeActivePowerLimitSet( + inverter, config_entry, coordinator + ) + ) + entities.append( + SolarEdgeCosPhiSet(inverter, config_entry, coordinator) ) - entities.append(SolarEdgeCosPhiSet(inverter, config_entry, coordinator)) """ Power Control Block """ if hub.option_detect_extras and inverter.advanced_power_control: - entities.append(SolarEdgePowerReduce(inverter, config_entry, coordinator)) - entities.append(SolarEdgeCurrentLimit(inverter, config_entry, coordinator)) + entities.append( + SolarEdgePowerReduce(inverter, config_entry, coordinator) + ) + entities.append( + SolarEdgeCurrentLimit(inverter, config_entry, coordinator) + ) """ Power Control Options: Storage Control """ if hub.option_storage_control is True: for inverter in hub.inverters: if inverter.decoded_storage_control is False: continue - entities.append(StorageACChargeLimit(inverter, config_entry, coordinator)) - entities.append(StorageBackupReserve(inverter, config_entry, coordinator)) - entities.append(StorageCommandTimeout(inverter, config_entry, coordinator)) + entities.append( + StorageACChargeLimit(inverter, config_entry, coordinator) + ) + entities.append( + StorageBackupReserve(inverter, config_entry, coordinator) + ) + entities.append( + StorageCommandTimeout(inverter, config_entry, coordinator) + ) if inverter.has_battery is True: - entities.append(StorageChargeLimit(inverter, config_entry, coordinator)) + entities.append( + StorageChargeLimit(inverter, config_entry, coordinator) + ) entities.append( StorageDischargeLimit(inverter, config_entry, coordinator) ) @@ -63,9 +79,13 @@ async def async_setup_entry( """ Power Control Options: Site Limit Control """ if hub.option_site_limit_control is True: for inverter in hub.inverters: - entities.append(SolarEdgeSiteLimit(inverter, config_entry, coordinator)) entities.append( - SolarEdgeExternalProductionMax(inverter, config_entry, coordinator) + SolarEdgeSiteLimit(inverter, config_entry, coordinator) + ) + entities.append( + SolarEdgeExternalProductionMax( + inverter, config_entry, coordinator + ) ) if entities: @@ -217,7 +237,8 @@ def available(self) -> bool: ) == hex(SunSpecNotImpl.FLOAT32) or self._platform.decoded_storage_control["backup_reserve"] < 0 - or self._platform.decoded_storage_control["backup_reserve"] > 100 + or self._platform.decoded_storage_control["backup_reserve"] + > 100 ): return False @@ -268,7 +289,8 @@ def available(self) -> bool: self._platform.decoded_storage_control is False or self._platform.decoded_storage_control["command_timeout"] == SunSpecNotImpl.UINT32 - or self._platform.decoded_storage_control["command_timeout"] > 86400 + or self._platform.decoded_storage_control["command_timeout"] + > 86400 ): return False @@ -317,7 +339,9 @@ def available(self) -> bool: try: if ( self._platform.decoded_storage_control is False - or float_to_hex(self._platform.decoded_storage_control["charge_limit"]) + or float_to_hex( + self._platform.decoded_storage_control["charge_limit"] + ) == hex(SunSpecNotImpl.FLOAT32) or self._platform.decoded_storage_control["charge_limit"] < 0 ): @@ -427,15 +451,17 @@ def name(self) -> str: @property def available(self) -> bool: try: - if float_to_hex(self._platform.decoded_model["E_Site_Limit"]) == hex( - SunSpecNotImpl.FLOAT32 - ): + if float_to_hex( + self._platform.decoded_model["E_Site_Limit"] + ) == hex(SunSpecNotImpl.FLOAT32): return False return super().available and ( (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 0) & 1 - or (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 1) & 1 - or (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 2) & 1 + or (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 1) + & 1 + or (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 2) + & 1 ) except (TypeError, KeyError): @@ -487,7 +513,8 @@ def available(self) -> bool: return ( super().available - and (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 10) & 1 + and (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 10) + & 1 ) except (TypeError, KeyError): @@ -539,7 +566,8 @@ def entity_registry_enabled_default(self) -> bool: def available(self) -> bool: try: if ( - self._platform.decoded_model["I_Power_Limit"] == SunSpecNotImpl.UINT16 + self._platform.decoded_model["I_Power_Limit"] + == SunSpecNotImpl.UINT16 or self._platform.decoded_model["I_Power_Limit"] > 100 or self._platform.decoded_model["I_Power_Limit"] < 0 ): diff --git a/custom_components/solaredge_modbus_multi/repairs.py b/custom_components/solaredge_modbus_multi/repairs.py index adf908e4..646c35f2 100644 --- a/custom_components/solaredge_modbus_multi/repairs.py +++ b/custom_components/solaredge_modbus_multi/repairs.py @@ -64,11 +64,11 @@ async def async_step_confirm( user_input[ConfName.DEVICE_LIST] = device_list_from_string( user_input[ConfName.DEVICE_LIST] ) - this_unique_id = f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" - existing_entry = ( - self.hass.config_entries.async_entry_for_domain_unique_id( - DOMAIN, this_unique_id - ) + this_unique_id = ( + f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" + ) + existing_entry = self.hass.config_entries.async_entry_for_domain_unique_id( + DOMAIN, this_unique_id ) if ( diff --git a/custom_components/solaredge_modbus_multi/scanner.py b/custom_components/solaredge_modbus_multi/scanner.py index c58c52dc..5b473216 100644 --- a/custom_components/solaredge_modbus_multi/scanner.py +++ b/custom_components/solaredge_modbus_multi/scanner.py @@ -187,7 +187,9 @@ async def disconnect(self) -> None: self._writer = None self._reader = None - def device_is_inverter(self, request: list[int], response: list[int]) -> int: + def device_is_inverter( + self, request: list[int], response: list[int] + ) -> int: """Check if device response matches SolarEdge inverter signature. Args: @@ -268,7 +270,9 @@ async def scan_device_id(self, device_id: int, timeout: float = 5.0) -> int: ) _LOGGER.debug(f" Received ({len(response)} bytes)") - _LOGGER.debug(f" {' '.join(format(x, '02x') for x in response)}") + _LOGGER.debug( + f" {' '.join(format(x, '02x') for x in response)}" + ) return self.FOUND diff --git a/custom_components/solaredge_modbus_multi/select.py b/custom_components/solaredge_modbus_multi/select.py index aa1c9fb4..02a50cff 100644 --- a/custom_components/solaredge_modbus_multi/select.py +++ b/custom_components/solaredge_modbus_multi/select.py @@ -37,17 +37,27 @@ async def async_setup_entry( for inverter in hub.inverters: """Power Control Options: Storage Control""" if hub.option_storage_control and inverter.decoded_storage_control: - entities.append(StorageControlMode(inverter, config_entry, coordinator)) - entities.append(StorageACChargePolicy(inverter, config_entry, coordinator)) - entities.append(StorageDefaultMode(inverter, config_entry, coordinator)) - entities.append(StorageCommandMode(inverter, config_entry, coordinator)) + entities.append( + StorageControlMode(inverter, config_entry, coordinator) + ) + entities.append( + StorageACChargePolicy(inverter, config_entry, coordinator) + ) + entities.append( + StorageDefaultMode(inverter, config_entry, coordinator) + ) + entities.append( + StorageCommandMode(inverter, config_entry, coordinator) + ) """ Power Control Options: Site Limit Control """ if hub.option_site_limit_control: entities.append( SolaredgeLimitControlMode(inverter, config_entry, coordinator) ) - entities.append(SolaredgeLimitControl(inverter, config_entry, coordinator)) + entities.append( + SolaredgeLimitControl(inverter, config_entry, coordinator) + ) """ Power Control Block """ if hub.option_detect_extras and inverter.advanced_power_control: @@ -136,7 +146,9 @@ def available(self) -> bool: @property def current_option(self) -> str: - return self._options[self._platform.decoded_storage_control["control_mode"]] + return self._options[ + self._platform.decoded_storage_control["control_mode"] + ] async def async_select_option(self, option: str) -> None: _LOGGER.debug(f"set {self.unique_id} to {option}") @@ -189,7 +201,9 @@ def available(self) -> bool: @property def current_option(self) -> str: - return self._options[self._platform.decoded_storage_control["ac_charge_policy"]] + return self._options[ + self._platform.decoded_storage_control["ac_charge_policy"] + ] async def async_select_option(self, option: str) -> None: _LOGGER.debug(f"set {self.unique_id} to {option}") @@ -246,7 +260,9 @@ def available(self) -> bool: @property def current_option(self) -> str: - return self._options[self._platform.decoded_storage_control["default_mode"]] + return self._options[ + self._platform.decoded_storage_control["default_mode"] + ] async def async_select_option(self, option: str) -> None: _LOGGER.debug(f"set {self.unique_id} to {option}") @@ -303,7 +319,9 @@ def available(self) -> bool: @property def current_option(self) -> str: - return self._options[self._platform.decoded_storage_control["command_mode"]] + return self._options[ + self._platform.decoded_storage_control["command_mode"] + ] async def async_select_option(self, option: str) -> None: _LOGGER.debug(f"set {self.unique_id} to {option}") @@ -328,7 +346,10 @@ def __init__(self, platform, config_entry, coordinator): @property def available(self) -> bool: try: - if self._platform.decoded_model["E_Lim_Ctl_Mode"] == SunSpecNotImpl.UINT16: + if ( + self._platform.decoded_model["E_Lim_Ctl_Mode"] + == SunSpecNotImpl.UINT16 + ): return None return super().available @@ -390,7 +411,10 @@ def __init__(self, platform, config_entry, coordinator): @property def available(self) -> bool: try: - if self._platform.decoded_model["E_Lim_Ctl"] == SunSpecNotImpl.UINT16: + if ( + self._platform.decoded_model["E_Lim_Ctl"] + == SunSpecNotImpl.UINT16 + ): return False return super().available diff --git a/custom_components/solaredge_modbus_multi/sensor.py b/custom_components/solaredge_modbus_multi/sensor.py index 67844074..d258f52e 100644 --- a/custom_components/solaredge_modbus_multi/sensor.py +++ b/custom_components/solaredge_modbus_multi/sensor.py @@ -64,23 +64,45 @@ async def async_setup_entry( entities = [] for inverter in hub.inverters: - entities.append(SolarEdgeLastUpdate(inverter, config_entry, coordinator)) + entities.append( + SolarEdgeLastUpdate(inverter, config_entry, coordinator) + ) entities.append(SolarEdgeDevice(inverter, config_entry, coordinator)) entities.append(Version(inverter, config_entry, coordinator)) - entities.append(SolarEdgeInverterStatus(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")) - entities.append(ACCurrentSensor(inverter, config_entry, coordinator, "C")) - entities.append(VoltageSensor(inverter, config_entry, coordinator, "AB")) - entities.append(VoltageSensor(inverter, config_entry, coordinator, "BC")) - entities.append(VoltageSensor(inverter, config_entry, coordinator, "CA")) - entities.append(VoltageSensor(inverter, config_entry, coordinator, "AN")) - entities.append(VoltageSensor(inverter, config_entry, coordinator, "BN")) - entities.append(VoltageSensor(inverter, config_entry, coordinator, "CN")) + entities.append( + ACCurrentSensor(inverter, config_entry, coordinator, "A") + ) + entities.append( + ACCurrentSensor(inverter, config_entry, coordinator, "B") + ) + entities.append( + ACCurrentSensor(inverter, config_entry, coordinator, "C") + ) + entities.append( + VoltageSensor(inverter, config_entry, coordinator, "AB") + ) + entities.append( + VoltageSensor(inverter, config_entry, coordinator, "BC") + ) + entities.append( + VoltageSensor(inverter, config_entry, coordinator, "CA") + ) + entities.append( + VoltageSensor(inverter, config_entry, coordinator, "AN") + ) + entities.append( + VoltageSensor(inverter, config_entry, coordinator, "BN") + ) + entities.append( + VoltageSensor(inverter, config_entry, coordinator, "CN") + ) entities.append(ACPower(inverter, config_entry, coordinator)) entities.append(ACFrequency(inverter, config_entry, coordinator)) entities.append(ACVoltAmp(inverter, config_entry, coordinator)) @@ -90,38 +112,54 @@ async def async_setup_entry( entities.append(DCCurrent(inverter, config_entry, coordinator)) entities.append(DCVoltage(inverter, config_entry, coordinator)) entities.append(DCPower(inverter, config_entry, coordinator)) - entities.append(HeatSinkTemperature(inverter, config_entry, coordinator)) + entities.append( + HeatSinkTemperature(inverter, config_entry, coordinator) + ) if hub.option_detect_extras and inverter.global_power_control: entities.append(SolarEdgeRRCR(inverter, config_entry, coordinator)) entities.append( SolarEdgeActivePowerLimit(inverter, config_entry, coordinator) ) - entities.append(SolarEdgeCosPhi(inverter, config_entry, coordinator)) + entities.append( + SolarEdgeCosPhi(inverter, config_entry, coordinator) + ) if hub.option_detect_extras and inverter.advanced_power_control: entities.append( - SolarEdgeCommitControlSettings(inverter, config_entry, coordinator) + SolarEdgeCommitControlSettings( + inverter, config_entry, coordinator + ) ) entities.append( - SolarEdgeDefaultControlSettings(inverter, config_entry, coordinator) + SolarEdgeDefaultControlSettings( + inverter, config_entry, coordinator + ) ) if inverter.is_mmppt: - entities.append(SolarEdgeMMPPTEvents(inverter, config_entry, coordinator)) + entities.append( + SolarEdgeMMPPTEvents(inverter, config_entry, coordinator) + ) for mmppt_unit in inverter.mmppt_units: entities.append( - SolarEdgeDCCurrentMMPPT(mmppt_unit, config_entry, coordinator) + SolarEdgeDCCurrentMMPPT( + mmppt_unit, config_entry, coordinator + ) ) entities.append( - SolarEdgeDCVoltageMMPPT(mmppt_unit, config_entry, coordinator) + SolarEdgeDCVoltageMMPPT( + mmppt_unit, config_entry, coordinator + ) ) entities.append( SolarEdgeDCPowerMMPPT(mmppt_unit, config_entry, coordinator) ) entities.append( - SolarEdgeTemperatureMMPPT(mmppt_unit, config_entry, coordinator) + SolarEdgeTemperatureMMPPT( + mmppt_unit, config_entry, coordinator + ) ) for meter in hub.meters: @@ -152,14 +190,22 @@ async def async_setup_entry( entities.append(ACVoltAmp(meter, config_entry, coordinator, "B")) entities.append(ACVoltAmp(meter, config_entry, coordinator, "C")) entities.append(ACVoltAmpReactive(meter, config_entry, coordinator)) - entities.append(ACVoltAmpReactive(meter, config_entry, coordinator, "A")) - entities.append(ACVoltAmpReactive(meter, config_entry, coordinator, "B")) - entities.append(ACVoltAmpReactive(meter, config_entry, coordinator, "C")) + entities.append( + ACVoltAmpReactive(meter, config_entry, coordinator, "A") + ) + entities.append( + ACVoltAmpReactive(meter, config_entry, coordinator, "B") + ) + entities.append( + ACVoltAmpReactive(meter, config_entry, coordinator, "C") + ) entities.append(ACPowerFactor(meter, config_entry, coordinator)) entities.append(ACPowerFactor(meter, config_entry, coordinator, "A")) entities.append(ACPowerFactor(meter, config_entry, coordinator, "B")) entities.append(ACPowerFactor(meter, config_entry, coordinator, "C")) - entities.append(SolarEdgeACEnergy(meter, config_entry, coordinator, "Exported")) + entities.append( + SolarEdgeACEnergy(meter, config_entry, coordinator, "Exported") + ) entities.append( SolarEdgeACEnergy(meter, config_entry, coordinator, "Exported_A") ) @@ -169,7 +215,9 @@ async def async_setup_entry( entities.append( SolarEdgeACEnergy(meter, config_entry, coordinator, "Exported_C") ) - entities.append(SolarEdgeACEnergy(meter, config_entry, coordinator, "Imported")) + entities.append( + SolarEdgeACEnergy(meter, config_entry, coordinator, "Imported") + ) entities.append( SolarEdgeACEnergy(meter, config_entry, coordinator, "Imported_A") ) @@ -179,40 +227,98 @@ async def async_setup_entry( entities.append( SolarEdgeACEnergy(meter, config_entry, coordinator, "Imported_C") ) - entities.append(MeterVAhIE(meter, config_entry, coordinator, "Exported")) - entities.append(MeterVAhIE(meter, config_entry, coordinator, "Exported_A")) - entities.append(MeterVAhIE(meter, config_entry, coordinator, "Exported_B")) - entities.append(MeterVAhIE(meter, config_entry, coordinator, "Exported_C")) - entities.append(MeterVAhIE(meter, config_entry, coordinator, "Imported")) - entities.append(MeterVAhIE(meter, config_entry, coordinator, "Imported_A")) - entities.append(MeterVAhIE(meter, config_entry, coordinator, "Imported_B")) - entities.append(MeterVAhIE(meter, config_entry, coordinator, "Imported_C")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q1")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q1_A")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q1_B")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q1_C")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q2")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q2_A")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q2_B")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q2_C")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q3")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q3_A")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q3_B")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q3_C")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q4")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q4_A")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q4_B")) - entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q4_C")) + entities.append( + MeterVAhIE(meter, config_entry, coordinator, "Exported") + ) + entities.append( + MeterVAhIE(meter, config_entry, coordinator, "Exported_A") + ) + entities.append( + MeterVAhIE(meter, config_entry, coordinator, "Exported_B") + ) + entities.append( + MeterVAhIE(meter, config_entry, coordinator, "Exported_C") + ) + entities.append( + MeterVAhIE(meter, config_entry, coordinator, "Imported") + ) + entities.append( + MeterVAhIE(meter, config_entry, coordinator, "Imported_A") + ) + entities.append( + MeterVAhIE(meter, config_entry, coordinator, "Imported_B") + ) + entities.append( + MeterVAhIE(meter, config_entry, coordinator, "Imported_C") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Import_Q1") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Import_Q1_A") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Import_Q1_B") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Import_Q1_C") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Import_Q2") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Import_Q2_A") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Import_Q2_B") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Import_Q2_C") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Export_Q3") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Export_Q3_A") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Export_Q3_B") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Export_Q3_C") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Export_Q4") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Export_Q4_A") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Export_Q4_B") + ) + entities.append( + MetervarhIE(meter, config_entry, coordinator, "Export_Q4_C") + ) for battery in hub.batteries: entities.append(SolarEdgeLastUpdate(battery, config_entry, coordinator)) entities.append(SolarEdgeDevice(battery, config_entry, coordinator)) entities.append(Version(battery, config_entry, coordinator)) - entities.append(SolarEdgeBatteryAvgTemp(battery, config_entry, coordinator)) - entities.append(SolarEdgeBatteryMaxTemp(battery, config_entry, coordinator)) - entities.append(SolarEdgeBatteryVoltage(battery, config_entry, coordinator)) - entities.append(SolarEdgeBatteryCurrent(battery, config_entry, coordinator)) - entities.append(SolarEdgeBatteryPower(battery, config_entry, coordinator)) + entities.append( + SolarEdgeBatteryAvgTemp(battery, config_entry, coordinator) + ) + entities.append( + SolarEdgeBatteryMaxTemp(battery, config_entry, coordinator) + ) + entities.append( + SolarEdgeBatteryVoltage(battery, config_entry, coordinator) + ) + entities.append( + SolarEdgeBatteryCurrent(battery, config_entry, coordinator) + ) + entities.append( + SolarEdgeBatteryPower(battery, config_entry, coordinator) + ) entities.append( SolarEdgeBatteryPowerInverted(battery, config_entry, coordinator) ) @@ -222,25 +328,35 @@ async def async_setup_entry( entities.append( SolarEdgeBatteryEnergyImport(battery, config_entry, coordinator) ) - entities.append(SolarEdgeBatteryMaxEnergy(battery, config_entry, coordinator)) + entities.append( + SolarEdgeBatteryMaxEnergy(battery, config_entry, coordinator) + ) entities.append( SolarEdgeBatteryMaxChargePower(battery, config_entry, coordinator) ) entities.append( - SolarEdgeBatteryMaxDischargePower(battery, config_entry, coordinator) + SolarEdgeBatteryMaxDischargePower( + battery, config_entry, coordinator + ) ) entities.append( - SolarEdgeBatteryMaxChargePeakPower(battery, config_entry, coordinator) + SolarEdgeBatteryMaxChargePeakPower( + battery, config_entry, coordinator + ) ) entities.append( - SolarEdgeBatteryMaxDischargePeakPower(battery, config_entry, coordinator) + SolarEdgeBatteryMaxDischargePeakPower( + battery, config_entry, coordinator + ) ) entities.append( SolarEdgeBatteryAvailableEnergy(battery, config_entry, coordinator) ) entities.append(SolarEdgeBatterySOH(battery, config_entry, coordinator)) entities.append(SolarEdgeBatterySOE(battery, config_entry, coordinator)) - entities.append(SolarEdgeBatteryStatus(battery, config_entry, coordinator)) + entities.append( + SolarEdgeBatteryStatus(battery, config_entry, coordinator) + ) for evse in hub.evses: entities.append(Version(evse, config_entry, coordinator)) @@ -355,7 +471,9 @@ def extra_state_attributes(self): pass attrs["mmppt_did"] = self._platform.decoded_mmppt["mmppt_DID"] - attrs["mmppt_units"] = self._platform.decoded_mmppt["mmppt_Units"] + attrs["mmppt_units"] = self._platform.decoded_mmppt[ + "mmppt_Units" + ] except AttributeError: pass @@ -391,7 +509,12 @@ def __init__(self, platform, config_entry, coordinator, phase: str = None): if self._platform.decoded_model["C_SunSpec_DID"] in [101, 102, 103]: self.SUNSPEC_NOT_IMPL = SunSpecNotImpl.UINT16 - elif self._platform.decoded_model["C_SunSpec_DID"] in [201, 202, 203, 204]: + elif self._platform.decoded_model["C_SunSpec_DID"] in [ + 201, + 202, + 203, + 204, + ]: self.SUNSPEC_NOT_IMPL = SunSpecNotImpl.INT16 else: raise RuntimeError( @@ -442,8 +565,10 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == self.SUNSPEC_NOT_IMPL - or self._platform.decoded_model["AC_Current_SF"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_Current_SF"] not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["AC_Current_SF"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_Current_SF"] + not in SUNSPEC_SF_RANGE ): return None @@ -473,7 +598,12 @@ def __init__(self, platform, config_entry, coordinator, phase: str = None): if self._platform.decoded_model["C_SunSpec_DID"] in [101, 102, 103]: self.SUNSPEC_NOT_IMPL = SunSpecNotImpl.UINT16 - elif self._platform.decoded_model["C_SunSpec_DID"] in [201, 202, 203, 204]: + elif self._platform.decoded_model["C_SunSpec_DID"] in [ + 201, + 202, + 203, + 204, + ]: self.SUNSPEC_NOT_IMPL = SunSpecNotImpl.INT16 else: raise RuntimeError( @@ -529,8 +659,10 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == self.SUNSPEC_NOT_IMPL - or self._platform.decoded_model["AC_Voltage_SF"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_Voltage_SF"] not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["AC_Voltage_SF"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_Voltage_SF"] + not in SUNSPEC_SF_RANGE ): return None @@ -601,7 +733,8 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_Power_SF"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_Power_SF"] + == SunSpecNotImpl.INT16 ): return None @@ -644,7 +777,9 @@ def name(self) -> str: @property def entity_registry_enabled_default(self) -> bool: - return AwesomeVersion(HA_VERSION) < AwesomeVersion(INVERTED_POWER_VERSION) + return AwesomeVersion(HA_VERSION) < AwesomeVersion( + INVERTED_POWER_VERSION + ) @property def native_value(self): @@ -671,7 +806,8 @@ def name(self) -> str: def native_value(self): try: if ( - self._platform.decoded_model["AC_Frequency"] == SunSpecNotImpl.UINT16 + self._platform.decoded_model["AC_Frequency"] + == SunSpecNotImpl.UINT16 or self._platform.decoded_model["AC_Frequency_SF"] == SunSpecNotImpl.INT16 or self._platform.decoded_model["AC_Frequency_SF"] @@ -731,8 +867,10 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_VA_SF"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_VA_SF"] not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["AC_VA_SF"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_VA_SF"] + not in SUNSPEC_SF_RANGE ): return None @@ -788,8 +926,10 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_var_SF"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_var_SF"] not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["AC_var_SF"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_var_SF"] + not in SUNSPEC_SF_RANGE ): return None @@ -845,8 +985,10 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_PF_SF"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_PF_SF"] not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["AC_PF_SF"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_PF_SF"] + not in SUNSPEC_SF_RANGE ): return None @@ -944,8 +1086,10 @@ def name(self) -> str: def available(self) -> bool: try: if ( - self._platform.decoded_model[self._model_key] == SunSpecAccum.NA32 - or self._platform.decoded_model[self._model_key] > SunSpecAccum.LIMIT32 + self._platform.decoded_model[self._model_key] + == SunSpecAccum.NA32 + or self._platform.decoded_model[self._model_key] + > SunSpecAccum.LIMIT32 or self._platform.decoded_model["AC_Energy_WH_SF"] not in SUNSPEC_SF_RANGE ): @@ -1004,9 +1148,12 @@ def name(self) -> str: @property def available(self) -> bool: if ( - self._platform.decoded_model["I_DC_Current"] == SunSpecNotImpl.UINT16 - or self._platform.decoded_model["I_DC_Current_SF"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["I_DC_Current_SF"] not in SUNSPEC_SF_RANGE + self._platform.decoded_model["I_DC_Current"] + == SunSpecNotImpl.UINT16 + or self._platform.decoded_model["I_DC_Current_SF"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["I_DC_Current_SF"] + not in SUNSPEC_SF_RANGE ): return False @@ -1025,7 +1172,10 @@ def native_value(self): @property def suggested_display_precision(self) -> int: - if self._platform.decoded_model["I_DC_Current_SF"] not in SUNSPEC_SF_RANGE: + if ( + self._platform.decoded_model["I_DC_Current_SF"] + not in SUNSPEC_SF_RANGE + ): return 1 return abs(self._platform.decoded_model["I_DC_Current_SF"]) @@ -1041,9 +1191,7 @@ class SolarEdgeDCCurrentMMPPT(SolarEdgeSensorBase): @property def unique_id(self) -> str: - return ( - f"{self._platform.inverter.uid_base}_dc_current_mmppt{self._platform.unit}" - ) + return f"{self._platform.inverter.uid_base}_dc_current_mmppt{self._platform.unit}" @property def name(self) -> str: @@ -1052,7 +1200,9 @@ def name(self) -> str: @property def available(self) -> bool: if ( - self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCA"] + self._platform.inverter.decoded_model[self._platform.mmppt_key][ + "DCA" + ] == SunSpecNotImpl.INT16 or self._platform.inverter.decoded_model["mmppt_DCA_SF"] == SunSpecNotImpl.INT16 @@ -1066,7 +1216,9 @@ def available(self) -> bool: @property def native_value(self): return self.scale_factor( - self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCA"], + self._platform.inverter.decoded_model[self._platform.mmppt_key][ + "DCA" + ], self._platform.inverter.decoded_model["mmppt_DCA_SF"], ) @@ -1094,7 +1246,8 @@ def name(self) -> str: def native_value(self): try: if ( - self._platform.decoded_model["I_DC_Voltage"] == SunSpecNotImpl.UINT16 + self._platform.decoded_model["I_DC_Voltage"] + == SunSpecNotImpl.UINT16 or self._platform.decoded_model["I_DC_Voltage_SF"] == SunSpecNotImpl.INT16 or self._platform.decoded_model["I_DC_Voltage_SF"] @@ -1125,9 +1278,7 @@ class SolarEdgeDCVoltageMMPPT(SolarEdgeSensorBase): @property def unique_id(self) -> str: - return ( - f"{self._platform.inverter.uid_base}_dc_voltage_mmppt{self._platform.unit}" - ) + return f"{self._platform.inverter.uid_base}_dc_voltage_mmppt{self._platform.unit}" @property def name(self) -> str: @@ -1136,7 +1287,9 @@ def name(self) -> str: @property def available(self) -> bool: if ( - self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCV"] + self._platform.inverter.decoded_model[self._platform.mmppt_key][ + "DCV" + ] == SunSpecNotImpl.INT16 or self._platform.inverter.decoded_model["mmppt_DCV_SF"] == SunSpecNotImpl.INT16 @@ -1150,7 +1303,9 @@ def available(self) -> bool: @property def native_value(self): return self.scale_factor( - self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCV"], + self._platform.inverter.decoded_model[self._platform.mmppt_key][ + "DCV" + ], self._platform.inverter.decoded_model["mmppt_DCV_SF"], ) @@ -1179,9 +1334,12 @@ def name(self) -> str: def native_value(self): try: if ( - self._platform.decoded_model["I_DC_Power"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["I_DC_Power_SF"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["I_DC_Power_SF"] not in SUNSPEC_SF_RANGE + self._platform.decoded_model["I_DC_Power"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["I_DC_Power_SF"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["I_DC_Power_SF"] + not in SUNSPEC_SF_RANGE ): return None @@ -1218,7 +1376,9 @@ def name(self) -> str: @property def available(self) -> bool: if ( - self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCW"] + self._platform.inverter.decoded_model[self._platform.mmppt_key][ + "DCW" + ] == SunSpecNotImpl.INT16 or self._platform.inverter.decoded_model["mmppt_DCW_SF"] == SunSpecNotImpl.INT16 @@ -1232,7 +1392,9 @@ def available(self) -> bool: @property def native_value(self): return self.scale_factor( - self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCW"], + self._platform.inverter.decoded_model[self._platform.mmppt_key][ + "DCW" + ], self._platform.inverter.decoded_model["mmppt_DCW_SF"], ) @@ -1262,9 +1424,12 @@ def native_value(self): try: if ( self._platform.decoded_model["I_Temp_Sink"] == 0x0 - or self._platform.decoded_model["I_Temp_Sink"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["I_Temp_SF"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["I_Temp_SF"] not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["I_Temp_Sink"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["I_Temp_SF"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["I_Temp_SF"] + not in SUNSPEC_SF_RANGE ): return None @@ -1293,7 +1458,9 @@ class SolarEdgeTemperatureMMPPT(SolarEdgeSensorBase): @property def unique_id(self) -> str: - return f"{self._platform.inverter.uid_base}_tmp_mmppt{self._platform.unit}" + return ( + f"{self._platform.inverter.uid_base}_tmp_mmppt{self._platform.unit}" + ) @property def name(self) -> str: @@ -1302,7 +1469,9 @@ def name(self) -> str: @property def available(self) -> bool: if ( - self._platform.inverter.decoded_model[self._platform.mmppt_key]["Tmp"] + self._platform.inverter.decoded_model[self._platform.mmppt_key][ + "Tmp" + ] == SunSpecNotImpl.INT16 ): return False @@ -1311,7 +1480,9 @@ def available(self) -> bool: @property def native_value(self): - return self._platform.inverter.decoded_model[self._platform.mmppt_key]["Tmp"] + return self._platform.inverter.decoded_model[self._platform.mmppt_key][ + "Tmp" + ] class SolarEdgeStatusSensor(SolarEdgeSensorBase): @@ -1333,7 +1504,10 @@ class SolarEdgeInverterStatus(SolarEdgeStatusSensor): @property def native_value(self): try: - if self._platform.decoded_model["I_Status"] == SunSpecNotImpl.UINT16: + if ( + self._platform.decoded_model["I_Status"] + == SunSpecNotImpl.UINT16 + ): return None return str(DEVICE_STATUS[self._platform.decoded_model["I_Status"]]) @@ -1368,7 +1542,10 @@ class SolarEdgeBatteryStatus(SolarEdgeStatusSensor): @property def native_value(self): try: - if self._platform.decoded_model["B_Status"] == SunSpecNotImpl.UINT32: + if ( + self._platform.decoded_model["B_Status"] + == SunSpecNotImpl.UINT32 + ): return None return str(BATTERY_STATUS[self._platform.decoded_model["B_Status"]]) @@ -1415,7 +1592,10 @@ def entity_registry_enabled_default(self) -> bool: @property def native_value(self): try: - if self._platform.decoded_model["I_Status_Vendor"] == SunSpecNotImpl.UINT16: + if ( + self._platform.decoded_model["I_Status_Vendor"] + == SunSpecNotImpl.UINT16 + ): return None else: @@ -1483,7 +1663,10 @@ def extra_state_attributes(self): "error_code": hex(error), } - if controller in VENDOR4_STATUS and error in VENDOR4_STATUS[controller]: + if ( + controller in VENDOR4_STATUS + and error in VENDOR4_STATUS[controller] + ): attrs["description"] = VENDOR4_STATUS[controller][error] return attrs @@ -1545,7 +1728,9 @@ def extra_state_attributes(self): else: for i in range(0, 4): - if int(str(self._platform.decoded_model["I_RRCR"])) & (1 << i): + if int(str(self._platform.decoded_model["I_RRCR"])) & ( + 1 << i + ): rrcr_inputs.append(RRCR_STATUS[i]) return {"inputs": str(rrcr_inputs)} @@ -1578,7 +1763,8 @@ def entity_registry_enabled_default(self) -> bool: def native_value(self) -> int: try: if ( - self._platform.decoded_model["I_Power_Limit"] == SunSpecNotImpl.UINT16 + self._platform.decoded_model["I_Power_Limit"] + == SunSpecNotImpl.UINT16 or self._platform.decoded_model["I_Power_Limit"] > 100 or self._platform.decoded_model["I_Power_Limit"] < 0 ): @@ -1642,7 +1828,10 @@ def name(self) -> str: @property def native_value(self): try: - if self._platform.decoded_model["M_Events"] == SunSpecNotImpl.UINT32: + if ( + self._platform.decoded_model["M_Events"] + == SunSpecNotImpl.UINT32 + ): return None else: @@ -1661,7 +1850,9 @@ def extra_state_attributes(self): else: for i in range(2, 31): try: - if int(str(self._platform.decoded_model["M_Events"])) & (1 << i): + if int(str(self._platform.decoded_model["M_Events"])) & ( + 1 << i + ): m_events_active.append(METER_EVENTS[i]) except KeyError: @@ -1687,7 +1878,10 @@ def name(self) -> str: @property def available(self) -> bool: try: - if self._platform.decoded_model["mmppt_Events"] == SunSpecNotImpl.UINT32: + if ( + self._platform.decoded_model["mmppt_Events"] + == SunSpecNotImpl.UINT32 + ): return False return super().available @@ -1709,15 +1903,17 @@ def extra_state_attributes(self) -> str: else: for i in range(0, 31): try: - if int(str(self._platform.decoded_model["mmppt_Events"])) & ( - 1 << i - ): + if int( + str(self._platform.decoded_model["mmppt_Events"]) + ) & (1 << i): mmppt_events_active.append(MMPPT_EVENTS[i]) except KeyError: pass attrs["events"] = str(mmppt_events_active) - attrs["bits"] = f"{int(self._platform.decoded_model['mmppt_Events']):032b}" + attrs["bits"] = ( + f"{int(self._platform.decoded_model['mmppt_Events']):032b}" + ) return attrs @@ -1775,9 +1971,12 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecAccum.NA32 - or self._platform.decoded_model[model_key] > SunSpecAccum.LIMIT32 - or self._platform.decoded_model["M_VAh_SF"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["M_VAh_SF"] not in SUNSPEC_SF_RANGE + or self._platform.decoded_model[model_key] + > SunSpecAccum.LIMIT32 + or self._platform.decoded_model["M_VAh_SF"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["M_VAh_SF"] + not in SUNSPEC_SF_RANGE ): return None @@ -1853,9 +2052,12 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecAccum.NA32 - or self._platform.decoded_model[model_key] > SunSpecAccum.LIMIT32 - or self._platform.decoded_model["M_varh_SF"] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["M_varh_SF"] not in SUNSPEC_SF_RANGE + or self._platform.decoded_model[model_key] + > SunSpecAccum.LIMIT32 + or self._platform.decoded_model["M_varh_SF"] + == SunSpecNotImpl.INT16 + or self._platform.decoded_model["M_varh_SF"] + not in SUNSPEC_SF_RANGE ): return None @@ -1895,8 +2097,10 @@ def native_value(self): if ( float_to_hex(self._platform.decoded_model["B_Temp_Average"]) == hex(SunSpecNotImpl.FLOAT32) - or self._platform.decoded_model["B_Temp_Average"] < BatteryLimit.Tmin - or self._platform.decoded_model["B_Temp_Average"] > BatteryLimit.Tmax + or self._platform.decoded_model["B_Temp_Average"] + < BatteryLimit.Tmin + or self._platform.decoded_model["B_Temp_Average"] + > BatteryLimit.Tmax ): return None @@ -1928,8 +2132,10 @@ def native_value(self): if ( float_to_hex(self._platform.decoded_model["B_Temp_Max"]) == hex(SunSpecNotImpl.FLOAT32) - or self._platform.decoded_model["B_Temp_Max"] < BatteryLimit.Tmin - or self._platform.decoded_model["B_Temp_Max"] > BatteryLimit.Tmax + or self._platform.decoded_model["B_Temp_Max"] + < BatteryLimit.Tmin + or self._platform.decoded_model["B_Temp_Max"] + > BatteryLimit.Tmax ): return None @@ -1949,8 +2155,10 @@ def native_value(self): if ( float_to_hex(self._platform.decoded_model["B_DC_Voltage"]) == hex(SunSpecNotImpl.FLOAT32) - or self._platform.decoded_model["B_DC_Voltage"] < BatteryLimit.Vmin - or self._platform.decoded_model["B_DC_Voltage"] > BatteryLimit.Vmax + or self._platform.decoded_model["B_DC_Voltage"] + < BatteryLimit.Vmin + or self._platform.decoded_model["B_DC_Voltage"] + > BatteryLimit.Vmax ): return None @@ -1985,8 +2193,10 @@ def available(self) -> bool: if ( float_to_hex(self._platform.decoded_model["B_DC_Current"]) == hex(SunSpecNotImpl.FLOAT32) - or self._platform.decoded_model["B_DC_Current"] < BatteryLimit.Amin - or self._platform.decoded_model["B_DC_Current"] > BatteryLimit.Amax + or self._platform.decoded_model["B_DC_Current"] + < BatteryLimit.Amin + or self._platform.decoded_model["B_DC_Current"] + > BatteryLimit.Amax ): return False @@ -2055,7 +2265,9 @@ def name(self) -> str: @property def entity_registry_enabled_default(self) -> bool: - return AwesomeVersion(HA_VERSION) < AwesomeVersion(INVERTED_POWER_VERSION) + return AwesomeVersion(HA_VERSION) < AwesomeVersion( + INVERTED_POWER_VERSION + ) @property def native_value(self): @@ -2104,14 +2316,21 @@ def native_value(self): if self._last is None: self._last = 0 - if self._platform.decoded_model["B_Export_Energy_WH"] >= self._last: - self._last = self._platform.decoded_model["B_Export_Energy_WH"] + if ( + self._platform.decoded_model["B_Export_Energy_WH"] + >= self._last + ): + self._last = self._platform.decoded_model[ + "B_Export_Energy_WH" + ] self._log_once = False if self._platform.allow_battery_energy_reset: self._count = 0 - return self._platform.decoded_model["B_Export_Energy_WH"] + return self._platform.decoded_model[ + "B_Export_Energy_WH" + ] else: if ( @@ -2138,7 +2357,10 @@ def native_value(self): ) ) - if self._count > self._platform.battery_energy_reset_cycles: + if ( + self._count + > self._platform.battery_energy_reset_cycles + ): _LOGGER.debug( f"B_Export_Energy reset at cycle {self._count}" ) @@ -2193,14 +2415,21 @@ def native_value(self): if self._last is None: self._last = 0 - if self._platform.decoded_model["B_Import_Energy_WH"] >= self._last: - self._last = self._platform.decoded_model["B_Import_Energy_WH"] + if ( + self._platform.decoded_model["B_Import_Energy_WH"] + >= self._last + ): + self._last = self._platform.decoded_model[ + "B_Import_Energy_WH" + ] self._log_once = False if self._platform.allow_battery_energy_reset: self._count = 0 - return self._platform.decoded_model["B_Import_Energy_WH"] + return self._platform.decoded_model[ + "B_Import_Energy_WH" + ] else: if ( @@ -2227,7 +2456,10 @@ def native_value(self): ) ) - if self._count > self._platform.battery_energy_reset_cycles: + if ( + self._count + > self._platform.battery_energy_reset_cycles + ): _LOGGER.debug( f"B_Import_Energy reset at cycle {self._count}" ) @@ -2368,7 +2600,9 @@ def name(self) -> str: @property def available(self): if ( - float_to_hex(self._platform.decoded_model["B_MaxDischargePeakPower"]) + float_to_hex( + self._platform.decoded_model["B_MaxDischargePeakPower"] + ) == hex(SunSpecNotImpl.FLOAT32) or self._platform.decoded_model["B_MaxDischargePeakPower"] < 0 ): @@ -2505,7 +2739,8 @@ def name(self) -> str: @property def available(self) -> bool: return ( - super().available and "CommitPwrCtlSettings" in self._platform.decoded_model + super().available + and "CommitPwrCtlSettings" in self._platform.decoded_model ) @property @@ -2516,11 +2751,18 @@ def native_value(self): def extra_state_attributes(self): attrs = {} - attrs["hex_value"] = hex(self._platform.decoded_model["CommitPwrCtlSettings"]) + attrs["hex_value"] = hex( + self._platform.decoded_model["CommitPwrCtlSettings"] + ) if self._platform.decoded_model["CommitPwrCtlSettings"] == 0x0: attrs["status"] = "SUCCESS" - if self._platform.decoded_model["CommitPwrCtlSettings"] in [0x1, 0x2, 0x3, 0x4]: + if self._platform.decoded_model["CommitPwrCtlSettings"] in [ + 0x1, + 0x2, + 0x3, + 0x4, + ]: attrs["status"] = "INTERNAL_ERROR" if self._platform.decoded_model["CommitPwrCtlSettings"] == 0xFFFF: attrs["status"] = "UNKNOWN_ERROR" @@ -2562,7 +2804,9 @@ def native_value(self): def extra_state_attributes(self): attrs = {} - attrs["hex_value"] = hex(self._platform.decoded_model["RestorePwrCtlDefaults"]) + attrs["hex_value"] = hex( + self._platform.decoded_model["RestorePwrCtlDefaults"] + ) if self._platform.decoded_model["RestorePwrCtlDefaults"] == 0x0: attrs["status"] = "SUCCESS" diff --git a/custom_components/solaredge_modbus_multi/switch.py b/custom_components/solaredge_modbus_multi/switch.py index 299b40cd..65bff00c 100644 --- a/custom_components/solaredge_modbus_multi/switch.py +++ b/custom_components/solaredge_modbus_multi/switch.py @@ -39,7 +39,9 @@ async def async_setup_entry( ) if hub.option_detect_extras and inverter.advanced_power_control: - entities.append(SolarEdgeGridControl(inverter, config_entry, coordinator)) + entities.append( + SolarEdgeGridControl(inverter, config_entry, coordinator) + ) if entities: async_add_entities(entities) @@ -85,7 +87,10 @@ class SolarEdgeExternalProduction(SolarEdgeSwitchBase): @property def available(self) -> bool: try: - if self._platform.decoded_model["E_Lim_Ctl_Mode"] == SunSpecNotImpl.UINT16: + if ( + self._platform.decoded_model["E_Lim_Ctl_Mode"] + == SunSpecNotImpl.UINT16 + ): return False return super().available @@ -152,7 +157,10 @@ class SolarEdgeNegativeSiteLimit(SolarEdgeSwitchBase): @property def available(self) -> bool: try: - if self._platform.decoded_model["E_Lim_Ctl_Mode"] == SunSpecNotImpl.UINT16: + if ( + self._platform.decoded_model["E_Lim_Ctl_Mode"] + == SunSpecNotImpl.UINT16 + ): return False return super().available @@ -238,7 +246,9 @@ async def async_turn_on(self, **kwargs: Any) -> None: await self._platform.write_registers( address=61762, payload=ModbusClientMixin.convert_to_registers( - 0x1, data_type=ModbusClientMixin.DATATYPE.INT32, word_order="little" + 0x1, + data_type=ModbusClientMixin.DATATYPE.INT32, + word_order="little", ), ) await self.async_update() @@ -249,7 +259,9 @@ async def async_turn_off(self, **kwargs: Any) -> None: await self._platform.write_registers( address=61762, payload=ModbusClientMixin.convert_to_registers( - 0x0, data_type=ModbusClientMixin.DATATYPE.INT32, word_order="little" + 0x0, + data_type=ModbusClientMixin.DATATYPE.INT32, + word_order="little", ), ) await self.async_update() From bb7720d4f231dac5f397fdae8150cef32a08fec0 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:04:22 -0700 Subject: [PATCH 08/13] Disable VALIDATE_SPELL_CODESPELL --- .github/workflows/linter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index b1263dd9..4df55e76 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -30,5 +30,6 @@ jobs: VALIDATE_PYTHON_BLACK: false VALIDATE_PYTHON_FLAKE8: false VALIDATE_PYTHON_ISORT: false + VALIDATE_SPELL_CODESPELL: false DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b865b9277c05662d58f0a080fdd5e6206b76ca06 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:09:09 -0700 Subject: [PATCH 09/13] Disable VALIDATE_BIOME_FORMAT --- .github/workflows/linter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 4df55e76..af21789c 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -31,5 +31,6 @@ jobs: VALIDATE_PYTHON_FLAKE8: false VALIDATE_PYTHON_ISORT: false VALIDATE_SPELL_CODESPELL: false + VALIDATE_BIOME_FORMAT: false DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From c051d3d3c6cf3ab0dfa2efae0611e34f2365062b Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:17:57 -0700 Subject: [PATCH 10/13] Remove VALIDATE_PYTHON_PYINK --- .github/workflows/linter.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index af21789c..44d9b9c6 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -26,7 +26,6 @@ jobs: VALIDATE_JSCPD: false VALIDATE_PYTHON_MYPY: false VALIDATE_PYTHON_PYLINT: false - VALIDATE_PYTHON_PYINK: false VALIDATE_PYTHON_BLACK: false VALIDATE_PYTHON_FLAKE8: false VALIDATE_PYTHON_ISORT: false From cdbbf7722014d6375f69ff0e2af799a45a6ab6b0 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:22:25 -0700 Subject: [PATCH 11/13] Pin to hashes --- .github/workflows/codeql-analysis.yml | 10 ++++++---- .github/workflows/hacs.yaml | 6 ++++-- .github/workflows/hassfest.yaml | 6 ++++-- .github/workflows/linter.yml | 5 +++-- .github/workflows/tests.yml | 6 ++++-- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8dd5bb96..b59a76e0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,11 +38,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -55,7 +57,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v4 + uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -68,4 +70,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd diff --git a/.github/workflows/hacs.yaml b/.github/workflows/hacs.yaml index cb139837..e912832a 100644 --- a/.github/workflows/hacs.yaml +++ b/.github/workflows/hacs.yaml @@ -15,8 +15,10 @@ jobs: name: HACS Action runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v7" + - uses: "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1" + with: + persist-credentials: false - name: HACS Action - uses: "hacs/action@main" + uses: "hacs/action@1ebf01c408f29afcb6406bd431bc98fd8cbb15aa" with: category: "integration" diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/hassfest.yaml index 995fd132..a0ff1268 100644 --- a/.github/workflows/hassfest.yaml +++ b/.github/workflows/hassfest.yaml @@ -14,5 +14,7 @@ jobs: validate: runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v7" - - uses: "home-assistant/actions/hassfest@master" + - uses: "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1" + with: + persist-credentials: false + - uses: "home-assistant/actions/hassfest@a7c616ce81ccda50150bf1595786c71b1883fabb" diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 44d9b9c6..1c431d07 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -16,11 +16,12 @@ jobs: statuses: write steps: - name: Checkout Code - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: fetch-depth: 0 + persist-credentials: false - name: Lint Code Base - uses: super-linter/super-linter@v8 + uses: super-linter/super-linter@4ce20838b8ab83717e78138c5b3a1407148e0918 env: VALIDATE_ALL_CODEBASE: false VALIDATE_JSCPD: false diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 260f26bb..98b00a72 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,9 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 with: python-version: "3.13" - name: Install test dependencies From 680f3a3c1095893528a45b64594f11c64e33d3a3 Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:24:11 -0700 Subject: [PATCH 12/13] Fix ruff line length --- .../solaredge_modbus_multi/__init__.py | 20 +- .../solaredge_modbus_multi/binary_sensor.py | 7 +- .../solaredge_modbus_multi/button.py | 12 +- .../solaredge_modbus_multi/config_flow.py | 72 +-- .../solaredge_modbus_multi/diagnostics.py | 8 +- .../solaredge_modbus_multi/helpers.py | 4 +- .../solaredge_modbus_multi/hub.py | 178 ++---- .../solaredge_modbus_multi/number.py | 68 +-- .../solaredge_modbus_multi/repairs.py | 10 +- .../solaredge_modbus_multi/scanner.py | 8 +- .../solaredge_modbus_multi/select.py | 46 +- .../solaredge_modbus_multi/sensor.py | 509 +++++------------- .../solaredge_modbus_multi/switch.py | 14 +- pyproject.toml | 2 +- 14 files changed, 268 insertions(+), 690 deletions(-) diff --git a/custom_components/solaredge_modbus_multi/__init__.py b/custom_components/solaredge_modbus_multi/__init__.py index 7641e4c8..5ca3320a 100644 --- a/custom_components/solaredge_modbus_multi/__init__.py +++ b/custom_components/solaredge_modbus_multi/__init__.py @@ -104,9 +104,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: solaredge_hub = hass.data[DOMAIN][entry.entry_id]["hub"] await solaredge_hub.shutdown() - unload_ok = await hass.config_entries.async_unload_platforms( - entry, PLATFORMS - ) + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) @@ -159,17 +157,13 @@ async def async_remove_config_entry_device( for device_id in this_device_ids: if device_id in known_devices: - _LOGGER.error( - f"Unable to remove entry: device {device_id} is in use" - ) + _LOGGER.error(f"Unable to remove entry: device {device_id} is in use") return False return True -async def async_migrate_entry( - hass: HomeAssistant, config_entry: ConfigEntry -) -> bool: +async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Migrate old entry.""" _LOGGER.debug( "Migrating from config version " @@ -224,9 +218,7 @@ async def async_migrate_entry( # and the "leader" modbus unit id can't be known programmatically. old_unique_id = config_entry.unique_id - new_unique_id = ( - f"{config_entry_data[CONF_HOST]}:{config_entry_data[CONF_PORT]}" - ) + new_unique_id = f"{config_entry_data[CONF_HOST]}:{config_entry_data[CONF_PORT]}" _LOGGER.warning( "Migrating config entry unique ID from %s to %s", @@ -314,9 +306,7 @@ async def _refresh_modbus_data_with_retry( if not isinstance(ex, ex_type): raise ex if 0 < limit <= attempt: - _LOGGER.debug( - f"No more data refresh attempts (maximum {limit})" - ) + _LOGGER.debug(f"No more data refresh attempts (maximum {limit})") raise ex _LOGGER.debug(f"Failed data refresh attempt {attempt}") diff --git a/custom_components/solaredge_modbus_multi/binary_sensor.py b/custom_components/solaredge_modbus_multi/binary_sensor.py index dec8964e..09846016 100644 --- a/custom_components/solaredge_modbus_multi/binary_sensor.py +++ b/custom_components/solaredge_modbus_multi/binary_sensor.py @@ -31,9 +31,7 @@ async def async_setup_entry( for inverter in hub.inverters: if hub.option_detect_extras and inverter.advanced_power_control: - entities.append( - AdvPowerControlEnabled(inverter, config_entry, coordinator) - ) + entities.append(AdvPowerControlEnabled(inverter, config_entry, coordinator)) entities.append(GridStatusOnOff(inverter, config_entry, coordinator)) @@ -110,8 +108,7 @@ class GridStatusOnOff(SolarEdgeBinarySensorBase): @property def available(self) -> bool: return ( - super().available - and "I_Grid_Status" in self._platform.decoded_model.keys() + super().available and "I_Grid_Status" in self._platform.decoded_model.keys() ) @property diff --git a/custom_components/solaredge_modbus_multi/button.py b/custom_components/solaredge_modbus_multi/button.py index 80b16c1f..754dcce6 100644 --- a/custom_components/solaredge_modbus_multi/button.py +++ b/custom_components/solaredge_modbus_multi/button.py @@ -28,21 +28,15 @@ async def async_setup_entry( entities = [] for inverter in hub.inverters: - entities.append( - SolarEdgeRefreshButton(inverter, config_entry, coordinator) - ) + entities.append(SolarEdgeRefreshButton(inverter, config_entry, coordinator)) """ Power Control Block """ if hub.option_detect_extras and inverter.advanced_power_control: entities.append( - SolarEdgeCommitControlSettings( - inverter, config_entry, coordinator - ) + SolarEdgeCommitControlSettings(inverter, config_entry, coordinator) ) entities.append( - SolarEdgeDefaultControlSettings( - inverter, config_entry, coordinator - ) + SolarEdgeDefaultControlSettings(inverter, config_entry, coordinator) ) if entities: diff --git a/custom_components/solaredge_modbus_multi/config_flow.py b/custom_components/solaredge_modbus_multi/config_flow.py index 458978ea..c017e6a8 100644 --- a/custom_components/solaredge_modbus_multi/config_flow.py +++ b/custom_components/solaredge_modbus_multi/config_flow.py @@ -52,23 +52,17 @@ class ScanNoResponseError(HomeAssistantError): pass -def generate_config_schema( - step_id: str, user_input: dict[str, Any] -) -> vol.Schema: +def generate_config_schema(step_id: str, user_input: dict[str, Any]) -> vol.Schema: """Generate config flow or repair schema.""" schema: dict[vol.Marker, Any] = {} if step_id == "user": - schema |= { - vol.Required(CONF_NAME, default=user_input[CONF_NAME]): cv.string - } + schema |= {vol.Required(CONF_NAME, default=user_input[CONF_NAME]): cv.string} if step_id in ["reconfigure", "confirm", "user"]: schema |= { vol.Required(CONF_HOST, default=user_input[CONF_HOST]): cv.string, - vol.Required(CONF_PORT, default=user_input[CONF_PORT]): vol.Coerce( - int - ), + vol.Required(CONF_PORT, default=user_input[CONF_PORT]): vol.Coerce(int), vol.Required( f"{ConfName.DEVICE_LIST}", default=user_input[ConfName.DEVICE_LIST], @@ -98,18 +92,14 @@ def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: """Create the options flow for SolarEdge Modbus Multi.""" return SolaredgeModbusMultiOptionsFlowHandler() - async def _async_update_progress_bar( - self, scanned: int, total: int - ) -> None: + async def _async_update_progress_bar(self, scanned: int, total: int) -> None: try: progress = scanned / total if total > 0 else 0 self.async_update_progress(progress) except asyncio.CancelledError: pass - async def _async_scan_devices( - self, user_input: dict[str, Any] - ) -> list[int]: + async def _async_scan_devices(self, user_input: dict[str, Any]) -> list[int]: """Scanner job for async_create_task""" scanner = SolarEdgeDeviceScanner( host=user_input[CONF_HOST], @@ -199,9 +189,7 @@ async def async_step_scan_ask_host( elif not 1 <= user_input[CONF_PORT] <= 65535: errors[CONF_PORT] = "invalid_tcp_port" else: - new_unique_id = ( - f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" - ) + new_unique_id = f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" await self.async_set_unique_id(new_unique_id) self._abort_if_unique_id_configured() @@ -232,15 +220,11 @@ async def async_step_scan_ask_host( step_id="scan_ask_host", data_schema=vol.Schema( { - vol.Optional( - CONF_NAME, default=user_input[CONF_NAME] - ): cv.string, - vol.Required( - CONF_HOST, default=user_input[CONF_HOST] - ): cv.string, - vol.Required( - CONF_PORT, default=user_input[CONF_PORT] - ): vol.Coerce(int), + vol.Optional(CONF_NAME, default=user_input[CONF_NAME]): cv.string, + vol.Required(CONF_HOST, default=user_input[CONF_HOST]): cv.string, + vol.Required(CONF_PORT, default=user_input[CONF_PORT]): vol.Coerce( + int + ), }, ), errors=errors, @@ -367,15 +351,11 @@ async def async_step_manual( step_id="manual", data_schema=vol.Schema( { - vol.Optional( - CONF_NAME, default=user_input[CONF_NAME] - ): cv.string, - vol.Required( - CONF_HOST, default=user_input[CONF_HOST] - ): cv.string, - vol.Required( - CONF_PORT, default=user_input[CONF_PORT] - ): vol.Coerce(int), + vol.Optional(CONF_NAME, default=user_input[CONF_NAME]): cv.string, + vol.Required(CONF_HOST, default=user_input[CONF_HOST]): cv.string, + vol.Required(CONF_PORT, default=user_input[CONF_PORT]): vol.Coerce( + int + ), vol.Required( f"{ConfName.DEVICE_LIST}", default=user_input[ConfName.DEVICE_LIST], @@ -393,9 +373,7 @@ async def async_step_features_info( entry = self._pending_entry self._pending_entry = None - return self.async_create_entry( - title=entry["title"], data=entry["data"] - ) + return self.async_create_entry(title=entry["title"], data=entry["data"]) return self.async_show_form( step_id="features_info", @@ -435,9 +413,7 @@ async def async_step_reconfigure( user_input[ConfName.DEVICE_LIST] = device_list_from_string( user_input[ConfName.DEVICE_LIST] ) - this_unique_id = ( - f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" - ) + this_unique_id = f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" if this_unique_id != config_entry.unique_id: self._async_abort_entries_match( @@ -463,9 +439,7 @@ async def async_step_reconfigure( user_input = { CONF_HOST: config_entry.data.get(CONF_HOST), - CONF_PORT: config_entry.data.get( - CONF_PORT, ConfDefaultInt.PORT - ), + CONF_PORT: config_entry.data.get(CONF_PORT, ConfDefaultInt.PORT), ConfName.DEVICE_LIST: reconfig_device_list, } @@ -504,9 +478,7 @@ async def async_step_init( return await self.async_step_adv_pwr_ctl() else: - return self.async_create_entry( - title="", data=user_input - ) + return self.async_create_entry(title="", data=user_input) else: user_input = { @@ -619,9 +591,7 @@ async def async_step_battery_options( ): cv.boolean, vol.Optional( f"{ConfName.BATTERY_ENERGY_RESET_CYCLES}", - default=user_input[ - ConfName.BATTERY_ENERGY_RESET_CYCLES - ], + default=user_input[ConfName.BATTERY_ENERGY_RESET_CYCLES], ): vol.Coerce(int), vol.Optional( f"{ConfName.BATTERY_RATING_ADJUST}", diff --git a/custom_components/solaredge_modbus_multi/diagnostics.py b/custom_components/solaredge_modbus_multi/diagnostics.py index cfb266d1..d30d4a26 100644 --- a/custom_components/solaredge_modbus_multi/diagnostics.py +++ b/custom_components/solaredge_modbus_multi/diagnostics.py @@ -49,9 +49,7 @@ async def async_get_config_entry_diagnostics( data: dict[str, Any] = { "pymodbus_version": hub.pymodbus_version, - "config_entry": async_redact_data( - config_entry.as_dict(), REDACT_CONFIG - ), + "config_entry": async_redact_data(config_entry.as_dict(), REDACT_CONFIG), "yaml": async_redact_data(hass.data[DOMAIN]["yaml"], REDACT_CONFIG), } @@ -67,9 +65,7 @@ async def async_get_config_entry_diagnostics( "is_mmppt": inverter.is_mmppt, "mmppt": format_values(inverter.decoded_mmppt), "has_battery": inverter.has_battery, - "storage_control": format_values( - inverter.decoded_storage_control - ), + "storage_control": format_values(inverter.decoded_storage_control), "use_status_vendor4": inverter.use_status_vendor4, } } diff --git a/custom_components/solaredge_modbus_multi/helpers.py b/custom_components/solaredge_modbus_multi/helpers.py index 4c900b80..92b4e0f0 100644 --- a/custom_components/solaredge_modbus_multi/helpers.py +++ b/custom_components/solaredge_modbus_multi/helpers.py @@ -29,9 +29,7 @@ def int_list_to_string(int_list: list[int]) -> str: 7fc8d3e02d9d9011c25c80149eb88318e7f50d0e/pymodbus/client/mixin.py#L719 """ byte_data = b"".join(i.to_bytes(2, "big") for i in int_list) - return ( - byte_data.decode("utf-8", errors="ignore").replace("\x00", "").rstrip() - ) + return byte_data.decode("utf-8", errors="ignore").replace("\x00", "").rstrip() def update_accum(self, accum_value: int) -> None: diff --git a/custom_components/solaredge_modbus_multi/hub.py b/custom_components/solaredge_modbus_multi/hub.py index 0a361c68..fffe3b46 100644 --- a/custom_components/solaredge_modbus_multi/hub.py +++ b/custom_components/solaredge_modbus_multi/hub.py @@ -295,9 +295,7 @@ async def _async_init_solaredge(self) -> None: except DeviceInvalid as e: # Inverters are mandatory, but if the Device ID is invalid or not responding # skip it and warn the user instead of failing the entire hub setup - _LOGGER.error( - f"Inverter at {self.hub_host} ID {inverter_unit_id}: {e}" - ) + _LOGGER.error(f"Inverter at {self.hub_host} ID {inverter_unit_id}: {e}") ir.async_create_issue( self._hass, DOMAIN, @@ -330,9 +328,7 @@ async def _async_init_solaredge(self) -> None: _LOGGER.debug( f"Looking for meter I{inverter_unit_id}M{meter_id}" ) - new_meter = SolarEdgeMeter( - inverter_unit_id, meter_id, self - ) + new_meter = SolarEdgeMeter(inverter_unit_id, meter_id, self) await new_meter.init_device() for meter in self.meters: @@ -617,23 +613,17 @@ async def modbus_read_holding_registers(self, unit, address, rcount): slave=self._rr_unit, ) - _LOGGER.debug( - f"unit={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"unit={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"unit={self._rr_unit} Read IllegalAddress: {result}" - ) + _LOGGER.debug(f"unit={self._rr_unit} Read IllegalAddress: {result}") raise ModbusIllegalAddress(result) if result.exception_code == ModbusExceptions.IllegalFunction: @@ -643,9 +633,7 @@ async def modbus_read_holding_registers(self, unit, address, rcount): raise ModbusIllegalFunction(result) if result.exception_code == ModbusExceptions.IllegalValue: - _LOGGER.debug( - f"unit={self._rr_unit} Read IllegalValue: {result}" - ) + _LOGGER.debug(f"unit={self._rr_unit} Read IllegalValue: {result}") raise ModbusIllegalValue(result) raise ModbusReadError(result) @@ -745,9 +733,7 @@ async def write_registers(self, unit: int, address: int, payload) -> None: ) if result.exception_code == ModbusExceptions.IllegalValue: - _LOGGER.debug( - f"Unit {self._wr_unit} Write IllegalValue: {result}" - ) + _LOGGER.debug(f"Unit {self._wr_unit} Write IllegalValue: {result}") raise HomeAssistantError( "Value invalid for device at ID {self._wr_unit}." ) @@ -875,25 +861,17 @@ def coordinator_timeout(self) -> int: if not self.initalized: this_timeout = SolarEdgeTimeouts.Inverter * self.number_of_inverters this_timeout += SolarEdgeTimeouts.Init * self.number_of_inverters - this_timeout += ( - SolarEdgeTimeouts.Device * 2 - ) * 3 # max 3 per inverter - this_timeout += ( - SolarEdgeTimeouts.Device * 2 - ) * 2 # max 2 per inverter + this_timeout += (SolarEdgeTimeouts.Device * 2) * 3 # max 3 per inverter + this_timeout += (SolarEdgeTimeouts.Device * 2) * 2 # max 2 per inverter if self.option_detect_extras: - this_timeout += ( - SolarEdgeTimeouts.Read * 3 - ) * self.number_of_inverters + this_timeout += (SolarEdgeTimeouts.Read * 3) * self.number_of_inverters else: this_timeout = SolarEdgeTimeouts.Inverter * self.number_of_inverters this_timeout += SolarEdgeTimeouts.Device * self.number_of_meters this_timeout += SolarEdgeTimeouts.Device * self.number_of_batteries if self.option_detect_extras: - this_timeout += ( - SolarEdgeTimeouts.Read * 3 - ) * self.number_of_inverters + this_timeout += (SolarEdgeTimeouts.Read * 3) * self.number_of_inverters this_timeout = this_timeout / 1000 @@ -953,9 +931,7 @@ async def init_device(self) -> None: "C_SunSpec_Length", "C_Device_address", ] - uint16_data = inverter_data.registers[2:4] + [ - inverter_data.registers[68] - ] + uint16_data = inverter_data.registers[2:4] + [inverter_data.registers[68]] self.decoded_common.update( dict( zip( @@ -1029,14 +1005,10 @@ async def init_device(self) -> None: ), ) - self.hub.inverter_common[self.inverter_unit_id] = ( - self.decoded_common - ) + self.hub.inverter_common[self.inverter_unit_id] = self.decoded_common except (ModbusIOError, ModbusIOException): - raise DeviceInvalid( - f"No response from Device ID {self.inverter_unit_id}" - ) + raise DeviceInvalid(f"No response from Device ID {self.inverter_unit_id}") except ModbusIllegalAddress: raise DeviceInvalid( @@ -1143,12 +1115,8 @@ async def init_device(self) -> None: 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) - ) - _LOGGER.debug( - f"I{self.inverter_unit_id} MMPPT Unit {unit_index}" - ) + self.mmppt_units.append(SolarEdgeMMPPTUnit(self, self.hub, unit_index)) + _LOGGER.debug(f"I{self.inverter_unit_id} MMPPT Unit {unit_index}") async def read_modbus_data(self) -> None: """Read and update dynamic modbus registers.""" @@ -1288,9 +1256,7 @@ async def read_modbus_data(self) -> None: or self.decoded_model["C_SunSpec_DID"] not in [101, 102, 103] or self.decoded_model["C_SunSpec_Length"] != 50 ): - raise DeviceInvalid( - f"Inverter {self.inverter_unit_id} not usable." - ) + raise DeviceInvalid(f"Inverter {self.inverter_unit_id} not usable.") except ModbusIOError: raise ModbusReadError( @@ -1368,8 +1334,7 @@ async def read_modbus_data(self) -> None: int_list_to_string( ModbusClientMixin.convert_from_registers( inverter_data.registers[ - 9 + unit_offset : 17 - + unit_offset + 9 + unit_offset : 17 + unit_offset ], data_type=ModbusClientMixin.DATATYPE.UINT16, ) @@ -1378,11 +1343,7 @@ async def read_modbus_data(self) -> None: ( "Tmp", ModbusClientMixin.convert_from_registers( - [ - inverter_data.registers[ - 24 + unit_offset - ] - ], + [inverter_data.registers[24 + unit_offset]], data_type=ModbusClientMixin.DATATYPE.INT16, ), ), @@ -1422,9 +1383,7 @@ async def read_modbus_data(self) -> None: "DCEvt", ] uint32_data = ( - inverter_data.registers[ - 20 + unit_offset : 22 + unit_offset - ] + inverter_data.registers[20 + unit_offset : 22 + unit_offset] + inverter_data.registers[ 22 + unit_offset : 24 + unit_offset ] @@ -1456,15 +1415,12 @@ async def read_modbus_data(self) -> None: """ Global Dynamic Power Control and Status """ if self.hub.option_detect_extras is True and ( - self.global_power_control is True - or self.global_power_control is None + self.global_power_control is True or self.global_power_control is None ): try: async with asyncio.timeout(SolarEdgeTimeouts.Read / 1000): - inverter_data = ( - await self.hub.modbus_read_holding_registers( - unit=self.inverter_unit_id, address=61440, rcount=4 - ) + inverter_data = await self.hub.modbus_read_holding_registers( + unit=self.inverter_unit_id, address=61440, rcount=4 ) self.decoded_model.update( @@ -1533,15 +1489,12 @@ async def read_modbus_data(self) -> None: """ Advanced Power Control """ """ Power Control Block """ if self.hub.option_detect_extras is True and ( - self.advanced_power_control is True - or self.advanced_power_control is None + self.advanced_power_control is True or self.advanced_power_control is None ): try: async with asyncio.timeout(SolarEdgeTimeouts.Read / 1000): - inverter_data = ( - await self.hub.modbus_read_holding_registers( - unit=self.inverter_unit_id, address=61696, rcount=86 - ) + inverter_data = await self.hub.modbus_read_holding_registers( + unit=self.inverter_unit_id, address=61696, rcount=86 ) int32_fields = [ @@ -1609,8 +1562,7 @@ async def read_modbus_data(self) -> None: "PwrVsFreqX_1", ] float32_data = ( - inverter_data.registers[10:66] - + inverter_data.registers[70:86] + inverter_data.registers[10:66] + inverter_data.registers[70:86] ) self.decoded_model.update( dict( @@ -1658,10 +1610,8 @@ async def read_modbus_data(self) -> None: ) async with asyncio.timeout(SolarEdgeTimeouts.Read / 1000): - inverter_data = ( - await self.hub.modbus_read_holding_registers( - unit=self.inverter_unit_id, address=61782, rcount=84 - ) + inverter_data = await self.hub.modbus_read_holding_registers( + unit=self.inverter_unit_id, address=61782, rcount=84 ) float32_fields = [ @@ -1730,8 +1680,7 @@ async def read_modbus_data(self) -> None: "PwrSoftStartTime", ] uint32_data = ( - inverter_data.registers[32:36] - + inverter_data.registers[52:56] + inverter_data.registers[32:36] + inverter_data.registers[52:56] ) self.decoded_model.update( dict( @@ -1861,9 +1810,7 @@ async def read_modbus_data(self) -> None: except KeyError: pass - _LOGGER.debug( - f"I{self.inverter_unit_id}: Ext_Prod_Max NOT available" - ) + _LOGGER.debug(f"I{self.inverter_unit_id}: Ext_Prod_Max NOT available") except ModbusIOError: raise ModbusReadError( @@ -1895,9 +1842,7 @@ async def read_modbus_data(self) -> None: except ModbusIllegalAddress: self._grid_status = False - _LOGGER.debug( - f"I{self.inverter_unit_id}: Grid On/Off NOT available" - ) + _LOGGER.debug(f"I{self.inverter_unit_id}: Grid On/Off NOT available") except ModbusIOException as e: _LOGGER.debug( @@ -1969,8 +1914,7 @@ async def read_modbus_data(self) -> None: "discharge_limit", ] float32_data = ( - inverter_data.registers[2:6] - + inverter_data.registers[10:14] + inverter_data.registers[2:6] + inverter_data.registers[10:14] ) self.decoded_storage_control.update( dict( @@ -2005,9 +1949,7 @@ async def read_modbus_data(self) -> None: if isinstance(value, float): display_value = float_to_hex(value) else: - display_value = ( - hex(value) if isinstance(value, int) else value - ) + display_value = hex(value) if isinstance(value, int) else value _LOGGER.debug( f"I{self.inverter_unit_id}: " f"{name} {display_value} {type(value)}" @@ -2082,9 +2024,7 @@ def __init__( @property def online(self) -> bool: """Device is online.""" - return ( - self.hub.online and self.inverter.is_mmppt and self.inverter.online - ) + return self.hub.online and self.inverter.is_mmppt and self.inverter.online @property def device_info(self) -> DeviceInfo: @@ -2240,9 +2180,7 @@ async def init_device(self) -> None: ) except ModbusIOError: - raise DeviceInvalid( - f"No response from inverter ID {self.inverter_unit_id}" - ) + raise DeviceInvalid(f"No response from inverter ID {self.inverter_unit_id}") except ModbusIllegalAddress: raise DeviceInvalid(f"Meter {self.meter_id}: unsupported address") @@ -2253,7 +2191,9 @@ async def init_device(self) -> None: self.fw_version = self.decoded_common["C_Version"] self.serial = self.decoded_common["C_SerialNumber"] self.device_address = self.decoded_common["C_Device_address"] - self.name = f"{self.hub.hub_id.capitalize()} I{self.inverter_unit_id} M{self.meter_id}" + self.name = ( + f"{self.hub.hub_id.capitalize()} I{self.inverter_unit_id} M{self.meter_id}" + ) inverter_model = self.inverter_common["C_Model"] inerter_serial = self.inverter_common["C_SerialNumber"] @@ -2543,9 +2483,7 @@ async def init_device(self) -> None: if isinstance(value, float): display_value = float_to_hex(value) else: - display_value = ( - hex(value) if isinstance(value, int) else value - ) + display_value = hex(value) if isinstance(value, int) else value _LOGGER.debug( ( f"I{self.inverter_unit_id}B{self.battery_id}: " @@ -2554,30 +2492,26 @@ async def init_device(self) -> None: ) except ModbusIOError: - raise DeviceInvalid( - f"No response from inverter ID {self.inverter_unit_id}" - ) + raise DeviceInvalid(f"No response from inverter ID {self.inverter_unit_id}") except ModbusIllegalAddress: - raise DeviceInvalid( - f"Battery {self.battery_id} unsupported address" - ) + raise DeviceInvalid(f"Battery {self.battery_id} unsupported address") self.decoded_common["B_Manufacturer"] = self.decoded_common[ "B_Manufacturer" ].removesuffix(self.decoded_common["B_SerialNumber"]) - self.decoded_common["B_Model"] = self.decoded_common[ - "B_Model" - ].removesuffix(self.decoded_common["B_SerialNumber"]) + self.decoded_common["B_Model"] = self.decoded_common["B_Model"].removesuffix( + self.decoded_common["B_SerialNumber"] + ) # Remove ASCII control characters from descriptive strings ascii_ctrl_chars = dict.fromkeys(range(32)) self.decoded_common["B_Manufacturer"] = self.decoded_common[ "B_Manufacturer" ].translate(ascii_ctrl_chars) - self.decoded_common["B_Model"] = self.decoded_common[ - "B_Model" - ].translate(ascii_ctrl_chars) + self.decoded_common["B_Model"] = self.decoded_common["B_Model"].translate( + ascii_ctrl_chars + ) self.decoded_common["B_SerialNumber"] = self.decoded_common[ "B_SerialNumber" ].translate(ascii_ctrl_chars) @@ -2587,9 +2521,7 @@ async def init_device(self) -> None: == hex(SunSpecNotImpl.FLOAT32) or self.decoded_common["B_RatedEnergy"] <= 0 ): - raise DeviceInvalid( - f"Battery {self.battery_id} not usable (rating <=0)" - ) + raise DeviceInvalid(f"Battery {self.battery_id} not usable (rating <=0)") self.manufacturer = self.decoded_common["B_Manufacturer"] self.model = self.decoded_common["B_Model"] @@ -2916,22 +2848,16 @@ async def read_modbus_data(self) -> None: if isinstance(value, float): display_value = float_to_hex(value) else: - display_value = ( - hex(value) if isinstance(value, int) else value - ) + 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}: EVSE register(s) 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}" - ) + raise ModbusReadError(f"No response from EVSE ID {self.evse_unit_id}") @property def online(self) -> bool: diff --git a/custom_components/solaredge_modbus_multi/number.py b/custom_components/solaredge_modbus_multi/number.py index 073a74fb..eb9d734a 100644 --- a/custom_components/solaredge_modbus_multi/number.py +++ b/custom_components/solaredge_modbus_multi/number.py @@ -37,41 +37,25 @@ async def async_setup_entry( """Dynamic Power Control""" if hub.option_detect_extras and inverter.global_power_control: entities.append( - SolarEdgeActivePowerLimitSet( - inverter, config_entry, coordinator - ) - ) - entities.append( - SolarEdgeCosPhiSet(inverter, config_entry, coordinator) + SolarEdgeActivePowerLimitSet(inverter, config_entry, coordinator) ) + entities.append(SolarEdgeCosPhiSet(inverter, config_entry, coordinator)) """ Power Control Block """ if hub.option_detect_extras and inverter.advanced_power_control: - entities.append( - SolarEdgePowerReduce(inverter, config_entry, coordinator) - ) - entities.append( - SolarEdgeCurrentLimit(inverter, config_entry, coordinator) - ) + entities.append(SolarEdgePowerReduce(inverter, config_entry, coordinator)) + entities.append(SolarEdgeCurrentLimit(inverter, config_entry, coordinator)) """ Power Control Options: Storage Control """ if hub.option_storage_control is True: for inverter in hub.inverters: if inverter.decoded_storage_control is False: continue - entities.append( - StorageACChargeLimit(inverter, config_entry, coordinator) - ) - entities.append( - StorageBackupReserve(inverter, config_entry, coordinator) - ) - entities.append( - StorageCommandTimeout(inverter, config_entry, coordinator) - ) + entities.append(StorageACChargeLimit(inverter, config_entry, coordinator)) + entities.append(StorageBackupReserve(inverter, config_entry, coordinator)) + entities.append(StorageCommandTimeout(inverter, config_entry, coordinator)) if inverter.has_battery is True: - entities.append( - StorageChargeLimit(inverter, config_entry, coordinator) - ) + entities.append(StorageChargeLimit(inverter, config_entry, coordinator)) entities.append( StorageDischargeLimit(inverter, config_entry, coordinator) ) @@ -79,13 +63,9 @@ async def async_setup_entry( """ Power Control Options: Site Limit Control """ if hub.option_site_limit_control is True: for inverter in hub.inverters: + entities.append(SolarEdgeSiteLimit(inverter, config_entry, coordinator)) entities.append( - SolarEdgeSiteLimit(inverter, config_entry, coordinator) - ) - entities.append( - SolarEdgeExternalProductionMax( - inverter, config_entry, coordinator - ) + SolarEdgeExternalProductionMax(inverter, config_entry, coordinator) ) if entities: @@ -237,8 +217,7 @@ def available(self) -> bool: ) == hex(SunSpecNotImpl.FLOAT32) or self._platform.decoded_storage_control["backup_reserve"] < 0 - or self._platform.decoded_storage_control["backup_reserve"] - > 100 + or self._platform.decoded_storage_control["backup_reserve"] > 100 ): return False @@ -289,8 +268,7 @@ def available(self) -> bool: self._platform.decoded_storage_control is False or self._platform.decoded_storage_control["command_timeout"] == SunSpecNotImpl.UINT32 - or self._platform.decoded_storage_control["command_timeout"] - > 86400 + or self._platform.decoded_storage_control["command_timeout"] > 86400 ): return False @@ -339,9 +317,7 @@ def available(self) -> bool: try: if ( self._platform.decoded_storage_control is False - or float_to_hex( - self._platform.decoded_storage_control["charge_limit"] - ) + or float_to_hex(self._platform.decoded_storage_control["charge_limit"]) == hex(SunSpecNotImpl.FLOAT32) or self._platform.decoded_storage_control["charge_limit"] < 0 ): @@ -451,17 +427,15 @@ def name(self) -> str: @property def available(self) -> bool: try: - if float_to_hex( - self._platform.decoded_model["E_Site_Limit"] - ) == hex(SunSpecNotImpl.FLOAT32): + if float_to_hex(self._platform.decoded_model["E_Site_Limit"]) == hex( + SunSpecNotImpl.FLOAT32 + ): return False return super().available and ( (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 0) & 1 - or (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 1) - & 1 - or (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 2) - & 1 + or (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 1) & 1 + or (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 2) & 1 ) except (TypeError, KeyError): @@ -513,8 +487,7 @@ def available(self) -> bool: return ( super().available - and (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 10) - & 1 + and (int(self._platform.decoded_model["E_Lim_Ctl_Mode"]) >> 10) & 1 ) except (TypeError, KeyError): @@ -566,8 +539,7 @@ def entity_registry_enabled_default(self) -> bool: def available(self) -> bool: try: if ( - self._platform.decoded_model["I_Power_Limit"] - == SunSpecNotImpl.UINT16 + self._platform.decoded_model["I_Power_Limit"] == SunSpecNotImpl.UINT16 or self._platform.decoded_model["I_Power_Limit"] > 100 or self._platform.decoded_model["I_Power_Limit"] < 0 ): diff --git a/custom_components/solaredge_modbus_multi/repairs.py b/custom_components/solaredge_modbus_multi/repairs.py index 646c35f2..adf908e4 100644 --- a/custom_components/solaredge_modbus_multi/repairs.py +++ b/custom_components/solaredge_modbus_multi/repairs.py @@ -64,11 +64,11 @@ async def async_step_confirm( user_input[ConfName.DEVICE_LIST] = device_list_from_string( user_input[ConfName.DEVICE_LIST] ) - this_unique_id = ( - f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" - ) - existing_entry = self.hass.config_entries.async_entry_for_domain_unique_id( - DOMAIN, this_unique_id + this_unique_id = f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}" + existing_entry = ( + self.hass.config_entries.async_entry_for_domain_unique_id( + DOMAIN, this_unique_id + ) ) if ( diff --git a/custom_components/solaredge_modbus_multi/scanner.py b/custom_components/solaredge_modbus_multi/scanner.py index 5b473216..c58c52dc 100644 --- a/custom_components/solaredge_modbus_multi/scanner.py +++ b/custom_components/solaredge_modbus_multi/scanner.py @@ -187,9 +187,7 @@ async def disconnect(self) -> None: self._writer = None self._reader = None - def device_is_inverter( - self, request: list[int], response: list[int] - ) -> int: + def device_is_inverter(self, request: list[int], response: list[int]) -> int: """Check if device response matches SolarEdge inverter signature. Args: @@ -270,9 +268,7 @@ async def scan_device_id(self, device_id: int, timeout: float = 5.0) -> int: ) _LOGGER.debug(f" Received ({len(response)} bytes)") - _LOGGER.debug( - f" {' '.join(format(x, '02x') for x in response)}" - ) + _LOGGER.debug(f" {' '.join(format(x, '02x') for x in response)}") return self.FOUND diff --git a/custom_components/solaredge_modbus_multi/select.py b/custom_components/solaredge_modbus_multi/select.py index 02a50cff..aa1c9fb4 100644 --- a/custom_components/solaredge_modbus_multi/select.py +++ b/custom_components/solaredge_modbus_multi/select.py @@ -37,27 +37,17 @@ async def async_setup_entry( for inverter in hub.inverters: """Power Control Options: Storage Control""" if hub.option_storage_control and inverter.decoded_storage_control: - entities.append( - StorageControlMode(inverter, config_entry, coordinator) - ) - entities.append( - StorageACChargePolicy(inverter, config_entry, coordinator) - ) - entities.append( - StorageDefaultMode(inverter, config_entry, coordinator) - ) - entities.append( - StorageCommandMode(inverter, config_entry, coordinator) - ) + entities.append(StorageControlMode(inverter, config_entry, coordinator)) + entities.append(StorageACChargePolicy(inverter, config_entry, coordinator)) + entities.append(StorageDefaultMode(inverter, config_entry, coordinator)) + entities.append(StorageCommandMode(inverter, config_entry, coordinator)) """ Power Control Options: Site Limit Control """ if hub.option_site_limit_control: entities.append( SolaredgeLimitControlMode(inverter, config_entry, coordinator) ) - entities.append( - SolaredgeLimitControl(inverter, config_entry, coordinator) - ) + entities.append(SolaredgeLimitControl(inverter, config_entry, coordinator)) """ Power Control Block """ if hub.option_detect_extras and inverter.advanced_power_control: @@ -146,9 +136,7 @@ def available(self) -> bool: @property def current_option(self) -> str: - return self._options[ - self._platform.decoded_storage_control["control_mode"] - ] + return self._options[self._platform.decoded_storage_control["control_mode"]] async def async_select_option(self, option: str) -> None: _LOGGER.debug(f"set {self.unique_id} to {option}") @@ -201,9 +189,7 @@ def available(self) -> bool: @property def current_option(self) -> str: - return self._options[ - self._platform.decoded_storage_control["ac_charge_policy"] - ] + return self._options[self._platform.decoded_storage_control["ac_charge_policy"]] async def async_select_option(self, option: str) -> None: _LOGGER.debug(f"set {self.unique_id} to {option}") @@ -260,9 +246,7 @@ def available(self) -> bool: @property def current_option(self) -> str: - return self._options[ - self._platform.decoded_storage_control["default_mode"] - ] + return self._options[self._platform.decoded_storage_control["default_mode"]] async def async_select_option(self, option: str) -> None: _LOGGER.debug(f"set {self.unique_id} to {option}") @@ -319,9 +303,7 @@ def available(self) -> bool: @property def current_option(self) -> str: - return self._options[ - self._platform.decoded_storage_control["command_mode"] - ] + return self._options[self._platform.decoded_storage_control["command_mode"]] async def async_select_option(self, option: str) -> None: _LOGGER.debug(f"set {self.unique_id} to {option}") @@ -346,10 +328,7 @@ def __init__(self, platform, config_entry, coordinator): @property def available(self) -> bool: try: - if ( - self._platform.decoded_model["E_Lim_Ctl_Mode"] - == SunSpecNotImpl.UINT16 - ): + if self._platform.decoded_model["E_Lim_Ctl_Mode"] == SunSpecNotImpl.UINT16: return None return super().available @@ -411,10 +390,7 @@ def __init__(self, platform, config_entry, coordinator): @property def available(self) -> bool: try: - if ( - self._platform.decoded_model["E_Lim_Ctl"] - == SunSpecNotImpl.UINT16 - ): + if self._platform.decoded_model["E_Lim_Ctl"] == SunSpecNotImpl.UINT16: return False return super().available diff --git a/custom_components/solaredge_modbus_multi/sensor.py b/custom_components/solaredge_modbus_multi/sensor.py index d258f52e..9ae4a058 100644 --- a/custom_components/solaredge_modbus_multi/sensor.py +++ b/custom_components/solaredge_modbus_multi/sensor.py @@ -64,45 +64,23 @@ async def async_setup_entry( entities = [] for inverter in hub.inverters: - entities.append( - SolarEdgeLastUpdate(inverter, config_entry, coordinator) - ) + entities.append(SolarEdgeLastUpdate(inverter, config_entry, coordinator)) entities.append(SolarEdgeDevice(inverter, config_entry, coordinator)) entities.append(Version(inverter, config_entry, coordinator)) - entities.append( - SolarEdgeInverterStatus(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") - ) - entities.append( - ACCurrentSensor(inverter, config_entry, coordinator, "C") - ) - entities.append( - VoltageSensor(inverter, config_entry, coordinator, "AB") - ) - entities.append( - VoltageSensor(inverter, config_entry, coordinator, "BC") - ) - entities.append( - VoltageSensor(inverter, config_entry, coordinator, "CA") - ) - entities.append( - VoltageSensor(inverter, config_entry, coordinator, "AN") - ) - entities.append( - VoltageSensor(inverter, config_entry, coordinator, "BN") - ) - entities.append( - VoltageSensor(inverter, config_entry, coordinator, "CN") - ) + entities.append(ACCurrentSensor(inverter, config_entry, coordinator, "A")) + entities.append(ACCurrentSensor(inverter, config_entry, coordinator, "B")) + entities.append(ACCurrentSensor(inverter, config_entry, coordinator, "C")) + entities.append(VoltageSensor(inverter, config_entry, coordinator, "AB")) + entities.append(VoltageSensor(inverter, config_entry, coordinator, "BC")) + entities.append(VoltageSensor(inverter, config_entry, coordinator, "CA")) + entities.append(VoltageSensor(inverter, config_entry, coordinator, "AN")) + entities.append(VoltageSensor(inverter, config_entry, coordinator, "BN")) + entities.append(VoltageSensor(inverter, config_entry, coordinator, "CN")) entities.append(ACPower(inverter, config_entry, coordinator)) entities.append(ACFrequency(inverter, config_entry, coordinator)) entities.append(ACVoltAmp(inverter, config_entry, coordinator)) @@ -112,54 +90,38 @@ async def async_setup_entry( entities.append(DCCurrent(inverter, config_entry, coordinator)) entities.append(DCVoltage(inverter, config_entry, coordinator)) entities.append(DCPower(inverter, config_entry, coordinator)) - entities.append( - HeatSinkTemperature(inverter, config_entry, coordinator) - ) + entities.append(HeatSinkTemperature(inverter, config_entry, coordinator)) if hub.option_detect_extras and inverter.global_power_control: entities.append(SolarEdgeRRCR(inverter, config_entry, coordinator)) entities.append( SolarEdgeActivePowerLimit(inverter, config_entry, coordinator) ) - entities.append( - SolarEdgeCosPhi(inverter, config_entry, coordinator) - ) + entities.append(SolarEdgeCosPhi(inverter, config_entry, coordinator)) if hub.option_detect_extras and inverter.advanced_power_control: entities.append( - SolarEdgeCommitControlSettings( - inverter, config_entry, coordinator - ) + SolarEdgeCommitControlSettings(inverter, config_entry, coordinator) ) entities.append( - SolarEdgeDefaultControlSettings( - inverter, config_entry, coordinator - ) + SolarEdgeDefaultControlSettings(inverter, config_entry, coordinator) ) if inverter.is_mmppt: - entities.append( - SolarEdgeMMPPTEvents(inverter, config_entry, coordinator) - ) + entities.append(SolarEdgeMMPPTEvents(inverter, config_entry, coordinator)) for mmppt_unit in inverter.mmppt_units: entities.append( - SolarEdgeDCCurrentMMPPT( - mmppt_unit, config_entry, coordinator - ) + SolarEdgeDCCurrentMMPPT(mmppt_unit, config_entry, coordinator) ) entities.append( - SolarEdgeDCVoltageMMPPT( - mmppt_unit, config_entry, coordinator - ) + SolarEdgeDCVoltageMMPPT(mmppt_unit, config_entry, coordinator) ) entities.append( SolarEdgeDCPowerMMPPT(mmppt_unit, config_entry, coordinator) ) entities.append( - SolarEdgeTemperatureMMPPT( - mmppt_unit, config_entry, coordinator - ) + SolarEdgeTemperatureMMPPT(mmppt_unit, config_entry, coordinator) ) for meter in hub.meters: @@ -190,22 +152,14 @@ async def async_setup_entry( entities.append(ACVoltAmp(meter, config_entry, coordinator, "B")) entities.append(ACVoltAmp(meter, config_entry, coordinator, "C")) entities.append(ACVoltAmpReactive(meter, config_entry, coordinator)) - entities.append( - ACVoltAmpReactive(meter, config_entry, coordinator, "A") - ) - entities.append( - ACVoltAmpReactive(meter, config_entry, coordinator, "B") - ) - entities.append( - ACVoltAmpReactive(meter, config_entry, coordinator, "C") - ) + entities.append(ACVoltAmpReactive(meter, config_entry, coordinator, "A")) + entities.append(ACVoltAmpReactive(meter, config_entry, coordinator, "B")) + entities.append(ACVoltAmpReactive(meter, config_entry, coordinator, "C")) entities.append(ACPowerFactor(meter, config_entry, coordinator)) entities.append(ACPowerFactor(meter, config_entry, coordinator, "A")) entities.append(ACPowerFactor(meter, config_entry, coordinator, "B")) entities.append(ACPowerFactor(meter, config_entry, coordinator, "C")) - entities.append( - SolarEdgeACEnergy(meter, config_entry, coordinator, "Exported") - ) + entities.append(SolarEdgeACEnergy(meter, config_entry, coordinator, "Exported")) entities.append( SolarEdgeACEnergy(meter, config_entry, coordinator, "Exported_A") ) @@ -215,9 +169,7 @@ async def async_setup_entry( entities.append( SolarEdgeACEnergy(meter, config_entry, coordinator, "Exported_C") ) - entities.append( - SolarEdgeACEnergy(meter, config_entry, coordinator, "Imported") - ) + entities.append(SolarEdgeACEnergy(meter, config_entry, coordinator, "Imported")) entities.append( SolarEdgeACEnergy(meter, config_entry, coordinator, "Imported_A") ) @@ -227,98 +179,40 @@ async def async_setup_entry( entities.append( SolarEdgeACEnergy(meter, config_entry, coordinator, "Imported_C") ) - entities.append( - MeterVAhIE(meter, config_entry, coordinator, "Exported") - ) - entities.append( - MeterVAhIE(meter, config_entry, coordinator, "Exported_A") - ) - entities.append( - MeterVAhIE(meter, config_entry, coordinator, "Exported_B") - ) - entities.append( - MeterVAhIE(meter, config_entry, coordinator, "Exported_C") - ) - entities.append( - MeterVAhIE(meter, config_entry, coordinator, "Imported") - ) - entities.append( - MeterVAhIE(meter, config_entry, coordinator, "Imported_A") - ) - entities.append( - MeterVAhIE(meter, config_entry, coordinator, "Imported_B") - ) - entities.append( - MeterVAhIE(meter, config_entry, coordinator, "Imported_C") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Import_Q1") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Import_Q1_A") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Import_Q1_B") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Import_Q1_C") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Import_Q2") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Import_Q2_A") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Import_Q2_B") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Import_Q2_C") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Export_Q3") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Export_Q3_A") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Export_Q3_B") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Export_Q3_C") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Export_Q4") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Export_Q4_A") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Export_Q4_B") - ) - entities.append( - MetervarhIE(meter, config_entry, coordinator, "Export_Q4_C") - ) + entities.append(MeterVAhIE(meter, config_entry, coordinator, "Exported")) + entities.append(MeterVAhIE(meter, config_entry, coordinator, "Exported_A")) + entities.append(MeterVAhIE(meter, config_entry, coordinator, "Exported_B")) + entities.append(MeterVAhIE(meter, config_entry, coordinator, "Exported_C")) + entities.append(MeterVAhIE(meter, config_entry, coordinator, "Imported")) + entities.append(MeterVAhIE(meter, config_entry, coordinator, "Imported_A")) + entities.append(MeterVAhIE(meter, config_entry, coordinator, "Imported_B")) + entities.append(MeterVAhIE(meter, config_entry, coordinator, "Imported_C")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q1")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q1_A")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q1_B")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q1_C")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q2")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q2_A")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q2_B")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Import_Q2_C")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q3")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q3_A")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q3_B")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q3_C")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q4")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q4_A")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q4_B")) + entities.append(MetervarhIE(meter, config_entry, coordinator, "Export_Q4_C")) for battery in hub.batteries: entities.append(SolarEdgeLastUpdate(battery, config_entry, coordinator)) entities.append(SolarEdgeDevice(battery, config_entry, coordinator)) entities.append(Version(battery, config_entry, coordinator)) - entities.append( - SolarEdgeBatteryAvgTemp(battery, config_entry, coordinator) - ) - entities.append( - SolarEdgeBatteryMaxTemp(battery, config_entry, coordinator) - ) - entities.append( - SolarEdgeBatteryVoltage(battery, config_entry, coordinator) - ) - entities.append( - SolarEdgeBatteryCurrent(battery, config_entry, coordinator) - ) - entities.append( - SolarEdgeBatteryPower(battery, config_entry, coordinator) - ) + entities.append(SolarEdgeBatteryAvgTemp(battery, config_entry, coordinator)) + entities.append(SolarEdgeBatteryMaxTemp(battery, config_entry, coordinator)) + entities.append(SolarEdgeBatteryVoltage(battery, config_entry, coordinator)) + entities.append(SolarEdgeBatteryCurrent(battery, config_entry, coordinator)) + entities.append(SolarEdgeBatteryPower(battery, config_entry, coordinator)) entities.append( SolarEdgeBatteryPowerInverted(battery, config_entry, coordinator) ) @@ -328,35 +222,25 @@ async def async_setup_entry( entities.append( SolarEdgeBatteryEnergyImport(battery, config_entry, coordinator) ) - entities.append( - SolarEdgeBatteryMaxEnergy(battery, config_entry, coordinator) - ) + entities.append(SolarEdgeBatteryMaxEnergy(battery, config_entry, coordinator)) entities.append( SolarEdgeBatteryMaxChargePower(battery, config_entry, coordinator) ) entities.append( - SolarEdgeBatteryMaxDischargePower( - battery, config_entry, coordinator - ) + SolarEdgeBatteryMaxDischargePower(battery, config_entry, coordinator) ) entities.append( - SolarEdgeBatteryMaxChargePeakPower( - battery, config_entry, coordinator - ) + SolarEdgeBatteryMaxChargePeakPower(battery, config_entry, coordinator) ) entities.append( - SolarEdgeBatteryMaxDischargePeakPower( - battery, config_entry, coordinator - ) + SolarEdgeBatteryMaxDischargePeakPower(battery, config_entry, coordinator) ) entities.append( SolarEdgeBatteryAvailableEnergy(battery, config_entry, coordinator) ) entities.append(SolarEdgeBatterySOH(battery, config_entry, coordinator)) entities.append(SolarEdgeBatterySOE(battery, config_entry, coordinator)) - entities.append( - SolarEdgeBatteryStatus(battery, config_entry, coordinator) - ) + entities.append(SolarEdgeBatteryStatus(battery, config_entry, coordinator)) for evse in hub.evses: entities.append(Version(evse, config_entry, coordinator)) @@ -471,9 +355,7 @@ def extra_state_attributes(self): pass attrs["mmppt_did"] = self._platform.decoded_mmppt["mmppt_DID"] - attrs["mmppt_units"] = self._platform.decoded_mmppt[ - "mmppt_Units" - ] + attrs["mmppt_units"] = self._platform.decoded_mmppt["mmppt_Units"] except AttributeError: pass @@ -565,10 +447,8 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == self.SUNSPEC_NOT_IMPL - or self._platform.decoded_model["AC_Current_SF"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_Current_SF"] - not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["AC_Current_SF"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_Current_SF"] not in SUNSPEC_SF_RANGE ): return None @@ -659,10 +539,8 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == self.SUNSPEC_NOT_IMPL - or self._platform.decoded_model["AC_Voltage_SF"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_Voltage_SF"] - not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["AC_Voltage_SF"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_Voltage_SF"] not in SUNSPEC_SF_RANGE ): return None @@ -733,8 +611,7 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_Power_SF"] - == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_Power_SF"] == SunSpecNotImpl.INT16 ): return None @@ -777,9 +654,7 @@ def name(self) -> str: @property def entity_registry_enabled_default(self) -> bool: - return AwesomeVersion(HA_VERSION) < AwesomeVersion( - INVERTED_POWER_VERSION - ) + return AwesomeVersion(HA_VERSION) < AwesomeVersion(INVERTED_POWER_VERSION) @property def native_value(self): @@ -806,8 +681,7 @@ def name(self) -> str: def native_value(self): try: if ( - self._platform.decoded_model["AC_Frequency"] - == SunSpecNotImpl.UINT16 + self._platform.decoded_model["AC_Frequency"] == SunSpecNotImpl.UINT16 or self._platform.decoded_model["AC_Frequency_SF"] == SunSpecNotImpl.INT16 or self._platform.decoded_model["AC_Frequency_SF"] @@ -867,10 +741,8 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_VA_SF"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_VA_SF"] - not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["AC_VA_SF"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_VA_SF"] not in SUNSPEC_SF_RANGE ): return None @@ -926,10 +798,8 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_var_SF"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_var_SF"] - not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["AC_var_SF"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_var_SF"] not in SUNSPEC_SF_RANGE ): return None @@ -985,10 +855,8 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_PF_SF"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["AC_PF_SF"] - not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["AC_PF_SF"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["AC_PF_SF"] not in SUNSPEC_SF_RANGE ): return None @@ -1086,10 +954,8 @@ def name(self) -> str: def available(self) -> bool: try: if ( - self._platform.decoded_model[self._model_key] - == SunSpecAccum.NA32 - or self._platform.decoded_model[self._model_key] - > SunSpecAccum.LIMIT32 + self._platform.decoded_model[self._model_key] == SunSpecAccum.NA32 + or self._platform.decoded_model[self._model_key] > SunSpecAccum.LIMIT32 or self._platform.decoded_model["AC_Energy_WH_SF"] not in SUNSPEC_SF_RANGE ): @@ -1148,12 +1014,9 @@ def name(self) -> str: @property def available(self) -> bool: if ( - self._platform.decoded_model["I_DC_Current"] - == SunSpecNotImpl.UINT16 - or self._platform.decoded_model["I_DC_Current_SF"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["I_DC_Current_SF"] - not in SUNSPEC_SF_RANGE + self._platform.decoded_model["I_DC_Current"] == SunSpecNotImpl.UINT16 + or self._platform.decoded_model["I_DC_Current_SF"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["I_DC_Current_SF"] not in SUNSPEC_SF_RANGE ): return False @@ -1172,10 +1035,7 @@ def native_value(self): @property def suggested_display_precision(self) -> int: - if ( - self._platform.decoded_model["I_DC_Current_SF"] - not in SUNSPEC_SF_RANGE - ): + if self._platform.decoded_model["I_DC_Current_SF"] not in SUNSPEC_SF_RANGE: return 1 return abs(self._platform.decoded_model["I_DC_Current_SF"]) @@ -1191,7 +1051,9 @@ class SolarEdgeDCCurrentMMPPT(SolarEdgeSensorBase): @property def unique_id(self) -> str: - return f"{self._platform.inverter.uid_base}_dc_current_mmppt{self._platform.unit}" + return ( + f"{self._platform.inverter.uid_base}_dc_current_mmppt{self._platform.unit}" + ) @property def name(self) -> str: @@ -1200,9 +1062,7 @@ def name(self) -> str: @property def available(self) -> bool: if ( - self._platform.inverter.decoded_model[self._platform.mmppt_key][ - "DCA" - ] + self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCA"] == SunSpecNotImpl.INT16 or self._platform.inverter.decoded_model["mmppt_DCA_SF"] == SunSpecNotImpl.INT16 @@ -1216,9 +1076,7 @@ def available(self) -> bool: @property def native_value(self): return self.scale_factor( - self._platform.inverter.decoded_model[self._platform.mmppt_key][ - "DCA" - ], + self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCA"], self._platform.inverter.decoded_model["mmppt_DCA_SF"], ) @@ -1246,8 +1104,7 @@ def name(self) -> str: def native_value(self): try: if ( - self._platform.decoded_model["I_DC_Voltage"] - == SunSpecNotImpl.UINT16 + self._platform.decoded_model["I_DC_Voltage"] == SunSpecNotImpl.UINT16 or self._platform.decoded_model["I_DC_Voltage_SF"] == SunSpecNotImpl.INT16 or self._platform.decoded_model["I_DC_Voltage_SF"] @@ -1278,7 +1135,9 @@ class SolarEdgeDCVoltageMMPPT(SolarEdgeSensorBase): @property def unique_id(self) -> str: - return f"{self._platform.inverter.uid_base}_dc_voltage_mmppt{self._platform.unit}" + return ( + f"{self._platform.inverter.uid_base}_dc_voltage_mmppt{self._platform.unit}" + ) @property def name(self) -> str: @@ -1287,9 +1146,7 @@ def name(self) -> str: @property def available(self) -> bool: if ( - self._platform.inverter.decoded_model[self._platform.mmppt_key][ - "DCV" - ] + self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCV"] == SunSpecNotImpl.INT16 or self._platform.inverter.decoded_model["mmppt_DCV_SF"] == SunSpecNotImpl.INT16 @@ -1303,9 +1160,7 @@ def available(self) -> bool: @property def native_value(self): return self.scale_factor( - self._platform.inverter.decoded_model[self._platform.mmppt_key][ - "DCV" - ], + self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCV"], self._platform.inverter.decoded_model["mmppt_DCV_SF"], ) @@ -1334,12 +1189,9 @@ def name(self) -> str: def native_value(self): try: if ( - self._platform.decoded_model["I_DC_Power"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["I_DC_Power_SF"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["I_DC_Power_SF"] - not in SUNSPEC_SF_RANGE + self._platform.decoded_model["I_DC_Power"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["I_DC_Power_SF"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["I_DC_Power_SF"] not in SUNSPEC_SF_RANGE ): return None @@ -1376,9 +1228,7 @@ def name(self) -> str: @property def available(self) -> bool: if ( - self._platform.inverter.decoded_model[self._platform.mmppt_key][ - "DCW" - ] + self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCW"] == SunSpecNotImpl.INT16 or self._platform.inverter.decoded_model["mmppt_DCW_SF"] == SunSpecNotImpl.INT16 @@ -1392,9 +1242,7 @@ def available(self) -> bool: @property def native_value(self): return self.scale_factor( - self._platform.inverter.decoded_model[self._platform.mmppt_key][ - "DCW" - ], + self._platform.inverter.decoded_model[self._platform.mmppt_key]["DCW"], self._platform.inverter.decoded_model["mmppt_DCW_SF"], ) @@ -1424,12 +1272,9 @@ def native_value(self): try: if ( self._platform.decoded_model["I_Temp_Sink"] == 0x0 - or self._platform.decoded_model["I_Temp_Sink"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["I_Temp_SF"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["I_Temp_SF"] - not in SUNSPEC_SF_RANGE + or self._platform.decoded_model["I_Temp_Sink"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["I_Temp_SF"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["I_Temp_SF"] not in SUNSPEC_SF_RANGE ): return None @@ -1458,9 +1303,7 @@ class SolarEdgeTemperatureMMPPT(SolarEdgeSensorBase): @property def unique_id(self) -> str: - return ( - f"{self._platform.inverter.uid_base}_tmp_mmppt{self._platform.unit}" - ) + return f"{self._platform.inverter.uid_base}_tmp_mmppt{self._platform.unit}" @property def name(self) -> str: @@ -1469,9 +1312,7 @@ def name(self) -> str: @property def available(self) -> bool: if ( - self._platform.inverter.decoded_model[self._platform.mmppt_key][ - "Tmp" - ] + self._platform.inverter.decoded_model[self._platform.mmppt_key]["Tmp"] == SunSpecNotImpl.INT16 ): return False @@ -1480,9 +1321,7 @@ def available(self) -> bool: @property def native_value(self): - return self._platform.inverter.decoded_model[self._platform.mmppt_key][ - "Tmp" - ] + return self._platform.inverter.decoded_model[self._platform.mmppt_key]["Tmp"] class SolarEdgeStatusSensor(SolarEdgeSensorBase): @@ -1504,10 +1343,7 @@ class SolarEdgeInverterStatus(SolarEdgeStatusSensor): @property def native_value(self): try: - if ( - self._platform.decoded_model["I_Status"] - == SunSpecNotImpl.UINT16 - ): + if self._platform.decoded_model["I_Status"] == SunSpecNotImpl.UINT16: return None return str(DEVICE_STATUS[self._platform.decoded_model["I_Status"]]) @@ -1542,10 +1378,7 @@ class SolarEdgeBatteryStatus(SolarEdgeStatusSensor): @property def native_value(self): try: - if ( - self._platform.decoded_model["B_Status"] - == SunSpecNotImpl.UINT32 - ): + if self._platform.decoded_model["B_Status"] == SunSpecNotImpl.UINT32: return None return str(BATTERY_STATUS[self._platform.decoded_model["B_Status"]]) @@ -1592,10 +1425,7 @@ def entity_registry_enabled_default(self) -> bool: @property def native_value(self): try: - if ( - self._platform.decoded_model["I_Status_Vendor"] - == SunSpecNotImpl.UINT16 - ): + if self._platform.decoded_model["I_Status_Vendor"] == SunSpecNotImpl.UINT16: return None else: @@ -1663,10 +1493,7 @@ def extra_state_attributes(self): "error_code": hex(error), } - if ( - controller in VENDOR4_STATUS - and error in VENDOR4_STATUS[controller] - ): + if controller in VENDOR4_STATUS and error in VENDOR4_STATUS[controller]: attrs["description"] = VENDOR4_STATUS[controller][error] return attrs @@ -1728,9 +1555,7 @@ def extra_state_attributes(self): else: for i in range(0, 4): - if int(str(self._platform.decoded_model["I_RRCR"])) & ( - 1 << i - ): + if int(str(self._platform.decoded_model["I_RRCR"])) & (1 << i): rrcr_inputs.append(RRCR_STATUS[i]) return {"inputs": str(rrcr_inputs)} @@ -1763,8 +1588,7 @@ def entity_registry_enabled_default(self) -> bool: def native_value(self) -> int: try: if ( - self._platform.decoded_model["I_Power_Limit"] - == SunSpecNotImpl.UINT16 + self._platform.decoded_model["I_Power_Limit"] == SunSpecNotImpl.UINT16 or self._platform.decoded_model["I_Power_Limit"] > 100 or self._platform.decoded_model["I_Power_Limit"] < 0 ): @@ -1828,10 +1652,7 @@ def name(self) -> str: @property def native_value(self): try: - if ( - self._platform.decoded_model["M_Events"] - == SunSpecNotImpl.UINT32 - ): + if self._platform.decoded_model["M_Events"] == SunSpecNotImpl.UINT32: return None else: @@ -1850,9 +1671,7 @@ def extra_state_attributes(self): else: for i in range(2, 31): try: - if int(str(self._platform.decoded_model["M_Events"])) & ( - 1 << i - ): + if int(str(self._platform.decoded_model["M_Events"])) & (1 << i): m_events_active.append(METER_EVENTS[i]) except KeyError: @@ -1878,10 +1697,7 @@ def name(self) -> str: @property def available(self) -> bool: try: - if ( - self._platform.decoded_model["mmppt_Events"] - == SunSpecNotImpl.UINT32 - ): + if self._platform.decoded_model["mmppt_Events"] == SunSpecNotImpl.UINT32: return False return super().available @@ -1903,17 +1719,15 @@ def extra_state_attributes(self) -> str: else: for i in range(0, 31): try: - if int( - str(self._platform.decoded_model["mmppt_Events"]) - ) & (1 << i): + if int(str(self._platform.decoded_model["mmppt_Events"])) & ( + 1 << i + ): mmppt_events_active.append(MMPPT_EVENTS[i]) except KeyError: pass attrs["events"] = str(mmppt_events_active) - attrs["bits"] = ( - f"{int(self._platform.decoded_model['mmppt_Events']):032b}" - ) + attrs["bits"] = f"{int(self._platform.decoded_model['mmppt_Events']):032b}" return attrs @@ -1971,12 +1785,9 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecAccum.NA32 - or self._platform.decoded_model[model_key] - > SunSpecAccum.LIMIT32 - or self._platform.decoded_model["M_VAh_SF"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["M_VAh_SF"] - not in SUNSPEC_SF_RANGE + or self._platform.decoded_model[model_key] > SunSpecAccum.LIMIT32 + or self._platform.decoded_model["M_VAh_SF"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["M_VAh_SF"] not in SUNSPEC_SF_RANGE ): return None @@ -2052,12 +1863,9 @@ def native_value(self): try: if ( self._platform.decoded_model[model_key] == SunSpecAccum.NA32 - or self._platform.decoded_model[model_key] - > SunSpecAccum.LIMIT32 - or self._platform.decoded_model["M_varh_SF"] - == SunSpecNotImpl.INT16 - or self._platform.decoded_model["M_varh_SF"] - not in SUNSPEC_SF_RANGE + or self._platform.decoded_model[model_key] > SunSpecAccum.LIMIT32 + or self._platform.decoded_model["M_varh_SF"] == SunSpecNotImpl.INT16 + or self._platform.decoded_model["M_varh_SF"] not in SUNSPEC_SF_RANGE ): return None @@ -2097,10 +1905,8 @@ def native_value(self): if ( float_to_hex(self._platform.decoded_model["B_Temp_Average"]) == hex(SunSpecNotImpl.FLOAT32) - or self._platform.decoded_model["B_Temp_Average"] - < BatteryLimit.Tmin - or self._platform.decoded_model["B_Temp_Average"] - > BatteryLimit.Tmax + or self._platform.decoded_model["B_Temp_Average"] < BatteryLimit.Tmin + or self._platform.decoded_model["B_Temp_Average"] > BatteryLimit.Tmax ): return None @@ -2132,10 +1938,8 @@ def native_value(self): if ( float_to_hex(self._platform.decoded_model["B_Temp_Max"]) == hex(SunSpecNotImpl.FLOAT32) - or self._platform.decoded_model["B_Temp_Max"] - < BatteryLimit.Tmin - or self._platform.decoded_model["B_Temp_Max"] - > BatteryLimit.Tmax + or self._platform.decoded_model["B_Temp_Max"] < BatteryLimit.Tmin + or self._platform.decoded_model["B_Temp_Max"] > BatteryLimit.Tmax ): return None @@ -2155,10 +1959,8 @@ def native_value(self): if ( float_to_hex(self._platform.decoded_model["B_DC_Voltage"]) == hex(SunSpecNotImpl.FLOAT32) - or self._platform.decoded_model["B_DC_Voltage"] - < BatteryLimit.Vmin - or self._platform.decoded_model["B_DC_Voltage"] - > BatteryLimit.Vmax + or self._platform.decoded_model["B_DC_Voltage"] < BatteryLimit.Vmin + or self._platform.decoded_model["B_DC_Voltage"] > BatteryLimit.Vmax ): return None @@ -2193,10 +1995,8 @@ def available(self) -> bool: if ( float_to_hex(self._platform.decoded_model["B_DC_Current"]) == hex(SunSpecNotImpl.FLOAT32) - or self._platform.decoded_model["B_DC_Current"] - < BatteryLimit.Amin - or self._platform.decoded_model["B_DC_Current"] - > BatteryLimit.Amax + or self._platform.decoded_model["B_DC_Current"] < BatteryLimit.Amin + or self._platform.decoded_model["B_DC_Current"] > BatteryLimit.Amax ): return False @@ -2265,9 +2065,7 @@ def name(self) -> str: @property def entity_registry_enabled_default(self) -> bool: - return AwesomeVersion(HA_VERSION) < AwesomeVersion( - INVERTED_POWER_VERSION - ) + return AwesomeVersion(HA_VERSION) < AwesomeVersion(INVERTED_POWER_VERSION) @property def native_value(self): @@ -2316,21 +2114,14 @@ def native_value(self): if self._last is None: self._last = 0 - if ( - self._platform.decoded_model["B_Export_Energy_WH"] - >= self._last - ): - self._last = self._platform.decoded_model[ - "B_Export_Energy_WH" - ] + if self._platform.decoded_model["B_Export_Energy_WH"] >= self._last: + self._last = self._platform.decoded_model["B_Export_Energy_WH"] self._log_once = False if self._platform.allow_battery_energy_reset: self._count = 0 - return self._platform.decoded_model[ - "B_Export_Energy_WH" - ] + return self._platform.decoded_model["B_Export_Energy_WH"] else: if ( @@ -2357,10 +2148,7 @@ def native_value(self): ) ) - if ( - self._count - > self._platform.battery_energy_reset_cycles - ): + if self._count > self._platform.battery_energy_reset_cycles: _LOGGER.debug( f"B_Export_Energy reset at cycle {self._count}" ) @@ -2415,21 +2203,14 @@ def native_value(self): if self._last is None: self._last = 0 - if ( - self._platform.decoded_model["B_Import_Energy_WH"] - >= self._last - ): - self._last = self._platform.decoded_model[ - "B_Import_Energy_WH" - ] + if self._platform.decoded_model["B_Import_Energy_WH"] >= self._last: + self._last = self._platform.decoded_model["B_Import_Energy_WH"] self._log_once = False if self._platform.allow_battery_energy_reset: self._count = 0 - return self._platform.decoded_model[ - "B_Import_Energy_WH" - ] + return self._platform.decoded_model["B_Import_Energy_WH"] else: if ( @@ -2456,10 +2237,7 @@ def native_value(self): ) ) - if ( - self._count - > self._platform.battery_energy_reset_cycles - ): + if self._count > self._platform.battery_energy_reset_cycles: _LOGGER.debug( f"B_Import_Energy reset at cycle {self._count}" ) @@ -2600,9 +2378,7 @@ def name(self) -> str: @property def available(self): if ( - float_to_hex( - self._platform.decoded_model["B_MaxDischargePeakPower"] - ) + float_to_hex(self._platform.decoded_model["B_MaxDischargePeakPower"]) == hex(SunSpecNotImpl.FLOAT32) or self._platform.decoded_model["B_MaxDischargePeakPower"] < 0 ): @@ -2739,8 +2515,7 @@ def name(self) -> str: @property def available(self) -> bool: return ( - super().available - and "CommitPwrCtlSettings" in self._platform.decoded_model + super().available and "CommitPwrCtlSettings" in self._platform.decoded_model ) @property @@ -2751,9 +2526,7 @@ def native_value(self): def extra_state_attributes(self): attrs = {} - attrs["hex_value"] = hex( - self._platform.decoded_model["CommitPwrCtlSettings"] - ) + attrs["hex_value"] = hex(self._platform.decoded_model["CommitPwrCtlSettings"]) if self._platform.decoded_model["CommitPwrCtlSettings"] == 0x0: attrs["status"] = "SUCCESS" @@ -2804,9 +2577,7 @@ def native_value(self): def extra_state_attributes(self): attrs = {} - attrs["hex_value"] = hex( - self._platform.decoded_model["RestorePwrCtlDefaults"] - ) + attrs["hex_value"] = hex(self._platform.decoded_model["RestorePwrCtlDefaults"]) if self._platform.decoded_model["RestorePwrCtlDefaults"] == 0x0: attrs["status"] = "SUCCESS" diff --git a/custom_components/solaredge_modbus_multi/switch.py b/custom_components/solaredge_modbus_multi/switch.py index 65bff00c..c95e31d8 100644 --- a/custom_components/solaredge_modbus_multi/switch.py +++ b/custom_components/solaredge_modbus_multi/switch.py @@ -39,9 +39,7 @@ async def async_setup_entry( ) if hub.option_detect_extras and inverter.advanced_power_control: - entities.append( - SolarEdgeGridControl(inverter, config_entry, coordinator) - ) + entities.append(SolarEdgeGridControl(inverter, config_entry, coordinator)) if entities: async_add_entities(entities) @@ -87,10 +85,7 @@ class SolarEdgeExternalProduction(SolarEdgeSwitchBase): @property def available(self) -> bool: try: - if ( - self._platform.decoded_model["E_Lim_Ctl_Mode"] - == SunSpecNotImpl.UINT16 - ): + if self._platform.decoded_model["E_Lim_Ctl_Mode"] == SunSpecNotImpl.UINT16: return False return super().available @@ -157,10 +152,7 @@ class SolarEdgeNegativeSiteLimit(SolarEdgeSwitchBase): @property def available(self) -> bool: try: - if ( - self._platform.decoded_model["E_Lim_Ctl_Mode"] - == SunSpecNotImpl.UINT16 - ): + if self._platform.decoded_model["E_Lim_Ctl_Mode"] == SunSpecNotImpl.UINT16: return False return super().available diff --git a/pyproject.toml b/pyproject.toml index 3320acbb..a8b297ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.ruff] -line-length = 80 +line-length = 88 [tool.ruff.lint] extend-select = ["I"] From 96a874d4a4b5cb27adc10eceb5fd0da51c6f9deb Mon Sep 17 00:00:00 2001 From: WillCodeForCats <48533968+WillCodeForCats@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:32:38 -0700 Subject: [PATCH 13/13] Use pyproject.toml in linter config --- .github/workflows/linter.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 1c431d07..c5df0fc7 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -32,5 +32,8 @@ jobs: VALIDATE_PYTHON_ISORT: false VALIDATE_SPELL_CODESPELL: false VALIDATE_BIOME_FORMAT: false + LINTER_RULES_PATH: . + PYTHON_RUFF_CONFIG_FILE: pyproject.toml + PYTHON_RUFF_FORMAT_CONFIG_FILE: pyproject.toml DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}