Skip to content

Commit bd2947a

Browse files
authored
Merge pull request #407 from plugwise/reconfigure
Implement config_flow reconfigure
2 parents 495338c + c433f44 commit bd2947a

10 files changed

Lines changed: 225 additions & 35 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## v0.59.0
4+
5+
- New Feature: implement reconfiguring of the Stick path via PR [407](https://github.com/plugwise/plugwise_usb-beta/pull/407)
6+
37
## v0.58.2 - 2026-01-29
48

59
- Update plugwise_usb to [v0.47.2](https://github.com/plugwise/python-plugwise-usb/releases/tag/v0.47.2) fixing a bug.

custom_components/plugwise_usb/__init__.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,20 @@ async def async_unload_entry(
162162
hass: HomeAssistant, config_entry: PlugwiseUSBConfigEntry
163163
) -> bool:
164164
"""Unload the Plugwise USB stick connection."""
165-
config_entry.runtime_data[UNSUBSCRIBE_DISCOVERY]()
166-
for coordinator in config_entry.runtime_data[NODES].values():
167-
await coordinator.unsubscribe_all_nodefeatures()
168-
unload = await hass.config_entries.async_unload_platforms(
165+
runtime_data = getattr(config_entry, "runtime_data", None) or {}
166+
if runtime_data:
167+
unsubscribe_discovery = runtime_data.get(UNSUBSCRIBE_DISCOVERY)
168+
if callable(unsubscribe_discovery):
169+
unsubscribe_discovery()
170+
for coordinator in runtime_data.get(NODES, {}).values():
171+
await coordinator.unsubscribe_all_nodefeatures()
172+
stick = runtime_data.get(STICK)
173+
if stick is not None:
174+
await stick.disconnect()
175+
176+
return await hass.config_entries.async_unload_platforms(
169177
config_entry, PLUGWISE_USB_PLATFORMS
170178
)
171-
await config_entry.runtime_data[STICK].disconnect()
172-
return unload
173179

174180

175181
async def async_remove_config_entry_device(

custom_components/plugwise_usb/config_flow.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@
99
import voluptuous as vol
1010

1111
from homeassistant.components import usb
12-
from homeassistant.config_entries import SOURCE_USER, ConfigFlow
12+
from homeassistant.config_entries import SOURCE_USER, ConfigFlow, ConfigFlowResult
1313
from homeassistant.const import CONF_BASE
1414
from homeassistant.core import callback
1515
from homeassistant.data_entry_flow import FlowResult
1616
import serial.tools.list_ports
1717

1818
from .const import CONF_MANUAL_PATH, CONF_USB_PATH, DOMAIN, MANUAL_PATH
1919

20+
STICK_RECONF_SCHEMA = vol.Schema(
21+
{
22+
vol.Required(CONF_USB_PATH): str,
23+
}
24+
)
25+
2026

2127
@callback
2228
def plugwise_stick_entries(hass):
@@ -28,7 +34,7 @@ def plugwise_stick_entries(hass):
2834
]
2935

3036

31-
async def validate_usb_connection(self, device_path=None) -> tuple[dict[str, str], str]:
37+
async def validate_usb_connection(self, device_path=None) -> tuple[dict[str, str], str | None]:
3238
"""Test if device_path is a real Plugwise USB-Stick."""
3339
errors = {}
3440

@@ -93,6 +99,7 @@ async def async_step_user(
9399
return self.async_create_entry(
94100
title="Stick", data={CONF_USB_PATH: device_path}
95101
)
102+
96103
return self.async_show_form(
97104
step_id=SOURCE_USER,
98105
data_schema=vol.Schema(
@@ -112,7 +119,10 @@ async def async_step_manual_path(
112119
)
113120
errors, mac_stick = await validate_usb_connection(self.hass, device_path)
114121
if not errors:
115-
await self.async_set_unique_id(mac_stick)
122+
await self.async_set_unique_id(
123+
unique_id=mac_stick, raise_on_progress=False
124+
)
125+
self._abort_if_unique_id_configured()
116126
return self.async_create_entry(
117127
title="Stick", data={CONF_USB_PATH: device_path}
118128
)
@@ -123,3 +133,35 @@ async def async_step_manual_path(
123133
),
124134
errors=errors,
125135
)
136+
137+
async def async_step_reconfigure(
138+
self, user_input: dict[str, Any] | None = None
139+
) -> ConfigFlowResult:
140+
"""Handle reconfiguration of the integration."""
141+
errors: dict[str, str] = {}
142+
reconfigure_entry = self._get_reconfigure_entry()
143+
144+
if user_input is not None:
145+
device_path = await self.hass.async_add_executor_job(
146+
usb.get_serial_by_id, user_input.get(CONF_USB_PATH)
147+
)
148+
errors, mac_stick = await validate_usb_connection(self.hass, device_path)
149+
if not errors:
150+
await self.async_set_unique_id(
151+
unique_id=mac_stick, raise_on_progress=False
152+
)
153+
self._abort_if_unique_id_mismatch(reason="not_the_same_stick")
154+
return self.async_update_reload_and_abort(
155+
reconfigure_entry,
156+
data_updates={CONF_USB_PATH: device_path}
157+
)
158+
159+
return self.async_show_form(
160+
step_id="reconfigure",
161+
data_schema=self.add_suggested_values_to_schema(
162+
data_schema=STICK_RECONF_SCHEMA,
163+
suggested_values=reconfigure_entry.data,
164+
),
165+
description_placeholders={"title": reconfigure_entry.title},
166+
errors=errors,
167+
)

custom_components/plugwise_usb/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@
1010
"issue_tracker": "https://github.com/plugwise/python-plugwise-usb/issues",
1111
"loggers": ["plugwise_usb"],
1212
"requirements": ["plugwise-usb==0.47.2"],
13-
"version": "0.58.2"
13+
"version": "0.59.0"
1414
}

custom_components/plugwise_usb/strings.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,18 @@
1212
"data": {
1313
"usb_path": "USB-path"
1414
}
15+
},
16+
"reconfigure": {
17+
"data": {
18+
"usb_path": "USB-path"
19+
},
20+
"description": "Update USB-{title} device-path"
1521
}
1622
},
23+
"abort": {
24+
"not_the_same_stick": "The configured Stick does not match with the Stick on the entered port",
25+
"reconfigure_successful": "Reconfiguration successful"
26+
},
1727
"error": {
1828
"already_configured": "This device is already configured",
1929
"cannot_connect": "Failed to connect",
@@ -29,7 +39,7 @@
2939
"fields": {
3040
"mac": {
3141
"name": "MAC address",
32-
"description": "The full 16 character MAC address of the plugwise device."
42+
"description": "The full 16 character MAC address of the plugwise device"
3343
}
3444
}
3545
},
@@ -39,7 +49,7 @@
3949
"fields": {
4050
"mac": {
4151
"name": "MAC address",
42-
"description": "The full 16 character MAC address of the plugwise device."
52+
"description": "The full 16 character MAC address of the plugwise device"
4353
}
4454
}
4555
}

custom_components/plugwise_usb/translations/en.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,18 @@
1212
"data": {
1313
"usb_path": "USB-path"
1414
}
15+
},
16+
"reconfigure": {
17+
"data": {
18+
"usb_path": "USB-path"
19+
},
20+
"description": "Update USB-{title} device-path"
1521
}
1622
},
23+
"abort": {
24+
"not_the_same_stick": "The configured Stick does not match with the Stick on the entered port",
25+
"reconfigure_successful": "Reconfiguration successful"
26+
},
1727
"error": {
1828
"already_configured": "This device is already configured",
1929
"cannot_connect": "Failed to connect",
@@ -29,7 +39,7 @@
2939
"fields": {
3040
"mac": {
3141
"name": "MAC address",
32-
"description": "The full 16 character MAC address of the plugwise device."
42+
"description": "The full 16 character MAC address of the plugwise device"
3343
}
3444
}
3545
},
@@ -39,7 +49,7 @@
3949
"fields": {
4050
"mac": {
4151
"name": "MAC address",
42-
"description": "The full 16 character MAC address of the plugwise device."
52+
"description": "The full 16 character MAC address of the plugwise device"
4353
}
4454
}
4555
}

custom_components/plugwise_usb/translations/nl.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,18 @@
1212
"data": {
1313
"usb_path": "USB-pad"
1414
}
15+
},
16+
"reconfigure": {
17+
"data": {
18+
"usb_path": "USB-pad"
19+
},
20+
"description": "USB-{title} pad bijwerken"
1521
}
1622
},
23+
"abort": {
24+
"not_the_same_stick": "De geconfigureerde Stick matcht niet met de Stick van de ingegeven poort",
25+
"reconfigure_successful": "Herconfiguratie succesvol"
26+
},
1727
"error": {
1828
"already_configured": "Dit apparaat is al geconfigureerd",
1929
"cannot_connect": "Verbinden is mislukt",
@@ -29,7 +39,7 @@
2939
"fields": {
3040
"mac": {
3141
"name": "MAC adres",
32-
"description": "Het volledige MAC address (16 karakters) van het plugwise apparaat."
42+
"description": "Het volledige MAC address (16 karakters) van het plugwise apparaat"
3343
}
3444
}
3545
},
@@ -39,7 +49,7 @@
3949
"fields": {
4050
"mac": {
4151
"name": "MAC adres",
42-
"description": "Het volledige MAC address (16 karakters) van het plugwise apparaat."
52+
"description": "Het volledige MAC address (16 karakters) van het plugwise apparaat"
4353
}
4454
}
4555
}

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "plugwise_usb-beta"
3-
version = "0.58.2"
3+
version = "0.59.0"
44
description = "Plugwise USB custom_component (BETA)"
55
readme = "README.md"
66
requires-python = ">=3.13"

tests/conftest.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
from custom_components.plugwise_usb.const import CONF_USB_PATH, DOMAIN
1313
from homeassistant.core import HomeAssistant
14+
1415
from pytest_homeassistant_custom_component.common import MockConfigEntry
1516

16-
TEST_MAC: Final[str] = "01:23:45:67:AB"
1717
STICK_IMPORT_MOCK: Final[str] = "custom_components.plugwise_usb.config_flow.Stick"
18+
TEST_MAC: Final[str] = "01:23:45:67:AB"
19+
TEST_USB_PATH: Final[str] = "/dev/ttyUSB1"
1820

1921

2022
@pytest.fixture
@@ -27,17 +29,15 @@ def mock_setup_entry() -> Generator[AsyncMock]:
2729
yield mock_setup
2830

2931

30-
TEST_USBPORT = "/dev/ttyUSB1"
31-
32-
3332
@pytest.fixture
3433
def mock_config_entry() -> MockConfigEntry:
3534
"""Return a mocked v1.2 config entry.""" # pw-beta only
3635
return MockConfigEntry(
3736
domain=DOMAIN,
38-
data={CONF_USB_PATH: TEST_USBPORT},
39-
title="plugwise_usb",
40-
unique_id="TEST_USBPORT",
37+
data={CONF_USB_PATH: TEST_USB_PATH},
38+
minor_version=0,
39+
version=1,
40+
unique_id=TEST_MAC,
4141
)
4242

4343

@@ -68,6 +68,21 @@ async def init_integration(
6868
# yield [port]
6969

7070

71+
@pytest.fixture
72+
def mock_usb_stick_not_setup() -> Generator[MagicMock]:
73+
"""Return a mocked usb_mock."""
74+
75+
with patch(STICK_IMPORT_MOCK, autospec=True) as mock_usb:
76+
usb = mock_usb.return_value
77+
78+
usb.connect = AsyncMock(return_value=None)
79+
usb.initialize = AsyncMock(return_value=None)
80+
usb.disconnect = AsyncMock(return_value=None)
81+
usb.mac_stick = None
82+
83+
yield usb
84+
85+
7186
@pytest.fixture
7287
def mock_usb_stick() -> Generator[MagicMock]:
7388
"""Return a mocked usb_mock."""
@@ -113,12 +128,16 @@ def mock_usb_stick_init_error() -> Generator[MagicMock]:
113128
yield usb
114129

115130

116-
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
131+
async def setup_integration(
132+
hass: HomeAssistant, config_entry: MockConfigEntry
133+
) -> MockConfigEntry:
117134
"""Set up the usb integration."""
118135
config_entry.add_to_hass(hass)
119136
await hass.config_entries.async_setup(config_entry.entry_id)
120137
await hass.async_block_till_done()
121138

139+
return config_entry
140+
122141

123142
@pytest.fixture(autouse=True)
124143
def auto_enable_custom_integrations(enable_custom_integrations):

0 commit comments

Comments
 (0)