python-roborock 1.0.0__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_roborock-1.0.0 → python_roborock-2.0.0}/PKG-INFO +1 -1
- {python_roborock-1.0.0 → python_roborock-2.0.0}/pyproject.toml +1 -1
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/api.py +0 -9
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/code_mappings.py +135 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/protocol.py +2 -3
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/roborock_message.py +42 -0
- python_roborock-2.0.0/roborock/version_a01_apis/roborock_client_a01.py +142 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +21 -8
- python_roborock-1.0.0/roborock/version_a01_apis/roborock_client_a01.py +0 -100
- {python_roborock-1.0.0 → python_roborock-2.0.0}/LICENSE +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/README.md +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/__init__.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/cli.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/cloud_api.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/command_cache.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/const.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/containers.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/exceptions.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/local_api.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/py.typed +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/roborock_future.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/util.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/web_api.py +0 -0
|
@@ -12,9 +12,6 @@ from typing import Any
|
|
|
12
12
|
|
|
13
13
|
from .containers import (
|
|
14
14
|
DeviceData,
|
|
15
|
-
ModelStatus,
|
|
16
|
-
S7MaxVStatus,
|
|
17
|
-
Status,
|
|
18
15
|
)
|
|
19
16
|
from .exceptions import (
|
|
20
17
|
RoborockTimeout,
|
|
@@ -48,16 +45,10 @@ class RoborockClient:
|
|
|
48
45
|
self._logger = RoborockLoggerAdapter(device_info.device.name, _LOGGER)
|
|
49
46
|
self.is_available: bool = True
|
|
50
47
|
self.queue_timeout = queue_timeout
|
|
51
|
-
self._status_type: type[Status] = ModelStatus.get(self.device_info.model, S7MaxVStatus)
|
|
52
48
|
|
|
53
49
|
def __del__(self) -> None:
|
|
54
50
|
self.release()
|
|
55
51
|
|
|
56
|
-
@property
|
|
57
|
-
def status_type(self) -> type[Status]:
|
|
58
|
-
"""Gets the status type for this device"""
|
|
59
|
-
return self._status_type
|
|
60
|
-
|
|
61
52
|
def release(self):
|
|
62
53
|
self.sync_disconnect()
|
|
63
54
|
|
|
@@ -450,3 +450,138 @@ class DyadError(RoborockEnum):
|
|
|
450
450
|
dirty_charging_contacts = 10007 # Disconnection between the device and dock. Wipe charging contacts.
|
|
451
451
|
low_battery = 20017 # Low battery level. Charge before starting self-cleaning.
|
|
452
452
|
battery_under_10 = 20018 # Charge until the battery level exceeds 10% before manually starting self-cleaning.
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class ZeoMode(RoborockEnum):
|
|
456
|
+
wash = 1
|
|
457
|
+
wash_and_dry = 2
|
|
458
|
+
dry = 3
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class ZeoState(RoborockEnum):
|
|
462
|
+
standby = 1
|
|
463
|
+
weighing = 2
|
|
464
|
+
soaking = 3
|
|
465
|
+
washing = 4
|
|
466
|
+
rinsing = 5
|
|
467
|
+
spinning = 6
|
|
468
|
+
drying = 7
|
|
469
|
+
cooling = 8
|
|
470
|
+
under_delay_start = 9
|
|
471
|
+
done = 10
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class ZeoProgram(RoborockEnum):
|
|
475
|
+
standard = 1
|
|
476
|
+
quick = 2
|
|
477
|
+
sanitize = 3
|
|
478
|
+
wool = 4
|
|
479
|
+
air_refresh = 5
|
|
480
|
+
custom = 6
|
|
481
|
+
bedding = 7
|
|
482
|
+
down = 8
|
|
483
|
+
silk = 9
|
|
484
|
+
rinse_and_spin = 10
|
|
485
|
+
spin = 11
|
|
486
|
+
down_clean = 12
|
|
487
|
+
baby_care = 13
|
|
488
|
+
anti_allergen = 14
|
|
489
|
+
sportswear = 15
|
|
490
|
+
night = 16
|
|
491
|
+
new_clothes = 17
|
|
492
|
+
shirts = 18
|
|
493
|
+
synthetics = 19
|
|
494
|
+
underwear = 20
|
|
495
|
+
gentle = 21
|
|
496
|
+
intensive = 22
|
|
497
|
+
cotton_linen = 23
|
|
498
|
+
season = 24
|
|
499
|
+
warming = 25
|
|
500
|
+
bra = 26
|
|
501
|
+
panties = 27
|
|
502
|
+
boiling_wash = 28
|
|
503
|
+
socks = 30
|
|
504
|
+
towels = 31
|
|
505
|
+
anti_mite = 32
|
|
506
|
+
exo_40_60 = 33
|
|
507
|
+
twenty_c = 34
|
|
508
|
+
t_shirts = 35
|
|
509
|
+
stain_removal = 36
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
class ZeoSoak(RoborockEnum):
|
|
513
|
+
normal = 0
|
|
514
|
+
low = 1
|
|
515
|
+
medium = 2
|
|
516
|
+
high = 3
|
|
517
|
+
max = 4
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
class ZeoTemperature(RoborockEnum):
|
|
521
|
+
normal = 1
|
|
522
|
+
low = 2
|
|
523
|
+
medium = 3
|
|
524
|
+
high = 4
|
|
525
|
+
max = 5
|
|
526
|
+
twenty_c = 6
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
class ZeoRinse(RoborockEnum):
|
|
530
|
+
none = 0
|
|
531
|
+
min = 1
|
|
532
|
+
low = 2
|
|
533
|
+
mid = 3
|
|
534
|
+
high = 4
|
|
535
|
+
max = 5
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
class ZeoSpin(RoborockEnum):
|
|
539
|
+
none = 1
|
|
540
|
+
very_low = 2
|
|
541
|
+
low = 3
|
|
542
|
+
mid = 4
|
|
543
|
+
high = 5
|
|
544
|
+
very_high = 6
|
|
545
|
+
max = 7
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
class ZeoDryingMode(RoborockEnum):
|
|
549
|
+
none = 0
|
|
550
|
+
quick = 1
|
|
551
|
+
iron = 2
|
|
552
|
+
store = 3
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
class ZeoDetergentType(RoborockEnum):
|
|
556
|
+
empty = 0
|
|
557
|
+
low = 1
|
|
558
|
+
medium = 2
|
|
559
|
+
high = 3
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class ZeoSoftenerType(RoborockEnum):
|
|
563
|
+
empty = 0
|
|
564
|
+
low = 1
|
|
565
|
+
medium = 2
|
|
566
|
+
high = 3
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
class ZeoError(RoborockEnum):
|
|
570
|
+
none = 0
|
|
571
|
+
refill_error = 1
|
|
572
|
+
drain_error = 2
|
|
573
|
+
door_lock_error = 3
|
|
574
|
+
water_level_error = 4
|
|
575
|
+
inverter_error = 5
|
|
576
|
+
heating_error = 6
|
|
577
|
+
temperature_error = 7
|
|
578
|
+
communication_error = 10
|
|
579
|
+
drying_error = 11
|
|
580
|
+
drying_error_e_12 = 12
|
|
581
|
+
drying_error_e_13 = 13
|
|
582
|
+
drying_error_e_14 = 14
|
|
583
|
+
drying_error_e_15 = 15
|
|
584
|
+
drying_error_e_16 = 16
|
|
585
|
+
drying_error_water_flow = 17 # Check for normal water flow
|
|
586
|
+
drying_error_restart = 18 # Restart the washer and try again
|
|
587
|
+
spin_error = 19 # re-arrange clothes
|
|
@@ -36,7 +36,6 @@ from roborock.roborock_message import RoborockMessage
|
|
|
36
36
|
_LOGGER = logging.getLogger(__name__)
|
|
37
37
|
SALT = b"TXdfu$jyZ#TZHsg4"
|
|
38
38
|
A01_HASH = "726f626f726f636b2d67a6d6da"
|
|
39
|
-
A01_AES_DECIPHER = "ELSYN0wTI4AUm7C4"
|
|
40
39
|
BROADCAST_TOKEN = b"qWKYcdQWrbm9hPqe"
|
|
41
40
|
AP_CONFIG = 1
|
|
42
41
|
SOCK_DISCOVERY = 2
|
|
@@ -208,7 +207,7 @@ class EncryptionAdapter(Construct):
|
|
|
208
207
|
"""
|
|
209
208
|
if context.version == b"A01":
|
|
210
209
|
iv = md5hex(format(context.random, "08x") + A01_HASH)[8:24]
|
|
211
|
-
decipher = AES.new(bytes(
|
|
210
|
+
decipher = AES.new(bytes(context.search("local_key"), "utf-8"), AES.MODE_CBC, bytes(iv, "utf-8"))
|
|
212
211
|
f = decipher.encrypt(obj)
|
|
213
212
|
return f
|
|
214
213
|
token = self.token_func(context)
|
|
@@ -219,7 +218,7 @@ class EncryptionAdapter(Construct):
|
|
|
219
218
|
"""Decrypts the given payload with the token stored in the context."""
|
|
220
219
|
if context.version == b"A01":
|
|
221
220
|
iv = md5hex(format(context.random, "08x") + A01_HASH)[8:24]
|
|
222
|
-
decipher = AES.new(bytes(
|
|
221
|
+
decipher = AES.new(bytes(context.search("local_key"), "utf-8"), AES.MODE_CBC, bytes(iv, "utf-8"))
|
|
223
222
|
f = decipher.decrypt(obj)
|
|
224
223
|
return f
|
|
225
224
|
token = self.token_func(context)
|
|
@@ -87,6 +87,48 @@ class RoborockDyadDataProtocol(RoborockEnum):
|
|
|
87
87
|
RPC_RESPONSE = 10102
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
class RoborockZeoProtocol(RoborockEnum):
|
|
91
|
+
START = 200 # rw
|
|
92
|
+
PAUSE = 201 # rw
|
|
93
|
+
SHUTDOWN = 202 # rw
|
|
94
|
+
STATE = 203 # ro
|
|
95
|
+
MODE = 204 # rw
|
|
96
|
+
PROGRAM = 205 # rw
|
|
97
|
+
CHILD_LOCK = 206 # rw
|
|
98
|
+
TEMP = 207 # rw
|
|
99
|
+
RINSE_TIMES = 208 # rw
|
|
100
|
+
SPIN_LEVEL = 209 # rw
|
|
101
|
+
DRYING_MODE = 210 # rw
|
|
102
|
+
DETERGENT_SET = 211 # rw
|
|
103
|
+
SOFTENER_SET = 212 # rw
|
|
104
|
+
DETERGENT_TYPE = 213 # rw
|
|
105
|
+
SOFTENER_TYPE = 214 # rw
|
|
106
|
+
COUNTDOWN = 217 # rw
|
|
107
|
+
WASHING_LEFT = 218 # ro
|
|
108
|
+
DOORLOCK_STATE = 219 # ro
|
|
109
|
+
ERROR = 220 # ro
|
|
110
|
+
CUSTOM_PARAM_SAVE = 221 # rw
|
|
111
|
+
CUSTOM_PARAM_GET = 222 # ro
|
|
112
|
+
SOUND_SET = 223 # rw
|
|
113
|
+
TIMES_AFTER_CLEAN = 224 # ro
|
|
114
|
+
DEFAULT_SETTING = 225 # rw
|
|
115
|
+
DETERGENT_EMPTY = 226 # ro
|
|
116
|
+
SOFTENER_EMPTY = 227 # ro
|
|
117
|
+
LIGHT_SETTING = 229 # rw
|
|
118
|
+
DETERGENT_VOLUME = 230 # rw
|
|
119
|
+
SOFTENER_VOLUME = 231 # rw
|
|
120
|
+
APP_AUTHORIZATION = 232 # rw
|
|
121
|
+
ID_QUERY = 10000
|
|
122
|
+
F_C = 10001
|
|
123
|
+
SND_STATE = 10004
|
|
124
|
+
PRODUCT_INFO = 10005
|
|
125
|
+
PRIVACY_INFO = 10006
|
|
126
|
+
OTA_NFO = 10007
|
|
127
|
+
WASHING_LOG = 10008
|
|
128
|
+
RPC_REQ = 10101
|
|
129
|
+
RPC_RESp = 10102
|
|
130
|
+
|
|
131
|
+
|
|
90
132
|
ROBOROCK_DATA_STATUS_PROTOCOL = [
|
|
91
133
|
RoborockDataProtocol.ERROR_CODE,
|
|
92
134
|
RoborockDataProtocol.STATE,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import json
|
|
3
|
+
import typing
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from datetime import time
|
|
6
|
+
|
|
7
|
+
from Crypto.Cipher import AES
|
|
8
|
+
from Crypto.Util.Padding import unpad
|
|
9
|
+
|
|
10
|
+
from roborock import DeviceData
|
|
11
|
+
from roborock.api import RoborockClient
|
|
12
|
+
from roborock.code_mappings import (
|
|
13
|
+
DyadBrushSpeed,
|
|
14
|
+
DyadCleanMode,
|
|
15
|
+
DyadError,
|
|
16
|
+
DyadSelfCleanLevel,
|
|
17
|
+
DyadSelfCleanMode,
|
|
18
|
+
DyadSuction,
|
|
19
|
+
DyadWarmLevel,
|
|
20
|
+
DyadWaterLevel,
|
|
21
|
+
RoborockDyadStateCode,
|
|
22
|
+
ZeoDetergentType,
|
|
23
|
+
ZeoDryingMode,
|
|
24
|
+
ZeoError,
|
|
25
|
+
ZeoMode,
|
|
26
|
+
ZeoProgram,
|
|
27
|
+
ZeoRinse,
|
|
28
|
+
ZeoSoftenerType,
|
|
29
|
+
ZeoSpin,
|
|
30
|
+
ZeoState,
|
|
31
|
+
ZeoTemperature,
|
|
32
|
+
)
|
|
33
|
+
from roborock.containers import DyadProductInfo, DyadSndState, RoborockCategory
|
|
34
|
+
from roborock.roborock_message import (
|
|
35
|
+
RoborockDyadDataProtocol,
|
|
36
|
+
RoborockMessage,
|
|
37
|
+
RoborockMessageProtocol,
|
|
38
|
+
RoborockZeoProtocol,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclasses.dataclass
|
|
43
|
+
class A01ProtocolCacheEntry:
|
|
44
|
+
post_process_fn: Callable
|
|
45
|
+
value: typing.Any | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Right now this cache is not active, it was too much complexity for the initial addition of dyad.
|
|
49
|
+
protocol_entries = {
|
|
50
|
+
RoborockDyadDataProtocol.STATUS: A01ProtocolCacheEntry(lambda val: RoborockDyadStateCode(val).name),
|
|
51
|
+
RoborockDyadDataProtocol.SELF_CLEAN_MODE: A01ProtocolCacheEntry(lambda val: DyadSelfCleanMode(val).name),
|
|
52
|
+
RoborockDyadDataProtocol.SELF_CLEAN_LEVEL: A01ProtocolCacheEntry(lambda val: DyadSelfCleanLevel(val).name),
|
|
53
|
+
RoborockDyadDataProtocol.WARM_LEVEL: A01ProtocolCacheEntry(lambda val: DyadWarmLevel(val).name),
|
|
54
|
+
RoborockDyadDataProtocol.CLEAN_MODE: A01ProtocolCacheEntry(lambda val: DyadCleanMode(val).name),
|
|
55
|
+
RoborockDyadDataProtocol.SUCTION: A01ProtocolCacheEntry(lambda val: DyadSuction(val).name),
|
|
56
|
+
RoborockDyadDataProtocol.WATER_LEVEL: A01ProtocolCacheEntry(lambda val: DyadWaterLevel(val).name),
|
|
57
|
+
RoborockDyadDataProtocol.BRUSH_SPEED: A01ProtocolCacheEntry(lambda val: DyadBrushSpeed(val).name),
|
|
58
|
+
RoborockDyadDataProtocol.POWER: A01ProtocolCacheEntry(lambda val: int(val)),
|
|
59
|
+
RoborockDyadDataProtocol.AUTO_DRY: A01ProtocolCacheEntry(lambda val: bool(val)),
|
|
60
|
+
RoborockDyadDataProtocol.MESH_LEFT: A01ProtocolCacheEntry(lambda val: int(360000 - val * 60)),
|
|
61
|
+
RoborockDyadDataProtocol.BRUSH_LEFT: A01ProtocolCacheEntry(lambda val: int(360000 - val * 60)),
|
|
62
|
+
RoborockDyadDataProtocol.ERROR: A01ProtocolCacheEntry(lambda val: DyadError(val).name),
|
|
63
|
+
RoborockDyadDataProtocol.VOLUME_SET: A01ProtocolCacheEntry(lambda val: int(val)),
|
|
64
|
+
RoborockDyadDataProtocol.STAND_LOCK_AUTO_RUN: A01ProtocolCacheEntry(lambda val: bool(val)),
|
|
65
|
+
RoborockDyadDataProtocol.AUTO_DRY_MODE: A01ProtocolCacheEntry(lambda val: bool(val)),
|
|
66
|
+
RoborockDyadDataProtocol.SILENT_DRY_DURATION: A01ProtocolCacheEntry(lambda val: int(val)), # in minutes
|
|
67
|
+
RoborockDyadDataProtocol.SILENT_MODE: A01ProtocolCacheEntry(lambda val: bool(val)),
|
|
68
|
+
RoborockDyadDataProtocol.SILENT_MODE_START_TIME: A01ProtocolCacheEntry(
|
|
69
|
+
lambda val: time(hour=int(val / 60), minute=val % 60)
|
|
70
|
+
), # in minutes since 00:00
|
|
71
|
+
RoborockDyadDataProtocol.SILENT_MODE_END_TIME: A01ProtocolCacheEntry(
|
|
72
|
+
lambda val: time(hour=int(val / 60), minute=val % 60)
|
|
73
|
+
), # in minutes since 00:00
|
|
74
|
+
RoborockDyadDataProtocol.RECENT_RUN_TIME: A01ProtocolCacheEntry(
|
|
75
|
+
lambda val: [int(v) for v in val.split(",")]
|
|
76
|
+
), # minutes of cleaning in past few days.
|
|
77
|
+
RoborockDyadDataProtocol.TOTAL_RUN_TIME: A01ProtocolCacheEntry(lambda val: int(val)),
|
|
78
|
+
RoborockDyadDataProtocol.SND_STATE: A01ProtocolCacheEntry(lambda val: DyadSndState.from_dict(val)),
|
|
79
|
+
RoborockDyadDataProtocol.PRODUCT_INFO: A01ProtocolCacheEntry(lambda val: DyadProductInfo.from_dict(val)),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
zeo_data_protocol_entries = {
|
|
83
|
+
# ro
|
|
84
|
+
RoborockZeoProtocol.STATE: A01ProtocolCacheEntry(lambda val: ZeoState(val).name),
|
|
85
|
+
RoborockZeoProtocol.COUNTDOWN: A01ProtocolCacheEntry(lambda val: int(val)),
|
|
86
|
+
RoborockZeoProtocol.WASHING_LEFT: A01ProtocolCacheEntry(lambda val: int(val)),
|
|
87
|
+
RoborockZeoProtocol.ERROR: A01ProtocolCacheEntry(lambda val: ZeoError(val).name),
|
|
88
|
+
RoborockZeoProtocol.TIMES_AFTER_CLEAN: A01ProtocolCacheEntry(lambda val: int(val)),
|
|
89
|
+
RoborockZeoProtocol.DETERGENT_EMPTY: A01ProtocolCacheEntry(lambda val: bool(val)),
|
|
90
|
+
RoborockZeoProtocol.SOFTENER_EMPTY: A01ProtocolCacheEntry(lambda val: bool(val)),
|
|
91
|
+
# rw
|
|
92
|
+
RoborockZeoProtocol.MODE: A01ProtocolCacheEntry(lambda val: ZeoMode(val).name),
|
|
93
|
+
RoborockZeoProtocol.PROGRAM: A01ProtocolCacheEntry(lambda val: ZeoProgram(val).name),
|
|
94
|
+
RoborockZeoProtocol.TEMP: A01ProtocolCacheEntry(lambda val: ZeoTemperature(val).name),
|
|
95
|
+
RoborockZeoProtocol.RINSE_TIMES: A01ProtocolCacheEntry(lambda val: ZeoRinse(val).name),
|
|
96
|
+
RoborockZeoProtocol.SPIN_LEVEL: A01ProtocolCacheEntry(lambda val: ZeoSpin(val).name),
|
|
97
|
+
RoborockZeoProtocol.DRYING_MODE: A01ProtocolCacheEntry(lambda val: ZeoDryingMode(val).name),
|
|
98
|
+
RoborockZeoProtocol.DETERGENT_TYPE: A01ProtocolCacheEntry(lambda val: ZeoDetergentType(val).name),
|
|
99
|
+
RoborockZeoProtocol.SOFTENER_TYPE: A01ProtocolCacheEntry(lambda val: ZeoSoftenerType(val).name),
|
|
100
|
+
RoborockZeoProtocol.SOUND_SET: A01ProtocolCacheEntry(lambda val: bool(val)),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class RoborockClientA01(RoborockClient):
|
|
105
|
+
def __init__(self, endpoint: str, device_info: DeviceData, category: RoborockCategory, queue_timeout: int = 4):
|
|
106
|
+
super().__init__(endpoint, device_info, queue_timeout)
|
|
107
|
+
self.category = category
|
|
108
|
+
|
|
109
|
+
def on_message_received(self, messages: list[RoborockMessage]) -> None:
|
|
110
|
+
for message in messages:
|
|
111
|
+
protocol = message.protocol
|
|
112
|
+
if message.payload and protocol in [
|
|
113
|
+
RoborockMessageProtocol.RPC_RESPONSE,
|
|
114
|
+
RoborockMessageProtocol.GENERAL_REQUEST,
|
|
115
|
+
]:
|
|
116
|
+
payload = message.payload
|
|
117
|
+
try:
|
|
118
|
+
payload = unpad(payload, AES.block_size)
|
|
119
|
+
except Exception:
|
|
120
|
+
continue
|
|
121
|
+
payload_json = json.loads(payload.decode())
|
|
122
|
+
for data_point_number, data_point in payload_json.get("dps").items():
|
|
123
|
+
data_point_protocol: RoborockDyadDataProtocol | RoborockZeoProtocol
|
|
124
|
+
entries: dict
|
|
125
|
+
if self.category == RoborockCategory.WET_DRY_VAC:
|
|
126
|
+
data_point_protocol = RoborockDyadDataProtocol(int(data_point_number))
|
|
127
|
+
entries = protocol_entries
|
|
128
|
+
elif self.category == RoborockCategory.WASHING_MACHINE:
|
|
129
|
+
data_point_protocol = RoborockZeoProtocol(int(data_point_number))
|
|
130
|
+
entries = zeo_data_protocol_entries
|
|
131
|
+
else:
|
|
132
|
+
continue
|
|
133
|
+
if data_point_protocol in entries:
|
|
134
|
+
# Auto convert into data struct we want.
|
|
135
|
+
converted_response = entries[data_point_protocol].post_process_fn(data_point)
|
|
136
|
+
queue = self._waiting_queue.get(int(data_point_number))
|
|
137
|
+
if queue and queue.protocol == protocol:
|
|
138
|
+
queue.resolve((converted_response, None))
|
|
139
|
+
|
|
140
|
+
async def update_values(self, dyad_data_protocols: list[RoborockDyadDataProtocol | RoborockZeoProtocol]):
|
|
141
|
+
"""This should handle updating for each given protocol."""
|
|
142
|
+
raise NotImplementedError
|
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import base64
|
|
3
3
|
import json
|
|
4
|
+
import typing
|
|
4
5
|
|
|
5
6
|
from Crypto.Cipher import AES
|
|
6
7
|
from Crypto.Util.Padding import pad, unpad
|
|
7
8
|
|
|
8
9
|
from roborock.cloud_api import RoborockMqttClient
|
|
9
|
-
from roborock.containers import DeviceData, UserData
|
|
10
|
+
from roborock.containers import DeviceData, RoborockCategory, UserData
|
|
10
11
|
from roborock.exceptions import RoborockException
|
|
11
12
|
from roborock.protocol import MessageParser, Utils
|
|
12
|
-
from roborock.roborock_message import
|
|
13
|
+
from roborock.roborock_message import (
|
|
14
|
+
RoborockDyadDataProtocol,
|
|
15
|
+
RoborockMessage,
|
|
16
|
+
RoborockMessageProtocol,
|
|
17
|
+
RoborockZeoProtocol,
|
|
18
|
+
)
|
|
13
19
|
|
|
14
20
|
from .roborock_client_a01 import RoborockClientA01
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
class RoborockMqttClientA01(RoborockMqttClient, RoborockClientA01):
|
|
18
|
-
def __init__(
|
|
24
|
+
def __init__(
|
|
25
|
+
self, user_data: UserData, device_info: DeviceData, category: RoborockCategory, queue_timeout: int = 10
|
|
26
|
+
) -> None:
|
|
19
27
|
rriot = user_data.rriot
|
|
20
28
|
if rriot is None:
|
|
21
29
|
raise RoborockException("Got no rriot data from user_data")
|
|
22
30
|
endpoint = base64.b64encode(Utils.md5(rriot.k.encode())[8:14]).decode()
|
|
23
31
|
|
|
24
32
|
RoborockMqttClient.__init__(self, user_data, device_info, queue_timeout)
|
|
25
|
-
RoborockClientA01.__init__(self, endpoint, device_info)
|
|
33
|
+
RoborockClientA01.__init__(self, endpoint, device_info, category, queue_timeout)
|
|
26
34
|
|
|
27
35
|
async def send_message(self, roborock_message: RoborockMessage):
|
|
28
36
|
await self.validate_connection()
|
|
@@ -37,14 +45,19 @@ class RoborockMqttClientA01(RoborockMqttClient, RoborockClientA01):
|
|
|
37
45
|
for dps in json.loads(payload["dps"]["10000"]):
|
|
38
46
|
futures.append(asyncio.ensure_future(self._async_response(dps, response_protocol)))
|
|
39
47
|
self._send_msg_raw(m)
|
|
40
|
-
responses = await asyncio.gather(*futures)
|
|
41
|
-
dps_responses = {}
|
|
48
|
+
responses = await asyncio.gather(*futures, return_exceptions=True)
|
|
49
|
+
dps_responses: dict[int, typing.Any] = {}
|
|
42
50
|
if "10000" in payload["dps"]:
|
|
43
51
|
for i, dps in enumerate(json.loads(payload["dps"]["10000"])):
|
|
44
|
-
|
|
52
|
+
response = responses[i]
|
|
53
|
+
if isinstance(response, BaseException):
|
|
54
|
+
self._logger.warning("Timed out get req for %s after %s s", dps, self.queue_timeout)
|
|
55
|
+
dps_responses[dps] = None
|
|
56
|
+
else:
|
|
57
|
+
dps_responses[dps] = response[0]
|
|
45
58
|
return dps_responses
|
|
46
59
|
|
|
47
|
-
async def update_values(self, dyad_data_protocols: list[RoborockDyadDataProtocol]):
|
|
60
|
+
async def update_values(self, dyad_data_protocols: list[RoborockDyadDataProtocol | RoborockZeoProtocol]):
|
|
48
61
|
payload = {"dps": {RoborockDyadDataProtocol.ID_QUERY: str([int(protocol) for protocol in dyad_data_protocols])}}
|
|
49
62
|
return await self.send_message(
|
|
50
63
|
RoborockMessage(
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import json
|
|
3
|
-
import typing
|
|
4
|
-
from collections.abc import Callable
|
|
5
|
-
from datetime import time
|
|
6
|
-
|
|
7
|
-
from Crypto.Cipher import AES
|
|
8
|
-
from Crypto.Util.Padding import unpad
|
|
9
|
-
|
|
10
|
-
from roborock import DeviceData
|
|
11
|
-
from roborock.api import RoborockClient
|
|
12
|
-
from roborock.code_mappings import (
|
|
13
|
-
DyadBrushSpeed,
|
|
14
|
-
DyadCleanMode,
|
|
15
|
-
DyadError,
|
|
16
|
-
DyadSelfCleanLevel,
|
|
17
|
-
DyadSelfCleanMode,
|
|
18
|
-
DyadSuction,
|
|
19
|
-
DyadWarmLevel,
|
|
20
|
-
DyadWaterLevel,
|
|
21
|
-
RoborockDyadStateCode,
|
|
22
|
-
)
|
|
23
|
-
from roborock.containers import DyadProductInfo, DyadSndState
|
|
24
|
-
from roborock.roborock_message import (
|
|
25
|
-
RoborockDyadDataProtocol,
|
|
26
|
-
RoborockMessage,
|
|
27
|
-
RoborockMessageProtocol,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@dataclasses.dataclass
|
|
32
|
-
class DyadProtocolCacheEntry:
|
|
33
|
-
post_process_fn: Callable
|
|
34
|
-
value: typing.Any | None = None
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# Right now this cache is not active, it was too much complexity for the initial addition of dyad.
|
|
38
|
-
protocol_entries = {
|
|
39
|
-
RoborockDyadDataProtocol.STATUS: DyadProtocolCacheEntry(lambda val: RoborockDyadStateCode(val).name),
|
|
40
|
-
RoborockDyadDataProtocol.SELF_CLEAN_MODE: DyadProtocolCacheEntry(lambda val: DyadSelfCleanMode(val).name),
|
|
41
|
-
RoborockDyadDataProtocol.SELF_CLEAN_LEVEL: DyadProtocolCacheEntry(lambda val: DyadSelfCleanLevel(val).name),
|
|
42
|
-
RoborockDyadDataProtocol.WARM_LEVEL: DyadProtocolCacheEntry(lambda val: DyadWarmLevel(val).name),
|
|
43
|
-
RoborockDyadDataProtocol.CLEAN_MODE: DyadProtocolCacheEntry(lambda val: DyadCleanMode(val).name),
|
|
44
|
-
RoborockDyadDataProtocol.SUCTION: DyadProtocolCacheEntry(lambda val: DyadSuction(val).name),
|
|
45
|
-
RoborockDyadDataProtocol.WATER_LEVEL: DyadProtocolCacheEntry(lambda val: DyadWaterLevel(val).name),
|
|
46
|
-
RoborockDyadDataProtocol.BRUSH_SPEED: DyadProtocolCacheEntry(lambda val: DyadBrushSpeed(val).name),
|
|
47
|
-
RoborockDyadDataProtocol.POWER: DyadProtocolCacheEntry(lambda val: int(val)),
|
|
48
|
-
RoborockDyadDataProtocol.AUTO_DRY: DyadProtocolCacheEntry(lambda val: bool(val)),
|
|
49
|
-
RoborockDyadDataProtocol.MESH_LEFT: DyadProtocolCacheEntry(lambda val: int(360000 - val * 60)),
|
|
50
|
-
RoborockDyadDataProtocol.BRUSH_LEFT: DyadProtocolCacheEntry(lambda val: int(360000 - val * 60)),
|
|
51
|
-
RoborockDyadDataProtocol.ERROR: DyadProtocolCacheEntry(lambda val: DyadError(val).name),
|
|
52
|
-
RoborockDyadDataProtocol.VOLUME_SET: DyadProtocolCacheEntry(lambda val: int(val)),
|
|
53
|
-
RoborockDyadDataProtocol.STAND_LOCK_AUTO_RUN: DyadProtocolCacheEntry(lambda val: bool(val)),
|
|
54
|
-
RoborockDyadDataProtocol.AUTO_DRY_MODE: DyadProtocolCacheEntry(lambda val: bool(val)),
|
|
55
|
-
RoborockDyadDataProtocol.SILENT_DRY_DURATION: DyadProtocolCacheEntry(lambda val: int(val)), # in minutes
|
|
56
|
-
RoborockDyadDataProtocol.SILENT_MODE: DyadProtocolCacheEntry(lambda val: bool(val)),
|
|
57
|
-
RoborockDyadDataProtocol.SILENT_MODE_START_TIME: DyadProtocolCacheEntry(
|
|
58
|
-
lambda val: time(hour=int(val / 60), minute=val % 60)
|
|
59
|
-
), # in minutes since 00:00
|
|
60
|
-
RoborockDyadDataProtocol.SILENT_MODE_END_TIME: DyadProtocolCacheEntry(
|
|
61
|
-
lambda val: time(hour=int(val / 60), minute=val % 60)
|
|
62
|
-
), # in minutes since 00:00
|
|
63
|
-
RoborockDyadDataProtocol.RECENT_RUN_TIME: DyadProtocolCacheEntry(
|
|
64
|
-
lambda val: [int(v) for v in val.split(",")]
|
|
65
|
-
), # minutes of cleaning in past few days.
|
|
66
|
-
RoborockDyadDataProtocol.TOTAL_RUN_TIME: DyadProtocolCacheEntry(lambda val: int(val)),
|
|
67
|
-
RoborockDyadDataProtocol.SND_STATE: DyadProtocolCacheEntry(lambda val: DyadSndState.from_dict(val)),
|
|
68
|
-
RoborockDyadDataProtocol.PRODUCT_INFO: DyadProtocolCacheEntry(lambda val: DyadProductInfo.from_dict(val)),
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
class RoborockClientA01(RoborockClient):
|
|
73
|
-
def __init__(self, endpoint: str, device_info: DeviceData):
|
|
74
|
-
super().__init__(endpoint, device_info)
|
|
75
|
-
|
|
76
|
-
def on_message_received(self, messages: list[RoborockMessage]) -> None:
|
|
77
|
-
for message in messages:
|
|
78
|
-
protocol = message.protocol
|
|
79
|
-
if message.payload and protocol in [
|
|
80
|
-
RoborockMessageProtocol.RPC_RESPONSE,
|
|
81
|
-
RoborockMessageProtocol.GENERAL_REQUEST,
|
|
82
|
-
]:
|
|
83
|
-
payload = message.payload
|
|
84
|
-
try:
|
|
85
|
-
payload = unpad(payload, AES.block_size)
|
|
86
|
-
except Exception:
|
|
87
|
-
continue
|
|
88
|
-
payload_json = json.loads(payload.decode())
|
|
89
|
-
for data_point_number, data_point in payload_json.get("dps").items():
|
|
90
|
-
data_point_protocol = RoborockDyadDataProtocol(int(data_point_number))
|
|
91
|
-
if data_point_protocol in protocol_entries:
|
|
92
|
-
# Auto convert into data struct we want.
|
|
93
|
-
converted_response = protocol_entries[data_point_protocol].post_process_fn(data_point)
|
|
94
|
-
queue = self._waiting_queue.get(int(data_point_number))
|
|
95
|
-
if queue and queue.protocol == protocol:
|
|
96
|
-
queue.resolve((converted_response, None))
|
|
97
|
-
|
|
98
|
-
async def update_values(self, dyad_data_protocols: list[RoborockDyadDataProtocol]):
|
|
99
|
-
"""This should handle updating for each given protocol."""
|
|
100
|
-
raise NotImplementedError
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
File without changes
|
{python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/version_1_apis/roborock_local_client_v1.py
RENAMED
|
File without changes
|
{python_roborock-1.0.0 → python_roborock-2.0.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|