zigbee-herdsman-converters
Version:
Collection of device converters to be used with zigbee-herdsman
1,104 lines • 79.6 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 (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const modernExtend_1 = require("../lib/modernExtend");
const zigbee_herdsman_1 = require("zigbee-herdsman");
const exposes = __importStar(require("../lib/exposes"));
const fromZigbee_1 = __importDefault(require("../converters/fromZigbee"));
const toZigbee_1 = __importDefault(require("../converters/toZigbee"));
const reporting = __importStar(require("../lib/reporting"));
const utils = __importStar(require("../lib/utils"));
const constants = __importStar(require("../lib/constants"));
const ota = __importStar(require("../lib/ota"));
const globalStore = __importStar(require("../lib/store"));
const logger_1 = require("../lib/logger");
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,
};
// BMCT
const stateDeviceMode = {
'light': 0x04,
'shutter': 0x01,
'disabled': 0x00,
};
// BMCT
const stateMotor = {
'idle': 0x00,
'opening': 0x01,
'closing': 0x02,
};
// BMCT
const stateSwitchType = {
'button': 0x01,
'button_key_change': 0x02,
'rocker_switch': 0x03,
'rocker_switch_key_change': 0x04,
};
// Twinguard
const smokeSensitivity = {
'low': 3,
'medium': 2,
'high': 1,
};
// Twinguard
const sirenState = {
'stop': 0,
'pre_alarm': 1,
'fire': 2,
'burglar': 3,
};
// Radiator Thermostat II
const operatingModes = {
'automatic': 0,
'manual': 1,
'pause': 5,
};
// Radiator Thermostat II
const stateOffOn = {
'OFF': 0,
'ON': 1,
};
// Radiator Thermostat II
const displayOrientation = {
'normal': 0,
'flipped': 1,
};
// Radiator Thermostat II
const displayedTemperature = {
'target': 0,
'measured': 1,
};
// Smoke detector II bsd-2
const smokeAlarmState = {
'OFF': 0x0000,
'ON': 0x3c00, // 15360 or 46080 works
};
// Smoke detector II bsd-2
const burglarAlarmState = {
'OFF': 0x0001,
'ON': 0xb401, // 46081
};
// Smoke detector II bsd-2
const smokeDetectorSensitivity = {
'low': 0x0,
'medium': 0x1,
'high': 0x2,
};
// Radiator Thermostat II
const setpointSource = {
'manual': 0,
'schedule': 1,
'external': 2,
};
// Radiator Thermostat II
const adaptationStatus = {
'none': 0,
'ready_to_calibrate': 1,
'calibration_in_progress': 2,
'error': 3,
'success': 4,
};
// 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 tzLocal = {
bsd2: {
key: ['alarm_smoke', 'alarm_burglar', 'sensitivity'],
convertSet: async (entity, key, value, meta) => {
if (key === 'alarm_smoke') {
const index = utils.getFromLookup(value, smokeAlarmState);
await entity.command('ssIasZone', 'boschSmokeDetectorSiren', { data: index }, manufacturerOptions);
return { state: { alarm_smoke: value } };
}
if (key === 'alarm_burglar') {
const index = utils.getFromLookup(value, burglarAlarmState);
await entity.command('ssIasZone', 'boschSmokeDetectorSiren', { data: index }, manufacturerOptions);
return { state: { alarm_burglar: value } };
}
if (key === 'sensitivity') {
const index = utils.getFromLookup(value, smokeDetectorSensitivity);
await entity.write('ssIasZone', { currentZoneSensitivityLevel: index });
return { state: { sensitivity: value } };
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case 'alarm_smoke':
case 'alarm_burglar':
case 'zone_status':
await entity.read('ssIasZone', ['zoneStatus']);
break;
case 'sensitivity':
await entity.read('ssIasZone', ['currentZoneSensitivityLevel']);
break;
default: // Unknown key
throw new Error(`Unhandled key toZigbee.bsd2.convertGet ${key}`);
}
},
},
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, { 0xa004: { value: index, type: 0x21 } }, manufacturerOptions);
return { state: { light_delay: value } };
}
if (key === 'siren_delay') {
const index = value;
await entity.write(0x0502, { 0xa003: { value: index, type: 0x21 } }, manufacturerOptions);
return { state: { siren_delay: value } };
}
if (key === 'light_duration') {
const index = value;
await entity.write(0x0502, { 0xa005: { value: index, type: 0x20 } }, manufacturerOptions);
return { state: { light_duration: value } };
}
if (key === 'siren_duration') {
const index = value;
await entity.write(0x0502, { 0xa000: { 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, { 0xa001: { 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, { 0xa002: { value: index, type: 0x20 } }, manufacturerOptions);
return { state: { siren_volume: value } };
}
if (key === 'power_source') {
const index = utils.getFromLookup(value, sirenPowerSupply);
await entity.write(0x0001, { 0xa002: { 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 } };
}
else {
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}`);
}
},
},
bmct: {
key: [
'device_mode',
'switch_type',
'child_lock',
'calibration', 'calibration_closing_time', 'calibration_opening_time',
'state',
'on_time',
'off_wait_time',
],
convertSet: async (entity, key, value, meta) => {
if (key === 'state') {
if ('ID' in entity && entity.ID === 1) {
await toZigbee_1.default.cover_state.convertSet(entity, key, value, meta);
}
else {
await toZigbee_1.default.on_off.convertSet(entity, key, value, meta);
}
}
if (key === 'on_time' || key === 'on_wait_time') {
if ('ID' in entity && entity.ID !== 1) {
await toZigbee_1.default.on_off.convertSet(entity, key, value, meta);
}
}
if (key === 'device_mode') {
const index = utils.getFromLookup(value, stateDeviceMode);
await entity.write('manuSpecificBosch10', { deviceMode: index });
await entity.read('manuSpecificBosch10', ['deviceMode']);
return { state: { device_mode: value } };
}
if (key === 'switch_type') {
const index = utils.getFromLookup(value, stateSwitchType);
await entity.write('manuSpecificBosch10', { switchType: index });
return { state: { switch_type: value } };
}
if (key === 'child_lock') {
const index = utils.getFromLookup(value, stateOffOn);
await entity.write('manuSpecificBosch10', { childLock: index });
return { state: { child_lock: value } };
}
if (key === 'calibration_opening_time') {
const number = utils.toNumber(value, 'calibration_opening_time');
const index = number * 10;
await entity.write('manuSpecificBosch10', { 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('manuSpecificBosch10', { calibrationClosingTime: index });
return { state: { calibration_closing_time: number } };
}
},
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('manuSpecificBosch10', ['deviceMode']);
break;
case 'switch_type':
await entity.read('manuSpecificBosch10', ['switchType']);
break;
case 'child_lock':
await entity.read('manuSpecificBosch10', ['childLock']);
break;
case 'calibration_opening_time':
await entity.read('manuSpecificBosch10', ['calibrationOpeningTime']);
break;
case 'calibration_closing_time':
await entity.read('manuSpecificBosch10', ['calibrationClosingTime']);
break;
default: // Unknown key
throw new Error(`Unhandled key toZigbee.bcmt.convertGet ${key}`);
}
},
},
bwa1_alarm_on_motion: {
key: ['alarm_on_motion'],
convertSet: async (entity, key, value, meta) => {
if (key === 'alarm_on_motion') {
const index = utils.getFromLookup(value, stateOffOn);
await entity.write('manuSpecificBosch11', { alarmOnMotion: index }, manufacturerOptions);
return { state: { alarm_on_motion: value } };
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case 'alarm_on_motion':
await entity.read('manuSpecificBosch11', ['alarmOnMotion'], manufacturerOptions);
break;
default: // Unknown key
throw new Error(`Unhandled key toZigbee.bwa1_alarm_on_motion.convertGet ${key}`);
}
},
},
bosch_thermostat: {
key: ['window_detection', 'boost', 'system_mode', 'pi_heating_demand', 'remote_temperature', 'valve_adapt_process'],
convertSet: async (entity, key, value, meta) => {
if (key === 'window_detection') {
const index = utils.getFromLookup(value, stateOffOn);
await entity.write('hvacThermostat', { 0x4042: { value: index, type: zigbee_herdsman_1.Zcl.DataType.enum8 } }, manufacturerOptions);
return { state: { window_detection: value } };
}
if (key === 'boost') {
const index = utils.getFromLookup(value, stateOffOn);
await entity.write('hvacThermostat', { 0x4043: { value: index, type: zigbee_herdsman_1.Zcl.DataType.enum8 } }, manufacturerOptions);
return { state: { boost: value } };
}
if (key === 'system_mode') {
// Map system_mode (Off/Auto/Heat) to Bosch operating mode
utils.assertString(value, key);
value = value.toLowerCase();
let opMode = operatingModes.manual; // OperatingMode 1 = Manual (Default)
if (value == 'off') {
opMode = operatingModes.pause; // OperatingMode 5 = Pause
}
else if (value == 'auto') {
opMode = operatingModes.automatic; // OperatingMode 0 = Automatic
}
await entity.write('hvacThermostat', { 0x4007: { value: opMode, type: zigbee_herdsman_1.Zcl.DataType.enum8 } }, manufacturerOptions);
return { state: { system_mode: value } };
}
if (key === 'pi_heating_demand') {
let demand = utils.toNumber(value, key);
demand = utils.numberWithinRange(demand, 0, 100);
await entity.write('hvacThermostat', { 0x4020: { value: demand, type: zigbee_herdsman_1.Zcl.DataType.enum8 } }, manufacturerOptions);
return { state: { pi_heating_demand: demand } };
}
if (key === 'remote_temperature') {
let temperature = utils.toNumber(value, key);
temperature = utils.precisionRound(temperature, 1);
const convertedTemperature = utils.precisionRound(temperature * 100, 0);
await entity.write('hvacThermostat', { 0x4040: { value: convertedTemperature, type: zigbee_herdsman_1.Zcl.DataType.int16 } }, manufacturerOptions);
return { state: { remote_temperature: temperature } };
}
if (key === 'valve_adapt_process') {
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', 'boschCalibrateValve', {}, manufacturerOptions);
break;
default:
throw new Error('Valve adaptation process not possible right now.');
}
}
return { state: { valve_adapt_process: value } };
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case 'window_detection':
await entity.read('hvacThermostat', [0x4042], manufacturerOptions);
break;
case 'boost':
await entity.read('hvacThermostat', [0x4043], manufacturerOptions);
break;
case 'system_mode':
await entity.read('hvacThermostat', [0x4007], manufacturerOptions);
break;
case 'pi_heating_demand':
await entity.read('hvacThermostat', [0x4020], manufacturerOptions);
break;
case 'remote_temperature':
await entity.read('hvacThermostat', [0x4040], manufacturerOptions);
break;
case 'valve_adapt_process':
// Reads the current valve adaptation status as it depends solely on it
await entity.read('hvacThermostat', [0x4022], manufacturerOptions);
break;
default: // Unknown key
throw new Error(`Unhandled key toZigbee.bosch_thermostat.convertGet ${key}`);
}
},
},
bosch_userInterface: {
key: ['display_orientation', 'display_ontime', 'display_brightness', 'child_lock', 'displayed_temperature'],
convertSet: async (entity, key, value, meta) => {
if (key === 'display_orientation') {
const index = utils.getFromLookup(value, displayOrientation);
await entity.write('hvacUserInterfaceCfg', { 0x400b: { value: index, type: zigbee_herdsman_1.Zcl.DataType.uint8 } }, manufacturerOptions);
return { state: { display_orientation: value } };
}
if (key === 'display_ontime') {
let ontime = utils.toNumber(value, key);
ontime = utils.numberWithinRange(ontime, 5, 30);
await entity.write('hvacUserInterfaceCfg', { 0x403a: { value: ontime, type: zigbee_herdsman_1.Zcl.DataType.enum8 } }, manufacturerOptions);
return { state: { display_ontime: ontime } };
}
if (key === 'display_brightness') {
let brightness = utils.toNumber(value, key);
brightness = utils.numberWithinRange(brightness, 0, 10);
await entity.write('hvacUserInterfaceCfg', { 0x403b: { value: brightness, type: zigbee_herdsman_1.Zcl.DataType.enum8 } }, manufacturerOptions);
return { state: { display_brightness: brightness } };
}
if (key === 'child_lock') {
const keypadLockout = Number(value === 'LOCK');
await entity.write('hvacUserInterfaceCfg', { keypadLockout });
return { state: { child_lock: value } };
}
if (key === 'displayed_temperature') {
const index = utils.getFromLookup(value, displayedTemperature);
await entity.write('hvacUserInterfaceCfg', { 0x4039: { value: index, type: zigbee_herdsman_1.Zcl.DataType.enum8 } }, manufacturerOptions);
return { state: { displayed_temperature: value } };
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case 'display_orientation':
await entity.read('hvacUserInterfaceCfg', [0x400b], manufacturerOptions);
break;
case 'display_ontime':
await entity.read('hvacUserInterfaceCfg', [0x403a], manufacturerOptions);
break;
case 'display_brightness':
await entity.read('hvacUserInterfaceCfg', [0x403b], manufacturerOptions);
break;
case 'child_lock':
await entity.read('hvacUserInterfaceCfg', ['keypadLockout']);
break;
case 'displayed_temperature':
await entity.read('hvacUserInterfaceCfg', [0x4039], manufacturerOptions);
break;
default: // Unknown key
throw new Error(`Unhandled key toZigbee.bosch_userInterface.convertGet ${key}`);
}
},
},
bosch_twinguard: {
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('manuSpecificBosch', { sensitivity: index }, manufacturerOptions);
return { state: { sensitivity: value } };
}
if (key === 'pre_alarm') {
const index = utils.getFromLookup(value, stateOffOn);
await entity.write('manuSpecificBosch5', { 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('manuSpecificBosch7', { heartbeat: index }, manufacturerOptions);
return { state: { heartbeat: value } };
}
if (key === 'self_test') {
if (value) {
await entity.command('manuSpecificBosch', 'initiateTestMode', manufacturerOptions);
}
}
if (key === 'alarm') {
const endpoint = meta.device.getEndpoint(12);
const index = utils.getFromLookup(value, sirenState);
utils.assertEndpoint(entity);
if (index == 0) {
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('manuSpecificBosch8', 'burglarAlarm', { data: 0 }, manufacturerOptions);
}
else if (index == 1) {
await entity.commandResponse('genAlarms', 'alarm', { alarmcode: 0x11, clusterid: 0xe000 }, { direction: 1 });
return { state: { siren_state: 'pre-alarm' } };
}
else if (index == 2) {
await entity.commandResponse('genAlarms', 'alarm', { alarmcode: 0x10, clusterid: 0xe000 }, { direction: 1 });
return { state: { siren_state: 'fire' } };
}
else if (index == 3) {
await endpoint.command('manuSpecificBosch8', 'burglarAlarm', { data: 1 }, manufacturerOptions);
}
}
},
convertGet: async (entity, key, meta) => {
switch (key) {
case 'sensitivity':
await entity.read('manuSpecificBosch', ['sensitivity'], manufacturerOptions);
break;
case 'pre_alarm':
await entity.read('manuSpecificBosch5', ['pre_alarm'], manufacturerOptions);
break;
case 'heartbeat':
await meta.device.getEndpoint(12).read('manuSpecificBosch7', ['heartbeat'], manufacturerOptions);
break;
case 'alarm':
case 'self_test':
await meta.device.getEndpoint(12).read('manuSpecificBosch8', ['alarm_status'], manufacturerOptions);
break;
default: // Unknown key
throw new Error(`Unhandled key toZigbee.bosch_twinguard.convertGet ${key}`);
}
},
},
bhius_config: {
key: Object.keys(buttonMap),
convertGet: async (entity, key, meta) => {
if (!buttonMap.hasOwnProperty(key)) {
throw new Error(`Unknown key ${key}`);
}
await entity.read('manuSpecificBosch9', [buttonMap[key]], manufacturerOptions);
},
convertSet: async (entity, key, value, meta) => {
if (!buttonMap.hasOwnProperty(key)) {
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('manuSpecificBosch9', payload, manufacturerOptions);
const result = {};
result[key] = value;
return { state: result };
},
},
};
const fzLocal = {
bsd2: {
cluster: 'ssIasZone',
type: ['commandStatusChangeNotification', 'attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const data = msg.data;
const lookup = { 0: 'low', 1: 'medium', 2: 'high' };
if (data.hasOwnProperty('zoneStatus') || data.hasOwnProperty('zonestatus')) {
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,
};
}
if (data.hasOwnProperty('currentZoneSensitivityLevel')) {
const value = data.currentZoneSensitivityLevel;
return { sensitivity: lookup[value] };
}
},
},
bmct: {
cluster: 'manuSpecificBosch10',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
if (data.hasOwnProperty('deviceMode')) {
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.hasOwnProperty('switchType')) {
result.switch_type = Object.keys(stateSwitchType).find((key) => stateSwitchType[key] === msg.data['switchType']);
}
if (data.hasOwnProperty('calibrationOpeningTime')) {
result.calibration_opening_time = msg.data['calibrationOpeningTime'] / 10;
}
if (data.hasOwnProperty('calibrationClosingTime')) {
result.calibration_closing_time = msg.data['calibrationClosingTime'] / 10;
}
if (data.hasOwnProperty('childLock')) {
const property = utils.postfixWithEndpointName('child_lock', msg, model, meta);
result[property] = msg.data['childLock'] === 1 ? 'ON' : 'OFF';
}
if (data.hasOwnProperty('motorState')) {
result.motor_state = Object.keys(stateMotor).find((key) => stateMotor[key] === msg.data['motorState']);
}
return result;
},
},
bwa1_alarm_on_motion: {
cluster: 'manuSpecificBosch11',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.hasOwnProperty('alarmOnMotion')) {
result.alarm_on_motion = (Object.keys(stateOffOn)[msg.data['alarmOnMotion']]);
}
return result;
},
},
bosch_contact: {
cluster: 'ssIasZone',
type: 'commandStatusChangeNotification',
convert: (model, msg, publish, options, meta) => {
const zoneStatus = msg.data.zonestatus;
const lookup = { 0: 'none', 1: 'single', 2: 'long' };
const result = {
contact: !((zoneStatus & 1) > 0),
vibration: (zoneStatus & 1 << 1) > 0,
battery_low: (zoneStatus & 1 << 3) > 0,
action: lookup[(zoneStatus >> 11) & 3],
};
if (result.action === 'none')
delete result.action;
return result;
},
},
bosch_ignore_dst: {
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);
}
},
},
bosch_thermostat: {
cluster: 'hvacThermostat',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
if (data.hasOwnProperty(0x4040)) {
result.remote_temperature = utils.precisionRound(data[0x4040] / 100, 1);
}
if (data.hasOwnProperty(0x4042)) {
result.window_detection = (Object.keys(stateOffOn)[data[0x4042]]);
}
if (data.hasOwnProperty(0x4043)) {
result.boost = (Object.keys(stateOffOn)[data[0x4043]]);
}
if (data.hasOwnProperty(0x4007)) {
const opModes = { 0: 'auto', 1: 'heat', 2: 'unknown_2', 3: 'unknown_3', 4: 'unknown_4', 5: 'off' };
result.system_mode = opModes[data[0x4007]];
}
if (data.hasOwnProperty(0x4020)) {
const demand = data[0x4020];
result.pi_heating_demand = demand;
result.running_state = demand > 0 ? 'heat' : 'idle';
}
if (data.hasOwnProperty(0x4022)) {
result.valve_adapt_status = utils.getFromLookupByValue(data[0x4022], adaptationStatus);
if (data[0x4022] === adaptationStatus.calibration_in_progress) {
result.valve_adapt_process = true;
}
else {
result.valve_adapt_process = false;
}
}
return result;
},
},
bosch_userInterface: {
cluster: 'hvacUserInterfaceCfg',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const result = {};
const data = msg.data;
if (data.hasOwnProperty(0x400b)) {
result.display_orientation = (Object.keys(displayOrientation)[data[0x400b]]);
}
if (data.hasOwnProperty(0x4039)) {
result.displayed_temperature = (Object.keys(displayedTemperature)[data[0x4039]]);
}
if (data.hasOwnProperty(0x403a)) {
result.display_ontime = data[0x403a];
}
if (data.hasOwnProperty(0x403b)) {
result.display_brightness = data[0x403b];
}
if (data.hasOwnProperty('keypadLockout')) {
result.child_lock = (data['keypadLockout'] == 1 ? 'LOCK' : 'UNLOCK');
}
return result;
},
},
bosch_twinguard_sensitivity: {
cluster: 'manuSpecificBosch',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.hasOwnProperty('sensitivity')) {
result.sensitivity = (Object.keys(smokeSensitivity)[msg.data['sensitivity']]);
}
return result;
},
},
bosch_twinguard_measurements: {
cluster: 'manuSpecificBosch3',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.hasOwnProperty('humidity')) {
const humidity = parseFloat(msg.data['humidity']) / 100.0;
if (humidity >= 0 && humidity <= 100) {
result.humidity = humidity;
}
}
if (msg.data.hasOwnProperty('airpurity')) {
const iaq = parseInt(msg.data['airpurity']);
result.aqi = iaq;
result.co2 = ((iaq * 10) + 500);
let factor = 6;
if ((iaq >= 51) && (iaq <= 100)) {
factor = 10;
}
else if ((iaq >= 101) && (iaq <= 150)) {
factor = 20;
}
else if ((iaq >= 151) && (iaq <= 200)) {
factor = 50;
}
else if ((iaq >= 201) && (iaq <= 250)) {
factor = 100;
}
else if (iaq >= 251) {
factor = 100;
}
result.voc = (iaq * factor);
}
if (msg.data.hasOwnProperty('temperature')) {
result.temperature = parseFloat(msg.data['temperature']) / 100.0;
}
if (msg.data.hasOwnProperty('illuminance_lux')) {
result.illuminance_lux = utils.precisionRound((msg.data['illuminance_lux'] / 2), 2);
}
if (msg.data.hasOwnProperty('battery')) {
result.battery = utils.precisionRound((msg.data['battery'] / 2), 2);
}
return result;
},
},
bosch_twinguard_pre_alarm: {
cluster: 'manuSpecificBosch5',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.hasOwnProperty('pre_alarm')) {
result.pre_alarm = (Object.keys(stateOffOn)[msg.data['pre_alarm']]);
}
return result;
},
},
bosch_twinguard_heartbeat: {
cluster: 'manuSpecificBosch7',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const result = {};
if (msg.data.hasOwnProperty('heartbeat')) {
result.heartbeat = (Object.keys(stateOffOn)[msg.data['heartbeat']]);
}
return result;
},
},
bosch_twinguard_alarm_state: {
cluster: 'manuSpecificBosch8',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const result = {};
const lookup = {
0x00200020: 'clear',
0x01200020: 'self_test',
0x02200020: 'burglar',
0x00200082: 'pre-alarm',
0x00200081: 'fire',
0x00200040: 'silenced',
};
if (msg.data.hasOwnProperty('alarm_status')) {
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;
},
},
bosch_twinguard_smoke_alarm_state: {
cluster: 'genAlarms',
type: ['commandAlarm', 'readResponse'],
convert: async (model, msg, publish, options, meta) => {
const result = {};
const lookup = {
0x10: 'fire',
0x11: 'pre-alarm',
0x14: 'clear',
0x16: '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;
},
},
bhius_button_press: {
cluster: 'manuSpecificBosch9',
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);
let buffer;
if (options.hasOwnProperty('led_response')) {
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;
globalStore.putValue(msg.endpoint, buttons[buttonId], duration);
command = 'longpress';
}
else {
globalStore.clearValue(msg.endpoint, buttons[buttonId]);
command = longPress ? 'longpress_release' : 'release';
msg.endpoint.command('manuSpecificBosch9', 'confirmButtonPressed', { data: buffer }, { sendPolicy: 'immediate' })
.catch((error) => { });
}
return { action: `button_${buttons[buttonId]}_${command}` };
}
else {
logger_1.logger.error(`Received message with unknown command ID ${buttonId}. Data: 0x${msg.data.toString('hex')}`, NS);
}
},
},
bhius_config: {
cluster: 'manuSpecificBosch9',
type: ['attributeReport', 'readResponse'],
convert: async (model, msg, publish, options, meta) => {
const result = {};
for (const id of Object.values(buttonMap)) {
if (msg.data.hasOwnProperty(id)) {
result[Object.keys(buttonMap).find((key) => buttonMap[key] === id)] = msg.data[id].toString('hex');
}
}
return result;
},
},
};
const definitions = [
{
zigbeeModel: ['RBSH-OS-ZB-EU'],
model: 'BSIR-EZ',
vendor: 'Bosch',
description: 'Outdoor siren',
fromZigbee: [fromZigbee_1.default.ias_alarm_only_alarm_1, fromZigbee_1.default.battery, fromZigbee_1.default.power_source],
toZigbee: [tzLocal.rbshoszbeu, toZigbee_1.default.warning],
meta: { battery: { voltageToPercentage: { min: 2500, max: 4200 } } },
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ['genPowerCfg', 'ssIasZone', 'ssIasWd', 'genBasic']);
await reporting.batteryVoltage(endpoint);
await endpoint.read(0x0502, [0xa000, 0xa001, 0xa002, 0xa003, 0xa004, 0xa005], manufacturerOptions);
await endpoint.unbind('genPollCtrl', coordinatorEndpoint);
},
exposes: [
e.binary('alarm_state', ea.ALL, 'ON', 'OFF').withDescription('Alarm turn ON/OFF'),
e.numeric('light_delay', ea.ALL).withValueMin(0).withValueMax(30).withValueStep(1)
.withUnit('s').withDescription('Flashing light delay').withUnit('s'),
e.numeric('siren_delay', ea.ALL).withValueMin(0).withValueMax(30).withValueStep(1)
.withUnit('s').withDescription('Siren alarm delay').withUnit('s'),
e.numeric('siren_duration', ea.ALL).withValueMin(1).withValueMax(15).withValueStep(1)
.withUnit('m').withDescription('Duration of the alarm siren').withUnit('m'),
e.numeric('light_duration', ea.ALL).withValueMin(1).withValueMax(15).withValueStep(1)
.withUnit('m').withDescription('Duration of the alarm light').withUnit('m'),
e.enum('siren_volume', ea.ALL, Object.keys(sirenVolume)).withDescription('Volume of the alarm'),
e.enum('siren_and_light', ea.ALL, Object.keys(sirenLight)).withDescription('Siren and Light behaviour during alarm '),
e.enum('power_source', ea.ALL, Object.keys(sirenPowerSupply)).withDescription('Siren power source'),
e.warning()
.removeFeature('strobe_level')
.removeFeature('strobe')
.removeFeature('strobe_duty_cycle')
.removeFeature('level')
.removeFeature('duration'),
e.test(), e.tamper(), e.battery(), e.battery_voltage(), e.battery_low(),
e.binary('ac_status', ea.STATE, true, false).withDescription('Is the device plugged in'),
],
extend: [
(0, modernExtend_1.quirkCheckinInterval)(0),
],
},
{
zigbeeModel: ['RBSH-WS-ZB-EU'],
model: 'BWA-1',
vendor: 'Bosch',
description: 'Zigbee smart water leak detector',
fromZigbee: [
fromZigbee_1.default.battery,
fromZigbee_1.default.ias_water_leak_alarm_1,
fzLocal.bwa1_alarm_on_motion,
],
toZigbee: [
tzLocal.bwa1_alarm_on_motion,
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, [
'genPowerCfg',
'genPollCtrl',
]);
await reporting.batteryPercentageRemaining(endpoint);
await endpoint.read('manuSpecificBosch11', ['alarmOnMotion'], manufacturerOptions);
},
exposes: [
e.water_leak(),
e.tamper(),
e.battery(),
e.battery_low(),
e.binary('alarm_on_motion', ea.ALL, 'ON', 'OFF').withDescription('Enable/Disable sound alarm on motion'),
],
},
{
zigbeeModel: ['RBSH-SD-ZB-EU'],
model: 'BSD-2',
vendor: 'Bosch',
description: 'Smoke alarm detector',
fromZigbee: [
fromZigbee_1.default.battery,
fzLocal.bsd2,
],
toZigbee: [
tzLocal.bsd2,
],
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, [
'genPowerCfg',
'genPollCtrl',
]);
await reporting.batteryPercentageRemaining(endpoint);
await endpoint.read('ssIasZone', ['zoneStatus']);
},
exposes: [
e.smoke(),
e.battery(),
e.battery_low(),
e.test(),
e.binary('alarm_burglar', ea.ALL, 'ON', 'OFF').withDescription('Toggle the burglar alarm on or off'),
e.binary('alarm_smoke', ea.ALL, 'ON', 'OFF').withDescription('Toggle the smoke alarm on or off'),
e.enum('sensitivity', ea.ALL, Object.keys(smokeDetectorSensitivity)).withDescription('Sensitivity of the smoke alarm'),
],
},
{
zigbeeModel: ['RFDL-ZB', 'RFDL-ZB-EU', 'RFDL-ZB-H', 'RFDL-ZB-K', 'RFDL-ZB-CHI', 'RFDL-ZB-MS', 'RFDL-ZB-ES', 'RFPR-ZB',
'RFPR-ZB-EU', 'RFPR-ZB-CHI', 'RFPR-ZB-ES', 'RFPR-ZB-MS'],
model: 'RADON TriTech ZB',
vendor: 'Bosch',
description: 'Wireless motion detector',
fromZigbee: [fromZigbee_1.default.temperature, fromZigbee_1.default.battery, fromZigbee_1.default.ias_occupancy_alarm_1, fromZigbee_1.default.illuminance],
toZigbee: [],
meta: { battery: { voltageToPercentage: '3V_2500' } },
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(1);
await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement', 'genPowerCfg']);
await reporting.temperature(endpoint);
await reporting.batteryVoltage(endpoint);
await reporting.illuminance(endpoint);
},
exposes: [e.temperature(), e.battery(), e.occupancy(), e.battery_low(), e.tamper(), e.illuminance(), e.illuminance_lux()],
},
{
zigbeeModel: ['ISW-ZPR1-WP13'],
model: 'ISW-ZPR1-WP13',
vendor: 'Bosch',
description: 'Motion sensor',
fromZigbee: [fromZigbee_1.default.temperature, fromZigbee_1.default.battery, fromZigbee_1.default.ias_occupancy_alarm_1, fromZigbee_1.default.ignore_iaszone_report],
toZigbee: [],
meta: { battery: { voltageToPercentage: '3V_2500' } },
configure: async (device, coordinatorEndpoint) => {
const endpoint = device.getEndpoint(5);
await reporting.bind(endpoint, coordinatorEndpoint, ['msTemperatureMeasurement', 'genPowerCfg']);
await reporting.temperature(endpoint);
await reporting.batteryVoltage(endpoint);
},
exposes: [e.temperature(), e.battery(), e.occupancy(), e.battery_low(), e.tamper()],
},
{
zigbeeModel: ['RBSH-TRV0-ZB-EU'],
model: 'BTH-RA',
vendor: 'Bosch',
description: 'Radiator thermostat II',
ota: ota.zigbeeOTA,
fromZigbee: [
fromZigbee_1.default.thermostat,
fromZigbee_1.default.battery,
fzLocal.bosch_ignore_dst,
fzLocal.bosch_thermostat,
fzLocal.bosch_userInterface,
],
toZigbee: [
toZigbee_1