UNPKG

homebridge-roborock-control

Version:

A Homebridge plugin to control Roborock vacuum cleaners.

121 lines 5.33 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable indent */ import assert from 'node:assert'; import { dirname, join } from 'path'; import { python } from 'pythonia'; import { fileURLToPath } from 'url'; import { tryParse } from '../util/helpers.js'; import { Log } from '../util/log.js'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: this is the recommended way to exit pythonia process.on('exit', () => python.exit()); // Get the path of the directory where this file is located. const __dirname = dirname(fileURLToPath(import.meta.url)); // Append the 'pylib' path to the python search path. const pylibDir = join(__dirname, '..', '..', 'pylib'); await (await python('sys')).path.insert(0, pylibDir); // Load asyncio and roborock packages via the python bridge. export const pyasyncio = await python('asyncio'); export const pyroborock = await python(join(__dirname, '..', 'python', 'pyimport.py')); // State codes and commands for the vacuum from the python library. export const PyRoborockState = await pyroborock.RoborockStateCode; export const PyRoborockCmd = await pyroborock.RoborockCommand; // Product categories which correspond to robot vacuum devices. const kVacuumCategories = { VACUUM: 'robot.vacuum.cleaner', }; // Enum of known protocol versions. var ProtocolVersion; (function (ProtocolVersion) { ProtocolVersion["V1"] = "1.0"; ProtocolVersion["A01"] = "A01"; })(ProtocolVersion || (ProtocolVersion = {})); // For a given device, retrieve the corresponding HomeDataProduct entry. export async function getProductForDevice(device, rrSession) { const productId = await device.product_id; for await (const product of await rrSession.home_data.products) { if (await product.id === productId) { return product; } } Log.debug(`No product info found for device ${await device.name}`); return null; } // Determine whether the device is a vacuum based on product category. export async function isVacuumDevice(product) { const productJson = tryParse(await pyroborock.json.dumps(await product.as_dict())); return Object.values(kVacuumCategories).includes(productJson?.category); } // Returns a roborock.containers.LoginData with email, user and home data. export async function startRoborockSession(username, authMode, password, pyUserData) { // Create a client to communicate with the cloud service. const cloudConn = await pyroborock.RoborockApiClient(username); // The session object that will be returned to the client. const rrSession = await pyroborock.LoginData(null, username); // Handle the situation where the user has requested OTP login. if (authMode === 'otp') { try { assert(pyUserData); rrSession.user_data = pyUserData; rrSession.home_data = await pyasyncio.run(await cloudConn.get_home_data(await rrSession.user_data)); return rrSession; } catch (ex) { Log.error('Invalid or expired one-time-code login data:', ex); Log.error('Update the config and restart the plugin.'); return null; } } // If we're here, then the user has requested password login. const tryPasswdLogin = async (userData) => { try { !userData && Log.debug('Missing or expired token data, refreshing login'); rrSession.user_data = userData || await pyasyncio.run(await cloudConn.pass_login(password)); rrSession.home_data = await pyasyncio.run(await cloudConn.get_home_data(await rrSession.user_data)); return rrSession; } catch (ex) { Log.info('Error while logging in:', ex); return null; } }; // Try to log in with the restored user data. If it fails, then force a // refresh of the user data and try to log in again. return tryPasswdLogin(pyUserData) || tryPasswdLogin(null); } // Class which wraps the python library to communicate with the vacuum. export class RoborockDeviceClient { rrRawClient; constructor(rrRawClient) { this.rrRawClient = rrRawClient; } // Returns an object of type Status as defined in containers.py. async getStatus() { return await this.sendCommand(await PyRoborockCmd.GET_STATUS); } async sendCommand(cmd, args) { try { return await pyasyncio.run(await this.rrRawClient._send_command(cmd, args)); } catch (ex) { Log.debug(`Error while sending command ${cmd}:`, ex); return null; } } } // Create an MqttClient to communicate with the vacuum. export async function makeRoborockDeviceClient(userData, device, product) { const deviceData = await pyroborock.DeviceData(device, await product.model); const protocolVersion = await device.pv; switch (protocolVersion) { case ProtocolVersion.V1: return new RoborockDeviceClient(await pyroborock.RoborockMqttClientV1(userData, deviceData)); case ProtocolVersion.A01: // NYI // eslint-disable-next-line no-fallthrough default: Log.info(`Unknown protocol '${protocolVersion}' for '${await device.name}'`); } return null; } //# sourceMappingURL=roborock-api.js.map