UNPKG

matterbridge-dyson-robot

Version:

A Matterbridge plugin that connects Dyson robot vacuums and air treatment devices to the Matter smart home ecosystem via their local or cloud MQTT APIs.

133 lines 5.5 kB
// Matterbridge plugin for Dyson robot vacuum and air treatment devices // Copyright © 2025 Alexander Thoukydides import EventEmitter from 'events'; import { connect } from 'mqtt'; import { isDeepStrictEqual } from 'util'; // Default MQTT options const DEFAULT_OPTIONS = { keepalive: 10, // Max 10 seconds between packets manualConnect: true, // Disable automatic (re)connection reconnectOnConnackError: false, // Disable automatic connection retry reconnectPeriod: 0, // Disable automatic reconnection resubscribe: true, // Resubscribe to topics on reconnect rejectUnauthorized: false, // Allow self-signed certificates protocolId: 'MQIsdp', // MQTT version 3.1 protocolVersion: 3 }; // A Dyson MQTT client that supports creating a new client for each connection export class DysonMqttClient extends EventEmitter { log; config; // The current MQTT client and its options delegate; clientOptions; count = 0; // Construct a new MQTT client constructor(log, config) { super({ captureRejections: true }); this.log = log; this.config = config; } // Destroy an MQTT client async destroyClient(mqtt) { this.log.debug('Cleaning up MQTT client'); // Remove any previous event listeners, if any mqtt.removeAllListeners('close'); mqtt.removeAllListeners('connect'); mqtt.removeAllListeners('error'); mqtt.removeAllListeners('message'); // Close the client await mqtt.endAsync(); } // Create a new MQTT client createClient(clientOptions) { this.log.debug(`Creating MQTT client #${++this.count}`); // MQTT debug logging, if enabled let log; if (this.config.debugFeatures.includes('Log MQTT Client')) { const logPrefix = `MQTT client #${this.count}:`; log = (...args) => { this.log.debug(logPrefix, ...args); }; } // Create the new client const { brokerUrl, options } = clientOptions; const mqtt = connect(brokerUrl, { ...DEFAULT_OPTIONS, log, ...options }); // Add the new listeners to forward events mqtt.on('close', (...args) => this.emit('close', ...args)); mqtt.on('connect', (...args) => this.emit('connect', ...args)); mqtt.on('error', (...args) => this.emit('error', ...args)); mqtt.on('message', (...args) => this.emit('message', ...args)); return mqtt; } // Start (re)connecting the MQTT client (resolves after initiating connect) async connect() { // Check whether a new client is required const clientOptions = await this.getConnectionOptions(); if (isDeepStrictEqual(clientOptions, this.clientOptions)) { // Connection options are unchanged, so just reconnect this.log.debug('Reconnecting existing MQTT client...'); this.mqtt.reconnect(); } else { // Clean-up the old MQTT client, if any if (this.delegate) await this.destroyClient(this.delegate); // Create a new MQTT client this.clientOptions = structuredClone(clientOptions); this.delegate = this.createClient(clientOptions); // Initiate the connection this.log.debug('Connecting new MQTT client...'); this.delegate.connect(); } } // The current MQTT client get mqtt() { if (!this.delegate) throw new Error('No MQTT client'); return this.delegate; } // Forward other MQTT client methods async publishAsync(...args) { return this.mqtt.publishAsync(...args); } async subscribeAsync(...args) { return this.mqtt.subscribeAsync(...args); } async endAsync(...args) { return this.mqtt.endAsync(...args); } } // A Dyson MQTT client using a local network connection export class DysonMqttClientLocal extends DysonMqttClient { deviceConfig; // Construct a new MQTT client constructor(log, config, deviceConfig) { super(log, config); this.deviceConfig = deviceConfig; } // Obtain the (re)connection options getConnectionOptions() { const { host, port, serialNumber, password } = this.deviceConfig; const brokerUrl = `mqtt://${host}:${port}`; const options = { username: serialNumber, password }; return { brokerUrl, options }; } } // A Dyson MQTT client using a AWS IoT connection via websockets export class DysonMqttClientRemote extends DysonMqttClient { deviceConfig; // Construct a new MQTT client constructor(log, config, deviceConfig) { super(log, config); this.deviceConfig = deviceConfig; } // Obtain the (re)connection options async getConnectionOptions() { const credentials = await this.deviceConfig.getCredentials(); const { Endpoint } = credentials; const { ClientId, CustomAuthorizerName, TokenKey, TokenSignature, TokenValue } = credentials.IoTCredentials; // Prepare and return the connection options const brokerUrl = `wss://${Endpoint}/mqtt`; const headers = { [TokenKey]: TokenValue, 'X-Amz-CustomAuthorizer-Name': CustomAuthorizerName, 'X-Amz-CustomAuthorizer-Signature': TokenSignature }; const options = { clientId: ClientId, wsOptions: { headers } }; return { brokerUrl, options }; } } //# sourceMappingURL=dyson-mqtt-client.js.map