Skip to content

fix: skip cached state override when bot ignores command (#213)#502

Draft
bluetoothbot wants to merge 2 commits into
sblibs:mainfrom
bluetoothbot:koan/bot-skip-override-on-failed-press
Draft

fix: skip cached state override when bot ignores command (#213)#502
bluetoothbot wants to merge 2 commits into
sblibs:mainfrom
bluetoothbot:koan/bot-skip-override-on-failed-press

Conversation

@bluetoothbot

@bluetoothbot bluetoothbot commented May 19, 2026

Copy link
Copy Markdown
Collaborator

What

When Switchbot.turn_on/turn_off get a "command ignored" reply (0x03 0xff 0x00, typically on back-to-back presses), don't flip the cached isOn state. Only override the state when _check_command_result returns True.

Why

Closes the latent half of #213. The bot's own protocol distinguishes acceptance (0x01 0x48 0x90) from rejection (0x03 0xff 0x00) and _check_command_result(result, 0, {1, 5}) already returns False for the rejection case — but the next line ran self._override_state({"isOn": True}) unconditionally, so HA's cached state flipped to on even when the device never actuated.

That makes the back-to-back press behaviour observable in the issue: log shows Turn on result: 03ff00 -> {'isOn': True} for the second press, and HA happily reports the bot as on despite no physical actuation. The 8.5s disconnect timer + a brief delay between commands is still the user-side workaround; this PR just stops the library from lying about state when the command is rejected.

How

Two-line gate in bot.py:

ret = self._check_command_result(result, 0, {1, 5})
if ret:
    self._override_state({"isOn": True})  # or False, for turn_off

Behaviour for the success path (notification byte 0x01 or 0x05) is unchanged.

Testing

  • New tests/test_bot.py covers turn_on/turn_off for both accepted and rejected results, plus an inverse-mode regression. (No existing test file for the Switchbot bot device class.)
  • Full suite: 1218 passed.

🤖 Generated with Claude Code


Quality Report

Changes: 2 files changed, 111 insertions(+), 2 deletions(-)

Code scan: clean

Tests: passed (1218 passed)

Branch hygiene: clean

Generated by Kōan post-mission quality pipeline

@codecov

codecov Bot commented May 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
switchbot/devices/bot.py 74.62% <100.00%> (+30.01%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes cached state handling for SwitchBot Bot (WoHand) commands by avoiding isOn cache overrides when the device replies that a command was ignored (e.g. 03ff00). This aligns the library’s cached state with the device’s actual acceptance/rejection semantics and prevents downstream consumers (e.g., Home Assistant) from reporting an incorrect state.

Changes:

  • Gate self._override_state({"isOn": ...}) in turn_on/turn_off so it only runs when _check_command_result(...) indicates success.
  • Add a new tests/test_bot.py suite covering accepted vs rejected results for turn_on/turn_off, plus an inverse-mode regression check.

Reviewed changes

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

File Description
switchbot/devices/bot.py Prevents cached isOn from being flipped when the device rejects/ignores the command.
tests/test_bot.py Adds regression tests to ensure state overrides only occur on accepted command results (including inverse mode).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

bluetoothbot added a commit to bluetoothbot/pySwitchbot that referenced this pull request Jun 20, 2026
Extends the bot fix from sblibs#502 to SwitchbotPlugMini and SwitchbotHumidifier:
both `turn_on`/`turn_off` (and humidifier's `set_level`) ran
`_override_state` unconditionally, even when `_check_command_result`
returned False. A rejected/garbled command response would still flip
the cached state, making HA report a state the device never reached.

Gate `_override_state` and `_fire_callbacks` on the result, matching the
original intent of the existing `_check_command_result` calls.

Tests added: tests/test_plug.py (4 cases), tests/test_humidifier.py (6
cases) covering accepted/rejected paths for turn_on, turn_off, and
set_level.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bluetoothbot

Copy link
Copy Markdown
Collaborator Author

PR Review — fix: skip cached state override when bot ignores command (#213)

Clean, minimal, well-tested fix — merge-ready.

The two-line gate correctly addresses the latent half of #213. Verified against the codebase: _check_command_result returns result[0] in {1,5}, so the ignored reply 0x03 0xff 0x00 yields False (length 3, no raise), and gating _override_state on ret stops the cache from flipping on a rejected press. _get_adv_value reads the override dict first, so is_on() correctly reflects (or preserves) state.

Strengths:

  • Surgical change — only the unconditional override is gated; success path (notification byte 0x01/0x05), logging, callbacks, and return value are untouched.
  • New tests/test_bot.py fills a real coverage gap (no prior test file for the bot class) and exercises accepted/rejected for both turn_on/turn_off plus an inverse-mode regression; codecov shows bot.py +30%.
  • Tests assert observable behavior (return value + is_on()), mock at the right level (_send_command/_check_command_result/update), and reuse the existing generate_ble_device helper.

No blocking issues found.

  • Correctness: confirmed via Read/Grep of device.py (_check_command_result, _override_state, _get_adv_value, update_after_operation).
  • _fire_callbacks() still fires on rejection, but that was already the case pre-change and is harmless (listeners re-read unchanged cached state) — no regression.
  • Scope matches the PR description; no scope creep.


Checklist

  • Logic correct for accepted/rejected/inverse paths
  • Untested branches covered
  • Tests verify behavior, not source text
  • No backward-incompatible API change
  • Diff matches PR description

Automated review by Kōan (Claude) HEAD=a02c6e0 1 min 1s

bluetoothbot and others added 2 commits June 20, 2026 15:00
When the bot replies with 0x03 0xff 0x00 ("command ignored", typically on
back-to-back presses), `Switchbot.turn_on`/`turn_off` still called
`_override_state` unconditionally, so Home Assistant's cached state
flipped even though the device never actuated. Gate the override on the
result of `_check_command_result` so a rejected command leaves the
cached state alone.

Adds tests covering accepted (`0x01`/`0x05`) and rejected return values
for both turn_on and turn_off, plus an inverse-mode regression check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bluetoothbot

Copy link
Copy Markdown
Collaborator Author

Simple rebase

Branch koan/bot-skip-override-on-failed-press was rebased onto master — no additional changes were needed.

Stats

2 files changed, 114 insertions(+), 2 deletions(-)
Actions performed
  • Already-solved check: negative (confidence=high, reasoning=No commit on master touches bot.py turn_on/turn_off state-override gating; the Back to Back commands not executing on WoHand #213 rejection-handli)
  • Rebased koan/bot-skip-override-on-failed-press onto upstream/master
  • Pre-push CI check: previous run passed
  • Force-pushed koan/bot-skip-override-on-failed-press to origin
  • CI check enqueued in ## CI (async)

CI status

CI will be checked asynchronously.


Automated by Kōan

@bluetoothbot bluetoothbot force-pushed the koan/bot-skip-override-on-failed-press branch from a02c6e0 to 9710999 Compare June 20, 2026 15:00
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