zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
462 lines • 20 kB
JavaScript
"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