Skip to content

Commit d53077d

Browse files
authored
Merge pull request #260 from plugwise/cpaj
Expose calling CirclePlusAllowJoiningRequest through enable_auto_join function
2 parents b29517b + cbf217b commit d53077d

File tree

8 files changed

+59
-88
lines changed

8 files changed

+59
-88
lines changed

CHANGELOG.md

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

3+
## v0.44.3 - 2025-06-12
4+
5+
- PR [#260](https://github.com/plugwise/python-plugwise-usb/pull/260)
6+
- Expose enable-auto-joining via CirclePlus interface
7+
38
## v0.44.2 - 2025-06-11
49

510
- Bugfix: implement solution for Issue [#259](https://github.com/plugwise/plugwise_usb-beta/issues/259)

plugwise_usb/__init__.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -180,37 +180,6 @@ def port(self, port: str) -> None:
180180

181181
self._port = port
182182

183-
@property
184-
def accept_join_request(self) -> bool | None:
185-
"""Automatically accept joining request of new nodes."""
186-
if not self._controller.is_connected:
187-
return None
188-
if self._network is None or not self._network.is_running:
189-
return None
190-
return self._network.accept_join_request
191-
192-
async def set_accept_join_request(self, state: bool) -> bool:
193-
"""Configure join request setting."""
194-
if not self._controller.is_connected:
195-
raise StickError(
196-
"Cannot accept joining node"
197-
+ " without an active USB-Stick connection."
198-
)
199-
200-
if self._network is None or not self._network.is_running:
201-
raise StickError(
202-
"Cannot accept joining node"
203-
+ " without node discovery be activated. Call discover() first."
204-
)
205-
206-
# Observation: joining is only temporarily possible after a HA (re)start or
207-
# Integration reload, force the setting when used otherwise
208-
try:
209-
await self._network.allow_join_requests(state)
210-
except (MessageError, NodeError) as exc:
211-
raise NodeError(f"Failed setting accept joining: {exc}") from exc
212-
return True
213-
214183
async def energy_reset_request(self, mac: str) -> bool:
215184
"""Send an energy-reset request to a Node."""
216185
_LOGGER.debug("Resetting energy logs for %s", mac)

plugwise_usb/api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class NodeFeature(str, Enum):
4141

4242
AVAILABLE = "available"
4343
BATTERY = "battery"
44+
CIRCLEPLUS = "circleplus"
4445
ENERGY = "energy"
4546
HUMIDITY = "humidity"
4647
INFO = "info"
@@ -83,6 +84,7 @@ class NodeType(Enum):
8384
NodeFeature.TEMPERATURE,
8485
NodeFeature.SENSE,
8586
NodeFeature.SWITCH,
87+
NodeFeature.CIRCLEPLUS,
8688
)
8789

8890

plugwise_usb/network/__init__.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
class StickNetwork:
4242
"""USB-Stick zigbee network class."""
4343

44-
accept_join_request = False
4544
_event_subscriptions: dict[StickEvent, int] = {}
4645

4746
def __init__(
@@ -152,9 +151,6 @@ def registry(self) -> dict[int, tuple[str, NodeType | None]]:
152151

153152
async def register_node(self, mac: str) -> bool:
154153
"""Register node to Plugwise network."""
155-
if not self.accept_join_request:
156-
return False
157-
158154
try:
159155
await self._register.register_node(mac)
160156
except NodeError as exc:
@@ -527,22 +523,6 @@ async def stop(self) -> None:
527523

528524
# endregion
529525

530-
async def allow_join_requests(self, state: bool) -> None:
531-
"""Enable or disable Plugwise network."""
532-
request = CirclePlusAllowJoiningRequest(self._controller.send, state)
533-
if (response := await request.send()) is None:
534-
raise NodeError("No response for CirclePlusAllowJoiningRequest.")
535-
536-
if response.response_type not in (
537-
NodeResponseType.JOIN_ACCEPTED, NodeResponseType.CIRCLE_PLUS
538-
):
539-
raise MessageError(
540-
f"Unknown NodeResponseType '{response.response_type.name}' received"
541-
)
542-
543-
_LOGGER.debug("Sent AllowJoiningRequest to Circle+ with state=%s", state)
544-
self.accept_join_request = state
545-
546526
async def energy_reset_request(self, mac: str) -> None:
547527
"""Send an energy-reset to a Node."""
548528
self._validate_energy_node(mac)

plugwise_usb/nodes/circle_plus.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
from ..messages.requests import (
1111
CirclePlusRealTimeClockGetRequest,
1212
CirclePlusRealTimeClockSetRequest,
13+
CirclePlusAllowJoiningRequest,
1314
)
1415
from ..messages.responses import NodeResponseType
1516
from .circle import PlugwiseCircle
1617
from .helpers.firmware import CIRCLE_PLUS_FIRMWARE_SUPPORT
17-
18+
from .helpers import raise_not_loaded
1819
_LOGGER = logging.getLogger(__name__)
1920

2021

@@ -37,6 +38,7 @@ async def load(self) -> bool:
3738
NodeFeature.RELAY_LOCK,
3839
NodeFeature.ENERGY,
3940
NodeFeature.POWER,
41+
NodeFeature.CIRCLEPLUS,
4042
),
4143
)
4244
if await self.initialize():
@@ -73,6 +75,7 @@ async def load(self) -> bool:
7375
NodeFeature.RELAY_LOCK,
7476
NodeFeature.ENERGY,
7577
NodeFeature.POWER,
78+
NodeFeature.CIRCLEPLUS,
7679
),
7780
)
7881
if not await self.initialize():
@@ -121,3 +124,18 @@ async def clock_synchronize(self) -> bool:
121124
self.name,
122125
)
123126
return False
127+
128+
@raise_not_loaded
129+
async def enable_auto_join(self) -> bool:
130+
"""Enable auto-join on the Circle+.
131+
132+
Returns:
133+
bool: True if the request was acknowledged, False otherwise.
134+
"""
135+
_LOGGER.info("Enabling auto-join for CirclePlus")
136+
request = CirclePlusAllowJoiningRequest(self._send, True)
137+
if (response := await request.send()) is None:
138+
return False
139+
140+
# JOIN_ACCEPTED is the ACK for enable=True
141+
return NodeResponseType(response.ack_id) == NodeResponseType.JOIN_ACCEPTED

plugwise_usb/nodes/helpers/firmware.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ class SupportedVersions(NamedTuple):
167167
NodeFeature.MOTION: 2.0,
168168
NodeFeature.MOTION_CONFIG: 2.0,
169169
NodeFeature.SWITCH: 2.0,
170+
NodeFeature.CIRCLEPLUS: 2.0,
170171
}
171172

172173
# endregion

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "plugwise_usb"
7-
version = "0.44.2"
7+
version = "0.44.3"
88
license = "MIT"
99
keywords = ["home", "automation", "plugwise", "module", "usb"]
1010
classifiers = [

tests/test_usb.py

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,6 @@ async def test_msg_properties(self) -> None:
344344
async def test_stick_connect_without_port(self) -> None:
345345
"""Test connecting to stick without port config."""
346346
stick = pw_stick.Stick()
347-
assert stick.accept_join_request is None
348347
assert stick.nodes == {}
349348
assert stick.joined_nodes is None
350349
with pytest.raises(pw_exceptions.StickError):
@@ -466,10 +465,6 @@ async def test_stick_connect(self, monkeypatch: pytest.MonkeyPatch) -> None:
466465
assert not stick.network_discovered
467466
assert stick.network_state
468467
assert stick.network_id == 17185
469-
assert stick.accept_join_request is None
470-
# test failing of join requests without active discovery
471-
with pytest.raises(pw_exceptions.StickError):
472-
await stick.set_accept_join_request(True)
473468
unsub_connect()
474469
await stick.disconnect()
475470
assert not stick.network_state
@@ -651,36 +646,36 @@ async def node_join(self, event: pw_api.NodeEvent, mac: str) -> None: # type: i
651646
)
652647
)
653648

654-
@pytest.mark.asyncio
655-
async def test_stick_node_join_subscription(
656-
self, monkeypatch: pytest.MonkeyPatch
657-
) -> None:
658-
"""Testing "new_node" subscription."""
659-
mock_serial = MockSerial(None)
660-
monkeypatch.setattr(
661-
pw_connection_manager,
662-
"create_serial_connection",
663-
mock_serial.mock_connection,
664-
)
665-
monkeypatch.setattr(pw_sender, "STICK_TIME_OUT", 0.1)
666-
monkeypatch.setattr(pw_requests, "NODE_TIME_OUT", 0.5)
667-
stick = pw_stick.Stick("test_port", cache_enabled=False)
668-
await stick.connect()
669-
await stick.initialize()
670-
await stick.discover_nodes(load=False)
671-
await stick.set_accept_join_request(True)
672-
# self.test_node_join = asyncio.Future()
673-
# unusb_join = stick.subscribe_to_node_events(
674-
# node_event_callback=self.node_join,
675-
# events=(pw_api.NodeEvent.JOIN,),
676-
# )
677-
678-
# Inject NodeJoinAvailableResponse
679-
# mock_serial.inject_message(b"00069999999999999999", b"1254") # @bouwew: seq_id is not FFFC!
680-
# mac_join_node = await self.test_node_join
681-
# assert mac_join_node == "9999999999999999"
682-
# unusb_join()
683-
await stick.disconnect()
649+
#@pytest.mark.asyncio
650+
#async def test_stick_node_join_subscription(
651+
# self, monkeypatch: pytest.MonkeyPatch
652+
#) -> None:
653+
#"""Testing "new_node" subscription."""
654+
#mock_serial = MockSerial(None)
655+
#monkeypatch.setattr(
656+
# pw_connection_manager,
657+
# "create_serial_connection",
658+
# mock_serial.mock_connection,
659+
#)
660+
#monkeypatch.setattr(pw_sender, "STICK_TIME_OUT", 0.1)
661+
#monkeypatch.setattr(pw_requests, "NODE_TIME_OUT", 0.5)
662+
#stick = pw_stick.Stick("test_port", cache_enabled=False)
663+
#await stick.connect()
664+
#await stick.initialize()
665+
#await stick.discover_nodes(load=False)
666+
667+
#self.test_node_join = asyncio.Future()
668+
#unusb_join = stick.subscribe_to_node_events(
669+
# node_event_callback=self.node_join,
670+
# events=(pw_api.NodeEvent.JOIN,),
671+
#)
672+
673+
## Inject NodeJoinAvailableResponse
674+
#mock_serial.inject_message(b"00069999999999999999", b"1253") # @bouwew: seq_id is not FFFC!
675+
#mac_join_node = await self.test_node_join
676+
#assert mac_join_node == "9999999999999999"
677+
#unusb_join()
678+
#await stick.disconnect()
684679

685680
@pytest.mark.asyncio
686681
async def test_node_discovery(self, monkeypatch: pytest.MonkeyPatch) -> None:
@@ -2498,6 +2493,7 @@ async def test_node_discovery_and_load(
24982493
pw_api.NodeFeature.RELAY,
24992494
pw_api.NodeFeature.RELAY_LOCK,
25002495
pw_api.NodeFeature.ENERGY,
2496+
pw_api.NodeFeature.CIRCLEPLUS,
25012497
pw_api.NodeFeature.POWER,
25022498
)
25032499
)

0 commit comments

Comments
 (0)