Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ebc92b5
Add reconfigure flow
bouwew Feb 25, 2026
f84bd99
Update strings.json and related
bouwew Feb 25, 2026
1149093
Add reconfigure testcases
bouwew Feb 25, 2026
16a7912
Test: correct mock-device
bouwew Feb 25, 2026
ee2b0a6
Add missing constant
bouwew Feb 25, 2026
13bf68a
PORT -> PATH
bouwew Feb 25, 2026
eb477e0
Improve async_step_reconfigure()
bouwew Feb 25, 2026
cfbca3a
Fix test
bouwew Feb 26, 2026
cb68799
Improve conftest.py
bouwew Feb 26, 2026
16b81df
Line up constants
bouwew Feb 26, 2026
78f3781
Try ABORT, add mock_setup_entry, mock_usb_stick
bouwew Feb 26, 2026
b8c4519
Add missing imports
bouwew Feb 26, 2026
73f54c3
Revert FLOW to ABORT change
bouwew Feb 26, 2026
752488a
Various improvements
bouwew Feb 26, 2026
643febe
Clean up
bouwew Feb 26, 2026
53f9a6a
Fix unique_id, test with different MAC
bouwew Feb 26, 2026
61daf1c
Guard config_entry.runtime_data
bouwew Feb 26, 2026
77c73d5
Correct logic
bouwew Feb 26, 2026
8ac4f13
Fixes
bouwew Feb 26, 2026
3c3e080
Update CHANGELOG
bouwew Feb 26, 2026
4b856dc
Update custom_components/plugwise_usb/__init__.py
bouwew Feb 27, 2026
986511a
Add missing _abort_if_unique_id_configured() in manual path
bouwew Feb 27, 2026
a51b2d1
Add mock_usb_stick_not_setup() fixture
bouwew Feb 27, 2026
622776c
And implement to correct related testcase
bouwew Feb 27, 2026
8f3f04c
Make one line
bouwew Feb 27, 2026
35bd2f1
Bump to v0.59.0b0
bouwew Feb 27, 2026
c17b81f
Remove unneeded line
bouwew Feb 27, 2026
9caa76c
Update strings.json and related
bouwew Feb 27, 2026
0426c91
Improve reconfigure string
bouwew Feb 27, 2026
730ca61
Use the same format as elsewhere
bouwew Feb 27, 2026
be5e070
Set to v0.59.0 release-version, update CHANGELOG
bouwew Mar 1, 2026
1af73a5
Ruff
bouwew Mar 1, 2026
e51ba4d
Revert "Use the same format as elsewhere"
bouwew Mar 1, 2026
bf0fc5c
Fix ident, as suggested
bouwew Mar 1, 2026
7a25f5a
Fix minor_version discrepancy
bouwew Mar 1, 2026
c433f44
Line up format with similar lines
bouwew Mar 1, 2026
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v0.59.0

- New Feature: implement reconfiguring of the Stick path via PR [407](https://github.com/plugwise/plugwise_usb-beta/pull/407)

## v0.58.2 - 2026-01-29

- Update plugwise_usb to [v0.47.2](https://github.com/plugwise/python-plugwise-usb/releases/tag/v0.47.2) fixing a bug.
Expand Down
18 changes: 12 additions & 6 deletions custom_components/plugwise_usb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,14 +162,20 @@ async def async_unload_entry(
hass: HomeAssistant, config_entry: PlugwiseUSBConfigEntry
) -> bool:
"""Unload the Plugwise USB stick connection."""
config_entry.runtime_data[UNSUBSCRIBE_DISCOVERY]()
for coordinator in config_entry.runtime_data[NODES].values():
await coordinator.unsubscribe_all_nodefeatures()
unload = await hass.config_entries.async_unload_platforms(
runtime_data = getattr(config_entry, "runtime_data", None) or {}
if runtime_data:
unsubscribe_discovery = runtime_data.get(UNSUBSCRIBE_DISCOVERY)
if callable(unsubscribe_discovery):
unsubscribe_discovery()
for coordinator in runtime_data.get(NODES, {}).values():
await coordinator.unsubscribe_all_nodefeatures()
stick = runtime_data.get(STICK)
if stick is not None:
await stick.disconnect()

return await hass.config_entries.async_unload_platforms(
config_entry, PLUGWISE_USB_PLATFORMS
)
await config_entry.runtime_data[STICK].disconnect()
return unload


async def async_remove_config_entry_device(
Expand Down
48 changes: 45 additions & 3 deletions custom_components/plugwise_usb/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@
import voluptuous as vol

from homeassistant.components import usb
from homeassistant.config_entries import SOURCE_USER, ConfigFlow
from homeassistant.config_entries import SOURCE_USER, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_BASE
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
import serial.tools.list_ports

from .const import CONF_MANUAL_PATH, CONF_USB_PATH, DOMAIN, MANUAL_PATH

STICK_RECONF_SCHEMA = vol.Schema(
{
vol.Required(CONF_USB_PATH): str,
}
)


@callback
def plugwise_stick_entries(hass):
Expand All @@ -28,7 +34,7 @@ def plugwise_stick_entries(hass):
]


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

Expand Down Expand Up @@ -93,6 +99,7 @@ async def async_step_user(
return self.async_create_entry(
title="Stick", data={CONF_USB_PATH: device_path}
)

return self.async_show_form(
step_id=SOURCE_USER,
data_schema=vol.Schema(
Expand All @@ -112,7 +119,10 @@ async def async_step_manual_path(
)
errors, mac_stick = await validate_usb_connection(self.hass, device_path)
if not errors:
await self.async_set_unique_id(mac_stick)
await self.async_set_unique_id(
unique_id=mac_stick, raise_on_progress=False
)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title="Stick", data={CONF_USB_PATH: device_path}
)
Expand All @@ -123,3 +133,35 @@ async def async_step_manual_path(
),
errors=errors,
)

async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of the integration."""
errors: dict[str, str] = {}
reconfigure_entry = self._get_reconfigure_entry()

if user_input is not None:
device_path = await self.hass.async_add_executor_job(
usb.get_serial_by_id, user_input.get(CONF_USB_PATH)
)
errors, mac_stick = await validate_usb_connection(self.hass, device_path)
if not errors:
await self.async_set_unique_id(
unique_id=mac_stick, raise_on_progress=False
)
self._abort_if_unique_id_mismatch(reason="not_the_same_stick")
return self.async_update_reload_and_abort(
reconfigure_entry,
data_updates={CONF_USB_PATH: device_path}
)

return self.async_show_form(
step_id="reconfigure",
data_schema=self.add_suggested_values_to_schema(
data_schema=STICK_RECONF_SCHEMA,
suggested_values=reconfigure_entry.data,
),
description_placeholders={"title": reconfigure_entry.title},
errors=errors,
)
2 changes: 1 addition & 1 deletion custom_components/plugwise_usb/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"issue_tracker": "https://github.com/plugwise/python-plugwise-usb/issues",
"loggers": ["plugwise_usb"],
"requirements": ["plugwise-usb==0.47.2"],
"version": "0.58.2"
"version": "0.59.0"
}
14 changes: 12 additions & 2 deletions custom_components/plugwise_usb/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,18 @@
"data": {
"usb_path": "USB-path"
}
},
"reconfigure": {
"data": {
"usb_path": "USB-path"
},
"description": "Update USB-{title} device-path"
}
},
"abort": {
"not_the_same_stick": "The configured Stick does not match with the Stick on the entered port",
"reconfigure_successful": "Reconfiguration successful"
},
"error": {
"already_configured": "This device is already configured",
"cannot_connect": "Failed to connect",
Expand All @@ -29,7 +39,7 @@
"fields": {
"mac": {
"name": "MAC address",
"description": "The full 16 character MAC address of the plugwise device."
"description": "The full 16 character MAC address of the plugwise device"
}
}
},
Expand All @@ -39,7 +49,7 @@
"fields": {
"mac": {
"name": "MAC address",
"description": "The full 16 character MAC address of the plugwise device."
"description": "The full 16 character MAC address of the plugwise device"
}
}
}
Expand Down
14 changes: 12 additions & 2 deletions custom_components/plugwise_usb/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,18 @@
"data": {
"usb_path": "USB-path"
}
},
"reconfigure": {
"data": {
"usb_path": "USB-path"
},
"description": "Update USB-{title} device-path"
}
},
"abort": {
"not_the_same_stick": "The configured Stick does not match with the Stick on the entered port",
"reconfigure_successful": "Reconfiguration successful"
},
"error": {
"already_configured": "This device is already configured",
"cannot_connect": "Failed to connect",
Expand All @@ -29,7 +39,7 @@
"fields": {
"mac": {
"name": "MAC address",
"description": "The full 16 character MAC address of the plugwise device."
"description": "The full 16 character MAC address of the plugwise device"
}
}
},
Expand All @@ -39,7 +49,7 @@
"fields": {
"mac": {
"name": "MAC address",
"description": "The full 16 character MAC address of the plugwise device."
"description": "The full 16 character MAC address of the plugwise device"
}
}
}
Expand Down
14 changes: 12 additions & 2 deletions custom_components/plugwise_usb/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,18 @@
"data": {
"usb_path": "USB-pad"
}
},
"reconfigure": {
"data": {
"usb_path": "USB-pad"
},
"description": "USB-{title} pad bijwerken"
}
},
"abort": {
"not_the_same_stick": "De geconfigureerde Stick matcht niet met de Stick van de ingegeven poort",
"reconfigure_successful": "Herconfiguratie succesvol"
},
"error": {
"already_configured": "Dit apparaat is al geconfigureerd",
"cannot_connect": "Verbinden is mislukt",
Expand All @@ -29,7 +39,7 @@
"fields": {
"mac": {
"name": "MAC adres",
"description": "Het volledige MAC address (16 karakters) van het plugwise apparaat."
"description": "Het volledige MAC address (16 karakters) van het plugwise apparaat"
}
}
},
Expand All @@ -39,7 +49,7 @@
"fields": {
"mac": {
"name": "MAC adres",
"description": "Het volledige MAC address (16 karakters) van het plugwise apparaat."
"description": "Het volledige MAC address (16 karakters) van het plugwise apparaat"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "plugwise_usb-beta"
version = "0.58.2"
version = "0.59.0"
description = "Plugwise USB custom_component (BETA)"
readme = "README.md"
requires-python = ">=3.13"
Expand Down
35 changes: 27 additions & 8 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@

from custom_components.plugwise_usb.const import CONF_USB_PATH, DOMAIN
from homeassistant.core import HomeAssistant

from pytest_homeassistant_custom_component.common import MockConfigEntry

TEST_MAC: Final[str] = "01:23:45:67:AB"
STICK_IMPORT_MOCK: Final[str] = "custom_components.plugwise_usb.config_flow.Stick"
TEST_MAC: Final[str] = "01:23:45:67:AB"
TEST_USB_PATH: Final[str] = "/dev/ttyUSB1"


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


TEST_USBPORT = "/dev/ttyUSB1"


@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return a mocked v1.2 config entry.""" # pw-beta only
return MockConfigEntry(
domain=DOMAIN,
data={CONF_USB_PATH: TEST_USBPORT},
title="plugwise_usb",
unique_id="TEST_USBPORT",
data={CONF_USB_PATH: TEST_USB_PATH},
minor_version=0,
version=1,
unique_id=TEST_MAC,
)


Expand Down Expand Up @@ -68,6 +68,21 @@ async def init_integration(
# yield [port]


@pytest.fixture
def mock_usb_stick_not_setup() -> Generator[MagicMock]:
"""Return a mocked usb_mock."""

with patch(STICK_IMPORT_MOCK, autospec=True) as mock_usb:
usb = mock_usb.return_value

usb.connect = AsyncMock(return_value=None)
usb.initialize = AsyncMock(return_value=None)
usb.disconnect = AsyncMock(return_value=None)
usb.mac_stick = None

yield usb


@pytest.fixture
def mock_usb_stick() -> Generator[MagicMock]:
"""Return a mocked usb_mock."""
Expand Down Expand Up @@ -113,12 +128,16 @@ def mock_usb_stick_init_error() -> Generator[MagicMock]:
yield usb


async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
async def setup_integration(
hass: HomeAssistant, config_entry: MockConfigEntry
) -> MockConfigEntry:
"""Set up the usb integration."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

return config_entry


@pytest.fixture(autouse=True)
def auto_enable_custom_integrations(enable_custom_integrations):
Expand Down
Loading
Loading