UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

371 lines (369 loc) • 17.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.generateDefinition = generateDefinition; exports.generateGreenPowerDefinition = generateGreenPowerDefinition; const zigbee_herdsman_1 = require("zigbee-herdsman"); const logger_1 = require("./logger"); const m = __importStar(require("./modernExtend")); const philips = __importStar(require("./philips")); const utils_1 = require("./utils"); const NS = "zhc:gendef"; // Generator allows to define instances of GeneratedExtend that have typed arguments to extender. class ExtendGenerator { extend; args; source; lib; constructor(args) { this.extend = args.extend; this.args = args.args; this.source = args.source; this.lib = args.lib; } getExtend() { return this.extend(this.args); } getSource() { let jsonArgs = JSON.stringify(this.args); if (!this.args || jsonArgs === "{}") { jsonArgs = ""; } return `${this.source}(${jsonArgs})`; } } // If generator will have endpoint argument - generator implementation // should not provide it if only the first device endpoint is passed in. // If multiple endpoints provided(maybe including the first device endpoint) - // they all should be passed as an argument, where possible, to be explicit. const INPUT_EXTENDERS = [ [ ["msTemperatureMeasurement"], async (d, eps) => [new ExtendGenerator({ extend: m.temperature, args: maybeEndpointArgs(d, eps), source: "temperature" })], ], [["msPressureMeasurement"], async (d, eps) => [new ExtendGenerator({ extend: m.pressure, args: maybeEndpointArgs(d, eps), source: "pressure" })]], [["msRelativeHumidity"], async (d, eps) => [new ExtendGenerator({ extend: m.humidity, args: maybeEndpointArgs(d, eps), source: "humidity" })]], [["msCO2"], async (d, eps) => [new ExtendGenerator({ extend: m.co2, args: maybeEndpointArgs(d, eps), source: "co2" })]], [["genPowerCfg"], async (d, eps) => [new ExtendGenerator({ extend: m.battery, source: "battery" })]], [["genOnOff", "genLevelCtrl", "lightingColorCtrl"], extenderOnOffLight], [["seMetering", "haElectricalMeasurement"], extenderElectricityMeter], [["closuresDoorLock"], extenderLock], [ ["msIlluminanceMeasurement"], async (d, eps) => [new ExtendGenerator({ extend: m.illuminance, args: maybeEndpointArgs(d, eps), source: "illuminance" })], ], [["msOccupancySensing"], async (d, eps) => [new ExtendGenerator({ extend: m.occupancy, source: "occupancy" })]], [ ["ssIasZone"], async (d, eps) => [ new ExtendGenerator({ extend: m.iasZoneAlarm, args: { zoneType: "generic", zoneAttributes: ["alarm_1", "alarm_2", "tamper", "battery_low"], }, source: "iasZoneAlarm", }), ], ], [["ssIasWd"], async (d, eps) => [new ExtendGenerator({ extend: m.iasWarning, source: "iasWarning" })]], [ ["genDeviceTempCfg"], async (d, eps) => [new ExtendGenerator({ extend: m.deviceTemperature, args: maybeEndpointArgs(d, eps), source: "deviceTemperature" })], ], [["pm25Measurement"], async (d, eps) => [new ExtendGenerator({ extend: m.pm25, args: maybeEndpointArgs(d, eps), source: "pm25" })]], [["msFlowMeasurement"], async (d, eps) => [new ExtendGenerator({ extend: m.flow, args: maybeEndpointArgs(d, eps), source: "flow" })]], [["msSoilMoisture"], async (d, eps) => [new ExtendGenerator({ extend: m.soilMoisture, args: maybeEndpointArgs(d, eps), source: "soilMoisture" })]], [ ["closuresWindowCovering"], async (d, eps) => [new ExtendGenerator({ extend: m.windowCovering, args: { controls: ["lift", "tilt"] }, source: "windowCovering" })], ], [["genBinaryInput"], extenderBinaryInput], [["genBinaryOutput"], extenderBinaryOutput], ]; const OUTPUT_EXTENDERS = [ [["genOnOff"], async (d, eps) => [new ExtendGenerator({ extend: m.commandsOnOff, args: maybeEndpointArgs(d, eps), source: "commandsOnOff" })]], [ ["genLevelCtrl"], async (d, eps) => [new ExtendGenerator({ extend: m.commandsLevelCtrl, args: maybeEndpointArgs(d, eps), source: "commandsLevelCtrl" })], ], [ ["lightingColorCtrl"], async (d, eps) => [new ExtendGenerator({ extend: m.commandsColorCtrl, args: maybeEndpointArgs(d, eps), source: "commandsColorCtrl" })], ], [ ["closuresWindowCovering"], async (d, eps) => [ new ExtendGenerator({ extend: m.commandsWindowCovering, args: maybeEndpointArgs(d, eps), source: "commandsWindowCovering" }), ], ], ]; function generateSource(definition, generatedExtend) { const imports = [...new Set(generatedExtend.map((e) => e.lib ?? "modernExtend"))]; const importsStr = imports.map((e) => `import * as ${e === "modernExtend" ? "m" : e} from 'zigbee-herdsman-converters/lib/${e}';`).join("\n"); return `${importsStr} export default { zigbeeModel: ['${definition.zigbeeModel}'], model: '${definition.model}', vendor: '${definition.vendor}', description: 'Automatically generated definition', extend: [${generatedExtend.map((e) => `${e.lib ?? "m"}.${e.getSource()}`).join(", ")}], meta: ${JSON.stringify(definition.meta || {})}, }; `; } function generateGreenPowerSource(definition, ieeeAddr) { return `import {genericGreenPower} from 'zigbee-herdsman-converters/lib/modernExtend'; export default { fingerprint: [{modelID: '${definition.model}', ieeeAddr: new RegExp('^${ieeeAddr}$')}], model: '${definition.model}', vendor: '${definition.vendor}', description: 'Automatically generated definition for Green Power', extend: [genericGreenPower()], };`; } async function generateDefinition(device) { if (device.type === "GreenPower") { return generateGreenPowerDefinition(device); } // Map cluster to all endpoints that have this cluster. const mapClusters = (endpoint, clusters, clusterMap) => { for (const cluster of clusters) { if (!clusterMap.has(cluster.name)) { clusterMap.set(cluster.name, []); } const endpointsWithCluster = clusterMap.get(cluster.name); endpointsWithCluster.push(endpoint); } }; const knownInputClusters = INPUT_EXTENDERS.flatMap((ext) => ext[0]); const knownOutputClusters = OUTPUT_EXTENDERS.flatMap((ext) => ext[0]); const inputClusterMap = new Map(); const outputClusterMap = new Map(); for (const endpoint of device.endpoints) { // Filter clusters to leave only the ones that we can generate extenders for. const inputClusters = endpoint.getInputClusters().filter((c) => knownInputClusters.find((known) => known === c.name)); const outputClusters = endpoint.getOutputClusters().filter((c) => knownOutputClusters.find((known) => known === c.name)); mapClusters(endpoint, inputClusters, inputClusterMap); mapClusters(endpoint, outputClusters, outputClusterMap); } // Generate extenders const usedExtenders = []; const generatedExtend = []; const addGenerators = async (clusterName, endpoints, extenders) => { const extender = extenders.find((e) => e[0].includes(clusterName)); if (!extender || usedExtenders.includes(extender)) { return; } usedExtenders.push(extender); generatedExtend.push(...(await extender[1](device, endpoints))); }; for (const [cluster, endpoints] of inputClusterMap) { await addGenerators(cluster, endpoints, INPUT_EXTENDERS); } for (const [cluster, endpoints] of outputClusterMap) { await addGenerators(cluster, endpoints, OUTPUT_EXTENDERS); } const extenders = generatedExtend.map((e) => e.getExtend()); // Generated definition below will provide this. for (const extender of extenders) { extender.endpoint = undefined; } // Currently multiEndpoint is enabled if device has more then 1 endpoint. // It is possible to better check if device should be considered multiEndpoint // based, for example, on generator arguments(i.e. presence of "endpointNames"), // but this will be enough for now. const endpointsWithoutGreenPower = device.endpoints.filter((e) => e.ID !== 242); const multiEndpoint = endpointsWithoutGreenPower.length > 1; if (multiEndpoint) { const endpoints = {}; for (const endpoint of endpointsWithoutGreenPower) { endpoints[endpoint.ID.toString()] = endpoint.ID; } // Add to beginning for better visibility. generatedExtend.unshift(new ExtendGenerator({ extend: m.deviceEndpoints, args: { endpoints }, source: "deviceEndpoints" })); extenders.unshift(generatedExtend[0].getExtend()); } const definition = { zigbeeModel: [device.modelID], model: device.modelID ?? "", vendor: device.manufacturerName ?? "", description: "Automatically generated definition", extend: extenders, generated: true, }; if (multiEndpoint) { definition.meta = { multiEndpoint }; } const externalDefinitionSource = generateSource(definition, generatedExtend); return { externalDefinitionSource, definition }; } function generateGreenPowerDefinition(device) { const definition = { fingerprint: [{ modelID: device.modelID, ieeeAddr: new RegExp(`^${device.ieeeAddr}$`) }], model: device.modelID ?? "", vendor: device.manufacturerName ?? "", description: "Automatically generated definition for Green Power", extend: [m.genericGreenPower()], generated: true, }; const externalDefinitionSource = generateGreenPowerSource(definition, device.ieeeAddr); return { externalDefinitionSource, definition }; } function stringifyEps(endpoints) { return endpoints.map((e) => e.ID.toString()); } // This function checks if provided array of endpoints contain // only first device endpoint, which is passed in as `firstEndpoint`. function onlyFirstDeviceEnpoint(device, endpoints) { return endpoints.length === 1 && endpoints[0].ID === device.endpoints[0].ID; } // maybeEndpoints returns either `toExtend` if only first device endpoint is provided // as `endpoints`, or `endpointNames` with `toExtend`. // This allows to drop unnecessary `endpointNames` argument if it is not needed. function maybeEndpointArgs(device, endpoints, toExtend) { if (onlyFirstDeviceEnpoint(device, endpoints)) { return toExtend; } return { endpointNames: stringifyEps(endpoints), ...toExtend }; } async function extenderLock(device, endpoints) { // TODO: Support multiple endpoints if (endpoints.length > 1) { logger_1.logger.warning("extenderLock can accept only one endpoint", NS); } const endpoint = endpoints[0]; const pinCodeCount = await (0, utils_1.getClusterAttributeValue)(endpoint, "closuresDoorLock", "numOfPinUsersSupported", 50); return [new ExtendGenerator({ extend: m.lock, args: { pinCodeCount }, source: "lock" })]; } async function extenderOnOffLight(device, endpoints) { const generated = []; const lightEndpoints = endpoints.filter((e) => e.supportsInputCluster("lightingColorCtrl") || e.supportsInputCluster("genLevelCtrl")); const onOffEndpoints = endpoints.filter((e) => lightEndpoints.findIndex((ep) => e.ID === ep.ID) === -1); if (onOffEndpoints.length !== 0) { let endpointNames = undefined; if (!onlyFirstDeviceEnpoint(device, endpoints)) { endpointNames = endpoints.map((e) => e.ID.toString()); } generated.push(new ExtendGenerator({ extend: m.onOff, args: { powerOnBehavior: false, endpointNames }, source: "onOff" })); } for (const endpoint of lightEndpoints) { // In case read fails, support all features with 31 let colorCapabilities = 0; if (endpoint.supportsInputCluster("lightingColorCtrl")) { colorCapabilities = await (0, utils_1.getClusterAttributeValue)(endpoint, "lightingColorCtrl", "colorCapabilities", 31); } const supportsHueSaturation = (colorCapabilities & (1 << 0)) > 0; const supportsEnhancedHueSaturation = (colorCapabilities & (1 << 1)) > 0; const supportsColorXY = (colorCapabilities & (1 << 3)) > 0; const supportsColorTemperature = (colorCapabilities & (1 << 4)) > 0; const args = {}; if (supportsColorTemperature) { const minColorTemp = await (0, utils_1.getClusterAttributeValue)(endpoint, "lightingColorCtrl", "colorTempPhysicalMin", 150); const maxColorTemp = await (0, utils_1.getClusterAttributeValue)(endpoint, "lightingColorCtrl", "colorTempPhysicalMax", 500); args.colorTemp = { range: [minColorTemp, maxColorTemp] }; } if (supportsColorXY) { args.color = true; if (supportsHueSaturation || supportsEnhancedHueSaturation) { args.color = {}; if (supportsHueSaturation) args.color.modes = ["xy", "hs"]; if (supportsEnhancedHueSaturation) args.color.enhancedHue = true; } } if (endpoint.getDevice().manufacturerID === zigbee_herdsman_1.Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V) { generated.push(new ExtendGenerator({ extend: philips.m.light, args, source: "m.light", lib: "philips" })); } else { generated.push(new ExtendGenerator({ extend: m.light, args, source: "light" })); } } return generated; } function extenderElectricityMeter(device, endpoints) { // TODO: Support multiple endpoints if (endpoints.length > 1) { logger_1.logger.warning("extenderElectricityMeter can accept only one endpoint", NS); } const endpoint = endpoints[0]; const metering = endpoint.supportsInputCluster("seMetering"); const electricalMeasurements = endpoint.supportsInputCluster("haElectricalMeasurement"); const args = {}; if (!metering || !electricalMeasurements) { args.cluster = metering ? "metering" : "electrical"; } return [new ExtendGenerator({ extend: m.electricityMeter, args, source: "electricityMeter" })]; } async function extenderBinaryInput(device, endpoints) { const generated = []; for (const endpoint of endpoints) { const description = `binary_input_${endpoint.ID}`; const args = { name: await (0, utils_1.getClusterAttributeValue)(endpoint, "genBinaryInput", "description", description), cluster: "genBinaryInput", attribute: "presentValue", reporting: { attribute: "presentValue", min: "MIN", max: "MAX", change: 1 }, valueOn: ["ON", 1], valueOff: ["OFF", 0], description: description, access: "STATE_GET", endpointName: `${endpoint.ID}`, }; generated.push(new ExtendGenerator({ extend: m.binary, args, source: "binary" })); } return generated; } async function extenderBinaryOutput(device, endpoints) { const generated = []; for (const endpoint of endpoints) { const description = `binary_output_${endpoint.ID}`; const args = { name: await (0, utils_1.getClusterAttributeValue)(endpoint, "genBinaryOutput", "description", description), cluster: "genBinaryOutput", attribute: "presentValue", reporting: { attribute: "presentValue", min: "MIN", max: "MAX", change: 1 }, valueOn: ["ON", 1], valueOff: ["OFF", 0], description: description, access: "ALL", endpointName: `${endpoint.ID}`, }; generated.push(new ExtendGenerator({ extend: m.binary, args, source: "binary" })); } return generated; } //# sourceMappingURL=generateDefinition.js.map