From a16f833f67d44207198c9f88bd72c79673fbc5f0 Mon Sep 17 00:00:00 2001 From: Simon Baatz Date: Sun, 16 Jan 2022 12:41:26 +0100 Subject: [PATCH] Improve `tuya_data_point_dump`: Log decoded DPs Up to now, `tuya_data_point_dump` dumped the DP values in machine readable format, necessitating the use of the Python script `tuya_data_point_dump.py` to decode it. Additionally, the list of data point IDs in that script is not complete (actually, it is rather specific for Saswell devices). `zigbee-herdsman-converters` actually has the most up-to-date list of DP values built-in. And it comes with DP value converters for the DP data types. We can use this to log incoming DP values in a human readable format containing the DP ID, data type, decoded value and the already known uses of the DP IP: ``` zigbee-herdsman-converters:tuya_data_point_dump: Received DP #1 from 0x845134eaae1d0412 with raw data '{"dp":1,"datatype":4,"data":{"type":"Buffer","data":[1]}}': type='commandDataResponse', datatype='enum', value='1', known DP# usage: ["state","moesSsystemMode","moes105DimmerState1","trsPresenceState","haozeeSystemMode","nousTemperature","wlsWaterLeak"] zigbee-herdsman-converters:tuya_data_point_dump: Received DP #4 from 0x845134eaae1d0412 with raw data '{"dp":4,"datatype":2,"data":{"type":"Buffer","data":[0,0,0,86]}}': type='commandDataResponse', datatype='value', value='86', known DP# usage: ["mode","moesSboostHeating","haozeeBoostHeating","nousBattery","wlsBatteryPercentage"] ``` In general, this output should be sufficient to enable new Tuya devices (However, this won't decode more complex data structures for Saswell devices (like tuya_data_point_dump.py does for schedules)). --- converters/fromZigbee.js | 13 +++++++++++++ test/fromZigbee.test.js | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/converters/fromZigbee.js b/converters/fromZigbee.js index 583c0b1829ded..68e681091c5cc 100644 --- a/converters/fromZigbee.js +++ b/converters/fromZigbee.js @@ -4362,6 +4362,14 @@ const converters = { convert: (model, msg, publis, options, meta) => { // Don't use in production! // Used in: https://www.zigbee2mqtt.io/how_tos/how_to_support_new_tuya_devices.html + const getType = (datatype) => { + const entry = Object.entries(tuya.dataTypes).find(([typeName, typeId]) => typeId === datatype); + return (entry ? entry[0] : 'unknown'); + }; + const getAllDpIds = (dp) => { + const entries = Object.entries(tuya.dataPoints).filter(([dpName, dpId]) => dpId === dp); + return entries.map(([dpName, dpId]) => dpName); + }; const getHex = (value) => { let hex = value.toString(16); if (hex.length < 2) { @@ -4372,6 +4380,11 @@ const converters = { const now = Date.now().toString(); let dataStr = ''; for (const [i, dpValue] of msg.data.dpValues.entries()) { + const value = tuya.getDataValue(dpValue); + meta.logger.info(`zigbee-herdsman-converters:tuya_data_point_dump: Received DP #${ + dpValue.dp} from ${meta.device.ieeeAddr} with raw data '${JSON.stringify(dpValue)}': type='${msg.type}', datatype='${ + getType(dpValue.datatype)}', value='${value}', known DP# usage: ${JSON.stringify(getAllDpIds(dpValue.dp))}`); + dataStr += now + ' ' + meta.device.ieeeAddr + ' ' + diff --git a/test/fromZigbee.test.js b/test/fromZigbee.test.js index 5c85257c8ee8e..b508ae8db7079 100644 --- a/test/fromZigbee.test.js +++ b/test/fromZigbee.test.js @@ -1,9 +1,12 @@ const fz = require('../converters/fromZigbee'); const tuya = require('../lib/tuya'); +jest.mock('fs'); +const fs = require('fs'); + describe('converters/fromZigbee', () => { describe('tuya', () => { - const meta = { logger: { warn: jest.fn()}} + const meta = {logger: {warn: jest.fn(), info: jest.fn()}, device: {ieeeAddr: "0x123456789abcdef"}}; describe('wls100z_water_leak', () => { it.each([ [ @@ -34,7 +37,7 @@ describe('converters/fromZigbee', () => { ], ]) ("Receives '%s' indication", (_name, dpValues, result) => { - expect(fz.wls100z_water_leak.convert(null, { data: {dpValues}}, null, null, meta)).toEqual(result); + expect(fz.wls100z_water_leak.convert(null, {data: {dpValues}}, null, null, meta)).toEqual(result); }); }); describe('tuya_smart_vibration_sensor', () => { @@ -60,7 +63,35 @@ describe('converters/fromZigbee', () => { ], ]) ("Receives '%s' indication", (_name, dpValues, result) => { - expect(fz.tuya_smart_vibration_sensor.convert(null, { data: {dpValues}}, null, null, meta)).toEqual(result); + expect(fz.tuya_smart_vibration_sensor.convert(null, {data: {dpValues}}, null, null, meta)).toEqual(result); + }); + }); + describe('tuya_data_point_dump', () => { + beforeEach(() => { + meta.logger.info.mockClear(); + }); + it('Logs all received DPs', () => { + const msg = { + type: 'commandDataResponse', + data: { + seq: 1, + dpValues: [ + tuya.dpValueFromRaw(tuya.dataPoints.state, [0, 1]), + tuya.dpValueFromBool(tuya.dataPoints.heatingSetpoint, true), + tuya.dpValueFromIntValue(tuya.dataPoints.occupancy, 97), + tuya.dpValueFromStringBuffer(tuya.dataPoints.mode, [102, 111, 111]), + tuya.dpValueFromEnum(tuya.dataPoints.config, 1), + tuya.dpValueFromBitmap(tuya.dataPoints.childLock, [1, 2]), + ], + }, + }; + expect(fz.tuya_data_point_dump.convert(null, msg, null, null, meta)).toEqual(undefined); + expect(meta.logger.info).nthCalledWith(1, expect.stringMatching(/Received DP #1 from 0x123456789abcdef with raw data '\{"dp":1,"datatype":0,"data":\[0,1\]\}': type='commandDataResponse', datatype='raw', value='0,1', known DP# usage: \[.*"state"/)); + expect(meta.logger.info).nthCalledWith(2, expect.stringMatching(/Received DP #2 from 0x123456789abcdef with raw data '\{"dp":2,"datatype":1,"data":\[1\]\}': type='commandDataResponse', datatype='bool', value='true', known DP# usage: \[.*"heatingSetpoint"/)); + expect(meta.logger.info).nthCalledWith(3, expect.stringMatching(/Received DP #3 from 0x123456789abcdef with raw data '\{"dp":3,"datatype":2,"data":\[0,0,0,97\]\}': type='commandDataResponse', datatype='value', value='97', known DP# usage: \[.*"occupancy"/)); + expect(meta.logger.info).nthCalledWith(4, expect.stringMatching(/Received DP #4 from 0x123456789abcdef with raw data '\{"dp":4,"datatype":3,"data":\[102,111,111\]\}': type='commandDataResponse', datatype='string', value='foo', known DP# usage: \[.*"mode"/)); + expect(meta.logger.info).nthCalledWith(5, expect.stringMatching(/Received DP #5 from 0x123456789abcdef with raw data '\{"dp":5,"datatype":4,"data":\[1\]\}': type='commandDataResponse', datatype='enum', value='1', known DP# usage: \[.*"config"/)); + expect(meta.logger.info).nthCalledWith(6, expect.stringMatching(/Received DP #7 from 0x123456789abcdef with raw data '\{"dp":7,"datatype":5,"data":\[1,2\]\}': type='commandDataResponse', datatype='bitmap', value='258', known DP# usage: \[.*"childLock"/)); }); }); });