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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Versions from 0.40 and up

## v0.52.1

- Downstream the latest Core Plugwise updates.
- Document code-differences vs Core Plugwise - parts for future upstreaming and parts that will not be downstreamed.
- Code improvements.

## v0.52.0

- Add battery-state binary_sensors for battery-powered devices.
Expand Down
33 changes: 19 additions & 14 deletions custom_components/plugwise/__init__.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
"""Plugwise platform for Home Assistant Core."""

from __future__ import annotations

from typing import Any

from plugwise.exceptions import PlugwiseError
import voluptuous as vol
import voluptuous as vol # pw-beta delete_notification

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.core import (
HomeAssistant,
ServiceCall, # pw-beta delete_notification
callback,
)
from homeassistant.helpers import device_registry as dr, entity_registry as er

from .const import (
CONF_REFRESH_INTERVAL, # pw-beta options
DOMAIN,
LOGGER,
PLATFORMS,
SERVICE_DELETE,
SERVICE_DELETE, # pw-beta delete_notifications
)
from .coordinator import PlugwiseDataUpdateCoordinator

type PlugwiseConfigEntry = ConfigEntry[PlugwiseDataUpdateCoordinator]


async def async_setup_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool:
"""Set up the Plugwise Device from a config entry."""
"""Set up Plugwise from a config entry."""
await er.async_migrate_entries(hass, entry.entry_id, async_migrate_entity_entry)

cooldown = 1.5 # pw-beta frontend refresh-interval
Expand All @@ -38,10 +43,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) ->
hass, cooldown
) # pw-beta - cooldown, update_interval as extra
await coordinator.async_config_entry_first_refresh()
# Migrate a changed sensor unique_id

migrate_sensor_entities(hass, coordinator)

entry.runtime_data = coordinator # pw-beta
entry.runtime_data = coordinator

device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
Expand Down Expand Up @@ -71,9 +76,9 @@ async def delete_notification(
)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener))

for component in PLATFORMS: # pw-beta
entry.async_on_unload(entry.add_update_listener(update_listener)) # pw-beta options_flow
for component in PLATFORMS: # pw-beta delete_notification
if component == Platform.BINARY_SENSOR:
hass.services.async_register(
DOMAIN, SERVICE_DELETE, delete_notification, schema=vol.Schema({})
Expand All @@ -88,7 +93,7 @@ async def update_listener(
await hass.config_entries.async_reload(entry.entry_id)

async def async_unload_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool:
"""Unload a config entry."""
"""Unload Plugwise."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

@callback
Expand All @@ -109,12 +114,11 @@ def async_migrate_entity_entry(entry: er.RegistryEntry) -> dict[str, Any] | None
"-relative_humidity"
):
return {
"new_unique_id": entry.unique_id.replace(
"-relative_humidity", "-humidity"
)
"new_unique_id": entry.unique_id.replace("-relative_humidity", "-humidity")
}
if entry.domain == Platform.SWITCH and entry.unique_id.endswith("-plug"):
return {"new_unique_id": entry.unique_id.replace("-plug", "-relay")}

# No migration needed
return None

Expand All @@ -125,15 +129,16 @@ def migrate_sensor_entities(
"""Migrate Sensors if needed."""
ent_reg = er.async_get(hass)

# Migrate opentherm_outdoor_temperature # pw-beta add to Core
# Migrate opentherm_outdoor_temperature
# to opentherm_outdoor_air_temperature sensor
for device_id, device in coordinator.data.devices.items():
if device["dev_class"] != "heater_central": # pw-beta add to Core
if device["dev_class"] != "heater_central":
continue

old_unique_id = f"{device_id}-outdoor_temperature"
if entity_id := ent_reg.async_get_entity_id(
Platform.SENSOR, DOMAIN, old_unique_id
):
new_unique_id = f"{device_id}-outdoor_air_temperature"
# Upstream remove LOGGER debug
ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)
35 changes: 26 additions & 9 deletions custom_components/plugwise/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Plugwise Binary Sensor component for Home Assistant."""

from __future__ import annotations

from collections.abc import Mapping
Expand All @@ -12,7 +13,7 @@
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import ATTR_NAME, EntityCategory
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback

Expand All @@ -27,16 +28,18 @@
DOMAIN,
FLAME_STATE,
HEATING_STATE,
LOGGER,
LOGGER, # pw-beta
NOTIFICATIONS,
PLUGWISE_NOTIFICATION,
SECONDARY_BOILER_STATE,
SEVERITIES,
)

# Upstream
from .coordinator import PlugwiseDataUpdateCoordinator
from .entity import PlugwiseEntity

PARALLEL_UPDATES = 0
PARALLEL_UPDATES = 0 # Upstream


@dataclass(frozen=True)
Expand All @@ -46,6 +49,7 @@ class PlugwiseBinarySensorEntityDescription(BinarySensorEntityDescription):
key: BinarySensorType


# Upstream PLUGWISE_BINARY_SENSORS
PLUGWISE_BINARY_SENSORS: tuple[PlugwiseBinarySensorEntityDescription, ...] = (
PlugwiseBinarySensorEntityDescription(
key=BATTERY_STATE,
Expand Down Expand Up @@ -101,15 +105,29 @@ async def async_setup_entry(
entry: PlugwiseConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Plugwise binary_sensors from a ConfigEntry."""
"""Set up Plugwise binary_sensors from a config entry."""
coordinator = entry.runtime_data

@callback
def _add_entities() -> None:
"""Add Entities during init and runtime."""
"""Add Entities."""
if not coordinator.new_devices:
return

# Upstream consts to HA
# async_add_entities(
# PlugwiseBinarySensorEntity(coordinator, device_id, description)
# for device_id in coordinator.new_devices
# if (
# binary_sensors := coordinator.data.devices[device_id].get(
# BINARY_SENSORS
# )
# )
# for description in PLUGWISE_BINARY_SENSORS
# if description.key in binary_sensors
# )

# pw-beta alternative for debugging
entities: list[PlugwiseBinarySensorEntity] = []
for device_id in coordinator.new_devices:
device = coordinator.data.devices[device_id]
Expand All @@ -120,17 +138,16 @@ def _add_entities() -> None:
continue
entities.append(PlugwiseBinarySensorEntity(coordinator, device_id, description))
LOGGER.debug(
"Add %s %s binary sensor", device[ATTR_NAME], description.translation_key
"Add %s %s binary sensor", device["name"], description.translation_key
)

async_add_entities(entities)

_add_entities()
entry.async_on_unload(coordinator.async_add_listener(_add_entities))


class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity):
"""Represent Smile Binary Sensors."""
"""Set up Plugwise binary_sensors from a config entry."""

entity_description: PlugwiseBinarySensorEntityDescription

Expand Down Expand Up @@ -162,7 +179,7 @@ def is_on(self) -> bool:
@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return entity specific state attributes."""
if self.entity_description.key != PLUGWISE_NOTIFICATION:
if self.entity_description.key != PLUGWISE_NOTIFICATION: # Upstream const
return None

# pw-beta adjustment with attrs is to only represent severities *with* content
Expand Down
23 changes: 17 additions & 6 deletions custom_components/plugwise/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,43 @@
from __future__ import annotations

from homeassistant.components.button import ButtonDeviceClass, ButtonEntity
from homeassistant.const import ATTR_NAME, EntityCategory
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import PlugwiseConfigEntry
from .const import GATEWAY_ID, LOGGER, REBOOT
from .const import (
GATEWAY_ID,
LOGGER, # pw-betea
REBOOT,
)
from .coordinator import PlugwiseDataUpdateCoordinator
from .entity import PlugwiseEntity
from .util import plugwise_command

PARALLEL_UPDATES = 0 # Upstream


async def async_setup_entry(
hass: HomeAssistant,
entry: PlugwiseConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Plugwise buttons from a ConfigEntry."""
"""Set up Plugwise buttons from a config entry."""
coordinator = entry.runtime_data

entities: list[PlugwiseButtonEntity] = []
gateway = coordinator.data.gateway
# async_add_entities(
# PlugwiseButtonEntity(coordinator, device_id)
# for device_id in coordinator.data.devices
# if device_id == gateway[GATEWAY_ID] and REBOOT in gateway
# )
# pw-beta alternative for debugging
entities: list[PlugwiseButtonEntity] = []
for device_id, device in coordinator.data.devices.items():
if device_id == gateway[GATEWAY_ID] and REBOOT in gateway:
entities.append(PlugwiseButtonEntity(coordinator, device_id))
LOGGER.debug("Add %s reboot button", device[ATTR_NAME])

LOGGER.debug("Add %s reboot button", device["name"])
async_add_entities(entities)


Expand Down
25 changes: 10 additions & 15 deletions custom_components/plugwise/climate.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Plugwise Climate component for Home Assistant."""

from __future__ import annotations

from typing import Any
Expand Down Expand Up @@ -58,13 +59,15 @@
from .entity import PlugwiseEntity
from .util import plugwise_command

PARALLEL_UPDATES = 0 # Upstream


async def async_setup_entry(
hass: HomeAssistant,
entry: PlugwiseConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Plugwise thermostats from a ConfigEntry."""
"""Set up Plugwise thermostats from a config entry."""
coordinator = entry.runtime_data
homekit_enabled: bool = entry.options.get(
CONF_HOMEKIT_EMULATION, False
Expand Down Expand Up @@ -102,8 +105,8 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
_attr_translation_key = DOMAIN
_enable_turn_on_off_backwards_compatibility = False

_homekit_mode: str | None = None # pw-beta homekit emulation
_previous_mode: str = HVACAction.HEATING
_previous_mode: str = HVACAction.HEATING # Upstream
_homekit_mode: str | None = None # pw-beta homekit emulation + intentional unsort

def __init__(
self,
Expand Down Expand Up @@ -234,13 +237,9 @@ def hvac_action(self) -> HVACAction: # pw-beta add to Core
self._previous_action_mode(self.coordinator)

# Adam provides the hvac_action for each thermostat
if (control_state := self.device.get(CONTROL_STATE)) == HVACAction.COOLING:
return HVACAction.COOLING
if control_state == HVACAction.HEATING:
return HVACAction.HEATING
if control_state == HVACAction.PREHEATING:
return HVACAction.PREHEATING
if control_state == STATE_OFF:
if (control_state := self.device.get(CONTROL_STATE)) in (HVACAction.COOLING, HVACAction.HEATING, HVACAction.PREHEATING):
return control_state
if control_state == HVACMode.OFF:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work with mypy?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The strange thing is some .get constructs do work in Core but not in beta ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant the control_state - str vs HVACMode.Off - StrEnum.

return HVACAction.IDLE

# Anna
Expand Down Expand Up @@ -269,11 +268,7 @@ async def async_set_temperature(self, **kwargs: Any) -> None:
if ATTR_TARGET_TEMP_LOW in kwargs:
data[TARGET_TEMP_LOW] = kwargs.get(ATTR_TARGET_TEMP_LOW)

for temperature in data.values():
if temperature is None or not (
self._attr_min_temp <= temperature <= self._attr_max_temp
):
raise ValueError("Invalid temperature change requested")
# Upstream removed input-valid check

if mode := kwargs.get(ATTR_HVAC_MODE):
await self.async_set_hvac_mode(mode)
Expand Down
9 changes: 9 additions & 0 deletions custom_components/plugwise/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Config flow for Plugwise integration."""

from __future__ import annotations

import datetime as dt # pw-beta options
Expand All @@ -24,6 +25,8 @@
OptionsFlow,
OptionsFlowWithConfigEntry,
)

# Upstream
from homeassistant.const import (
ATTR_CONFIGURATION_URL,
CONF_BASE,
Expand All @@ -34,6 +37,8 @@
CONF_SCAN_INTERVAL,
CONF_USERNAME,
)

# Upstream
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
Expand Down Expand Up @@ -62,10 +67,14 @@
VERSION,
ZEROCONF_MAP,
)

# Upstream
from .coordinator import PlugwiseDataUpdateCoordinator

type PlugwiseConfigEntry = ConfigEntry[PlugwiseDataUpdateCoordinator]

# Upstream basically the whole file (excluding the pw-beta options)


def _base_schema(
discovery_info: ZeroconfServiceInfo | None,
Expand Down
Loading