Skip to content

chore(ctw3): add diagnostics to pinpoint detect_status offset (Refs #65)#68

Merged
aavdberg merged 2 commits intodevfrom
chore/ctw3-detect-status-diagnostics
May 1, 2026
Merged

chore(ctw3): add diagnostics to pinpoint detect_status offset (Refs #65)#68
aavdberg merged 2 commits intodevfrom
chore/ctw3-detect-status-diagnostics

Conversation

@aavdberg
Copy link
Copy Markdown
Owner

@aavdberg aavdberg commented May 1, 2026

Refs #65.

Why

The drink-events sensor never increments. Our CMD 210 parser reads detect_status from byte 19 of the CTW3 30-byte payload, but in every captured frame across four debug logs (15+ samples) byte 19 is always 0x00 — including the ground-truth log captured by @aavdberg where his cat was actually drinking and HA UX showed Pet drinks = detected for a few seconds before flipping back to clear.

Looking at the trailing four bytes (26..29) of the CTW3 30-byte frame — currently not parsed at all — there is a strong outlier in the post-drink frame:

Time Idle? bytes 26..29
09:08:58 idle 08 07 23 07
09:10:07 idle 08 07 07 07
09:11:16 idle 08 07 ff 06
09:12:26 idle 08 07 1f 07
09:59:06 idle 19 07 37 07
12:14:57 post-drink c5 06 be 06

Byte 26 is a strong candidate, but a single sample is not enough to risk patching the parser — the same code path runs on W4/W5/CTW2, and an incorrect offset would silently break those devices.

What this PR does (instrumentation only)

  • Adds raw_state and state_tail fields to PetkitFountainData (populated by both CTW3 and generic parsers).
  • Adds a coordinator-level DEBUG log that diffs consecutive CMD 210 raw payloads, skipping the always-changing uptime/tick bytes 9..18 so semantic byte changes (such as pet-detection events) stand out at a glance.
  • Adds a hidden DIAGNOSTIC sensor sensor.<device>_state_tail_hex exposing bytes 26..29 of CTW3 30-byte frames so users can graph their behaviour in HA history without grepping logs.

No parser-offset change yet — we ship the diagnostics first, ask for a fresh log captured with debug enabled before the next observed drink event, and patch in a follow-up PR once the responsible byte is unambiguous.

How to validate (for the user once a pre-release is published)

  1. Update to the next dev pre-release.
  2. Enable debug logging for custom_components.petkit_ble in HA configuration.
  3. Wait for a drink event you can observe in the UI.
  4. Capture the log + note the timestamp the UI flipped to detected, attach to issue CTW3: 'Drink events today' counter never increments #65.

Test plan

  • ruff check and ruff format --check — clean.
  • pytest tests/ — 89 passed (4 new tests for state_tail parsing and the diff helper, including a regression test using the real captured frames).

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>
Copilot AI review requested due to automatic review settings May 1, 2026 10:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds CTW3-focused diagnostics to help pinpoint the correct detect_status byte offset by preserving/logging raw CMD 210 state payload changes and exposing the unparsed CTW3 tail bytes as a hidden diagnostic sensor.

Changes:

  • Capture raw_state (all models) and CTW3 state_tail (bytes 26–29 when present) in PetkitFountainData.
  • Add a DEBUG-only coordinator log that diffs consecutive CMD 210 payloads while suppressing noisy indices 9–18.
  • Add a hidden diagnostic sensor for the CTW3 state tail hex string, plus unit tests for the diff helper and tail parsing.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
custom_components/petkit_ble/ble_client.py Persist raw CMD 210 payloads and CTW3 tail bytes into the data model during parsing.
custom_components/petkit_ble/coordinator.py Add byte-diff helper + DEBUG log of changed CMD 210 bytes between polls.
custom_components/petkit_ble/sensor.py Add a hidden diagnostic sensor exposing CTW3 state_tail as hex.
custom_components/petkit_ble/translations/en.json Add entity translation for the new state_tail_hex sensor.
custom_components/petkit_ble/translations/nl.json Add entity translation for the new state_tail_hex sensor.
custom_components/petkit_ble/translations/uk.json Add entity translation for the new state_tail_hex sensor.
custom_components/petkit_ble/manifest.json Bump integration version to 1.1.13.
tests/test_state_diff.py New tests for the CMD 210 byte-diff diagnostic helper.
tests/test_data_model.py Tests covering raw_state + state_tail capture for CTW3 and non-CTW3 parsers.

Comment thread custom_components/petkit_ble/coordinator.py Outdated
Comment thread custom_components/petkit_ble/coordinator.py Outdated
…de 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>
@aavdberg aavdberg merged commit 4e55d8a into dev May 1, 2026
3 checks passed
@aavdberg aavdberg deleted the chore/ctw3-detect-status-diagnostics branch May 1, 2026 10:41
aavdberg added a commit that referenced this pull request May 1, 2026
…#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants