UNPKG

zigbee-herdsman-converters

Version:

Collection of device converters to be used with zigbee-herdsman

1,104 lines • 79.6 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 (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