UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

462 lines • 20 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.fzLegrand = exports.tzLegrand = exports.readInitialBatteryState = exports.eLegrand = exports.legrandExtend = exports.legrandOptions = void 0; const zigbee_herdsman_1 = require("zigbee-herdsman"); const m = __importStar(require("../lib/modernExtend")); const utils = __importStar(require("../lib/utils")); const exposes = __importStar(require("./exposes")); const logger_1 = require("./logger"); const NS = "zhc:legrand"; const e = exposes.presets; const ea = exposes.access; const shutterCalibrationModes = { 0: { description: "classic_nllv", onlyNLLV: true, supportsTilt: false }, 1: { description: "specific_nllv", onlyNLLV: true, supportsTilt: false }, 2: { description: "up_down_stop", onlyNLLV: false, supportsTilt: false }, 3: { description: "temporal", onlyNLLV: false, supportsTilt: false }, 4: { description: "venetian_bso", onlyNLLV: false, supportsTilt: true }, }; const ledModes = { 1: "led_in_dark", 2: "led_if_on", }; const ledEffects = { 0: "blink 3", 1: "fixed", 2: "blink green", 3: "blink blue", }; const ledColors = { 0: "default", 1: "red", 2: "green", 3: "blue", 4: "lightblue", 5: "yellow", 6: "pink", 7: "white", }; const optsLegrand = { identityEffect: () => { return e .composite("Identity effect", "identity_effect", ea.SET) .withDescription("Defines the identification effect to simplify the device identification.") .withFeature(e.enum("effect", ea.SET, Object.values(ledEffects)).withLabel("Effect")) .withFeature(e.enum("color", ea.SET, Object.values(ledColors)).withLabel("Color")); }, }; const getApplicableCalibrationModes = (isNLLVSwitch) => { return Object.fromEntries(Object.entries(shutterCalibrationModes) .filter((e) => (isNLLVSwitch ? true : e[1].onlyNLLV === false)) .map((e) => [e[0], e[1].description])); }; exports.legrandOptions = { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.LEGRAND_GROUP, disableDefaultResponse: true }; exports.legrandExtend = { addLegrandDevicesCluster: () => m.deviceAddCustomCluster("manuSpecificLegrandDevices", { name: "manuSpecificLegrandDevices", ID: 0xfc01, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.LEGRAND_GROUP, attributes: { deviceMode: { name: "deviceMode", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.DATA16, write: true }, ledInDark: { name: "ledInDark", ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true }, ledIfOn: { name: "ledIfOn", ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN, write: true }, }, commands: {}, commandsResponse: {}, }), addLegrandDevices2Cluster: () => m.deviceAddCustomCluster("manuSpecificLegrandDevices2", { name: "manuSpecificLegrandDevices2", ID: 0xfc40, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.LEGRAND_GROUP, attributes: { pilotWireMode: { name: "pilotWireMode", ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 }, }, commands: { command0: { name: "command0", ID: 0x00, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] }, }, commandsResponse: {}, }), addLegrandDevices3Cluster: () => m.deviceAddCustomCluster("manuSpecificLegrandDevices3", { name: "manuSpecificLegrandDevices3", ID: 0xfc41, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.LEGRAND_GROUP, attributes: {}, commands: { command0: { name: "command0", ID: 0x00, parameters: [{ name: "data", type: zigbee_herdsman_1.Zcl.BuffaloZclDataType.BUFFER }] }, }, commandsResponse: {}, }), addLegrandClosuresWindowCovering: () => m.deviceAddCustomCluster("closuresWindowCovering", { name: "closuresWindowCovering", ID: zigbee_herdsman_1.Zcl.Clusters.closuresWindowCovering.ID, attributes: { stepPositionLift: { name: "stepPositionLift", ID: 0xf001, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.LEGRAND_GROUP, write: true, max: 0xff, }, calibrationMode: { name: "calibrationMode", ID: 0xf002, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.LEGRAND_GROUP, write: true, max: 0xff, }, targetPositionTiltPercentage: { name: "targetPositionTiltPercentage", ID: 0xf003, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.LEGRAND_GROUP, write: true, max: 0xff, }, stepPositionTilt: { name: "stepPositionTilt", ID: 0xf004, type: zigbee_herdsman_1.Zcl.DataType.ENUM8, manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.LEGRAND_GROUP, write: true, max: 0xff, }, }, commands: {}, commandsResponse: {}, }), }; exports.eLegrand = { identify: () => { return e .enum("identify", ea.SET, ["identify"]) .withDescription("Blinks the built-in LED to make it easier to identify the device") .withCategory("config"); }, ledInDark: () => { return e .binary("led_in_dark", ea.ALL, "ON", "OFF") .withDescription("Enables the built-in LED allowing to see the switch in the dark") .withCategory("config"); }, ledIfOn: () => { return e.binary("led_if_on", ea.ALL, "ON", "OFF").withDescription("Enables the LED on activity").withCategory("config"); }, getCover: (device) => { const c = e.cover_position(); const calMode = !utils.isDummyDevice(device) ? Number(device.getEndpoint(1)?.clusters?.closuresWindowCovering?.attributes?.calibrationMode) : 0; const showTilt = calMode ? utils.getFromLookup(calMode, shutterCalibrationModes)?.supportsTilt === true : false; if (showTilt) { c.addFeature(new exposes.Numeric("tilt", ea.ALL) .withValueMin(0) .withValueMax(100) .withValueStep(25) .withPreset("Closed", 0, "Vertical") .withPreset("25 %", 25, "25%") .withPreset("50 %", 50, "50%") .withPreset("75 %", 75, "75%") .withPreset("Open", 100, "Horizontal") .withUnit("%") .withDescription("Tilt percentage of that cover")); } return c; }, getCalibrationModes: (isNLLVSwitch) => { const modes = getApplicableCalibrationModes(isNLLVSwitch); return e .enum("calibration_mode", ea.ALL, Object.values(modes)) .withDescription("Defines the calibration mode of the switch. (Caution: Changing modes requires a recalibration of the shutter switch!)") .withCategory("config"); }, }; const readInitialBatteryState = async (event) => { if (event.type === "deviceAnnounce") { const endpoint = event.data.device.getEndpoint(1); await endpoint.read("genPowerCfg", ["batteryVoltage"], exports.legrandOptions); } }; exports.readInitialBatteryState = readInitialBatteryState; exports.tzLegrand = { auto_mode: { key: ["auto_mode"], convertSet: async (entity, key, value, meta) => { const mode = utils.getFromLookup(value, { off: 0x00, auto: 0x02, on_override: 0x03 }); const payload = { data: Buffer.from([mode]) }; await entity.command("manuSpecificLegrandDevices3", "command0", payload); return { state: { auto_mode: value } }; }, }, calibration_mode: (isNLLVSwitch) => { return { key: ["calibration_mode"], convertSet: async (entity, key, value, meta) => { const applicableModes = getApplicableCalibrationModes(isNLLVSwitch); utils.validateValue(value, Object.values(applicableModes)); const idx = Number(utils.getKey(applicableModes, value)); await entity.write("closuresWindowCovering", { calibrationMode: idx }, exports.legrandOptions); }, convertGet: async (entity, key, meta) => { await entity.read("closuresWindowCovering", ["calibrationMode"], exports.legrandOptions); }, }; }, // biome-ignore lint/style/useNamingConvention: model-specific converter K4003C_state: { key: ["state"], convertSet: async (entity, key, value, meta) => { const cmd = String(value).toUpperCase(); const command = cmd === "TOGGLE" ? "toggle" : cmd === "ON" ? "on" : "off"; await entity.command("genOnOff", command, {}, {}); if (cmd === "TOGGLE") return {}; return { state: { state: cmd } }; }, convertGet: async (entity, key, meta) => { await entity.read("genBinaryInput", ["presentValue"]); }, }, led_mode: { key: ["led_in_dark", "led_if_on"], convertSet: async (entity, key, value, meta) => { utils.validateValue(key, Object.values(ledModes)); const idx = utils.getKey(ledModes, key); const state = value === "ON" || (value === "OFF" ? false : !!value); const payload = { [idx]: { value: state, type: 16 } }; await entity.write("manuSpecificLegrandDevices", payload, exports.legrandOptions); return { state: { [key]: value } }; }, convertGet: async (entity, key, meta) => { utils.validateValue(key, Object.values(ledModes)); const idx = utils.getKey(ledModes, key); await entity.read("manuSpecificLegrandDevices", [Number(idx)], exports.legrandOptions); }, }, identify: { key: ["identify"], options: [optsLegrand.identityEffect()], convertSet: async (entity, key, value, meta) => { const identityEffect = meta.options.identity_effect; const selEffect = identityEffect?.effect ?? ledEffects[0]; const selColor = identityEffect?.color ?? ledColors[0]; const effectID = Number.parseInt(utils.getFromLookupByValue(selEffect, ledEffects, "0"), 10); const effectVariant = Number.parseInt(utils.getFromLookupByValue(selColor, ledColors, "0"), 10); // Trigger an effect await entity.command("genIdentify", "triggerEffect", { effectid: effectID, effectvariant: effectVariant }, {}); // Trigger the identification await entity.command("genIdentify", "identify", { identifytime: 10 }, {}); }, }, legrand_device_mode: { key: ["device_mode"], convertSet: async (entity, key, value, meta) => { utils.assertString(value, key); // enable the dimmer, requires a recent firmware on the device const lookup = { dimmer_on: 0x0101, dimmer_off: 0x0100, switch: 0x0003, auto: 0x0004, pilot_on: 0x0002, pilot_off: 0x0001, }; const val = value.toLowerCase(); utils.validateValue(val, Object.keys(lookup)); const payload = { deviceMode: utils.getFromLookup(val, lookup) }; await entity.write("manuSpecificLegrandDevices", payload, exports.legrandOptions); return { state: { device_mode: val } }; }, convertGet: async (entity, key, meta) => { await entity.read("manuSpecificLegrandDevices", [0x0000, 0x0001, 0x0002], exports.legrandOptions); }, }, legrand_pilot_wire_mode: { key: ["pilot_wire_mode"], convertSet: async (entity, key, value, meta) => { const modeLookup = { comfort: 0x00, "comfort_-1": 0x01, "comfort_-2": 0x02, eco: 0x03, frost_protection: 0x04, off: 0x05, }; utils.validateValue(value, Object.keys(modeLookup)); const payload = { data: Buffer.from([utils.getFromLookup(value, modeLookup)]) }; await entity.command("manuSpecificLegrandDevices2", "command0", payload); return { state: { pilot_wire_mode: value } }; }, convertGet: async (entity, key, meta) => { await entity.read("manuSpecificLegrandDevices2", [0x0000], exports.legrandOptions); }, }, }; exports.fzLegrand = { calibration_mode: (isNLLVSwitch) => { return { cluster: "closuresWindowCovering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const attr = "calibrationMode"; if (msg.data[attr] !== undefined) { const applicableModes = getApplicableCalibrationModes(isNLLVSwitch); const idx = msg.data[attr]; utils.validateValue(String(idx), Object.keys(applicableModes)); const calMode = applicableModes[idx]; return { calibration_mode: calMode }; } }, }; }, cluster_fc01: { cluster: "manuSpecificLegrandDevices", type: ["readResponse"], convert: (model, msg, publish, options, meta) => { const payload = {}; if (msg.data.deviceMode !== undefined) { const modeLookup = { 1: "pilot_off", 2: "pilot_on", 3: "switch", 4: "auto", 256: "dimmer_off", 257: "dimmer_on", }; const mode = msg.data.deviceMode; if (modeLookup[mode] !== undefined) { payload.device_mode = modeLookup[mode]; } else { logger_1.logger.warning(`Device_mode ${mode} not recognized, please fix me!`, NS); payload.device_mode = "unknown"; } } if (msg.data.ledInDark !== undefined) { payload.led_in_dark = msg.data.ledInDark ? "ON" : "OFF"; } if (msg.data.ledIfOn !== undefined) { payload.led_if_on = msg.data.ledIfOn ? "ON" : "OFF"; } return payload; }, }, stop_poll_on_checkin: { cluster: "genPollCtrl", type: ["commandCheckin"], convert: (model, msg, publish, options, meta) => { // TODO current solution is a work around, it would be cleaner to answer to the request const endpoint = msg.device.getEndpoint(1); endpoint .command("genPollCtrl", "fastPollStop", {}, exports.legrandOptions) .catch((error) => logger_1.logger.debug(`Failed to stop poll on '${msg.device.ieeeAddr}' (${error})`, NS)); }, }, command_cover: { cluster: "closuresWindowCovering", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { const payload = {}; if (msg.data.tuyaMovingState !== undefined) { if (utils.hasAlreadyProcessedMessage(msg, model)) return; if (msg.data.tuyaMovingState === 0) { // return { // action: 'open', // }; payload.action = utils.postfixWithEndpointName("OPEN", msg, model, meta); utils.addActionGroup(payload, msg, model); } if (msg.data.tuyaMovingState === 100) { // return { // action: 'closed', // }; payload.action = utils.postfixWithEndpointName("CLOSE", msg, model, meta); utils.addActionGroup(payload, msg, model); } if (msg.data.tuyaMovingState >= 1 && msg.data.tuyaMovingState < 100) { // return { // action: 'stop', // }; payload.action = utils.postfixWithEndpointName("STOP", msg, model, meta); utils.addActionGroup(payload, msg, model); } } return payload; }, }, identify: { cluster: "genIdentify", type: ["attributeReport", "readResponse"], convert: (model, msg, publish, options, meta) => { return {}; }, }, legrand_master_switch_center: { cluster: "manuSpecificLegrandDevices", type: "raw", convert: (model, msg, publish, options, meta) => { const centerSignature = [0x15, 0x21, 0x10, 0x00, 0x03, 0xff]; if (msg.data?.length === 6 && centerSignature.every((b, i) => msg.data[i] === b)) { return { action: utils.postfixWithEndpointName("center", msg, model, meta) }; } }, }, legrand_pilot_wire_mode: { cluster: "manuSpecificLegrandDevices2", type: ["readResponse"], convert: (model, msg, publish, options, meta) => { if (msg.data.pilotWireMode !== undefined) { const lookup = { 0: "comfort", 1: "comfort_-1", 2: "comfort_-2", 3: "eco", 4: "frost_protection", 5: "off", }; const mode = msg.data.pilotWireMode; if (lookup[mode] !== undefined) { return { pilot_wire_mode: lookup[mode] }; } logger_1.logger.warning(`Pilot_wire_mode ${mode} not recognized, please fix me!`, NS); return { pilot_wire_mode: "unknown" }; } }, }, }; //# sourceMappingURL=legrand.js.map