diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fa0af01..e117e6b 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: # These are duplicated from requirements.txt additional_dependencies: [ - homeassistant-stubs==2025.1.0, + homeassistant-stubs==2025.1.1, types-python-slugify==8.0.0.2, voluptuous-stubs==0.1.1, ] diff --git a/custom_components/foxess_modbus/client/custom_modbus_tcp_client.py b/custom_components/foxess_modbus/client/custom_modbus_tcp_client.py index 95f3be7..37426b9 100644 --- a/custom_components/foxess_modbus/client/custom_modbus_tcp_client.py +++ b/custom_components/foxess_modbus/client/custom_modbus_tcp_client.py @@ -36,7 +36,7 @@ def connect(self) -> bool: # https://github.com/nathanmarlor/foxess_modbus/issues/275 def recv(self, size: int | None) -> bytes: """Read data from the underlying descriptor.""" - super(ModbusTcpClient, self).recv(size) + super(ModbusTcpClient, self).recv(size) # type: ignore if not self.socket: raise ConnectionException(str(self)) @@ -73,9 +73,7 @@ def recv(self, size: int | None) -> bytes: # We expect a single-element list if this succeeds, or an empty list if it timed out if len(poll_res) > 0: if (recv_data := self.socket.recv(recv_size)) == b"": - return self._handle_abrupt_socket_close( # type: ignore[no-any-return] - size, data, time.time() - time_ - ) + return self._handle_abrupt_socket_close(size, data, time.time() - time_) data.append(recv_data) data_length += len(recv_data) time_ = time.time() diff --git a/custom_components/foxess_modbus/client/modbus_client.py b/custom_components/foxess_modbus/client/modbus_client.py index 8b6f01e..53b71b7 100644 --- a/custom_components/foxess_modbus/client/modbus_client.py +++ b/custom_components/foxess_modbus/client/modbus_client.py @@ -7,7 +7,6 @@ from typing import Callable from typing import Type from typing import TypeVar -from typing import cast import serial from homeassistant.core import HomeAssistant @@ -152,7 +151,7 @@ async def read_registers( response, ) - return cast(list[int], response.registers) + return response.registers async def write_registers(self, register_address: int, register_values: list[int], slave: int) -> None: """Write registers""" @@ -199,7 +198,7 @@ async def write_registers(self, register_address: int, register_values: list[int async def _async_pymodbus_call(self, call: Callable[..., T], *args: Any, auto_connect: bool = True) -> T: """Convert async to sync pymodbus call.""" - def _call() -> T: + def _call(_: Any) -> T: # When using pollserial://, connected calls into serial.serial_for_url, which calls importlib.import_module, # which HA doesn't like (see https://github.com/nathanmarlor/foxess_modbus/issues/618). # Therefore we need to do this check inside the executor job diff --git a/custom_components/foxess_modbus/entities/entity_factory.py b/custom_components/foxess_modbus/entities/entity_factory.py index 3ebe3e4..75bc69d 100644 --- a/custom_components/foxess_modbus/entities/entity_factory.py +++ b/custom_components/foxess_modbus/entities/entity_factory.py @@ -6,7 +6,7 @@ from typing import Sequence from homeassistant.helpers.entity import Entity -from homeassistant.helpers.entity import EntityDescription +from homeassistant.util.frozen_dataclass_compat import FrozenOrThawed from ..common.entity_controller import EntityController from ..common.types import Inv @@ -19,7 +19,7 @@ # We need to combine EntityDescription's metaclass with ABC's metaclass, see # https://github.com/nathanmarlor/foxess_modbus/issues/480. This is to allow HA to move to frozen entity descriptions # (to aid caching), and will start logging deprecation warnings in 2024.x. -class EntityFactoryMetaclass(type(EntityDescription), type(ABC)): # type: ignore +class EntityFactoryMetaclass(FrozenOrThawed, type(ABC)): # type: ignore """ Metaclass to use for EntityFactory. """ diff --git a/custom_components/foxess_modbus/entities/modbus_battery_sensor.py b/custom_components/foxess_modbus/entities/modbus_battery_sensor.py index 0be1f21..86b0169 100644 --- a/custom_components/foxess_modbus/entities/modbus_battery_sensor.py +++ b/custom_components/foxess_modbus/entities/modbus_battery_sensor.py @@ -18,7 +18,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusBatterySensorDescription(ModbusSensorDescription): +class ModbusBatterySensorDescription(ModbusSensorDescription): # type: ignore[misc] """Description for ModbusBatterySensor""" bms_connect_state_address: list[ModbusAddressSpec] diff --git a/custom_components/foxess_modbus/entities/modbus_binary_sensor.py b/custom_components/foxess_modbus/entities/modbus_binary_sensor.py index 57cce71..e5a4aa0 100644 --- a/custom_components/foxess_modbus/entities/modbus_binary_sensor.py +++ b/custom_components/foxess_modbus/entities/modbus_binary_sensor.py @@ -25,7 +25,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusBinarySensorDescription(BinarySensorEntityDescription, EntityFactory): +class ModbusBinarySensorDescription(BinarySensorEntityDescription, EntityFactory): # type: ignore[misc, override] """Description for ModbusBinarySensor""" address: list[InverterModelSpec] diff --git a/custom_components/foxess_modbus/entities/modbus_charge_period_sensors.py b/custom_components/foxess_modbus/entities/modbus_charge_period_sensors.py index ca440cb..383cc7e 100644 --- a/custom_components/foxess_modbus/entities/modbus_charge_period_sensors.py +++ b/custom_components/foxess_modbus/entities/modbus_charge_period_sensors.py @@ -55,7 +55,7 @@ def _is_force_charge_enabled( @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusChargePeriodStartEndSensorDescription(SensorEntityDescription, EntityFactory): +class ModbusChargePeriodStartEndSensorDescription(SensorEntityDescription, EntityFactory): # type: ignore[misc, override] """Entity description for ModbusChargePeriodStartEndSensor""" address: list[InverterModelSpec] @@ -187,7 +187,9 @@ def addresses(self) -> list[int]: @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusEnableForceChargeSensorDescription(BinarySensorEntityDescription, EntityFactory): +class ModbusEnableForceChargeSensorDescription( # type ignore[misc, override] + BinarySensorEntityDescription, EntityFactory +): """Entity description for ModbusEnableForceChargeSensor""" period_start_address: list[InverterModelSpec] diff --git a/custom_components/foxess_modbus/entities/modbus_entity_mixin.py b/custom_components/foxess_modbus/entities/modbus_entity_mixin.py index 7a31d33..0d76625 100644 --- a/custom_components/foxess_modbus/entities/modbus_entity_mixin.py +++ b/custom_components/foxess_modbus/entities/modbus_entity_mixin.py @@ -8,8 +8,10 @@ from homeassistant.const import Platform from homeassistant.helpers import entity_registry +from homeassistant.helpers.entity import ABCCachedProperties from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import Entity +from propcache import cached_property from ..common.entity_controller import EntityController from ..common.entity_controller import ModbusControllerEntity @@ -67,18 +69,18 @@ class ModbusEntityProtocol(Protocol): _controller: EntityController +# HA introduced a ABCCachedProperties metaclass which is used by Entity, and which derives from ABCMeta. +# This conflicts with Protocol's metaclass (from ModbusEntityProtocol). +class ModbusEntityMixinMetaclass(ABCCachedProperties, type(Protocol)): # type: ignore + pass + + if TYPE_CHECKING: _ModbusEntityMixinBase = Entity else: _ModbusEntityMixinBase = object -# HA introduced a ABCCachedProperties metaclass which is used by Entity, and which derives from ABCMeta. -# This conflicts with Protocol's metaclass (from ModbusEntityProtocol). -class ModbusEntityMixinMetaclass(type(Entity), type(Protocol)): # type: ignore - pass - - class ModbusEntityMixin( ModbusControllerEntity, ModbusEntityProtocol, _ModbusEntityMixinBase, metaclass=ModbusEntityMixinMetaclass ): @@ -88,7 +90,7 @@ class ModbusEntityMixin( This provides properties which are common to all FoxESS entities. """ - @property + @cached_property def unique_id(self) -> str: """Return a unique ID.""" return _create_unique_id(self.entity_description.key, self._controller.inverter_details) diff --git a/custom_components/foxess_modbus/entities/modbus_fault_sensor.py b/custom_components/foxess_modbus/entities/modbus_fault_sensor.py index cd5127a..6d2183b 100644 --- a/custom_components/foxess_modbus/entities/modbus_fault_sensor.py +++ b/custom_components/foxess_modbus/entities/modbus_fault_sensor.py @@ -228,7 +228,7 @@ def __post_init__(self) -> None: @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusFaultSensorDescription(SensorEntityDescription, EntityFactory): +class ModbusFaultSensorDescription(SensorEntityDescription, EntityFactory): # type: ignore[misc, override] """Description for ModbusFaultSensor""" addresses: list[ModbusAddressesSpec] diff --git a/custom_components/foxess_modbus/entities/modbus_integration_sensor.py b/custom_components/foxess_modbus/entities/modbus_integration_sensor.py index c345fbf..817e802 100644 --- a/custom_components/foxess_modbus/entities/modbus_integration_sensor.py +++ b/custom_components/foxess_modbus/entities/modbus_integration_sensor.py @@ -29,7 +29,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusIntegrationSensorDescription(SensorEntityDescription, EntityFactory): +class ModbusIntegrationSensorDescription(SensorEntityDescription, EntityFactory): # type: ignore[misc, override] """Custom sensor description""" models: list[EntitySpec] diff --git a/custom_components/foxess_modbus/entities/modbus_inverter_state_sensor.py b/custom_components/foxess_modbus/entities/modbus_inverter_state_sensor.py index ffcc303..d952c2a 100644 --- a/custom_components/foxess_modbus/entities/modbus_inverter_state_sensor.py +++ b/custom_components/foxess_modbus/entities/modbus_inverter_state_sensor.py @@ -40,7 +40,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusInverterStateSensorDescription(SensorEntityDescription, EntityFactory): +class ModbusInverterStateSensorDescription(SensorEntityDescription, EntityFactory): # type: ignore[misc, override] """Description for ModbusInverterStateSensor""" address: list[ModbusAddressSpec] @@ -104,7 +104,7 @@ def addresses(self) -> list[int]: @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusG2InverterStateSensorDescription(SensorEntityDescription, EntityFactory): +class ModbusG2InverterStateSensorDescription(SensorEntityDescription, EntityFactory): # type ignore[override] """Description for ModbusInverterStateSensor""" # Fault 1 code, fault 3 code diff --git a/custom_components/foxess_modbus/entities/modbus_lambda_sensor.py b/custom_components/foxess_modbus/entities/modbus_lambda_sensor.py index 3e4649b..3d593e5 100644 --- a/custom_components/foxess_modbus/entities/modbus_lambda_sensor.py +++ b/custom_components/foxess_modbus/entities/modbus_lambda_sensor.py @@ -25,7 +25,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusLambdaSensorDescription(SensorEntityDescription, EntityFactory): +class ModbusLambdaSensorDescription(SensorEntityDescription, EntityFactory): # type: ignore[misc, override] """Entity description for ModbusLambdaSensors""" models: list[EntitySpec] diff --git a/custom_components/foxess_modbus/entities/modbus_number.py b/custom_components/foxess_modbus/entities/modbus_number.py index dd51c36..f1ed9c4 100644 --- a/custom_components/foxess_modbus/entities/modbus_number.py +++ b/custom_components/foxess_modbus/entities/modbus_number.py @@ -26,7 +26,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusNumberDescription(NumberEntityDescription, EntityFactory): +class ModbusNumberDescription(NumberEntityDescription, EntityFactory): # type: ignore[misc, override] """Custom number entity description""" address: list[ModbusAddressSpec] diff --git a/custom_components/foxess_modbus/entities/modbus_remote_control_number.py b/custom_components/foxess_modbus/entities/modbus_remote_control_number.py index c2c6f22..45a3f40 100644 --- a/custom_components/foxess_modbus/entities/modbus_remote_control_number.py +++ b/custom_components/foxess_modbus/entities/modbus_remote_control_number.py @@ -26,7 +26,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusRemoteControlNumberDescription(NumberEntityDescription, EntityFactory): +class ModbusRemoteControlNumberDescription(NumberEntityDescription, EntityFactory): # type: ignore[misc, override] """Custom number entity description""" models: list[EntitySpec] diff --git a/custom_components/foxess_modbus/entities/modbus_remote_control_select.py b/custom_components/foxess_modbus/entities/modbus_remote_control_select.py index a03b7af..455119c 100644 --- a/custom_components/foxess_modbus/entities/modbus_remote_control_select.py +++ b/custom_components/foxess_modbus/entities/modbus_remote_control_select.py @@ -22,7 +22,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusRemoteControlSelectDescription(SelectEntityDescription, EntityFactory): +class ModbusRemoteControlSelectDescription(SelectEntityDescription, EntityFactory): # type: ignore[misc] models: list[EntitySpec] @property diff --git a/custom_components/foxess_modbus/entities/modbus_select.py b/custom_components/foxess_modbus/entities/modbus_select.py index 285de27..6d57962 100644 --- a/custom_components/foxess_modbus/entities/modbus_select.py +++ b/custom_components/foxess_modbus/entities/modbus_select.py @@ -24,7 +24,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusSelectDescription(SelectEntityDescription, EntityFactory): +class ModbusSelectDescription(SelectEntityDescription, EntityFactory): # type: ignore[misc] """Custom select entity description""" address: list[ModbusAddressSpec] diff --git a/custom_components/foxess_modbus/entities/modbus_sensor.py b/custom_components/foxess_modbus/entities/modbus_sensor.py index 99590c4..b115232 100644 --- a/custom_components/foxess_modbus/entities/modbus_sensor.py +++ b/custom_components/foxess_modbus/entities/modbus_sensor.py @@ -31,7 +31,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusSensorDescription(SensorEntityDescription, EntityFactory): +class ModbusSensorDescription(SensorEntityDescription, EntityFactory): # type: ignore[misc, override] """Custom sensor description""" addresses: list[ModbusAddressesSpec] diff --git a/custom_components/foxess_modbus/entities/modbus_version_sensor.py b/custom_components/foxess_modbus/entities/modbus_version_sensor.py index c8fb9ef..b928695 100644 --- a/custom_components/foxess_modbus/entities/modbus_version_sensor.py +++ b/custom_components/foxess_modbus/entities/modbus_version_sensor.py @@ -18,7 +18,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusVersionSensorDescription(SensorEntityDescription, EntityFactory): +class ModbusVersionSensorDescription(SensorEntityDescription, EntityFactory): # type: ignore[misc, override] """Description for ModbusVersionSensor""" address: list[ModbusAddressSpec] diff --git a/custom_components/foxess_modbus/entities/modbus_work_mode_select.py b/custom_components/foxess_modbus/entities/modbus_work_mode_select.py index 85dc7d9..69fa1ee 100644 --- a/custom_components/foxess_modbus/entities/modbus_work_mode_select.py +++ b/custom_components/foxess_modbus/entities/modbus_work_mode_select.py @@ -15,7 +15,7 @@ @dataclass(kw_only=True, **ENTITY_DESCRIPTION_KWARGS) -class ModbusWorkModeSelectDescription(ModbusSelectDescription): +class ModbusWorkModeSelectDescription(ModbusSelectDescription): # type: ignore[misc, override] def create_entity_if_supported( self, controller: EntityController, diff --git a/pyproject.toml b/pyproject.toml index 604a87c..a28b032 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ namespace_packages = true explicit_package_bases = true [[tool.mypy.overrides]] -module = 'pymodbus.*' +module = 'serial.*' ignore_missing_imports = true [tool.ruff] diff --git a/requirements.txt b/requirements.txt index 319cd3c..bab459e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ # pip>=21.0,<23.2 -homeassistant==2025.1.0 +homeassistant==2025.1.1 # Testing -pytest-homeassistant-custom-component==0.13.201 # Matching version for 2025.1.0 +pytest-homeassistant-custom-component==0.13.202 # Matching version for 2025.1.0 psutil-home-assistant # Not sure why this is needed? fnv_hash_fast # Or this? pytest-asyncio @@ -13,8 +13,8 @@ black==23.9.0 ruff==0.0.275 # These are duplicated in .pre-commit-config.yaml reorder-python-imports==3.10.0 -mypy==1.14.1 -homeassistant-stubs==2025.1.0 # Matching HA version +mypy==1.13.0 +homeassistant-stubs==2025.1.1 # Matching HA version types-python-slugify==8.0.0.2 voluptuous-stubs==0.1.1 # For mypy. Keep in sync with manifest.json and https://github.com/home-assistant/core/blob/master/requirements_all.txt.