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.

100 lines 4.56 kB
// 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