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

3384 lines
123 KiB
JavaScript

'use strict';
const debounce = require('debounce');
const common = require('./common');
const clickLookup = {
1: 'single',
2: 'double',
3: 'triple',
4: 'quadruple',
};
const occupancyTimeout = 90; // In seconds
const defaultPrecision = {
temperature: 2,
humidity: 2,
pressure: 1,
};
const precisionRoundOptions = (number, options, type) => {
const key = `${type}_precision`;
const defaultValue = defaultPrecision[type];
const precision = options && options.hasOwnProperty(key) ? options[key] : defaultValue;
return precisionRound(number, precision);
};
const precisionRound = (number, precision) => {
const factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
};
const calibrateOptions = (number, options, type) => {
const key = `${type}_calibration`;
let calibrationOffset = options && options.hasOwnProperty(key) ? options[key] : 0;
if (type == 'illuminance') {
// linear calibration because measured value is zero based
// +/- percent
calibrationOffset = Math.round(number * calibrationOffset / 100);
}
return number + calibrationOffset;
};
const toPercentage = (value, min, max) => {
if (value > max) {
value = max;
} else if (value < min) {
value = min;
}
const normalised = (value - min) / (max - min);
return (normalised * 100).toFixed(2);
};
const toPercentageCR2032 = (voltage) => {
let percentage = null;
if (voltage < 2100) {
percentage = 0;
} else if (voltage < 2440) {
percentage = 6 - ((2440 - voltage) * 6) / 340;
} else if (voltage < 2740) {
percentage = 18 - ((2740 - voltage) * 12) / 300;
} else if (voltage < 2900) {
percentage = 42 - ((2900 - voltage) * 24) / 160;
} else if (voltage < 3000) {
percentage = 100 - ((3000 - voltage) * 58) / 100;
} else if (voltage >= 3000) {
percentage = 100;
}
return Math.round(percentage);
};
const numberWithinRange = (number, min, max) => {
if (number > max) {
return max;
} else if (number < min) {
return min;
} else {
return number;
}
};
// get object property name (key) by it's value
const getKey = (object, value) => {
for (const key in object) {
if (object[key]==value) return key;
}
};
// Global variable store that can be used by devices.
const store = {};
const ictcg1 = (model, msg, publish, options, action) => {
const deviceID = msg.endpoints[0].device.ieeeAddr;
const payload = {};
if (!store[deviceID]) {
const _publish = debounce((msg) => publish(msg), 250);
store[deviceID] = {since: false, direction: false, value: 255, publish: _publish};
}
const s = store[deviceID];
if (s.since && s.direction) {
// Update value
const duration = Date.now() - s.since;
const delta = Math.round((duration / 10) * (s.direction === 'left' ? -1 : 1));
const newValue = s.value + delta;
if (newValue >= 0 && newValue <= 255) {
s.value = newValue;
}
}
if (action === 'move') {
s.since = Date.now();
const direction = msg.data.data.movemode === 1 ? 'left' : 'right';
s.direction = direction;
payload.action = `rotate_${direction}`;
} else if (action === 'stop' || action === 'level') {
if (action === 'level') {
s.value = msg.data.data.level;
const direction = s.value === 0 ? 'left' : 'right';
payload.action = `rotate_${direction}_quick`;
} else {
payload.action = 'rotate_stop';
}
s.since = false;
s.direction = false;
}
payload.brightness = s.value;
s.publish(payload);
};
const holdUpdateBrightness324131092621 = (deviceID) => {
if (store[deviceID] && store[deviceID].brightnessSince && store[deviceID].brightnessDirection) {
const duration = Date.now() - store[deviceID].brightnessSince;
const delta = (duration / 10) * (store[deviceID].brightnessDirection === 'up' ? 1 : -1);
const newValue = store[deviceID].brightnessValue + delta;
store[deviceID].brightnessValue = numberWithinRange(newValue, 0, 255);
}
};
const converters = {
HS2SK_power: {
cid: 'haElectricalMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
return {
power: msg.data.data['activePower'] / 10,
current: msg.data.data['rmsCurrent'] / 100,
voltage: msg.data.data['rmsVoltage'] / 100,
};
},
},
generic_lock: {
cid: 'closuresDoorLock',
type: ['attReport', 'readRsp', 'devChange'],
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('lockState')) {
return {state: msg.data.data.lockState == 2 ? 'UNLOCK' : 'LOCK'};
}
},
},
generic_lock_operation_event: {
cid: 'closuresDoorLock',
type: 'cmdOperationEventNotification',
convert: (model, msg, publish, options) => {
return {
state: msg.data.data['opereventcode'] == 2 ? 'UNLOCK' : 'LOCK',
user: msg.data.data['userid'],
source: msg.data.data['opereventsrc'],
};
},
},
genOnOff_cmdOn: {
cid: 'genOnOff',
type: 'cmdOn',
convert: (model, msg, publish, options) => {
return {click: 'on'};
},
},
genOnOff_cmdOff: {
cid: 'genOnOff',
type: 'cmdOff',
convert: (model, msg, publish, options) => {
return {click: 'off'};
},
},
E1743_brightness_up: {
cid: 'genLevelCtrl',
type: 'cmdMove',
convert: (model, msg, publish, options) => {
return {click: 'brightness_down'};
},
},
E1743_brightness_down: {
cid: 'genLevelCtrl',
type: 'cmdMoveWithOnOff',
convert: (model, msg, publish, options) => {
return {click: 'brightness_up'};
},
},
E1743_brightness_stop: {
cid: 'genLevelCtrl',
type: 'cmdStopWithOnOff',
convert: (model, msg, publish, options) => {
return {click: 'brightness_stop'};
},
},
AC0251100NJ_long_middle: {
cid: 'lightingColorCtrl',
type: 'cmdMoveHue',
convert: (model, msg, publish, options) => {
return {click: 'long_middle'};
},
},
AV2010_34_click: {
cid: 'genScenes',
type: 'cmdRecall',
convert: (model, msg, publish, options) => {
return {click: msg.data.data.groupid};
},
},
bitron_power: {
cid: 'seMetering',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
return {power: parseFloat(msg.data.data['instantaneousDemand']) / 10.0};
},
},
bitron_occupancy: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
// The occupancy sensor only sends a message when motion detected.
// Therefore we need to publish the no_motion detected by ourselves.
const useOptionsTimeout = options && options.hasOwnProperty('occupancy_timeout');
const timeout = useOptionsTimeout ? options.occupancy_timeout : occupancyTimeout;
const deviceID = msg.endpoints[0].device.ieeeAddr;
// Stop existing timer because motion is detected and set a new one.
if (store[deviceID]) {
clearTimeout(store[deviceID]);
store[deviceID] = null;
}
if (timeout !== 0) {
store[deviceID] = setTimeout(() => {
publish({occupancy: false});
store[deviceID] = null;
}, timeout * 1000);
}
return {occupancy: true};
},
},
bitron_battery_att_report: {
cid: 'genPowerCfg',
type: 'attReport',
convert: (model, msg, publish, options) => {
const result = {};
if (typeof msg.data.data['batteryVoltage'] == 'number') {
const battery = {max: 3200, min: 2500};
const voltage = msg.data.data['batteryVoltage'] * 100;
result.battery = toPercentage(voltage, battery.min, battery.max);
result.voltage = voltage;
}
if (typeof msg.data.data['batteryAlarmState'] == 'number') {
result.battery_alarm_state = msg.data.data['batteryAlarmState'];
}
return result;
},
},
bitron_battery_dev_change: {
cid: 'genPowerCfg',
type: 'devChange',
convert: (model, msg, publish, options) => {
const result = {};
if (typeof msg.data.data['batteryVoltage'] == 'number') {
const battery = {max: 3200, min: 2500};
const voltage = msg.data.data['batteryVoltage'] * 100;
result.battery = toPercentage(voltage, battery.min, battery.max);
result.voltage = voltage;
}
if (typeof msg.data.data['batteryAlarmState'] == 'number') {
result.battery_alarm_state = msg.data.data['batteryAlarmState'];
}
return result;
},
},
bitron_thermostat_att_report: {
cid: 'hvacThermostat',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const result = {};
if (typeof msg.data.data['localTemp'] == 'number') {
result.local_temperature = precisionRound(msg.data.data['localTemp'], 2) / 100;
}
if (typeof msg.data.data['localTemperatureCalibration'] == 'number') {
result.local_temperature_calibration =
precisionRound(msg.data.data['localTemperatureCalibration'], 2) / 10;
}
if (typeof msg.data.data['occupiedHeatingSetpoint'] == 'number') {
result.occupied_heating_setpoint =
precisionRound(msg.data.data['occupiedHeatingSetpoint'], 2) / 100;
}
if (typeof msg.data.data['runningState'] == 'number') {
result.running_state = msg.data.data['runningState'];
}
if (typeof msg.data.data['batteryAlarmState'] == 'number') {
result.battery_alarm_state = msg.data.data['batteryAlarmState'];
}
return result;
},
},
bitron_thermostat_dev_change: {
cid: 'hvacThermostat',
type: 'devChange',
convert: (model, msg, publish, options) => {
const result = {};
if (typeof msg.data.data['localTemp'] == 'number') {
result.local_temperature = precisionRound(msg.data.data['localTemp'], 2) / 100;
}
if (typeof msg.data.data['localTemperatureCalibration'] == 'number') {
result.local_temperature_calibration =
precisionRound(msg.data.data['localTemperatureCalibration'], 2) / 10;
}
if (typeof msg.data.data['occupiedHeatingSetpoint'] == 'number') {
result.occupied_heating_setpoint =
precisionRound(msg.data.data['occupiedHeatingSetpoint'], 2) / 100;
}
if (typeof msg.data.data['runningState'] == 'number') {
result.running_state = msg.data.data['runningState'];
}
if (typeof msg.data.data['batteryAlarmState'] == 'number') {
result.battery_alarm_state = msg.data.data['batteryAlarmState'];
}
return result;
},
},
nue_click: {
cid: 'genScenes',
type: 'cmdRecall',
convert: (model, msg, publish, options) => {
return {click: msg.data.data.sceneid};
},
},
smartthings_contact: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
return {contact: msg.data.zoneStatus === 48};
},
},
xiaomi_battery_3v: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
let voltage = null;
if (msg.data.data['65281']) {
voltage = msg.data.data['65281']['1'];
} else if (msg.data.data['65282']) {
voltage = msg.data.data['65282']['1'].elmVal;
}
if (voltage) {
return {
battery: parseFloat(toPercentageCR2032(voltage)),
voltage: voltage,
};
}
},
},
RTCGQ11LM_interval: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data['65281']) {
return {illuminance: msg.data.data['65281']['11']};
}
},
},
WSDCGQ01LM_WSDCGQ11LM_interval: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data['65281']) {
const temperature = parseFloat(msg.data.data['65281']['100']) / 100.0;
const humidity = parseFloat(msg.data.data['65281']['101']) / 100.0;
const result = {};
// https://github.com/Koenkk/zigbee2mqtt/issues/798
// Sometimes the sensor publishes non-realistic vales, as the sensor only works from
// -20 till +60, don't produce messages beyond these values.
if (temperature > -25 && temperature < 65) {
result.temperature = precisionRoundOptions(temperature, options, 'temperature');
}
// in the 0 - 100 range, don't produce messages beyond these values.
if (humidity >= 0 && humidity <= 100) {
result.humidity = precisionRoundOptions(humidity, options, 'humidity');
}
// Check if contains pressure (WSDCGQ11LM only)
if (msg.data.data['65281'].hasOwnProperty('102')) {
const pressure = parseFloat(msg.data.data['65281']['102']) / 100.0;
result.pressure = precisionRoundOptions(pressure, options, 'pressure');
}
return result;
}
},
},
WXKG01LM_click: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const deviceID = msg.endpoints[0].device.ieeeAddr;
const state = msg.data.data['onOff'];
if (!store[deviceID]) {
store[deviceID] = {};
}
// 0 = click down, 1 = click up, else = multiple clicks
if (state === 0) {
store[deviceID].timer = setTimeout(() => {
publish({click: 'long'});
store[deviceID].timer = null;
store[deviceID].long = Date.now();
}, options.long_timeout || 1000); // After 1000 milliseconds of not releasing we assume long click.
} else if (state === 1) {
if (store[deviceID].long) {
const duration = Date.now() - store[deviceID].long;
publish({click: 'long_release', duration: duration});
store[deviceID].long = false;
}
if (store[deviceID].timer) {
clearTimeout(store[deviceID].timer);
store[deviceID].timer = null;
publish({click: 'single'});
}
} else {
const clicks = msg.data.data['32768'];
const payload = clickLookup[clicks] ? clickLookup[clicks] : 'many';
publish({click: payload});
}
},
},
generic_temperature: {
cid: 'msTemperatureMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const temperature = parseFloat(msg.data.data['measuredValue']) / 100.0;
const calTemperature = calibrateOptions(temperature, options, 'temperature');
return {temperature: precisionRoundOptions(calTemperature, options, 'temperature')};
},
},
generic_temperature_change: {
cid: 'msTemperatureMeasurement',
type: 'devChange',
convert: (model, msg, publish, options) => {
const temperature = parseFloat(msg.data.data['measuredValue']) / 100.0;
const calTemperature = calibrateOptions(temperature, options, 'temperature');
return {temperature: precisionRoundOptions(calTemperature, options, 'temperature')};
},
},
xiaomi_temperature: {
cid: 'msTemperatureMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const temperature = parseFloat(msg.data.data['measuredValue']) / 100.0;
// https://github.com/Koenkk/zigbee2mqtt/issues/798
// Sometimes the sensor publishes non-realistic vales, as the sensor only works from
// -20 till +60, don't produce messages beyond these values.
if (temperature > -25 && temperature < 65) {
return {temperature: precisionRoundOptions(temperature, options, 'temperature')};
}
},
},
MFKZQ01LM_action_multistate: {
cid: 'genMultistateInput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
/*
Source: https://github.com/kirovilya/ioBroker.zigbee
+---+
| 2 |
+---+---+---+
| 4 | 0 | 1 |
+---+---+---+
|M5I|
+---+
| 3 |
+---+
Side 5 is with the MI logo, side 3 contains the battery door.
presentValue = 0 = shake
presentValue = 2 = wakeup
presentValue = 3 = fly/fall
presentValue = y + x * 8 + 64 = 90º Flip from side x on top to side y on top
presentValue = x + 128 = 180º flip to side x on top
presentValue = x + 256 = push/slide cube while side x is on top
presentValue = x + 512 = double tap while side x is on top
*/
const value = msg.data.data['presentValue'];
let action = null;
if (value === 0) action = {'action': 'shake'};
else if (value === 2) action = {'action': 'wakeup'};
else if (value === 3) action = {'action': 'fall'};
else if (value >= 512) action = {'action': 'tap', 'side': value-512};
else if (value >= 256) action = {'action': 'slide', 'side': value-256};
else if (value >= 128) action = {'action': 'flip180', 'side': value-128};
else if (value >= 64) {
action = {'action': 'flip90', 'from_side': Math.floor((value-64) / 8), 'to_side': value % 8};
}
return action ? action : null;
},
},
MFKZQ01LM_action_analog: {
cid: 'genAnalogInput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
/*
Source: https://github.com/kirovilya/ioBroker.zigbee
presentValue = rotation angle left < 0, right > 0
*/
const value = msg.data.data['presentValue'];
return {
action: value < 0 ? 'rotate_left' : 'rotate_right',
angle: Math.floor(value * 100) / 100,
};
},
},
WXKG12LM_action_click_multistate: {
cid: 'genMultistateInput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const value = msg.data.data['presentValue'];
const lookup = {
1: {click: 'single'}, // single click
2: {click: 'double'}, // double click
16: {action: 'hold'}, // hold for more than 400ms
17: {action: 'release'}, // release after hold for more than 400ms
18: {action: 'shake'}, // shake
};
return lookup[value] ? lookup[value] : null;
},
},
xiaomi_action_click_multistate: {
cid: 'genMultistateInput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const value = msg.data.data['presentValue'];
const lookup = {
1: {click: 'single'}, // single click
2: {click: 'double'}, // double click
0: {action: 'hold'}, // hold for more than 400ms
255: {action: 'release'}, // release after hold for more than 400ms
};
return lookup[value] ? lookup[value] : null;
},
},
generic_humidity: {
cid: 'msRelativeHumidity',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const humidity = parseFloat(msg.data.data['measuredValue']) / 100.0;
// https://github.com/Koenkk/zigbee2mqtt/issues/798
// Sometimes the sensor publishes non-realistic vales, it should only publish message
// in the 0 - 100 range, don't produce messages beyond these values.
if (humidity >= 0 && humidity <= 100) {
return {humidity: precisionRoundOptions(humidity, options, 'humidity')};
}
},
},
generic_occupancy: {
// This is for occupancy sensor that send motion start AND stop messages
// Note: options.occupancy_timeout not available yet, to implement it will be
// needed to update device report intervall as well, see devices.js
cid: 'msOccupancySensing',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data.occupancy === 0) {
return {occupancy: false};
} else if (msg.data.data.occupancy === 1) {
return {occupancy: true};
}
},
},
E1525_occupancy: {
cid: 'genOnOff',
type: 'cmdOnWithTimedOff',
convert: (model, msg, publish, options) => {
const timeout = msg.data.data.ontime / 10;
const deviceID = msg.endpoints[0].device.ieeeAddr;
// Stop existing timer because motion is detected and set a new one.
if (store[deviceID]) {
clearTimeout(store[deviceID]);
store[deviceID] = null;
}
if (timeout !== 0) {
store[deviceID] = setTimeout(() => {
publish({occupancy: false});
store[deviceID] = null;
}, timeout * 1000);
}
return {occupancy: true};
},
},
generic_occupancy_no_off_msg: {
// This is for occupancy sensor that only send a message when motion detected,
// but do not send a motion stop.
// Therefore we need to publish the no_motion detected by ourselves.
cid: 'msOccupancySensing',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data.occupancy !== 1) {
// In case of 0 no occupancy is reported.
// https://github.com/Koenkk/zigbee2mqtt/issues/467
return;
}
// The occupancy sensor only sends a message when motion detected.
// Therefore we need to publish the no_motion detected by ourselves.
const useOptionsTimeout = options && options.hasOwnProperty('occupancy_timeout');
const timeout = useOptionsTimeout ? options.occupancy_timeout : occupancyTimeout;
const deviceID = msg.endpoints[0].device.ieeeAddr;
// Stop existing timers because motion is detected and set a new one.
if (store[deviceID]) {
store[deviceID].forEach((t) => clearTimeout(t));
}
store[deviceID] = [];
if (timeout !== 0) {
const timer = setTimeout(() => {
publish({occupancy: false});
}, timeout * 1000);
store[deviceID].push(timer);
}
// No occupancy since
if (options && options.no_occupancy_since) {
options.no_occupancy_since.forEach((since) => {
const timer = setTimeout(() => {
publish({no_occupancy_since: since});
}, since * 1000);
store[deviceID].push(timer);
});
}
if (options && options.no_occupancy_since) {
return {occupancy: true, no_occupancy_since: 0};
} else {
return {occupancy: true};
}
},
},
xiaomi_contact: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
return {contact: msg.data.data['onOff'] === 0};
},
},
xiaomi_contact_interval: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('65281') && msg.data.data['65281'].hasOwnProperty('100')) {
return {contact: msg.data.data['65281']['100'] === 0};
}
},
},
brightness: {
cid: 'genLevelCtrl',
type: 'devChange',
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('currentLevel')) {
return {brightness: msg.data.data['currentLevel']};
}
},
},
color_colortemp: {
cid: 'lightingColorCtrl',
type: 'devChange',
convert: (model, msg, publish, options) => {
const result = {};
if (msg.data.data['colorTemperature']) {
result.color_temp = msg.data.data['colorTemperature'];
}
if (msg.data.data['colorMode']) {
result.color_mode = msg.data.data['colorMode'];
}
if (
msg.data.data['currentX']
|| msg.data.data['currentY']
|| msg.data.data['currentSaturation']
|| msg.data.data['enhancedCurrentHue']
) {
result.color = {};
if (msg.data.data['currentX']) {
result.color.x = precisionRound(msg.data.data['currentX'] / 65535, 4);
}
if (msg.data.data['currentY']) {
result.color.y = precisionRound(msg.data.data['currentY'] / 65535, 4);
}
if (msg.data.data['currentSaturation']) {
result.color.saturation = precisionRound(msg.data.data['currentSaturation'] / 2.54, 1);
}
if (msg.data.data['enhancedCurrentHue']) {
result.color.hue = precisionRound(msg.data.data['enhancedCurrentHue'] / (65535 / 360), 1);
}
}
return result;
},
},
color_colortemp_report: {
cid: 'lightingColorCtrl',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const result = {};
if (msg.data.data['colorTemperature']) {
result.color_temp = msg.data.data['colorTemperature'];
}
if (msg.data.data['colorMode']) {
result.color_mode = msg.data.data['colorMode'];
}
if (
msg.data.data['currentX']
|| msg.data.data['currentY']
|| msg.data.data['currentSaturation']
|| msg.data.data['enhancedCurrentHue']
) {
result.color = {};
if (msg.data.data['currentX']) {
result.color.x = precisionRound(msg.data.data['currentX'] / 65535, 4);
}
if (msg.data.data['currentY']) {
result.color.y = precisionRound(msg.data.data['currentY'] / 65535, 4);
}
if (msg.data.data['currentSaturation']) {
result.color.saturation = precisionRound(msg.data.data['currentSaturation'] / 2.54, 1);
}
if (msg.data.data['enhancedCurrentHue']) {
result.color.hue = precisionRound(msg.data.data['enhancedCurrentHue'] / (65535 / 360), 1);
}
}
return result;
},
},
WXKG11LM_click: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const data = msg.data.data;
let clicks;
if (data.onOff) {
clicks = 1;
} else if (data['32768']) {
clicks = data['32768'];
}
if (clickLookup[clicks]) {
return {click: clickLookup[clicks]};
}
},
},
generic_illuminance: {
cid: 'msIlluminanceMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const illuminance = msg.data.data['measuredValue'];
const calIlluminance = calibrateOptions(illuminance, options, 'illuminance');
// calibration value in +/- percent!
return {illuminance: calIlluminance};
},
},
generic_pressure: {
cid: 'msPressureMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const pressure = parseFloat(msg.data.data['measuredValue']);
const calPressure = calibrateOptions(pressure, options, 'pressure');
return {pressure: precisionRoundOptions(calPressure, options, 'pressure')};
},
},
WXKG02LM_click: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const ep = msg.endpoints[0];
return {click: getKey(model.ep(ep.device), ep.epId)};
},
},
WXKG02LM_click_multistate: {
cid: 'genMultistateInput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const ep = msg.endpoints[0];
const button = getKey(model.ep(ep.device), ep.epId);
const value = msg.data.data['presentValue'];
const actionLookup = {
0: 'long',
1: null,
2: 'double',
};
const action = actionLookup[value];
if (button) {
return {click: button + (action ? `_${action}` : '')};
}
},
},
WXKG03LM_click: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
return {click: 'single'};
},
},
KEF1PA_arm: {
cid: 'ssIasAce',
type: 'cmdArm',
convert: (model, msg, publish, options) => {
const action = msg.data.data['armmode'];
delete msg.data.data['armmode'];
const modeLookup = {
0: 'home',
2: 'sleep',
3: 'away',
};
return {action: modeLookup[action]};
},
},
KEF1PA_panic: {
cid: 'ssIasAce',
type: 'cmdPanic',
convert: (model, msg, publish, options) => {
delete msg.data.data['armmode'];
return {action: 'panic'};
},
},
SJCGQ11LM_water_leak_iaszone: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
return {water_leak: msg.data.zoneStatus === 1};
},
},
SJCGQ11LM_water_leak_interval: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('65281') && msg.data.data['65281'].hasOwnProperty('100')) {
return {water_leak: msg.data.data['65281']['100'] === 1};
}
},
},
state: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('onOff')) {
return {state: msg.data.data['onOff'] === 1 ? 'ON' : 'OFF'};
}
},
},
state_report: {
cid: 'genOnOff',
type: 'attReport',
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('onOff')) {
return {state: msg.data.data['onOff'] === 1 ? 'ON' : 'OFF'};
}
},
},
state_change: {
cid: 'genOnOff',
type: 'devChange',
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('onOff')) {
return {state: msg.data.data['onOff'] === 1 ? 'ON' : 'OFF'};
}
},
},
xiaomi_power: {
cid: 'genAnalogInput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
return {power: precisionRound(msg.data.data['presentValue'], 2)};
},
},
xiaomi_plug_state: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data['65281']) {
const data = msg.data.data['65281'];
return {
state: data['100'] === 1 ? 'ON' : 'OFF',
power: precisionRound(data['152'], 2),
voltage: precisionRound(data['150'] * 0.1, 1),
consumption: precisionRound(data['149'], 2),
temperature: precisionRoundOptions(data['3'], options, 'temperature'),
};
}
},
},
xiaomi_bulb_interval: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data['65281']) {
const data = msg.data.data['65281'];
return {
state: data['100'] === 1 ? 'ON' : 'OFF',
brightness: data['101'],
color_temp: data['102'],
};
}
},
},
QBKG11LM_power: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data['65281']) {
const data = msg.data.data['65281'];
return {
power: precisionRound(data['152'], 2),
consumption: precisionRound(data['149'], 2),
temperature: precisionRoundOptions(data['3'], options, 'temperature'),
};
}
},
},
QBKG12LM_LLKZMK11LM_power: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data['65281']) {
const data = msg.data.data['65281'];
return {
power: precisionRound(data['152'], 2),
consumption: precisionRound(data['149'], 2),
temperature: precisionRoundOptions(data['3'], options, 'temperature'),
};
}
},
},
QBKG04LM_QBKG11LM_state: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data['61440']) {
return {state: msg.data.data['onOff'] === 1 ? 'ON' : 'OFF'};
} else {
return {click: 'single'};
}
},
},
QBKG04LM_buttons: {
cid: 'genOnOff',
type: 'devChange',
convert: (model, msg, publish, options) => {
if (msg.endpoints[0].epId == 4) {
return {action: msg.data.data['onOff'] === 1 ? 'release' : 'hold'};
}
},
},
QBKG04LM_QBKG11LM_operation_mode: {
cid: 'genBasic',
type: 'devChange',
convert: (model, msg, publish, options) => {
const mappingMode = {
0x12: 'control_relay',
0xFE: 'decoupled',
};
const key = '65314';
if (msg.data.data.hasOwnProperty(key)) {
const mode = mappingMode[msg.data.data[key]];
return {operation_mode: mode};
}
},
},
QBKG03LM_QBKG12LM_LLKZMK11LM_state: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data['61440']) {
const ep = msg.endpoints[0];
const key = `state_${getKey(model.ep(ep.device), ep.epId)}`;
const payload = {};
payload[key] = msg.data.data['onOff'] === 1 ? 'ON' : 'OFF';
return payload;
} else {
const mapping = {4: 'left', 5: 'right', 6: 'both'};
const button = mapping[msg.endpoints[0].epId];
return {click: button};
}
},
},
QBKG11LM_click: {
cid: 'genMultistateInput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if ([1, 2].includes(msg.data.data.presentValue)) {
const times = {1: 'single', 2: 'double'};
return {click: times[msg.data.data.presentValue]};
}
},
},
QBKG12LM_click: {
cid: 'genMultistateInput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if ([1, 2].includes(msg.data.data.presentValue)) {
const mapping = {5: 'left', 6: 'right', 7: 'both'};
const times = {1: 'single', 2: 'double'};
const button = mapping[msg.endpoints[0].epId];
return {click: `${button}_${times[msg.data.data.presentValue]}`};
}
},
},
QBKG03LM_buttons: {
cid: 'genOnOff',
type: 'devChange',
convert: (model, msg, publish, options) => {
const mapping = {4: 'left', 5: 'right'};
const button = mapping[msg.endpoints[0].epId];
if (button) {
const payload = {};
payload[`button_${button}`] = msg.data.data['onOff'] === 1 ? 'release' : 'hold';
return payload;
}
},
},
QBKG03LM_QBKG12LM_operation_mode: {
cid: 'genBasic',
type: 'devChange',
convert: (model, msg, publish, options) => {
const mappingButton = {
'65314': 'left',
'65315': 'right',
};
const mappingMode = {
0x12: 'control_left_relay',
0x22: 'control_right_relay',
0xFE: 'decoupled',
};
for (const key in mappingButton) {
if (msg.data.data.hasOwnProperty(key)) {
const payload = {};
const mode = mappingMode[msg.data.data[key]];
payload[`operation_mode_${mappingButton[key]}`] = mode;
return payload;
}
}
},
},
xiaomi_lock_report: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data['65328']) {
const data = msg.data.data['65328'];
const state = data.substr(2, 2);
const action = data.substr(4, 2);
const keynum = data.substr(6, 2);
if (state == 11) {
if (action == 1) {
// unknown key
return {keyerror: true, inserted: 'unknown'};
}
if (action == 3) {
// explicitly disabled key (i.e. reported lost)
return {keyerror: true, inserted: keynum};
}
if (action == 7) {
// strange object introduced into the cylinder (e.g. a lock pick)
return {keyerror: true, inserted: 'strange'};
}
}
if (state == 12) {
if (action == 1) {
return {inserted: keynum};
}
if (action == 11) {
return {forgotten: keynum};
}
}
}
},
},
ZNCLDJ11LM_curtain_genAnalogOutput_change: {
cid: 'genAnalogOutput',
type: 'devChange',
convert: (model, msg, publish, options) => {
let running = false;
if (msg.data.data['61440']) {
running = msg.data.data['61440'] !== 0;
}
const position = precisionRound(msg.data.data['presentValue'], 2);
return {position: position, running: running};
},
},
ZNCLDJ11LM_curtain_genAnalogOutput_report: {
cid: 'genAnalogOutput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
let running = false;
if (msg.data.data['61440']) {
running = msg.data.data['61440'] !== 0;
}
const position = precisionRound(msg.data.data['presentValue'], 2);
return {position: position, running: running};
},
},
JTYJGD01LMBW_smoke: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
return {smoke: msg.data.zoneStatus === 1};
},
},
heiman_pir: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
occupancy: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Motion detection
tamper: (zoneStatus & 1<<2) > 0, // Bit 3 = Tamper status
battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator
};
},
},
heiman_smoke: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
smoke: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Smoke
battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator
};
},
},
heiman_smart_controller_armmode: {
cid: 'ssIasAce',
type: 'cmdArm',
convert: (model, msg, publish, options) => {
if (msg.data.data.armmode != null) {
const lookup = {
0: 'disarm',
1: 'arm_partial_zones',
3: 'arm_all_zones',
};
const value = msg.data.data.armmode;
return {action: lookup[value] || `armmode_${value}`};
}
},
},
heiman_smart_controller_emergency: {
cid: 'ssIasAce',
type: 'cmdEmergency',
convert: (model, msg, publish, options) => {
return {action: 'emergency'};
},
},
battery_200: {
cid: 'genPowerCfg',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const batt = msg.data.data.batteryPercentageRemaining;
const battLow = msg.data.data.batteryAlarmState;
const voltage = msg.data.data.batteryVoltage;
const results = {};
if (batt != null) {
const value = Math.round(batt/200.0*10000)/100; // Out of 200
results['battery'] = value;
}
if (battLow != null) {
if (battLow) {
results['battery_low'] = true;
} else {
results['battery_low'] = false;
}
}
if (voltage != null) {
results['voltage'] = voltage * 100;
}
return results;
},
},
heiman_smoke_enrolled: {
cid: 'ssIasZone',
type: 'devChange',
convert: (model, msg, publish, options) => {
const zoneId = msg.data.data.zoneId;
const zoneState = msg.data.data.zoneState;
const results = {};
if (zoneState) {
results['enrolled'] = true;
} else {
results['enrolled'] = false;
}
results['zone_id'] = zoneId;
return results;
},
},
heiman_gas: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
gas: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Gas
battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator
};
},
},
heiman_water_leak: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
water_leak: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Water leak
tamper: (zoneStatus & 1<<2) > 0, // Bit 3 = Tamper status
battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator
};
},
},
heiman_contact: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
contact: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Contact detection
tamper: (zoneStatus & 1<<2) > 0, // Bit 3 = Tamper status
battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator
};
},
},
heiman_carbon_monoxide: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
carbon_monoxide: (zoneStatus & 1) > 0, // Bit 1 = Alarm: Carbon monoxide
battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator
};
},
},
JTQJBF01LMBW_gas: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
return {gas: msg.data.zoneStatus === 1};
},
},
JTQJBF01LMBW_gas_density: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const data = msg.data.data;
if (data && data['65281']) {
const basicAttrs = data['65281'];
if (basicAttrs.hasOwnProperty('100')) {
return {gas_density: basicAttrs['100']};
}
}
},
},
JTQJBF01LMBW_sensitivity: {
cid: 'ssIasZone',
type: 'devChange',
convert: (model, msg, publish, options) => {
const data = msg.data.data;
const lookup = {
'1': 'low',
'2': 'medium',
'3': 'high',
};
if (data && data.hasOwnProperty('65520')) {
const value = data['65520'];
if (value && value.startsWith('0x020')) {
return {
sensitivity: lookup[value.charAt(5)],
};
}
}
},
},
DJT11LM_vibration: {
cid: 'closuresDoorLock',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const result = {};
const vibrationLookup = {
1: 'vibration',
2: 'tilt',
3: 'drop',
};
if (msg.data.data['85']) {
const data = msg.data.data['85'];
result.action = vibrationLookup[data];
}
if (msg.data.data['1283']) {
const data = msg.data.data['1283'];
result.angle = data;
}
if (msg.data.data['1288']) {
const data = msg.data.data['1288'];
// array interpretation:
// 12 bit two's complement sign extended integer
// data[1][bit0..bit15] : x
// data[1][bit16..bit31]: y
// data[0][bit0..bit15] : z
// left shift first to preserve sign extension for 'x'
const x = ((data['1'] << 16) >> 16);
const y = (data['1'] >> 16);
// left shift first to preserve sign extension for 'z'
const z = ((data['0'] << 16) >> 16);
// calculate angle
result.angle_x = Math.round(Math.atan(x/Math.sqrt(y*y+z*z)) * 180 / Math.PI);
result.angle_y = Math.round(Math.atan(y/Math.sqrt(x*x+z*z)) * 180 / Math.PI);
result.angle_z = Math.round(Math.atan(z/Math.sqrt(x*x+y*y)) * 180 / Math.PI);
// calculate absolulte angle
const R = Math.sqrt(x * x + y * y + z * z);
result.angle_x_absolute = Math.round((Math.acos(x / R)) * 180 / Math.PI);
result.angle_y_absolute = Math.round((Math.acos(y / R)) * 180 / Math.PI);
}
return result;
},
},
generic_power: {
cid: 'seMetering',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const result = {};
const endpoint = msg.endpoints[0];
let factor = null;
if (endpoint.clusters.has('seMetering')) {
const attrs = endpoint.clusters['seMetering'].attrs;
if (attrs.multiplier && attrs.divisor) {
factor = attrs.multiplier / attrs.divisor;
}
}
if (msg.data.data.hasOwnProperty('instantaneousDemand')) {
let power = msg.data.data['instantaneousDemand'];
if (factor != null) {
power = (power * factor) * 1000; // kWh to Watt
}
result.power = precisionRound(power, 2);
}
if (factor != null && (msg.data.data.hasOwnProperty('currentSummDelivered') ||
msg.data.data.hasOwnProperty('currentSummReceived'))) {
result.energy = 0;
if (msg.data.data.hasOwnProperty('currentSummDelivered')) {
const data = msg.data.data['currentSummDelivered'];
const value = (parseInt(data[0]) << 32) + parseInt(data[1]);
result.energy += value * factor;
}
if (msg.data.data.hasOwnProperty('currentSummReceived')) {
const data = msg.data.data['currentSummReceived'];
const value = (parseInt(data[0]) << 32) + parseInt(data[1]);
result.energy -= value * factor;
}
}
return result;
},
},
CC2530ROUTER_state: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
return {state: true, led_state: msg.data.data['onOff'] === 1};
},
},
CC2530ROUTER_meta: {
cid: 'genBinaryValue',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const data = msg.data.data;
return {
description: data['description'],
type: data['inactiveText'],
rssi: data['presentValue'],
};
},
},
DNCKAT_S00X_state: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const ep = msg.endpoints[0];
const key = `state_${getKey(model.ep(ep.device), ep.epId)}`;
const payload = {};
payload[key] = msg.data.data['onOff'] === 1 ? 'ON' : 'OFF';
return payload;
},
},
DNCKAT_S00X_buttons: {
cid: 'genOnOff',
type: 'devChange',
convert: (model, msg, publish, options) => {
const ep = msg.endpoints[0];
const key = `button_${getKey(model.ep(ep.device), ep.epId)}`;
const payload = {};
payload[key] = msg.data.data['onOff'] === 1 ? 'release' : 'hold';
return payload;
},
},
ZigUP_parse: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const lookup = {
'0': 'timer',
'1': 'key',
'2': 'dig-in',
};
let ds18b20Id = null;
let ds18b20Value = null;
if (msg.data.data['41368']) {
ds18b20Id = msg.data.data['41368'].split(':')[0];
ds18b20Value = precisionRound(msg.data.data['41368'].split(':')[1], 2);
}
return {
state: msg.data.data['onOff'] === 1 ? 'ON' : 'OFF',
cpu_temperature: precisionRound(msg.data.data['41361'], 2),
external_temperature: precisionRound(msg.data.data['41362'], 1),
external_humidity: precisionRound(msg.data.data['41363'], 1),
s0_counts: msg.data.data['41364'],
adc_volt: precisionRound(msg.data.data['41365'], 3),
dig_input: msg.data.data['41366'],
reason: lookup[msg.data.data['41367']],
[`${ds18b20Id}`]: ds18b20Value,
};
},
},
Z809A_power: {
cid: 'haElectricalMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
return {
power: msg.data.data['activePower'],
current: msg.data.data['rmsCurrent'],
voltage: msg.data.data['rmsVoltage'],
power_factor: msg.data.data['powerFactor'],
};
},
},
SP120_power: {
cid: 'haElectricalMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const result = {};
if (msg.data.data.hasOwnProperty('activePower')) {
result.power = msg.data.data['activePower'];
}
if (msg.data.data.hasOwnProperty('rmsCurrent')) {
result.current = msg.data.data['rmsCurrent'] / 1000;
}
if (msg.data.data.hasOwnProperty('rmsVoltage')) {
result.voltage = msg.data.data['rmsVoltage'];
}
return result;
},
},
peanut_electrical: {
cid: 'haElectricalMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const result = {};
const deviceID = msg.endpoints[0].device.ieeeAddr;
// initialize stored defaults with observed values
if (!store[deviceID]) {
store[deviceID] = {
acVoltageMultiplier: 180, acVoltageDivisor: 39321, acCurrentMultiplier: 72,
acCurrentDivisor: 39321, acPowerMultiplier: 10255, acPowerDivisor: 39321,
};
}
// if new multipliers/divisors come in, replace prior values or defaults
Object.keys(store[deviceID]).forEach((key) => {
if (msg.data.data.hasOwnProperty(key)) {
store[deviceID][key] = msg.data.data[key];
}
});
// if raw measurement comes in, apply stored/default multiplier and divisor
if (msg.data.data.hasOwnProperty('rmsVoltage')) {
result.voltage = (msg.data.data['rmsVoltage']
* store[deviceID].acVoltageMultiplier
/ store[deviceID].acVoltageDivisor).toFixed(2);
}
if (msg.data.data.hasOwnProperty('rmsCurrent')) {
result.current = (msg.data.data['rmsCurrent']
* store[deviceID].acCurrentMultiplier
/ store[deviceID].acCurrentDivisor).toFixed(2);
}
if (msg.data.data.hasOwnProperty('activePower')) {
result.power = (msg.data.data['activePower']
* store[deviceID].acPowerMultiplier
/ store[deviceID].acPowerDivisor).toFixed(2);
}
return result;
},
},
STS_PRS_251_presence: {
cid: 'genBinaryInput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const useOptionsTimeout = options && options.hasOwnProperty('presence_timeout');
const timeout = useOptionsTimeout ? options.presence_timeout : 100; // 100 seconds by default
const deviceID = msg.endpoints[0].device.ieeeAddr;
// Stop existing timer because presence is detected and set a new one.
if (store.hasOwnProperty(deviceID)) {
clearTimeout(store[deviceID]);
store[deviceID] = null;
}
store[deviceID] = setTimeout(() => {
publish({presence: false});
store[deviceID] = null;
}, timeout * 1000);
return {presence: true};
},
},
generic_batteryvoltage_3000_2500: {
cid: 'genPowerCfg',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const battery = {max: 3000, min: 2500};
const voltage = msg.data.data['batteryVoltage'] * 100;
return {
battery: toPercentage(voltage, battery.min, battery.max),
voltage: voltage,
};
},
},
STS_PRS_251_beeping: {
cid: 'genIdentify',
type: 'devChange',
convert: (model, msg, publish, options) => {
return {action: 'beeping'};
},
},
_324131092621_notification: {
cid: 'manuSpecificPhilips',
type: 'cmdHueNotification',
convert: (model, msg, publish, options) => {
const multiplePressTimeout = options && options.hasOwnProperty('multiple_press_timeout') ?
options.multiple_press_timeout : 0.25;
const getPayload = function(button, pressType, pressDuration, pressCounter,
brightnessSend, brightnessValue) {
const payLoad = {};
payLoad['action'] = `${button}-${pressType}`;
payLoad['duration'] = pressDuration / 1000;
if (pressCounter) {
payLoad['counter'] = pressCounter;
}
if (brightnessSend) {
payLoad['brightness'] = store[deviceID].brightnessValue;
}
return payLoad;
};
const deviceID = msg.endpoints[0].device.ieeeAddr;
let button = null;
switch (msg.data.data['button']) {
case 1:
button = 'on';
break;
case 2:
button = 'up';
break;
case 3:
button = 'down';
break;
case 4:
button = 'off';
break;
}
let type = null;
switch (msg.data.data['type']) {
case 0:
type = 'press';
break;
case 1:
type = 'hold';
break;
case 2:
case 3:
type = 'release';
break;
}
const brightnessEnabled = options && options.hasOwnProperty('send_brightess') ?
options.send_brightess : true;
const brightnessSend = brightnessEnabled && button && (button == 'up' || button == 'down');
// Initialize store
if (!store[deviceID]) {
store[deviceID] = {pressStart: null, pressType: null,
delayedButton: null, delayedBrightnessSend: null, delayedType: null,
delayedCounter: 0, delayedTimerStart: null, delayedTimer: null};
if (brightnessEnabled) {
store[deviceID].brightnessValue = 255;
store[deviceID].brightnessSince = null;
store[deviceID].brightnessDirection = null;
}
}
if (button && type) {
if (type == 'press') {
store[deviceID].pressStart = Date.now();
store[deviceID].pressType = 'press';
if (brightnessSend) {
const newValue = store[deviceID].brightnessValue + (button === 'up' ? 50 : -50);
store[deviceID].brightnessValue = numberWithinRange(newValue, 0, 255);
}
} else if (type == 'hold') {
store[deviceID].pressType = 'hold';
if (brightnessSend) {
holdUpdateBrightness324131092621(deviceID);
store[deviceID].brightnessSince = Date.now();
store[deviceID].brightnessDirection = button;
}
} else if (type == 'release') {
if (brightnessSend) {
store[deviceID].brightnessSince = null;
store[deviceID].brightnessDirection = null;
}
if (store[deviceID].pressType == 'hold') {
store[deviceID].pressType += '-release';
}
}
if (type == 'press') {
// pressed different button
if (store[deviceID].delayedTimer && (store[deviceID].delayedButton != button)) {
clearTimeout(store[deviceID].delayedTimer);
store[deviceID].delayedTimer = null;
publish(getPayload(store[deviceID].delayedButton,
store[deviceID].delayedType, 0, store[deviceID].delayedCounter,
store[deviceID].delayedBrightnessSend,
store[deviceID].brightnessValue));
}
} else {
// released after press: start timer
if (store[deviceID].pressType == 'press') {
if (store[deviceID].delayedTimer) {
clearTimeout(store[deviceID].delayedTimer);
store[deviceID].delayedTimer = null;
} else {
store[deviceID].delayedCounter = 0;
}
store[deviceID].delayedButton = button;
store[deviceID].delayedBrightnessSend = brightnessSend;
store[deviceID].delayedType = store[deviceID].pressType;
store[deviceID].delayedCounter++;
store[deviceID].delayedTimerStart = Date.now();
store[deviceID].delayedTimer = setTimeout(() => {
publish(getPayload(store[deviceID].delayedButton,
store[deviceID].delayedType, 0, store[deviceID].delayedCounter,
store[deviceID].delayedBrightnessSend,
store[deviceID].brightnessValue));
store[deviceID].delayedTimer = null;
}, multiplePressTimeout * 1000);
} else {
const pressDuration =
(store[deviceID].pressType == 'hold' || store[deviceID].pressType == 'hold-release') ?
Date.now() - store[deviceID].pressStart : 0;
return getPayload(button,
store[deviceID].pressType, pressDuration, null, brightnessSend,
store[deviceID].brightnessValue);
}
}
}
return {};
},
},
generic_battery: {
cid: 'genPowerCfg',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('batteryPercentageRemaining')) {
return {battery: msg.data.data['batteryPercentageRemaining']};
}
},
},
generic_battery_change: {
cid: 'genPowerCfg',
type: 'devChange',
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('batteryPercentageRemaining')) {
return {battery: msg.data.data['batteryPercentageRemaining']};
}
},
},
generic_battery_remaining: {
cid: 'genPowerCfg',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('batteryPercentageRemaining')) {
return {battery: precisionRound(msg.data.data['batteryPercentageRemaining'] / 2, 2)};
}
},
},
generic_battery_voltage: {
cid: 'genPowerCfg',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('batteryVoltage')) {
return {voltage: msg.data.data['batteryVoltage'] / 100};
}
},
},
cmd_move: {
cid: 'genLevelCtrl',
type: 'cmdMove',
convert: (model, msg, publish, options) => {
ictcg1(model, msg, publish, options, 'move');
const direction = msg.data.data.movemode === 1 ? 'left' : 'right';
return {action: `rotate_${direction}`, rate: msg.data.data.rate};
},
},
cmd_move_with_onoff: {
cid: 'genLevelCtrl',
type: 'cmdMoveWithOnOff',
convert: (model, msg, publish, options) => {
ictcg1(model, msg, publish, options, 'move');
const direction = msg.data.data.movemode === 1 ? 'left' : 'right';
return {action: `rotate_${direction}`, rate: msg.data.data.rate};
},
},
cmd_stop: {
cid: 'genLevelCtrl',
type: 'cmdStop',
convert: (model, msg, publish, options) => {
ictcg1(model, msg, publish, options, 'stop');
return {action: `rotate_stop`};
},
},
cmd_stop_with_onoff: {
cid: 'genLevelCtrl',
type: 'cmdStopWithOnOff',
convert: (model, msg, publish, options) => {
ictcg1(model, msg, publish, options, 'stop');
return {action: `rotate_stop`};
},
},
cmd_move_to_level_with_onoff: {
cid: 'genLevelCtrl',
type: 'cmdMoveToLevelWithOnOff',
convert: (model, msg, publish, options) => {
ictcg1(model, msg, publish, options, 'level');
const direction = msg.data.data.level === 0 ? 'left' : 'right';
return {action: `rotate_${direction}_quick`, level: msg.data.data.level};
},
},
iris_3210L_power: {
cid: 'haElectricalMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
return {power: msg.data.data['activePower'] / 10.0};
},
},
iris_3320L_contact: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
return {contact: msg.data.zoneStatus === 36};
},
},
nue_power_state: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const ep = msg.endpoints[0];
const button = getKey(model.ep(ep.device), ep.epId);
if (button) {
const payload = {};
payload[`state_${button}`] = msg.data.data['onOff'] === 1 ? 'ON' : 'OFF';
return payload;
}
},
},
generic_state_multi_ep: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const ep = msg.endpoints[0];
const key = `state_${getKey(model.ep(ep.device), ep.epId)}`;
const payload = {};
payload[key] = msg.data.data['onOff'] === 1 ? 'ON' : 'OFF';
return payload;
},
},
RZHAC_4256251_power: {
cid: 'haElectricalMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
return {
power: msg.data.data['activePower'],
current: msg.data.data['rmsCurrent'],
voltage: msg.data.data['rmsVoltage'],
};
},
},
ias_zone_motion_dev_change: {
cid: 'ssIasZone',
type: 'devChange',
convert: (model, msg, publish, options) => {
if (msg.data.data.zoneType === 0x000D) { // type 0x000D = motion sensor
const zoneStatus = msg.data.data.zoneStatus;
return {
occupancy: (zoneStatus & 1<<1) > 0, // Bit 1 = Alarm 2: Presence Indication
tamper: (zoneStatus & 1<<2) > 0, // Bit 2 = Tamper status
battery_low: (zoneStatus & 1<<3) > 0, // Bit 3 = Battery LOW indicator (trips around 2.4V)
};
}
},
},
ias_zone_motion_status_change: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
occupancy: (zoneStatus & 1<<1) > 0, // Bit 1 = Alarm 2: Presence Indication
tamper: (zoneStatus & 1<<2) > 0, // Bit 2 = Tamper status
battery_low: (zoneStatus & 1<<3) > 0, // Bit 3 = Battery LOW indicator (trips around 2.4V)
};
},
},
generic_ias_zone_occupancy_status_change: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
occupancy: (zoneStatus & 1) > 0, // Bit 0 = Alarm 1: Presence Indication
tamper: (zoneStatus & 1<<2) > 0, // Bit 2 = Tamper status
battery_low: (zoneStatus & 1<<3) > 0, // Bit 3 = Battery LOW indicator (trips around 2.4V)
};
},
},
generic_ias_zone_occupancy_status_change_no_off_msg: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
const useOptionsTimeout = options && options.hasOwnProperty('occupancy_timeout');
const timeout = useOptionsTimeout ? options.occupancy_timeout : occupancyTimeout;
const deviceID = msg.endpoints[0].device.ieeeAddr;
if (store[deviceID]) {
clearTimeout(store[deviceID]);
store[deviceID] = null;
}
if (timeout !== 0) {
store[deviceID] = setTimeout(() => {
publish({occupancy: false});
store[deviceID] = null;
}, timeout * 1000);
}
return {
occupancy: (zoneStatus & 1) > 0, // Bit 0 = Alarm 1: Presence Indication
tamper: (zoneStatus & 1<<2) > 0, // Bit 2 = Tamper status
battery_low: (zoneStatus & 1<<3) > 0, // Bit 3 = Battery LOW indicator (trips around 2.4V)
};
},
},
generic_ias_zone_motion_dev_change: {
cid: 'ssIasZone',
type: 'devChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
occupancy: (zoneStatus & 1) > 0, // Bit 0 = Alarm 1: Presence Indication
tamper: (zoneStatus & 1<<2) > 0, // Bit 2 = Tamper status
battery_low: (zoneStatus & 1<<3) > 0, // Bit 3 = Battery LOW indicator (trips around 2.4V)
};
},
},
ias_contact_dev_change: {
cid: 'ssIasZone',
type: 'devChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
contact: !((zoneStatus & 1) > 0),
};
},
},
ias_contact_status_change: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
contact: !((zoneStatus & 1) > 0),
};
},
},
brightness_report: {
cid: 'genLevelCtrl',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('currentLevel')) {
return {brightness: msg.data.data['currentLevel']};
}
},
},
smartsense_multi: {
cid: 'ssIasZone',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.data.zoneStatus;
return {
contact: !(zoneStatus & 1), // Bit 1 = Contact
// Bit 5 = Currently always set?
};
},
},
st_leak: {
cid: 'ssIasZone',
type: 'attReport',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.data.zoneStatus;
return {
water_leak: (zoneStatus & 1) > 0, // Bit 1 = wet
};
},
},
st_leak_change: {
cid: 'ssIasZone',
type: 'devChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.data.zoneStatus;
return {
water_leak: (zoneStatus & 1) > 0, // Bit 1 = wet
};
},
},
st_contact_status_change: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
contact: !((zoneStatus & 1) > 0), // Bit 0 = Alarm: Contact detection
battery_low: (zoneStatus & 1<<3) > 0, // Bit 3 = Battery LOW indicator
};
},
},
st_button_state: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const buttonStates = {
0: 'off',
1: 'single',
2: 'double',
3: 'hold',
};
if (msg.data.hasOwnProperty('data')) {
const zoneStatus = msg.data.data.zoneStatus;
return {click: buttonStates[zoneStatus]};
} else {
const zoneStatus = msg.data.zoneStatus;
return {click: buttonStates[zoneStatus]};
}
},
},
CTR_U_brightness_updown_click: {
cid: 'genLevelCtrl',
type: 'cmdStep',
convert: (model, msg, publish, options) => {
const deviceID = msg.endpoints[0].device.ieeeAddr;
const direction = msg.data.data.stepmode === 1 ? 'down' : 'up';
// Save last direction for release event
if (!store[deviceID]) {
store[deviceID] = {};
}
store[deviceID].direction = direction;
return {
action: `brightness_${direction}_click`,
step_size: msg.data.data.stepsize,
transition_time: msg.data.data.transtime,
};
},
},
CTR_U_brightness_updown_hold: {
cid: 'genLevelCtrl',
type: 'cmdMove',
convert: (model, msg, publish, options) => {
const deviceID = msg.endpoints[0].device.ieeeAddr;
const direction = msg.data.data.movemode === 1 ? 'down' : 'up';
// Save last direction for release event
if (!store[deviceID]) {
store[deviceID] = {};
}
store[deviceID].direction = direction;
return {
action: `brightness_${direction}_hold`,
rate: msg.data.data.rate,
};
},
},
CTR_U_brightness_updown_release: {
cid: 'genLevelCtrl',
type: 'cmdStop',
convert: (model, msg, publish, options) => {
const deviceID = msg.endpoints[0].device.ieeeAddr;
if (!store[deviceID]) {
return null;
}
const direction = store[deviceID].direction;
return {
action: `brightness_${direction}_release`,
};
},
},
CTR_U_scene: {
cid: 'genScenes',
type: 'cmdRecall',
convert: (model, msg, publish, options) => {
return {click: `scene_${msg.data.data.groupid}_${msg.data.data.sceneid}`};
},
},
thermostat_dev_change: {
cid: 'hvacThermostat',
type: 'devChange',
convert: (model, msg, publish, options) => {
const result = {};
if (typeof msg.data.data['localTemp'] == 'number') {
result.local_temperature = precisionRound(msg.data.data['localTemp'], 2) / 100;
}
if (typeof msg.data.data['localTemperatureCalibration'] == 'number') {
result.local_temperature_calibration =
precisionRound(msg.data.data['localTemperatureCalibration'], 2) / 10;
}
if (typeof msg.data.data['occupancy'] == 'number') {
result.occupancy = msg.data.data['occupancy'];
}
if (typeof msg.data.data['occupiedHeatingSetpoint'] == 'number') {
result.occupied_heating_setpoint =
precisionRound(msg.data.data['occupiedHeatingSetpoint'], 2) / 100;
}
if (typeof msg.data.data['unoccupiedHeatingSetpoint'] == 'number') {
result.unoccupied_heating_setpoint =
precisionRound(msg.data.data['unoccupiedHeatingSetpoint'], 2) / 100;
}
if (typeof msg.data.data['occupiedCoolingSetpoint'] == 'number') {
result.occupied_cooling_setpoint =
precisionRound(msg.data.data['occupiedCoolingSetpoint'], 2) / 100;
}
if (typeof msg.data.data['weeklySchedule'] == 'number') {
result.weekly_schedule = msg.data.data['weeklySchedule'];
}
if (typeof msg.data.data['setpointChangeAmount'] == 'number') {
result.setpoint_change_amount = msg.data.data['setpointChangeAmount'] / 100;
}
if (typeof msg.data.data['setpointChangeSource'] == 'number') {
result.setpoint_change_source = msg.data.data['setpointChangeSource'];
}
if (typeof msg.data.data['setpointChangeSourceTimeStamp'] == 'number') {
result.setpoint_change_source_timestamp = msg.data.data['setpointChangeSourceTimeStamp'];
}
if (typeof msg.data.data['remoteSensing'] == 'number') {
result.remote_sensing = msg.data.data['remoteSensing'];
}
const ctrl = msg.data.data['ctrlSeqeOfOper'];
if (typeof ctrl == 'number' && common.thermostatControlSequenceOfOperations.hasOwnProperty(ctrl)) {
result.control_sequence_of_operation = common.thermostatControlSequenceOfOperations[ctrl];
}
const smode = msg.data.data['systemMode'];
if (typeof smode == 'number' && common.thermostatSystemModes.hasOwnProperty(smode)) {
result.system_mode = common.thermostatSystemModes[smode];
}
const rmode = msg.data.data['runningMode'];
if (typeof rmode == 'number' && common.thermostatSystemModes.hasOwnProperty(rmode)) {
result.running_mode = common.thermostatSystemModes[rmode];
}
const state = msg.data.data['runningState'];
if (typeof state == 'number' && common.thermostatRunningStates.hasOwnProperty(state)) {
result.running_state = common.thermostatRunningStates[state];
}
if (typeof msg.data.data['pIHeatingDemand'] == 'number') {
result.pi_heating_demand = msg.data.data['pIHeatingDemand'];
}
return result;
},
},
thermostat_att_report: {
cid: 'hvacThermostat',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const result = {};
if (typeof msg.data.data['localTemp'] == 'number') {
result.local_temperature = precisionRound(msg.data.data['localTemp'], 2) / 100;
}
if (typeof msg.data.data['localTemperatureCalibration'] == 'number') {
result.local_temperature_calibration =
precisionRound(msg.data.data['localTemperatureCalibration'], 2) / 10;
}
if (typeof msg.data.data['occupancy'] == 'number') {
result.occupancy = msg.data.data['occupancy'];
}
if (typeof msg.data.data['occupiedHeatingSetpoint'] == 'number') {
result.occupied_heating_setpoint =
precisionRound(msg.data.data['occupiedHeatingSetpoint'], 2) / 100;
}
if (typeof msg.data.data['unoccupiedHeatingSetpoint'] == 'number') {
result.unoccupied_heating_setpoint =
precisionRound(msg.data.data['unoccupiedHeatingSetpoint'], 2) / 100;
}
if (typeof msg.data.data['occupiedCoolingSetpoint'] == 'number') {
result.occupied_cooling_setpoint =
precisionRound(msg.data.data['occupiedCoolingSetpoint'], 2) / 100;
}
if (typeof msg.data.data['weeklySchedule'] == 'number') {
result.weekly_schedule = msg.data.data['weeklySchedule'];
}
if (typeof msg.data.data['setpointChangeAmount'] == 'number') {
result.setpoint_change_amount = msg.data.data['setpointChangeAmount'] / 100;
}
if (typeof msg.data.data['setpointChangeSource'] == 'number') {
result.setpoint_change_source = msg.data.data['setpointChangeSource'];
}
if (typeof msg.data.data['setpointChangeSourceTimeStamp'] == 'number') {
result.setpoint_change_source_timestamp = msg.data.data['setpointChangeSourceTimeStamp'];
}
if (typeof msg.data.data['remoteSensing'] == 'number') {
result.remote_sensing = msg.data.data['remoteSensing'];
}
const ctrl = msg.data.data['ctrlSeqeOfOper'];
if (typeof ctrl == 'number' && common.thermostatControlSequenceOfOperations.hasOwnProperty(ctrl)) {
result.control_sequence_of_operation = common.thermostatControlSequenceOfOperations[ctrl];
}
const smode = msg.data.data['systemMode'];
if (typeof smode == 'number' && common.thermostatSystemModes.hasOwnProperty(smode)) {
result.system_mode = common.thermostatSystemModes[smode];
}
const rmode = msg.data.data['runningMode'];
if (typeof rmode == 'number' && common.thermostatSystemModes.hasOwnProperty(rmode)) {
result.running_mode = common.thermostatSystemModes[rmode];
}
const state = msg.data.data['runningState'];
if (typeof state == 'number' && common.thermostatRunningStates.hasOwnProperty(state)) {
result.running_state = common.thermostatRunningStates[state];
}
if (typeof msg.data.data['pIHeatingDemand'] == 'number') {
result.pi_heating_demand = msg.data.data['pIHeatingDemand'];
}
return result;
},
},
eurotronic_thermostat_dev_change: {
cid: 'hvacThermostat',
type: 'devChange',
convert: (model, msg, publish, options) => {
const result = {};
if (typeof msg.data.data[0x4003] == 'number') {
result.current_heating_setpoint =
precisionRound(msg.data.data[0x4003], 2) / 100;
}
if (typeof msg.data.data[0x4008] == 'number') {
result.eurotronic_system_mode = msg.data.data[0x4008];
}
if (typeof msg.data.data[0x4002] == 'number') {
result.eurotronic_error_status = msg.data.data[0x4002];
}
if (typeof msg.data.data[0x4000] == 'number') {
result.eurotronic_trv_mode = msg.data.data[0x4000];
}
if (typeof msg.data.data[0x4001] == 'number') {
result.eurotronic_valve_position = msg.data.data[0x4001];
}
return result;
},
},
tint404011_on: {
cid: 'genOnOff',
type: 'cmdOn',
convert: (model, msg, publish, options) => {
return {action: 'on'};
},
},
tint404011_off: {
cid: 'genOnOff',
type: 'cmdOff',
convert: (model, msg, publish, options) => {
return {action: 'off'};
},
},
tint404011_brightness_updown_click: {
cid: 'genLevelCtrl',
type: 'cmdStep',
convert: (model, msg, publish, options) => {
const direction = msg.data.data.stepmode === 1 ? 'down' : 'up';
return {
action: `brightness_${direction}_click`,
step_size: msg.data.data.stepsize,
transition_time: msg.data.data.transtime,
};
},
},
tint404011_brightness_updown_hold: {
cid: 'genLevelCtrl',
type: 'cmdMove',
convert: (model, msg, publish, options) => {
const deviceID = msg.endpoints[0].device.ieeeAddr;
const direction = msg.data.data.movemode === 1 ? 'down' : 'up';
// Save last direction for release event
if (!store[deviceID]) {
store[deviceID] = {};
}
store[deviceID].movemode = direction;
return {
action: `brightness_${direction}_hold`,
rate: msg.data.data.rate,
};
},
},
tint404011_brightness_updown_release: {
cid: 'genLevelCtrl',
type: 'cmdStop',
convert: (model, msg, publish, options) => {
const deviceID = msg.endpoints[0].device.ieeeAddr;
if (!store[deviceID]) {
return null;
}
const direction = store[deviceID].movemode;
return {
action: `brightness_${direction}_release`,
};
},
},
tint404011_scene: {
cid: 'genBasic',
type: 'cmdWrite',
convert: (model, msg, publish, options) => {
return {action: `scene${msg.data.data[0].attrData}`};
},
},
tint404011_move_to_color_temp: {
cid: 'lightingColorCtrl',
type: 'cmdMoveToColorTemp',
convert: (model, msg, publish, options) => {
return {
action: `color_temp`,
action_color_temperature: msg.data.data.colortemp,
transition_time: msg.data.data.transtime,
};
},
},
tint404011_move_to_color: {
cid: 'lightingColorCtrl',
type: 'cmdMoveToColor',
convert: (model, msg, publish, options) => {
return {
action_color: {
x: precisionRound(msg.data.data.colorx / 65535, 3),
y: precisionRound(msg.data.data.colory / 65535, 3),
},
action: 'color_wheel',
transition_time: msg.data.data.transtime,
};
},
},
cmdToggle: {
cid: 'genOnOff',
type: 'cmdToggle',
convert: (model, msg, publish, options) => {
return {action: 'toggle'};
},
},
E1524_hold: {
cid: 'genLevelCtrl',
type: 'cmdMoveToLevelWithOnOff',
convert: (model, msg, publish, options) => {
return {action: 'toggle_hold'};
},
},
E1524_arrow_click: {
cid: 'genScenes',
type: 'cmdTradfriArrowSingle',
convert: (model, msg, publish, options) => {
if (msg.data.data.value === 2) {
// This is send on toggle hold, ignore it as a toggle_hold is already handled above.
return;
}
const direction = msg.data.data.value === 257 ? 'left' : 'right';
return {action: `arrow_${direction}_click`};
},
},
E1524_arrow_hold: {
cid: 'genScenes',
type: 'cmdTradfriArrowHold',
convert: (model, msg, publish, options) => {
const direction = msg.data.data.value === 3329 ? 'left' : 'right';
store[msg.endpoints[0].device.ieeeAddr] = direction;
return {action: `arrow_${direction}_hold`};
},
},
E1524_arrow_release: {
cid: 'genScenes',
type: 'cmdTradfriArrowRelease',
convert: (model, msg, publish, options) => {
const direction = store[msg.endpoints[0].device.ieeeAddr];
if (direction) {
delete store[msg.endpoints[0].device.ieeeAddr];
return {action: `arrow_${direction}_release`, duration: msg.data.data.value / 1000};
}
},
},
E1524_brightness_up_click: {
cid: 'genLevelCtrl',
type: 'cmdStepWithOnOff',
convert: (model, msg, publish, options) => {
return {action: `brightness_up_click`};
},
},
E1524_brightness_down_click: {
cid: 'genLevelCtrl',
type: 'cmdStep',
convert: (model, msg, publish, options) => {
return {action: `brightness_down_click`};
},
},
E1524_brightness_up_hold: {
cid: 'genLevelCtrl',
type: 'cmdMoveWithOnOff',
convert: (model, msg, publish, options) => {
return {action: `brightness_up_hold`};
},
},
E1524_brightness_up_release: {
cid: 'genLevelCtrl',
type: 'cmdStopWithOnOff',
convert: (model, msg, publish, options) => {
return {action: `brightness_up_release`};
},
},
E1524_brightness_down_hold: {
cid: 'genLevelCtrl',
type: 'cmdMove',
convert: (model, msg, publish, options) => {
return {action: `brightness_down_hold`};
},
},
E1524_brightness_down_release: {
cid: 'genLevelCtrl',
type: 'cmdStop',
convert: (model, msg, publish, options) => {
return {action: `brightness_down_release`};
},
},
livolo_switch_dev_change: {
cid: 'genOnOff',
type: 'devChange',
convert: (model, msg, publish, options) => {
const status = msg.data.data.onOff;
const payload = {};
payload['state_left'] = status & 1 ? 'ON' : 'OFF';
payload['state_right'] = status & 2 ? 'ON' : 'OFF';
if (msg.endpoints[0].hasOwnProperty('linkquality')) {
payload['linkquality'] = msg.endpoints[0].linkquality;
}
return payload;
},
},
eria_81825_on: {
cid: 'genOnOff',
type: 'cmdOn',
convert: (model, msg, publish, options) => {
return {action: 'on'};
},
},
eria_81825_off: {
cid: 'genOnOff',
type: 'cmdOff',
convert: (model, msg, publish, options) => {
return {action: 'off'};
},
},
eria_81825_updown: {
cid: 'genLevelCtrl',
type: 'cmdStep',
convert: (model, msg, publish, options) => {
const direction = msg.data.data.stepmode === 0 ? 'up' : 'down';
return {action: `${direction}`};
},
},
ZYCT202_on: {
cid: 'genOnOff',
type: 'cmdOn',
convert: (model, msg, publish, options) => {
return {action: 'on', action_group: msg.groupid};
},
},
ZYCT202_off: {
cid: 'genOnOff',
type: 'cmdOffWithEffect',
convert: (model, msg, publish, options) => {
return {action: 'off', action_group: msg.groupid};
},
},
ZYCT202_stop: {
cid: 'genLevelCtrl',
type: 'cmdStop',
convert: (model, msg, publish, options) => {
return {action: 'stop', action_group: msg.groupid};
},
},
ZYCT202_up_down: {
cid: 'genLevelCtrl',
type: 'cmdMove',
convert: (model, msg, publish, options) => {
const value = msg.data.data['movemode'];
let action = null;
if (value === 0) action = {'action': 'up-press', 'action_group': msg.groupid};
else if (value === 1) action = {'action': 'down-press', 'action_group': msg.groupid};
return action ? action : null;
},
},
cover_position: {
cid: 'genLevelCtrl',
type: 'devChange',
convert: (model, msg, publish, options) => {
const currentLevel = msg.data.data['currentLevel'];
const position = Math.round(Number(currentLevel) / 2.55).toString();
const state = position > 0 ? 'OPEN' : 'CLOSE';
return {state: state, position: position};
},
},
cover_position_report: {
cid: 'genLevelCtrl',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const currentLevel = msg.data.data['currentLevel'];
const position = Math.round(Number(currentLevel) / 2.55).toString();
const state = position > 0 ? 'OPEN' : 'CLOSE';
return {state: state, position: position};
},
},
cover_state_report: {
cid: 'genOnOff',
type: 'attReport',
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('onOff')) {
return {state: msg.data.data['onOff'] === 1 ? 'OPEN' : 'CLOSE'};
}
},
},
cover_state_change: {
cid: 'genOnOff',
type: 'devChange',
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('onOff')) {
return {state: msg.data.data['onOff'] === 1 ? 'OPEN' : 'CLOSE'};
}
},
},
keen_home_smart_vent_pressure: {
cid: 'msPressureMeasurement',
type: 'devChange',
convert: (model, msg, publish, options) => {
// '{"cid":"msPressureMeasurement","data":{"32":990494}}'
const pressure = parseFloat(msg.data.data['32']) / 1000.0;
return {pressure: precisionRoundOptions(pressure, options, 'pressure')};
},
},
keen_home_smart_vent_pressure_report: {
cid: 'msPressureMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
// '{"cid":"msPressureMeasurement","data":{"32":990494}}'
const pressure = parseFloat(msg.data.data['32']) / 1000.0;
return {pressure: precisionRoundOptions(pressure, options, 'pressure')};
},
},
AC0251100NJ_cmdOn: {
cid: 'genOnOff',
type: 'cmdOn',
convert: (model, msg, publish, options) => {
return {action: 'up'};
},
},
AC0251100NJ_cmdOff: {
cid: 'genOnOff',
type: 'cmdOff',
convert: (model, msg, publish, options) => {
return {action: 'down'};
},
},
AC0251100NJ_cmdMoveWithOnOff: {
cid: 'genLevelCtrl',
type: 'cmdMoveWithOnOff',
convert: (model, msg, publish, options) => {
return {action: 'up_hold'};
},
},
AC0251100NJ_cmdStop: {
cid: 'genLevelCtrl',
type: 'cmdStop',
convert: (model, msg, publish, options) => {
const map = {
1: 'up_release',
2: 'down_release',
};
return {action: map[msg.endpoints[0].epId]};
},
},
AC0251100NJ_cmdMove: {
cid: 'genLevelCtrl',
type: 'cmdMove',
convert: (model, msg, publish, options) => {
return {action: 'down_hold'};
},
},
AC0251100NJ_cmdMoveHue: {
cid: 'lightingColorCtrl',
type: 'cmdMoveHue',
convert: (model, msg, publish, options) => {
if (msg.data.data.movemode === 0) {
return {action: 'circle_release'};
}
},
},
AC0251100NJ_cmdMoveToSaturation: {
cid: 'lightingColorCtrl',
type: 'cmdMoveToSaturation',
convert: (model, msg, publish, options) => {
return {action: 'circle_hold'};
},
},
AC0251100NJ_cmdMoveToLevelWithOnOff: {
cid: 'genLevelCtrl',
type: 'cmdMoveToLevelWithOnOff',
convert: (model, msg, publish, options) => {
return {action: 'circle_click'};
},
},
AC0251100NJ_cmdMoveToColorTemp: {
cid: 'lightingColorCtrl',
type: 'cmdMoveToColorTemp',
convert: (model, msg, publish, options) => null,
},
visonic_contact: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const zoneStatus = msg.data.zoneStatus;
return {
contact: !((zoneStatus & 1) > 0), // Bit 1 = Alarm: Contact detection
tamper: (zoneStatus & 1<<2) > 0, // Bit 3 = Tamper status
battery_low: (zoneStatus & 1<<3) > 0, // Bit 4 = Battery LOW indicator
};
},
},
OJBCR701YZ_statuschange: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => {
const {zoneStatus} = msg.data;
return {
carbon_monoxide: (zoneStatus & 1) > 0, // Bit 0 = Alarm 1: Carbon Monoxide (CO)
gas: (zoneStatus & 1 << 1) > 0, // Bit 1 = Alarm 2: Gas (CH4)
tamper: (zoneStatus & 1 << 2) > 0, // Bit 2 = Tamper
battery_low: (zoneStatus & 1 << 3) > 0, // Bit 3 = Low battery alarm
trouble: (zoneStatus & 1 << 6) > 0, // Bit 6 = Trouble/Failure
ac_connected: !((zoneStatus & 1 << 7) > 0), // Bit 7 = AC Connected
test: (zoneStatus & 1 << 8) > 0, // Bit 8 = Self test
battery_defect: (zoneStatus & 1 << 9) > 0, // Bit 9 = Battery Defect
};
},
},
closuresWindowCovering_report: {
cid: 'closuresWindowCovering',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
return {position: msg.data.data.currentPositionLiftPercentage};
},
},
closuresWindowCovering_report_pos_and_tilt: {
cid: 'closuresWindowCovering',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const result = {};
// ZigBee officially expects "open" to be 0 and "closed" to be 100 whereas
// HomeAssistant etc. work the other way round.
// ubisys J1 will report 255 if lift or tilt positions are not known.
if (msg.data.data.hasOwnProperty('currentPositionLiftPercentage')) {
const liftPercentage = msg.data.data['currentPositionLiftPercentage'];
result.position = liftPercentage <= 100 ? (100 - liftPercentage) : null;
}
if (msg.data.data.hasOwnProperty('currentPositionTiltPercentage')) {
const tiltPercentage = msg.data.data['currentPositionTiltPercentage'];
result.tilt = tiltPercentage <= 100 ? (100 - tiltPercentage) : null;
}
return result;
},
},
generic_fan_mode: {
cid: 'hvacFanCtrl',
type: 'attReport',
convert: (model, msg, publish, options) => {
const key = getKey(common.fanMode, msg.data.data.fanMode);
return {fan_mode: key, fan_state: key === 'off' ? 'OFF' : 'ON'};
},
},
GIRA2430_scene_click: {
cid: 'genScenes',
type: 'cmdRecall',
convert: (model, msg, publish, options) => {
return {
action: `select_${msg.data.data.sceneid}`,
};
},
},
GIRA2430_on_click: {
cid: 'genOnOff',
type: 'cmdOn',
convert: (model, msg, publish, options) => {
return {action: 'on'};
},
},
GIRA2430_off_click: {
cid: 'genOnOff',
type: 'cmdOffWithEffect',
convert: (model, msg, publish, options) => {
return {action: 'off'};
},
},
GIRA2430_down_hold: {
cid: 'genLevelCtrl',
type: 'cmdStep',
convert: (model, msg, publish, options) => {
return {
action: 'down',
step_mode: msg.data.data.stepmode,
step_size: msg.data.data.stepsize,
transition_time: msg.data.data.transtime,
};
},
},
GIRA2430_up_hold: {
cid: 'genLevelCtrl',
type: 'cmdStepWithOnOff',
convert: (model, msg, publish, options) => {
return {
action: 'up',
step_mode: msg.data.data.stepmode,
step_size: msg.data.data.stepsize,
transition_time: msg.data.data.transtime,
};
},
},
GIRA2430_stop: {
cid: 'genLevelCtrl',
type: 'cmdStop',
convert: (model, msg, publish, options) => {
return {
action: 'stop',
};
},
},
ZGRC013_cmdOn: {
cid: 'genOnOff',
type: 'cmdOn',
convert: (model, msg, publish, options) => {
const button = msg.endpoints[0].epId;
if (button) {
return {click: `${button}_on`};
}
},
},
ZGRC013_cmdOff: {
cid: 'genOnOff',
type: 'cmdOff',
convert: (model, msg, publish, options) => {
const button = msg.endpoints[0].epId;
if (button) {
return {click: `${button}_off`};
}
},
},
ZGRC013_brightness: {
cid: 'genLevelCtrl',
type: 'cmdMove',
convert: (model, msg, publish, options) => {
const button = msg.endpoints[0].epId;
const direction = msg.data.data.movemode == 0 ? 'up' : 'down';
if (button) {
return {click: `${button}_${direction}`};
}
},
},
ZGRC013_brightness_onoff: {
cid: 'genLevelCtrl',
type: 'cmdMoveWithOnOff',
convert: (model, msg, publish, options) => {
const button = msg.endpoints[0].epId;
const direction = msg.data.data.movemode == 0 ? 'up' : 'down';
if (button) {
return {click: `${button}_${direction}`};
}
},
},
ZGRC013_brightness_stop: {
cid: 'genLevelCtrl',
type: 'cmdStopWithOnOff',
convert: (model, msg, publish, options) => {
const button = msg.endpoints[0].epId;
if (button) {
return {click: `${button}_stop`};
}
},
},
ZGRC013_scene: {
cid: 'genScenes',
type: 'cmdRecall',
convert: (model, msg, publish, options) => {
return {click: `scene_${msg.data.data.groupid}_${msg.data.data.sceneid}`};
},
},
SZ_ESW01_AU_power: {
cid: 'seMetering',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('instantaneousDemand')) {
return {power: precisionRound(msg.data.data['instantaneousDemand'] / 1000, 2)};
}
},
},
meazon_meter: {
cid: 'seMetering',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const result = {};
// typo on property name to stick with zcl definition
if (msg.data.data.hasOwnProperty('inletTempreature')) {
result.inletTemperature = precisionRound(msg.data.data['inletTempreature'], 2);
}
if (msg.data.data.hasOwnProperty('status')) {
result.status = precisionRound(msg.data.data['status'], 2);
}
if (msg.data.data.hasOwnProperty('8192')) {
result.linefrequency = precisionRound((parseFloat(msg.data.data['8192'])) / 100.0, 2);
}
if (msg.data.data.hasOwnProperty('8193')) {
result.power = precisionRound(msg.data.data['8193'], 2);
}
if (msg.data.data.hasOwnProperty('8196')) {
result.voltage = precisionRound(msg.data.data['8196'], 2);
}
if (msg.data.data.hasOwnProperty('8213')) {
result.voltage = precisionRound(msg.data.data['8213'], 2);
}
if (msg.data.data.hasOwnProperty('8199')) {
result.current = precisionRound(msg.data.data['8199'], 2);
}
if (msg.data.data.hasOwnProperty('8216')) {
result.current = precisionRound(msg.data.data['8216'], 2);
}
if (msg.data.data.hasOwnProperty('8202')) {
result.reactivepower = precisionRound(msg.data.data['8202'], 2);
}
if (msg.data.data.hasOwnProperty('12288')) {
result.energyconsumed = precisionRound(msg.data.data['12288'], 2);
}
if (msg.data.data.hasOwnProperty('12291')) {
result.energyproduced = precisionRound(msg.data.data['12291'], 2);
}
if (msg.data.data.hasOwnProperty('12294')) {
result.reactivesummation = precisionRound(msg.data.data['12294'], 2);
}
if (msg.data.data.hasOwnProperty('16408')) {
result.measureserial = precisionRound(msg.data.data['16408'], 2);
}
return result;
},
},
ZNMS12LM_ZNMS13LM_closuresDoorLock_report: {
cid: 'closuresDoorLock',
type: 'attReport',
convert: (model, msg, publish, options) => {
const result = {};
const lockStatusLookup = {
1: 'finger_not_match',
2: 'password_not_match',
3: 'reverse_lock', // disable open from outside
4: 'reverse_lock_cancel', // enable open from outside
5: 'locked',
6: 'lock_opened',
7: 'finger_add',
8: 'finger_delete',
9: 'password_add',
10: 'password_delete',
11: 'lock_opened_inside', // Open form inside reverse lock enbable
12: 'lock_opened_outside', // Open form outside reverse lock disable
13: 'ring_bell',
14: 'change_language_to',
15: 'finger_open',
16: 'password_open',
17: 'door_closed',
};
result.user = null;
result.repeat = null;
if (msg.data.data['65526']) { // lock final status
// Convert data back to hex to decode
const data = Buffer.from(msg.data.data['65526'], 'ascii').toString('hex');
const command = data.substr(6, 4);
if (
command === '0301' // ZNMS12LM
|| command === '0341' // ZNMS13LM
) {
result.action = lockStatusLookup[4];
result.state = 'UNLOCK';
result.reverse = 'UNLOCK';
} else if (
command === '0311' // ZNMS12LM
|| command === '0351' // ZNMS13LM
) {
result.action = lockStatusLookup[4];
result.state = 'LOCK';
result.reverse = 'UNLOCK';
} else if (
command === '0205' // ZNMS12LM
|| command === '0245' // ZNMS13LM
) {
result.action = lockStatusLookup[3];
result.state = 'UNLOCK';
result.reverse = 'LOCK';
} else if (
command === '0215' // ZNMS12LM
|| command === '0255' // ZNMS13LM
|| command === '1355' // ZNMS13LM
) {
result.action = lockStatusLookup[3];
result.state = 'LOCK';
result.reverse = 'LOCK';
} else if (
command === '0111' // ZNMS12LM
|| command === '1351' // ZNMS13LM locked from inside
|| command === '1451' // ZNMS13LM locked from outside
) {
result.action = lockStatusLookup[5];
result.state = 'LOCK';
result.reverse = 'UNLOCK';
} else if (
command === '0b00' // ZNMS12LM
|| command === '0640' // ZNMS13LM
||command === '0600' // ZNMS13LM
) {
result.action = lockStatusLookup[12];
result.state = 'UNLOCK';
result.reverse = 'UNLOCK';
} else if (
command === '0c00' // ZNMS12LM
|| command === '2300' // ZNMS13LM
|| command === '0540' // ZNMS13LM
|| command === '0440' // ZNMS13LM
) {
result.action = lockStatusLookup[11];
result.state = 'UNLOCK';
result.reverse = 'UNLOCK';
} else if (
command === '2400' // ZNMS13LM door closed from insed
|| command === '2401' // ZNMS13LM door closed from outside
) {
result.action = lockStatusLookup[17];
result.state = 'UNLOCK';
result.reverse = 'UNLOCK';
}
} else if (msg.data.data['65296']) { // finger/password success
const data = Buffer.from(msg.data.data['65296'], 'ascii').toString('hex');
const command = data.substr(6, 2); // 1 finger open, 2 password open
const userId = data.substr(12, 2);
const userType = data.substr(8, 1); // 1 admin, 2 user
result.action = (lockStatusLookup[14+parseInt(command, 16)]
+ (userType === '1' ? '_admin' : '_user') + '_id' + parseInt(userId, 16).toString());
result.user = parseInt(userId, 16);
} else if (msg.data.data['65297']) { // finger, password failed or bell
const data = Buffer.from(msg.data.data['65297'], 'ascii').toString('hex');
const times = data.substr(6, 2);
const type = data.substr(12, 2); // 00 bell, 02 password, 40 error finger
if (type === '40') {
result.action = lockStatusLookup[1];
result.repeat = parseInt(times, 16);
} else if (type === '00') {
result.action = lockStatusLookup[13];
result.repeat = null;
} else if (type === '02') {
result.action = lockStatusLookup[2];
result.repeat = parseInt(times, 16);
}
} else if (msg.data.data['65281']) { // password added/delete
const data = Buffer.from(msg.data.data['65281'], 'ascii').toString('hex');
const command = data.substr(18, 2); // 1 add, 2 delete
const userId = data.substr(12, 2);
result.action = lockStatusLookup[6+parseInt(command, 16)];
result.user = parseInt(userId, 16);
result.repeat = null;
} else if (msg.data.data['65522']) { // set languge
const data = Buffer.from(msg.data.data['65522'], 'ascii').toString('hex');
const langId = data.substr(6, 2); // 1 chinese, 2: english
result.action = (lockStatusLookup[14])+ (langId==='2'?'_english':'_chinese');
result.user = null;
result.repeat = null;
}
return result;
},
},
DTB190502A1_parse: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const lookupKEY = {
'0': 'KEY_SYS',
'1': 'KEY_UP',
'2': 'KEY_DOWN',
'3': 'KEY_NONE',
};
const lookupLED = {
'0': 'OFF',
'1': 'ON',
};
return {
cpu_temperature: precisionRound(msg.data.data['41361'], 2),
key_state: lookupKEY[msg.data.data['41362']],
led_state: lookupLED[msg.data.data['41363']],
};
},
},
konke_click: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
const value = msg.data.data['onOff'];
const lookup = {
128: {click: 'single'}, // single click
129: {click: 'double'}, // double and many click
130: {click: 'long'}, // hold
};
return lookup[value] ? lookup[value] : null;
},
},
E1746_linkquality: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => {
return {linkquality: msg.linkquality};
},
},
generic_change_batteryvoltage_3000_2500: {
cid: 'genPowerCfg',
type: ['devChange'],
convert: (model, msg, publish, options) => {
const battery = {max: 3000, min: 2500};
const voltage = msg.data.data['batteryVoltage'] * 100;
return {
battery: toPercentage(voltage, battery.min, battery.max),
voltage: voltage,
};
},
},
generic_device_temperature: {
cid: 'genDeviceTempCfg',
type: ['devChange'],
convert: (model, msg, publish, options) => {
if (msg.data.data.hasOwnProperty('currentTemperature')) {
return {temperature: msg.data.data.currentTemperature};
}
},
},
ptvo_switch_state: {
cid: 'genOnOff',
type: 'attReport',
convert: (model, msg, publish, options) => {
const ep = msg.endpoints[0];
const key = `state_${getKey(model.ep(ep.device), ep.epId)}`;
const payload = {};
payload[key] = msg.data.data['onOff'] === 1 ? 'ON' : 'OFF';
return payload;
},
},
ptvo_switch_buttons: {
cid: 'genMultistateInput',
type: 'attReport',
convert: (model, msg, publish, options) => {
const ep = msg.endpoints[0];
const button = getKey(model.ep(ep.device), ep.epId);
const value = msg.data.data['presentValue'];
const actionLookup = {
1: 'single',
2: 'double',
3: 'tripple',
4: 'hold',
};
const action = actionLookup[value];
if (button) {
return {click: button + (action ? `_${action}` : '')};
}
},
},
keypad20states: {
cid: 'genOnOff',
type: ['devChange', 'attReport'],
convert: (model, msg, publish, options) => {
const ep = msg.endpoints[0];
const button = getKey(model.ep(ep.device), ep.epId);
const state = msg.data.data['onOff'] === 1 ? true : false;
if (button) {
return {[button]: state};
}
},
},
keypad20_battery: {
cid: 'genPowerCfg',
type: ['devChange', 'attReport'],
convert: (model, msg, publish, options) => {
const battery = {max: 3000, min: 2100};
const voltage = msg.data.data['mainsVoltage'] /10;
return {
battery: toPercentage(voltage, battery.min, battery.max),
voltage: voltage,
};
},
},
// Ignore converters (these message dont need parsing).
ignore_fan_change: {
cid: 'hvacFanCtrl',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_light_brightness_change: {
cid: 'genLevelCtrl',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_doorlock_change: {
cid: 'closuresDoorLock',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_onoff_change: {
cid: 'genOnOff',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_onoff_report: {
cid: 'genOnOff',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_basic_change: {
cid: 'genBasic',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_basic_report: {
cid: 'genBasic',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_illuminance_change: {
cid: 'msIlluminanceMeasurement',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_occupancy_change: {
cid: 'msOccupancySensing',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_illuminance_report: {
cid: 'msIlluminanceMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_occupancy_report: {
cid: 'msOccupancySensing',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_temperature_change: {
cid: 'msTemperatureMeasurement',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_temperature_report: {
cid: 'msTemperatureMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_humidity_change: {
cid: 'msRelativeHumidity',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_humidity_report: {
cid: 'msRelativeHumidity',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_pressure_change: {
cid: 'msPressureMeasurement',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_pressure_report: {
cid: 'msPressureMeasurement',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_analog_change: {
cid: 'genAnalogInput',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_analog_report: {
cid: 'genAnalogInput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_multistate_report: {
cid: 'genMultistateInput',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_multistate_change: {
cid: 'genMultistateInput',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_power_change: {
cid: 'genPowerCfg',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_power_report: {
cid: 'genPowerCfg',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_metering_change: {
cid: 'seMetering',
type: ['devChange', 'attReport'],
convert: (model, msg, publish, options) => null,
},
ignore_electrical_change: {
cid: 'haElectricalMeasurement',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_light_brightness_report: {
cid: 'genLevelCtrl',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_light_color_colortemp_report: {
cid: 'lightingColorCtrl',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_closuresWindowCovering_change: {
cid: 'closuresWindowCovering',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_closuresWindowCovering_report: {
cid: 'closuresWindowCovering',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_thermostat_change: {
cid: 'hvacThermostat',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_thermostat_report: {
cid: 'hvacThermostat',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_genGroups_devChange: {
cid: 'genGroups',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_iaszone_attreport: {
cid: 'ssIasZone',
type: 'attReport',
convert: (model, msg, publish, options) => null,
},
ignore_iaszone_change: {
cid: 'ssIasZone',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_iaszone_statuschange: {
cid: 'ssIasZone',
type: 'statusChange',
convert: (model, msg, publish, options) => null,
},
ignore_iaszone_report: {
cid: 'ssIasZone',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_genIdentify: {
cid: 'genIdentify',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
_324131092621_ignore_on: {
cid: 'genOnOff',
type: 'cmdOn',
convert: (model, msg, publish, options) => null,
},
_324131092621_ignore_off: {
cid: 'genOnOff',
type: 'cmdOffWithEffect',
convert: (model, msg, publish, options) => null,
},
_324131092621_ignore_step: {
cid: 'genLevelCtrl',
type: 'cmdStep',
convert: (model, msg, publish, options) => null,
},
_324131092621_ignore_stop: {
cid: 'genLevelCtrl',
type: 'cmdStop',
convert: (model, msg, publish, options) => null,
},
ignore_poll_ctrl: {
cid: 'genPollCtrl',
type: ['attReport', 'readRsp'],
convert: (model, msg, publish, options) => null,
},
ignore_poll_ctrl_change: {
cid: 'genPollCtrl',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_genIdentify_change: {
cid: 'genIdentify',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_diagnostic_change: {
cid: 'haDiagnostic',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_genScenes_change: {
cid: 'genScenes',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_lightLink_change: {
cid: 'lightLink',
type: 'devChange',
convert: (model, msg, publish, options) => null,
},
ignore_genLevelCtrl_report: {
cid: 'genLevelCtrl',
type: 'attReport',
convert: (model, msg, publish, options) => null,
},
};
module.exports = converters;