homebridge-z2m
Version:
Expose your Zigbee devices to HomeKit with ease, by integrating Zigbee2MQTT with Homebridge.
182 lines • 7.08 kB
JavaScript
;
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