Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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

## Ongoing

- PR [342](https://github.com/plugwise/python-plugwise-usb/pull/342): Improve node_type chaching.

## 0.46.0 - 2025-09-12

- PR [338](https://github.com/plugwise/python-plugwise-usb/pull/338): Append report interval to Sense node configuration
Expand Down
28 changes: 18 additions & 10 deletions plugwise_usb/network/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ def nodetypes(self) -> dict[str, NodeType]:

async def save_cache(self) -> None:
"""Save the node information to file."""
cache_data_to_save: dict[str, str] = {}
for mac, node_type in self._nodetypes.items():
node_value = str(node_type)
cache_data_to_save[mac] = node_value
_LOGGER.debug("Save NodeTypes %s", str(len(cache_data_to_save)))
await self.write_cache(cache_data_to_save)
cache_data_to_save: dict[str, str] = {
mac: node_type.name for mac, node_type in self._nodetypes.items()
}
_LOGGER.debug("Save NodeTypes for %s Nodes", len(cache_data_to_save))
await self.write_cache(cache_data_to_save, rewrite=True) # Make sure the cache-contents is actual

async def clear_cache(self) -> None:
"""Clear current cache."""
Expand All @@ -44,13 +43,18 @@ async def restore_cache(self) -> None:
self._nodetypes = {}
for mac, node_value in data.items():
node_type: NodeType | None = None
if len(node_value) >= 10:
# Backward-compatible parsing: support full enums, enum names, or numeric values
val = node_value.strip()
key = (val.split(".", 1)[1] if val.startswith("NodeType.") else val).upper()
node_type = NodeType.__members__.get(key) # e.g., "CIRCLE"
if node_type is None:
try:
node_type = NodeType[node_value[9:]]
except KeyError:
node_type = NodeType(int(val))
except ValueError:
node_type = None

if node_type is None:
_LOGGER.warning("Invalid NodeType in cache: %s", node_value)
_LOGGER.warning("Invalid NodeType in cache for mac %s: %s", mac, node_value)
continue
self._nodetypes[mac] = node_type
_LOGGER.debug(
Expand Down Expand Up @@ -86,5 +90,9 @@ async def prune_cache(self, registry: list[str]) -> None:
continue
if (node_type := self.get_nodetype(mac)) is not None:
new_nodetypes[mac] = node_type

if new_nodetypes == self._nodetypes:
_LOGGER.debug("prune_cache: no changes; skipping save")
return
self._nodetypes = new_nodetypes
await self.save_cache()
12 changes: 6 additions & 6 deletions tests/test_usb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1712,9 +1712,9 @@ async def makedirs(cache_dir: str, exist_ok: bool) -> None:

# test with valid data
mock_read_data = [
"0123456789ABCDEF;NodeType.CIRCLE_PLUS",
"0123456789ABCDEF;CIRCLE_PLUS",
"FEDCBA9876543210;NodeType.CIRCLE",
"1298347650AFBECD;NodeType.SCAN",
"1298347650AFBECD;6",
]
file_chunks_iter = iter(mock_read_data)
mock_file_stream = MagicMock(readlines=lambda *args, **kwargs: file_chunks_iter)
Expand All @@ -1733,10 +1733,10 @@ async def makedirs(cache_dir: str, exist_ok: bool) -> None:
)
mock_file_stream.writelines.assert_called_with(
[
"0123456789ABCDEF;NodeType.CIRCLE_PLUS\n",
"FEDCBA9876543210;NodeType.CIRCLE\n",
"1298347650AFBECD;NodeType.SCAN\n",
"1234ABCD4321FEDC;NodeType.STEALTH\n",
"0123456789ABCDEF;CIRCLE_PLUS\n",
"FEDCBA9876543210;CIRCLE\n",
"1298347650AFBECD;SCAN\n",
"1234ABCD4321FEDC;STEALTH\n",
]
)
assert pw_nw_cache.nodetypes == {
Expand Down