Skip to content

Commit f9ea8f7

Browse files
authored
Merge pull request #327 from plugwise/mdi_sonar
Improve code quality
2 parents 580b4cd + 88d0e46 commit f9ea8f7

File tree

4 files changed

+40
-47
lines changed

4 files changed

+40
-47
lines changed

CHANGELOG.md

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

3+
## ongoing
4+
5+
- PR [327](https://github.com/plugwise/python-plugwise-usb/pull/327): Improve code quality according to SonarCloud, simplify sed awake timer
6+
37
## v0.44.12 - 2025-08-24
48

59
- PR [323](https://github.com/plugwise/python-plugwise-usb/pull/323): Motion Sensitivity to use named levels (Off/Medium/High) instead of numeric values, add light sensitivity calibration on wake-up for scan devices.

plugwise_usb/nodes/sed.py

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from asyncio import CancelledError, Future, Task, gather, get_running_loop, wait_for
5+
from asyncio import Task, create_task, gather, sleep
66
from collections.abc import Awaitable, Callable, Coroutine
77
from dataclasses import replace
88
from datetime import datetime, timedelta
@@ -85,7 +85,6 @@ def __init__(
8585
):
8686
"""Initialize base class for Sleeping End Device."""
8787
super().__init__(mac, node_type, controller, loaded_callback)
88-
self._loop = get_running_loop()
8988
self._node_info.is_battery_powered = True
9089

9190
# Configure SED
@@ -95,7 +94,6 @@ def __init__(
9594

9695
self._last_awake: dict[NodeAwakeResponseType, datetime] = {}
9796
self._last_awake_reason: str = "Unknown"
98-
self._awake_future: Future[bool] | None = None
9997

10098
# Maintenance
10199
self._maintenance_last_awake: datetime | None = None
@@ -118,10 +116,8 @@ async def load(self) -> bool:
118116

119117
async def unload(self) -> None:
120118
"""Deactivate and unload node features."""
121-
if self._awake_future is not None and not self._awake_future.done():
122-
self._awake_future.set_result(True)
123119
if self._awake_timer_task is not None and not self._awake_timer_task.done():
124-
await self._awake_timer_task
120+
self._awake_timer_task.cancel()
125121
if self._awake_subscription is not None:
126122
self._awake_subscription()
127123
if self._delayed_task is not None and not self._delayed_task.done():
@@ -440,7 +436,7 @@ async def _awake_response(self, response: PlugwiseResponse) -> bool:
440436
),
441437
self.save_cache(),
442438
]
443-
self._delayed_task = self._loop.create_task(
439+
self._delayed_task = create_task(
444440
self._run_awake_tasks(), name=f"Delayed update for {self._mac_in_str}"
445441
)
446442
if response.awake_type == NodeAwakeResponseType.MAINTENANCE:
@@ -516,41 +512,30 @@ def _detect_maintenance_interval(self, timestamp: datetime) -> None:
516512

517513
async def _reset_awake(self, last_alive: datetime) -> None:
518514
"""Reset node alive state."""
519-
if self._awake_future is not None and not self._awake_future.done():
520-
self._awake_future.set_result(True)
515+
if self._awake_timer_task is not None and not self._awake_timer_task.done():
516+
self._awake_timer_task.cancel()
521517
# Setup new maintenance timer
522-
self._awake_future = self._loop.create_future()
523-
self._awake_timer_task = self._loop.create_task(
518+
self._awake_timer_task = create_task(
524519
self._awake_timer(), name=f"Node awake timer for {self._mac_in_str}"
525520
)
526521

527522
async def _awake_timer(self) -> None:
528523
"""Task to monitor to get next awake in time. If not it sets device to be unavailable."""
529524
# wait for next maintenance timer, but allow missing one
530-
if self._awake_future is None:
531-
return
532525
timeout_interval = self.maintenance_interval * 60 * 2.1
533-
try:
534-
await wait_for(
535-
self._awake_future,
536-
timeout=timeout_interval,
526+
await sleep(timeout_interval)
527+
# No maintenance awake message within expected time frame
528+
# Mark node as unavailable
529+
if self._available:
530+
last_awake = self._last_awake.get(NodeAwakeResponseType.MAINTENANCE)
531+
_LOGGER.warning(
532+
"No awake message received from %s | last_maintenance_awake=%s | interval=%s (%s) | Marking node as unavailable",
533+
self.name,
534+
last_awake,
535+
self.maintenance_interval,
536+
timeout_interval,
537537
)
538-
except TimeoutError:
539-
# No maintenance awake message within expected time frame
540-
# Mark node as unavailable
541-
if self._available:
542-
last_awake = self._last_awake.get(NodeAwakeResponseType.MAINTENANCE)
543-
_LOGGER.warning(
544-
"No awake message received from %s | last_maintenance_awake=%s | interval=%s (%s) | Marking node as unavailable",
545-
self.name,
546-
last_awake,
547-
self.maintenance_interval,
548-
timeout_interval,
549-
)
550-
await self._available_update_state(False)
551-
except CancelledError:
552-
pass
553-
self._awake_future = None
538+
await self._available_update_state(False)
554539

555540
async def _run_awake_tasks(self) -> None:
556541
"""Execute all awake tasks."""

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.12"
7+
version = "0.44.13a0"
88
license = "MIT"
99
keywords = ["home", "automation", "plugwise", "module", "usb"]
1010
classifiers = [

tests/test_usb.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ async def test_stick_connect(self, monkeypatch: pytest.MonkeyPatch) -> None:
465465
await stick.disconnect()
466466
assert not stick.network_state
467467
with pytest.raises(pw_exceptions.StickError):
468-
assert stick.mac_stick
468+
stick.mac_stick
469469

470470
async def disconnected(self, event: pw_api.StickEvent) -> None: # type: ignore[name-defined]
471471
"""Handle disconnect event callback."""
@@ -611,17 +611,17 @@ async def test_stick_node_discovered_subscription( # noqa: PLR0915
611611

612612
# Check Scan is raising NodeError for unsupported features
613613
with pytest.raises(pw_exceptions.FeatureError):
614-
assert stick.nodes["5555555555555555"].relay
614+
stick.nodes["5555555555555555"].relay
615615
with pytest.raises(pw_exceptions.FeatureError):
616-
assert stick.nodes["5555555555555555"].relay_state
616+
stick.nodes["5555555555555555"].relay_state
617617
with pytest.raises(pw_exceptions.FeatureError):
618-
assert stick.nodes["5555555555555555"].switch
618+
stick.nodes["5555555555555555"].switch
619619
with pytest.raises(pw_exceptions.FeatureError):
620-
assert stick.nodes["5555555555555555"].power
620+
stick.nodes["5555555555555555"].power
621621
with pytest.raises(pw_exceptions.FeatureError):
622-
assert stick.nodes["5555555555555555"].sense
622+
stick.nodes["5555555555555555"].sense
623623
with pytest.raises(pw_exceptions.FeatureError):
624-
assert stick.nodes["5555555555555555"].energy
624+
stick.nodes["5555555555555555"].energy
625625

626626
# Motion
627627
self.test_motion_on = asyncio.Future()
@@ -851,11 +851,11 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No
851851

852852
# Check Circle is raising NodeError for unsupported features
853853
with pytest.raises(pw_exceptions.FeatureError):
854-
assert stick.nodes["0098765432101234"].motion
854+
stick.nodes["0098765432101234"].motion
855855
with pytest.raises(pw_exceptions.FeatureError):
856-
assert stick.nodes["0098765432101234"].switch
856+
stick.nodes["0098765432101234"].switch
857857
with pytest.raises(pw_exceptions.FeatureError):
858-
assert stick.nodes["0098765432101234"].sense
858+
stick.nodes["0098765432101234"].sense
859859

860860
# Test relay init
861861
# load node 2222222222222222 which has
@@ -931,7 +931,7 @@ async def fake_get_missing_energy_logs(address: int) -> None:
931931
last_second=None, last_8_seconds=None, timestamp=None
932932
)
933933
pu = await stick.nodes["0098765432101234"].power_update()
934-
assert pu.last_second == 21.2780505980402
934+
assert pu.last_second == pytest.approx(21.2780505980402, rel=1e-09, abs=1e-09)
935935
assert pu.last_8_seconds == -27.150578775440106
936936

937937
# Test energy state without request
@@ -1392,7 +1392,9 @@ def test_energy_counter(self) -> None:
13921392
0.07204743061527973,
13931393
reset_timestamp,
13941394
)
1395-
assert energy_counter_init.energy == 0.07204743061527973
1395+
assert energy_counter_init.energy == pytest.approx(
1396+
0.07204743061527973, rel=1e-09, abs=1e-09
1397+
)
13961398
assert energy_counter_init.last_reset == reset_timestamp
13971399
assert energy_counter_init.last_update == reset_timestamp + td(minutes=10)
13981400

@@ -1401,7 +1403,9 @@ def test_energy_counter(self) -> None:
14011403
0.08263379198066137,
14021404
reset_timestamp,
14031405
)
1404-
assert energy_counter_init.energy == 0.08263379198066137
1406+
assert energy_counter_init.energy == pytest.approx(
1407+
0.08263379198066137, rel=1e-09, abs=1e-09
1408+
)
14051409
assert energy_counter_init.last_reset == reset_timestamp
14061410
assert energy_counter_init.last_update == reset_timestamp + td(
14071411
minutes=15, seconds=10

0 commit comments

Comments
 (0)