|
| 1 | +# Roborock Python Library Design |
| 2 | + |
| 3 | +This document outlines the current architecture and design of the `python-roborock` library. |
| 4 | + |
| 5 | +## High-Level Architecture |
| 6 | + |
| 7 | +The library is designed to communicate with Roborock devices via two primary transport mechanisms: |
| 8 | +1. **Cloud (MQTT)**: Uses the Roborock cloud infrastructure. |
| 9 | +2. **Local (TCP)**: Direct connection to the device on the local network. |
| 10 | + |
| 11 | +The core components are: |
| 12 | +* **Device Manager**: Handles discovery and lifecycle of devices. |
| 13 | +* **Web API**: Fetches user and home configuration data. |
| 14 | +* **Device Model**: Represents a physical device and its capabilities. |
| 15 | +* **Communication Channels**: Abstracts the transport layer (MQTT vs Local). |
| 16 | + |
| 17 | +## Component Detail |
| 18 | + |
| 19 | +### 1. Device Discovery (`DeviceManager`) |
| 20 | + |
| 21 | +The `DeviceManager` (`roborock/devices/device_manager.py`) is the entry point. |
| 22 | +* **Input**: `UserParams` (credentials). |
| 23 | +* **Process**: |
| 24 | + 1. Authenticates via `UserWebApiClient`. |
| 25 | + 2. Fetches `HomeData` (list of devices, products, rooms). |
| 26 | + 3. Iterates through devices and uses a factory pattern (`device_creator`) to instantiate specific `RoborockDevice` subclasses based on the protocol version (`V1`, `A01`, `B01`). |
| 27 | +* **Output**: A list of `RoborockDevice` instances. |
| 28 | + |
| 29 | +### 2. Device Model (`RoborockDevice`) |
| 30 | + |
| 31 | +The `RoborockDevice` (`roborock/devices/device.py`) is the base class for all devices. |
| 32 | +* **Composition**: |
| 33 | + * `HomeDataDevice`: Static info (DUID, name). |
| 34 | + * `HomeDataProduct`: Model info. |
| 35 | + * `Channel`: The communication pipe. |
| 36 | + * `Traits`: Capabilities mixed in via `TraitsMixin`. |
| 37 | +* **Traits System**: Devices expose functionality through traits (e.g., `FanSpeedTrait`, `CleaningTrait`). This allows for a unified interface across different device protocols. |
| 38 | + |
| 39 | +### 3. Communication Layer |
| 40 | + |
| 41 | +The library uses a layered channel architecture to abstract the differences between MQTT and Local connections. |
| 42 | + |
| 43 | +#### Channels (`Channel` Protocol) |
| 44 | +* **`MqttChannel`**: Wraps the `MqttSession`. Handles topic construction (`rr/m/i/...`) and message encoding/decoding using the device's `local_key`. |
| 45 | +* **`LocalChannel`**: Manages a direct TCP connection (port 58867). Handles the custom handshake/heartbeat protocol and message framing. |
| 46 | +* **`V1Channel`**: A "smart" channel for V1 devices. It holds both an `MqttChannel` and a `LocalChannel`. It manages the complexity of: |
| 47 | + * Fetching `NetworkingInfo` (to get the local IP). |
| 48 | + * Establishing the local connection. |
| 49 | + * Fallback logic (preferring local, falling back to MQTT). |
| 50 | + |
| 51 | +#### RPC Abstraction (`V1RpcChannel`) |
| 52 | +Above the raw byte-oriented `Channel`, the `V1RpcChannel` provides a command-oriented interface (`send_command`). |
| 53 | +* **`PayloadEncodedV1RpcChannel`**: Handles serialization of RPC commands (JSON payload -> Encrypted Bytes). |
| 54 | +* **`PickFirstAvailable`**: A composite channel that attempts to send a command via the Local channel first, and falls back to MQTT if the local connection is unavailable. |
| 55 | + |
| 56 | +### 4. Session Management |
| 57 | + |
| 58 | +* **`RoborockMqttSession`**: Manages the persistent connection to the Roborock MQTT broker. It handles authentication, keepalives, and dispatching incoming messages to the appropriate `MqttChannel` based on topic. |
| 59 | +* **`LocalSession`**: Currently a factory for creating `LocalChannel` instances. |
| 60 | + |
| 61 | +## Protocol Details |
| 62 | + |
| 63 | +The library handles two variations of the underlying wire protocol depending on the transport. |
| 64 | + |
| 65 | +#### Message Framing |
| 66 | +* **Local (TCP)**: Messages are **length-prefixed**. A 4-byte integer at the start of each packet indicates the total length of the message. This is necessary for framing over the streaming TCP connection. |
| 67 | +* **MQTT**: Messages are **raw**. The MQTT packet boundaries themselves serve as the framing mechanism, so no length prefix is added. |
| 68 | + |
| 69 | +#### MQTT Authentication |
| 70 | +The connection to the Roborock MQTT broker requires specific credentials derived from the user's `rriot` data (obtained during login): |
| 71 | +* **Username**: Derived from `MD5(rriot.u + ":" + rriot.k)`. |
| 72 | +* **Password**: Derived from `MD5(rriot.s + ":" + rriot.k)`. |
| 73 | +* **Topics**: |
| 74 | + * Command (Publish): `rr/m/i/{rriot.u}/{username}/{duid}` |
| 75 | + * Response (Subscribe): `rr/m/o/{rriot.u}/{username}/{duid}` |
| 76 | + |
| 77 | +#### Local Handshake |
| 78 | +1. **Negotiation**: The client attempts to connect using a list of supported versions (currently `V1` and `L01`). |
| 79 | +2. **Hello Request**: Client sends a `HELLO_REQUEST` message containing the version string and a `connect_nonce`. |
| 80 | +3. **Hello Response**: Device responds with `HELLO_RESPONSE`. The client extracts the `ack_nonce` (from the message's `random` field). |
| 81 | +4. **Session Setup**: The `local_key`, `connect_nonce`, and `ack_nonce` are used to configure the encryption for subsequent messages. |
| 82 | + |
| 83 | +#### Protocol Versions |
| 84 | + |
| 85 | +The library supports multiple protocol versions which differ primarily in their encryption schemes: |
| 86 | + |
| 87 | +* **V1 (Legacy/Standard)**: |
| 88 | + * **Encryption**: AES-128-ECB. |
| 89 | + * **Key Derivation**: `MD5(timestamp + local_key + SALT)`. |
| 90 | + * **Structure**: Header (Version, Seq, Random, Timestamp, Protocol) + Encrypted Payload + CRC32 Checksum. |
| 91 | + |
| 92 | +* **L01 (Newer)**: |
| 93 | + * **Encryption**: AES-256-GCM (Authenticated Encryption). |
| 94 | + * **Key Derivation**: SHA256 based on `timestamp`, `local_key`, and `SALT`. |
| 95 | + * **IV/AAD**: Derived from sequence numbers and nonces (`connect_nonce`, `ack_nonce`) exchanged during handshake. |
| 96 | + * **Security**: Provides better security against replay attacks and tampering compared to V1. |
| 97 | + |
| 98 | +* **A01 / B01**: |
| 99 | + * **Encryption**: AES-CBC. |
| 100 | + * **IV**: Derived from `MD5(random + HASH)`. |
| 101 | + * These are typically used by newer camera-equipped models (e.g., S7 MaxV, Zeo). |
| 102 | + |
| 103 | +## Data Flow (V1 Device Example) |
| 104 | + |
| 105 | +1. **Initialization**: `DeviceManager` creates a `V1Channel` with an `MqttChannel` and `LocalSession`. |
| 106 | +2. **Connection**: |
| 107 | + * The `MqttChannel` is ready immediately (sharing the global `MqttSession`). |
| 108 | + * The `V1Channel` attempts to connect locally in the background: |
| 109 | + 1. Sends a request via MQTT to get `NetworkingInfo` (contains Local IP). |
| 110 | + 2. Uses `LocalSession` to create a `LocalChannel` to that IP. |
| 111 | + 3. Performs the local handshake. |
| 112 | +3. **Command Execution**: |
| 113 | + * User calls a method (e.g., `start_cleaning`). |
| 114 | + * The method calls `send_command` on the device's `V1RpcChannel`. |
| 115 | + * The `PickFirstAvailable` logic checks if `LocalChannel` is connected. |
| 116 | + * **If Yes**: Sends via TCP. |
| 117 | + * **If No**: Sends via MQTT. |
| 118 | +4. **Response**: The response is received, decrypted, decoded, and returned to the caller. |
| 119 | + |
| 120 | +## Current Design Observations |
| 121 | + |
| 122 | +* **Complexity**: The wrapping of channels (`Device` -> `V1Channel` -> `V1RpcChannel` -> `PickFirstAvailable` -> `PayloadEncoded...` -> `Mqtt/LocalChannel`) is deep. |
| 123 | +* **State Management**: Synchronization between the global MQTT session and individual device local connections is handled within `V1Channel`. |
| 124 | +* **Protocol Versions**: Distinct logic paths exist for V1, A01, and B01 protocols, though they share the underlying MQTT transport. |
0 commit comments