Skip to content

Commit 7657617

Browse files
fix: improving local integration
1 parent a89d352 commit 7657617

File tree

6 files changed

+63
-73
lines changed

6 files changed

+63
-73
lines changed

roborock/api.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,7 @@ async def get_clean_summary(self, device_id: str) -> CleanSummary | None:
194194
elif isinstance(clean_summary, list):
195195
clean_time, clean_area, clean_count, records = unpack_list(clean_summary, 4)
196196
return CleanSummary(
197-
clean_time=clean_time,
198-
clean_area=clean_area,
199-
clean_count=clean_count,
200-
records=records
197+
clean_time=clean_time, clean_area=clean_area, clean_count=clean_count, records=records
201198
)
202199
elif isinstance(clean_summary, int):
203200
return CleanSummary(clean_time=clean_summary)
@@ -268,7 +265,9 @@ async def get_dock_summary(self, device_id: str, dock_type: RoborockEnum) -> Rob
268265
self.get_wash_towel_mode(device_id),
269266
self.get_smart_wash_params(device_id),
270267
]
271-
[dust_collection_mode, wash_towel_mode, smart_wash_params] = unpack_list(list(await asyncio.gather(*commands)), 3)
268+
[dust_collection_mode, wash_towel_mode, smart_wash_params] = unpack_list(
269+
list(await asyncio.gather(*commands)), 3
270+
)
272271

273272
return RoborockDockSummary(dust_collection_mode, wash_towel_mode, smart_wash_params)
274273
except RoborockTimeout as e:

roborock/cloud_api.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import threading
66
import uuid
77
from asyncio import Lock
8-
from typing import Any, Mapping, Optional
8+
from typing import Mapping, Optional
99
from urllib.parse import urlparse
1010

1111
import paho.mqtt.client as mqtt
@@ -137,34 +137,30 @@ def sync_disconnect(self) -> bool:
137137
return rc == mqtt.MQTT_ERR_SUCCESS
138138

139139
def sync_connect(self) -> bool:
140-
rc = mqtt.MQTT_ERR_AGAIN
141140
self.sync_start_loop()
142141
if not self.is_connected():
143142
if self._mqtt_port is None or self._mqtt_host is None:
144143
raise RoborockException("Mqtt information was not entered. Cannot connect.")
145144
_LOGGER.info("Connecting to mqtt")
146-
rc = super().connect(host=self._mqtt_host, port=self._mqtt_port, keepalive=MQTT_KEEPALIVE)
147-
if rc != mqtt.MQTT_ERR_SUCCESS:
148-
raise RoborockException(f"Failed to connect (rc:{rc})")
149-
return rc == mqtt.MQTT_ERR_SUCCESS
145+
super().connect_async(host=self._mqtt_host, port=self._mqtt_port, keepalive=MQTT_KEEPALIVE)
146+
return True
147+
return False
150148

151-
async def async_disconnect(self) -> Any:
149+
async def async_disconnect(self) -> None:
152150
async with self._mutex:
153151
disconnecting = self.sync_disconnect()
154152
if disconnecting:
155-
(response, err) = await self._async_response(DISCONNECT_REQUEST_ID)
153+
(_, err) = await self._async_response(DISCONNECT_REQUEST_ID)
156154
if err:
157155
raise RoborockException(err) from err
158-
return response
159156

160-
async def async_connect(self) -> Any:
157+
async def async_connect(self) -> None:
161158
async with self._mutex:
162159
connecting = self.sync_connect()
163160
if connecting:
164-
(response, err) = await self._async_response(CONNECT_REQUEST_ID)
161+
(_, err) = await self._async_response(CONNECT_REQUEST_ID)
165162
if err:
166163
raise RoborockException(err) from err
167-
return response
168164

169165
async def validate_connection(self) -> None:
170166
await self.async_connect()
@@ -194,4 +190,8 @@ async def send_command(self, device_id: str, method: RoborockCommand, params: Op
194190
return response
195191

196192
async def get_map_v1(self, device_id):
197-
return await self.send_command(device_id, RoborockCommand.GET_MAP_V1)
193+
try:
194+
return await self.send_command(device_id, RoborockCommand.GET_MAP_V1)
195+
except RoborockException as e:
196+
_LOGGER.error(e)
197+
return None

roborock/containers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ def decamelize(s: str):
3535

3636

3737
def decamelize_obj(d: dict | list, ignore_keys: list[str]):
38+
if isinstance(d, RoborockBase):
39+
d = d.as_dict()
3840
if isinstance(d, list):
3941
return [decamelize_obj(i, ignore_keys) if isinstance(i, (dict, list)) else i for i in d]
4042
return {

roborock/local_api.py

Lines changed: 37 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
import asyncio
44
import logging
55
import socket
6-
from asyncio import Lock
6+
from asyncio import Transport
77
from typing import Callable, Mapping, Optional
88

99
import async_timeout
1010

11-
from .api import SPECIAL_COMMANDS, RoborockClient
11+
from .api import SPECIAL_COMMANDS, RoborockClient, QUEUE_TIMEOUT
1212
from .containers import RoborockLocalDeviceInfo
13-
from .exceptions import CommandVacuumError, RoborockConnectionException, RoborockException, RoborockTimeout
13+
from .exceptions import CommandVacuumError, RoborockConnectionException, RoborockException
1414
from .roborock_message import RoborockMessage, RoborockParser
1515
from .typing import CommandInfoMap, RoborockCommand
1616
from .util import get_running_loop_or_create_one
@@ -30,15 +30,15 @@ def __init__(self, devices_info: Mapping[str, RoborockLocalDeviceInfo]):
3030
)
3131
for device_id, device_info in devices_info.items()
3232
}
33-
self._mutex = Lock()
3433
self._batch_structs: list[RoborockMessage] = []
3534
self._executing = False
3635

37-
async def async_connect(self):
36+
async def async_connect(self) -> None:
3837
await asyncio.gather(*[listener.connect() for listener in self.device_listener.values()])
3938

4039
async def async_disconnect(self) -> None:
41-
await asyncio.gather(*[listener.disconnect() for listener in self.device_listener.values()])
40+
for listener in self.device_listener.values():
41+
listener.disconnect()
4242

4343
def build_roborock_message(self, method: RoborockCommand, params: Optional[list] = None) -> RoborockMessage:
4444
secured = True if method in SPECIAL_COMMANDS else False
@@ -103,72 +103,56 @@ def is_closed(self):
103103
return self._closed
104104

105105

106-
class RoborockSocketListener:
106+
class RoborockSocketListener(asyncio.Protocol):
107107
roborock_port = 58867
108108

109109
def __init__(
110110
self,
111111
ip: str,
112112
local_key: str,
113113
on_message: Callable[[list[RoborockMessage]], None],
114-
timeout: float | int = 4,
114+
timeout: float | int = QUEUE_TIMEOUT,
115115
):
116116
self.ip = ip
117117
self.local_key = local_key
118-
self.socket = RoborockSocket(socket.AF_INET, socket.SOCK_STREAM)
119-
self.socket.setblocking(False)
120118
self.loop = get_running_loop_or_create_one()
121119
self.on_message = on_message
122120
self.timeout = timeout
123-
self.is_connected = False
124-
self._mutex = Lock()
125121
self.remaining = b""
122+
self.transport: Transport | None = None
126123

127-
async def _main_coro(self):
128-
while not self.socket.is_closed:
129-
try:
130-
message = await self.loop.sock_recv(self.socket, 4096)
131-
try:
132-
if self.remaining:
133-
message = self.remaining + message
134-
self.remaining = b""
135-
(parser_msg, remaining) = RoborockParser.decode(message, self.local_key)
136-
self.remaining = remaining
137-
self.on_message(parser_msg)
138-
except Exception as e:
139-
_LOGGER.exception(e)
140-
except BrokenPipeError as e:
141-
_LOGGER.exception(e)
142-
await self.disconnect()
124+
def data_received(self, message):
125+
if self.remaining:
126+
message = self.remaining + message
127+
self.remaining = b""
128+
(parser_msg, remaining) = RoborockParser.decode(message, self.local_key)
129+
self.remaining = remaining
130+
self.on_message(parser_msg)
131+
132+
def connection_lost(self, exc):
133+
print("The server closed the connection")
134+
135+
def is_connected(self):
136+
return self.transport and self.transport.is_reading()
143137

144138
async def connect(self):
145-
async with self._mutex:
146-
if not self.is_connected or self.socket.is_closed:
147-
self.socket = RoborockSocket(socket.AF_INET, socket.SOCK_STREAM)
148-
self.socket.setblocking(False)
149-
try:
150-
async with async_timeout.timeout(self.timeout):
151-
_LOGGER.info(f"Connecting to {self.ip}")
152-
await self.loop.sock_connect(self.socket, (self.ip, 58867))
153-
self.is_connected = True
154-
except Exception as e:
155-
await self.disconnect()
156-
raise RoborockConnectionException(f"Failed connecting to {self.ip}") from e
157-
self.loop.create_task(self._main_coro())
158-
159-
async def disconnect(self):
160-
self.socket.close()
161-
self.is_connected = False
139+
try:
140+
if not self.is_connected():
141+
async with async_timeout.timeout(self.timeout):
142+
_LOGGER.info(f"Connecting to {self.ip}")
143+
self.transport, _ = await self.loop.create_connection(lambda: self, self.ip, 58867)
144+
except Exception as e:
145+
raise RoborockConnectionException(f"Failed connecting to {self.ip}") from e
146+
147+
def disconnect(self):
148+
if self.transport:
149+
self.transport.close()
162150

163151
async def send_message(self, data: bytes) -> None:
164152
await self.connect()
165153
try:
166-
async with self._mutex:
167-
async with async_timeout.timeout(self.timeout):
168-
await self.loop.sock_sendall(self.socket, data)
169-
except (asyncio.TimeoutError, asyncio.CancelledError):
170-
await self.disconnect()
171-
raise RoborockTimeout(f"Timeout after {self.timeout} seconds waiting for response") from None
172-
except BrokenPipeError as e:
173-
await self.disconnect()
154+
if not self.transport:
155+
raise RoborockException("Can not send message without connection")
156+
self.transport.write(data)
157+
except Exception as e:
174158
raise RoborockException(e) from e

roborock/roborock_future.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@ def resolve(self, item: tuple[Any, VacuumError | None]) -> None:
1818
self.loop.call_soon_threadsafe(self.fut.set_result, item)
1919

2020
async def async_get(self, timeout: float | int) -> tuple[Any, VacuumError | None]:
21-
async with async_timeout.timeout(timeout):
22-
return await self.fut
21+
try:
22+
async with async_timeout.timeout(timeout):
23+
return await self.fut
24+
finally:
25+
self.fut.cancel()

roborock/util.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77

88
T = TypeVar("T")
99

10+
1011
def unpack_list(value: list[T], size: int) -> list[T | None]:
1112
return (value + [None] * size)[:size]
1213

14+
1315
def get_running_loop_or_create_one() -> AbstractEventLoop:
1416
try:
1517
loop = asyncio.get_event_loop()

0 commit comments

Comments
 (0)