matterbridge-dyson-robot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/LICENSE +14 -0
- package/README.md +365 -0
- package/dist/async-eventemitter.d.ts +9 -0
- package/dist/async-eventemitter.d.ts.map +1 -0
- package/dist/async-eventemitter.js +35 -0
- package/dist/async-eventemitter.js.map +1 -0
- package/dist/check-configuration.d.ts +5 -0
- package/dist/check-configuration.d.ts.map +1 -0
- package/dist/check-configuration.js +35 -0
- package/dist/check-configuration.js.map +1 -0
- package/dist/check-versions.d.ts +3 -0
- package/dist/check-versions.d.ts.map +1 -0
- package/dist/check-versions.js +31 -0
- package/dist/check-versions.js.map +1 -0
- package/dist/config-types.d.ts +48 -0
- package/dist/config-types.d.ts.map +1 -0
- package/dist/config-types.js +4 -0
- package/dist/config-types.js.map +1 -0
- package/dist/decorator-changed.d.ts +16 -0
- package/dist/decorator-changed.d.ts.map +1 -0
- package/dist/decorator-changed.js +62 -0
- package/dist/decorator-changed.js.map +1 -0
- package/dist/dyson-360-msg-types.d.ts +113 -0
- package/dist/dyson-360-msg-types.d.ts.map +1 -0
- package/dist/dyson-360-msg-types.js +4 -0
- package/dist/dyson-360-msg-types.js.map +1 -0
- package/dist/dyson-360-types.d.ts +76 -0
- package/dist/dyson-360-types.d.ts.map +1 -0
- package/dist/dyson-360-types.js +72 -0
- package/dist/dyson-360-types.js.map +1 -0
- package/dist/dyson-air-msg-types.d.ts +99 -0
- package/dist/dyson-air-msg-types.d.ts.map +1 -0
- package/dist/dyson-air-msg-types.js +4 -0
- package/dist/dyson-air-msg-types.js.map +1 -0
- package/dist/dyson-air-sensor-types.d.ts +59 -0
- package/dist/dyson-air-sensor-types.d.ts.map +1 -0
- package/dist/dyson-air-sensor-types.js +4 -0
- package/dist/dyson-air-sensor-types.js.map +1 -0
- package/dist/dyson-air-state-types.d.ts +96 -0
- package/dist/dyson-air-state-types.d.ts.map +1 -0
- package/dist/dyson-air-state-types.js +4 -0
- package/dist/dyson-air-state-types.js.map +1 -0
- package/dist/dyson-air-types.d.ts +325 -0
- package/dist/dyson-air-types.d.ts.map +1 -0
- package/dist/dyson-air-types.js +382 -0
- package/dist/dyson-air-types.js.map +1 -0
- package/dist/dyson-device-360-base.d.ts +35 -0
- package/dist/dyson-device-360-base.d.ts.map +1 -0
- package/dist/dyson-device-360-base.js +233 -0
- package/dist/dyson-device-360-base.js.map +1 -0
- package/dist/dyson-device-360-commands.d.ts +7 -0
- package/dist/dyson-device-360-commands.d.ts.map +1 -0
- package/dist/dyson-device-360-commands.js +129 -0
- package/dist/dyson-device-360-commands.js.map +1 -0
- package/dist/dyson-device-360-faults-table.d.ts +20 -0
- package/dist/dyson-device-360-faults-table.d.ts.map +1 -0
- package/dist/dyson-device-360-faults-table.js +161 -0
- package/dist/dyson-device-360-faults-table.js.map +1 -0
- package/dist/dyson-device-360-faults.d.ts +10 -0
- package/dist/dyson-device-360-faults.d.ts.map +1 -0
- package/dist/dyson-device-360-faults.js +123 -0
- package/dist/dyson-device-360-faults.js.map +1 -0
- package/dist/dyson-device-360.d.ts +38 -0
- package/dist/dyson-device-360.d.ts.map +1 -0
- package/dist/dyson-device-360.js +45 -0
- package/dist/dyson-device-360.js.map +1 -0
- package/dist/dyson-device-air-base.d.ts +41 -0
- package/dist/dyson-device-air-base.d.ts.map +1 -0
- package/dist/dyson-device-air-base.js +446 -0
- package/dist/dyson-device-air-base.js.map +1 -0
- package/dist/dyson-device-air-heat.d.ts +52 -0
- package/dist/dyson-device-air-heat.d.ts.map +1 -0
- package/dist/dyson-device-air-heat.js +71 -0
- package/dist/dyson-device-air-heat.js.map +1 -0
- package/dist/dyson-device-air-quality.d.ts +7 -0
- package/dist/dyson-device-air-quality.d.ts.map +1 -0
- package/dist/dyson-device-air-quality.js +101 -0
- package/dist/dyson-device-air-quality.js.map +1 -0
- package/dist/dyson-device-air.d.ts +216 -0
- package/dist/dyson-device-air.d.ts.map +1 -0
- package/dist/dyson-device-air.js +80 -0
- package/dist/dyson-device-air.js.map +1 -0
- package/dist/dyson-device-base.d.ts +49 -0
- package/dist/dyson-device-base.d.ts.map +1 -0
- package/dist/dyson-device-base.js +46 -0
- package/dist/dyson-device-base.js.map +1 -0
- package/dist/dyson-device.d.ts +5 -0
- package/dist/dyson-device.d.ts.map +1 -0
- package/dist/dyson-device.js +43 -0
- package/dist/dyson-device.js.map +1 -0
- package/dist/dyson-mqtt-360.d.ts +16 -0
- package/dist/dyson-mqtt-360.d.ts.map +1 -0
- package/dist/dyson-mqtt-360.js +83 -0
- package/dist/dyson-mqtt-360.js.map +1 -0
- package/dist/dyson-mqtt-air.d.ts +48 -0
- package/dist/dyson-mqtt-air.d.ts.map +1 -0
- package/dist/dyson-mqtt-air.js +216 -0
- package/dist/dyson-mqtt-air.js.map +1 -0
- package/dist/dyson-mqtt-config.d.ts +6 -0
- package/dist/dyson-mqtt-config.d.ts.map +1 -0
- package/dist/dyson-mqtt-config.js +47 -0
- package/dist/dyson-mqtt-config.js.map +1 -0
- package/dist/dyson-mqtt-connect.d.ts +22 -0
- package/dist/dyson-mqtt-connect.d.ts.map +1 -0
- package/dist/dyson-mqtt-connect.js +145 -0
- package/dist/dyson-mqtt-connect.js.map +1 -0
- package/dist/dyson-mqtt-filter.d.ts +10 -0
- package/dist/dyson-mqtt-filter.d.ts.map +1 -0
- package/dist/dyson-mqtt-filter.js +25 -0
- package/dist/dyson-mqtt-filter.js.map +1 -0
- package/dist/dyson-mqtt-parse.d.ts +16 -0
- package/dist/dyson-mqtt-parse.d.ts.map +1 -0
- package/dist/dyson-mqtt-parse.js +100 -0
- package/dist/dyson-mqtt-parse.js.map +1 -0
- package/dist/dyson-mqtt-subscribe.d.ts +28 -0
- package/dist/dyson-mqtt-subscribe.d.ts.map +1 -0
- package/dist/dyson-mqtt-subscribe.js +85 -0
- package/dist/dyson-mqtt-subscribe.js.map +1 -0
- package/dist/dyson-mqtt.d.ts +51 -0
- package/dist/dyson-mqtt.d.ts.map +1 -0
- package/dist/dyson-mqtt.js +138 -0
- package/dist/dyson-mqtt.js.map +1 -0
- package/dist/dyson-types.d.ts +18 -0
- package/dist/dyson-types.d.ts.map +1 -0
- package/dist/dyson-types.js +20 -0
- package/dist/dyson-types.js.map +1 -0
- package/dist/endpoint-360-behavior.d.ts +55 -0
- package/dist/endpoint-360-behavior.d.ts.map +1 -0
- package/dist/endpoint-360-behavior.js +156 -0
- package/dist/endpoint-360-behavior.js.map +1 -0
- package/dist/endpoint-360-rvc.d.ts +14 -0
- package/dist/endpoint-360-rvc.d.ts.map +1 -0
- package/dist/endpoint-360-rvc.js +149 -0
- package/dist/endpoint-360-rvc.js.map +1 -0
- package/dist/endpoint-360.d.ts +35 -0
- package/dist/endpoint-360.d.ts.map +1 -0
- package/dist/endpoint-360.js +197 -0
- package/dist/endpoint-360.js.map +1 -0
- package/dist/endpoint-air-purifier.d.ts +24 -0
- package/dist/endpoint-air-purifier.d.ts.map +1 -0
- package/dist/endpoint-air-purifier.js +97 -0
- package/dist/endpoint-air-purifier.js.map +1 -0
- package/dist/endpoint-air-quality.d.ts +11 -0
- package/dist/endpoint-air-quality.d.ts.map +1 -0
- package/dist/endpoint-air-quality.js +104 -0
- package/dist/endpoint-air-quality.js.map +1 -0
- package/dist/endpoint-air-thermostat.d.ts +358 -0
- package/dist/endpoint-air-thermostat.d.ts.map +1 -0
- package/dist/endpoint-air-thermostat.js +67 -0
- package/dist/endpoint-air-thermostat.js.map +1 -0
- package/dist/endpoint-air.d.ts +125 -0
- package/dist/endpoint-air.d.ts.map +1 -0
- package/dist/endpoint-air.js +459 -0
- package/dist/endpoint-air.js.map +1 -0
- package/dist/endpoint-base.d.ts +36 -0
- package/dist/endpoint-base.d.ts.map +1 -0
- package/dist/endpoint-base.js +137 -0
- package/dist/endpoint-base.js.map +1 -0
- package/dist/error-360.d.ts +35 -0
- package/dist/error-360.d.ts.map +1 -0
- package/dist/error-360.js +105 -0
- package/dist/error-360.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/logger-filter.d.ts +23 -0
- package/dist/logger-filter.d.ts.map +1 -0
- package/dist/logger-filter.js +104 -0
- package/dist/logger-filter.js.map +1 -0
- package/dist/logger-options.d.ts +18 -0
- package/dist/logger-options.d.ts.map +1 -0
- package/dist/logger-options.js +41 -0
- package/dist/logger-options.js.map +1 -0
- package/dist/logger-prefix.d.ts +12 -0
- package/dist/logger-prefix.d.ts.map +1 -0
- package/dist/logger-prefix.js +37 -0
- package/dist/logger-prefix.js.map +1 -0
- package/dist/periodic.d.ts +31 -0
- package/dist/periodic.d.ts.map +1 -0
- package/dist/periodic.js +102 -0
- package/dist/periodic.js.map +1 -0
- package/dist/platform.d.ts +18 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +138 -0
- package/dist/platform.js.map +1 -0
- package/dist/settings.d.ts +9 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +28 -0
- package/dist/settings.js.map +1 -0
- package/dist/ti/config-types-ti.d.ts +16 -0
- package/dist/ti/config-types-ti.d.ts.map +1 -0
- package/dist/ti/config-types-ti.js +65 -0
- package/dist/ti/config-types-ti.js.map +1 -0
- package/dist/ti/config-types.d.ts +37 -0
- package/dist/ti/config-types.d.ts.map +1 -0
- package/dist/ti/config-types.js +11 -0
- package/dist/ti/config-types.js.map +1 -0
- package/dist/ti/dyson-360-msg-types-ti.d.ts +22 -0
- package/dist/ti/dyson-360-msg-types-ti.d.ts.map +1 -0
- package/dist/ti/dyson-360-msg-types-ti.js +134 -0
- package/dist/ti/dyson-360-msg-types-ti.js.map +1 -0
- package/dist/ti/dyson-360-msg-types.d.ts +55 -0
- package/dist/ti/dyson-360-msg-types.d.ts.map +1 -0
- package/dist/ti/dyson-360-msg-types.js +13 -0
- package/dist/ti/dyson-360-msg-types.js.map +1 -0
- package/dist/ti/dyson-360-types-ti.d.ts +17 -0
- package/dist/ti/dyson-360-types-ti.d.ts.map +1 -0
- package/dist/ti/dyson-360-types-ti.js +94 -0
- package/dist/ti/dyson-360-types-ti.js.map +1 -0
- package/dist/ti/dyson-360-types.d.ts +40 -0
- package/dist/ti/dyson-360-types.d.ts.map +1 -0
- package/dist/ti/dyson-360-types.js +11 -0
- package/dist/ti/dyson-360-types.js.map +1 -0
- package/dist/ti/dyson-air-msg-types-ti.d.ts +19 -0
- package/dist/ti/dyson-air-msg-types-ti.d.ts.map +1 -0
- package/dist/ti/dyson-air-msg-types-ti.js +115 -0
- package/dist/ti/dyson-air-msg-types-ti.js.map +1 -0
- package/dist/ti/dyson-air-msg-types.d.ts +46 -0
- package/dist/ti/dyson-air-msg-types.d.ts.map +1 -0
- package/dist/ti/dyson-air-msg-types.js +15 -0
- package/dist/ti/dyson-air-msg-types.js.map +1 -0
- package/dist/ti/dyson-air-sensor-types-ti.d.ts +9 -0
- package/dist/ti/dyson-air-sensor-types-ti.d.ts.map +1 -0
- package/dist/ti/dyson-air-sensor-types-ti.js +68 -0
- package/dist/ti/dyson-air-sensor-types-ti.js.map +1 -0
- package/dist/ti/dyson-air-sensor-types.d.ts +16 -0
- package/dist/ti/dyson-air-sensor-types.d.ts.map +1 -0
- package/dist/ti/dyson-air-sensor-types.js +12 -0
- package/dist/ti/dyson-air-sensor-types.js.map +1 -0
- package/dist/ti/dyson-air-state-types-ti.d.ts +9 -0
- package/dist/ti/dyson-air-state-types-ti.d.ts.map +1 -0
- package/dist/ti/dyson-air-state-types-ti.js +105 -0
- package/dist/ti/dyson-air-state-types-ti.js.map +1 -0
- package/dist/ti/dyson-air-state-types.d.ts +16 -0
- package/dist/ti/dyson-air-state-types.d.ts.map +1 -0
- package/dist/ti/dyson-air-state-types.js +12 -0
- package/dist/ti/dyson-air-state-types.js.map +1 -0
- package/dist/ti/dyson-air-types-ti.d.ts +59 -0
- package/dist/ti/dyson-air-types-ti.d.ts.map +1 -0
- package/dist/ti/dyson-air-types-ti.js +385 -0
- package/dist/ti/dyson-air-types-ti.js.map +1 -0
- package/dist/ti/dyson-air-types.d.ts +166 -0
- package/dist/ti/dyson-air-types.d.ts.map +1 -0
- package/dist/ti/dyson-air-types.js +11 -0
- package/dist/ti/dyson-air-types.js.map +1 -0
- package/dist/ti/dyson-types-ti.d.ts +10 -0
- package/dist/ti/dyson-types-ti.d.ts.map +1 -0
- package/dist/ti/dyson-types-ti.js +29 -0
- package/dist/ti/dyson-types-ti.js.map +1 -0
- package/dist/ti/dyson-types.d.ts +19 -0
- package/dist/ti/dyson-types.d.ts.map +1 -0
- package/dist/ti/dyson-types.js +11 -0
- package/dist/ti/dyson-types.js.map +1 -0
- package/dist/utils.d.ts +24 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +150 -0
- package/dist/utils.js.map +1 -0
- package/matterbridge-dyson-robot.schema.json +359 -0
- package/package.json +93 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Matterbridge plugin for Dyson robot vacuum and air treatment devices
|
|
2
|
+
// Copyright © 2025 Alexander Thoukydides
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
// Filter received Dyson MQTT messages for reordering and duplication
|
|
5
|
+
export class DysonMQTTFilter {
|
|
6
|
+
log;
|
|
7
|
+
// The most recent message of each type (indexed by msg)
|
|
8
|
+
lastMsg = new Map;
|
|
9
|
+
// Create a new message filter
|
|
10
|
+
constructor(log) {
|
|
11
|
+
this.log = log;
|
|
12
|
+
}
|
|
13
|
+
// Filter a received message
|
|
14
|
+
filter(msg) {
|
|
15
|
+
const lastMsg = this.lastMsg.get(msg.msg);
|
|
16
|
+
if (lastMsg) {
|
|
17
|
+
if (msg.time < lastMsg.time)
|
|
18
|
+
return 'reordered';
|
|
19
|
+
if (isDeepStrictEqual(msg, lastMsg))
|
|
20
|
+
return 'duplicate';
|
|
21
|
+
}
|
|
22
|
+
this.lastMsg.set(msg.msg, structuredClone(msg));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=dyson-mqtt-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dyson-mqtt-filter.js","sourceRoot":"","sources":["../src/dyson-mqtt-filter.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,yCAAyC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAO9C,qEAAqE;AACrE,MAAM,OAAO,eAAe;IAMH;IAJrB,wDAAwD;IAC/C,OAAO,GAAG,IAAI,GAAqB,CAAC;IAE7C,8BAA8B;IAC9B,YAAqB,GAAe;QAAf,QAAG,GAAH,GAAG,CAAY;IAAG,CAAC;IAExC,4BAA4B;IAC5B,MAAM,CAAC,GAAa;QAChB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,OAAO,EAAE,CAAC;YACV,IAAI,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI;gBAAa,OAAO,WAAW,CAAC;YAC3D,IAAI,iBAAiB,CAAC,GAAG,EAAE,OAAO,CAAC;gBAAK,OAAO,WAAW,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,CAAC;CACJ"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AnsiLogger } from 'matterbridge/logger';
|
|
2
|
+
import { CheckerT } from 'ts-interface-checker';
|
|
3
|
+
import { DysonMsg } from './dyson-types.js';
|
|
4
|
+
export type DysonMsgAny<T> = {
|
|
5
|
+
[K in keyof T]: T[K] extends DysonMsg ? T[K] : never;
|
|
6
|
+
}[keyof T];
|
|
7
|
+
export type DysonMsgTypeName<T> = Extract<keyof T, string>;
|
|
8
|
+
export type DysonMsgCheckers<T> = {
|
|
9
|
+
[K in DysonMsgTypeName<T>]: CheckerT<T[K]>;
|
|
10
|
+
};
|
|
11
|
+
export interface DysonMqttParserConfig<T> {
|
|
12
|
+
prefix: string;
|
|
13
|
+
checkers: DysonMsgCheckers<T>;
|
|
14
|
+
}
|
|
15
|
+
export declare function dysonMqttParse<T>(log: AnsiLogger, config: DysonMqttParserConfig<T>, topic: string, normalise: boolean, payload: Buffer): DysonMsgAny<T>;
|
|
16
|
+
//# sourceMappingURL=dyson-mqtt-parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dyson-mqtt-parse.d.ts","sourceRoot":"","sources":["../src/dyson-mqtt-parse.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAY,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EAAE,QAAQ,EAAgB,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAK5C,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;KACxB,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK;CACvD,CAAC,MAAM,CAAC,CAAC,CAAC;AACX,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;AAC3D,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC;AAGjF,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACpC,MAAM,EAAM,MAAM,CAAC;IACnB,QAAQ,EAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;CACnC;AAGD,wBAAgB,cAAc,CAAC,CAAC,EAC5B,GAAG,EAAS,UAAU,EACtB,MAAM,EAAM,qBAAqB,CAAC,CAAC,CAAC,EACpC,KAAK,EAAO,MAAM,EAClB,SAAS,EAAG,OAAO,EACnB,OAAO,EAAK,MAAM,GACnB,WAAW,CAAC,CAAC,CAAC,CA8EhB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Matterbridge plugin for Dyson robot vacuum and air treatment devices
|
|
2
|
+
// Copyright © 2025 Alexander Thoukydides
|
|
3
|
+
import { assertIsDefined, getValidationTree } from './utils.js';
|
|
4
|
+
import { checkers as dysonMsgCheckers } from './ti/dyson-types.js';
|
|
5
|
+
import { INSPECT_VERBOSE } from './logger-options.js';
|
|
6
|
+
import { inspect } from 'util';
|
|
7
|
+
// Parse and check a received MQTT message
|
|
8
|
+
export function dysonMqttParse(log, config, topic, normalise, payload) {
|
|
9
|
+
// Parse a raw message buffer as JSON and normalise property names
|
|
10
|
+
const parseAndNormalise = (text) => {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(text);
|
|
13
|
+
return normalise ? normaliseKeys(parsed) : parsed;
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
logCheckerValidation("error" /* LogLevel.ERROR */, text);
|
|
17
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
18
|
+
throw new Error(`Failed to parse Dyson MQTT message as JSON: ${message}`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
// Check that a received message is known and matches the expected type
|
|
22
|
+
const assertIsDysonMsg = (payload) => {
|
|
23
|
+
// Check that the message is of the general form expected
|
|
24
|
+
const baseChecker = dysonMsgCheckers.DysonMsg;
|
|
25
|
+
if (!baseChecker.test(payload)) {
|
|
26
|
+
baseChecker.setReportedPath('DysonMsg');
|
|
27
|
+
const validation = baseChecker.validate(payload);
|
|
28
|
+
assertIsDefined(validation);
|
|
29
|
+
logCheckerValidation("error" /* LogLevel.ERROR */, payload, validation);
|
|
30
|
+
throw new Error('Unexpected structure of Dyson MQTT message');
|
|
31
|
+
}
|
|
32
|
+
// Check whether the message type is known
|
|
33
|
+
const checker = getCheckerForMsg(payload);
|
|
34
|
+
// Check that the message is of the form expected for this type
|
|
35
|
+
const validation = checker.validate(payload);
|
|
36
|
+
if (validation) {
|
|
37
|
+
logCheckerValidation("error" /* LogLevel.ERROR */, payload, validation);
|
|
38
|
+
throw new Error('Unexpected structure of Dyson MQTT message');
|
|
39
|
+
}
|
|
40
|
+
const strictValidation = checker.strictValidate(payload);
|
|
41
|
+
if (strictValidation) {
|
|
42
|
+
logCheckerValidation("warn" /* LogLevel.WARN */, payload, strictValidation);
|
|
43
|
+
// (Continue processing messages that include unexpected properties)
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
// Map a message name to the corresponding type checker
|
|
47
|
+
const getCheckerForMsg = (payload) => {
|
|
48
|
+
const { prefix, checkers } = config;
|
|
49
|
+
// Construct the type name for this message
|
|
50
|
+
const msgPascalCase = kebabToPascalCase(payload.msg);
|
|
51
|
+
const typeName = `${prefix}${msgPascalCase}`;
|
|
52
|
+
const assertIsTypeName = (name) => {
|
|
53
|
+
if (name in config.checkers)
|
|
54
|
+
return;
|
|
55
|
+
logCheckerValidation("error" /* LogLevel.ERROR */, payload);
|
|
56
|
+
throw new Error(`Unrecognised Dyson MQTT message type: ${name}`);
|
|
57
|
+
};
|
|
58
|
+
assertIsTypeName(typeName);
|
|
59
|
+
// Return the type checker
|
|
60
|
+
const checker = checkers[typeName];
|
|
61
|
+
checker.setReportedPath(String(typeName));
|
|
62
|
+
return checker;
|
|
63
|
+
};
|
|
64
|
+
// Log checker validation errors
|
|
65
|
+
const logCheckerValidation = (level, payload, errors) => {
|
|
66
|
+
// Log the formatted message
|
|
67
|
+
log.log(level, `MQTT topic '${topic}':`);
|
|
68
|
+
if (errors) {
|
|
69
|
+
const validationLines = getValidationTree(errors);
|
|
70
|
+
validationLines.forEach(line => { log.log(level, line); });
|
|
71
|
+
}
|
|
72
|
+
const payloadLines = inspect(payload, INSPECT_VERBOSE).split('\n');
|
|
73
|
+
payloadLines.forEach(line => { log.info(` ${line}`); });
|
|
74
|
+
};
|
|
75
|
+
// Parse and check the message
|
|
76
|
+
const msg = parseAndNormalise(payload.toString());
|
|
77
|
+
assertIsDysonMsg(msg);
|
|
78
|
+
return msg;
|
|
79
|
+
}
|
|
80
|
+
// Convert a string from kebab-case (or FLAMING-KEBAB-CASE) to PascalCase
|
|
81
|
+
function kebabToPascalCase(str) {
|
|
82
|
+
return str.toLowerCase().split('-')
|
|
83
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
84
|
+
.join('');
|
|
85
|
+
}
|
|
86
|
+
// Convert a string from kebab-case or 'space case' to camelCase
|
|
87
|
+
function kebabToCamelCase(str) {
|
|
88
|
+
return str.replace(/[-\s]([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
89
|
+
}
|
|
90
|
+
// Recursively convert property names from snake-case to camelCase
|
|
91
|
+
function normaliseKeys(obj) {
|
|
92
|
+
if (Array.isArray(obj)) {
|
|
93
|
+
return obj.map(item => normaliseKeys(item));
|
|
94
|
+
}
|
|
95
|
+
if (obj !== null && typeof obj === 'object') {
|
|
96
|
+
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [kebabToCamelCase(key), normaliseKeys(value)]));
|
|
97
|
+
}
|
|
98
|
+
return obj;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=dyson-mqtt-parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dyson-mqtt-parse.js","sourceRoot":"","sources":["../src/dyson-mqtt-parse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import EventEmitter from 'events';
|
|
2
|
+
import { AnsiLogger } from 'matterbridge/logger';
|
|
3
|
+
import { MqttClient } from 'mqtt';
|
|
4
|
+
import { Config } from './config-types.js';
|
|
5
|
+
export interface DysonMqttSubscribeConfig {
|
|
6
|
+
command: string;
|
|
7
|
+
subscribe: string[];
|
|
8
|
+
other?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface DysonMqttSubscribeEventMap {
|
|
11
|
+
error: [unknown];
|
|
12
|
+
subscribed: [];
|
|
13
|
+
}
|
|
14
|
+
export type DysonMqttTopic = 'subscribed' | 'command' | 'expected' | 'unexpected';
|
|
15
|
+
export declare class DysonMqttSubscribe extends EventEmitter<DysonMqttSubscribeEventMap> {
|
|
16
|
+
readonly log: AnsiLogger;
|
|
17
|
+
readonly mqtt: MqttClient;
|
|
18
|
+
readonly config: Config;
|
|
19
|
+
readonly topics: DysonMqttSubscribeConfig;
|
|
20
|
+
readonly root_topic: string;
|
|
21
|
+
readonly username: string;
|
|
22
|
+
constructor(log: AnsiLogger, mqtt: MqttClient, config: Config, topics: DysonMqttSubscribeConfig, root_topic: string, username: string);
|
|
23
|
+
subscribe(): Promise<void>;
|
|
24
|
+
checkTopic(topic: string): DysonMqttTopic;
|
|
25
|
+
get commandTopic(): string;
|
|
26
|
+
replaceTopicPlaceholders(topic: string): string;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=dyson-mqtt-subscribe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dyson-mqtt-subscribe.d.ts","sourceRoot":"","sources":["../src/dyson-mqtt-subscribe.ts"],"names":[],"mappings":"AAGA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAI3C,MAAM,WAAW,wBAAwB;IACrC,OAAO,EAAK,MAAM,CAAC;IACnB,SAAS,EAAG,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAM,MAAM,EAAE,CAAC;CACxB;AAGD,MAAM,WAAW,0BAA0B;IACvC,KAAK,EAAO,CAAC,OAAO,CAAC,CAAC;IACtB,UAAU,EAAE,EAAE,CAAC;CAClB;AAGD,MAAM,MAAM,cAAc,GAAI,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,CAAC;AAGnF,qBAAa,kBAAmB,SAAQ,YAAY,CAAC,0BAA0B,CAAC;IAIxE,QAAQ,CAAC,GAAG,EAAY,UAAU;IAClC,QAAQ,CAAC,IAAI,EAAW,UAAU;IAClC,QAAQ,CAAC,MAAM,EAAS,MAAM;IAC9B,QAAQ,CAAC,MAAM,EAAS,wBAAwB;IAChD,QAAQ,CAAC,UAAU,EAAK,MAAM;IAC9B,QAAQ,CAAC,QAAQ,EAAO,MAAM;gBALrB,GAAG,EAAY,UAAU,EACzB,IAAI,EAAW,UAAU,EACzB,MAAM,EAAS,MAAM,EACrB,MAAM,EAAS,wBAAwB,EACvC,UAAU,EAAK,MAAM,EACrB,QAAQ,EAAO,MAAM;IAY5B,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBhC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc;IAqBzC,IAAI,YAAY,IAAI,MAAM,CAEzB;IAGD,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAKlD"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Matterbridge plugin for Dyson robot vacuum and air treatment devices
|
|
2
|
+
// Copyright © 2025 Alexander Thoukydides
|
|
3
|
+
import EventEmitter from 'events';
|
|
4
|
+
import { plural, tryListener } from './utils.js';
|
|
5
|
+
// Manage Dyson MQTT topic subscriptions
|
|
6
|
+
export class DysonMqttSubscribe extends EventEmitter {
|
|
7
|
+
log;
|
|
8
|
+
mqtt;
|
|
9
|
+
config;
|
|
10
|
+
topics;
|
|
11
|
+
root_topic;
|
|
12
|
+
username;
|
|
13
|
+
// Construct a new subscription manager
|
|
14
|
+
constructor(log, mqtt, config, topics, root_topic, username) {
|
|
15
|
+
super({ captureRejections: true });
|
|
16
|
+
this.log = log;
|
|
17
|
+
this.mqtt = mqtt;
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.topics = topics;
|
|
20
|
+
this.root_topic = root_topic;
|
|
21
|
+
this.username = username;
|
|
22
|
+
// (Re)subscribe to topics when the MQTT (re)connects
|
|
23
|
+
mqtt.on('connect', tryListener(this, async (_connack) => {
|
|
24
|
+
await this.subscribe();
|
|
25
|
+
this.emit('subscribed');
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
// Subscribe to topics when the client (re)connects
|
|
29
|
+
async subscribe() {
|
|
30
|
+
// Select the required subscription topics
|
|
31
|
+
const topics = this.topics.subscribe.map(t => this.replaceTopicPlaceholders(t));
|
|
32
|
+
if (this.config.wildcardTopic)
|
|
33
|
+
topics.push('#');
|
|
34
|
+
// Attempt the subscription
|
|
35
|
+
const grant = await this.mqtt.subscribeAsync(topics, { qos: 1 });
|
|
36
|
+
// Check whether
|
|
37
|
+
const failures = topics.filter(topic => !grant.some(g => g.topic === topic));
|
|
38
|
+
if (!grant.length) {
|
|
39
|
+
throw new Error(`MQTT subscribe unsuccessful: all ${plural(topics.length, 'topic')} rejected`);
|
|
40
|
+
}
|
|
41
|
+
else if (failures.length) {
|
|
42
|
+
this.log.warn('MQTT subscribe partially successful: '
|
|
43
|
+
+ `${failures.length} of ${plural(topics.length, 'topic')} rejected`);
|
|
44
|
+
failures.forEach(topic => { this.log.warn(` '${topic}'`); });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
this.log.info(`MQTT subscribe successful: all ${plural(grant.length, 'topic')} granted`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Warn if a received message's topic is unexpected
|
|
51
|
+
checkTopic(topic) {
|
|
52
|
+
// Check whether the topic is expected
|
|
53
|
+
const isTopics = (topics) => topics.some(t => this.replaceTopicPlaceholders(t) === topic);
|
|
54
|
+
if (isTopics([this.topics.command]))
|
|
55
|
+
return 'command';
|
|
56
|
+
if (isTopics(this.topics.subscribe))
|
|
57
|
+
return 'subscribed';
|
|
58
|
+
if (isTopics(this.topics.other ?? []))
|
|
59
|
+
return 'expected';
|
|
60
|
+
// Attempt to diagnose common problems
|
|
61
|
+
const [root_topic, username] = topic.split('/', 2);
|
|
62
|
+
if (root_topic !== this.root_topic) {
|
|
63
|
+
this.log.warn('MQTT topic root (product type) mismatch:'
|
|
64
|
+
+ ` expected '${this.root_topic}', received '${root_topic}'`);
|
|
65
|
+
}
|
|
66
|
+
else if (username !== this.username) {
|
|
67
|
+
this.log.warn('MQTT topic username (product serial number) mismatch:'
|
|
68
|
+
+ ` expected '${this.username}', received '${username}'`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.log.warn(`Unexpected MQTT topic received: ${topic}`);
|
|
72
|
+
}
|
|
73
|
+
return 'unexpected';
|
|
74
|
+
}
|
|
75
|
+
get commandTopic() {
|
|
76
|
+
return this.replaceTopicPlaceholders(this.topics.command);
|
|
77
|
+
}
|
|
78
|
+
// Replace any placeholders in a topic
|
|
79
|
+
replaceTopicPlaceholders(topic) {
|
|
80
|
+
return topic
|
|
81
|
+
.replace('@', this.root_topic)
|
|
82
|
+
.replace('@', this.username);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=dyson-mqtt-subscribe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dyson-mqtt-subscribe.js","sourceRoot":"","sources":["../src/dyson-mqtt-subscribe.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,yCAAyC;AAEzC,OAAO,YAAY,MAAM,QAAQ,CAAC;AAIlC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAkBjD,wCAAwC;AACxC,MAAM,OAAO,kBAAmB,SAAQ,YAAwC;IAI/D;IACA;IACA;IACA;IACA;IACA;IAPb,uCAAuC;IACvC,YACa,GAAyB,EACzB,IAAyB,EACzB,MAAqB,EACrB,MAAuC,EACvC,UAAqB,EACrB,QAAqB;QAE9B,KAAK,CAAC,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;QAP1B,QAAG,GAAH,GAAG,CAAsB;QACzB,SAAI,GAAJ,IAAI,CAAqB;QACzB,WAAM,GAAN,MAAM,CAAe;QACrB,WAAM,GAAN,MAAM,CAAiC;QACvC,eAAU,GAAV,UAAU,CAAW;QACrB,aAAQ,GAAR,QAAQ,CAAa;QAI9B,qDAAqD;QACrD,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,EAAE,KAAK,EAAC,QAAQ,EAAC,EAAE;YAClD,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;YACvB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC,CAAC;IACR,CAAC;IAED,mDAAmD;IACnD,KAAK,CAAC,SAAS;QACX,0CAA0C;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAC;QAChF,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEhD,2BAA2B;QAC3B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAEjE,gBAAgB;QAChB,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC;QAC7E,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;QACnG,CAAC;aAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YACzB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,uCAAuC;kBACnC,GAAG,QAAQ,CAAC,MAAM,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;YACtF,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,kCAAkC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7F,CAAC;IACL,CAAC;IAED,mDAAmD;IACnD,UAAU,CAAC,KAAa;QACpB,sCAAsC;QACtC,MAAM,QAAQ,GAAG,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;QACpG,IAAI,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAAK,OAAO,SAAS,CAAC;QACzD,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;YAAK,OAAO,YAAY,CAAC;QAC5D,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;YAAG,OAAO,UAAU,CAAC;QAE1D,sCAAsC;QACtC,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACnD,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YACjC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,0CAA0C;kBAC1C,cAAc,IAAI,CAAC,UAAU,gBAAgB,UAAU,GAAG,CAAC,CAAC;QAC9E,CAAC;aAAM,IAAI,QAAQ,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,uDAAuD;kBACvD,cAAc,IAAI,CAAC,QAAQ,gBAAgB,QAAQ,GAAG,CAAC,CAAC;QAC1E,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,mCAAmC,KAAK,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,YAAY,CAAC;IACxB,CAAC;IAED,IAAI,YAAY;QACZ,OAAO,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9D,CAAC;IAED,sCAAsC;IACtC,wBAAwB,CAAC,KAAa;QAClC,OAAO,KAAK;aACP,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC;aAC7B,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;CACJ"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AnsiLogger } from 'matterbridge/logger';
|
|
2
|
+
import { Config, DeviceConfig } from './config-types.js';
|
|
3
|
+
import EventEmitter from 'events';
|
|
4
|
+
import { DysonMsg } from './dyson-types.js';
|
|
5
|
+
import { DysonMqttParserConfig, DysonMsgAny } from './dyson-mqtt-parse.js';
|
|
6
|
+
import { DysonMqttSubscribeConfig } from './dyson-mqtt-subscribe.js';
|
|
7
|
+
import { DysonMqttFiltered } from './dyson-mqtt-filter.js';
|
|
8
|
+
import { AsyncEventEmitter } from './async-eventemitter.js';
|
|
9
|
+
export interface DysonMqttConfig<T> {
|
|
10
|
+
topics: DysonMqttSubscribeConfig;
|
|
11
|
+
messages: DysonMqttParserConfig<T>;
|
|
12
|
+
}
|
|
13
|
+
export interface DysonMqttStatusBase {
|
|
14
|
+
reachable: boolean;
|
|
15
|
+
initialised: boolean;
|
|
16
|
+
}
|
|
17
|
+
export type DysonMqttStatus<T> = T & DysonMqttStatusBase;
|
|
18
|
+
export interface DysonMqttEventMapBase {
|
|
19
|
+
error: [unknown];
|
|
20
|
+
subscribed: [];
|
|
21
|
+
status: [];
|
|
22
|
+
}
|
|
23
|
+
export interface DysonMqttEventMap<T> extends DysonMqttEventMapBase {
|
|
24
|
+
message: [DysonMsgAny<T>];
|
|
25
|
+
}
|
|
26
|
+
export type PublishArgs<T, O extends string> = {
|
|
27
|
+
[K in keyof T]: T[K] extends DysonMsg ? (Omit<T[K], O | 'msg'> extends Record<string, never> ? [T[K]['msg']] : [T[K]['msg'], Omit<T[K], O | 'msg'>]) : never;
|
|
28
|
+
}[keyof T];
|
|
29
|
+
export interface DysonMqttLike extends EventEmitter {
|
|
30
|
+
waitUntilInitialised(): Promise<void>;
|
|
31
|
+
stop(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export declare abstract class DysonMqtt<T, S> extends AsyncEventEmitter<DysonMqttEventMap<T>> implements DysonMqttLike {
|
|
34
|
+
readonly log: AnsiLogger;
|
|
35
|
+
readonly config: Config;
|
|
36
|
+
readonly deviceConfig: DeviceConfig;
|
|
37
|
+
readonly mqttConfig: DysonMqttConfig<T>;
|
|
38
|
+
private mqtt;
|
|
39
|
+
private mqttConnection;
|
|
40
|
+
private mqttSubscribe;
|
|
41
|
+
private mqttFilter;
|
|
42
|
+
readonly status: DysonMqttStatus<S>;
|
|
43
|
+
constructor(log: AnsiLogger, config: Config, deviceConfig: DeviceConfig, mqttConfig: DysonMqttConfig<T>);
|
|
44
|
+
downTimerHandle?: NodeJS.Timeout;
|
|
45
|
+
updateReachable(reachable: boolean): void;
|
|
46
|
+
waitUntilInitialised(): Promise<void>;
|
|
47
|
+
stop(): Promise<void>;
|
|
48
|
+
publish(...[msg, params]: PublishArgs<T, 'time'>): Promise<void>;
|
|
49
|
+
logPayload(direction: 'publish' | 'receive', topic: string, payload: DysonMsg, filter?: DysonMqttFiltered): void;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=dyson-mqtt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dyson-mqtt.d.ts","sourceRoot":"","sources":["../src/dyson-mqtt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Matterbridge plugin for Dyson robot vacuum and air treatment devices
|
|
2
|
+
// Copyright © 2025 Alexander Thoukydides
|
|
3
|
+
import { INSPECT_SINGLE_LINE, MM, MP, MR, RD, SR, ST } from './logger-options.js';
|
|
4
|
+
import { MS, tryListener } from './utils.js';
|
|
5
|
+
import { isConfigMqtt } from './dyson-mqtt-config.js';
|
|
6
|
+
import { dysonMqttParse } from './dyson-mqtt-parse.js';
|
|
7
|
+
import { DysonMqttSubscribe } from './dyson-mqtt-subscribe.js';
|
|
8
|
+
import { DysonMqttConnectionIoT, DysonMqttConnectionLocal } from './dyson-mqtt-connect.js';
|
|
9
|
+
import { inspect } from 'util';
|
|
10
|
+
import { DysonMQTTFilter } from './dyson-mqtt-filter.js';
|
|
11
|
+
import { AsyncEventEmitter } from './async-eventemitter.js';
|
|
12
|
+
;
|
|
13
|
+
// Minimum disconnection time before indicating device not reachable
|
|
14
|
+
const MIN_DOWN_TIME = 5 * MS; // 5 seconds
|
|
15
|
+
// Dyson MQTT client for all device types
|
|
16
|
+
export class DysonMqtt extends AsyncEventEmitter {
|
|
17
|
+
log;
|
|
18
|
+
config;
|
|
19
|
+
deviceConfig;
|
|
20
|
+
mqttConfig;
|
|
21
|
+
// The MQTT client
|
|
22
|
+
mqtt;
|
|
23
|
+
mqttConnection;
|
|
24
|
+
mqttSubscribe;
|
|
25
|
+
mqttFilter;
|
|
26
|
+
// The current status
|
|
27
|
+
status = {
|
|
28
|
+
reachable: false,
|
|
29
|
+
initialised: false
|
|
30
|
+
};
|
|
31
|
+
// Construct a new MQTT client
|
|
32
|
+
constructor(log, config, deviceConfig, mqttConfig) {
|
|
33
|
+
super({ captureRejections: true });
|
|
34
|
+
this.log = log;
|
|
35
|
+
this.config = config;
|
|
36
|
+
this.deviceConfig = deviceConfig;
|
|
37
|
+
this.mqttConfig = mqttConfig;
|
|
38
|
+
// Configure the MQTT client
|
|
39
|
+
this.mqttConnection = isConfigMqtt(deviceConfig)
|
|
40
|
+
? new DysonMqttConnectionLocal(log, config, deviceConfig)
|
|
41
|
+
: new DysonMqttConnectionIoT(log, config, deviceConfig);
|
|
42
|
+
this.mqtt = this.mqttConnection.mqtt;
|
|
43
|
+
this.mqtt.on('close', tryListener(this, () => { this.updateReachable(false); }));
|
|
44
|
+
// Manage MQTT topic subscriptions
|
|
45
|
+
const { root_topic, username } = deviceConfig;
|
|
46
|
+
this.mqttSubscribe = new DysonMqttSubscribe(log, this.mqtt, config, mqttConfig.topics, root_topic, username);
|
|
47
|
+
this.mqttSubscribe.on('error', err => this.emit('error', err));
|
|
48
|
+
this.mqttSubscribe.on('subscribed', () => {
|
|
49
|
+
this.emit('subscribed');
|
|
50
|
+
this.updateReachable(true);
|
|
51
|
+
});
|
|
52
|
+
// Handle received MQTT messages
|
|
53
|
+
this.mqttFilter = new DysonMQTTFilter(log);
|
|
54
|
+
this.mqtt.on('message', tryListener(this, (topic, payload) => {
|
|
55
|
+
// Check the received topic and message
|
|
56
|
+
const topicStatus = this.mqttSubscribe.checkTopic(topic);
|
|
57
|
+
const normalise = topicStatus !== 'command';
|
|
58
|
+
const msg = dysonMqttParse(log, mqttConfig.messages, topic, normalise, payload);
|
|
59
|
+
const filter = this.mqttFilter.filter(msg);
|
|
60
|
+
// Dispatch the validated message and indicate a status update
|
|
61
|
+
this.logPayload('receive', topic, msg, filter);
|
|
62
|
+
if (!filter && topicStatus === 'subscribed') {
|
|
63
|
+
this.emit('message', msg);
|
|
64
|
+
this.emit('status');
|
|
65
|
+
}
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
// Update the device reachability, with hysteresis on unreachable indications
|
|
69
|
+
downTimerHandle;
|
|
70
|
+
updateReachable(reachable) {
|
|
71
|
+
if (reachable) {
|
|
72
|
+
// Cancel any pending down timer
|
|
73
|
+
clearTimeout(this.downTimerHandle);
|
|
74
|
+
this.downTimerHandle = undefined;
|
|
75
|
+
// Mark the device as reachable
|
|
76
|
+
if (!this.status.reachable) {
|
|
77
|
+
this.status.reachable = true;
|
|
78
|
+
this.emit('status');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Only start down timer if not already running
|
|
83
|
+
if (!this.downTimerHandle && this.status.reachable) {
|
|
84
|
+
this.downTimerHandle = setTimeout(() => {
|
|
85
|
+
// Mark the device as not reachable after a delay
|
|
86
|
+
this.log.error('MQTT client is down');
|
|
87
|
+
this.downTimerHandle = undefined;
|
|
88
|
+
this.status.reachable = false;
|
|
89
|
+
this.emit('status');
|
|
90
|
+
}, MIN_DOWN_TIME);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Wait until the device is reachable and all initial state has been received
|
|
95
|
+
async waitUntilInitialised() {
|
|
96
|
+
while (!this.status.reachable || !this.status.initialised) {
|
|
97
|
+
await this.onceAsync('status');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Stop the MQTT client
|
|
101
|
+
async stop() {
|
|
102
|
+
await this.mqttConnection.stop();
|
|
103
|
+
}
|
|
104
|
+
// Publish a command
|
|
105
|
+
async publish(...[msg, params]) {
|
|
106
|
+
// Construct the full message
|
|
107
|
+
const time = new Date().toISOString();
|
|
108
|
+
const fullMsg = { msg, ...params, time };
|
|
109
|
+
const payload = JSON.stringify(fullMsg);
|
|
110
|
+
// Publish to the command topic
|
|
111
|
+
const topic = this.mqttSubscribe.commandTopic;
|
|
112
|
+
this.logPayload('publish', topic, fullMsg);
|
|
113
|
+
await this.mqtt.publishAsync(topic, payload, { qos: 1, retain: false });
|
|
114
|
+
}
|
|
115
|
+
// Log received or transmitted message payloads
|
|
116
|
+
logPayload(direction, topic, payload, filter) {
|
|
117
|
+
if (!this.config.debugFeatures.includes('Log MQTT Payloads'))
|
|
118
|
+
return;
|
|
119
|
+
// List the fixed fields first
|
|
120
|
+
const { msg, time, ...other } = payload;
|
|
121
|
+
const properties = [
|
|
122
|
+
`msg: ${direction === 'publish' ? MP : MR}'${msg}'${MM}`,
|
|
123
|
+
`time: ${RD}'${time}'${MM}`
|
|
124
|
+
];
|
|
125
|
+
// Include the other fields from the message, unless it is a duplicate
|
|
126
|
+
if (filter === 'duplicate') {
|
|
127
|
+
properties.push('...');
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
properties.push(...Object.entries(other).sort().map(([key, value]) => `${key}: ${inspect(value, INSPECT_SINGLE_LINE)}`));
|
|
131
|
+
}
|
|
132
|
+
// Log the message (formatting with strikethrough if dropped by filter)
|
|
133
|
+
const object = `${MM}{ ${properties.join(', ')} }${RD}`;
|
|
134
|
+
this.log.debug(filter ? `MQTT ${direction}: ${ST}${object}${SR} (${filter})`
|
|
135
|
+
: `MQTT ${direction}: ${object} topic '${topic}'`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=dyson-mqtt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dyson-mqtt.js","sourceRoot":"","sources":["../src/dyson-mqtt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface DysonMsg {
|
|
2
|
+
msg: string;
|
|
3
|
+
time: string;
|
|
4
|
+
}
|
|
5
|
+
export declare enum DysonModeReason {
|
|
6
|
+
Unknown = "",
|
|
7
|
+
LocalApp = "LAPP",
|
|
8
|
+
LocalSchedule = "LSCH",
|
|
9
|
+
RemoteApp = "RAPP",
|
|
10
|
+
Preconditioning = "PRC",
|
|
11
|
+
PhysicalUserInteraction = "PUI"
|
|
12
|
+
}
|
|
13
|
+
export declare enum DysonStateReason {
|
|
14
|
+
Environment = "ENV",
|
|
15
|
+
FLT = "FLT",
|
|
16
|
+
Mode = "MODE"
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=dyson-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dyson-types.d.ts","sourceRoot":"","sources":["../src/dyson-types.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,QAAQ;IACrB,GAAG,EAAqB,MAAM,CAAC;IAC/B,IAAI,EAAoB,MAAM,CAAC;CAClC;AAGD,oBAAY,eAAe;IACvB,OAAO,KAAqB;IAC5B,QAAQ,SAAwB;IAChC,aAAa,SAAmB;IAChC,SAAS,SAAuB;IAChC,eAAe,QAAgB;IAC/B,uBAAuB,QAAQ;CAClC;AAGD,oBAAY,gBAAgB;IACxB,WAAW,QAAoB;IAC/B,GAAG,QAA4B;IAC/B,IAAI,SAA4B;CACnC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Matterbridge plugin for Dyson robot vacuum and air treatment devices
|
|
2
|
+
// Copyright © 2025 Alexander Thoukydides
|
|
3
|
+
// Dyson mode reason
|
|
4
|
+
export var DysonModeReason;
|
|
5
|
+
(function (DysonModeReason) {
|
|
6
|
+
DysonModeReason["Unknown"] = "";
|
|
7
|
+
DysonModeReason["LocalApp"] = "LAPP";
|
|
8
|
+
DysonModeReason["LocalSchedule"] = "LSCH";
|
|
9
|
+
DysonModeReason["RemoteApp"] = "RAPP";
|
|
10
|
+
DysonModeReason["Preconditioning"] = "PRC";
|
|
11
|
+
DysonModeReason["PhysicalUserInteraction"] = "PUI";
|
|
12
|
+
})(DysonModeReason || (DysonModeReason = {}));
|
|
13
|
+
// Dyson state reason
|
|
14
|
+
export var DysonStateReason;
|
|
15
|
+
(function (DysonStateReason) {
|
|
16
|
+
DysonStateReason["Environment"] = "ENV";
|
|
17
|
+
DysonStateReason["FLT"] = "FLT";
|
|
18
|
+
DysonStateReason["Mode"] = "MODE";
|
|
19
|
+
})(DysonStateReason || (DysonStateReason = {}));
|
|
20
|
+
//# sourceMappingURL=dyson-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dyson-types.js","sourceRoot":"","sources":["../src/dyson-types.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,yCAAyC;AAQzC,oBAAoB;AACpB,MAAM,CAAN,IAAY,eAOX;AAPD,WAAY,eAAe;IACvB,+BAA4B,CAAA;IAC5B,oCAAgC,CAAA;IAChC,yCAAgC,CAAA;IAChC,qCAAgC,CAAA;IAChC,0CAA+B,CAAA;IAC/B,kDAA+B,CAAA;AACnC,CAAC,EAPW,eAAe,KAAf,eAAe,QAO1B;AAED,qBAAqB;AACrB,MAAM,CAAN,IAAY,gBAIX;AAJD,WAAY,gBAAgB;IACxB,uCAA+B,CAAA;IAC/B,+BAA+B,CAAA;IAC/B,iCAAgC,CAAA;AACpC,CAAC,EAJW,gBAAgB,KAAhB,gBAAgB,QAI3B"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Behavior, MaybePromise } from 'matterbridge/matter';
|
|
2
|
+
import { ModeBase, RvcOperationalState } from 'matterbridge/matter/clusters';
|
|
3
|
+
import { RvcCleanModeBehavior, RvcOperationalStateBehavior, RvcRunModeBehavior } from 'matterbridge/matter/behaviors';
|
|
4
|
+
import { AnsiLogger } from 'matterbridge/logger';
|
|
5
|
+
export declare enum RvcRunMode360 {
|
|
6
|
+
Idle = 0,
|
|
7
|
+
Cleaning = 1,
|
|
8
|
+
Mapping = 2
|
|
9
|
+
}
|
|
10
|
+
export declare enum RvcCleanMode360 {
|
|
11
|
+
Quiet = 0,// Eye: Quiet Heurist: Quiet Vis Nav: Quiet
|
|
12
|
+
Quick = 1,// Vis Nav: Quick
|
|
13
|
+
High = 2,// Heurist: High
|
|
14
|
+
MaxBoost = 3,// Eye: Max Heurist: Max Vis Nav: Boost
|
|
15
|
+
Auto = 4
|
|
16
|
+
}
|
|
17
|
+
export declare const VENDOR_ERROR_360 = 128;
|
|
18
|
+
export interface EndpointCommands360 {
|
|
19
|
+
ChangeRunMode: (newMode: RvcRunMode360) => MaybePromise;
|
|
20
|
+
ChangeCleanMode: (newMode: RvcCleanMode360) => MaybePromise;
|
|
21
|
+
Pause: () => MaybePromise;
|
|
22
|
+
Resume: () => MaybePromise;
|
|
23
|
+
GoHome: () => MaybePromise;
|
|
24
|
+
}
|
|
25
|
+
type EndpointCommand360Args<T extends keyof EndpointCommands360> = Parameters<EndpointCommands360[T]>;
|
|
26
|
+
export declare class BehaviorDevice360 {
|
|
27
|
+
readonly log: AnsiLogger;
|
|
28
|
+
readonly commands: Partial<EndpointCommands360>;
|
|
29
|
+
constructor(log: AnsiLogger);
|
|
30
|
+
setCommandHandler<Command extends keyof EndpointCommands360>(command: Command, handler: EndpointCommands360[Command]): void;
|
|
31
|
+
executeCommand<Command extends keyof EndpointCommands360>(command: Command, ...args: EndpointCommand360Args<Command>): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export declare class Behavior360 extends Behavior {
|
|
34
|
+
static readonly id = "dyson-rvc";
|
|
35
|
+
state: Behavior360.State;
|
|
36
|
+
}
|
|
37
|
+
export declare namespace Behavior360 {
|
|
38
|
+
class State {
|
|
39
|
+
device: BehaviorDevice360;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export declare class RvcRunModeServer360 extends RvcRunModeBehavior {
|
|
43
|
+
changeToMode({ newMode }: ModeBase.ChangeToModeRequest): Promise<ModeBase.ChangeToModeResponse>;
|
|
44
|
+
}
|
|
45
|
+
export declare class RvcCleanModeServer360 extends RvcCleanModeBehavior {
|
|
46
|
+
changeToMode({ newMode }: ModeBase.ChangeToModeRequest): Promise<ModeBase.ChangeToModeResponse>;
|
|
47
|
+
}
|
|
48
|
+
export declare class RvcOperationalStateServer360 extends RvcOperationalStateBehavior {
|
|
49
|
+
command(command: 'Pause' | 'Resume' | 'GoHome', defaultErrorId: RvcOperationalState.ErrorState): Promise<RvcOperationalState.OperationalCommandResponse>;
|
|
50
|
+
pause(): Promise<RvcOperationalState.OperationalCommandResponse>;
|
|
51
|
+
resume(): Promise<RvcOperationalState.OperationalCommandResponse>;
|
|
52
|
+
goHome(): Promise<RvcOperationalState.OperationalCommandResponse>;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=endpoint-360-behavior.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-360-behavior.d.ts","sourceRoot":"","sources":["../src/endpoint-360-behavior.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EACH,oBAAoB,EACpB,2BAA2B,EAC3B,kBAAkB,EACrB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAKjD,oBAAY,aAAa;IACrB,IAAI,IAAA;IACJ,QAAQ,IAAA;IACR,OAAO,IAAA;CACV;AAGD,oBAAY,eAAe;IACvB,KAAK,IAAA,CAAO,6CAA6C;IACzD,KAAK,IAAA,CAAO,6CAA6C;IACzD,IAAI,IAAA,CAAQ,4BAA4B;IACxC,QAAQ,IAAA,CAAI,6CAA6C;IACzD,IAAI,IAAA;CACP;AAGD,eAAO,MAAM,gBAAgB,MAAO,CAAC;AAGrC,MAAM,WAAW,mBAAmB;IAChC,aAAa,EAAI,CAAC,OAAO,EAAE,aAAa,KAAO,YAAY,CAAC;IAC5D,eAAe,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,YAAY,CAAC;IAC5D,KAAK,EAAY,MAA8B,YAAY,CAAC;IAC5D,MAAM,EAAW,MAA8B,YAAY,CAAC;IAC5D,MAAM,EAAW,MAA8B,YAAY,CAAC;CAC/D;AACD,KAAK,sBAAsB,CAAC,CAAC,SAAS,MAAM,mBAAmB,IAAI,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAItG,qBAAa,iBAAiB;IAMd,QAAQ,CAAC,GAAG,EAAE,UAAU;IAHpC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAM;gBAGhC,GAAG,EAAE,UAAU;IAGpC,iBAAiB,CAAC,OAAO,SAAS,MAAM,mBAAmB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,CAAC,OAAO,CAAC,GAAG,IAAI;IAMrH,cAAc,CAAC,OAAO,SAAS,MAAM,mBAAmB,EAC1D,OAAO,EAAK,OAAO,EACnB,GAAG,IAAI,EAAK,sBAAsB,CAAC,OAAO,CAAC,GAC5C,OAAO,CAAC,IAAI,CAAC;CAKnB;AACD,qBAAa,WAAY,SAAQ,QAAQ;IACrC,gBAAyB,EAAE,eAAe;IAClC,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC;CACpC;AAED,yBAAiB,WAAW,CAAC;IACzB,MAAa,KAAK;QACd,MAAM,EAAG,iBAAiB,CAAC;KAC9B;CACJ;AAGD,qBAAa,mBAAoB,SAAQ,kBAAkB;IAGjD,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE,QAAQ,CAAC,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC;CAoBxG;AAGD,qBAAa,qBAAsB,SAAQ,oBAAoB;IAGrD,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE,QAAQ,CAAC,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC;CAoBxG;AAGD,qBAAa,4BAA6B,SAAQ,2BAA2B;IAuBnE,OAAO,CACT,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,EACtC,cAAc,EAAE,mBAAmB,CAAC,UAAU,GAC/C,OAAO,CAAC,mBAAmB,CAAC,0BAA0B,CAAC;IAejD,KAAK,IAAK,OAAO,CAAC,mBAAmB,CAAC,0BAA0B,CAAC;IAKjE,MAAM,IAAI,OAAO,CAAC,mBAAmB,CAAC,0BAA0B,CAAC;IAKjE,MAAM,IAAI,OAAO,CAAC,mBAAmB,CAAC,0BAA0B,CAAC;CAG7E"}
|