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.
114 lines • 4.85 kB
JavaScript
// Matterbridge plugin for Dyson robot vacuum and air treatment devices
// Copyright © 2025-2026 Alexander Thoukydides
import { DysonMqtt } from './dyson-mqtt.js';
import { checkers as dysonMsgCheckers360 } from './ti/dyson-360-msg-types.js';
import { tryListener } from './utils.js';
import { Dyson360CleaningMode, Dyson360CleaningStrategy, Dyson360CleaningType } from './dyson-360-types.js';
import { DysonModeReason } from './dyson-types.js';
// Configuration of a Dyson MQTT client for robot vacuums
const DYSON_MQTT_CONFIG_360 = {
topics: {
command: '@/@/command',
subscribe: ['@/@/status'],
other: ['@/initialconnection/credentials',
'@/initialconnection/status']
},
messages: {
prefix: 'Dyson360Msg',
checkers: dysonMsgCheckers360
}
};
// Dyson MQTT client for robot vacuums
export class DysonMqtt360 extends DysonMqtt {
// Construct a new MQTT client
constructor(log, config, persist, device) {
super(log, config, persist, device, DYSON_MQTT_CONFIG_360);
// Handle MQTT events
this.on('subscribed', tryListener(this, async () =>
// Request the current status when (re)connected
this.publish('REQUEST-CURRENT-STATE', {}))).on('message', tryListener(this, msg => {
// Update the robot vacuum state from the received messages
switch (msg.msg) {
case 'STATE-CHANGE':
msg = this.convertStateChange(msg);
// (fallthrough)
case 'CURRENT-STATE':
this.updateState(msg);
break;
}
}));
}
// Convert a STATE-CHANGE message to CURRENT-STATE format
convertStateChange(msg) {
const {
// Discard unmapped or replaced fields
msg: _, endOfClean, oldActiveFaults, oldOutOfBoxState, oldstate, oldZoneId,
// Capture renamed or modified fields
newActiveFaults, newOutOfBoxState, newstate, newZoneId,
// Everything else is copied unchanged
...rest } = msg;
return {
msg: 'CURRENT-STATE',
...rest,
activeFaults: newActiveFaults ?? rest.activeFaults,
outOfBoxState: newOutOfBoxState,
state: newstate,
zoneId: newZoneId,
batteryChargeLevel: rest.batteryChargeLevel ?? 0 // (may be omitted on power on)
};
}
// Update the robot vacuum state from a received message
updateState(msg) {
// Clear fields from the state that might have disappeared
const DELETE_KEYS = [
'activeFaults', 'cleanDuration', 'cleanId',
'cleaningProgramme', 'faults', 'globalPosition',
'persistentMapId', 'sessionId', 'traverseTargetId',
'zoneId', 'zonesDefinitionVersion', 'zoneStatus'
];
for (const key of DELETE_KEYS)
this.status[key] = undefined;
// Copy status fields from the message to the state
const { msg: _msg, time, ...status } = msg;
Object.assign(this.status, status);
// State is fully initialised after the first message has been received
this.updateInitialised();
}
commandAction(msg, cleaningProgramme) {
switch (msg) {
case 'START':
return this.publish('START', cleaningProgramme ? {
'mode-reason': DysonModeReason.LocalApp,
fullCleanType: Dyson360CleaningType.Immediate,
cleaningMode: Dyson360CleaningMode.ZoneConfigured,
cleaningProgramme
} : {
'mode-reason': DysonModeReason.LocalApp,
fullCleanType: Dyson360CleaningType.Immediate,
cleaningMode: this.status.defaultCleaningMode && Dyson360CleaningMode.Global,
cleaningStrategy: this.status.defaultCleaningStrategy && Dyson360CleaningStrategy.Auto
});
case 'PAUSE':
case 'RESUME':
case 'ABORT':
return this.publish(msg, {
'mode-reason': DysonModeReason.LocalApp
});
}
}
// Publish a robot vacuum command to set the default power level (360 Eye)
commandSetPowerMode(defaultVacuumPowerMode) {
return this.publish('STATE-SET', {
'mode-reason': DysonModeReason.LocalApp,
data: { defaultVacuumPowerMode }
});
}
// Publish a robot vacuum command to set the default power level (360 Vis Nav)
commandSetCleaningStrategy(defaultCleaningStrategy) {
return this.publish('STATE-SET', {
'mode-reason': DysonModeReason.LocalApp,
defaults: { defaultCleaningStrategy }
});
}
}
//# sourceMappingURL=dyson-mqtt-360.js.map