Files
ZigUP/zigbee-shepherd-converters/converters/toZigbee.js
2019-09-02 21:18:36 +02:00

1851 lines
66 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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;