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.
131 lines • 5.49 kB
JavaScript
// Matterbridge plugin for Dyson robot vacuum and air treatment devices
// Copyright © 2025-2026 Alexander Thoukydides
import { connect } from 'mqtt';
import { isDeepStrictEqual } from 'util';
import { DysonMqttClient } from './dyson-mqtt-client-base.js';
// 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 DysonMqttClientLive extends DysonMqttClient {
// The current MQTT client and its options
delegate;
clientOptions;
count = 0;
// 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(true);
}
// 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();
}
}
// Terminate the MQTT client
async stop() {
if (!this.delegate)
return;
await this.destroyClient(this.delegate);
this.delegate = undefined;
}
// 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); }
}
// A Dyson MQTT client using a local network connection
export class DysonMqttClientLocal extends DysonMqttClientLive {
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 DysonMqttClientLive {
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-live.js.map