Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
dbfabf3
Set timeout based on discovered version
bouwew Sep 26, 2024
53133c1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 26, 2024
ee84683
Fix typo
bouwew Sep 26, 2024
2d333fd
Add testing
bouwew Sep 26, 2024
4bd7909
Correct parameter use
bouwew Sep 26, 2024
765cbec
Improve
bouwew Sep 26, 2024
2b9b9be
Fix typo
bouwew Sep 26, 2024
87a34ca
Implement suggestion
bouwew Sep 26, 2024
4b4ccde
Correct argument
bouwew Sep 26, 2024
3dcbf49
Improve testing
bouwew Sep 26, 2024
a9ff7f7
Force a fail
bouwew Sep 26, 2024
265ef25
Debug
bouwew Sep 26, 2024
997b230
Improve
bouwew Sep 26, 2024
18013f6
Correct order
bouwew Sep 26, 2024
022e240
Improve config_flow.py
bouwew Sep 26, 2024
0ab8629
More debug
bouwew Sep 26, 2024
a4a5063
Fix
bouwew Sep 26, 2024
0d6c052
More debug
bouwew Sep 26, 2024
ed6c083
Try
bouwew Sep 26, 2024
15d998d
Try 2
bouwew Sep 26, 2024
45d11fb
Add missing init
bouwew Sep 26, 2024
cc74e4a
Fix assert
bouwew Sep 26, 2024
5f4cd0b
Correct fail
bouwew Sep 26, 2024
15eaad7
One more fix
bouwew Sep 26, 2024
cdd25d5
Remove debugging
bouwew Sep 26, 2024
fd62502
Update CHANGELOG
bouwew Sep 26, 2024
2e8801c
Simplify
bouwew Sep 26, 2024
75a5722
Remove double assert
bouwew Sep 26, 2024
eb69283
Pass the timeout-value to the DUC
bouwew Sep 26, 2024
9ec4a09
Add to TITLE_PLACEHOLDER
bouwew Sep 26, 2024
b023643
Debug
bouwew Sep 27, 2024
13d2dda
Update debug
bouwew Sep 27, 2024
3a5abe6
Add CONF_TIMEOUT to mock_config_entry
bouwew Sep 27, 2024
b4c5d20
Set config_entry version to 2
bouwew Sep 27, 2024
c294815
Add entry-migration
bouwew Sep 27, 2024
b5f3940
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 27, 2024
bb0cff0
Set version to 2 in mock_config_entry
bouwew Sep 27, 2024
06e4ce5
Fix function-call arguments
bouwew Sep 27, 2024
709b4fe
Fix typo
bouwew Sep 27, 2024
f78a8c0
Use packaging.version for version-compare
bouwew Sep 27, 2024
e581564
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 27, 2024
603ba38
Add entry-migration test
bouwew Sep 27, 2024
d264fad
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 27, 2024
ec8e85c
Fix imports
bouwew Sep 27, 2024
02bc805
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 27, 2024
0859068
Create empty test_init.ambr snapshot-file
bouwew Sep 27, 2024
43b554c
Update test_options_flow_thermo()
bouwew Sep 27, 2024
f5c5163
Fix/revert
bouwew Sep 27, 2024
cdf8dc7
Save updated test_init.ambr file
bouwew Sep 27, 2024
7c0d09d
Correct expected version nr in test_init.ambr
bouwew Sep 27, 2024
a0bc14a
Correct mock_config_entry
bouwew Sep 27, 2024
b136a30
Add debugging
bouwew Sep 27, 2024
fc10927
More corrections
bouwew Sep 27, 2024
864542f
Correct
bouwew Sep 27, 2024
46fa157
Change to minor_version upgrade
bouwew Sep 27, 2024
46753ea
Try
bouwew Sep 27, 2024
e77b729
Try 2
bouwew Sep 27, 2024
62f32b4
Try 3
bouwew Sep 27, 2024
cfa3120
Try 4
bouwew Sep 27, 2024
9c11317
Fix
bouwew Sep 27, 2024
ac8c704
Fix 2
bouwew Sep 27, 2024
b4b5c51
Fix 3
bouwew Sep 27, 2024
2ee8d44
Fix 4
bouwew Sep 27, 2024
73463c9
Try 5
bouwew Sep 27, 2024
c85a6dc
Try 6
bouwew Sep 27, 2024
93bcfd5
Update snapshot
bouwew Sep 27, 2024
4661218
Remove debugging
bouwew Sep 27, 2024
5410667
Add api_version to config_entry.data and use
bouwew Sep 27, 2024
d404f91
Change to version, improve
bouwew Sep 27, 2024
490d5bc
Improve
bouwew Sep 27, 2024
d6aeb48
Improve 2
bouwew Sep 27, 2024
a87c613
Save updated test-data-file
bouwew Sep 28, 2024
902133c
Improve 3
bouwew Sep 28, 2024
e748ad4
Try 7
bouwew Sep 28, 2024
ee9e023
Save updated test_init.ambr file
bouwew Sep 28, 2024
46f83d9
Add log.debug message
bouwew Sep 28, 2024
201e8d7
Remove debug-logging
bouwew Sep 28, 2024
8207413
Bump to v0.53.3 release-version
bouwew Sep 28, 2024
82aac91
Update CHANGELOG
bouwew Sep 28, 2024
37eef2f
Optimize - remove storing of version
bouwew Sep 28, 2024
6275994
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 28, 2024
33b6c18
async improvements
bouwew Sep 28, 2024
b48a673
Improve doc-string, add comment as suggested
bouwew Sep 28, 2024
f294db6
Cleanup
bouwew Sep 28, 2024
b24abcb
Correct logger.debug placement
bouwew Sep 29, 2024
38a9b1d
Update entry strings, warn about possible long processing time
bouwew Sep 29, 2024
72080ef
Improve strings
bouwew Sep 29, 2024
77b115c
Implement suggestion
bouwew Sep 29, 2024
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

## Versions from 0.40 and up

## Ongoing
## v0.53.3

- Test-code improvements.
- Fix errors showing after Core 2023.9.3 release.
- Set the connection-timeout based on the device discovered, 10s for actual devices, legacy devices require a 30s timeout.

## v0.53.2

Expand Down
32 changes: 29 additions & 3 deletions custom_components/plugwise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import voluptuous as vol # pw-beta delete_notification

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.const import CONF_TIMEOUT, Platform
from homeassistant.core import (
HomeAssistant,
ServiceCall, # pw-beta delete_notification
Expand All @@ -24,6 +24,7 @@
SERVICE_DELETE, # pw-beta delete_notifications
)
from .coordinator import PlugwiseDataUpdateCoordinator
from .util import get_timeout_for_version

type PlugwiseConfigEntry = ConfigEntry[PlugwiseDataUpdateCoordinator]

Expand All @@ -44,7 +45,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) ->
) # pw-beta - cooldown, update_interval as extra
await coordinator.async_config_entry_first_refresh()

migrate_sensor_entities(hass, coordinator)
await async_migrate_sensor_entities(hass, coordinator)
await async_migrate_plugwise_entry(hass, coordinator, entry)

entry.runtime_data = coordinator

Expand Down Expand Up @@ -123,7 +125,7 @@ def async_migrate_entity_entry(entry: er.RegistryEntry) -> dict[str, Any] | None
# No migration needed
return None

def migrate_sensor_entities(
async def async_migrate_sensor_entities(
hass: HomeAssistant,
coordinator: PlugwiseDataUpdateCoordinator,
) -> None:
Expand All @@ -143,3 +145,27 @@ def migrate_sensor_entities(
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)

async def async_migrate_plugwise_entry(
hass: HomeAssistant,
coordinator: PlugwiseDataUpdateCoordinator,
entry: ConfigEntry
) -> bool:
"""Migrate to new config entry."""
if entry.version > 1:
return False

if entry.version == 1 and entry.minor_version < 2:
new_data = {**entry.data}
new_data[CONF_TIMEOUT] = get_timeout_for_version(coordinator.api.smile_version)
hass.config_entries.async_update_entry(
entry, data=new_data, minor_version=2, version=1
)
LOGGER.debug(
"Migration to version %s.%s successful",
entry.version,
entry.minor_version,
)
return True

return False
19 changes: 13 additions & 6 deletions custom_components/plugwise/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
CONF_PASSWORD,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_TIMEOUT,
CONF_USERNAME,
)

Expand All @@ -50,6 +51,7 @@
CONTEXT,
DEFAULT_PORT,
DEFAULT_SCAN_INTERVAL, # pw-beta option
DEFAULT_TIMEOUT,
DEFAULT_USERNAME,
DOMAIN,
FLOW_ID,
Expand All @@ -70,6 +72,7 @@

# Upstream
from .coordinator import PlugwiseDataUpdateCoordinator
from .util import get_timeout_for_version

type PlugwiseConfigEntry = ConfigEntry[PlugwiseDataUpdateCoordinator]

Expand Down Expand Up @@ -103,7 +106,6 @@ def _base_schema(
),
}
)

return vol.Schema({vol.Required(CONF_PASSWORD): str})


Expand All @@ -118,7 +120,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> Smile:
password=data[CONF_PASSWORD],
port=data[CONF_PORT],
username=data[CONF_USERNAME],
timeout=30,
timeout=data[CONF_TIMEOUT],
websession=websession,
)
await api.connect()
Expand All @@ -129,9 +131,11 @@ class PlugwiseConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Plugwise Smile."""

VERSION = 1
MINOR_VERSION = 2

discovery_info: ZeroconfServiceInfo | None = None
_username: str = DEFAULT_USERNAME
_timeout: int = DEFAULT_TIMEOUT

async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
Expand All @@ -147,9 +151,10 @@ async def async_step_zeroconf(
self.hass,
{
CONF_HOST: discovery_info.host,
CONF_PASSWORD: config_entry.data[CONF_PASSWORD],
CONF_PORT: discovery_info.port,
CONF_USERNAME: config_entry.data[CONF_USERNAME],
CONF_PASSWORD: config_entry.data[CONF_PASSWORD],
CONF_TIMEOUT: self._timeout,
},
)
except Exception: # noqa: BLE001
Expand All @@ -168,6 +173,8 @@ async def async_step_zeroconf(
_version = _properties.get(VERSION, "n/a")
_name = f"{ZEROCONF_MAP.get(_product, _product)} v{_version}"

self._timeout = get_timeout_for_version(_version)

# This is an Anna, but we already have config entries.
# Assuming that the user has already configured Adam, aborting discovery.
if self._async_current_entries() and _product == SMILE_THERMO:
Expand Down Expand Up @@ -200,6 +207,7 @@ async def async_step_zeroconf(
CONF_HOST: discovery_info.host,
CONF_NAME: _name,
CONF_PORT: discovery_info.port,
CONF_TIMEOUT: self._timeout,
CONF_USERNAME: self._username,
},
ATTR_CONFIGURATION_URL: (
Expand Down Expand Up @@ -227,6 +235,8 @@ async def async_step_user(
user_input[CONF_HOST] = self.discovery_info.host
user_input[CONF_PORT] = self.discovery_info.port
user_input[CONF_USERNAME] = self._username

user_input[CONF_TIMEOUT] = self._timeout
try:
api = await validate_input(self.hass, user_input)
except ConnectionFailedError:
Expand All @@ -253,7 +263,6 @@ async def async_step_user(
api.smile_hostname or api.gateway_id, raise_on_progress=False
)
self._abort_if_unique_id_configured()

return self.async_create_entry(title=api.smile_name, data=user_input)

@staticmethod
Expand All @@ -278,7 +287,6 @@ async def async_step_none(
if user_input is not None:
# Apparently not possible to abort an options flow at the moment
return self.async_create_entry(title="", data=self._options)

return self.async_show_form(step_id="none")

async def async_step_init(
Expand Down Expand Up @@ -322,5 +330,4 @@ async def async_step_init(
): vol.All(vol.Coerce(float), vol.Range(min=1.5, max=10.0)),
}
) # pw-beta

return self.async_show_form(step_id=INIT, data_schema=vol.Schema(data))
2 changes: 1 addition & 1 deletion custom_components/plugwise/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@
"stretch": timedelta(seconds=60),
"thermostat": timedelta(seconds=60),
}
DEFAULT_TIMEOUT: Final[int] = 10
DEFAULT_TIMEOUT: Final[int] = 30
DEFAULT_USERNAME: Final = "smile"

# --- Const for Plugwise Smile and Stretch
Expand Down
12 changes: 7 additions & 5 deletions custom_components/plugwise/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
CONF_PASSWORD,
CONF_PORT,
CONF_SCAN_INTERVAL, # pw-beta options
CONF_TIMEOUT,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
Expand All @@ -30,6 +31,7 @@
from .const import (
DEFAULT_PORT,
DEFAULT_SCAN_INTERVAL,
DEFAULT_TIMEOUT,
DEFAULT_USERNAME,
DOMAIN,
GATEWAY_ID,
Expand Down Expand Up @@ -73,7 +75,7 @@ def __init__(
username=self.config_entry.data.get(CONF_USERNAME, DEFAULT_USERNAME),
password=self.config_entry.data[CONF_PASSWORD],
port=self.config_entry.data.get(CONF_PORT, DEFAULT_PORT),
timeout=30,
timeout=self.config_entry.data.get(CONF_TIMEOUT, DEFAULT_TIMEOUT),
websession=async_get_clientsession(hass, verify_ssl=False),
)
self._current_devices: set[str] = set()
Expand Down Expand Up @@ -115,21 +117,21 @@ async def _async_update_data(self) -> PlugwiseData:
raise ConfigEntryError("Device with unsupported firmware") from err
else:
LOGGER.debug(f"{self.api.smile_name} data: %s", data)
self._async_add_remove_devices(data, self.config_entry)
await self.async_add_remove_devices(data, self.config_entry)

return data

def _async_add_remove_devices(self, data: PlugwiseData, entry: ConfigEntry) -> None:
async def async_add_remove_devices(self, data: PlugwiseData, entry: ConfigEntry) -> None:
"""Add new Plugwise devices, remove non-existing devices."""
# Check for new or removed devices
self.new_devices = set(data.devices) - self._current_devices
removed_devices = self._current_devices - set(data.devices)
self._current_devices = set(data.devices)

if removed_devices:
self._async_remove_devices(data, entry)
await self.async_remove_devices(data, entry)

def _async_remove_devices(self, data: PlugwiseData, entry: ConfigEntry) -> None:
async def async_remove_devices(self, data: PlugwiseData, entry: ConfigEntry) -> None:
"""Clean registries when removed devices found."""
device_reg = dr.async_get(self.hass)
device_list = dr.async_entries_for_config_entry(
Expand Down
2 changes: 1 addition & 1 deletion custom_components/plugwise/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
"iot_class": "local_polling",
"loggers": ["plugwise"],
"requirements": ["plugwise==1.4.0"],
"version": "0.53.2",
"version": "0.53.3",
"zeroconf": ["_plugwise._tcp.local."]
}
4 changes: 2 additions & 2 deletions custom_components/plugwise/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"config": {
"step": {
"user": {
"title": "Connect to the Plugwise Adam/Smile/Stretch",
"description": "Please enter:",
"title": "Set up Plugwise Adam/Smile/Stretch",
"description": "Enter your Plugwise device: (setup can take up to 90s)",
"data": {
"password": "ID",
"username": "Username",
Expand Down
4 changes: 2 additions & 2 deletions custom_components/plugwise/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"config": {
"step": {
"user": {
"title": "Connect to the Plugwise Adam/Smile/Stretch",
"description": "Please enter:",
"title": "Set up Plugwise Adam/Smile/Stretch",
"description": "Enter your Plugwise device: (setup can take up to 90s)",
"data": {
"password": "ID",
"username": "Username",
Expand Down
4 changes: 2 additions & 2 deletions custom_components/plugwise/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"config": {
"step": {
"user": {
"title": "Verbinden met de Plugwise Adam/Smile/Stretch",
"description": "Aub invoeren:",
"title": "Installeer Plugwise Adam/Smile/Stretch",
"description": "Van uw Plugwise apparaat voer in: (installeren kan tot 90s duren)",
"data": {
"password": "ID",
"username": "Gebruikersnaam",
Expand Down
13 changes: 13 additions & 0 deletions custom_components/plugwise/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
from plugwise.exceptions import PlugwiseException

from homeassistant.exceptions import HomeAssistantError
from packaging import version

from .const import DEFAULT_TIMEOUT
from .entity import PlugwiseEntity

# For reference:
Expand All @@ -17,6 +19,17 @@
# _P = ParamSpec("_P")


def get_timeout_for_version(version_str: str) -> int:
"""Determine timeout value based on gateway version.

A gateway firmware version > 3.2.0 should mean a latest-generation-device, allowing for a timeout of 10s.
Legacy devices require a timeout of 30s.
"""
if version.parse(version_str) >= version.parse("3.2.0"):
return 10
return DEFAULT_TIMEOUT


def plugwise_command[_PlugwiseEntityT: PlugwiseEntity, **_P, _R](
func: Callable[Concatenate[_PlugwiseEntityT, _P], Awaitable[_R]],
) -> Callable[Concatenate[_PlugwiseEntityT, _P], Coroutine[Any, Any, _R]]:
Expand Down
5 changes: 5 additions & 0 deletions tests/components/plugwise/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
CONF_MAC,
CONF_PASSWORD,
CONF_PORT,
CONF_TIMEOUT,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
Expand All @@ -39,8 +40,11 @@ def mock_config_entry() -> MockConfigEntry:
CONF_MAC: "AA:BB:CC:DD:EE:FF",
CONF_PASSWORD: "test-password",
CONF_PORT: 80,
CONF_TIMEOUT: 30,
CONF_USERNAME: "smile",
},
minor_version=2,
version=1,
unique_id="smile98765",
)

Expand All @@ -66,6 +70,7 @@ def mock_smile_config_flow() -> Generator[None, MagicMock, None]:
smile.smile_model = "Test Model"
smile.smile_model_id = "Test Model ID"
smile.smile_name = "Test Smile Name"
smile.smile_version = "4.3.2"
smile.connect.return_value = True
yield smile

Expand Down
25 changes: 25 additions & 0 deletions tests/components/plugwise/snapshots/test_init.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# serializer version: 1
# name: test_entry_migration
ConfigEntrySnapshot({
'data': dict({
'host': '127.0.0.1',
'mac': 'AA:BB:CC:DD:EE:FF',
'password': 'test-password',
'port': 80,
'timeout': 10,
'username': 'smile',
}),
'disabled_by': None,
'domain': 'plugwise',
'entry_id': <ANY>,
'minor_version': 2,
'options': dict({
}),
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'title': 'My Plugwise',
'unique_id': 'smile98765',
'version': 1,
})
# ---
Loading