'use strict'; const utils = require('./utils'); const common = require('./common'); const Zcl = require('zigbee-herdsman/dist/zcl'); const cfg = { default: { manufSpec: 0, disDefaultRsp: 0, }, defaultdisFeedbackRsp: { manufSpec: 0, disDefaultRsp: 0, disFeedbackRsp: true, }, xiaomi: { manufSpec: 1, disDefaultRsp: 1, manufCode: 0x115F, }, eurotronic: { manufSpec: 1, manufCode: 4151, }, hue: { manufSpec: 1, manufCode: 4107, }, osram: { manufSpec: 1, manufCode: 0x110c, }, sinope: { manufSpec: 1, manufCode: 0x119C, }, }; function getTransition(message, options) { if (message.hasOwnProperty('transition')) { return message.transition; } else if (options.hasOwnProperty('transition')) { return options.transition; } else { return 0; } } const converters = { /** * Generic */ factory_reset: { key: ['reset'], convert: (key, value, message, type, postfix, options) => { if (type === 'set') { return [{ cid: 'genBasic', cmd: 'resetFactDefault', cmdType: 'functional', zclData: {}, cfg: cfg.default, }]; } }, }, on_off: { key: ['state'], convert: (key, value, message, type, postfix, options) => { const cid = 'genOnOff'; const attrId = 'onOff'; if (type === 'set') { if (typeof value !== 'string') { return; } return [{ cid: cid, cmd: value.toLowerCase(), cmdType: 'functional', zclData: {}, cfg: options.disFeedbackRsp ? cfg.defaultdisFeedbackRsp : cfg.default, newState: {state: value.toUpperCase()}, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, cover_open_close: { key: ['state'], convert: (key, value, message, type, postfix, options) => { if (type === 'set') { if (typeof value !== 'string') { return; } const positionByState = { 'open': 100, 'close': 0, }; value = positionByState[value.toLowerCase()]; } return converters.cover_position.convert(key, value, message, type, postfix, options); }, }, cover_position: { key: ['position'], convert: (key, value, message, type, postfix, options) => { const cid = 'genLevelCtrl'; const attrId = 'currentLevel'; if (type === 'set') { return [{ cid: cid, cmd: 'moveToLevelWithOnOff', cmdType: 'functional', zclData: { level: Math.round(Number(value) * 2.55).toString(), transtime: 0, }, cfg: cfg.default, newState: {position: value}, readAfterWriteTime: 0, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, warning: { key: ['warning'], convert: (key, value, message, type, postfix, options) => { if (type === 'set') { const mode = { 'stop': 0, 'burglar': 1, 'fire': 2, 'emergency': 3, 'police_panic': 4, 'fire_panic': 5, 'emergency_panic': 6, }; const level = { 'low': 0, 'medium': 1, 'high': 2, 'very_high': 3, }; const values = { mode: value.mode || 'emergency', level: value.level || 'medium', strobe: value.hasOwnProperty('strobe') ? value.strobe : true, duration: value.hasOwnProperty('duration') ? value.duration : 10, }; const info = (mode[values.mode] << 4) + ((values.strobe ? 1 : 0) << 2) + (level[values.level]); return [{ cid: 'ssIasWd', cmd: 'startWarning', cmdType: 'functional', zclData: {startwarninginfo: info, warningduration: values.duration}, cfg: cfg.default, }]; } }, }, cover_control: { key: ['state'], convert: (key, value, message, type, postfix, options) => { const zclCmdLookup = { 'open': 'upOpen', 'close': 'downClose', 'stop': 'stop', 'on': 'upOpen', 'off': 'downClose', }; const zclCmd = zclCmdLookup[value.toLowerCase()]; if (zclCmd) { return [{ cid: 'closuresWindowCovering', cmdType: 'functional', cmd: zclCmd, zclData: {}, cfg: cfg.default, }]; } }, }, cover_gotopercentage: { key: ['position', 'tilt'], convert: (key, value, message, type, postfix, options) => { const isPosition = (key === 'position'); const cid = 'closuresWindowCovering'; const attrId = isPosition ? 'currentPositionLiftPercentage' : 'currentPositionTiltPercentage'; // ZigBee officially expects "open" to be 0 and "closed" to be 100 whereas // HomeAssistant etc. work the other way round. value = 100 - value; if (type === 'set') { return [{ cid: cid, cmdType: 'functional', cmd: isPosition ? 'goToLiftPercentage' : 'goToTiltPercentage', zclData: isPosition ? {percentageliftvalue: value} : {percentagetiltvalue: value}, cfg: cfg.default, }]; } else if (type === 'get') { return [{ cid: cid, cmdType: 'foundation', cmd: 'read', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, occupancy_timeout: { // set delay after motion detector changes from occupied to unoccupied key: ['occupancy_timeout'], convert: (key, value, message, type, postfix, options) => { const cid = 'msOccupancySensing'; // 1030 const attrId = Zcl.getAttributeLegacy(cid, 'pirOToUDelay').value; // = 16 if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 33, // uint16 // hue_sml001: // in seconds, minimum 10 seconds, <10 values result // in 10 seconds delay // make sure you write to second endpoint! attrData: value, }], cfg: cfg.default, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{ attrId: attrId, }], cfg: cfg.default, }]; } }, }, light_brightness: { key: ['brightness', 'brightness_percent'], convert: (key, value, message, type, postfix, options) => { const cid = 'genLevelCtrl'; const attrId = 'currentLevel'; if (type === 'set') { if (key === 'brightness_percent') { value = Math.round(Number(value) * 2.55).toString(); } if (Number(value) === 0) { const converted = converters.on_off.convert('state', 'off', message, 'set', postfix, options); converted[0].newState.brightness = 0; return converted; } else { return [{ cid: cid, cmd: 'moveToLevel', cmdType: 'functional', zclData: { level: Number(value), transtime: getTransition(message, options) * 10, }, cfg: cfg.default, newState: {brightness: Number(value)}, readAfterWriteTime: message.hasOwnProperty('transition') ? message.transition * 1000 : 0, }]; } } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, light_onoff_brightness: { key: ['state', 'brightness', 'brightness_percent'], convert: (key, value, message, type, postfix, options) => { if (type === 'set') { const hasBrightness = message.hasOwnProperty('brightness') || message.hasOwnProperty('brightness_percent'); const brightnessValue = message.hasOwnProperty('brightness') ? message.brightness : message.brightness_percent; const hasState = message.hasOwnProperty('state'); const hasTrasition = message.hasOwnProperty('transition') || options.hasOwnProperty('transition'); const state = hasState ? message.state.toLowerCase() : null; if (hasState && (state === 'off' || !hasBrightness) && !hasTrasition) { const converted = converters.on_off.convert('state', state, message, 'set', postfix, options); if (state === 'on') { converted[0].readAfterWriteTime = 0; } return converted; } else if (!hasState && hasBrightness && Number(brightnessValue) === 0) { const converted = converters.on_off.convert('state', 'off', message, 'set', postfix, options); converted[0].newState.brightness = 0; return converted; } else { let brightness = 0; if (hasState && !hasBrightness && state == 'on') { brightness = 255; } else if (message.hasOwnProperty('brightness')) { brightness = message.brightness; } else if (message.hasOwnProperty('brightness_percent')) { brightness = Math.round(Number(message.brightness_percent) * 2.55).toString(); } const transition = getTransition(message, options); return [{ cid: 'genLevelCtrl', cmd: 'moveToLevelWithOnOff', cmdType: 'functional', zclData: { level: Number(brightness), transtime: transition * 10, }, cfg: options.disFeedbackRsp ? cfg.defaultdisFeedbackRsp : cfg.default, newState: {state: brightness === 0 ? 'OFF' : 'ON', brightness: Number(brightness)}, readAfterWriteTime: transition * 1000, }]; } } else if (type === 'get') { return [ { cid: 'genOnOff', cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy('genOnOff', 'onOff').value}], cfg: cfg.default, }, { cid: 'genLevelCtrl', cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy('genLevelCtrl', 'currentLevel').value}], cfg: cfg.default, }, ]; } }, }, light_colortemp: { key: ['color_temp', 'color_temp_percent'], convert: (key, value, message, type, postfix, options) => { const cid = 'lightingColorCtrl'; const attrId = 'colorTemperature'; if (type === 'set') { if (key === 'color_temp_percent') { value = Number(value) * 3.46; value = Math.round(value + 154).toString(); } value = Number(value); return [{ cid: cid, cmd: 'moveToColorTemp', cmdType: 'functional', zclData: { colortemp: value, transtime: getTransition(message, options) * 10, }, cfg: cfg.default, newState: {color_temp: value}, readAfterWriteTime: message.hasOwnProperty('transition') ? message.transition * 1000 : 0, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, light_color: { key: ['color'], convert: (key, value, message, type, postfix, options) => { const cid = 'lightingColorCtrl'; if (type === 'set') { // Check if we need to convert from RGB to XY and which cmd to use let cmd; if (value.hasOwnProperty('r') && value.hasOwnProperty('g') && value.hasOwnProperty('b')) { const xy = utils.rgbToXY(value.r, value.g, value.b); value.x = xy.x; value.y = xy.y; } else if (value.hasOwnProperty('rgb')) { const rgb = value.rgb.split(',').map((i) => parseInt(i)); const xy = utils.rgbToXY(rgb[0], rgb[1], rgb[2]); value.x = xy.x; value.y = xy.y; } else if (value.hasOwnProperty('hex')) { const xy = utils.hexToXY(value.hex); value.x = xy.x; value.y = xy.y; } else if (value.hasOwnProperty('hue') && value.hasOwnProperty('saturation')) { value.hue = value.hue * (65535 / 360); value.saturation = value.saturation * (2.54); cmd = 'enhancedMoveToHueAndSaturation'; } else if (value.hasOwnProperty('hue')) { value.hue = value.hue * (65535 / 360); cmd = 'enhancedMoveToHue'; } else if (value.hasOwnProperty('saturation')) { value.saturation = value.saturation * (2.54); cmd = 'moveToSaturation'; } const zclData = { transtime: getTransition(message, options) * 10, }; let newState = null; switch (cmd) { case 'enhancedMoveToHueAndSaturation': zclData.enhancehue = value.hue; zclData.saturation = value.saturation; zclData.direction = value.direction || 0; break; case 'enhancedMoveToHue': zclData.enhancehue = value.hue; zclData.direction = value.direction || 0; break; case 'moveToSaturation': zclData.saturation = value.saturation; break; default: cmd = 'moveToColor'; newState = {color: {x: value.x, y: value.y}}; zclData.colorx = Math.round(value.x * 65535); zclData.colory = Math.round(value.y * 65535); } return [{ cid: cid, cmd: cmd, cmdType: 'functional', zclData: zclData, cfg: cfg.default, newState: newState, readAfterWriteTime: message.hasOwnProperty('transition') ? message.transition * 1000 : 0, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [ {attrId: Zcl.getAttributeLegacy(cid, 'currentX').value}, {attrId: Zcl.getAttributeLegacy(cid, 'currentY').value}, ], cfg: cfg.default, }]; } }, }, light_color_colortemp: { /** * This converter is a combination of light_color and light_colortemp and * can be used instead of the two individual converters. When used to set, * it actually calls out to light_color or light_colortemp to get the * return value. When used to get, it gets both color and colorTemp in * one call. * The reason for the existence of this somewhat peculiar converter is * that some lights don't report their state when changed. To fix this, * we query the state after we set it. We want to query color and colorTemp * both when setting either, because both change when setting one. This * converter is used to do just that. */ key: ['color', 'color_temp', 'color_temp_percent'], convert: (key, value, message, type, postfix, options) => { const cid = 'lightingColorCtrl'; if (type === 'set') { if (key == 'color') { return converters.light_color.convert(key, value, message, type, postfix, options); } else if (key == 'color_temp' || key == 'color_temp_percent') { return converters.light_colortemp.convert(key, value, message, type, postfix, options); } } else if (type == 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [ {attrId: Zcl.getAttributeLegacy(cid, 'currentX').value}, {attrId: Zcl.getAttributeLegacy(cid, 'currentY').value}, {attrId: Zcl.getAttributeLegacy(cid, 'colorTemperature').value}, ], cfg: cfg.default, }]; } }, }, light_alert: { key: ['alert', 'flash'], convert: (key, value, message, type, postfix, options) => { const cid = 'genIdentify'; if (type === 'set') { const lookup = { 'select': 0x00, 'lselect': 0x01, 'none': 0xFF, }; if (key === 'flash') { if (value === 2) { value = 'select'; } else if (value === 10) { value = 'lselect'; } } return [{ cid: cid, cmd: 'triggerEffect', cmdType: 'functional', zclData: { effectid: lookup[value.toLowerCase()], effectvariant: 0x01, }, cfg: cfg.default, }]; } }, }, thermostat_local_temperature: { key: 'local_temperature', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'localTemp'; if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, thermostat_local_temperature_calibration: { key: 'local_temperature_calibration', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'localTemperatureCalibration'; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: Zcl.getAttributeLegacy(cid, attrId).value, dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: Math.round(value * 10), }], cfg: cfg.default, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, thermostat_occupancy: { key: 'occupancy', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'ocupancy'; if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, thermostat_occupied_heating_setpoint: { key: 'occupied_heating_setpoint', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'occupiedHeatingSetpoint'; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: Zcl.getAttributeLegacy(cid, attrId).value, dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: (Math.round((value * 2).toFixed(1))/2).toFixed(1) * 100, }], cfg: cfg.default, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, thermostat_unoccupied_heating_setpoint: { key: 'unoccupied_heating_setpoint', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'unoccupiedHeatingSetpoint'; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: Zcl.getAttributeLegacy(cid, attrId).value, dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: (Math.round((value * 2).toFixed(1))/2).toFixed(1) * 100, }], cfg: cfg.default, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, thermostat_occupied_cooling_setpoint: { key: 'occupied_cooling_setpoint', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'occupiedCoolingSetpoint'; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: Zcl.getAttributeLegacy(cid, attrId).value, dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: (Math.round((value * 2).toFixed(1))/2).toFixed(1) * 100, }], cfg: cfg.default, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, thermostat_remote_sensing: { key: 'remote_sensing', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'remoteSensing'; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ // Bit 0 = 0 – local temperature sensed internally // Bit 0 = 1 – local temperature sensed remotely // Bit 1 = 0 – outdoor temperature sensed internally // Bit 1 = 1 – outdoor temperature sensed remotely // Bit 2 = 0 – occupancy sensed internally // Bit 2 = 1 – occupancy sensed remotely attrId: Zcl.getAttributeLegacy(cid, attrId).value, dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: value, // TODO: Lookup in Zigbee documentation }], cfg: cfg.default, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, thermostat_control_sequence_of_operation: { key: 'control_sequence_of_operation', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'ctrlSeqeOfOper'; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: Zcl.getAttributeLegacy(cid, attrId).value, dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: utils.getKeyByValue(common.thermostatControlSequenceOfOperations, value, value), }], cfg: cfg.default, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, thermostat_system_mode: { key: 'system_mode', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'systemMode'; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: Zcl.getAttributeLegacy(cid, attrId).value, dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: utils.getKeyByValue(common.thermostatSystemModes, value, value), }], cfg: cfg.default, readAfterWriteTime: 250, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, thermostat_setpoint_raise_lower: { key: 'setpoint_raise_lower', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; if (type === 'set') { return [{ cid: cid, cmd: 'setpointRaiseLower', cmdType: 'functional', zclData: { mode: value.mode, amount: Math.round(value.amount) * 100, }, cfg: cfg.default, }]; } }, }, thermostat_weekly_schedule: { key: 'weekly_schedule', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'weeklySchedule'; if (type === 'set') { return [{ cid: cid, cmd: 'setWeeklySchedule', cmdType: 'functional', zclData: { dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: value, // TODO: Combine attributes in attrData? temperature_setpoint_hold: value.temperature_setpoint_hold, temperature_setpoint_hold_duration: value.temperature_setpoint_hold_duration, thermostat_programming_operation_mode: value.thermostat_programming_operation_mode, thermostat_running_state: value.thermostat_running_state, }, cfg: cfg.default, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'getWeeklySchedule', cmdType: 'functional', zclData: {}, cfg: cfg.default, }]; } }, }, thermostat_clear_weekly_schedule: { key: 'clear_weekly_schedule', attr: [], convert: (key, value, message, type, postfix, options) => { return [{ cid: 'hvacThermostat', cmd: 'clearWeeklySchedule', type: 'functional', zclData: {}, }]; }, }, thermostat_relay_status_log: { key: 'relay_status_log', attr: [], convert: (key, value, message, type, postfix, options) => { return [{ cid: 'hvacThermostat', cmd: 'getRelayStatusLog', type: 'functional', zclData: {}, }]; }, }, thermostat_weekly_schedule_rsp: { key: 'weekly_schedule_rsp', attr: [], convert: (key, value, message, type, postfix, options) => { return [{ cid: 'hvacThermostat', cmd: 'getWeeklyScheduleRsp', type: 'functional', zclData: { number_of_transitions: value.numoftrans, // TODO: Lookup in Zigbee documentation day_of_week: value.dayofweek, mode: value.mode, thermoseqmode: value.thermoseqmode, }, }]; }, }, thermostat_relay_status_log_rsp: { key: 'relay_status_log_rsp', attr: [], convert: (key, value, message, type, postfix, options) => { return [{ cid: 'hvacThermostat', cmd: 'getRelayStatusLogRsp', type: 'functional', zclData: { time_of_day: value.timeofday, // TODO: Lookup in Zigbee documentation relay_status: value.relaystatus, local_temperature: value.localtemp, humidity: value.humidity, setpoint: value.setpoint, unread_entries: value.unreadentries, }, }]; }, }, thermostat_running_mode: { key: 'running_mode', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'runningMode'; if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, thermostat_running_state: { key: 'running_state', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 'runningState'; if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, thermostat_temperature_display_mode: { key: 'temperature_display_mode', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacUserInterfaceCfg'; const attrId = 'tempDisplayMode'; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: Zcl.getAttributeLegacy(cid, attrId).value, dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: utils.getKeyByValue(common.temperatureDisplayMode, value, value), }], cfg: cfg.default, }]; } }, }, thermostat_keypad_lockout: { key: 'keypad_lockout', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacUserInterfaceCfg'; const attrId = 'keypadLockout'; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: Zcl.getAttributeLegacy(cid, attrId).value, dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: utils.getKeyByValue(common.keypadLockoutMode, value, value), }], cfg: cfg.default, }]; } }, }, fan_mode: { key: ['fan_mode', 'fan_state'], convert: (key, value, message, type, postfix, options) => { const cid = 'hvacFanCtrl'; const attrId = 'fanMode'; if (type === 'set') { value = value.toLowerCase(); const attrData = common.fanMode[value]; return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value, attrData: attrData, dataType: 48}], cfg: cfg.default, newState: {fan_mode: value, fan_state: value === 'off' ? 'OFF' : 'ON'}, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, /** * Device specific */ DJT11LM_vibration_sensitivity: { key: ['sensitivity'], convert: (key, value, message, type, postfix, options) => { const cid = 'genBasic'; const attrId = 0xFF0D; if (type === 'set') { const lookup = { 'low': 0x15, 'medium': 0x0B, 'high': 0x01, }; if (lookup.hasOwnProperty(value)) { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 0x20, attrData: lookup[value], }], cfg: cfg.xiaomi, }]; } } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: attrId}], cfg: cfg.xiaomi, }]; } }, }, JTQJBF01LMBW_sensitivity: { key: ['sensitivity'], convert: (key, value, message, type, postfix, options) => { const cid = 'ssIasZone'; if (type === 'set') { const lookup = { 'low': 0x04010000, 'medium': 0x04020000, 'high': 0x04030000, }; if (lookup.hasOwnProperty(value)) { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: 0xFFF1, // presentValue dataType: 0x23, // dataType attrData: lookup[value], }], cfg: cfg.xiaomi, }]; } } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{ attrId: 0xFFF0, // presentValue dataType: 0x39, // dataType }], cfg: cfg.xiaomi, }]; } }, }, JTQJBF01LMBW_selfest: { key: ['selftest'], convert: (key, value, message, type, postfix, options) => { if (type === 'set') { return [{ cid: 'ssIasZone', cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: 0xFFF1, // presentValue dataType: 0x23, // dataType attrData: 0x03010000, }], cfg: cfg.xiaomi, }]; } }, }, xiaomi_switch_operation_mode: { key: ['operation_mode'], convert: (key, value, message, type, postfix, options) => { const cid = 'genBasic'; const lookupAttrId = { 'single': 0xFF22, 'left': 0xFF22, 'right': 0xFF23, }; const lookupState = { 'control_relay': 0x12, 'control_left_relay': 0x12, 'control_right_relay': 0x22, 'decoupled': 0xFE, }; let button; if (value.hasOwnProperty('button')) { button = value.button; } else { button = 'single'; } if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: lookupAttrId[button], dataType: 0x20, attrData: lookupState[value.state], }], cfg: cfg.xiaomi, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{ attrId: lookupAttrId[button], dataType: 0x20, }], cfg: cfg.xiaomi, }]; } }, }, STS_PRS_251_beep: { key: ['beep'], convert: (key, value, message, type, postfix, options) => { const cid = 'genIdentify'; const attrId = 'identifyTime'; if (type === 'set') { return [{ cid: cid, cmd: 'identify', cmdType: 'functional', zclData: { identifytime: value, }, cfg: cfg.default, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, ZNCLDJ11LM_control: { key: ['state', 'position'], convert: (key, value, message, type, postfix, options) => { if (key === 'state' && value.toLowerCase() === 'stop') { return [{ cid: 'closuresWindowCovering', cmd: 'stop', cmdType: 'functional', zclData: {}, cfg: cfg.default, }]; } else { const lookup = { 'open': 100, 'close': 0, 'on': 100, 'off': 0, }; value = typeof value === 'string' ? value.toLowerCase() : value; value = lookup.hasOwnProperty(value) ? lookup[value] : value; return [{ cid: 'genAnalogOutput', cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: 0x0055, dataType: 0x39, attrData: value, }], cfg: cfg.default, }]; } }, }, osram_cmds: { key: ['osram_set_transition', 'osram_remember_state'], convert: (key, value, message, type, postfix, options) => { if (type === 'set') { if ( key === 'osram_set_transition' ) { if (value) { const transition = ( value > 1 ) ? (Math.round((value * 2).toFixed(1))/2).toFixed(1) * 10 : 1; return [{ cid: 'genLevelCtrl', cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: 0x0012, dataType: 0x21, attrData: transition, }, {attrId: 0x0013, dataType: 0x21, attrData: transition, }], cfg: cfg.default, }]; } } else if ( key == 'osram_remember_state' ) { if (value === true) { return [{ cid: 'manuSpecificOsram', cmd: 'saveStartupParams', cmdType: 'functional', zclData: {}, cfg: cfg.osram, }]; } else if ( value === false ) { return [{ cid: 'manuSpecificOsram', cmd: 'resetStartupParams', cmdType: 'functional', zclData: {}, cfg: cfg.osram, }]; } } } }, }, eurotronic_system_mode: { key: 'eurotronic_system_mode', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 0x4008; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ // Bit 0 = ? (default 1) // Bit 2 = Boost // Bit 4 = Window open // Bit 7 = Child protection attrId: attrId, dataType: 0x22, attrData: value, }], cfg: cfg.eurotronic, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: attrId}], cfg: cfg.eurotronic, }]; } }, }, eurotronic_error_status: { key: 'eurotronic_error_status', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 0x4002; if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: attrId}], cfg: cfg.eurotronic, }]; } }, }, eurotronic_current_heating_setpoint: { key: 'current_heating_setpoint', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 0x4003; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 0x29, attrData: (Math.round((value * 2).toFixed(1))/2).toFixed(1) * 100, }], cfg: cfg.eurotronic, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: attrId}], cfg: cfg.eurotronic, }]; } }, }, eurotronic_valve_position: { key: 'eurotronic_valve_position', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 0x4001; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 0x20, attrData: value, }], cfg: cfg.eurotronic, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: attrId}], cfg: cfg.eurotronic, }]; } }, }, eurotronic_trv_mode: { key: 'eurotronic_trv_mode', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 0x4000; if (type === 'set') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 0x30, attrData: value, }], cfg: cfg.eurotronic, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: attrId}], cfg: cfg.eurotronic, }]; } }, }, livolo_switch_on_off: { key: ['state'], convert: (key, value, message, type, postfix, options) => { if (type === 'set') { if (typeof value !== 'string') { return; } postfix = postfix || 'left'; const cid = 'genLevelCtrl'; let state = value.toLowerCase(); let channel = 1; if (state === 'on') { state = 108; } else if (state === 'off') { state = 1; } else { return; } if (postfix === 'left') { channel = 1.0; } else if (postfix === 'right') { channel = 2.0; } else { return; } return [{ cid: cid, cmd: 'moveToLevelWithOnOff', cmdType: 'functional', zclData: { level: state, transtime: channel, }, cfg: cfg.default, newState: {state: value.toUpperCase()}, readAfterWriteTime: 250, }]; } else if (type === 'get') { const cid = 'genOnOff'; const attrId = 'onOff'; return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, generic_lock: { key: ['state'], convert: (key, value, message, type, postfix, options) => { const cid = 'closuresDoorLock'; const attrId = 'lockState'; if (type === 'set') { if (typeof value !== 'string') { return; } return [{ cid: cid, cmd: `${value.toLowerCase()}Door`, cmdType: 'functional', zclData: { 'pincodevalue': '', }, cfg: cfg.default, readAfterWriteTime: 200, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{attrId: Zcl.getAttributeLegacy(cid, attrId).value}], cfg: cfg.default, }]; } }, }, gledopto_light_onoff_brightness: { key: ['state', 'brightness', 'brightness_percent'], convert: (key, value, message, type, postfix, options) => { if (message && message.hasOwnProperty('transition')) { message.transition = message.transition * 3.3; } return converters.light_onoff_brightness.convert(key, value, message, type, postfix, options); }, }, gledopto_light_color_colortemp: { key: ['color', 'color_temp', 'color_temp_percent'], convert: (key, value, message, type, postfix, options) => { if (message && message.hasOwnProperty('transition')) { message.transition = message.transition * 3.3; } return converters.light_color_colortemp.convert(key, value, message, type, postfix, options); }, }, gledopto_light_colortemp: { key: ['color_temp', 'color_temp_percent'], convert: (key, value, message, type, postfix, options) => { if (message && message.hasOwnProperty('transition')) { message.transition = message.transition * 3.3; } return converters.light_colortemp.convert(key, value, message, type, postfix, options); }, }, hue_power_on_behavior: { key: ['hue_power_on_behavior'], convert: (key, value, message, type, postfix, options) => { const lookup = { 'default': 0x01, 'on': 0x01, 'off': 0x00, 'recover': 0xff, }; if (type === 'set') { return [{ cid: 'genOnOff', cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: 0x4003, dataType: 0x30, attrData: lookup[value], }], cfg: cfg.default, }]; } }, }, hue_power_on_brightness: { key: ['hue_power_on_brightness'], convert: (key, value, message, type, postfix, options) => { if (type === 'set') { if (value === 'default') { value = 255; } return [{ cid: 'genLevelCtrl', cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: 0x4000, dataType: 0x20, attrData: value, }], cfg: cfg.default, }]; } }, }, hue_power_on_color_temperature: { key: ['hue_power_on_color_temperature'], convert: (key, value, message, type, postfix, options) => { if (type === 'set') { if (value === 'default') { value = 366; } return [{ cid: 'lightingColorCtrl', cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: 0x4010, dataType: 0x21, attrData: value, }], cfg: cfg.default, }]; } }, }, hue_motion_sensitivity: { // motion detect sensitivity, philips specific key: ['motion_sensitivity'], convert: (key, value, message, type, postfix, options) => { const cid = 'msOccupancySensing'; // 1030 const attrId = 48; if (type === 'set') { // hue_sml: // 0: low, 1: medium, 2: high (default) // make sure you write to second endpoint! const lookup = { 'low': 0, 'medium': 1, 'high': 2, }; return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 32, attrData: lookup[value], }], cfg: cfg.hue, }]; } else if (type === 'get') { return [{ cid: cid, cmd: 'read', cmdType: 'foundation', zclData: [{ attrId: attrId, }], cfg: cfg.hue, }]; } }, }, ZigUP_lock: { key: ['led'], convert: (key, value, message, type, postfix, options) => { const lookup = { 'off': 'lockDoor', 'on': 'unlockDoor', 'toggle': 'toggleDoor', }; const cid = 'closuresDoorLock'; if (type === 'set') { if (typeof value !== 'string') { return; } return [{ cid: cid, cmd: lookup[value], cmdType: 'functional', zclData: { 'pincodevalue': '', }, cfg: cfg.default, }]; } }, }, // Sinope sinope_thermostat_backlight_autodim_param: { key: 'backlight_auto_dim', convert: (key, value, message, type, postfix, options) => { const cid = 'hvacThermostat'; const attrId = 0x0402; if (type === 'set') { const sinopeBacklightAutoDimParam = { 0: 'on demand', 1: 'sensing', }; return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 0x30, attrData: utils.getKeyByValue(sinopeBacklightAutoDimParam, value, value), }], cfg: cfg.default, }]; } }, }, sinope_thermostat_enable_outdoor_temperature: { key: 'enable_outdoor_temperature', convert: (key, value, message, type, postfix, options) => { const cid = 0xFF01; const attrId = 0x0011; if (type === 'set' && value.toLowerCase()=='on') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 0x21, // set outdoor temperature timer to 3 hours attrData: 10800, }], cfg: cfg.sinope, }]; } else if (type === 'set' && value.toLowerCase()=='off') { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 0x21, // set timer to 30sec in order to disable outdoor temperature attrData: 30, }], cfg: cfg.sinope, }]; } }, }, sinope_thermostat_outdoor_temperature: { key: 'thermostat_outdoor_temperature', convert: (key, value, message, type, postfix, options) => { const cid = 0xFF01; const attrId = 0x0010; if (type === 'set' && value > -100 && value < 100) { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 0x29, attrData: value * 100, }], cfg: cfg.sinope, }]; } }, }, sinope_thermostat_time: { key: 'thermostat_time', convert: (key, value, message, type, postfix, options) => { const cid = 0xFF01; const attrId = 0x0020; if (type === 'set' && value === '' ) { const thermostatDate = new Date(); const thermostatTimeSec = thermostatDate.getTime() / 1000; const thermostatTimezoneOffsetSec = thermostatDate.getTimezoneOffset() * 60; return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 0x23, // Current time in second since 2000-01-01T00:00 in the current time zone attrData: Math.round(thermostatTimeSec - thermostatTimezoneOffsetSec - 946684800), }], cfg: cfg.sinope, }]; } else if (type === 'set' && value !== '' ) { return [{ cid: cid, cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: attrId, dataType: 0x23, attrData: value, }], cfg: cfg.sinope, }]; } }, }, DTB190502A1_LED: { key: ['LED'], convert: (key, value, message, type, postfix, options) => { if (type === 'set') { if (value === 'default') { value = 1; } const lookup = { 'OFF': '0', 'ON': '1', }; value = lookup[value]; // Check for valid data if ( ((value >= 0) && value < 2) == false ) value = 0; return [{ cid: 'genBasic', cmd: 'write', cmdType: 'foundation', zclData: [{ attrId: 0x4010, dataType: 0x21, attrData: value, }], cfg: cfg.default, }]; } }, }, ptvo_switch_trigger: { key: ['trigger', 'interval'], convert: (key, value, message, type, postfix, options) => { console.log('ptvo_switch_trigger:', key, value, message, type, postfix); if (type === 'set') { value = parseInt(value); if (!value) { return; } const cid = 'genOnOff'; if (key === 'trigger') { return [{ cid: cid, cmd: 'onWithTimedOff', cmdType: 'functional', zclData: { ctrlbits: 0, ontime: value, offwaittime: 0, }, cfg: cfg.default, }]; } else if (key === 'interval') { const attrId = 'onOff'; return [{ cid: cid, cmd: 'configReport', cmdType: 'foundation', zclData: [{ direction: 0, attrId: Zcl.getAttributeLegacy(cid, attrId).value, dataType: Zcl.getAttributeTypeLegacy(cid, attrId).value, attrData: value, minRepIntval: value, maxRepIntval: value, }], cfg: cfg.default, }]; } } return; }, }, /** * Ignore converters */ ignore_transition: { key: ['transition'], attr: [], convert: (key, value, message, type, postfix, options) => null, }, }; module.exports = converters;