Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split prepare (after fork) #384

Merged
merged 20 commits into from
Apr 22, 2023
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Versions from 0.4x

### 0.41x

- More formal Split between Networked and USB code
- Refactoring where necessary

## Versions from 0.30 and up

### NEW APR 2023 [0.34.10] Implement latest Core PR's
Expand Down
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Plugwise custom_component (BETA)
# Plugwise Smile/Stretch custom_component (BETA)

:no_entry::no_entry::no_entry: If you are **not** using USB based Plugwise equipment or have no intention to beta-test our integration, please defer to the **supported** release of this integration **natively** available in [Home Assistant](https://www.home-assistant.io/integrations/plugwise/)! :no_entry::no_entry::no_entry:
:warning::no_entry::warning: Do **not** use this custom_component for **USB**, this functionality was moved to [plugwise_usb](https://github.com/plugwise/plugwise_usb-beta), see [why](#usb-notes) :warning::no_entry::warning:

:no_entry::no_entry::no_entry: If you have **no** intention to beta-test our integration, please defer to the **supported** release of this integration **natively** available in [Home Assistant](https://www.home-assistant.io/integrations/plugwise/)! :no_entry::no_entry::no_entry:

:warning::warning::warning: Always **read** the [release notes](https://github.com/plugwise/plugwise-beta/releases) before upgrading, in case there are BREAKING changes! **Do note** the release title on alpha releases and only install them if specifically instructed by our team! :warning::warning::warning:

Expand Down Expand Up @@ -52,7 +54,14 @@ Our [Changelog](CHANGELOG.MD) is available as a [separate file](CHANGELOG.md) in
- Power-related
- Smile P1 (firmware 2.x, 3.x and 4.x)
- Stretch (firmware 2.x and 3.x, legacy Circle's and Stealth's)
- Stick (legacy Circle's, Stealth's and Scan's)

Additional to the **Core** component we support Homekit emulation, notifications and changing some timing. This will not be upstreamed and is code that remained in our codebase (i.e. denied upstreaming by the Core Team for acceptable reasons though we have some people already using that (mostly by them requested) functionality).

#### USB notes

Up to spring of 2023 this `custom_component` supported both Networked and USB Plugwise products, as of that time we have split both the backend (python module) and the frontend into separate instances as per recent discussion with the Core team. The `plugwise` integration in HA Core (and therefore `plugwise-beta`) will remain supporting networked Plugwise products under an envisioned `plugwise_bv` Brand umbrella. This paves the way for the upcoming `plugwise_usb-beta` `custom_integration` to refactor and again upstream to HA Core (which was originally planned but there was no branding umbrella in Core back then).

As such we ask USB users, who are tied in with the `custom_component` as there is no Core integration available yet, for a little patience so we can split and refactor all repositories without loss of functionality for the end users. For our USB users that will however mean some **breaking changes** or customizing under the hood as the `custom_component` name will change and the appropriate naming in HA will do so accordingly. It is for the best though as this will ensure a way forward to HA Core integration, which has been our goal since starting to write Plugwise supporting code for Home Assistant.

### What can I expect in HA Core from this component

Expand All @@ -79,21 +88,15 @@ For each Plugwise Smile (i.e. gateway) you will have to add it as an integration
- [ ] Click the `Configure` button and enter the Smile ID
- [ ] Click Add to see the magic happens

If there is no discovered Smile present or you are using the USB stick:
If there is no discovered Smile present:

- [ ] Hit the `+` button in the right lower corner
- [ ] Search or browse for 'Plugwise beta' and click it
- [ ] Select the type of integration: Network or USB

- For the Network-selection:
- [ ] Enter your Smile IP-address and the 8 character ID of the smile
- [ ] Click SUBMIT and FINISH and hopefully the magic happens
- [ ] Repeat this process to add more Smiles

- For the USB-selection:
- [ ] Select or enter the USB-path
- [ ] Click SUBMIT and FINISH

The config flow will then continue to ask you if you want to put your Smile and detected other devices in area's and presto, things should be available to configure in lovelace.

#### Options
Expand Down Expand Up @@ -139,8 +142,10 @@ As things like async were in high demand from HA Core, desired by the original a

With the three combined forces we now support, maintain and improve on:

- `plugwise-beta` (this repository) for beta-testing new features to go into the `plugwise`-integration for HA
- [`python-plugwise`](https://github.com/plugwise/python-plugwise) for connecting to Plugwise products
- [`progress`](https://github.com/plugwise/progress) showing what are the differences between HA-core and this `custom_component` on [our progress page](https://plugwise.github.io/progress/)
- `plugwise-beta` (this repository) for beta-testing new features to go into the Core `plugwise`-integration for HA
- [`python-plugwise`](https://github.com/plugwise/python-plugwise-usb) for connecting to Networked Plugwise products
- `plugwise_usb-beta` (the USB repository) for beta-testing new features to eventually go upstream to Core into the `plugwise_usb`-integration for HA
- [`python-plugwise-usb`](https://github.com/plugwise/python-plugwise-usb) for connecting to Plugwise products via USB
- [`progress`](https://github.com/plugwise/progress) showing what are the differences between HA-core and the network `custom_component` on [our progress page](https://plugwise.github.io/progress/) (marked as todo for USB as well)

And yes anna-ha with haanna (to some degree) support Anna v1.8 - but they don't support Adam nor the Smile P1.
149 changes: 129 additions & 20 deletions custom_components/plugwise/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,136 @@
"""Plugwise platform for Home Assistant Core."""
from __future__ import annotations

from typing import Any

import voluptuous as vol

from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
from plugwise.exceptions import PlugwiseError

from .const import CONF_USB_PATH # pw-beta usb
from .gateway import async_setup_entry_gw, async_unload_entry_gw
from .usb import async_setup_entry_usb, async_unload_entry_usb # pw-beta usb
from .const import (
COORDINATOR,
DOMAIN,
LOGGER,
PLATFORMS_GATEWAY,
SERVICE_DELETE,
UNDO_UPDATE_LISTENER,
)
from .const import CONF_REFRESH_INTERVAL # pw-beta options
from .coordinator import PlugwiseDataUpdateCoordinator


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Plugwise components from a config entry."""
if entry.data.get(CONF_HOST):
return await async_setup_entry_gw(hass, entry)
if entry.data.get(CONF_USB_PATH): # pw-beta usb
return await async_setup_entry_usb(hass, entry) # pw-beta usb
return False # pragma: no cover


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload the Plugwise components."""
if entry.data.get(CONF_HOST):
return await async_unload_entry_gw(hass, entry)
if entry.data.get(CONF_USB_PATH): # pw-beta usb
return await async_unload_entry_usb(hass, entry) # pw-beta usb
return False # pragma: no cover
"""Set up Plugwise Smiles 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
if (
custom_refresh := entry.options.get(CONF_REFRESH_INTERVAL)
) is not None: # pragma: no cover
cooldown = custom_refresh
LOGGER.debug("DUC cooldown interval: %s", cooldown)

coordinator = PlugwiseDataUpdateCoordinator(
hass, entry, 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)

undo_listener = entry.add_update_listener(_update_listener) # pw-beta

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
COORDINATOR: coordinator, # pw-beta
UNDO_UPDATE_LISTENER: undo_listener, # pw-beta
}

device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, str(coordinator.api.gateway_id))},
manufacturer="Plugwise",
model=coordinator.api.smile_model,
name=coordinator.api.smile_name,
sw_version=coordinator.api.smile_version[0],
)

async def delete_notification(
self,
): # pragma: no cover # pw-beta: HA service - delete_notification
"""Service: delete the Plugwise Notification."""
LOGGER.debug(
"Service delete PW Notification called for %s", coordinator.api.smile_name
)
try:
deleted = await coordinator.api.delete_notification()
LOGGER.debug("PW Notification deleted: %s", deleted)
except PlugwiseError:
LOGGER.debug(
"Failed to delete the Plugwise Notification for %s",
coordinator.api.smile_name,
)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS_GATEWAY)

for component in PLATFORMS_GATEWAY: # pw-beta
if component == Platform.BINARY_SENSOR:
hass.services.async_register(
DOMAIN, SERVICE_DELETE, delete_notification, schema=vol.Schema({})
)

return True


async def _update_listener(
hass: HomeAssistant, entry: ConfigEntry
): # pragma: no cover # pw-beta
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(
entry, PLATFORMS_GATEWAY
):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok


@callback
def async_migrate_entity_entry(entry: er.RegistryEntry) -> dict[str, Any] | None:
"""Migrate Plugwise entity entries.

- Migrates unique ID from old relay switches to the new unique ID
"""
if entry.domain == SWITCH_DOMAIN and entry.unique_id.endswith("-plug"):
return {"new_unique_id": entry.unique_id.replace("-plug", "-relay")}

# No migration needed
return None


def migrate_sensor_entities(
hass: HomeAssistant,
coordinator: PlugwiseDataUpdateCoordinator,
) -> None:
"""Migrate Sensors if needed."""
ent_reg = er.async_get(hass)

# Migrate opentherm_outdoor_temperature # pw-beta add to Core
# 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
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"
ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)
Loading