From 0c763e64991cf362e4483c8a520ec266fecc4eb1 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 29 Nov 2025 16:11:10 -0500 Subject: [PATCH 1/8] chore: refactor to seperate b01 ss and sc logic --- roborock/devices/device_manager.py | 7 ++++- roborock/devices/traits/b01/sc/__init__.py | 31 ++++++++++++++++++++++ roborock/devices/traits/b01/ss/__init__.py | 1 + 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 roborock/devices/traits/b01/sc/__init__.py create mode 100644 roborock/devices/traits/b01/ss/__init__.py diff --git a/roborock/devices/device_manager.py b/roborock/devices/device_manager.py index e490be12..bb310555 100644 --- a/roborock/devices/device_manager.py +++ b/roborock/devices/device_manager.py @@ -196,7 +196,12 @@ def device_creator(home_data: HomeData, device: HomeDataDevice, product: HomeDat trait = a01.create(product, channel) case DeviceVersion.B01: channel = create_mqtt_channel(user_data, mqtt_params, mqtt_session, device) - trait = b01.create(channel) + if "ss" in product.model.strip(".")[-1]: + raise NotImplementedError( + f"Device {device.name} has unsupported version B01_{product.model.strip('.')[-1]}" + ) + elif "sc" in product.model.strip(".")[-1]: + trait = b01.sc.create(channel) case _: raise NotImplementedError(f"Device {device.name} has unsupported version {device.pv}") return RoborockDevice(device, product, channel, trait) diff --git a/roborock/devices/traits/b01/sc/__init__.py b/roborock/devices/traits/b01/sc/__init__.py new file mode 100644 index 00000000..2a4bd9de --- /dev/null +++ b/roborock/devices/traits/b01/sc/__init__.py @@ -0,0 +1,31 @@ +"""Traits for SC B01 devices. +Right now, the only known Q7 devices are sc devices.""" + +from roborock import RoborockB01Methods +from roborock.devices.b01_channel import send_decoded_command +from roborock.devices.mqtt_channel import MqttChannel +from roborock.devices.traits import Trait +from roborock.roborock_message import RoborockB01Props + +__all__ = [ + "PropertiesApi", +] + + +class PropertiesApi(Trait): + """API for interacting with B01 devices.""" + + def __init__(self, channel: MqttChannel) -> None: + """Initialize the B01Props API.""" + self._channel = channel + + async def query_values(self, props: list[RoborockB01Props]) -> None: + """Query the device for the values of the given Dyad protocols.""" + await send_decoded_command( + self._channel, dps=10000, command=RoborockB01Methods.GET_PROP, params={"property": props} + ) + + +def create(channel: MqttChannel) -> PropertiesApi: + """Create traits for B01 devices.""" + return PropertiesApi(channel) diff --git a/roborock/devices/traits/b01/ss/__init__.py b/roborock/devices/traits/b01/ss/__init__.py new file mode 100644 index 00000000..b3cd30d6 --- /dev/null +++ b/roborock/devices/traits/b01/ss/__init__.py @@ -0,0 +1 @@ +"""Q10""" From 2f3ee9ccb90a517d219d27b15fb1dde5bca73f56 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 29 Nov 2025 16:11:48 -0500 Subject: [PATCH 2/8] chore: refactor to seperate b01 ss and sc logic --- roborock/devices/traits/b01/__init__.py | 29 ++----------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/roborock/devices/traits/b01/__init__.py b/roborock/devices/traits/b01/__init__.py index 75c2d61f..c082de96 100644 --- a/roborock/devices/traits/b01/__init__.py +++ b/roborock/devices/traits/b01/__init__.py @@ -1,30 +1,5 @@ """Traits for B01 devices.""" -from roborock import RoborockB01Methods -from roborock.devices.b01_channel import send_decoded_command -from roborock.devices.mqtt_channel import MqttChannel -from roborock.devices.traits import Trait -from roborock.roborock_message import RoborockB01Props +from .sc import PropertiesApi -__all__ = [ - "PropertiesApi", -] - - -class PropertiesApi(Trait): - """API for interacting with B01 devices.""" - - def __init__(self, channel: MqttChannel) -> None: - """Initialize the B01Props API.""" - self._channel = channel - - async def query_values(self, props: list[RoborockB01Props]) -> None: - """Query the device for the values of the given Dyad protocols.""" - await send_decoded_command( - self._channel, dps=10000, command=RoborockB01Methods.GET_PROP, params={"property": props} - ) - - -def create(channel: MqttChannel) -> PropertiesApi: - """Create traits for B01 devices.""" - return PropertiesApi(channel) +__all__ = ["PropertiesApi", "sc", "ss"] From 8e2a06e587197192caaf97241c94e13de60d05bd Mon Sep 17 00:00:00 2001 From: Luke Date: Fri, 5 Dec 2025 20:58:41 -0500 Subject: [PATCH 3/8] fix: clean up some naming --- roborock/devices/device_manager.py | 3 +- roborock/devices/traits/b01/__init__.py | 4 +-- .../traits/b01/{ss => q10}/__init__.py | 0 roborock/devices/traits/b01/q7/__init__.py | 31 +++++++++++++++++++ roborock/devices/traits/b01/sc/__init__.py | 31 ------------------- roborock/devices/traits/traits_mixin.py | 4 +-- roborock/protocols/b01_protocol.py | 4 +-- roborock/roborock_typing.py | 4 +-- 8 files changed, 41 insertions(+), 40 deletions(-) rename roborock/devices/traits/b01/{ss => q10}/__init__.py (100%) create mode 100644 roborock/devices/traits/b01/q7/__init__.py delete mode 100644 roborock/devices/traits/b01/sc/__init__.py diff --git a/roborock/devices/device_manager.py b/roborock/devices/device_manager.py index bb310555..ba0446fc 100644 --- a/roborock/devices/device_manager.py +++ b/roborock/devices/device_manager.py @@ -201,7 +201,8 @@ def device_creator(home_data: HomeData, device: HomeDataDevice, product: HomeDat f"Device {device.name} has unsupported version B01_{product.model.strip('.')[-1]}" ) elif "sc" in product.model.strip(".")[-1]: - trait = b01.sc.create(channel) + # Q7 devices start with 'sc' in their model naming. + trait = b01.q7.create(channel) case _: raise NotImplementedError(f"Device {device.name} has unsupported version {device.pv}") return RoborockDevice(device, product, channel, trait) diff --git a/roborock/devices/traits/b01/__init__.py b/roborock/devices/traits/b01/__init__.py index c082de96..e7ed42d0 100644 --- a/roborock/devices/traits/b01/__init__.py +++ b/roborock/devices/traits/b01/__init__.py @@ -1,5 +1,5 @@ """Traits for B01 devices.""" -from .sc import PropertiesApi +from .q7 import Q7PropertiesApi -__all__ = ["PropertiesApi", "sc", "ss"] +__all__ = ["q&PropertiesApi", "q7", "q10"] diff --git a/roborock/devices/traits/b01/ss/__init__.py b/roborock/devices/traits/b01/q10/__init__.py similarity index 100% rename from roborock/devices/traits/b01/ss/__init__.py rename to roborock/devices/traits/b01/q10/__init__.py diff --git a/roborock/devices/traits/b01/q7/__init__.py b/roborock/devices/traits/b01/q7/__init__.py new file mode 100644 index 00000000..03eb455d --- /dev/null +++ b/roborock/devices/traits/b01/q7/__init__.py @@ -0,0 +1,31 @@ +"""Traits for Q7 B01 devices. +Potentially other devices may fall into this category in the future.""" + +from roborock.devices.b01_channel import send_decoded_command +from roborock.devices.mqtt_channel import MqttChannel +from roborock.devices.traits import Trait +from roborock.roborock_message import RoborockB01Props +from roborock.roborock_typing import RoborockB01Q7Methods + +__all__ = [ + "Q7PropertiesApi", +] + + +class Q7PropertiesApi(Trait): + """API for interacting with Q7 B01 devices.""" + + def __init__(self, channel: MqttChannel) -> None: + """Initialize the B01Props API.""" + self._channel = channel + + async def query_values(self, props: list[RoborockB01Props]) -> None: + """Query the device for the values of the given Q7 protocols.""" + await send_decoded_command( + self._channel, dps=10000, command=RoborockB01Q7Methods.GET_PROP, params={"property": props} + ) + + +def create(channel: MqttChannel) -> Q7PropertiesApi: + """Create traits for B01 devices.""" + return Q7PropertiesApi(channel) diff --git a/roborock/devices/traits/b01/sc/__init__.py b/roborock/devices/traits/b01/sc/__init__.py deleted file mode 100644 index 2a4bd9de..00000000 --- a/roborock/devices/traits/b01/sc/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Traits for SC B01 devices. -Right now, the only known Q7 devices are sc devices.""" - -from roborock import RoborockB01Methods -from roborock.devices.b01_channel import send_decoded_command -from roborock.devices.mqtt_channel import MqttChannel -from roborock.devices.traits import Trait -from roborock.roborock_message import RoborockB01Props - -__all__ = [ - "PropertiesApi", -] - - -class PropertiesApi(Trait): - """API for interacting with B01 devices.""" - - def __init__(self, channel: MqttChannel) -> None: - """Initialize the B01Props API.""" - self._channel = channel - - async def query_values(self, props: list[RoborockB01Props]) -> None: - """Query the device for the values of the given Dyad protocols.""" - await send_decoded_command( - self._channel, dps=10000, command=RoborockB01Methods.GET_PROP, params={"property": props} - ) - - -def create(channel: MqttChannel) -> PropertiesApi: - """Create traits for B01 devices.""" - return PropertiesApi(channel) diff --git a/roborock/devices/traits/traits_mixin.py b/roborock/devices/traits/traits_mixin.py index 9c1c98dd..92b9597e 100644 --- a/roborock/devices/traits/traits_mixin.py +++ b/roborock/devices/traits/traits_mixin.py @@ -31,8 +31,8 @@ class TraitsMixin: zeo: a01.ZeoApi | None = None """Zeo API, if supported.""" - b01_properties: b01.PropertiesApi | None = None - """B01 properties trait, if supported.""" + b01_q7_properties: b01.Q7PropertiesApi | None = None + """B01 Q7 properties trait, if supported.""" def __init__(self, trait: Trait) -> None: """Initialize the TraitsMixin with the given trait. diff --git a/roborock/protocols/b01_protocol.py b/roborock/protocols/b01_protocol.py index 5e60071c..27a05ecf 100644 --- a/roborock/protocols/b01_protocol.py +++ b/roborock/protocols/b01_protocol.py @@ -7,7 +7,7 @@ from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad -from roborock import RoborockB01Methods +from roborock import RoborockB01Q7Methods from roborock.exceptions import RoborockException from roborock.roborock_message import ( RoborockMessage, @@ -18,7 +18,7 @@ _LOGGER = logging.getLogger(__name__) B01_VERSION = b"B01" -CommandType = RoborockB01Methods | str +CommandType = RoborockB01Q7Methods | str ParamsType = list | dict | int | None diff --git a/roborock/roborock_typing.py b/roborock/roborock_typing.py index 5f90dc5f..23d16cb8 100644 --- a/roborock/roborock_typing.py +++ b/roborock/roborock_typing.py @@ -271,8 +271,8 @@ class RoborockCommand(str, Enum): APP_GET_ROBOT_SETTING = "app_get_robot_setting" -class RoborockB01Methods(StrEnum): - """Methods used by the Roborock B01 model.""" +class RoborockB01Q7Methods(StrEnum): + """Methods used by the Roborock Q7 model.""" GET_PROP = "prop.get" GET_MAP_LIST = "service.get_map_list" From fcef0145eeedf72a890b0b5daffb2b22c3da0b8f Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Fri, 5 Dec 2025 21:04:52 -0500 Subject: [PATCH 4/8] fix: update roborock/devices/traits/b01/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- roborock/devices/traits/b01/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roborock/devices/traits/b01/__init__.py b/roborock/devices/traits/b01/__init__.py index e7ed42d0..bf6d8b23 100644 --- a/roborock/devices/traits/b01/__init__.py +++ b/roborock/devices/traits/b01/__init__.py @@ -2,4 +2,4 @@ from .q7 import Q7PropertiesApi -__all__ = ["q&PropertiesApi", "q7", "q10"] +__all__ = ["Q7PropertiesApi", "q7", "q10"] From 107d3dea0b5ef246f11b5770f70692491631033a Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Fri, 5 Dec 2025 21:05:06 -0500 Subject: [PATCH 5/8] chore: update roborock/devices/traits/b01/q7/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- roborock/devices/traits/b01/q7/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roborock/devices/traits/b01/q7/__init__.py b/roborock/devices/traits/b01/q7/__init__.py index 03eb455d..e63803ee 100644 --- a/roborock/devices/traits/b01/q7/__init__.py +++ b/roborock/devices/traits/b01/q7/__init__.py @@ -20,7 +20,7 @@ def __init__(self, channel: MqttChannel) -> None: self._channel = channel async def query_values(self, props: list[RoborockB01Props]) -> None: - """Query the device for the values of the given Q7 protocols.""" + """Query the device for the values of the given Q7 properties.""" await send_decoded_command( self._channel, dps=10000, command=RoborockB01Q7Methods.GET_PROP, params={"property": props} ) From 52ee77951421bb4fba396c9cf4f534a75256e173 Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Fri, 5 Dec 2025 21:06:13 -0500 Subject: [PATCH 6/8] chore: update roborock/devices/device_manager.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- roborock/devices/device_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/roborock/devices/device_manager.py b/roborock/devices/device_manager.py index 92a9684e..8f8afddf 100644 --- a/roborock/devices/device_manager.py +++ b/roborock/devices/device_manager.py @@ -204,6 +204,10 @@ def device_creator(home_data: HomeData, device: HomeDataDevice, product: HomeDat elif "sc" in product.model.strip(".")[-1]: # Q7 devices start with 'sc' in their model naming. trait = b01.q7.create(channel) + else: + raise NotImplementedError( + f"Device {device.name} has unsupported B01 model: {product.model}" + ) case _: raise NotImplementedError(f"Device {device.name} has unsupported version {device.pv}") return RoborockDevice(device, product, channel, trait) From c171ff86a4271bcb95d0ea50543ed8033e4284c1 Mon Sep 17 00:00:00 2001 From: Luke Date: Fri, 5 Dec 2025 21:17:48 -0500 Subject: [PATCH 7/8] fix: use strip not split --- roborock/devices/device_manager.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/roborock/devices/device_manager.py b/roborock/devices/device_manager.py index 8f8afddf..9d874d71 100644 --- a/roborock/devices/device_manager.py +++ b/roborock/devices/device_manager.py @@ -197,17 +197,15 @@ def device_creator(home_data: HomeData, device: HomeDataDevice, product: HomeDat trait = a01.create(product, channel) case DeviceVersion.B01: channel = create_mqtt_channel(user_data, mqtt_params, mqtt_session, device) - if "ss" in product.model.strip(".")[-1]: + if "ss" in product.model.split(".")[-1]: raise NotImplementedError( f"Device {device.name} has unsupported version B01_{product.model.strip('.')[-1]}" ) - elif "sc" in product.model.strip(".")[-1]: + elif "sc" in product.model.split(".")[-1]: # Q7 devices start with 'sc' in their model naming. trait = b01.q7.create(channel) else: - raise NotImplementedError( - f"Device {device.name} has unsupported B01 model: {product.model}" - ) + raise NotImplementedError(f"Device {device.name} has unsupported B01 model: {product.model}") case _: raise NotImplementedError(f"Device {device.name} has unsupported version {device.pv}") return RoborockDevice(device, product, channel, trait) From 7cbb9eedd4fbef32f3e9c87ad47f88f7326c867e Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Sat, 6 Dec 2025 23:41:52 -0500 Subject: [PATCH 8/8] chore: share duplicated code Co-authored-by: Allen Porter --- roborock/devices/device_manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/roborock/devices/device_manager.py b/roborock/devices/device_manager.py index 9d874d71..f7f7bfea 100644 --- a/roborock/devices/device_manager.py +++ b/roborock/devices/device_manager.py @@ -197,11 +197,12 @@ def device_creator(home_data: HomeData, device: HomeDataDevice, product: HomeDat trait = a01.create(product, channel) case DeviceVersion.B01: channel = create_mqtt_channel(user_data, mqtt_params, mqtt_session, device) - if "ss" in product.model.split(".")[-1]: + model_part = product.model.split(".")[-1] + if "ss" in model_part: raise NotImplementedError( f"Device {device.name} has unsupported version B01_{product.model.strip('.')[-1]}" ) - elif "sc" in product.model.split(".")[-1]: + elif "sc" in model_part: # Q7 devices start with 'sc' in their model naming. trait = b01.q7.create(channel) else: