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 }
0 commit comments