add roller mode for switch2pm#457
Conversation
… test cases [by wanzi]
Codecov Report❌ Patch coverage is
🚀 New features to boost your workflow:
|
…ion missing channel arg [by wanzi]
Small position values (< 16) produced odd-length hex strings causing fromhex() ValueError. Format position as 02X to ensure even hex digits. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… mode - Format position as 2-digit hex (02X) to prevent odd-length hex string causing fromhex() ValueError on small position values - Add _update_motion_direction override with HA semantics (new > prev = opening) - Convert device positions (0=open, 100=closed) to HA positions before direction comparison in set_position - Fix test expectations to match device/HA semantic mapping Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…h2PM The method was defined twice; the first definition was dead code that caused 8 lines of missing coverage reported by codecov. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds roller/cover mode support for SwitchbotRelaySwitch2PM by having it additionally inherit from SwitchbotBaseCover. It also refactors the SwitchbotBaseCover constructor to accept reverse as a keyword argument, updates the advertisement parser for 2PM to include mode, position, and calibration fields, and updates tests accordingly.
Changes:
SwitchbotRelaySwitch2PMnow inherits from bothSwitchbotRelaySwitchandSwitchbotBaseCover, gaining cover/roller commands (open,close,stop,set_position) and properties (position,mode).SwitchbotBaseCover.__init__changedreversefrom a positional to a keyword argument; all subclass call sites updated.- Advertisement parser and
_parse_common_dataextended withmode,position,calibrationfields for 2PM devices.
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 |
|---|---|
switchbot/devices/base_cover.py |
Refactored constructor to accept reverse as keyword-only via kwargs.pop |
switchbot/devices/relay_switch.py |
Added cover inheritance, roller commands, position/mode properties, motion direction logic, and refactored 2PM get_basic_info |
switchbot/adv_parsers/relay_switch.py |
Added mode, position, calibration fields to 2PM advertisement parser |
switchbot/devices/curtain.py |
Updated super().__init__ call for new keyword arg signature |
switchbot/devices/blind_tilt.py |
Updated super().__init__ call for new keyword arg signature |
switchbot/devices/roller_shade.py |
Updated super().__init__ call for new keyword arg signature |
tests/test_relay_switch.py |
Added tests for cover operations, position, mode, and motion direction |
tests/test_base_cover.py |
Updated SwitchbotBaseCover instantiation to match new keyword arg signature |
tests/test_adv_parser.py |
Added expected mode, position, calibration fields to 2PM test cases |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
… abstract methods Fix operator precedence bug where `mfr_data[9] & 0b11110000 >> 4` was incorrectly extracting the lower nibble instead of the upper nibble for channel 2 mode. Add `get_position()` and `get_extended_info_summary()` implementations to satisfy SwitchbotBaseCover abstract interface. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts: # switchbot/devices/relay_switch.py
Delegate the basic-info and channel 1 fetch/parse back to the parent SwitchbotRelaySwitch.get_basic_info, so 2PM only adds the channel 2 read on top. The parent's _parse_common_data already returns calibration, mode, and position, so those fields flow through unchanged.
|
Out of time to finish reviewing this toda. Will try to pickup reviews on this repo later this week. |
PR sblibs#457 changed `SwitchbotBaseCover.__init__` to read `reverse` from `**kwargs` so it can travel through the MRO when `SwitchbotRelaySwitch2PM` inherits from both `SwitchbotRelaySwitch` and `SwitchbotBaseCover`. That silently broke any external caller passing `reverse` as a leading positional arg — `reverse` would be ignored and the bool would be forwarded to `super().__init__` as `device`. Restore backward compatibility by accepting either form: a leading positional bool (legacy) or a `reverse=` kwarg (preferred). The bool sniff is unambiguous because the only other candidate for the first positional is `BLEDevice`. Adds tests covering the legacy positional form, the kwarg form, and the omitted-default form.
`open()`, `close()`, `set_position()` previously set `_is_opening`/ `_is_closing` *before* awaiting `_send_command`, and `stop()` cleared them before the await. If the command raised or the result failed `_check_command_result`, the device was left in a stale motion state that adv updates could not correct until the next position change. Move the flag updates after the result check using the walrus operator so they apply only on success. Adds failure-path tests for all four methods.
| return success | ||
|
|
||
| @update_after_operation | ||
| async def set_position(self, position: int, mode: int = 0) -> bool: |
There was a problem hiding this comment.
This doesn't appear to honor reverse
| def channel(self) -> int: | ||
| return self._channel | ||
|
|
||
| def get_position(self) -> Any: |
There was a problem hiding this comment.
This doesn't appear to honor reverse either
| return success | ||
|
|
||
| @update_after_operation | ||
| async def set_position(self, position: int, mode: int = 0) -> bool: |
| "isOn": bool(mfr_data[7] & 0b01000000), | ||
| "power": parse_power_data(mfr_data, 12), | ||
| "mode": (mfr_data[9] & 0b11110000) >> 4, | ||
| "position": mfr_data[14], |
There was a problem hiding this comment.
Uses the same position as 1: is that correct?
Locks in the parenthesization in `process_relay_switch_2pm` for channel 2's `mode`. Without parens, Python's precedence parses `mfr_data[9] & 0b11110000 >> 4` as `mfr_data[9] & (0b11110000 >> 4)` = `mfr_data[9] & 0x0F`, which silently returns channel 1's mode. The existing 2PM adv fixtures all use `mfr_data[9] = 0` so they cannot detect this regression.
The 2PM roller mode work refactored `_parse_common_data` and dropped the `sequence_number` key (sourced from `raw_data[1]`). This is a silent regression for 1PM `get_basic_info` consumers that previously read it. Add the key back and pin it with a regression test.
bdraco
left a comment
There was a problem hiding this comment.
Please see review comments above
Summary
Add roller/cover mode support for
SwitchbotRelaySwitch2PMand fix related test cases.Changes
Features
SwitchbotRelaySwitch2PMnow inherits fromSwitchbotBaseCover, enabling cover/roller control support.open(),close(),stop(),set_position()commands for roller mode.positionandmodeproperties toSwitchbotRelaySwitch2PM.COMMAND_OPEN,COMMAND_CLOSE,COMMAND_POSITION,COMMAND_STOP.Parser
process_relay_switch_2pm: Addedmode,position,calibrationfields to both channel 1 and channel 2 advertisement data.Refactor
SwitchbotBaseCover.__init__: Changedreverseto a keyword-only argument, updated all subclass call sites (curtain.py,blind_tilt.py,roller_shade.py)._parse_common_datainSwitchbotRelaySwitch:sequence_numbernow reads fromraw_data[0](wasraw_data[1]).Tests
test_adv_parser.py: Added expectedmode,position,calibrationfields toSwitchbotRelaySwitch2PMtest cases.test_base_cover.py: FixedSwitchbotBaseCoverinstantiation to use new keyword argument signature.