Skip to content

Commit

Permalink
Fantem MultiSensor ZB003-X (Koenkk#2072)
Browse files Browse the repository at this point in the history
* Fantem MultiSensor ZB003-X

Koenkk#2071

* fix lint

* expose description

* access.STATE_SET

* fix

* Update devices.js

* calibrate with options

* Update fromZigbee.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
  • Loading branch information
kirovilya and Koenkk authored Jan 12, 2021
1 parent 288bef5 commit de1c830
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 12 deletions.
68 changes: 68 additions & 0 deletions converters/fromZigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -4958,6 +4958,74 @@ const converters = {
}
},
},
ZB003X: {
cluster: 'manuSpecificTuya',
type: ['raw'],
convert: (model, msg, publish, options, meta) => {
const dp = msg.data[5];
const value = tuya.getDataValue(tuya.dataTypes.value, msg.data.slice(8));
let val;
switch (dp) {
case 107: // 0x6b temperature
return {temperature: calibrateAndPrecisionRoundOptions(
(value / 10).toFixed(1), options, 'temperature')};
case 108: // 0x6c humidity
return {humidity: calibrateAndPrecisionRoundOptions(value, options, 'humidity')};
case 110: // 0x6e battery
return {battery: value};
case 102: // 0x66 reporting time
return {reporting_time: value};
case 104: // 0x68 temperature calibration
val = value;
// for negative values produce complimentary hex (equivalent to negative values)
if (val > 4294967295) val = val - 4294967295;
return {temperature_calibration: (val / 10).toFixed(1)};
case 105: // 0x69 humidity calibration
val = value;
// for negative values produce complimentary hex (equivalent to negative values)
if (val > 4294967295) val = val - 4294967295;
return {humidity_calibration: val};
case 106: // 0x6a lux calibration
val = value;
// for negative values produce complimentary hex (equivalent to negative values)
if (val > 4294967295) val = val - 4294967295;
return {illuminance_calibration: val};
case 109: // 0x6d PIR enable
return {pir_enable: tuya.getDataValue(tuya.dataTypes.bool, msg.data.slice(9))};
case 111: // 0x6f led enable
return {led_enable: tuya.getDataValue(tuya.dataTypes.bool, msg.data.slice(9))};
case 112: // 0x70 reporting enable
return {reporting_enable: tuya.getDataValue(tuya.dataTypes.bool, msg.data.slice(9))};
default: // Unknown code
meta.logger.warn(`Unhandled DP #${dp}: ${JSON.stringify(msg.data)}`);
}
},
},
ZB003X_attr: {
cluster: 'ssIasZone',
type: ['attributeReport', 'readResponse'],
convert: (model, msg, publish, options, meta) => {
const data = msg.data;
const senslookup = {'0': 'low', '1': 'medium', '2': 'high'};
const keeptimelookup = {'0': 0, '1': 30, '2': 60, '3': 120, '4': 240};
if (data && data.hasOwnProperty('currentZoneSensitivityLevel')) {
const value = data.currentZoneSensitivityLevel;
return {sensitivity: senslookup[value]};
}
if (data && data.hasOwnProperty('61441')) {
const value = data['61441'];
return {keep_time: keeptimelookup[value]};
}
},
},
ZB003X_occupancy: {
cluster: 'ssIasZone',
type: 'commandStatusChangeNotification',
convert: (model, msg, publish, options, meta) => {
const zoneStatus = msg.data.zonestatus;
return {occupancy: (zoneStatus & 1<<2) > 0};
},
},
// #endregion

// #region Ignore converters (these message dont need parsing).
Expand Down
44 changes: 44 additions & 0 deletions converters/toZigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -4269,6 +4269,50 @@ const converters = {
}
},
},
ZB003X: {
key: [
'reporting_time', 'temperature_calibration', 'humidity_calibration',
'illuminance_calibration', 'pir_enable', 'led_enable',
'reporting_enable', 'sensitivity', 'keep_time',
],
convertSet: async (entity, key, value, meta) => {
switch (key) {
case 'reporting_time':
await tuya.sendDataPointValue(entity, 102, value, 'sendData');
break;
case 'temperature_calibration':
value = Math.round(value * 10);
if (value < 0) value = 0xFFFFFFFF + value + 1;
await tuya.sendDataPointValue(entity, 104, value, 'sendData');
break;
case 'humidity_calibration':
if (value < 0) value = 0xFFFFFFFF + value + 1;
await tuya.sendDataPointValue(entity, 105, value, 'sendData');
break;
case 'illuminance_calibration':
if (value < 0) value = 0xFFFFFFFF + value + 1;
await tuya.sendDataPointValue(entity, 106, value, 'sendData');
break;
case 'pir_enable':
await tuya.sendDataPointBool(entity, 109, value, 'sendData');
break;
case 'led_enable':
await tuya.sendDataPointBool(entity, 111, value, 'sendData');
break;
case 'reporting_enable':
await tuya.sendDataPointBool(entity, 112, value, 'sendData');
break;
case 'sensitivity':
await entity.write('ssIasZone', {currentZoneSensitivityLevel: {'low': 0, 'medium': 1, 'high': 2}[value]});
break;
case 'keep_time':
await entity.write('ssIasZone', {61441: {value: {'0': 0, '30': 1, '60': 2, '120': 3, '240': 4}[value], type: 0x20}});
break;
default: // Unknown key
throw new Error(`Unhandled key ${key}`);
}
},
},
// #endregion

// #region Ignore converters
Expand Down
21 changes: 21 additions & 0 deletions devices.js
Original file line number Diff line number Diff line change
Expand Up @@ -15202,6 +15202,27 @@ const devices = [
description: 'E27 white and colour bulb',
extend: preset.light_onoff_brightness_colortemp_colorxy,
},

// Fantem
{
fingerprint: [{modelID: 'TS0202', manufacturerName: '_TZ3210_rxqls8v0'}],
model: 'ZB003-X',
vendor: 'Fantem',
description: '4 in 1 multi sensor',
fromZigbee: [fz.battery, fz.ignore_basic_report, fz.illuminance, fz.ZB003X, fz.ZB003X_attr, fz.ZB003X_occupancy],
toZigbee: [tz.ZB003X],
exposes: [e.occupancy(), e.battery(), e.illuminance().withUnit('lx'), e.temperature(), e.humidity(),
exposes.numeric('reporting_time', exposes.access.STATE_SET).withDescription('Reporting interval in minutes'),
exposes.numeric('temperature_calibration', exposes.access.STATE_SET).withDescription('Temperature calibration'),
exposes.numeric('humidity_calibration', exposes.access.STATE_SET).withDescription('Humidity calibration'),
exposes.numeric('illuminance_calibration', exposes.access.STATE_SET).withDescription('Illuminance calibration'),
exposes.binary('pir_enable', exposes.access.STATE_SET, true, false).withDescription('Enable PIR sensor'),
exposes.binary('led_enable', exposes.access.STATE_SET, true, false).withDescription('Enabled LED'),
exposes.binary('reporting_enable', exposes.access.STATE_SET, true, false).withDescription('Enabled reporting'),
exposes.enum('sensitivity', exposes.access.STATE_SET, ['low', 'medium', 'high']).withDescription('PIR sensor sensitivity'),
// eslint-disable-next-line
exposes.enum('keep_time', exposes.access.STATE_SET, ['0', '30', '60', '120', '240']).withDescription('PIR keep time in seconds')],
},
];

module.exports = devices.map((device) => {
Expand Down
34 changes: 22 additions & 12 deletions lib/tuya.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ const fanModes = {
3: 'auto',
};

async function sendDataPoint(entity, datatype, dp, data) {
async function sendDataPoint(entity, datatype, dp, data, cmd) {
if (sendDataPoint.transId === undefined) {
sendDataPoint.transId = 0;
} else {
Expand All @@ -349,7 +349,7 @@ async function sendDataPoint(entity, datatype, dp, data) {
}
await entity.command(
'manuSpecificTuya',
'setData',
cmd || 'setData',
{
status: 0,
transid: sendDataPoint.transId,
Expand All @@ -363,44 +363,54 @@ async function sendDataPoint(entity, datatype, dp, data) {
);
}

async function sendDataPointValue(entity, dp, value) {
async function sendDataPointValue(entity, dp, value, cmd) {
await sendDataPoint(
entity,
dataTypes.value,
dp,
convertDecimalValueTo4ByteHexArray(value));
convertDecimalValueTo4ByteHexArray(value),
cmd,
);
}

async function sendDataPointBool(entity, dp, value) {
async function sendDataPointBool(entity, dp, value, cmd) {
await sendDataPoint(
entity,
dataTypes.bool,
dp,
[value ? 1 : 0]);
[value ? 1 : 0],
cmd,
);
}

async function sendDataPointEnum(entity, dp, value) {
async function sendDataPointEnum(entity, dp, value, cmd) {
await sendDataPoint(
entity,
dataTypes.enum,
dp,
[value]);
[value],
cmd,
);
}

async function sendDataPointRaw(entity, dp, value) {
async function sendDataPointRaw(entity, dp, value, cmd) {
await sendDataPoint(
entity,
dataTypes.raw,
dp,
value);
value,
cmd,
);
}

async function sendDataPointBitmap(entity, dp, value) {
async function sendDataPointBitmap(entity, dp, value, cmd) {
await sendDataPoint(
entity,
dataTypes.bitmap,
dp,
value);
value,
cmd)
;
}

module.exports = {
Expand Down

0 comments on commit de1c830

Please sign in to comment.