Skip to content

Commit eb038ef

Browse files
committed
feat: Update the cli cache to also store network info
A follow up step will support caching network info for the new device manager API, and will use the CLI to do this.
1 parent 97dfd16 commit eb038ef

File tree

1 file changed

+85
-63
lines changed

1 file changed

+85
-63
lines changed

roborock/cli.py

Lines changed: 85 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
from __future__ import annotations
2-
31
import asyncio
42
import json
53
import logging
64
from pathlib import Path
75
from typing import Any
6+
from dataclasses import dataclass, field
87

98
import click
109
from pyshark import FileCapture # type: ignore
1110
from pyshark.capture.live_capture import LiveCapture, UnknownInterfaceException # type: ignore
1211
from pyshark.packet.packet import Packet # type: ignore
1312

1413
from roborock import RoborockException
15-
from roborock.containers import DeviceData, HomeData, HomeDataProduct, LoginData
14+
from roborock.containers import DeviceData, HomeData, HomeDataProduct, LoginData, NetworkInfo, UserData, RoborockBase
1615
from roborock.devices.device_manager import create_device_manager, create_home_data_api
1716
from roborock.protocol import MessageParser
1817
from roborock.util import run_sync
@@ -23,9 +22,26 @@
2322
_LOGGER = logging.getLogger(__name__)
2423

2524

25+
@dataclass
26+
class ConnectionCache(RoborockBase):
27+
"""Cache for Roborock data.
28+
29+
This is used to store data retrieved from the Roborock API, such as user
30+
data and home data to avoid repeated API calls.
31+
32+
This cache is superset of `LoginData` since we used to directly store that
33+
dataclass, but now we also store additional data.
34+
"""
35+
36+
user_data: UserData
37+
email: str
38+
home_data: HomeData | None = None
39+
network_info: dict[str, NetworkInfo] | None = None
40+
41+
2642
class RoborockContext:
2743
roborock_file = Path("~/.roborock").expanduser()
28-
_login_data: LoginData | None = None
44+
_cache_data: ConnectionCache | None = None
2945

3046
def __init__(self):
3147
self.reload()
@@ -35,22 +51,22 @@ def reload(self):
3551
with open(self.roborock_file) as f:
3652
data = json.load(f)
3753
if data:
38-
self._login_data = LoginData.from_dict(data)
54+
self._cache_data = ConnectionCache.from_dict(data)
3955

40-
def update(self, login_data: LoginData):
41-
data = json.dumps(login_data.as_dict(), default=vars)
56+
def update(self, cache_data: ConnectionCache):
57+
data = json.dumps(cache_data.as_dict(), default=vars, indent=4)
4258
with open(self.roborock_file, "w") as f:
4359
f.write(data)
4460
self.reload()
4561

4662
def validate(self):
47-
if self._login_data is None:
63+
if self._cache_data is None:
4864
raise RoborockException("You must login first")
4965

50-
def login_data(self) -> LoginData:
51-
"""Get the login data."""
66+
def cache_data(self) -> ConnectionCache:
67+
"""Get the cache data."""
5268
self.validate()
53-
return self._login_data
69+
return self._cache_data
5470

5571

5672
@click.option("-d", "--debug", default=False, count=True)
@@ -99,18 +115,18 @@ async def login(ctx, email, password):
99115
@run_sync()
100116
async def session(ctx, duration: int):
101117
context: RoborockContext = ctx.obj
102-
login_data = context.login_data()
118+
cache_data = context.cache_data()
103119

104-
home_data_api = create_home_data_api(login_data.email, login_data.user_data)
120+
home_data_api = create_home_data_api(cache_data.email, cache_data.user_data)
105121

106122
async def home_data_cache() -> HomeData:
107-
if login_data.home_data is None:
108-
login_data.home_data = await home_data_api()
109-
context.update(login_data)
110-
return login_data.home_data
123+
if cache_data.home_data is None:
124+
cache_data.home_data = await home_data_api()
125+
context.update(cache_data)
126+
return cache_data.home_data
111127

112128
# Create device manager
113-
device_manager = await create_device_manager(login_data.user_data, home_data_cache)
129+
device_manager = await create_device_manager(cache_data.user_data, home_data_cache)
114130

115131
devices = await device_manager.get_devices()
116132
click.echo(f"Discovered devices: {', '.join([device.name for device in devices])}")
@@ -136,16 +152,27 @@ async def home_data_cache() -> HomeData:
136152

137153
async def _discover(ctx):
138154
context: RoborockContext = ctx.obj
139-
login_data = context.login_data()
140-
if not login_data:
155+
cache_data = context.cache_data()
156+
if not cache_data:
141157
raise Exception("You need to login first")
142-
client = RoborockApiClient(login_data.email)
143-
home_data = await client.get_home_data(login_data.user_data)
144-
login_data.home_data = home_data
145-
context.update(login_data)
158+
client = RoborockApiClient(cache_data.email)
159+
home_data = await client.get_home_data(cache_data.user_data)
160+
cache_data.home_data = home_data
161+
context.update(cache_data)
146162
click.echo(f"Discovered devices {', '.join([device.name for device in home_data.get_all_devices()])}")
147163

148164

165+
async def _load_and_discover(ctx) -> RoborockContext:
166+
"""Discover devices if home data is not available."""
167+
context: RoborockContext = ctx.obj
168+
cache_data = context.cache_data()
169+
if not cache_data.home_data:
170+
await _discover(ctx)
171+
cache_data = context.cache_data()
172+
return context
173+
174+
175+
149176
@click.command()
150177
@click.pass_context
151178
@run_sync()
@@ -157,30 +184,25 @@ async def discover(ctx):
157184
@click.pass_context
158185
@run_sync()
159186
async def list_devices(ctx):
160-
context: RoborockContext = ctx.obj
161-
login_data = context.login_data()
162-
if not login_data.home_data:
163-
await _discover(ctx)
164-
login_data = context.login_data()
165-
home_data = login_data.home_data
166-
device_name_id = ", ".join(
167-
[f"{device.name}: {device.duid}" for device in home_data.devices + home_data.received_devices]
168-
)
169-
click.echo(f"Known devices {device_name_id}")
187+
context: RoborockContext = await _load_and_discover(ctx)
188+
cache_data = context.cache_data()
189+
home_data = cache_data.home_data
190+
device_name_id = {
191+
device.name: device.duid
192+
for device in home_data.devices + home_data.received_devices
193+
}
194+
click.echo(json.dumps(device_name_id, indent=4))
170195

171196

172197
@click.command()
173198
@click.option("--device_id", required=True)
174199
@click.pass_context
175200
@run_sync()
176201
async def list_scenes(ctx, device_id):
177-
context: RoborockContext = ctx.obj
178-
login_data = context.login_data()
179-
if not login_data.home_data:
180-
await _discover(ctx)
181-
login_data = context.login_data()
182-
client = RoborockApiClient(login_data.email)
183-
scenes = await client.get_scenes(login_data.user_data, device_id)
202+
context: RoborockContext = await _load_and_discover(ctx)
203+
cache_data = context.cache_data()
204+
client = RoborockApiClient(cache_data.email)
205+
scenes = await client.get_scenes(cache_data.user_data, device_id)
184206
output_list = []
185207
for scene in scenes:
186208
output_list.append(scene.as_dict())
@@ -192,32 +214,34 @@ async def list_scenes(ctx, device_id):
192214
@click.pass_context
193215
@run_sync()
194216
async def execute_scene(ctx, scene_id):
195-
context: RoborockContext = ctx.obj
196-
login_data = context.login_data()
197-
if not login_data.home_data:
198-
await _discover(ctx)
199-
login_data = context.login_data()
200-
client = RoborockApiClient(login_data.email)
201-
await client.execute_scene(login_data.user_data, scene_id)
217+
context: RoborockContext = await _load_and_discover(ctx)
218+
cache_data = context.cache_data()
219+
client = RoborockApiClient(cache_data.email)
220+
await client.execute_scene(cache_data.user_data, scene_id)
202221

203222

204223
@click.command()
205224
@click.option("--device_id", required=True)
206225
@click.pass_context
207226
@run_sync()
208227
async def status(ctx, device_id):
209-
context: RoborockContext = ctx.obj
210-
login_data = context.login_data()
211-
if not login_data.home_data:
212-
await _discover(ctx)
213-
login_data = context.login_data()
214-
home_data = login_data.home_data
228+
context: RoborockContext = await _load_and_discover(ctx)
229+
cache_data = context.cache_data()
230+
231+
home_data = cache_data.home_data
215232
devices = home_data.devices + home_data.received_devices
216233
device = next(device for device in devices if device.duid == device_id)
217234
product_info: dict[str, HomeDataProduct] = {product.id: product for product in home_data.products}
218235
device_data = DeviceData(device, product_info[device.product_id].model)
219-
mqtt_client = RoborockMqttClientV1(login_data.user_data, device_data)
220-
networking = await mqtt_client.get_networking()
236+
237+
mqtt_client = RoborockMqttClientV1(cache_data.user_data, device_data)
238+
if not (networking := cache_data.network_info.get(device.duid)):
239+
networking = await mqtt_client.get_networking()
240+
cache_data.network_info[device.duid] = networking
241+
context.update(cache_data)
242+
else:
243+
_LOGGER.debug("Using cached networking info for device %s: %s", device.duid, networking)
244+
221245
local_device_data = DeviceData(device, product_info[device.product_id].model, networking.ip)
222246
local_client = RoborockLocalClientV1(local_device_data)
223247
status = await local_client.get_status()
@@ -231,12 +255,10 @@ async def status(ctx, device_id):
231255
@click.pass_context
232256
@run_sync()
233257
async def command(ctx, cmd, device_id, params):
234-
context: RoborockContext = ctx.obj
235-
login_data = context.login_data()
236-
if not login_data.home_data:
237-
await _discover(ctx)
238-
login_data = context.login_data()
239-
home_data = login_data.home_data
258+
context: RoborockContext = await _load_and_discover(ctx)
259+
cache_data = context.cache_data()
260+
261+
home_data = cache_data.home_data
240262
devices = home_data.devices + home_data.received_devices
241263
device = next(device for device in devices if device.duid == device_id)
242264
model = next(
@@ -246,7 +268,7 @@ async def command(ctx, cmd, device_id, params):
246268
if model is None:
247269
raise RoborockException(f"Could not find model for device {device.name}")
248270
device_info = DeviceData(device=device, model=model)
249-
mqtt_client = RoborockMqttClientV1(login_data.user_data, device_info)
271+
mqtt_client = RoborockMqttClientV1(cache_data.user_data, device_info)
250272
await mqtt_client.send_command(cmd, json.loads(params) if params is not None else None)
251273
await mqtt_client.async_release()
252274

0 commit comments

Comments
 (0)