Skip to content
Closed
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

Versions from 0.40 and up

## v0.58.1

- Repair automatic device-removal via PR [#940](https://github.com/plugwise/plugwise-beta/pull/940)
- Improve detection of deleted devices via plugwise [v1.8.1](https://github.com/plugwise/python-plugwise/releases/tag/v1.8.1)

## v0.58.0

- Fix mypy errors in Core (not accepted in HA Core)
Expand Down
2 changes: 1 addition & 1 deletion custom_components/plugwise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -
"""Migrate back to v1.1 config entry."""
if entry.version > 1:
# This means the user has downgraded from a future version
return False
return False # pragma: no cover

if entry.version == 1 and entry.minor_version == 2:
new_data = {**entry.data}
Expand Down
2 changes: 1 addition & 1 deletion custom_components/plugwise/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def hvac_mode(self) -> HVACMode:
return HVACMode.HEAT # pragma: no cover
try:
hvac = HVACMode(mode)
except ValueError:
except ValueError: # pragma: no cover
return HVACMode.HEAT # pragma: no cover
if hvac not in self.hvac_modes:
return HVACMode.HEAT # pragma: no cover
Expand Down
59 changes: 35 additions & 24 deletions custom_components/plugwise/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.device_registry import DeviceEntry, DeviceRegistry
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from packaging.version import Version

Expand Down Expand Up @@ -73,7 +74,7 @@ def __init__(
username=self.config_entry.data[CONF_USERNAME],
websession=async_get_clientsession(hass, verify_ssl=False),
)
self._current_devices: set[str] = set()
self._current_devices: list[DeviceEntry] = []
self.new_devices: set[str] = set()
self.update_interval = update_interval

Expand Down Expand Up @@ -132,34 +133,43 @@ async def _async_update_data(self) -> dict[str, GwEntityData]:
async def _async_add_remove_devices(self, data: dict[str, GwEntityData]) -> None:
"""Add new Plugwise devices, remove non-existing devices."""
# Check for new or removed devices
self.new_devices = set(data) - self._current_devices
removed_devices = self._current_devices - set(data)
self._current_devices = set(data)

if removed_devices:
await self._async_remove_devices(data)

async def _async_remove_devices(self, data: dict[str, GwEntityData]) -> None:
"""Clean registries when removed devices found."""
device_reg = dr.async_get(self.hass)
device_list = dr.async_entries_for_config_entry(
device_reg, self.config_entry.entry_id
device_registry = dr.async_get(self.hass)
self._current_devices = dr.async_entries_for_config_entry(
device_registry, self.config_entry.entry_id
)

current_device_ids = {
identifier[1]
for device_entry in self._current_devices
for identifier in device_entry.identifiers
if identifier[0] == DOMAIN
}
data_ids = set(data)
self.new_devices = data_ids - current_device_ids
removed_ids: set[str] = current_device_ids - data_ids

if removed_ids:
await self._async_remove_devices(device_registry, removed_ids)

async def _async_remove_devices(
self,
device_registry: DeviceRegistry,
removed_ids: set[str],
) -> None:
"""Clean registries when removed devices found."""
# First find the Plugwise via_device
gateway_device = device_reg.async_get_device({(DOMAIN, self.api.gateway_id)})
if gateway_device is not None:
via_device_id = gateway_device.id
gateway_device = device_registry.async_get_device({(DOMAIN, self.api.gateway_id)})
if gateway_device is None:
LOGGER.warning("Failed to remove device, plugwise gateway reference not found") # pragma: no cover
return # pragma: no cover

# Then remove the connected orphaned device(s)
for device_entry in device_list:
via_device_id = gateway_device.id
for device_entry in self._current_devices:
if device_entry.via_device_id != via_device_id:
continue
for identifier in device_entry.identifiers:
if (
identifier[0] == DOMAIN
and device_entry.via_device_id == via_device_id
and identifier[1] not in data
):
device_reg.async_update_device(
if identifier[0] == DOMAIN and identifier[1] in removed_ids:
device_registry.async_update_device(
device_entry.id, remove_config_entry_id=self.config_entry.entry_id
)
LOGGER.debug(
Expand All @@ -168,3 +178,4 @@ async def _async_remove_devices(self, data: dict[str, GwEntityData]) -> None:
device_entry.model,
identifier[1],
)
break
4 changes: 2 additions & 2 deletions custom_components/plugwise/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["plugwise"],
"requirements": ["plugwise==1.8.0"],
"version": "0.58.0",
"requirements": ["plugwise==1.8.1"],
"version": "0.58.1",
"zeroconf": ["_plugwise._tcp.local."]
}
25 changes: 12 additions & 13 deletions tests/components/plugwise/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,20 +139,19 @@ async def test_p1_3ph_dsmr_sensor_disabled_entities(
mock_smile_p1: MagicMock,
init_integration: MockConfigEntry,
) -> None:
"""Test disabled power related sensor entities intent."""
"""Test enabling of disabled voltage sensor."""
entity_id = "sensor.p1_voltage_phase_one"
state = hass.states.get(entity_id)
assert not state

entity_registry.async_update_entity(entity_id=entity_id, disabled_by=None)
await hass.async_block_till_done()

await hass.config_entries.async_reload(init_integration.entry_id)
await hass.async_block_till_done()

state = hass.states.get("sensor.p1_voltage_phase_one")
assert state
assert float(state.state) == 233.2
entry = entity_registry.async_get(entity_id)
assert entry
assert entry.unique_id == "b82b6b3322484f2ea4e25e0bd5f3d61f-voltage_phase_one"
assert entry.disabled
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION

updated_entry = entity_registry.async_update_entity(
entity_id=entity_id, disabled_by=None
)
assert updated_entry != entry
assert updated_entry.disabled is False


@pytest.mark.parametrize("platforms", [(SENSOR_DOMAIN,)])
Expand Down
Loading