Skip to content

Commit d40d0b9

Browse files
Merge pull request #717 from aronkahrs-us/dev_extracted_libraries
Add Xiaomi vacuum support and update manifest dependencies
2 parents 0b7fc5e + 4cfd4c1 commit d40d0b9

4 files changed

Lines changed: 99 additions & 2 deletions

File tree

custom_components/xiaomi_cloud_map_extractor/connector/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from .vacuums.vacuum_unsupported import UnsupportedCloudVacuum
2929
from .vacuums.vacuum_viomi import ViomiCloudVacuum
3030
from .vacuums.vacuum_ijai import IjaiCloudVacuum
31+
from .vacuums.vacuum_xiaomi import XiaomiCloudVacuum
3132
from .xiaomi_cloud.connector import (
3233
XiaomiCloudConnector,
3334
XiaomiCloudDeviceInfo,
@@ -42,6 +43,7 @@
4243
RoidmiCloudVacuum,
4344
DreameCloudVacuum,
4445
IjaiCloudVacuum,
46+
XiaomiCloudVacuum,
4547
UnsupportedCloudVacuum
4648
]}
4749

custom_components/xiaomi_cloud_map_extractor/connector/vacuums/base/model.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class VacuumApi(StrEnum):
3434
ROIDMI = "ROIDMI"
3535
DREAME = "DREAME"
3636
IJAI = "IJAI"
37+
XIAOMI = "XIAOMI"
3738
UNSUPPORTED = "UNSUPPORTED"
3839

3940
@staticmethod
@@ -55,7 +56,8 @@ def list_contains_model(prefixes, model_to_check):
5556
VacuumApi.ROIDMI: ["roidmi.vacuum.", "zhimi.vacuum.", "chuangmi.vacuum."],
5657
VacuumApi.VIOMI: ["viomi.vacuum."],
5758
VacuumApi.ROBOROCK: ["roborock.vacuum", "rockrobo.vacuum"],
58-
VacuumApi.IJAI: ["ijai.vacuum.", "xiaomi.vacuum."],
59+
VacuumApi.IJAI: ["ijai.vacuum."],
60+
VacuumApi.XIAOMI: ["xiaomi.vacuum."],
5961
}
6062

6163
API_EXCEPTIONS = {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import logging
2+
import json
3+
import base64
4+
from typing import Self, Any
5+
6+
from miio.miot_device import MiotDevice
7+
from miio.exceptions import DeviceException
8+
from vacuum_map_parser_base.map_data import MapData
9+
from vacuum_map_parser_xiaomi.map_data_parser import XiaomiMapDataParser
10+
from vacuum_map_parser_xiaomi.status_mapping import get_status_mapping
11+
from vacuum_map_parser_xiaomi.aes_decryptor import gen_md5_key
12+
13+
from .base.vacuum_v2 import BaseXiaomiCloudVacuumV2
14+
from .base.model import VacuumConfig, VacuumApi
15+
from ..utils.exceptions import FailedConnectionException
16+
17+
_LOGGER = logging.getLogger(__name__)
18+
OFF_UPDATES = 3
19+
20+
21+
class XiaomiCloudVacuum(BaseXiaomiCloudVacuumV2):
22+
def __init__(self, vacuum_config: VacuumConfig):
23+
super().__init__(vacuum_config)
24+
self._token = vacuum_config.token
25+
self._host = vacuum_config.host
26+
27+
self._miot_device = MiotDevice(self._host, self._token, timeout=2)
28+
29+
self._xiaomi_map_data_parser = XiaomiMapDataParser(
30+
vacuum_config.palette,
31+
vacuum_config.sizes,
32+
vacuum_config.drawables,
33+
vacuum_config.image_config,
34+
vacuum_config.texts
35+
)
36+
37+
self._status_mapping = get_status_mapping(self.model)
38+
self._off_counter = 0
39+
40+
@property
41+
def should_update_map(self: Self) -> bool:
42+
try:
43+
status_value = self._miot_device.get_property_by(self._status_mapping.siid,
44+
self._status_mapping.piid)[0]["value"]
45+
46+
if status_value in self._status_mapping.idle_at:
47+
self._off_counter += 1
48+
_LOGGER.debug(
49+
"Vacuum is not moving. Off counter: %d", self._off_counter)
50+
return self._off_counter <= OFF_UPDATES
51+
else:
52+
self._off_counter = 0
53+
return True
54+
except DeviceException as de:
55+
if "token" not in repr(de):
56+
return False
57+
raise FailedConnectionException(de)
58+
59+
@staticmethod
60+
def vacuum_platform() -> VacuumApi:
61+
return VacuumApi.XIAOMI
62+
63+
@property
64+
def map_archive_extension(self) -> str:
65+
return "zlib.enc"
66+
67+
@property
68+
def map_data_parser(self) -> XiaomiMapDataParser:
69+
return self._xiaomi_map_data_parser
70+
71+
async def get_map_url(self, map_name: str) -> str | None:
72+
return await self.get_fallback_map_url(map_name)
73+
74+
def decode_and_parse(self, raw_map: bytes) -> MapData:
75+
76+
raw_map = base64.decodebytes(json.loads(raw_map)["data"].encode("latin1"))
77+
raw_map = raw_map.hex()
78+
decoded_map = self.map_data_parser.unpack_map(
79+
raw_map,
80+
model=self.model.replace("xiaomi", "mi"),
81+
device_id=str(self._device_id),
82+
)
83+
return self.map_data_parser.parse(decoded_map)
84+
85+
def additional_data(self: Self) -> dict[str, Any]:
86+
super_data = super().additional_data()
87+
enc_key = gen_md5_key(
88+
self.model.replace("xiaomi", "mi"),
89+
str(self._device_id),
90+
)
91+
92+
return {**super_data, "enc_key": enc_key}

custom_components/xiaomi_cloud_map_extractor/manifest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"vacuum-map-parser-viomi==0.1.3",
2020
"vacuum-map-parser-roidmi==0.1.3",
2121
"vacuum-map-parser-dreame==0.1.3",
22-
"vacuum-map-parser-ijai==0.1.0"
22+
"vacuum-map-parser-ijai==0.1.0",
23+
"vacuum-map-parser-xiaomi==0.1.1"
2324
],
2425
"version": "v3.0.0-beta"
2526
}

0 commit comments

Comments
 (0)