UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

502 lines 22.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.develcoModernExtend = void 0; const zigbee_herdsman_1 = require("zigbee-herdsman"); const exposes_1 = require("./exposes"); const modernExtend_1 = require("./modernExtend"); const utils_1 = require("./utils"); const manufacturerOptions = { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO }; exports.develcoModernExtend = { addCustomClusterManuSpecificDevelcoGenBasic: () => (0, modernExtend_1.deviceAddCustomCluster)("genBasic", { name: "genBasic", ID: zigbee_herdsman_1.Zcl.Clusters.genBasic.ID, attributes: { develcoPrimarySwVersion: { name: "develcoPrimarySwVersion", ID: 0x8000, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO, write: true, }, develcoPrimaryHwVersion: { name: "develcoPrimaryHwVersion", ID: 0x8020, type: zigbee_herdsman_1.Zcl.DataType.OCTET_STR, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO, write: true, }, develcoLedControl: { name: "develcoLedControl", ID: 0x8100, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO, write: true, }, develcoTxPower: { name: "develcoTxPower", ID: 0x8101, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO, write: true, max: 0xff, }, }, commands: {}, commandsResponse: {}, }), addCustomClusterManuSpecificDevelcoIasZone: () => (0, modernExtend_1.deviceAddCustomCluster)("ssIasZone", { name: "ssIasZone", ID: zigbee_herdsman_1.Zcl.Clusters.ssIasZone.ID, attributes: { develcoZoneStatusInterval: { name: "develcoZoneStatusInterval", ID: 0x8000, type: zigbee_herdsman_1.Zcl.DataType.UINT16, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO, write: true, max: 0xffff, }, develcoAlarmOffDelay: { name: "develcoAlarmOffDelay", ID: 0x8001, type: zigbee_herdsman_1.Zcl.DataType.UINT16, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO, write: true, max: 0xffff, }, }, commands: {}, commandsResponse: {}, }), addCustomClusterManuSpecificDevelcoAirQuality: () => (0, modernExtend_1.deviceAddCustomCluster)("manuSpecificDevelcoAirQuality", { name: "manuSpecificDevelcoAirQuality", ID: 0xfc03, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO, attributes: { measuredValue: { name: "measuredValue", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff }, minMeasuredValue: { name: "minMeasuredValue", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff }, maxMeasuredValue: { name: "maxMeasuredValue", ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff }, resolution: { name: "resolution", ID: 0x0003, type: zigbee_herdsman_1.Zcl.DataType.UINT16, write: true, max: 0xffff }, }, commands: {}, commandsResponse: {}, }), addCustomDevelcoSeMeteringCluster: () => (0, modernExtend_1.deviceAddCustomCluster)("seMetering", { name: "seMetering", ID: zigbee_herdsman_1.Zcl.Clusters.seMetering.ID, attributes: { develcoPulseConfiguration: { name: "develcoPulseConfiguration", ID: 0x0300, type: zigbee_herdsman_1.Zcl.DataType.UINT16, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO, write: true, max: 0xffff, }, develcoCurrentSummation: { name: "develcoCurrentSummation", ID: 0x0301, type: zigbee_herdsman_1.Zcl.DataType.UINT48, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO, write: true, max: 0xffffffffffff, }, develcoInterfaceMode: { name: "develcoInterfaceMode", ID: 0x0302, type: zigbee_herdsman_1.Zcl.DataType.ENUM16, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.DEVELCO, write: true, max: 0xffff, }, }, commands: {}, commandsResponse: {}, }), readGenBasicPrimaryVersions: () => { /* * Develco (and there B2C brand Frient) do not use swBuildId * The versions are stored in develcoPrimarySwVersion and develcoPrimaryHwVersion, we read them during configure. */ const configure = [ async (device, coordinatorEndpoint, definition) => { for (const ep of device.endpoints) { if (ep.supportsInputCluster("genBasic")) { try { const data = await ep.read("genBasic", ["develcoPrimarySwVersion", "develcoPrimaryHwVersion"], manufacturerOptions); if (data.develcoPrimarySwVersion !== undefined) { device.softwareBuildID = data.develcoPrimarySwVersion.join("."); } if (data.develcoPrimaryHwVersion !== undefined) { device.hardwareVersion = Number.parseInt(data.develcoPrimaryHwVersion.join(""), 10); } device.save(); } catch { /* catch timeouts of sleeping devices */ } break; } } }, ]; return { configure, isModernExtend: true }; }, voc: (args) => (0, modernExtend_1.numeric)({ name: "voc", cluster: "manuSpecificDevelcoAirQuality", attribute: "measuredValue", reporting: { min: "1_MINUTE", max: "1_HOUR", change: 10 }, description: "Measured VOC value", // from Sensirion_Gas_Sensors_SGP3x_TVOC_Concept.pdf // "The mean molar mass of this mixture is 110 g/mol and hence, // 1 ppb TVOC corresponds to 4.5 μg/m3." scale: (value, type) => { if (type === "from") { return value * 4.5; } return value; }, unit: "µg/m³", access: "STATE_GET", ...args, }), airQuality: () => { // NOTE: do not setup reporting, this is handled by the voc() modernExtend const clusterName = "manuSpecificDevelcoAirQuality"; const attributeName = "measuredValue"; const propertyName = "air_quality"; const access = exposes_1.access.STATE; const expose = exposes_1.presets .enum("air_quality", access, ["excellent", "good", "moderate", "poor", "unhealthy", "out_of_range", "unknown"]) .withDescription("Measured air quality"); const fromZigbee = [ { cluster: clusterName, type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { if (msg.data[attributeName] !== undefined) { const vocPpb = Number.parseFloat(msg.data[attributeName]); // from aqszb-110-technical-manual-air-quality-sensor-04-08-20.pdf page 6, section 2.2 voc // this contains a ppb to level mapping table. // biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress` let airQuality; if (vocPpb <= 65) { airQuality = "excellent"; } else if (vocPpb <= 220) { airQuality = "good"; } else if (vocPpb <= 660) { airQuality = "moderate"; } else if (vocPpb <= 2200) { airQuality = "poor"; } else if (vocPpb <= 5500) { airQuality = "unhealthy"; } else if (vocPpb > 5500) { airQuality = "out_of_range"; } else { airQuality = "unknown"; } return { [propertyName]: airQuality }; } }, }, ]; return { exposes: [expose], fromZigbee, isModernExtend: true }; }, batteryLowAA: () => { /* * Per the technical documentation for AQSZB-110: * To detect low battery the system can monitor the "BatteryVoltage" by setting up a reporting interval of every 12 hour. * When a voltage of 2.5V is measured the battery should be replaced. * Low batt LED indication–RED LED will blink twice every 60 second. * * Similar notes found in other 2x AA powered Develco devices like HMSZB-110 and MOSZB-140 */ const clusterName = "genPowerCfg"; const attributeName = "batteryVoltage"; const propertyName = "battery_low"; const expose = exposes_1.presets.battery_low(); const fromZigbee = [ { cluster: clusterName, type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { if (msg.data[attributeName] !== undefined && msg.data[attributeName] < 255) { const voltage = msg.data[attributeName]; return { [propertyName]: voltage <= 25 }; } }, }, ]; return { exposes: [expose], fromZigbee, isModernExtend: true }; }, temperature: (args) => (0, modernExtend_1.temperature)({ ...args, }), deviceTemperature: (args) => (0, modernExtend_1.deviceTemperature)({ reporting: { min: "5_MINUTES", max: "1_HOUR", change: 2 }, // Device temperature reports with 2 degree change ...args, }), currentSummation: (args) => (0, modernExtend_1.numeric)({ name: "current_summation", cluster: "seMetering", attribute: "develcoCurrentSummation", description: "Current summation value sent to the display. e.g. 570 = 0,570 kWh", access: "SET", valueMin: 0, valueMax: 268435455, ...args, }), pulseConfiguration: (args) => (0, modernExtend_1.numeric)({ name: "pulse_configuration", cluster: "seMetering", attribute: "develcoPulseConfiguration", description: "Pulses per kwh. Default 1000 imp/kWh. Range 0 to 65535", access: "ALL", valueMin: 0, valueMax: 65535, ...args, }), ledControl: () => { const expose = exposes_1.presets .composite("led_control", "led_control", exposes_1.access.ALL) .withFeature(exposes_1.presets .binary("indicate_faults", exposes_1.access.ALL, true, false) .withDescription("Enable/disable LED indication for faults (e.g., lost connection to gateway)")) .withFeature(exposes_1.presets.binary("indicate_mains_power", exposes_1.access.ALL, true, false).withDescription("Enable/disable green LED indication for mains power status")); const fromZigbee = [ { cluster: "genBasic", type: ["attributeReport", "readResponse"], convert: (_model, msg, _publish, _options, _meta) => { if (Object.hasOwn(msg.data, "develcoLedControl")) { const ledControl = msg.data.develcoLedControl; return { led_control: { indicate_faults: (ledControl & 1) > 0, indicate_mains_power: (ledControl & 2) > 0, }, }; } }, }, ]; const toZigbee = [ { key: ["led_control"], convertSet: async (entity, _key, value, meta) => { // biome-ignore lint/style/useNamingConvention: zigbee2mqtt uses snake_case for exposed attributes const currentState = meta.state.led_control || { indicate_faults: false, indicate_mains_power: false, }; // biome-ignore lint/style/useNamingConvention: zigbee2mqtt uses snake_case for exposed attributes const newState = { ...currentState, ...value }; let bitmap = 0; if (newState.indicate_faults) bitmap |= 1; if (newState.indicate_mains_power) bitmap |= 2; await entity.write("genBasic", { develcoLedControl: bitmap }, manufacturerOptions); return { state: { led_control: newState } }; }, convertGet: async (entity, _key, _meta) => { await entity.read("genBasic", ["develcoLedControl"], manufacturerOptions); }, }, ]; const configure = [ async (device, _coordinatorEndpoint, _definition) => { for (const ep of device.endpoints) { if (ep.supportsInputCluster("genBasic")) { try { await ep.read("genBasic", ["develcoLedControl"], manufacturerOptions); } catch { /* catch timeouts of sleeping devices */ } break; } } }, ]; return { exposes: [expose], fromZigbee, toZigbee, configure, isModernExtend: true }; }, txPower: () => { const expose = exposes_1.presets .enum("tx_power", exposes_1.access.ALL, ["CE", "FCC"]) .withDescription("TX power mode for regulatory compliance (CE or FCC). Requires device rejoin to apply."); const fromZigbee = [ { cluster: "genBasic", type: ["attributeReport", "readResponse"], convert: (_model, msg, _publish, _options, _meta) => { if (Object.hasOwn(msg.data, "develcoTxPower")) { return { tx_power: msg.data.develcoTxPower === 0 ? "CE" : "FCC" }; } }, }, ]; const toZigbee = [ { key: ["tx_power"], convertSet: async (entity, _key, value, _meta) => { const numericValue = value === "CE" ? 0 : 1; await entity.write("genBasic", { develcoTxPower: numericValue }, manufacturerOptions); return { state: { tx_power: value } }; }, convertGet: async (entity, _key, _meta) => { await entity.read("genBasic", ["develcoTxPower"], manufacturerOptions); }, }, ]; const configure = [ async (device, _coordinatorEndpoint, _definition) => { for (const ep of device.endpoints) { if (ep.supportsInputCluster("genBasic")) { try { await ep.read("genBasic", ["develcoTxPower"], manufacturerOptions); } catch { /* catch timeouts of sleeping devices */ } break; } } }, ]; return { exposes: [expose], fromZigbee, toZigbee, configure, isModernExtend: true }; }, zoneStatusInterval: () => { const expose = exposes_1.presets .numeric("zone_status_interval", exposes_1.access.ALL) .withUnit("s") .withValueMin(0) .withValueMax(65535) .withDescription("Heartbeat interval in seconds. Controls the periodic interval between ZoneStatusChange commands (default 300s)"); const fromZigbee = [ { cluster: "ssIasZone", type: ["attributeReport", "readResponse"], convert: (_model, msg, _publish, _options, _meta) => { if (Object.hasOwn(msg.data, "develcoZoneStatusInterval")) { return { zone_status_interval: msg.data.develcoZoneStatusInterval }; } }, }, ]; const toZigbee = [ { key: ["zone_status_interval"], convertSet: async (entity, _key, value, _meta) => { await entity.write("ssIasZone", { develcoZoneStatusInterval: value }, manufacturerOptions); return { state: { zone_status_interval: value } }; }, convertGet: async (entity, _key, _meta) => { await entity.read("ssIasZone", ["develcoZoneStatusInterval"], manufacturerOptions); }, }, ]; const configure = [ async (device, _coordinatorEndpoint, _definition) => { for (const ep of device.endpoints) { if (ep.supportsInputCluster("ssIasZone")) { try { await ep.read("ssIasZone", ["develcoZoneStatusInterval"], manufacturerOptions); } catch { /* catch timeouts of sleeping devices */ } break; } } }, ]; return { exposes: [expose], fromZigbee, toZigbee, configure, isModernExtend: true }; }, acConnected: () => { const expose = exposes_1.presets .binary("ac_connected", exposes_1.access.STATE, true, false) .withDescription("Indicates whether the device is connected to AC mains power") .withCategory("diagnostic"); const fromZigbee = [ { cluster: "ssIasZone", type: ["commandStatusChangeNotification", "attributeReport", "readResponse"], convert: (_model, msg, _publish, _options, _meta) => { const zoneStatus = "zonestatus" in msg.data ? msg.data.zonestatus : msg.data.zoneStatus; if (zoneStatus !== undefined) { // Bit 7 = 1 means "mains power lost", so ac_connected = false // Bit 7 = 0 means "mains power ok", so ac_connected = true return { ac_connected: (zoneStatus & (1 << 7)) === 0 }; } }, }, ]; const configure = [ async (device, _coordinatorEndpoint, _definition) => { for (const ep of device.endpoints) { if (ep.supportsInputCluster("ssIasZone")) { try { await ep.read("ssIasZone", ["zoneStatus"]); } catch { /* catch timeouts of sleeping devices */ } break; } } }, ]; return { exposes: [expose], fromZigbee, configure, isModernExtend: true }; }, customPulseTrigger: (options) => { const endpointNames = options?.endpointNames || []; const durationExpose = exposes_1.presets .numeric("duration", exposes_1.access.STATE_SET) .withLabel("Pulse duration") .withUnit("s") .withValueMin(0.0) .withValueMax(3600) .withDescription("Duration of the pulse."); const triggerExpose = exposes_1.presets .enum("trigger", exposes_1.access.SET, ["press"]) .withDescription("Trigger a timed pulse. The length of the pulse is defined by 'Pulse duration'. If the 'Pulse duration' is undefined a default value of 1s will be used."); const exposes = [...(0, utils_1.exposeEndpoints)(durationExpose, endpointNames), ...(0, utils_1.exposeEndpoints)(triggerExpose, endpointNames)]; const toZigbee = [ { key: ["trigger", "duration"], convertSet: async (entity, key, value, meta) => { if (key === "duration") { const val = value === null ? 1.0 : Number(value); return { state: { [key]: val } }; } if (key === "trigger" && value === "press") { const epSuffix = meta.endpoint_name ? `_${meta.endpoint_name}` : ""; const stateLookupKey = `duration${epSuffix}`; const seconds = meta.state[stateLookupKey] !== undefined ? Number(meta.state[stateLookupKey]) : 1.0; const deciseconds = Math.round(seconds * 10); await entity.command("genOnOff", "onWithTimedOff", { ctrlbits: 0, ontime: deciseconds, offwaittime: 0, }, meta.options); return { state: { [key]: null } }; } }, }, ]; return { isModernExtend: true, exposes, toZigbee, }; }, }; //# sourceMappingURL=develco.js.map