-
Notifications
You must be signed in to change notification settings - Fork 2
Alternative solution for handling EnergyLogs outside MAX_LOG_HOURS #307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThe PR refactors energy log handling in plugwise_usb/nodes/circle.py: changes energy_log_update to return a boolean, introduces a recency check using DAY_IN_HOURS, simplifies initial log collection, and overhauls cache parsing/serialization to maintain reverse-sorted, recency-filtered records. pyproject.toml updates the version to 0.44.11a1. Changes
Sequence Diagram(s)sequenceDiagram
participant Caller as Startup/Timer
participant Circle as PlugwiseCircle
participant Device as USB Device
participant Cache as Cache Store
Caller->>Circle: energy_log_update(address)
alt address is None
Circle-->>Caller: False
else address provided
Circle->>Device: request energy logs
Device-->>Circle: records (pulses, timestamp, slot)
loop for each record
Circle->>Circle: _check_timestamp_is_recent(slot, timestamp)
alt recent
Circle->>Cache: add_pulse_log(record)
Circle->>Cache: save reverse-sorted cache
else outdated
Circle->>Cache: add empty log for slot
end
end
Circle-->>Caller: True if any recent non-empty stored else False
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #307 +/- ##
==========================================
+ Coverage 80.54% 80.61% +0.07%
==========================================
Files 36 36
Lines 7540 7543 +3
==========================================
+ Hits 6073 6081 +8
+ Misses 1467 1462 -5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (4)
plugwise_usb/nodes/circle.py (4)
549-551: Typo in comment“store am empty record instead” → “store an empty record instead”.
668-672: Ensure deterministic cache serialization orderThe comment says “logs is already sorted in reverse”, but dict ordering depends on insertion order. Unless EnergyCounters.get_pulse_logs guarantees reverse insertion, cache order may become unstable. Consider explicit sorting and iterating items directly:
- # logs is already sorted in reverse - for address, record in logs.items(): - for slot in record: - log = record[slot] + # Ensure deterministic reverse order + for address, record in sorted(logs.items(), reverse=True): + for slot, log in sorted(record.items(), reverse=True):This trades minimal CPU for stable cache diffs and simpler troubleshooting.
710-711: Avoid trailing delimiter when cache is emptyWhen cached_logs == "", the assignment produces "new|", leaving a trailing pipe. While harmless on load, it’s easy to avoid:
- self._set_cache( - CACHE_ENERGY_COLLECTION, log_cache_record + "|" + cached_logs - ) + sep = "" if cached_logs == "" else "|" + self._set_cache( + CACHE_ENERGY_COLLECTION, log_cache_record + sep + cached_logs + )
456-466: Docstring vs. implementation: off-by-one in number of addressesDocstring says “last 10 log addresses,” but total_addresses is 11, including current + 10 previous. Either update the docstring to “last 11 (current + 10 previous) log addresses” or set total_addresses = 10 to match the text.
Also applies to: 468-476
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugwise_usb/nodes/circle.py(11 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:615-627
Timestamp: 2025-06-20T07:58:41.534Z
Learning: In the plugwise_usb/nodes/circle.py file, the `log_addresses_missing` property being `None` means the parameter has not been initialized yet, not that all logs are present. When `log_addresses_missing` is `None`, returning `False` from cache loading methods is correct because the system is not properly initialized and further execution would be useless.
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#302
File: plugwise_usb/nodes/scan.py:132-142
Timestamp: 2025-08-07T17:50:29.163Z
Learning: In the plugwise_usb codebase, the `_load_defaults` method in node classes (scan.py, switch.py, sense.py) is only called once during node initialization, not during runtime. Therefore, flags like `_sed_node_info_update_task_scheduled` don't need to be reset at the start of `_load_defaults` as there's no risk of stale state from previous loads.
📚 Learning: 2025-06-19T06:38:04.702Z
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
Applied to files:
plugwise_usb/nodes/circle.py
📚 Learning: 2025-06-20T07:58:41.534Z
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:615-627
Timestamp: 2025-06-20T07:58:41.534Z
Learning: In the plugwise_usb/nodes/circle.py file, the `log_addresses_missing` property being `None` means the parameter has not been initialized yet, not that all logs are present. When `log_addresses_missing` is `None`, returning `False` from cache loading methods is correct because the system is not properly initialized and further execution would be useless.
Applied to files:
plugwise_usb/nodes/circle.py
📚 Learning: 2025-08-07T17:50:29.163Z
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#302
File: plugwise_usb/nodes/scan.py:132-142
Timestamp: 2025-08-07T17:50:29.163Z
Learning: In the plugwise_usb codebase, the `_load_defaults` method in node classes (scan.py, switch.py, sense.py) is only called once during node initialization, not during runtime. Therefore, flags like `_sed_node_info_update_task_scheduled` don't need to be reset at the start of `_load_defaults` as there's no risk of stale state from previous loads.
Applied to files:
plugwise_usb/nodes/circle.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run pytest using Python 3.13
🔇 Additional comments (2)
plugwise_usb/nodes/circle.py (2)
27-34: Consistency of “max age” constantYou’re using DAY_IN_HOURS as the recency ceiling. If the project defines a distinct MAX_LOG_HOURS elsewhere (as the PR title suggests), consider using that to keep semantics aligned. If not, DAY_IN_HOURS is fine—just confirm intent.
Would you like me to scan constants for a MAX_LOG_HOURS (or similar) and propose a single source of truth?
Also applies to: 582-599
369-371: Verified: energy_log_update usages now consistently return a single booleanAll call sites assign the result to a single variable (or schedule a task), and no tuple unpacking remains. Ready to merge.
ddaed7e to
dd65ed9
Compare
dd65ed9 to
350451a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
plugwise_usb/nodes/circle.py (1)
579-598: Previous review already noted that logging every outdated slot at WARNING may spam logs during catch-up; consider downgrading to DEBUG/INFO.
🧹 Nitpick comments (3)
plugwise_usb/nodes/circle.py (3)
468-475: Rephrase debug message for clarity“Energy None-data of outdated data …” is hard to parse; consider:
- _LOGGER.debug( - "Energy None-data of outdated data collected from log address %s, stopping collection", - log_address, - ) + _LOGGER.debug( + "All slots at log address %s are empty or outdated – stopping initial collection", + log_address, + )
511-578: Clarify return semantics in docstring
energy_log_updatenow returnsTrueonly when at least one recent, non-empty record was stored.
Update the docstring accordingly to avoid confusion for future callers.- """Request energy log statistics from node. Returns true if successful.""" + """Request energy logs and return True only when at least one recent, non-empty + record was stored; otherwise return False."""
667-676: Don’t rely on dict insertion order for cache serialization
logsis assumed “already sorted”, but dict key order is an implementation detail ofEnergyCounters. For deterministic cache output:- for address, record in logs.items(): + for address in sorted(logs.keys(), reverse=True): + record = logs[address]
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugwise_usb/nodes/circle.py(11 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:615-627
Timestamp: 2025-06-20T07:58:41.534Z
Learning: In the plugwise_usb/nodes/circle.py file, the `log_addresses_missing` property being `None` means the parameter has not been initialized yet, not that all logs are present. When `log_addresses_missing` is `None`, returning `False` from cache loading methods is correct because the system is not properly initialized and further execution would be useless.
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#302
File: plugwise_usb/nodes/scan.py:132-142
Timestamp: 2025-08-07T17:50:29.163Z
Learning: In the plugwise_usb codebase, the `_load_defaults` method in node classes (scan.py, switch.py, sense.py) is only called once during node initialization, not during runtime. Therefore, flags like `_sed_node_info_update_task_scheduled` don't need to be reset at the start of `_load_defaults` as there's no risk of stale state from previous loads.
📚 Learning: 2025-06-19T06:38:04.702Z
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
Applied to files:
plugwise_usb/nodes/circle.py
📚 Learning: 2025-06-20T07:58:41.534Z
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:615-627
Timestamp: 2025-06-20T07:58:41.534Z
Learning: In the plugwise_usb/nodes/circle.py file, the `log_addresses_missing` property being `None` means the parameter has not been initialized yet, not that all logs are present. When `log_addresses_missing` is `None`, returning `False` from cache loading methods is correct because the system is not properly initialized and further execution would be useless.
Applied to files:
plugwise_usb/nodes/circle.py
📚 Learning: 2025-08-07T17:50:29.163Z
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#302
File: plugwise_usb/nodes/scan.py:132-142
Timestamp: 2025-08-07T17:50:29.163Z
Learning: In the plugwise_usb codebase, the `_load_defaults` method in node classes (scan.py, switch.py, sense.py) is only called once during node initialization, not during runtime. Therefore, flags like `_sed_node_info_update_task_scheduled` don't need to be reset at the start of `_load_defaults` as there's no risk of stale state from previous loads.
Applied to files:
plugwise_usb/nodes/circle.py
🧬 Code Graph Analysis (1)
plugwise_usb/nodes/circle.py (5)
plugwise_usb/messages/requests.py (18)
CircleEnergyLogsRequest(1131-1165)response(124-128)send(366-375)send(388-397)send(439-441)send(477-486)send(513-522)send(543-554)send(567-576)send(589-598)send(622-631)send(668-677)send(720-729)send(776-785)send(809-818)send(849-858)send(881-890)send(913-922)plugwise_usb/nodes/helpers/counter.py (1)
add_empty_log(70-72)plugwise_usb/nodes/helpers/pulses.py (1)
add_empty_log(389-421)plugwise_usb/nodes/node.py (1)
save_cache(704-718)plugwise_usb/nodes/helpers/cache.py (1)
save_cache(40-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Process test coverage
🔇 Additional comments (6)
plugwise_usb/nodes/circle.py (6)
26-34: No issues – just an extra constant imported.
368-371: Looks fine; aligns flag with new boolean return.
382-390: Call-site update matches the new signature.
396-404: Call-site update matches the new signature.
417-426: Call-site update matches the new signature.
708-712: Prepended-record change looks good.
350451a to
1704df4
Compare
this fits better to the reverse sorting of the data in the cache
improve docstrings
1704df4 to
6776f83
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (5)
plugwise_usb/nodes/circle.py (5)
368-371: Naming may mislead: _last_energy_log_requested now stores a success booleanSince energy_log_update returns “stored any recent record”, assigning it to _last_energy_log_requested conflates “requested” with “had data”. Consider renaming to reflect semantics (e.g., _initial_energy_logs_collected) or add a clarifying comment.
- self._last_energy_log_requested = await self.energy_log_update( + # Tracks whether this initial request yielded at least one recent, non-empty record + self._last_energy_log_requested = await self.energy_log_update( self._current_log_address )
467-475: Break-on-empty/outdated now works; check wording vs count and log style
- Behavior: good. The loop now exits when only empty/outdated slots are returned.
- Minor: the debug above says “last 10 log addresses” but total_addresses is 11; verify intended count.
- Minor: consider replacing the en dash with a hyphen in logs for consistency across environments.
Would you like to adjust total_addresses or the log message for consistency?
541-549: Typo in comment: “am empty” → “an empty”Small grammar fix.
- # Don't store an old log-record, store am empty record instead + # Don't store an old log record, store an empty record instead
569-576: Avoid redundant cache saves during batched retrievalsenergy_log_update saves the cache for each address. In get_missing_energy_logs you already save once after awaiting all tasks. To reduce churn and potential interleaving, make saving optional and disable it for batch calls.
- async def energy_log_update(self, address: int | None) -> bool: + async def energy_log_update(self, address: int | None, *, save_cache: bool = True) -> bool: @@ - if any_record_stored: + if any_record_stored and save_cache: _LOGGER.debug( "Saving energy record update to cache for %s", self._mac_in_str ) await self.save_cache()Then call with save_cache=False in batched contexts:
# get_missing_energy_logs tasks = [create_task(self.energy_log_update(address, save_cache=False)) for address in missing_addresses] # _get_initial_energy_logs result = await self.energy_log_update(log_address, save_cache=False)
578-597: 24h boundary bug fixed; consider lowering log level to avoid noiseUsing a direct seconds comparison removes the floor-division bug. Consider downgrading the warning to info/debug during catch-up to avoid log spam when many outdated entries are encountered.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugwise_usb/nodes/circle.py(11 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
📚 Learning: 2025-06-19T06:38:04.702Z
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
Applied to files:
plugwise_usb/nodes/circle.py
🧬 Code Graph Analysis (1)
plugwise_usb/nodes/circle.py (4)
plugwise_usb/messages/responses.py (1)
log_address(770-772)plugwise_usb/messages/requests.py (18)
CircleEnergyLogsRequest(1131-1165)response(124-128)send(366-375)send(388-397)send(439-441)send(477-486)send(513-522)send(543-554)send(567-576)send(589-598)send(622-631)send(668-677)send(720-729)send(776-785)send(809-818)send(849-858)send(881-890)send(913-922)plugwise_usb/nodes/helpers/pulses.py (2)
add_empty_log(389-421)logs(110-124)plugwise_usb/nodes/helpers/counter.py (2)
add_empty_log(70-72)timestamp(179-187)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run pytest using Python 3.13
🔇 Additional comments (6)
plugwise_usb/nodes/circle.py (6)
27-27: Import DAY_IN_HOURS is appropriate for 24h recency logicThe constant is used correctly in _check_timestamp_is_recent.
382-404: Rollover handling now cleanly leverages boolean resultUsing the simplified boolean from energy_log_update improves readability of failure handling and the fallback to previous address.
417-425: Single-missing-address path updated correctlySwitch to the boolean return type is consistent and correct here.
516-520: Fix for logging argument order looks good“node %s address %s” now receives (mac, address) in the correct order.
708-709: Prepending new cache records aligns with reverse chronological orderThis improves cache readability and keeps recent data first.
666-669: Confirmed reverse-sorted pulse logs — no changes needed
Theget_pulse_logs()method delegates toPulseCollection.logs, which sorts both addresses and slots in descending order before building the dict, so the iteration order assumption is valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
plugwise_usb/nodes/circle.py (1)
551-558: Critical: Return value still depends on cache being enabled; decouple any_record_stored.
This repeats a previously flagged issue: _energy_log_record_update_state returns False when cache is disabled, so energy_log_update will incorrectly return False even when a valid, recent record was added to in-memory counters. This breaks initial collection and other callers that rely on True meaning “we updated something.”Apply this diff to set any_record_stored independent of cache writes:
- if await self._energy_log_record_update_state( + # Store the record; treat it as "stored" regardless of cache availability + await self._energy_log_record_update_state( response.log_address, _slot, log_timestamp.replace(tzinfo=UTC), log_pulses, import_only=True, - ): - any_record_stored = True + ) + any_record_stored = True
🧹 Nitpick comments (2)
plugwise_usb/nodes/circle.py (2)
542-549: Nit: fix comment typo and clarity (“am” -> “an”).
Minor, but it reads better and avoids confusion.- # Don't store an old log-record, store am empty record instead + # Don't store an old log-record, store an empty record instead
681-685: Ensure deterministic slot ordering when serializing cache.
Relying on insertion order can be fragile; explicitly sort slots in reverse to match the intended “already reversed” invariant.- # logs is already sorted in reverse - for address, record in logs.items(): - for slot in record: + # logs is already sorted in reverse on addresses; sort slots explicitly + for address, record in logs.items(): + for slot in sorted(record.keys(), reverse=True): log = record[slot]
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugwise_usb/nodes/circle.py(12 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
📚 Learning: 2025-06-19T06:38:04.702Z
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
Applied to files:
plugwise_usb/nodes/circle.py
🧬 Code Graph Analysis (1)
plugwise_usb/nodes/circle.py (3)
plugwise_usb/messages/requests.py (18)
CircleEnergyLogsRequest(1131-1165)response(124-128)send(366-375)send(388-397)send(439-441)send(477-486)send(513-522)send(543-554)send(567-576)send(589-598)send(622-631)send(668-677)send(720-729)send(776-785)send(809-818)send(849-858)send(881-890)send(913-922)plugwise_usb/nodes/helpers/pulses.py (3)
add_empty_log(389-421)PulseLogRecord(40-45)logs(110-124)plugwise_usb/nodes/node.py (1)
save_cache(705-719)
🪛 GitHub Actions: Latest commit
plugwise_usb/nodes/circle.py
[error] 600-600: Ruff: PLR0912 Too many branches (14 > 12). Command: 'ruff check plugwise_usb/ tests/'
[error] 641-641: F821 Undefined name 'timedelta'. Ensure 'timedelta' is imported (e.g., from datetime import timedelta). Command: 'ruff check plugwise_usb/ tests/'
[error] 647-647: F821 Undefined name 'sorted_log'. Command: 'ruff check plugwise_usb/ tests/'
[error] 648-648: F821 Undefined name 'sorted_log'. Command: 'ruff check plugwise_usb/ tests/'
[error] 649-649: F821 Undefined name 'sorted_log'. Command: 'ruff check plugwise_usb/ tests/'
🔇 Additional comments (9)
plugwise_usb/nodes/circle.py (9)
27-30: Constants import looks good; matches intent to bound log retention window.
Importing DAY_IN_HOURS and MAX_LOG_HOURS is appropriate for recency and cache pruning logic.
369-371: Startup: single-boolean result usage is correct.
Storing the boolean from energy_log_update in _last_energy_log_requested aligns with the new return semantics.
383-405: Rollover handling updated correctly to the new boolean return.
Both the current and previous addresses are retried with clear logging on failure.
418-426: Missing-address single fetch path is correct.
The new boolean contract integrates cleanly and the follow-up power_update is preserved.
468-476: Initial collection: break condition now meaningful, but beware cache-coupling downstream.
Breaking on “no recent, non-empty slots” is correct. However, this depends on energy_log_update returning True when in-memory records were stored even if cache is disabled. See my separate comment on decoupling any_record_stored from cache writes.
511-521: Return semantics and logging clarified—good.
Docstring clearly defines “success,” and the node/address logging order is fixed.
525-529: Early return on missing response is correct.
Aligns with the new single-boolean contract and avoids ambiguous “success but nothing stored.”
530-531: Extra debug line is helpful for tracing.
Logging the source of EnergyLogs with node and address aids diagnostics.
571-577: Cache save trigger on actual updates—good.
Only saving on any_record_stored avoids unnecessary writes.
989cfca to
c89456e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🔭 Outside diff range comments (1)
plugwise_usb/nodes/circle.py (1)
599-654: Cache loading logic needs consistency with MAX_LOG_HOURSThe cache loading and pruning logic uses
DAY_IN_HOURSfor the skip_before threshold. For consistency with the PR objective and the rest of the codebase, this should useMAX_LOG_HOURS.# Sort and prune the records loaded from cache sorted_logs: dict[int, dict[int, tuple[int, datetime]]] = {} - skip_before = datetime.now(tz=UTC) - timedelta(hours=DAY_IN_HOURS) + skip_before = datetime.now(tz=UTC) - timedelta(hours=MAX_LOG_HOURS) sorted_addresses = sorted(restored_logs.keys(), reverse=True)
♻️ Duplicate comments (1)
plugwise_usb/nodes/circle.py (1)
578-597: Recency check should use MAX_LOG_HOURS, not DAY_IN_HOURSPer the PR objective of handling logs outside
MAX_LOG_HOURS, the recency check should useMAX_LOG_HOURSinstead of the hardcodedDAY_IN_HOURS(24h). This ensures consistency with the pruning logic used elsewhere in the codebase.def _check_timestamp_is_recent( self, address: int, slot: int, timestamp: datetime ) -> bool: """Check if the timestamp of the received log-record is recent. - A timestamp from within the last 24 hours is considered recent. + A timestamp newer than MAX_LOG_HOURS is considered recent. """ age_seconds = ( datetime.now(tz=UTC) - timestamp.replace(tzinfo=UTC) ).total_seconds() - if age_seconds > DAY_IN_HOURS * 3600: + if age_seconds > MAX_LOG_HOURS * 3600: _LOGGER.warning( "EnergyLog from Node %s | address %s | slot %s | timestamp %s is outdated, ignoring...", self._mac_in_str, address, slot, timestamp, ) return False return True
🧹 Nitpick comments (1)
plugwise_usb/nodes/circle.py (1)
632-632: Remove debug log statementThe debug statement "HOI restored_logs=%s" appears to be a temporary debugging aid and should be removed.
- _LOGGER.debug("HOI restored_logs=%s", restored_logs)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugwise_usb/nodes/circle.py(13 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
📚 Learning: 2025-06-19T06:38:04.702Z
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
Applied to files:
plugwise_usb/nodes/circle.py
🧬 Code Graph Analysis (1)
plugwise_usb/nodes/circle.py (4)
plugwise_usb/messages/responses.py (1)
log_address(770-772)plugwise_usb/messages/requests.py (18)
CircleEnergyLogsRequest(1131-1165)response(124-128)send(366-375)send(388-397)send(439-441)send(477-486)send(513-522)send(543-554)send(567-576)send(589-598)send(622-631)send(668-677)send(720-729)send(776-785)send(809-818)send(849-858)send(881-890)send(913-922)plugwise_usb/nodes/helpers/counter.py (3)
add_empty_log(70-72)timestamp(179-187)add_pulse_log(74-89)plugwise_usb/nodes/helpers/pulses.py (2)
add_empty_log(389-421)logs(110-124)
🔇 Additional comments (3)
plugwise_usb/nodes/circle.py (3)
510-576: Return semantics are correct and match PR objectivesThe refactored
energy_log_updatemethod correctly returnsTrueonly when at least one recent, non-empty record was stored. The recency check using_check_timestamp_is_recentensures outdated logs are filtered out as per PR objectives. The logging has been improved for clarity.
455-477: Simplified initial collection logic is cleanerThe refactored
_get_initial_energy_logsmethod is much simpler and correctly relies on the boolean return fromenergy_log_updateto determine when to stop collection. This aligns well with the PR objectives.
726-726: Cache record prepending maintains chronological orderThe change to prepend new records (
log_cache_record + "|" + cached_logs) ensures that newer records appear first in the cache, maintaining a reverse chronological order that aligns with the sorting strategy used throughout the code.
800f307 to
ada08b1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
plugwise_usb/nodes/circle.py (4)
27-34: Use MAX_LOG_HOURS for recency window (aligns with configuration and EnergyCounters)
The PR objective targets handling logs outside MAX_LOG_HOURS. Using DAY_IN_HOURS hard-codes 24h and diverges from configured behavior and the pulses logs pruning (which uses MAX_LOG_HOURS). Align both to avoid inconsistencies.Apply this diff:
from ..constants import ( - DAY_IN_HOURS, + DAY_IN_HOURS, + MAX_LOG_HOURS, @@ - A timestamp from within the last 24 hours is considered recent. + A timestamp newer than MAX_LOG_HOURS is considered recent. @@ - if age_seconds > DAY_IN_HOURS * 3600: + if age_seconds > MAX_LOG_HOURS * 3600:Optional: consider downgrading the log level to info/debug to reduce noise during normal catch-up scenarios.
Also applies to: 583-589
467-474: Initial collection loop will terminate prematurely when cache is disabled (depends on fix above)
Because energy_log_update currently ties its return value to cache writes, this loop breaks on valid-but-uncached records. After decoupling any_record_stored from caching (see earlier comment), this loop will behave as intended.
550-557: Decouple return value from cache state to avoid false negatives when cache is disabledany_record_stored is only set when _energy_log_record_update_state returns True, but that returns False when caching is disabled. This makes energy_log_update return False even when valid records were added to in-memory counters, breaking initial collection and rollover handling.
Apply this diff to set any_record_stored based on processing of recent, non-empty slots, regardless of cache writes, and avoid unnecessary save attempts when cache is disabled:
- if await self._energy_log_record_update_state( + await self._energy_log_record_update_state( response.log_address, _slot, log_timestamp.replace(tzinfo=UTC), log_pulses, import_only=True, - ): - any_record_stored = True + ) + any_record_stored = True if not last_energy_timestamp_collected: # Collect the timestamp of the most recent response self._last_collected_energy_timestamp = log_timestamp.replace( tzinfo=UTC ) @@ - if any_record_stored: + if any_record_stored and self._cache_enabled: _LOGGER.debug( "Saving energy record update to cache for %s", self._mac_in_str ) await self.save_cache()Also applies to: 570-575
634-645: Prune loaded cache using MAX_LOG_HOURS instead of DAY_IN_HOURS
Keep the same recency window everywhere to prevent reloading/pruning inconsistencies versus EnergyCounters.get_pulse_logs().- skip_before = datetime.now(tz=UTC) - timedelta(hours=DAY_IN_HOURS) + skip_before = datetime.now(tz=UTC) - timedelta(hours=MAX_LOG_HOURS)
🧹 Nitpick comments (2)
plugwise_usb/nodes/circle.py (2)
632-633: Remove stray debug log (“HOI restored_logs”)
This looks like a leftover dev log and is noisy.- _LOGGER.debug("HOI restored_logs=%s", restored_logs)
541-547: Nit: fix typo in comment
Minor grammar nit in the inline comment.- # Don't store an old log-record, store am empty record instead + # Don't store an old log-record; store an empty record instead
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
plugwise_usb/nodes/circle.py(13 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
📚 Learning: 2025-06-19T06:38:04.702Z
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
Applied to files:
plugwise_usb/nodes/circle.py
🧬 Code Graph Analysis (1)
plugwise_usb/nodes/circle.py (5)
plugwise_usb/messages/responses.py (1)
log_address(770-772)plugwise_usb/messages/requests.py (18)
CircleEnergyLogsRequest(1131-1165)response(124-128)send(366-375)send(388-397)send(439-441)send(477-486)send(513-522)send(543-554)send(567-576)send(589-598)send(622-631)send(668-677)send(720-729)send(776-785)send(809-818)send(849-858)send(881-890)send(913-922)plugwise_usb/nodes/helpers/pulses.py (2)
add_empty_log(389-421)logs(110-124)plugwise_usb/nodes/helpers/counter.py (3)
add_empty_log(70-72)timestamp(179-187)add_pulse_log(74-89)plugwise_usb/nodes/node.py (2)
_get_cache(652-656)name(218-222)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run pytest using Python 3.13
🔇 Additional comments (2)
plugwise_usb/nodes/circle.py (2)
517-520: Logging placeholder order fixed — correct mapping of node and addressArgs now match the message placeholders: node MAC first, then address. Good catch.
680-687: LGTM: cache serialization uses already-pruned/sorted logs
Using EnergyCounters.get_pulse_logs(), which returns a pruned, reverse-sorted view, is the right approach and keeps cache compact.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
pyproject.toml(1 hunks)scripts/tests_and_coverage.sh(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- pyproject.toml
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: bouwew
PR: plugwise/python-plugwise-usb#255
File: plugwise_usb/nodes/circle.py:477-482
Timestamp: 2025-06-19T06:38:04.702Z
Learning: In plugwise_usb/nodes/circle.py, the timestamp comparison `prev_address_timestamp - self._last_collected_energy_timestamp` in the `get_missing_energy_logs` method is intentional. The code iterates backwards through time-ordered energy log addresses, where `prev_address_timestamp` contains the more recent timestamp from the previous iteration, and this subtraction correctly calculates the time gap to determine when to stop collecting outdated data.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Ruff check and force
scripts/tests_and_coverage.sh
Outdated
| # PYTHONPATH=$(pwd) pytest -qx tests/ --cov='.' --no-cov-on-fail --cov-report term-missing || | ||
| PYTHONPATH=$(pwd) pytest -xrpP --log-level debug tests/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Retaining optional coverage and CI-friendly behavior (avoid -x in CI).
You removed coverage collection and now abort on first failure unconditionally. If CI or release gating depends on coverage (Codecov/Coveralls/thresholds), this is a regression; and -x reduces failure signal in CI. Suggest toggling coverage and -x via env or arg.
Apply this diff to restore coverage optionally and keep fast local runs:
- # PYTHONPATH=$(pwd) pytest -qx tests/ --cov='.' --no-cov-on-fail --cov-report term-missing ||
- PYTHONPATH=$(pwd) pytest -xrpP --log-level debug tests/
+ if [ "${COVERAGE:-0}" = "1" ]; then
+ # CI/coverage runs: collect coverage, run full suite (no -x)
+ PYTHONPATH=$(pwd) pytest -q tests/ --cov='.' --no-cov-on-fail --cov-report=term-missing
+ else
+ # Local fast feedback: stop on first failure with detailed reporting
+ PYTHONPATH=$(pwd) pytest -xrpP --log-level debug tests/
+ fiTo verify whether coverage is still required by your pipelines, run:
🏁 Script executed:
#!/bin/bash
# Check for coverage dependencies/config or usage in CI
set -euo pipefail
echo "Searching for coverage configs and usage..."
fd -H -a -t f '.coveragerc' || true
rg -n -A2 -S --hidden -e 'tool\.coverage|--cov|coverage xml|coverage:|codecov|coveralls' || true
echo "Inspecting GitHub Actions workflows for coverage steps..."
fd -H -a -t f '.github/workflows' | xargs -I{} rg -n -A2 -S 'coverage|codecov|coveralls' {} || true
echo "Searching pyproject.toml/tox.ini for coverage config..."
fd -a -t f 'pyproject.toml' 'tox.ini' | xargs -I{} rg -n -A2 -S 'coverage|--cov' {} || trueLength of output: 2947
Restore optional coverage runs and CI-friendly behavior
We’ve confirmed that coverage is required by CI (see pyproject.toml’s [tool.coverage.run] section and the verify.yml workflow steps running pytest with --cov and uploading to Codecov). Removing coverage collection and unconditionally using -x will break CI metrics and coverage gating.
Please update scripts/tests_and_coverage.sh (around lines 60–66) as follows:
File: scripts/tests_and_coverage.sh lines 60–66
- # PYTHONPATH=$(pwd) pytest -qx tests/ --cov='.' --no-cov-on-fail --cov-report term-missing ||
- PYTHONPATH=$(pwd) pytest -xrpP --log-level debug tests/
+ if [ "${COVERAGE:-0}" = "1" ]; then
+ # CI/coverage runs: collect coverage, no fast-fail
+ PYTHONPATH=$(pwd) pytest -q tests/ \
+ --cov='.' --no-cov-on-fail --cov-report=term-missing
+ else
+ # Local fast feedback: stop on first failure with debug logging
+ PYTHONPATH=$(pwd) pytest -xrpP --log-level debug tests/
+ fiThis preserves coverage collection for CI and retains fast local feedback.
🤖 Prompt for AI Agents
In scripts/tests_and_coverage.sh around lines 60 to 66, the current change
removed coverage collection and forces -x unconditionally which will break CI
coverage gating; restore CI-friendly behavior by running pytest with coverage
when in CI (detect via CI or GITHUB_ACTIONS env var) using PYTHONPATH=$(pwd)
pytest -qx tests/ --cov='.' --no-cov-on-fail --cov-report term-missing (so CI
metrics/upload work), and for local dev run the faster command PYTHONPATH=$(pwd)
pytest -xrpP --log-level debug tests/; ensure the script chooses the coverage
command when CI is set and preserves exit codes so failures fail the CI job.
ce3b615 to
9285048
Compare
|
|
Split in #311 and a 2nd future PR. |



Summary by CodeRabbit