homebridge-z2m
Version:
Expose your Zigbee devices to HomeKit with ease, by integrating Zigbee2MQTT with Homebridge.
152 lines (139 loc) • 4.71 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-console */
/* eslint-disable max-len */
import * as herdsman from 'zigbee-herdsman-converters';
import { ExposesEntry, exposesHasFeatures, exposesHasProperty } from './src/z2mModels';
// Filter out devices that only expose a `linkquality`
// and add white label devices
const allDevices = herdsman.definitions.filter(
(d) => typeof d.exposes === 'function' || d.exposes.find((e) => e.name !== 'linkquality') !== undefined,
);
for (const device of allDevices) {
if (typeof device.exposes === 'function') {
// Call function to generate array of exposes information.
console.log(`Generating exposes array for ${device.vendor} ${device.model}`);
device.exposes = device.exposes();
}
if (device.whiteLabel) {
for (const whiteLabel of device.whiteLabel) {
const whiteLabelDevice = {
...device,
model: whiteLabel.model,
vendor: whiteLabel.vendor,
description: whiteLabel.description,
whiteLabelOf: device,
};
delete whiteLabelDevice.whiteLabel;
allDevices.push(whiteLabelDevice);
}
}
}
function generateZigbee2MqttLink(device: any) {
const find = '[/| |:]';
const re = new RegExp(find, 'g');
return 'https://www.zigbee2mqtt.io/devices/' + encodeURIComponent(device.model.replace(re, '_')) + '.html';
}
function getEndpointMapping(exposes: ExposesEntry[], previousEndpoint?: string | undefined): Record<string, Set<string>> {
const mapping: Record<string, Set<string>> = {};
for (const e of exposes) {
if (exposesHasFeatures(e)) {
const propertyPrefix = e.property !== undefined ? `${e.property}.` : '';
const endpoint = e.endpoint === undefined ? previousEndpoint : e.endpoint;
const subMapping = getEndpointMapping(e.features, endpoint);
for (const [endpoint, properties] of Object.entries(subMapping)) {
if (mapping[endpoint] === undefined) {
mapping[endpoint] = new Set();
}
for (const property of properties) {
mapping[endpoint].add(propertyPrefix + property);
}
}
// No need to check for properties again.
continue;
}
if (exposesHasProperty(e)) {
if (e.property === 'linkquality' || e.property === 'battery') {
// Ignore this one
continue;
}
let endpoint = e.endpoint === undefined ? previousEndpoint : e.endpoint;
if (endpoint === undefined) {
endpoint = 'UNDEFINED';
}
if (mapping[endpoint] === undefined) {
mapping[endpoint] = new Set();
}
mapping[endpoint].add(e.property);
}
}
return mapping;
}
function hasExposesWithNameWithoutEndpoint(exposes: ExposesEntry[]) {
for (const e of exposes) {
if (exposesHasProperty(e)) {
if (e.property === 'linkquality') {
// Ignore this one
continue;
}
if (e.endpoint === undefined) {
return true;
}
}
// If this entry defines an endpoint, we assume the features inherit it.
// TODO: update if statement
if (exposesHasFeatures(e)) {
if (e.endpoint !== undefined) {
continue;
}
if (hasExposesWithNameWithoutEndpoint(e.features)) {
return true;
}
}
}
return false;
}
function hasAtLeastOneDefinedEndpoint(exposes: ExposesEntry[]) {
for (const e of exposes) {
if (e.endpoint !== undefined) {
return true;
}
if (exposesHasProperty(e)) {
if (e.property === 'linkquality' || e.property === 'battery') {
// Ignore this one
continue;
}
if (e.endpoint !== undefined) {
return true;
}
}
if (exposesHasFeatures(e)) {
if (hasAtLeastOneDefinedEndpoint(e.features)) {
return true;
}
}
}
return false;
}
const devices: string[] = [];
allDevices.forEach((d) => {
try {
if (d.whiteLabelOf === undefined) {
if (hasAtLeastOneDefinedEndpoint(d.exposes) && hasExposesWithNameWithoutEndpoint(d.exposes)) {
let entry = `* [${d.vendor} ${d.model}](${generateZigbee2MqttLink(d)}):`;
const mapping = getEndpointMapping(d.exposes);
const keys = Object.keys(mapping).sort();
for (const endpoint of keys) {
entry += `\n * Endpoint \`${endpoint}\`: \`${Array.from(mapping[endpoint]).join('`, `')}\``;
}
devices.push(entry);
}
}
} catch (Error) {
console.log(`ERROR: ${d.vendor} ${d.model}: ${Error}`);
}
});
console.log('---------------');
console.log(`Devices with exposes without endpoint (${devices.length}):`);
// Sort list alphabetically
devices.sort();
devices.forEach((d) => console.log(d));