UNPKG

homebridge-z2m

Version:

Expose your Zigbee devices to HomeKit with ease, by integrating Zigbee2MQTT with Homebridge.

182 lines 7.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.errorToString = errorToString; exports.parseBridgeOnlineState = parseBridgeOnlineState; exports.sanitizeAccessoryName = sanitizeAccessoryName; exports.getDiffFromArrays = getDiffFromArrays; exports.getOrAddCharacteristic = getOrAddCharacteristic; exports.roundToDecimalPlaces = roundToDecimalPlaces; exports.copyExposesRangeToCharacteristic = copyExposesRangeToCharacteristic; exports.allowSingleValueForCharacteristic = allowSingleValueForCharacteristic; exports.setValidValuesOnCharacteristic = setValidValuesOnCharacteristic; exports.groupByEndpoint = groupByEndpoint; exports.getAllEndpoints = getAllEndpoints; exports.sanitizeAndFilterExposesEntries = sanitizeAndFilterExposesEntries; const z2mModels_1 = require("./z2mModels"); function errorToString(e) { if (typeof e === 'string') { return e; } if (e instanceof Error) { return e.message; // works, `e` narrowed to Error } return JSON.stringify(e); } /** * Parse bridge/state payload from Zigbee2MQTT and return online status. * Supports both z2m 2.0+ JSON format ({"state":"online"}) and legacy plain string format ("online"). * @param payload The raw payload string from MQTT * @returns true if Zigbee2MQTT is online, false otherwise */ function parseBridgeOnlineState(payload) { let state; try { const parsed = JSON.parse(payload); if (parsed && typeof parsed === 'object' && typeof parsed.state === 'string') { state = parsed.state; } else { state = payload; } } catch { // Not valid JSON, treat as plain string format (legacy z2m versions) state = payload; } return state !== 'offline'; } /** * Added because of the following warning from HAP-NodeJS: * "The accessory '<SOME NAME HERE>' has an invalid 'Name' characteristic ('<SOME NAME HERE>'). Please use only alphanumeric, space, and * apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the * accessory from being added in the Home App or cause unresponsiveness." * @param name */ function sanitizeAccessoryName(name) { // Replace all non-alphanumeric characters with a space (except spaces of course) const sanitized = name.replace(/[^a-zA-Z0-9' ]+/g, ' '); // Make sure there's at most one space in a row, and remove leading/trailing spaces as well as leading apostrophes return sanitized .replace(/\s{2,}/g, ' ') .replace(/^[ ']+/, '') .trim(); } function getDiffFromArrays(a, b) { return a.filter((x) => !b.includes(x)).concat(b.filter((x) => !a.includes(x))); } function getOrAddCharacteristic(service, characteristic) { return service.getCharacteristic(characteristic) || service.addCharacteristic(characteristic); } function roundToDecimalPlaces(input, decimalPlaces) { if (decimalPlaces !== Math.round(decimalPlaces) || decimalPlaces < 1 || decimalPlaces > 10) { throw new Error(`decimalPlaces must be a whole number between 1 and 10, not ${decimalPlaces}`); } const maxDecimals = Math.pow(10, decimalPlaces); return Math.round((input + Number.EPSILON) * maxDecimals) / maxDecimals; } function copyExposesRangeToCharacteristic(exposes, characteristic) { if ((0, z2mModels_1.exposesHasNumericRangeProperty)(exposes)) { // Make sure value is within range before setting the range properties. const current_value = characteristic.value; if (current_value === undefined) { characteristic.value = Math.round((exposes.value_min + exposes.value_max) / 2); } else if (current_value < exposes.value_min) { characteristic.value = exposes.value_min; } else if (current_value > exposes.value_max) { characteristic.value = exposes.value_max; } characteristic.setProps({ minValue: exposes.value_min, maxValue: exposes.value_max, minStep: exposes.value_step ?? 1, }); return true; } return false; } function allowSingleValueForCharacteristic(characteristic, value) { characteristic.value = value; characteristic.setProps({ minValue: value, maxValue: value, validValues: [value], }); return characteristic; } function setValidValuesOnCharacteristic(characteristic, validValues) { if (validValues.length > 0) { const current_value = characteristic.value; if (current_value === undefined || !validValues.includes(current_value)) { characteristic.value = validValues[0]; } characteristic.setProps({ minValue: Math.min(...validValues), maxValue: Math.max(...validValues), validValues: validValues, }); } return characteristic; } function groupByEndpoint(entries) { const endpointMap = new Map(); entries.forEach((entry) => { const collection = endpointMap.get(entry.endpoint); if (!collection) { endpointMap.set(entry.endpoint, [entry]); } else { collection.push(entry); } }); return endpointMap; } function getAllEndpoints(entries, parentEndpoint) { const endpoints = new Set(); entries.forEach((entry) => { const endpoint = entry.endpoint ?? parentEndpoint; if (endpoint !== undefined || entry.property !== undefined) { endpoints.add(endpoint); } if ((0, z2mModels_1.exposesHasFeatures)(entry)) { getAllEndpoints(entry.features, endpoint).forEach((e) => { endpoints.add(e); }); } }); const result = Array.from(endpoints); // Sort so that `undefined` is always the first and the rest is sorted alphabetically. result.sort((a, b) => { if (a === undefined) { return -1; } if (b === undefined) { return 1; } return a.localeCompare(b); }); return result; } function sanitizeAndFilterExposesEntries(input, filter, valueFilter, parentEndpoint) { return input .filter((e) => filter === undefined || filter(e)) .map((e) => sanitizeAndFilterExposesEntry(e, filter, valueFilter, parentEndpoint)); } function sanitizeAndFilterExposesEntry(input, filter, valueFilter, parentEndpoint) { const output = { ...input, }; if (output.endpoint === undefined && parentEndpoint !== undefined) { // Make sure features inherit the endpoint from their parent, if it is not defined explicitly. output.endpoint = parentEndpoint; } if ((0, z2mModels_1.exposesHasFeatures)(output)) { output.features = sanitizeAndFilterExposesEntries(output.features, filter, valueFilter, output.endpoint); } if (Array.isArray(output.values) && valueFilter !== undefined) { output.values = valueFilter(output); } return output; } //# sourceMappingURL=helpers.js.map