diff --git a/CHANGELOG.md b/CHANGELOG.md index 274c282e1..e41f77be0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Versions from 0.40 and up +## Ongoing + +- Fix mypy errors in Core + ## v0.57.6 - Fix issue #897, partly via plugwise [v1.7.8](https://github.com/plugwise/python-plugwise/releases/tag/v1.7.8) diff --git a/custom_components/plugwise/binary_sensor.py b/custom_components/plugwise/binary_sensor.py index dad3d8dc2..fb7430118 100644 --- a/custom_components/plugwise/binary_sensor.py +++ b/custom_components/plugwise/binary_sensor.py @@ -165,7 +165,7 @@ def __init__( self._notification: dict[str, str] = {} # pw-beta @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" # pw-beta: show Plugwise notifications as HA persistent notifications if self._notification: @@ -174,7 +174,7 @@ def is_on(self) -> bool: self.hass, message, "Plugwise Notification:", f"{DOMAIN}.{notify_id}" ) - return self.device[BINARY_SENSORS][self.entity_description.key] + return self.device.get(BINARY_SENSORS, {}).get(self.entity_description.key) @property def extra_state_attributes(self) -> Mapping[str, Any] | None: diff --git a/custom_components/plugwise/climate.py b/custom_components/plugwise/climate.py index c12255073..aa008f66a 100644 --- a/custom_components/plugwise/climate.py +++ b/custom_components/plugwise/climate.py @@ -23,7 +23,6 @@ UnitOfTemperature, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import ( @@ -108,7 +107,7 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): _enable_turn_on_off_backwards_compatibility = False _previous_mode: str = HVACAction.HEATING # Upstream - _homekit_mode: str | None = None # pw-beta homekit emulation + intentional unsort + _homekit_mode: HVACMode | None = None # pw-beta homekit emulation + intentional unsort def __init__( self, @@ -127,11 +126,11 @@ def __init__( if (location := self.device.get(LOCATION)) is not None: self._location = location - self._attr_max_temp = min(self.device[THERMOSTAT][UPPER_BOUND], 35.0) - self._attr_min_temp = self.device[THERMOSTAT][LOWER_BOUND] + self._attr_max_temp = min(self.device.get(THERMOSTAT, {}).get(UPPER_BOUND, 35.0), 35.0) + self._attr_min_temp = self.device.get(THERMOSTAT, {}).get(LOWER_BOUND, 0.0) # Ensure we don't drop below 0.1 self._attr_target_temperature_step = max( - self.device[THERMOSTAT][RESOLUTION], 0.1 + self.device.get(THERMOSTAT, {}).get(RESOLUTION, 0.5), 0.1 ) self._attr_unique_id = f"{device_id}-climate" @@ -148,7 +147,7 @@ def __init__( self._attr_supported_features |= ( ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON ) - if presets := self.device["preset_modes"]: # can be NONE + if presets := self.device.get("preset_modes", None): # can be NONE self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE self._attr_preset_modes = presets @@ -167,47 +166,51 @@ def _previous_action_mode(self, coordinator: PlugwiseDataUpdateCoordinator) -> N self._previous_mode = mode @property - def current_temperature(self) -> float: + def current_temperature(self) -> float | None: """Return the current temperature.""" - return self.device[SENSORS][ATTR_TEMPERATURE] + return self.device.get(SENSORS, {}).get(ATTR_TEMPERATURE) @property - def target_temperature(self) -> float: + def target_temperature(self) -> float | None: """Return the temperature we try to reach. Connected to the HVACMode combination of AUTO-HEAT. """ - return self.device[THERMOSTAT][TARGET_TEMP] + return self.device.get(THERMOSTAT, {}).get(TARGET_TEMP) @property - def target_temperature_high(self) -> float: + def target_temperature_high(self) -> float | None: """Return the temperature we try to reach in case of cooling. Connected to the HVACMode combination of AUTO-HEAT_COOL. """ - return self.device[THERMOSTAT][TARGET_TEMP_HIGH] + return self.device.get(THERMOSTAT, {}).get(TARGET_TEMP_HIGH) @property - def target_temperature_low(self) -> float: + def target_temperature_low(self) -> float | None: """Return the heating temperature we try to reach in case of heating. Connected to the HVACMode combination AUTO-HEAT_COOL. """ - return self.device[THERMOSTAT][TARGET_TEMP_LOW] + return self.device.get(THERMOSTAT, {}).get(TARGET_TEMP_LOW) @property def hvac_mode(self) -> HVACMode: """Return HVAC operation ie. auto, cool, heat, heat_cool, or off mode.""" - if ( - mode := self.device[CLIMATE_MODE] - ) is None or mode not in self.hvac_modes: # pw-beta add to Core + mode = self.device.get(CLIMATE_MODE) + if mode is None: + return HVACMode.HEAT # pragma: no cover + try: + hvac = HVACMode(mode) + except ValueError: + return HVACMode.HEAT # pragma: no cover + if hvac not in self.hvac_modes: return HVACMode.HEAT # pragma: no cover # pw-beta homekit emulation if self._homekit_enabled and self._homekit_mode == HVACMode.OFF: - mode = HVACMode.OFF # pragma: no cover - - return HVACMode(mode) + return HVACMode.OFF # pragma: no cover + return hvac @property def hvac_modes(self) -> list[HVACMode]: @@ -219,14 +222,15 @@ def hvac_modes(self) -> list[HVACMode]: ): hvac_modes.append(HVACMode.OFF) - if self.device.get(AVAILABLE_SCHEDULES): + if self.device.get(AVAILABLE_SCHEDULES, []): hvac_modes.append(HVACMode.AUTO) if self.coordinator.api.cooling_present: if REGULATION_MODES in self._gateway_data: - if self._gateway_data[SELECT_REGULATION_MODE] == HVACAction.COOLING: + selected = self._gateway_data.get(SELECT_REGULATION_MODE) + if selected == HVACAction.COOLING: hvac_modes.append(HVACMode.COOL) - if self._gateway_data[SELECT_REGULATION_MODE] == HVACAction.HEATING: + if selected == HVACAction.HEATING: hvac_modes.append(HVACMode.HEAT) else: hvac_modes.append(HVACMode.HEAT_COOL) @@ -249,7 +253,7 @@ def hvac_action(self) -> HVACAction: # pw-beta add to Core @property def preset_mode(self) -> str | None: """Return the current preset mode.""" - return self.device[ACTIVE_PRESET] + return self.device.get(ACTIVE_PRESET) @plugwise_command async def async_set_temperature(self, **kwargs: Any) -> None: @@ -272,9 +276,6 @@ async def async_set_temperature(self, **kwargs: Any) -> None: @plugwise_command async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set the hvac mode.""" - if hvac_mode not in self.hvac_modes: - raise HomeAssistantError("Unsupported hvac_mode") - if hvac_mode == self.hvac_mode: return @@ -297,7 +298,7 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: await self.async_set_preset_mode(PRESET_AWAY) # pragma: no cover if ( self._homekit_mode in [HVACMode.HEAT, HVACMode.HEAT_COOL] - and self.device[ACTIVE_PRESET] == PRESET_AWAY + and self.device.get(ACTIVE_PRESET) == PRESET_AWAY ): # pragma: no cover await self.async_set_preset_mode(PRESET_HOME) # pragma: no cover diff --git a/custom_components/plugwise/entity.py b/custom_components/plugwise/entity.py index 17705b13a..c0a52b062 100644 --- a/custom_components/plugwise/entity.py +++ b/custom_components/plugwise/entity.py @@ -83,7 +83,7 @@ def available(self) -> bool: # Upstream: Do not change the AVAILABLE line below: some Plugwise devices and zones # Upstream: do not provide their availability-status! self._dev_id in self.coordinator.data - and (AVAILABLE not in self.device or self.device[AVAILABLE] is True) + and (self.device.get(AVAILABLE, True) is True) and super().available ) diff --git a/custom_components/plugwise/number.py b/custom_components/plugwise/number.py index a9637de22..0576cbd3e 100644 --- a/custom_components/plugwise/number.py +++ b/custom_components/plugwise/number.py @@ -121,23 +121,23 @@ def __init__( ) -> None: """Initiate Plugwise Number.""" super().__init__(coordinator, device_id) - self.actuator = self.device[description.key] # Upstream self.device_id = device_id self.entity_description = description self._attr_unique_id = f"{device_id}-{description.key}" self._attr_mode = NumberMode.BOX - self._attr_native_max_value = self.device[description.key][UPPER_BOUND] # Upstream const - self._attr_native_min_value = self.device[description.key][LOWER_BOUND] # Upstream const + ctrl = self.device.get(description.key, {}) + self._attr_native_max_value = ctrl.get(UPPER_BOUND, 100.0) # Upstream const + self._attr_native_min_value = ctrl.get(LOWER_BOUND, 0.0) # Upstream const - native_step = self.device[description.key][RESOLUTION] # Upstream const + native_step = ctrl.get(RESOLUTION, 0.5) # Upstream const if description.key != TEMPERATURE_OFFSET: # Upstream const native_step = max(native_step, 0.5) self._attr_native_step = native_step @property - def native_value(self) -> float: + def native_value(self) -> float | None: """Return the present setpoint value.""" - return self.device[self.entity_description.key]["setpoint"] + return self.device.get(self.entity_description.key, {}).get("setpoint") @plugwise_command async def async_set_native_value(self, value: float) -> None: diff --git a/custom_components/plugwise/select.py b/custom_components/plugwise/select.py index 13fb13457..5a195ee5e 100644 --- a/custom_components/plugwise/select.py +++ b/custom_components/plugwise/select.py @@ -132,14 +132,14 @@ def __init__( self._location = location @property - def current_option(self) -> str| None: + def current_option(self) -> str | None: """Return the selected entity option to represent the entity state.""" - return self.device[self.entity_description.key] + return self.device.get(self.entity_description.key) @property def options(self) -> list[str]: """Return the available select-options.""" - return self.device[self.entity_description.options_key] + return self.device.get(self.entity_description.options_key, []) @plugwise_command async def async_select_option(self, option: str) -> None: diff --git a/custom_components/plugwise/sensor.py b/custom_components/plugwise/sensor.py index d071d584a..a3d116eaa 100644 --- a/custom_components/plugwise/sensor.py +++ b/custom_components/plugwise/sensor.py @@ -516,6 +516,6 @@ def __init__( self._attr_unique_id = f"{device_id}-{description.key}" @property - def native_value(self) -> int | float: + def native_value(self) -> int | float | None: """Return the value reported by the sensor.""" - return self.device[SENSORS][self.entity_description.key] # Upstream consts + return self.device.get(SENSORS, {}).get(self.entity_description.key) # Upstream consts diff --git a/custom_components/plugwise/switch.py b/custom_components/plugwise/switch.py index 1d330c02d..af67e7c4e 100644 --- a/custom_components/plugwise/switch.py +++ b/custom_components/plugwise/switch.py @@ -127,9 +127,9 @@ def __init__( self._attr_unique_id = f"{device_id}-{description.key}" @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return True if entity is on.""" - return self.device[SWITCHES][self.entity_description.key] # Upstream const + return self.device.get(SWITCHES, {}).get(self.entity_description.key) # Upstream const @plugwise_command async def async_turn_on(self, **kwargs: Any) -> None: