From a0668d7175d97ee829977e77b65df3d0d1e2086e Mon Sep 17 00:00:00 2001 From: niracler Date: Fri, 14 Feb 2025 14:42:18 +0800 Subject: [PATCH 1/2] feat: support indicatorLight detectionArea illuminanceThreshold for SR-ZG9030F-PS --- src/devices/sunricher.ts | 109 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/src/devices/sunricher.ts b/src/devices/sunricher.ts index cb748164bccbc..3e2fe565bb737 100644 --- a/src/devices/sunricher.ts +++ b/src/devices/sunricher.ts @@ -313,7 +313,7 @@ function sunricherSRZG9002K16Pro(): ModernExtend { const fromZigbee: Fz.Converter[] = [ { - cluster: 0xff03, + cluster, type: ['raw'], convert: (model, msg, publish, options, meta) => { const bytes = [...msg.data]; @@ -368,6 +368,67 @@ function sunricherSRZG9002K16Pro(): ModernExtend { }; } +function sunricherIndicatorLight(): ModernExtend { + const cluster = 0xfc8b; + const attribute = 0xf001; + const data_type = 0x20; + const manufacturerCode = 0x120b; + + const exposes: Expose[] = [ + e.enum('indicator_light', ea.ALL, ['on', 'off']).withDescription('Enable/disable the LED indicator').withCategory('config'), + ]; + + const fromZigbee: Fz.Converter[] = [ + { + cluster, + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + if (!Object.prototype.hasOwnProperty.call(msg.data, attribute)) return; + const indicatorLight = msg.data[attribute]; + const firstBit = indicatorLight & 0x01; + return {indicator_light: firstBit === 1 ? 'on' : 'off'}; + }, + } satisfies Fz.Converter, + ]; + + const toZigbee: Tz.Converter[] = [ + { + key: ['indicator_light'], + convertSet: async (entity, key, value, meta) => { + const attributeRead = await entity.read(cluster, [attribute]); + if (attributeRead === undefined) return; + + // @ts-expect-error ignore + const currentValue = attributeRead[attribute]; + const newValue = value === 'on' ? currentValue | 0x01 : currentValue & ~0x01; + + await entity.write(cluster, {[attribute]: {value: newValue, type: data_type}}, {manufacturerCode}); + + return {state: {indicator_light: value}}; + }, + convertGet: async (entity, key, meta) => { + await entity.read(cluster, [attribute], {manufacturerCode}); + }, + }, + ]; + + const configure: [Configure] = [ + async (device, coordinatorEndpoint, definition) => { + const endpoint = device.getEndpoint(1); + await endpoint.bind(cluster, coordinatorEndpoint); + await endpoint.read(cluster, [attribute], {manufacturerCode}); + }, + ]; + + return { + exposes, + configure, + fromZigbee, + toZigbee, + isModernExtend: true, + }; +} + const fzLocal = { sunricher_SRZGP2801K45C: { cluster: 'greenPower', @@ -419,7 +480,47 @@ const definitions: DefinitionWithExtend[] = [ model: 'SR-ZG9030F-PS', vendor: 'Sunricher', description: 'Smart human presence sensor', - extend: [m.illuminance({scale: (value) => value}), m.occupancy(), m.commandsOnOff()], + extend: [ + m.illuminance({scale: (value) => value}), + m.occupancy(), + m.commandsOnOff(), + m.deviceAddCustomCluster('sunricherSensor', { + ID: 0xfc8b, + manufacturerCode: 0x120b, + attributes: { + indicatorLight: {ID: 0xf001, type: 0x20}, + detectionArea: {ID: 0xf002, type: 0x20}, + illuminanceThreshold: {ID: 0xf004, type: 0x20}, + }, + commands: {}, + commandsResponse: {}, + }), + sunricherIndicatorLight(), + m.numeric({ + name: 'detection_area', + cluster: 'sunricherSensor', + attribute: 'detectionArea', + description: 'Detection area range (default: 50%)', + valueMin: 0, + valueMax: 100, + valueStep: 1, + unit: '%', + access: 'ALL', + entityCategory: 'config', + }), + m.numeric({ + name: 'illuminance_threshold', + cluster: 'sunricherSensor', + attribute: 'illuminanceThreshold', + description: 'Illuminance threshold for triggering (default: 100)', + valueMin: 10, + valueMax: 100, + valueStep: 1, + unit: 'lx', + access: 'ALL', + entityCategory: 'config', + }), + ], }, { zigbeeModel: ['HK-SENSOR-GAS'], @@ -533,6 +634,8 @@ const definitions: DefinitionWithExtend[] = [ }), ], meta: {multiEndpoint: true}, + toZigbee: [sunricher.tz.setModel], + exposes: [e.enum('model', ea.SET, ['HK-DIM', 'ZG9030A-MW']).withDescription('Model of the device').withCategory('config')], }, { zigbeeModel: ['HK-ZRC-K16N-E'], @@ -1094,8 +1197,6 @@ const definitions: DefinitionWithExtend[] = [ extend: [m.light()], whiteLabel: [{vendor: 'Yphix', model: '50208702'}], toZigbee: [sunricher.tz.setModel], - // Some ZG9030A-MW devices were mistakenly set with the modelId HK-DIM during manufacturing. - // This allows users to update the modelId from HK-DIM to ZG9030A-MW to ensure proper device functionality. exposes: [e.enum('model', ea.SET, ['HK-DIM', 'ZG9030A-MW']).withDescription('Model of the device')], }, { From a7c73af899685caa114df837a7a387d2ed0d7503 Mon Sep 17 00:00:00 2001 From: niracler Date: Fri, 14 Feb 2025 14:49:09 +0800 Subject: [PATCH 2/2] refactor: clean code --- src/devices/sunricher.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devices/sunricher.ts b/src/devices/sunricher.ts index 3e2fe565bb737..8f7fa1f94cc49 100644 --- a/src/devices/sunricher.ts +++ b/src/devices/sunricher.ts @@ -634,8 +634,6 @@ const definitions: DefinitionWithExtend[] = [ }), ], meta: {multiEndpoint: true}, - toZigbee: [sunricher.tz.setModel], - exposes: [e.enum('model', ea.SET, ['HK-DIM', 'ZG9030A-MW']).withDescription('Model of the device').withCategory('config')], }, { zigbeeModel: ['HK-ZRC-K16N-E'], @@ -1197,6 +1195,8 @@ const definitions: DefinitionWithExtend[] = [ extend: [m.light()], whiteLabel: [{vendor: 'Yphix', model: '50208702'}], toZigbee: [sunricher.tz.setModel], + // Some ZG9030A-MW devices were mistakenly set with the modelId HK-DIM during manufacturing. + // This allows users to update the modelId from HK-DIM to ZG9030A-MW to ensure proper device functionality. exposes: [e.enum('model', ea.SET, ['HK-DIM', 'ZG9030A-MW']).withDescription('Model of the device')], }, {