zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
1,090 lines (1,089 loc) • 93.8 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.definitions = void 0;
const zigbee_herdsman_1 = require("zigbee-herdsman");
const fz = __importStar(require("../converters/fromZigbee"));
const tz = __importStar(require("../converters/toZigbee"));
const constants = __importStar(require("../lib/constants"));
const exposes = __importStar(require("../lib/exposes"));
const logger_1 = require("../lib/logger");
const m = __importStar(require("../lib/modernExtend"));
const reporting = __importStar(require("../lib/reporting"));
const globalStore = __importStar(require("../lib/store"));
const utils = __importStar(require("../lib/utils"));
const e = exposes.presets;
const ea = exposes.access;
const NS = "zhc:bosch";
const manufacturerOptions = { manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH };
const sirenVolume = {
low: 0x01,
medium: 0x02,
high: 0x03,
};
const sirenLight = {
only_light: 0x00,
only_siren: 0x01,
siren_and_light: 0x02,
};
const outdoorSirenState = {
ON: 0x07,
OFF: 0x00,
};
const sirenPowerSupply = {
solar_panel: 0x01,
ac_power_supply: 0x02,
dc_power_supply: 0x03,
};
// Universal Switch II
const buttonMap = {
config_led_top_left_press: 0x10,
config_led_top_right_press: 0x11,
config_led_bottom_left_press: 0x12,
config_led_bottom_right_press: 0x13,
config_led_top_left_longpress: 0x20,
config_led_top_right_longpress: 0x21,
config_led_bottom_left_longpress: 0x22,
config_led_bottom_right_longpress: 0x23,
};
// Universal Switch II
const labelShortPress = `Specifies LED color (rgb) and pattern on short press as hex string.
0-2: RGB value (e.g. ffffff = white)
3: Light position (01=top, 02=bottom, 00=full)
4-7: Durations for sequence fade-in -> on -> fade-out -> off (e.g. 01020102)
8: Number of Repetitions (01=1 to ff=255)
Example: ff1493000104010001`;
// Universal Switch II
const labelLongPress = `Specifies LED color (rgb) and pattern on long press as hex string.
0-2: RGB value (e.g. ffffff = white)
3: Light position (01=top, 02=bottom, 00=full)
4-7: Durations for sequence fade-in -> on -> fade-out -> off (e.g. 01020102)
8: Number of Repetitions (01=1 to ff=255)
Example: ff4200000502050001`;
// Universal Switch II
const labelConfirmation = `Specifies LED color (rgb) and pattern of the confirmation response as hex string.
0-2: RGB value (e.g. ffffff = white)
3: Light position (01=top, 02=bottom, 00=full)
4-7: Durations for sequence fade-in -> on -> fade-out -> off (e.g. 01020102)
8: Number of Repetitions (01=1 to ff=255)
Example: 30ff00000102010001`;
const boschExtend = {
hvacThermostatCluster: () => m.deviceAddCustomCluster("hvacThermostat", {
ID: zigbee_herdsman_1.Zcl.Clusters.hvacThermostat.ID,
attributes: {
operatingMode: {
ID: 0x4007,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
},
heatingDemand: {
ID: 0x4020,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
},
valveAdaptStatus: {
ID: 0x4022,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
},
remoteTemperature: {
ID: 0x4040,
type: zigbee_herdsman_1.Zcl.DataType.INT16,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
},
windowDetection: {
ID: 0x4042,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
},
boostHeating: {
ID: 0x4043,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
},
},
commands: {
calibrateValve: {
ID: 0x41,
parameters: [],
},
},
commandsResponse: {},
}),
hvacUserInterfaceCfgCluster: () => m.deviceAddCustomCluster("hvacUserInterfaceCfg", {
ID: zigbee_herdsman_1.Zcl.Clusters.hvacUserInterfaceCfg.ID,
attributes: {
displayOrientation: {
ID: 0x400b,
type: zigbee_herdsman_1.Zcl.DataType.UINT8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
},
displayedTemperature: {
ID: 0x4039,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
},
displayOntime: {
ID: 0x403a,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
},
displayBrightness: {
ID: 0x403b,
type: zigbee_herdsman_1.Zcl.DataType.ENUM8,
manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
},
},
commands: {},
commandsResponse: {},
}),
operatingMode: () => m.enumLookup({
name: "operating_mode",
cluster: "hvacThermostat",
attribute: "operatingMode",
reporting: { min: "10_SECONDS", max: "MAX", change: null },
description: "Bosch-specific operating mode (overrides system mode)",
lookup: { schedule: 0x00, manual: 0x01, pause: 0x05 },
zigbeeCommandOptions: manufacturerOptions,
}),
windowDetection: () => m.binary({
name: "window_detection",
cluster: "hvacThermostat",
attribute: "windowDetection",
description: "Enable/disable window open (Lo.) mode",
valueOn: ["ON", 0x01],
valueOff: ["OFF", 0x00],
zigbeeCommandOptions: manufacturerOptions,
}),
boostHeating: () => m.binary({
name: "boost_heating",
cluster: "hvacThermostat",
attribute: "boostHeating",
reporting: { min: "10_SECONDS", max: "MAX", change: null, attribute: "boostHeating" },
description: "Activate boost heating (5 min. on TRV)",
valueOn: ["ON", 0x01],
valueOff: ["OFF", 0x00],
zigbeeCommandOptions: manufacturerOptions,
}),
childLock: () => m.binary({
name: "child_lock",
cluster: "hvacUserInterfaceCfg",
attribute: "keypadLockout",
description: "Enables/disables physical input on the device",
valueOn: ["LOCK", 0x01],
valueOff: ["UNLOCK", 0x00],
}),
displayOntime: () => m.numeric({
name: "display_ontime",
cluster: "hvacUserInterfaceCfg",
attribute: "displayOntime",
description: "Sets the display on-time",
valueMin: 5,
valueMax: 30,
unit: "s",
zigbeeCommandOptions: manufacturerOptions,
}),
displayBrightness: () => m.numeric({
name: "display_brightness",
cluster: "hvacUserInterfaceCfg",
attribute: "displayBrightness",
description: "Sets brightness of the display",
valueMin: 0,
valueMax: 10,
zigbeeCommandOptions: manufacturerOptions,
}),
valveAdaptProcess: () => {
const adaptationStatus = {
none: 0x00,
ready_to_calibrate: 0x01,
calibration_in_progress: 0x02,
error: 0x03,
success: 0x04,
};
const exposes = [
e
.binary("valve_adapt_process", ea.ALL, true, false)
.withLabel("Trigger adaptation process")
.withDescription('Trigger the valve adaptation process. Only possible when adaptation status is "ready_to_calibrate" or "error".')
.withCategory("config"),
];
const fromZigbee = [
{
cluster: "hvacThermostat",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.valveAdaptStatus !== undefined) {
if (msg.data.valveAdaptStatus === adaptationStatus.calibration_in_progress) {
result.valve_adapt_process = true;
}
else {
result.valve_adapt_process = false;
}
}
return result;
},
},
];
const toZigbee = [
{
key: ["valve_adapt_process"],
convertSet: async (entity, key, value, meta) => {
if (value === true) {
const adaptStatus = utils.getFromLookup(meta.state.valve_adapt_status, adaptationStatus);
switch (adaptStatus) {
case adaptationStatus.ready_to_calibrate:
case adaptationStatus.error:
await entity.command("hvacThermostat", "calibrateValve", {}, manufacturerOptions);
break;
default:
throw new Error("Valve adaptation process not possible right now.");
}
}
return { state: { valve_adapt_process: value } };
},
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["valveAdaptStatus"], manufacturerOptions);
},
},
];
return {
exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
heatingDemand: () => {
const fromZigbee = [
{
cluster: "hvacThermostat",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.heatingDemand !== undefined) {
const demand = msg.data.heatingDemand;
result.pi_heating_demand = demand;
result.running_state = demand > 0 ? "heat" : "idle";
}
return result;
},
},
];
const toZigbee = [
{
key: ["pi_heating_demand"],
convertSet: async (entity, key, value, meta) => {
if (key === "pi_heating_demand") {
let demand = utils.toNumber(value, key);
demand = utils.numberWithinRange(demand, 0, 100);
await entity.write("hvacThermostat", { heatingDemand: demand }, manufacturerOptions);
return { state: { pi_heating_demand: demand } };
}
},
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["heatingDemand"], manufacturerOptions);
},
},
{
key: ["running_state"],
convertGet: async (entity, key, meta) => {
await entity.read("hvacThermostat", ["heatingDemand"], manufacturerOptions);
},
},
];
return {
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
ignoreDst: () => {
const fromZigbee = [
{
cluster: "genTime",
type: "read",
convert: async (model, msg, publish, options, meta) => {
if (msg.data.includes("dstStart", "dstEnd", "dstShift")) {
const response = {
dstStart: { attribute: 0x0003, status: zigbee_herdsman_1.Zcl.Status.SUCCESS, value: 0x00 },
dstEnd: { attribute: 0x0004, status: zigbee_herdsman_1.Zcl.Status.SUCCESS, value: 0x00 },
dstShift: { attribute: 0x0005, status: zigbee_herdsman_1.Zcl.Status.SUCCESS, value: 0x00 },
};
await msg.endpoint.readResponse(msg.cluster, msg.meta.zclTransactionSequenceNumber, response);
}
},
},
];
return {
fromZigbee,
isModernExtend: true,
};
},
doorWindowContact: (hasVibrationSensor) => {
const exposes = [
e.binary("contact", ea.STATE, false, true).withDescription("Indicates whether the device is opened or closed"),
e
.enum("action", ea.STATE, ["none", "single", "long"])
.withDescription("Triggered action (e.g. a button click)")
.withCategory("diagnostic"),
];
if (hasVibrationSensor) {
exposes.push(e.binary("vibration", ea.STATE, true, false).withDescription("Indicates whether the device detected vibration"));
}
const fromZigbee = [
{
cluster: "ssIasZone",
type: ["commandStatusChangeNotification", "attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.zoneStatus !== undefined || msg.data.zonestatus !== undefined) {
const zoneStatus = msg.type === "commandStatusChangeNotification" ? msg.data.zonestatus : msg.data.zoneStatus;
const lookup = { 0: "none", 1: "single", 2: "long" };
const result = {
contact: !((zoneStatus & 1) > 0),
vibration: (zoneStatus & (1 << 1)) > 0,
tamper: (zoneStatus & (1 << 2)) > 0,
battery_low: (zoneStatus & (1 << 3)) > 0,
supervision_reports: (zoneStatus & (1 << 4)) > 0,
restore_reports: (zoneStatus & (1 << 5)) > 0,
trouble: (zoneStatus & (1 << 6)) > 0,
ac_status: (zoneStatus & (1 << 7)) > 0,
test: (zoneStatus & (1 << 8)) > 0,
battery_defect: (zoneStatus & (1 << 9)) > 0,
action: lookup[(zoneStatus >> 11) & 3],
};
// biome-ignore lint/performance/noDelete: ignored using `--suppress`
if (result.action === "none")
delete result.action;
return result;
}
},
},
];
return {
exposes,
fromZigbee,
isModernExtend: true,
};
},
smokeAlarm: () => {
const smokeAlarm = {
OFF: 0x0000,
ON: 0x3c00, // 15360 or 46080 works
};
const burglarAlarm = {
OFF: 0x0001,
ON: 0xb401, // 46081
};
const exposes = [
e.binary("smoke", ea.STATE, true, false).withDescription("Indicates whether the device detected smoke"),
e
.binary("test", ea.STATE, true, false)
.withDescription("Indicates whether the device is currently performing a test")
.withCategory("diagnostic"),
e.binary("alarm_smoke", ea.ALL, true, false).withDescription("Toggle the smoke alarm siren").withCategory("config"),
e.binary("alarm_burglar", ea.ALL, true, false).withDescription("Toggle the burglar alarm siren").withCategory("config"),
];
const fromZigbee = [
{
cluster: "ssIasZone",
type: ["commandStatusChangeNotification", "attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
if (msg.data.zoneStatus !== undefined || msg.data.zonestatus !== undefined) {
const zoneStatus = msg.type === "commandStatusChangeNotification" ? msg.data.zonestatus : msg.data.zoneStatus;
return {
smoke: (zoneStatus & 1) > 0,
alarm_smoke: (zoneStatus & (1 << 1)) > 0,
battery_low: (zoneStatus & (1 << 3)) > 0,
supervision_reports: (zoneStatus & (1 << 4)) > 0,
restore_reports: (zoneStatus & (1 << 5)) > 0,
alarm_burglar: (zoneStatus & (1 << 7)) > 0,
test: (zoneStatus & (1 << 8)) > 0,
alarm_silenced: (zoneStatus & (1 << 11)) > 0,
};
}
},
},
];
const toZigbee = [
{
key: ["alarm_smoke", "alarm_burglar"],
convertSet: async (entity, key, value, meta) => {
if (key === "alarm_smoke") {
let transformedValue = "OFF";
if (value === true) {
transformedValue = "ON";
}
const index = utils.getFromLookup(transformedValue, smokeAlarm);
await entity.command("ssIasZone", "boschSmokeAlarmSiren", { data: index }, manufacturerOptions);
return { state: { alarm_smoke: value } };
}
if (key === "alarm_burglar") {
let transformedValue = "OFF";
if (value === true) {
transformedValue = "ON";
}
const index = utils.getFromLookup(transformedValue, burglarAlarm);
await entity.command("ssIasZone", "boschSmokeAlarmSiren", { data: index }, manufacturerOptions);
return { state: { alarm_burglar: value } };
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case "alarm_smoke":
case "alarm_burglar":
case "zone_status":
await entity.read("ssIasZone", ["zoneStatus"]);
break;
default:
throw new Error(`Unhandled key boschExtend.smokeAlarm.toZigbee.convertGet ${key}`);
}
},
},
];
return {
exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
broadcastAlarm: () => {
const sirenState = {
smoke_off: 0x0000,
smoke_on: 0x3c00,
burglar_off: 0x0001,
burglar_on: 0xb401,
};
const exposes = [
e
.enum("broadcast_alarm", ea.SET, Object.keys(sirenState))
.withDescription("Set siren state of all BSD-2 via broadcast")
.withCategory("config"),
];
const toZigbee = [
{
key: ["broadcast_alarm"],
convertSet: async (entity, key, value, meta) => {
if (key === "broadcast_alarm") {
const index = utils.getFromLookup(value, sirenState);
utils.assertEndpoint(entity);
await entity.zclCommandBroadcast(255, zigbee_herdsman_1.ZSpec.BroadcastAddress.SLEEPY, zigbee_herdsman_1.Zcl.Clusters.ssIasZone.ID, "boschSmokeAlarmSiren", { data: index }, manufacturerOptions);
return;
}
},
},
];
return {
exposes,
toZigbee,
isModernExtend: true,
};
},
twinguard: () => {
const smokeSensitivity = {
low: 0x03,
medium: 0x02,
high: 0x01,
};
const sirenState = {
stop: 0x00,
pre_alarm: 0x01,
fire: 0x02,
burglar: 0x03,
};
const stateOffOn = {
OFF: 0x00,
ON: 0x01,
};
const exposes = [
e.binary("smoke", ea.STATE, true, false).withDescription("Indicates whether the device detected smoke"),
e
.numeric("temperature", ea.STATE)
.withValueMin(0)
.withValueMax(65)
.withValueStep(0.1)
.withUnit("°C")
.withDescription("Measured temperature value"),
e
.numeric("humidity", ea.STATE)
.withValueMin(0)
.withValueMax(100)
.withValueStep(0.1)
.withUnit("%")
.withDescription("Measured relative humidity"),
e
.numeric("voc", ea.STATE)
.withValueMin(0)
.withValueMax(50000)
.withValueStep(1)
.withLabel("VOC")
.withUnit("µg/m³")
.withDescription("Measured VOC value"),
e
.numeric("co2", ea.STATE)
.withValueMin(400)
.withValueMax(2400)
.withValueStep(1)
.withLabel("CO2")
.withUnit("ppm")
.withDescription("The measured CO2 (carbon dioxide) value"),
e.numeric("aqi", ea.STATE).withValueMin(0).withValueMax(500).withValueStep(1).withLabel("AQI").withDescription("Air Quality Index"),
e.illuminance(),
e
.numeric("battery", ea.STATE)
.withUnit("%")
.withValueMin(0)
.withValueMax(100)
.withDescription("Remaining battery in %")
.withCategory("diagnostic"),
e.text("siren_state", ea.STATE).withDescription("Siren state").withCategory("diagnostic"),
e.enum("alarm", ea.ALL, Object.keys(sirenState)).withDescription("Alarm mode for siren"),
e.binary("self_test", ea.ALL, true, false).withDescription("Initiate self-test").withCategory("config"),
e.enum("sensitivity", ea.ALL, Object.keys(smokeSensitivity)).withDescription("Sensitivity of the smoke detector").withCategory("config"),
e.binary("pre_alarm", ea.ALL, "ON", "OFF").withDescription("Enable/disable pre-alarm").withCategory("config"),
e.binary("heartbeat", ea.ALL, "ON", "OFF").withDescription("Enable/disable heartbeat (blue LED)").withCategory("config"),
];
const fromZigbee = [
{
cluster: "twinguardSmokeDetector",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.sensitivity !== undefined) {
result.sensitivity = Object.keys(smokeSensitivity)[msg.data.sensitivity];
}
return result;
},
},
{
cluster: "twinguardMeasurements",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.humidity !== undefined) {
const humidity = utils.toNumber(msg.data.humidity) / 100.0;
if (utils.isInRange(0, 100, humidity)) {
result.humidity = humidity;
}
}
if (msg.data.airpurity !== undefined) {
const iaq = utils.toNumber(msg.data.airpurity);
result.aqi = iaq;
let factorVoc = 6;
let factorCo2 = 2;
if (iaq >= 51 && iaq <= 100) {
factorVoc = 10;
factorCo2 = 4;
}
else if (iaq >= 101 && iaq <= 150) {
factorVoc = 20;
factorCo2 = 4;
}
else if (iaq >= 151 && iaq <= 200) {
factorVoc = 50;
factorCo2 = 4;
}
else if (iaq >= 201 && iaq <= 250) {
factorVoc = 100;
factorCo2 = 4;
}
else if (iaq >= 251) {
factorVoc = 100;
factorCo2 = 4;
}
result.voc = iaq * factorVoc;
result.co2 = iaq * factorCo2 + 400;
}
if (msg.data.temperature !== undefined) {
result.temperature = utils.toNumber(msg.data.temperature) / 100.0;
}
if (msg.data.illuminance !== undefined) {
result.illuminance = utils.precisionRound(msg.data.illuminance / 2, 2);
}
if (msg.data.battery !== undefined) {
result.battery = utils.precisionRound(msg.data.battery / 2, 2);
}
return result;
},
},
{
cluster: "twinguardOptions",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.pre_alarm !== undefined) {
result.pre_alarm = Object.keys(stateOffOn)[msg.data.pre_alarm];
}
return result;
},
},
{
cluster: "twinguardSetup",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.heartbeat !== undefined) {
result.heartbeat = Object.keys(stateOffOn)[msg.data.heartbeat];
}
return result;
},
},
{
cluster: "twinguardAlarm",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const lookup = {
2097184: "clear",
18874400: "self_test",
35651616: "burglar",
2097282: "pre_alarm",
2097281: "fire",
2097216: "silenced",
};
if (msg.data.alarm_status !== undefined) {
result.self_test = (msg.data.alarm_status & (1 << 24)) > 0;
result.smoke = (msg.data.alarm_status & (1 << 7)) > 0;
result.siren_state = lookup[msg.data.alarm_status];
}
return result;
},
},
{
cluster: "genAlarms",
type: ["commandAlarm", "readResponse"],
convert: async (model, msg, publish, options, meta) => {
const result = {};
const lookup = {
16: "fire",
17: "pre_alarm",
20: "clear",
22: "silenced",
};
result.siren_state = lookup[msg.data.alarmcode];
if (msg.data.alarmcode === 0x10 || msg.data.alarmcode === 0x11) {
await msg.endpoint.commandResponse("genAlarms", "alarm", { alarmcode: msg.data.alarmcode, clusterid: 0xe000 }, { direction: 1 });
}
return result;
},
},
];
const toZigbee = [
{
key: ["sensitivity", "pre_alarm", "self_test", "alarm", "heartbeat"],
convertSet: async (entity, key, value, meta) => {
if (key === "sensitivity") {
const index = utils.getFromLookup(value, smokeSensitivity);
await entity.write("twinguardSmokeDetector", { sensitivity: index }, manufacturerOptions);
return { state: { sensitivity: value } };
}
if (key === "pre_alarm") {
const index = utils.getFromLookup(value, stateOffOn);
await entity.write("twinguardOptions", { pre_alarm: index }, manufacturerOptions);
return { state: { pre_alarm: value } };
}
if (key === "heartbeat") {
const endpoint = meta.device.getEndpoint(12);
const index = utils.getFromLookup(value, stateOffOn);
await endpoint.write("twinguardSetup", { heartbeat: index }, manufacturerOptions);
return { state: { heartbeat: value } };
}
if (key === "self_test") {
if (value) {
await entity.command("twinguardSmokeDetector", "initiateTestMode", manufacturerOptions);
}
}
if (key === "alarm") {
const endpoint = meta.device.getEndpoint(12);
const index = utils.getFromLookup(value, sirenState);
utils.assertEndpoint(entity);
if (index === 0x00) {
await entity.commandResponse("genAlarms", "alarm", { alarmcode: 0x16, clusterid: 0xe000 }, { direction: 1 });
await entity.commandResponse("genAlarms", "alarm", { alarmcode: 0x14, clusterid: 0xe000 }, { direction: 1 });
await endpoint.command("twinguardAlarm", "burglarAlarm", { data: 0x00 }, manufacturerOptions);
}
else if (index === 0x01) {
await entity.commandResponse("genAlarms", "alarm", { alarmcode: 0x11, clusterid: 0xe000 }, { direction: 1 });
return { state: { siren_state: "pre_alarm" } };
}
else if (index === 0x02) {
await entity.commandResponse("genAlarms", "alarm", { alarmcode: 0x10, clusterid: 0xe000 }, { direction: 1 });
return { state: { siren_state: "fire" } };
}
else if (index === 0x03) {
await endpoint.command("twinguardAlarm", "burglarAlarm", { data: 0x01 }, manufacturerOptions);
}
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case "sensitivity":
await entity.read("twinguardSmokeDetector", ["sensitivity"], manufacturerOptions);
break;
case "pre_alarm":
await entity.read("twinguardOptions", ["pre_alarm"], manufacturerOptions);
break;
case "heartbeat":
await meta.device.getEndpoint(12).read("twinguardSetup", ["heartbeat"], manufacturerOptions);
break;
case "alarm":
case "self_test":
await meta.device.getEndpoint(12).read("twinguardAlarm", ["alarm_status"], manufacturerOptions);
break;
default:
throw new Error(`Unhandled key boschExtend.twinguard.toZigbee.convertGet ${key}`);
}
},
},
];
return {
exposes,
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
bmct: () => {
const stateDeviceMode = {
light: 0x04,
shutter: 0x01,
disabled: 0x00,
};
const stateMotor = {
stopped: 0x00,
opening: 0x01,
closing: 0x02,
};
const stateSwitchType = {
button: 0x01,
button_key_change: 0x02,
rocker_switch: 0x03,
rocker_switch_key_change: 0x04,
};
const stateOffOn = {
OFF: 0x00,
ON: 0x01,
};
const fromZigbee = [
fz.on_off,
fz.power_on_behavior,
fz.cover_position_tilt,
{
cluster: "boschSpecific",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
if (data.deviceMode !== undefined) {
result.device_mode = Object.keys(stateDeviceMode).find((key) => stateDeviceMode[key] === msg.data.deviceMode);
const deviceMode = msg.data.deviceMode;
if (deviceMode !== meta.device.meta.deviceMode) {
meta.device.meta.deviceMode = deviceMode;
meta.deviceExposesChanged();
}
}
if (data.switchType !== undefined) {
result.switch_type = Object.keys(stateSwitchType).find((key) => stateSwitchType[key] === msg.data.switchType);
}
if (data.calibrationOpeningTime !== undefined) {
result.calibration_opening_time = msg.data.calibrationOpeningTime / 10;
}
if (data.calibrationClosingTime !== undefined) {
result.calibration_closing_time = msg.data.calibrationClosingTime / 10;
}
if (data.calibrationButtonHoldTime !== undefined) {
result.calibration_button_hold_time = msg.data.calibrationButtonHoldTime / 10;
}
if (data.calibrationMotorStartDelay !== undefined) {
result.calibration_motor_start_delay = msg.data.calibrationMotorStartDelay / 10;
}
if (data.childLock !== undefined) {
const property = utils.postfixWithEndpointName("child_lock", msg, model, meta);
result[property] = msg.data.childLock === 1 ? "ON" : "OFF";
}
if (data.motorState !== undefined) {
result.motor_state = Object.keys(stateMotor).find((key) => stateMotor[key] === msg.data.motorState);
}
return result;
},
},
];
const toZigbee = [
tz.power_on_behavior,
tz.cover_position_tilt,
{
key: ["device_mode", "switch_type", "child_lock", "state", "on_time", "off_wait_time"],
convertSet: async (entity, key, value, meta) => {
if (key === "state") {
if ("ID" in entity && entity.ID === 1) {
await tz.cover_state.convertSet(entity, key, value, meta);
}
else {
await tz.on_off.convertSet(entity, key, value, meta);
}
}
if (key === "on_time" || key === "on_wait_time") {
if ("ID" in entity && entity.ID !== 1) {
await tz.on_off.convertSet(entity, key, value, meta);
}
}
if (key === "device_mode") {
const index = utils.getFromLookup(value, stateDeviceMode);
await entity.write("boschSpecific", { deviceMode: index });
await entity.read("boschSpecific", ["deviceMode"]);
return { state: { device_mode: value } };
}
if (key === "switch_type") {
const index = utils.getFromLookup(value, stateSwitchType);
await entity.write("boschSpecific", { switchType: index });
return { state: { switch_type: value } };
}
if (key === "child_lock") {
const index = utils.getFromLookup(value, stateOffOn);
await entity.write("boschSpecific", { childLock: index });
return { state: { child_lock: value } };
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case "state":
case "on_time":
case "off_wait_time":
if ("ID" in entity && entity.ID !== 1) {
await entity.read("genOnOff", ["onOff"]);
}
break;
case "device_mode":
await entity.read("boschSpecific", ["deviceMode"]);
break;
case "switch_type":
await entity.read("boschSpecific", ["switchType"]);
break;
case "child_lock":
await entity.read("boschSpecific", ["childLock"]);
break;
default:
throw new Error(`Unhandled key boschExtend.bmct.toZigbee.convertGet ${key}`);
}
},
},
{
key: ["calibration_closing_time", "calibration_opening_time", "calibration_button_hold_time", "calibration_motor_start_delay"],
convertSet: async (entity, key, value, meta) => {
if (key === "calibration_opening_time") {
const number = utils.toNumber(value, "calibration_opening_time");
const index = number * 10;
await entity.write("boschSpecific", { calibrationOpeningTime: index });
return { state: { calibration_opening_time: number } };
}
if (key === "calibration_closing_time") {
const number = utils.toNumber(value, "calibration_closing_time");
const index = number * 10;
await entity.write("boschSpecific", { calibrationClosingTime: index });
return { state: { calibration_closing_time: number } };
}
if (key === "calibration_button_hold_time") {
const number = utils.toNumber(value, "calibration_button_hold_time");
const index = number * 10;
await entity.write("boschSpecific", { calibrationButtonHoldTime: index });
return { state: { calibration_button_hold_time: number } };
}
if (key === "calibration_motor_start_delay") {
const number = utils.toNumber(value, "calibration_motor_start_delay");
const index = number * 10;
await entity.write("boschSpecific", { calibrationMotorStartDelay: index });
return { state: { calibration_motor_start_delay: number } };
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case "calibration_opening_time":
await entity.read("boschSpecific", ["calibrationOpeningTime"]);
break;
case "calibration_closing_time":
await entity.read("boschSpecific", ["calibrationClosingTime"]);
break;
case "calibration_button_hold_time":
await entity.read("boschSpecific", ["calibrationButtonHoldTime"]);
break;
case "calibration_motor_start_delay":
await entity.read("boschSpecific", ["calibrationMotorStartDelay"]);
break;
default:
throw new Error(`Unhandled key boschExtend.bmct.toZigbee.convertGet ${key}`);
}
},
},
];
return {
fromZigbee,
toZigbee,
isModernExtend: true,
};
},
};
const tzLocal = {
rbshoszbeu: {
key: ["light_delay", "siren_delay", "light_duration", "siren_duration", "siren_volume", "alarm_state", "power_source", "siren_and_light"],
convertSet: async (entity, key, value, meta) => {
if (key === "light_delay") {
const index = value;
await entity.write(0x0502, { 40964: { value: index, type: 0x21 } }, manufacturerOptions);
return { state: { light_delay: value } };
}
if (key === "siren_delay") {
const index = value;
await entity.write(0x0502, { 40963: { value: index, type: 0x21 } }, manufacturerOptions);
return { state: { siren_delay: value } };
}
if (key === "light_duration") {
const index = value;
await entity.write(0x0502, { 40965: { value: index, type: 0x20 } }, manufacturerOptions);
return { state: { light_duration: value } };
}
if (key === "siren_duration") {
const index = value;
await entity.write(0x0502, { 40960: { value: index, type: 0x20 } }, manufacturerOptions);
return { state: { siren_duration: value } };
}
if (key === "siren_and_light") {
const index = utils.getFromLookup(value, sirenLight);
await entity.write(0x0502, { 40961: { value: index, type: 0x20 } }, manufacturerOptions);
return { state: { siren_and_light: value } };
}
if (key === "siren_volume") {
const index = utils.getFromLookup(value, sirenVolume);
await entity.write(0x0502, { 40962: { value: index, type: 0x20 } }, manufacturerOptions);
return { state: { siren_volume: value } };
}
if (key === "power_source") {
const index = utils.getFromLookup(value, sirenPowerSupply);
await entity.write(0x0001, { 40962: { value: index, type: 0x20 } }, manufacturerOptions);
return { state: { power_source: value } };
}
if (key === "alarm_state") {
const endpoint = meta.device.getEndpoint(1);
const index = utils.getFromLookup(value, outdoorSirenState);
if (index === 0) {
await endpoint.command(0x0502, 0xf0, { data: 0 }, manufacturerOptions);
return { state: { alarm_state: value } };
}
await endpoint.command(0x0502, 0xf0, { data: 7 }, manufacturerOptions);
return { state: { alarm_state: value } };
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case "light_delay":
await entity.read(0x0502, [0xa004], manufacturerOptions);
break;
case "siren_delay":
await entity.read(0x0502, [0xa003], manufacturerOptions);
break;
case "light_duration":
await entity.read(0x0502, [0xa005], manufacturerOptions);
break;
case "siren_duration":
await entity.read(0x0502, [0xa000], manufacturerOptions);
break;
case "siren_and_light":
await entity.read(0x0502, [0xa001], manufacturerOptions);
break;
case "siren_volume":
await entity.read(0x0502, [0xa002], manufacturerOptions);
break;
case "alarm_state":
await entity.read(0x0502, [0xf0], manufacturerOptions);
break;
default: // Unknown key
throw new Error(`Unhandled key toZigbee.rbshoszbeu.convertGet ${key}`);
}
},
},
bhius_config: {
key: Object.keys(buttonMap),
convertGet: async (entity, key, meta) => {
if (buttonMap[key] === undefined) {
throw new Error(`Unknown key ${key}`);
}
await entity.read("boschSpecific", [buttonMap[key]], manufacturerOptions);
},
convertSet: async (entity, key, value, meta) => {
if (buttonMap[key] === undefined) {
return;
}
const buffer = Buffer.from(value, "hex");
if (buffer.length !== 9)
throw new Error(`Invalid configuration length: ${buffer.length} (should be 9)`);
const payload = {};
payload[buttonMap[key]] = { value: buffer, type: 65 };
await entity.write("boschSpecific", payload, manufacturerOptions);
const result = {};
result[key] = value;
return { state: result };
},
},
};
const fzLocal = {
bhius_button_press: {
cluster: "boschSpecific",
type: "raw",
options: [e.text("led_response", ea.ALL).withLabel("LED config (confirmation response)").withDescription(labelConfirmation)],
convert: async (model, msg, publish, options, meta) => {
const sequenceNumber = msg.data.readUInt8(3);
const buttonId = msg.data.readUInt8(4);
const longPress = msg.data.readUInt8(5);
const duration = msg.data.readUInt16LE(6);
// biome-ignore lint/suspicious/noImplicitAnyLet: ignored using `--suppress`
let buffer;
if (options.led_response != null) {
buffer = Buffer.from(options.led_response, "hex");
if (buffer.length !== 9) {
logger_1.logger.error(`Invalid length of led_response: ${buffer.length} (should be 9)`, NS);
buffer = Buffer.from("30ff00000102010001", "hex");
}
}
else {
buffer = Buffer.from("30ff00000102010001", "hex");
}
if (utils.hasAlreadyProcessedMessage(msg, model, sequenceNumber))
return;
const buttons = { 0: "top_left", 1: "top_right", 2: "bottom_left", 3: "bottom_right" };
let command = "";
if (buttonId in buttons) {
if (longPress && duration > 0) {
if (globalStore.hasValue(msg.endpoint, buttons[buttonId]))
return;