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.
100 lines • 4.56 kB
JavaScript
// Matterbridge plugin for Dyson robot vacuum and air treatment devices
// Copyright © 2025 Alexander Thoukydides
import { assertIsDefined, getValidationTree } from './utils.js';
import { checkers as dysonMsgCheckers } from './ti/dyson-types.js';
import { INSPECT_VERBOSE } from './logger-options.js';
import { inspect } from 'util';
// Parse and check a received MQTT message
export function dysonMqttParse(log, config, topic, normalise, payload) {
// Parse a raw message buffer as JSON and normalise property names
const parseAndNormalise = (text) => {
try {
const parsed = JSON.parse(text);
return normalise ? normaliseKeys(parsed) : parsed;
}
catch (err) {
logCheckerValidation("error" /* LogLevel.ERROR */, text);
const message = err instanceof Error ? err.message : String(err);
throw new Error(`Failed to parse Dyson MQTT message as JSON: ${message}`);
}
};
// Check that a received message is known and matches the expected type
const assertIsDysonMsg = (payload) => {
// Check that the message is of the general form expected
const baseChecker = dysonMsgCheckers.DysonMsg;
if (!baseChecker.test(payload)) {
baseChecker.setReportedPath('DysonMsg');
const validation = baseChecker.validate(payload);
assertIsDefined(validation);
logCheckerValidation("error" /* LogLevel.ERROR */, payload, validation);
throw new Error('Unexpected structure of Dyson MQTT message');
}
// Check whether the message type is known
const checker = getCheckerForMsg(payload);
// Check that the message is of the form expected for this type
const validation = checker.validate(payload);
if (validation) {
logCheckerValidation("error" /* LogLevel.ERROR */, payload, validation);
throw new Error('Unexpected structure of Dyson MQTT message');
}
const strictValidation = checker.strictValidate(payload);
if (strictValidation) {
logCheckerValidation("warn" /* LogLevel.WARN */, payload, strictValidation);
// (Continue processing messages that include unexpected properties)
}
};
// Map a message name to the corresponding type checker
const getCheckerForMsg = (payload) => {
const { prefix, checkers } = config;
// Construct the type name for this message
const msgPascalCase = kebabToPascalCase(payload.msg);
const typeName = `${prefix}${msgPascalCase}`;
const assertIsTypeName = (name) => {
if (name in config.checkers)
return;
logCheckerValidation("error" /* LogLevel.ERROR */, payload);
throw new Error(`Unrecognised Dyson MQTT message type: ${name}`);
};
assertIsTypeName(typeName);
// Return the type checker
const checker = checkers[typeName];
checker.setReportedPath(String(typeName));
return checker;
};
// Log checker validation errors
const logCheckerValidation = (level, payload, errors) => {
// Log the formatted message
log.log(level, `MQTT topic '${topic}':`);
if (errors) {
const validationLines = getValidationTree(errors);
validationLines.forEach(line => { log.log(level, line); });
}
const payloadLines = inspect(payload, INSPECT_VERBOSE).split('\n');
payloadLines.forEach(line => { log.info(` ${line}`); });
};
// Parse and check the message
const msg = parseAndNormalise(payload.toString());
assertIsDysonMsg(msg);
return msg;
}
// Convert a string from kebab-case (or FLAMING-KEBAB-CASE) to PascalCase
function kebabToPascalCase(str) {
return str.toLowerCase().split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join('');
}
// Convert a string from kebab-case or 'space case' to camelCase
function kebabToCamelCase(str) {
return str.replace(/[-\s]([a-z])/g, (_, letter) => letter.toUpperCase());
}
// Recursively convert property names from snake-case to camelCase
function normaliseKeys(obj) {
if (Array.isArray(obj)) {
return obj.map(item => normaliseKeys(item));
}
if (obj !== null && typeof obj === 'object') {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [kebabToCamelCase(key), normaliseKeys(value)]));
}
return obj;
}
//# sourceMappingURL=dyson-mqtt-parse.js.map