Skip to content

Commit ce27d24

Browse files
author
Alexander "Tarh
committed
[WIP] Added ijai models basic support, fixed initialization of Palette
and ImageConfig objects
1 parent b49cf96 commit ce27d24

File tree

6 files changed

+92
-57
lines changed

6 files changed

+92
-57
lines changed

custom_components/xiaomi_cloud_map_extractor/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

custom_components/xiaomi_cloud_map_extractor/camera.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import logging
33
from datetime import timedelta
44
from enum import StrEnum
5+
from dataclasses import fields
56

67
from vacuum_map_parser_base.config.color import ColorsPalette
78
from vacuum_map_parser_base.config.drawable import Drawable
8-
from vacuum_map_parser_base.config.image_config import ImageConfig
9+
from vacuum_map_parser_base.config.image_config import ImageConfig, TrimConfig
910
from vacuum_map_parser_base.config.size import Sizes
1011
from vacuum_map_parser_base.config.text import Text
1112

@@ -27,6 +28,7 @@
2728
from .vacuum_platforms.vacuum_viomi import ViomiCloudVacuum
2829
from .vacuum_platforms.vacuum_ijai import IjaiCloudVacuum
2930
from .vacuum_platforms.vacuum_unsupported import UnsupportedCloudVacuum
31+
from .initializer import from_dict
3032
from .const import *
3133

3234

@@ -122,24 +124,26 @@
122124
})
123125

124126

127+
125128
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
126129
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
127-
130+
_LOGGER.debug(f"config={config}")
128131
host = config[CONF_HOST]
129132
token = config[CONF_TOKEN]
130133
username = config[CONF_USERNAME]
131134
password = config[CONF_PASSWORD]
132135
country = config[CONF_COUNTRY]
133136
name = config[CONF_NAME]
134137
should_poll = config[CONF_AUTO_UPDATE]
135-
image_config = config[CONF_MAP_TRANSFORM]
138+
image_config = from_dict(ImageConfig, config[CONF_MAP_TRANSFORM])
136139
colors = config[CONF_COLORS]
137140
room_colors = config[CONF_ROOM_COLORS]
138141
for room, color in room_colors.items():
139142
colors[f"{COLOR_ROOM_PREFIX}{room}"] = color
140143
drawables = config[CONF_DRAW]
141-
sizes = config[CONF_SIZES]
142-
texts = config[CONF_TEXTS]
144+
sizes = Sizes(config[CONF_SIZES])
145+
texts = from_dict(list, config[CONF_TEXTS])
146+
143147
if DRAWABLE_ALL in drawables:
144148
drawables = CONF_AVAILABLE_DRAWABLES[1:]
145149
attributes = config[CONF_ATTRIBUTES]
@@ -149,7 +153,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
149153
force_api = config[CONF_FORCE_API]
150154
entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=hass)
151155
async_add_entities([VacuumCamera(entity_id, host, token, username, password, country, name, should_poll,
152-
image_config, colors, drawables, sizes, texts, attributes, store_map_raw,
156+
image_config, ColorsPalette(colors, room_colors), drawables, sizes, texts, attributes, store_map_raw,
153157
store_map_image, store_map_path, force_api)])
154158

155159

@@ -230,6 +234,7 @@ def should_poll(self) -> bool:
230234

231235
@staticmethod
232236
def extract_attributes(map_data: MapData, attributes_to_return: list[str], country) -> dict[str, any]:
237+
_LOGGER.debug(f"extract_attributes{map_data}, {attributes_to_return}, country")
233238
attributes = {}
234239
rooms = []
235240
if map_data.rooms is not None:
@@ -299,7 +304,7 @@ def _login(self):
299304

300305
def _initialize_device(self):
301306
_LOGGER.debug("Retrieving device info, country: %s", self._country)
302-
country, user_id, device_id, model, mac = self._connector.get_device_details(self._vacuum.token, self._country)
307+
country, user_id, device_id, model, mac = self._connector.get_device_details(self._token, self._country)
303308
if model is not None:
304309
self._country = country
305310
_LOGGER.debug("Retrieved device model: %s", model)
@@ -345,26 +350,29 @@ def _create_device(self, user_id: str, device_id: str, model: str, mac: str) ->
345350
self._used_api = self._detect_api(model)
346351
store_map_path = self._store_map_path if self._store_map_raw else None
347352
vacuum_config = VacuumConfig(
348-
self._connector,
349-
self._country,
350-
user_id,
351-
device_id,
352-
self._host,
353-
self._token,
354-
model,
355-
self._colors,
356-
self._drawables,
357-
self._image_config,
358-
self._sizes,
359-
self._texts,
360-
store_map_path
353+
connector = self._connector,
354+
country = self._country,
355+
user_id = user_id,
356+
device_id = device_id,
357+
host = self._host,
358+
token = self._token,
359+
model = model,
360+
_mac = mac,
361+
palette = self._colors,
362+
drawables = self._drawables,
363+
image_config = self._image_config,
364+
sizes = self._sizes,
365+
texts = self._texts,
366+
store_map_path = store_map_path
361367
)
362368
if self._used_api == CONF_AVAILABLE_API_XIAOMI:
363369
return RoborockCloudVacuum(vacuum_config)
364370
if self._used_api == CONF_AVAILABLE_API_VIOMI:
365371
return ViomiCloudVacuum(vacuum_config)
366372
if self._used_api == CONF_AVAILABLE_API_IJAI:
367-
return IjaiCloudVacuum(self._connector, self._country, user_id, device_id, model, mac)
373+
_LOGGER.debug(f"palette={vacuum_config.palette}")
374+
_LOGGER.debug(f"image_config={vacuum_config.image_config}")
375+
return IjaiCloudVacuum(vacuum_config)
368376
if self._used_api == CONF_AVAILABLE_API_ROIDMI:
369377
return RoidmiCloudVacuum(vacuum_config)
370378
if self._used_api == CONF_AVAILABLE_API_DREAME:

custom_components/xiaomi_cloud_map_extractor/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
CONF_Y = "y"
4444

4545
CONF_AVAILABLE_APIS = [CONF_AVAILABLE_API_XIAOMI, CONF_AVAILABLE_API_VIOMI, CONF_AVAILABLE_API_ROIDMI,
46-
CONF_AVAILABLE_API_DREAME]
46+
CONF_AVAILABLE_API_DREAME, CONF_AVAILABLE_API_IJAI]
4747

4848
CONF_AVAILABLE_SIZES = [CONF_SIZE_VACUUM_RADIUS, CONF_SIZE_PATH_WIDTH, CONF_SIZE_IGNORED_OBSTACLE_RADIUS,
4949
CONF_SIZE_IGNORED_OBSTACLE_WITH_PHOTO_RADIUS, CONF_SIZE_MOP_PATH_WIDTH,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from dataclasses import fields
2+
3+
def from_dict(cls, d):
4+
try:
5+
fieldtypes = {f.name:f.type for f in fields(cls)}
6+
return cls(**{f:from_dict(fieldtypes[f],d[f]) for f in d})
7+
except:
8+
return d

custom_components/xiaomi_cloud_map_extractor/vacuum_platforms/vacuum_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
from .xiaomi_cloud_connector import XiaomiCloudConnector
1313

14-
1514
@dataclass
1615
class VacuumConfig:
1716
connector: XiaomiCloudConnector
@@ -21,6 +20,7 @@ class VacuumConfig:
2120
host: str
2221
token: str
2322
model: str
23+
_mac: str
2424
palette: ColorsPalette
2525
drawables: list[Drawable]
2626
image_config: ImageConfig
Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,59 @@
1-
import zlib
21

3-
from custom_components.xiaomi_cloud_map_extractor.common.map_data import MapData
4-
from custom_components.xiaomi_cloud_map_extractor.common.vacuum_v2 import XiaomiCloudVacuumV2
5-
from custom_components.xiaomi_cloud_map_extractor.common.xiaomi_cloud_connector import XiaomiCloudConnector
6-
from custom_components.xiaomi_cloud_map_extractor.types import Colors, Drawables, ImageConfig, Sizes, Texts
7-
from custom_components.xiaomi_cloud_map_extractor.ijai.map_data_parser import MapDataParserIjai
8-
from custom_components.xiaomi_cloud_map_extractor.ijai.aes_decryptor import *
2+
from vacuum_map_parser_ijai.map_data_parser import IjaiMapDataParser
3+
from miio.miot_device import MiotDevice
94

10-
class IjaiCloudVacuum(XiaomiCloudVacuumV2):
11-
12-
def __init__(self, connector: XiaomiCloudConnector, country: str, user_id: str, device_id: str, model: str, mac:str):
13-
super().__init__(connector, country, user_id, device_id, model, mac)
5+
from .vacuum_v2 import XiaomiCloudVacuumV2
6+
from .vacuum_base import VacuumConfig
7+
import logging
8+
_LOGGER = logging.getLogger(__name__)
149

15-
def get_map_url(self, map_name: str) -> str | None:
16-
url = self._connector.get_api_url(self._country) + '/v2/home/get_interim_file_url/pro'
17-
18-
def decode_map(self,
19-
raw_map: bytes,
20-
colors: Colors,
21-
drawables: Drawables,
22-
texts: Texts,
23-
sizes: Sizes,
24-
image_config: ImageConfig) -> MapData:
10+
class IjaiCloudVacuum(XiaomiCloudVacuumV2):
11+
WIFI_STR_LEN = 18
12+
WIFI_STR_POS = 11
13+
def __init__(self, vacuum_config: VacuumConfig):
14+
super().__init__(vacuum_config)
15+
self._token = vacuum_config.token
16+
self._host = vacuum_config.host
17+
self._mac = vacuum_config._mac
18+
self._wifi_info_sn = None
2519

26-
unzipped = zlib.decompress(raw_map)
27-
28-
return MapDataParserIjai.parse(unzipped, colors, drawables, texts, sizes, image_config)
29-
30-
def get_map_archive_extension(self) -> str:
20+
self._ijai_map_data_parser = IjaiMapDataParser(
21+
vacuum_config.palette,
22+
vacuum_config.sizes,
23+
vacuum_config.drawables,
24+
vacuum_config.image_config,
25+
vacuum_config.texts
26+
)
27+
@property
28+
def map_archive_extension(self) -> str:
3129
return "zlib"
3230

33-
def decrypt_map(self, data:bytes, wifi_info_sn:str, user_id:str, device_id:str, model:str, mac:str):
34-
return unGzipCommon(data=data, \
35-
wifi_info_sn=wifi_info_sn, \
36-
owner_id=str(user_id), \
37-
device_id=device_id, \
38-
model=model, \
39-
device_mac=mac);
31+
@property
32+
def map_data_parser(self) -> IjaiMapDataParser:
33+
return self._ijai_map_data_parser
34+
35+
def get_map_url(self, map_name: str) -> str | None:
36+
url = self._connector.get_api_url(self._country) + '/v2/home/get_interim_file_url_pro'
37+
params = {
38+
"data": f'{{"obj_name":"{self._user_id}/{self._device_id}/{map_name}"}}'
39+
}
40+
api_response = self._connector.execute_api_call_encrypted(url, params)
41+
if api_response is None or ("result" not in api_response) or (api_response["result"] is None) or ("url" not in api_response["result"]):
42+
self._LOGGER.debug(f"API returned {api_response['code']}" + "(" + api_response["message"] + ")")
43+
return None
44+
return api_response["result"]["url"]
45+
46+
def decode_and_parse(self, raw_map: bytes):
47+
if self._wifi_info_sn is None or self._wifi_info_sn is "":
48+
device = MiotDevice(self._host, self._token)
49+
props = device.get_property_by(7, 45)[0]["value"].split(',')
50+
self._wifi_info_sn = props[self.WIFI_STR_POS].replace('"','')[:self.WIFI_STR_LEN]
51+
_LOGGER.debug(f"wifi_sn = {self._wifi_info_sn}")
52+
53+
decoded_map = self.map_data_parser.unpack_map(raw_map,
54+
wifi_sn=self._wifi_info_sn,
55+
owner_id = str(self._user_id),
56+
device_id = str(self._device_id),
57+
model = self.model,
58+
device_mac = self._mac)
59+
return self.map_data_parser.parse(decoded_map)

0 commit comments

Comments
 (0)