Conversation
* chore(docs): require minor version bump on dev->main release PRs Codify the rule that every dev->main promotion bumps manifest.json to the next minor version (e.g. 1.1.8 -> 1.2.0, 1.2.2 -> 1.3.0). Patch increments stay on dev for the pre-release/HACS beta channel; minor increments mark stable releases. Major bumps remain reserved for breaking changes and require explicit user approval. Updates both .github/copilot-instructions.md (full Releasing section) and .github/instructions/petkit-ble.instructions.md (one-line summary). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: address Copilot review on #62 - Reword the version-bump rule to make sequencing unambiguous: bump on dev (via a chore/release-* PR) BEFORE opening the dev->main release PR. - Drop the release/vX.Y.0 branch suggestion; reuse the documented chore/* prefix instead, so guidance matches the Branching Strategy. - Split the long petkit-ble.instructions.md bullet into a short quick-reference list that defers to the full release section in .github/copilot-instructions.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ctw3): keep LED switch on screen when CMD 211 never replies (#63) Some CTW3 firmware revisions (observed on fw 111) never reply to CMD 211 (read settings). Without that read-back, `data.led_switch`, `led_brightness`, `smart_time_on/off`, `battery_work/sleep_time`, `do_not_disturb_switch` and `is_locked` stay at their dataclass defaults, which causes two bugs: * Clicking the LED switch flips back to off immediately because the coordinator refresh shows the stale cached zero. * Every CMD 221 write also blasts the unread fields as zero, wiping the user's smart-cycle / battery / DND / child-lock settings on the device. Apply two complementary mitigations: * Optimistically update `coordinator.data.<field>` right after a successful CMD 221 in switch / number / time platforms, before the refresh, so the UI stays consistent and the next write preserves the freshly chosen value. * Add a `config_loaded` flag on `PetkitFountainData` set by both `_parse_config_ctw3` and `_parse_config_generic`. When `build_full_settings_payload` runs against data where the flag is still False, log a one-shot WARNING explaining the situation. Bumps manifest 1.1.9 -> 1.1.10 (dev pre-release). Closes #63 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(coordinator): cache settings across polls so optimistic writes survive The previous optimistic-update fix mutated the PetkitFountainData instance that async_poll() discards on the next refresh, so the LED switch still flipped back. Move the persistence to the coordinator level: - New `_settings_cache` dict on the coordinator stores the last known value of every CMD 221-writable field. - `_reconcile_settings` (extracted to free helper `_reconcile_settings_into` for testability) runs after every async_poll: if CMD 211 succeeded the cache is refreshed from the new data; if it failed the cache is copied onto the new data so values do not regress to defaults. - New public `apply_setting_optimistic(field, value)` method updates the cache, the live data, and emits async_set_updated_data so the UI does not wait on a refresh round-trip. - Switch / number / time platforms call apply_setting_optimistic instead of the old setattr-on-soon-discarded-data dance. - One-shot WARNING moved from protocol.py (which keyed on id(data) and leaked) to the coordinator so it tracks once-per-entry correctly. Tests now exercise the merge logic directly; verified all 68 pass. Closes the Copilot review on PR #64. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ght (#66) * fix(ctw3): persist daily drink-event counter and reset at local midnight The 'Drink events today' counter was a coordinator-instance variable that reset to zero on every Home Assistant restart or integration reload. It also never reset at local midnight, despite the 'today' / 'vandaag' label, making it effectively a lifetime counter that frequently appeared stuck at zero. - Move state into a small _DrinkCountState dataclass plus free helpers (_load_drink_state_into / _track_drink_event_into) so the logic is testable without instantiating the full DataUpdateCoordinator chain. - Persist {count, date} via homeassistant.helpers.storage.Store keyed by MAC. Loaded once before the first refresh; written on every increment. - Drop the persisted count when the stored date is not today. - Reset to 0 when the local date rolls over during a poll. - Storage failures are swallowed at debug level so a corrupt or unwritable store cannot block setup or break polling. Refs #65 (does not close: the underlying detect_status reliability question still requires a ground-truth log captured during a confirmed drinking event). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(ctw3): address Copilot review on drink-count persistence - Use HA-aware dt_util.now().date() instead of date.today() so the daily reset honours the configured Home Assistant timezone, not the host OS timezone. - Switch the Store key from the raw MAC address (which contains colons, invalid in filenames on Windows) to config_entry.entry_id. - Discard a negative count loaded from a corrupt store before assigning it to state.count. - Capture today_iso once per test via a fixture so assertions are stable across midnight. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CTW3 firmware 111 never replies to CMD 211 (read settings). The settings cache added in #64 already keeps user-set values intact across polls, but the BLE client still emitted 'Timeout waiting for response to CMD 211' at WARNING level on every 60s poll, flooding the log. Add a 'quiet' kwarg to _send_and_wait that demotes its timeout log to DEBUG, and use it for CMD 211 when the device alias is CTW3. The coordinator still surfaces a single user-visible WARNING via _reconcile_settings_into the first time the cache is empty, so a genuine problem is not hidden. Other devices (W5, CTW2, W4X) keep the existing WARNING behaviour because there CMD 211 is expected to reply. Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…) (#68) * chore(ctw3): add diagnostics to pinpoint detect_status offset Issue #65: drink-events sensor never increments because the parser reads detect_status from CMD 210 byte 19, which appears to always be 0 on CTW3 firmware 111. The ground-truth log captured by the user (cat Milka drinking; HA UX showed 'detected' then back to 'clear') contains a single post-drink frame whose byte 26 jumps to 0xc5 vs 0x08 in idle frames — strongly suggesting the real detect signal lives in the currently-unparsed bytes 26..29. To gather conclusive evidence without risking a regression on W4/W5/CTW2, this commit ships diagnostic instrumentation only: * New raw_state and state_tail fields on PetkitFountainData populated by both CTW3 and generic CMD 210 parsers. * Coordinator logs a DEBUG-level byte-by-byte diff between consecutive CMD 210 polls, skipping the always-changing uptime tick at bytes 9..18 so semantically meaningful changes (e.g. pet-detection events) stand out. * New hidden DIAGNOSTIC sensor sensor.<device>_state_tail_hex exposes bytes 26..29 of CTW3 30-byte frames so users can graph their behaviour in HA history without grepping logs. Refs #65 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * review: handle length-mismatched payloads and skip noise filter outside CTW3 Address Copilot review comments on PR #68: * _diff_state_bytes now reports appended/truncated bytes against 0x00 for both growing and shrinking payloads, matching the behaviour the docstring promised. Two new unit tests pin the contract. * Coordinator only applies the CTW3 uptime-noise filter (bytes 9..18) for CTW3 devices. On 12-byte W4/W5/CTW2 payloads those indices carry pump_runtime tail / filter_percent / running_status, so suppressing them would have made the diagnostic misleading. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…#65) (#69) * fix(ctw3): normalize detect_status so counter sees 0->1 edge (Closes #65) CTW3 firmware 111 emits `0x02` on byte 19 of the CMD 210 state payload when the proximity sensor detects a pet, not `0x01` as previously assumed. The parser stored the raw byte unchanged, so: - `binary_sensor.pet_drinks` (uses `bool(detect_status)`) toggled correctly — value `2` is truthy. - `sensor.drink_events_today` requires a strict `0 -> 1` edge in the coordinator and therefore never incremented. Captured ground-truth (Logs/home-assistant_petkit_ble_2026-05-01T12-01-29.952Z.log): 13:59:13 byte[19]=0x00->0x02 <- cat starts drinking 14:00:23 byte[19]=0x02->0x00 <- cat leaves Fix is one line in `_parse_state_ctw3`: normalise any non-zero byte 19 to `1`. Future-proofs the field against further bit patterns (0x03/0x04 etc.). Tests: - New parser test exercising the captured detect=2 frame. - New parser test for the cleared (byte19=0x00) frame. - New end-to-end test feeding three CTW3 30-byte payloads through parser + `_track_drink_event_into` and asserting the count goes from 0 to 1. Diagnostics shipped in #68 (state_tail_hex sensor + byte-diff log) are left in place as a reusable debugging aid for future offset investigations. Refs #65, follows up #68. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(ctw3): use a real pre-detection idle frame in counter regression test Replaces the synthesised IDLE_BEFORE (detected frame with byte 19 zeroed) with the actual frame captured at 13:58:00 in the ground-truth log, so the test faithfully matches the PR description's claim of three real frames. Addresses Copilot review on PR #69. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The CTW3 CMD 221 (write settings) payload had led_switch / led_brightness / dnd_enabled in the wrong positions. Real-device captures (debug log 2026-05-03 18:52:13-48, user actions: LED on -> brightness changes -> LED off) show the correct layout is: [smart_work, smart_sleep, batt_w_hi, batt_w_lo, batt_s_hi, batt_s_lo, dnd_enabled, led_switch, led_brightness, child_lock] Previously led_switch was always sent at index 6 (wrong), so user clicks on the LED switch had inconsistent effects. Brightness slider values 1/5/8/9 showed up at index 8 where the integration thought it was writing dnd_enabled. _parse_config_ctw3 was kept symmetric (currently dead code on CTW3 fw 111 which never replies to CMD 211, but ready for future firmware). Adds: - Builder regression test covering the 5 captured payloads. - Parser round-trip test using the same sequence. - Updated existing parser tests to the corrected layout. Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per repo convention, dev->main merges bump the minor version. Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR promotes the recent CTW3 fixes from dev to main as a stable v1.2.0 release, focusing on correct CTW3 settings byte layout, drink-counter correctness/persistence, and better diagnostics around CTW3 state parsing.
Changes:
- Bump integration version to
1.2.0for the stable release. - Fix/solidify CTW3 behavior: CMD 221 LED field offsets, detect-status normalization + persisted daily drink counter, and CMD 211 timeout log noise reduction with settings caching.
- Add CTW3 diagnostics (raw-state tail + byte-diff logging) and corresponding tests/translations.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| custom_components/petkit_ble/manifest.json | Version bump to 1.2.0 for the stable release. |
| custom_components/petkit_ble/protocol.py | Correct CTW3 CMD 221 settings payload field order + document reverse-engineering evidence. |
| custom_components/petkit_ble/ble_client.py | Add raw_state/state_tail, normalize CTW3 detect_status, quiet CMD 211 timeouts for CTW3, align CTW3 config parsing with new layout. |
| custom_components/petkit_ble/coordinator.py | Persist daily drink counter via Store, add settings cache reconciliation, add CMD 210 byte-diff debug logging helper. |
| custom_components/petkit_ble/init.py | Load persisted drink-counter state before first refresh. |
| custom_components/petkit_ble/switch.py | Apply optimistic settings cache updates after successful CMD 221 writes. |
| custom_components/petkit_ble/number.py | Apply optimistic cache updates; avoid repeated int(value) conversion. |
| custom_components/petkit_ble/time.py | Apply optimistic cache updates after successful CMD 221 writes. |
| custom_components/petkit_ble/sensor.py | Add hidden diagnostic sensor for CTW3 state_tail_hex. |
| custom_components/petkit_ble/translations/en.json | Add translation for state_tail_hex. |
| custom_components/petkit_ble/translations/nl.json | Add translation for state_tail_hex. |
| custom_components/petkit_ble/translations/uk.json | Add translation for state_tail_hex. |
| tests/conftest.py | Extend HA stubs and add dt_util.now() behavior for date-dependent tests. |
| tests/test_protocol.py | Add/adjust CTW3 settings builder/parser regression tests for corrected byte layout. |
| tests/test_data_model.py | Add tests for CTW3 state_tail capture and detect_status normalization. |
| tests/test_state_diff.py | Add unit tests for CMD 210 byte-diff diagnostic helper. |
| tests/test_settings_optimistic.py | Add tests for settings cache reconciliation and config_loaded behavior. |
| tests/test_drink_count.py | Add tests for drink-counter edge detection, daily rollover, and persistence. |
| .github/copilot-instructions.md | Document release process for dev → main promotions (minor version bump + merge strategy). |
| .github/instructions/petkit-ble.instructions.md | Mirror release guidance for repository instructions. |
Comment on lines
+53
to
+54
| _dt_module = sys.modules["homeassistant.util.dt"] | ||
| _dt_module.now = lambda: _datetime.datetime.now() |
Comment on lines
+134
to
+135
| # device. protocol.build_full_settings_payload logs a one-shot warning | ||
| # in that case. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release v1.2.0
Promotes recent CTW3 fixes from dev to main. Per repo convention this is a minor bump (v1.1.9 -> v1.2.0).
Changes since v1.1.9
Highlights
Verification
After merging, the release workflow will tag v1.2.0 and publish a GitHub Release automatically.