Skip to content

Commit 160f715

Browse files
zerzhangpre-commit-ci[bot]bdraco
authored
Add support for vacuum (#326)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston <[email protected]>
1 parent 5e58ac7 commit 160f715

File tree

7 files changed

+753
-0
lines changed

7 files changed

+753
-0
lines changed

switchbot/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from .devices.plug import SwitchbotPlugMini
3333
from .devices.relay_switch import SwitchbotRelaySwitch
3434
from .devices.roller_shade import SwitchbotRollerShade
35+
from .devices.vacuum import SwitchbotVacuum
3536
from .discovery import GetSwitchbotDevices
3637
from .models import SwitchBotAdvertisement
3738

@@ -66,6 +67,7 @@
6667
"SwitchbotRollerShade",
6768
"SwitchbotSupportedType",
6869
"SwitchbotSupportedType",
70+
"SwitchbotVacuum",
6971
"close_stale_connections",
7072
"close_stale_connections_by_address",
7173
"get_device",

switchbot/adv_parser.py

+31
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
)
3434
from .adv_parsers.remote import process_woremote
3535
from .adv_parsers.roller_shade import process_worollershade
36+
from .adv_parsers.vacuum import process_vacuum, process_vacuum_k
3637
from .const import SwitchbotModel
3738
from .models import SwitchBotAdvertisement
3839

@@ -237,6 +238,36 @@ class SwitchbotSupportedType(TypedDict):
237238
"func": process_fan,
238239
"manufacturer_id": 2409,
239240
},
241+
".": {
242+
"modelName": SwitchbotModel.K20_VACUUM,
243+
"modelFriendlyName": "K20 Vacuum",
244+
"func": process_vacuum,
245+
"manufacturer_id": 2409,
246+
},
247+
"z": {
248+
"modelName": SwitchbotModel.S10_VACUUM,
249+
"modelFriendlyName": "S10 Vacuum",
250+
"func": process_vacuum,
251+
"manufacturer_id": 2409,
252+
},
253+
"3": {
254+
"modelName": SwitchbotModel.K10_PRO_COMBO_VACUUM,
255+
"modelFriendlyName": "K10+ Pro Combo Vacuum",
256+
"func": process_vacuum,
257+
"manufacturer_id": 2409,
258+
},
259+
"}": {
260+
"modelName": SwitchbotModel.K10_VACUUM,
261+
"modelFriendlyName": "K10+ Vacuum",
262+
"func": process_vacuum_k,
263+
"manufacturer_id": 2409,
264+
},
265+
"(": {
266+
"modelName": SwitchbotModel.K10_PRO_VACUUM,
267+
"modelFriendlyName": "K10+ Pro Vacuum",
268+
"func": process_vacuum_k,
269+
"manufacturer_id": 2409,
270+
},
240271
}
241272

242273
_SWITCHBOT_MODEL_TO_CHAR = {

switchbot/adv_parsers/vacuum.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Vacuum parser."""
2+
3+
from __future__ import annotations
4+
5+
import struct
6+
7+
8+
def process_vacuum(
9+
data: bytes | None, mfr_data: bytes | None
10+
) -> dict[str, bool | int | str]:
11+
"""Support for s10, k10+ pro combo, k20 process service data."""
12+
if mfr_data is None:
13+
return {}
14+
15+
_seq_num = mfr_data[6]
16+
_soc_version = get_device_fw_version(mfr_data[8:11])
17+
# Steps at the end of the last network configuration
18+
_step = mfr_data[11] & 0b00001111
19+
_mqtt_connected = bool(mfr_data[11] & 0b00010000)
20+
_battery = mfr_data[12]
21+
_work_status = mfr_data[13] & 0b00111111
22+
23+
return {
24+
"sequence_number": _seq_num,
25+
"soc_version": _soc_version,
26+
"step": _step,
27+
"mqtt_connected": _mqtt_connected,
28+
"battery": _battery,
29+
"work_status": _work_status,
30+
}
31+
32+
33+
def get_device_fw_version(version_bytes: bytes) -> str | None:
34+
version1 = version_bytes[0] & 0x0F
35+
version2 = version_bytes[0] >> 4
36+
version3 = struct.unpack("<H", version_bytes[1:])[0]
37+
return f"{version1}.{version2}.{version3:>03d}"
38+
39+
40+
def process_vacuum_k(
41+
data: bytes | None, mfr_data: bytes | None
42+
) -> dict[str, bool | int | str]:
43+
"""Support for k10+, k10+ pro process service data."""
44+
if mfr_data is None:
45+
return {}
46+
47+
_seq_num = mfr_data[6]
48+
_dustbin_bound = bool(mfr_data[7] & 0b10000000)
49+
_dusbin_connected = bool(mfr_data[7] & 0b01000000)
50+
_network_connected = bool(mfr_data[7] & 0b00100000)
51+
_work_status = (mfr_data[7] & 0b00010000) >> 4
52+
_battery = mfr_data[8] & 0b01111111
53+
54+
return {
55+
"sequence_number": _seq_num,
56+
"dustbin_bound": _dustbin_bound,
57+
"dusbin_connected": _dusbin_connected,
58+
"network_connected": _network_connected,
59+
"work_status": _work_status,
60+
"battery": _battery,
61+
}

switchbot/const/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ class SwitchbotModel(StrEnum):
6767
ROLLER_SHADE = "Roller Shade"
6868
HUBMINI_MATTER = "HubMini Matter"
6969
CIRCULATOR_FAN = "Circulator Fan"
70+
K20_VACUUM = "K20 Vacuum"
71+
S10_VACUUM = "S10 Vacuum"
72+
K10_VACUUM = "K10+ Vacuum"
73+
K10_PRO_VACUUM = "K10+ Pro Vacuum"
74+
K10_PRO_COMBO_VACUUM = "K10+ Pro Combo Vacuum"
7075

7176

7277
__all__ = [

switchbot/devices/vacuum.py

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""Library to handle connection with Switchbot."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from .device import SwitchbotSequenceDevice, update_after_operation
8+
9+
COMMAND_CLEAN_UP = {
10+
1: "570F5A00FFFF7001",
11+
2: "5A400101010126",
12+
}
13+
COMMAND_RETURN_DOCK = {
14+
1: "570F5A00FFFF7002",
15+
2: "5A400101010225",
16+
}
17+
18+
19+
class SwitchbotVacuum(SwitchbotSequenceDevice):
20+
"""Representation of a Switchbot Vacuum."""
21+
22+
def __init__(self, device, password=None, interface=0, **kwargs):
23+
super().__init__(device, password, interface, **kwargs)
24+
25+
@update_after_operation
26+
async def clean_up(self, protocol_version: int) -> bool:
27+
"""Send command to perform a spot clean-up."""
28+
return await self._send_command(COMMAND_CLEAN_UP[protocol_version])
29+
30+
@update_after_operation
31+
async def return_to_dock(self, protocol_version: int) -> bool:
32+
"""Send command to return the dock."""
33+
return await self._send_command(COMMAND_RETURN_DOCK[protocol_version])
34+
35+
async def get_basic_info(self) -> dict[str, Any] | None:
36+
"""Only support get the ble version through the command."""
37+
if not (_data := await self._get_basic_info()):
38+
return None
39+
return {
40+
"firmware": _data[2],
41+
}
42+
43+
def get_soc_version(self) -> str:
44+
"""Return device soc version."""
45+
return self._get_adv_value("soc_version")
46+
47+
def get_last_step(self) -> int:
48+
"""Return device last step after network configuration."""
49+
return self._get_adv_value("step")
50+
51+
def get_mqtt_connnect_status(self) -> bool:
52+
"""Return device mqtt connect status."""
53+
return self._get_adv_value("mqtt_connected")
54+
55+
def get_battery(self) -> int:
56+
"""Return device battery."""
57+
return self._get_adv_value("battery")
58+
59+
def get_work_status(self) -> int:
60+
"""Return device work status."""
61+
return self._get_adv_value("work_status")
62+
63+
def get_dustbin_bound_status(self) -> bool:
64+
"""Return the dustbin bound status"""
65+
return self._get_adv_value("dustbin_bound")
66+
67+
def get_dustbin_connnected_status(self) -> bool:
68+
"""Return the dustbin connected status"""
69+
return self._get_adv_value("dusbin_connected")
70+
71+
def get_network_connected_status(self) -> bool:
72+
"""Return the network connected status"""
73+
return self._get_adv_value("network_connected")

0 commit comments

Comments
 (0)