Skip to content

Conversation

@allenporter
Copy link
Contributor

@allenporter allenporter commented Nov 15, 2025

This changes the contract for traits that have commands that mutate state to always ensure they reflect the latest device state after the command completes.

Raw commands or commands that impact the state of other traits as a side effect do not have this behavior and need to be managed by the caller. This provides an inconsistency between parts of the API, but I think it is more intuitive than never refreshing state for simple traits.

This changes the contract for traits that have commands that mutate
state to always ensure they reflect the latest device state after
the command completes.
Copilot AI review requested due to automatic review settings November 15, 2025 17:42
Copy link
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

This PR establishes a contract for traits with state-mutating commands to ensure they always reflect the latest device state after command completion. The implementation uses a mix of optimistic updates (directly setting local state) and refresh calls (fetching state from the device).

Key Changes:

  • Updated trait methods to maintain state consistency after command execution through either optimistic updates or refresh calls
  • Enhanced documentation to clarify state update responsibilities and distinguish between trait commands and raw commands
  • Added comprehensive test coverage to verify state updates after command execution

Reviewed Changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/devices/traits/v1/test_dnd.py Added test assertions to verify state updates and multiple command calls after DnD timer operations
tests/devices/traits/v1/test_consumable.py Added test assertions to verify state refresh after consumable reset operations
roborock/devices/traits/v1/volume.py Added optimistic state update (self.volume = volume) after volume change command
roborock/devices/traits/v1/valley_electricity_timer.py Added refresh call to clear_timer() and optimistic updates to enable()/disable() methods
roborock/devices/traits/v1/led_status.py Added optimistic state updates to enable()/disable() methods
roborock/devices/traits/v1/flow_led_status.py Added optimistic state updates to enable()/disable() methods
roborock/devices/traits/v1/do_not_disturb.py Added refresh calls to set_dnd_timer()/clear_dnd_timer() and optimistic updates to enable()/disable()
roborock/devices/traits/v1/consumeable.py Added refresh call after consumable reset operation
roborock/devices/traits/v1/child_lock.py Added optimistic state updates to enable()/disable() methods
roborock/devices/traits/v1/common.py Expanded documentation to clarify trait state update contract and cross-trait synchronization responsibilities
roborock/devices/traits/v1/command.py Added documentation clarifying that raw commands don't auto-update trait state

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

Copy link
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

Copilot reviewed 11 out of 11 changed files in this pull request and generated 21 comments.


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

RoborockCommand.SET_VALLEY_ELECTRICITY_TIMER,
params=self.as_list(),
)
# Optimistcally update state to avoid an extra refresh
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Typo in comment: "Optimistcally" should be "Optimistically"

Copilot uses AI. Check for mistakes.
Comment on lines 21 to 23
await self.rpc_channel.send_command(RoborockCommand.SET_LED_STATUS, params=[1])
# Optimistcally update state to avoid an extra refresh
self.status = 1
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

The optimistic state update occurs after the command is sent, but if the command fails with an exception, the state will remain inconsistent. Consider moving the state update before the command and reverting on exception, or wrapping in a try-except to only update on success.

Copilot uses AI. Check for mistakes.
Comment on lines 22 to 28
# Optimistcally update state to avoid an extra refresh
self.status = 1

async def disable(self) -> None:
"""Disable the LED status."""
await self.rpc_channel.send_command(RoborockCommand.SET_LED_STATUS, params=[0])
# Optimistcally update state to avoid an extra refresh
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Typo in comment: "Optimistcally" should be "Optimistically"

Suggested change
# Optimistcally update state to avoid an extra refresh
self.status = 1
async def disable(self) -> None:
"""Disable the LED status."""
await self.rpc_channel.send_command(RoborockCommand.SET_LED_STATUS, params=[0])
# Optimistcally update state to avoid an extra refresh
# Optimistically update state to avoid an extra refresh
self.status = 1
async def disable(self) -> None:
"""Disable the LED status."""
await self.rpc_channel.send_command(RoborockCommand.SET_LED_STATUS, params=[0])
# Optimistically update state to avoid an extra refresh

Copilot uses AI. Check for mistakes.
Comment on lines 22 to 28
# Optimistcally update state to avoid an extra refresh
self.status = 1

async def disable(self) -> None:
"""Disable the LED status."""
await self.rpc_channel.send_command(RoborockCommand.SET_LED_STATUS, params=[0])
# Optimistcally update state to avoid an extra refresh
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Typo in comment: "Optimistcally" should be "Optimistically"

Suggested change
# Optimistcally update state to avoid an extra refresh
self.status = 1
async def disable(self) -> None:
"""Disable the LED status."""
await self.rpc_channel.send_command(RoborockCommand.SET_LED_STATUS, params=[0])
# Optimistcally update state to avoid an extra refresh
# Optimistically update state to avoid an extra refresh
self.status = 1
async def disable(self) -> None:
"""Disable the LED status."""
await self.rpc_channel.send_command(RoborockCommand.SET_LED_STATUS, params=[0])
# Optimistically update state to avoid an extra refresh

Copilot uses AI. Check for mistakes.
Comment on lines 22 to 28
# Optimistcally update state to avoid an extra refresh
self.status = 1

async def disable(self) -> None:
"""Disable the Flow LED status."""
await self.rpc_channel.send_command(RoborockCommand.SET_FLOW_LED_STATUS, params={_STATUS_PARAM: 0})
# Optimistcally update state to avoid an extra refresh
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Typo in comment: "Optimistcally" should be "Optimistically"

Suggested change
# Optimistcally update state to avoid an extra refresh
self.status = 1
async def disable(self) -> None:
"""Disable the Flow LED status."""
await self.rpc_channel.send_command(RoborockCommand.SET_FLOW_LED_STATUS, params={_STATUS_PARAM: 0})
# Optimistcally update state to avoid an extra refresh
# Optimistically update state to avoid an extra refresh
self.status = 1
async def disable(self) -> None:
"""Disable the Flow LED status."""
await self.rpc_channel.send_command(RoborockCommand.SET_FLOW_LED_STATUS, params={_STATUS_PARAM: 0})
# Optimistically update state to avoid an extra refresh

Copilot uses AI. Check for mistakes.
Comment on lines 21 to 29
await self.rpc_channel.send_command(RoborockCommand.SET_CHILD_LOCK_STATUS, params={_STATUS_PARAM: 1})
# Optimistcally update state to avoid an extra refresh
self.lock_status = 1

async def disable(self) -> None:
"""Disable the child lock."""
await self.rpc_channel.send_command(RoborockCommand.SET_CHILD_LOCK_STATUS, params={_STATUS_PARAM: 0})
# Optimistcally update state to avoid an extra refresh
self.lock_status = 0
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

The optimistic state update occurs after the command is sent, but if the command fails with an exception, the state will remain inconsistent. Consider moving the state update before the command and reverting on exception, or wrapping in a try-except to only update on success.

Suggested change
await self.rpc_channel.send_command(RoborockCommand.SET_CHILD_LOCK_STATUS, params={_STATUS_PARAM: 1})
# Optimistcally update state to avoid an extra refresh
self.lock_status = 1
async def disable(self) -> None:
"""Disable the child lock."""
await self.rpc_channel.send_command(RoborockCommand.SET_CHILD_LOCK_STATUS, params={_STATUS_PARAM: 0})
# Optimistcally update state to avoid an extra refresh
self.lock_status = 0
try:
await self.rpc_channel.send_command(RoborockCommand.SET_CHILD_LOCK_STATUS, params={_STATUS_PARAM: 1})
except Exception:
# Optionally, log the exception here
raise
else:
# Optimistically update state to avoid an extra refresh
self.lock_status = 1
async def disable(self) -> None:
"""Disable the child lock."""
try:
await self.rpc_channel.send_command(RoborockCommand.SET_CHILD_LOCK_STATUS, params={_STATUS_PARAM: 0})
except Exception:
# Optionally, log the exception here
raise
else:
# Optimistically update state to avoid an extra refresh
self.lock_status = 0

Copilot uses AI. Check for mistakes.
await self.rpc_channel.send_command(
RoborockCommand.CLOSE_VALLEY_ELECTRICITY_TIMER,
)
# Optimistcally update state to avoid an extra refresh
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Typo in comment: "Optimistcally" should be "Optimistically"

Copilot uses AI. Check for mistakes.
async def disable(self) -> None:
"""Disable the Flow LED status."""
await self.rpc_channel.send_command(RoborockCommand.SET_FLOW_LED_STATUS, params={_STATUS_PARAM: 0})
# Optimistcally update state to avoid an extra refresh
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Typo in comment: "Optimistcally" should be "Optimistically"

Suggested change
# Optimistcally update state to avoid an extra refresh
# Optimistically update state to avoid an extra refresh

Copilot uses AI. Check for mistakes.
Comment on lines 27 to 35
"""Set the Do Not Disturb (DND) timer settings of the device."""
await self.rpc_channel.send_command(
RoborockCommand.SET_DND_TIMER,
params=self.as_list(),
)
# Optimistcally update state to avoid an extra refresh
self.enabled = 1
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

The optimistic state update occurs after the command is sent, but if the command fails with an exception, the state will remain inconsistent. Consider moving the state update before the command and reverting on exception, or wrapping in a try-except to only update on success.

Copilot uses AI. Check for mistakes.
Comment on lines 21 to 23
await self.rpc_channel.send_command(RoborockCommand.SET_CHILD_LOCK_STATUS, params={_STATUS_PARAM: 1})
# Optimistcally update state to avoid an extra refresh
self.lock_status = 1
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

The optimistic state update occurs after the command is sent, but if the command fails with an exception, the state will remain inconsistent. Consider moving the state update before the command and reverting on exception, or wrapping in a try-except to only update on success.

Copilot uses AI. Check for mistakes.
@allenporter allenporter merged commit 3d0ad74 into Python-roborock:main Nov 25, 2025
7 checks passed
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