diff --git a/src/devices/sunricher.ts b/src/devices/sunricher.ts index 701a795d11053..66b32fa5e284c 100644 --- a/src/devices/sunricher.ts +++ b/src/devices/sunricher.ts @@ -13,17 +13,20 @@ import { commandsScenes, deviceEndpoints, electricityMeter, + enumLookup, humidity, iasZoneAlarm, identify, illuminance, light, + numeric, occupancy, onOff, temperature, } from '../lib/modernExtend'; import * as reporting from '../lib/reporting'; import * as globalStore from '../lib/store'; +import * as sunricher from '../lib/sunricher'; import {Configure, DefinitionWithExtend, Expose, Fz, ModernExtend, Tz, Zh} from '../lib/types'; import * as utils from '../lib/utils'; @@ -362,6 +365,195 @@ async function syncTime(endpoint: Zh.Endpoint) { } const definitions: DefinitionWithExtend[] = [ + { + zigbeeModel: ['ZG9030A-MW'], + model: 'SR-ZG9030A-MW', + vendor: 'Sunricher', + description: 'Zigbee compatible ceiling mount occupancy sensor', + extend: [ + numeric({ + name: 'light_pwm_frequency', + cluster: 'genBasic', + attribute: {ID: 0x9001, type: 0x21}, + valueMin: 0, + valueMax: 65535, + description: 'Light PWM frequency (0-65535, default: 3300)', + access: 'ALL', + }), + enumLookup({ + name: 'brightness_curve', + cluster: 'genBasic', + attribute: {ID: 0x8806, type: 0x20}, + lookup: { + linear: 0, + gamma_logistics_1_5: 0x0f, + gamma_logistics_1_8: 0x12, + }, + description: 'Brightness curve (default: Linear)', + access: 'ALL', + }), + enumLookup({ + name: 'start_up_on_off', + cluster: 'genOnOff', + attribute: {ID: 0x4003, type: 0x30}, + lookup: { + last_state: 0xff, + on: 1, + off: 0, + }, + description: 'Start up on/off (default: last_state)', + access: 'ALL', + }), + numeric({ + name: 'motion_sensor_light_duration', + cluster: 'genBasic', + attribute: {ID: 0x8902, type: 0x21}, + valueMin: 0, + valueMax: 65535, + unit: 's', + description: 'Motion sensor light duration (0s-65535s, default: 5s)', + access: 'ALL', + }), + numeric({ + name: 'motion_sensor_light_sensitivity', + cluster: 'genBasic', + attribute: {ID: 0x8903, type: 0x21}, + valueMin: 0, + valueMax: 255, + description: 'Motion sensor light sensitivity (0-255, default: 0)', + access: 'ALL', + }), + enumLookup({ + name: 'motion_sensor_working_mode', + cluster: 'genBasic', + attribute: {ID: 0x8904, type: 0x20}, + lookup: { + automatic: 0, + manual: 1, + }, + description: 'Motion sensor working mode (default: Automatic)', + access: 'ALL', + }), + numeric({ + name: 'motion_sensor_sensing_distance', + cluster: 'genBasic', + attribute: {ID: 0x8905, type: 0x20}, + valueMin: 0, + valueMax: 15, + description: 'Motion sensor sensing distance (0-15, default: 1)', + access: 'ALL', + }), + enumLookup({ + name: 'motion_sensor_microwave_switch', + cluster: 'genBasic', + attribute: {ID: 0x8906, type: 0x20}, + lookup: { + on: 1, + off: 0, + }, + description: 'Motion sensor microwave switch (default: On)', + access: 'ALL', + }), + enumLookup({ + name: 'motion_sensor_onoff_broadcast', + cluster: 'genBasic', + attribute: {ID: 0x8907, type: 0x20}, + lookup: { + on: 1, + off: 0, + }, + description: 'Motion sensor on/off broadcast (default: On)', + access: 'ALL', + }), + enumLookup({ + name: 'motion_sensor_light_state', + cluster: 'genBasic', + attribute: {ID: 0x890c, type: 0x20}, + lookup: { + on: 1, + off: 0, + }, + description: 'Motion sensor light state (default: On)', + access: 'ALL', + }), + numeric({ + name: 'motion_sensor_in_pwm_brightness', + cluster: 'genBasic', + attribute: {ID: 0x8908, type: 0x21}, + valueMin: 0, + valueMax: 1000, + unit: 'lux', + description: 'Motion sensor IN PWM brightness (0-1000 lux, default: 0)', + access: 'ALL', + }), + numeric({ + name: 'motion_sensor_in_pwm_output', + cluster: 'genBasic', + attribute: {ID: 0x8909, type: 0x20}, + valueMin: 0, + valueMax: 254, + description: 'Motion sensor IN PWM output (0-254, default: 254)', + access: 'ALL', + }), + numeric({ + name: 'motion_sensor_leave_pwm_output', + cluster: 'genBasic', + attribute: {ID: 0x890a, type: 0x20}, + valueMin: 0, + valueMax: 100, + unit: '%', + description: 'Motion sensor LEAVE PWM output (0%-100%, default: 0%)', + access: 'ALL', + }), + numeric({ + name: 'motion_sensor_leave_delay', + cluster: 'genBasic', + attribute: {ID: 0x8901, type: 0x21}, + valueMin: 0, + valueMax: 65535, + unit: 's', + description: 'Motion sensor LEAVE delay (0s-65535s, default: 0s)', + access: 'ALL', + }), + numeric({ + name: 'motion_sensor_pwm_output_after_delay', + cluster: 'genBasic', + attribute: {ID: 0x890b, type: 0x20}, + valueMin: 0, + valueMax: 100, + unit: '%', + description: 'Motion sensor PWM output after delay (0%-100%, default: 0%)', + access: 'ALL', + }), + numeric({ + name: 'linear_error_ratio_coefficient_of_lux_measurement', + cluster: 'genBasic', + attribute: {ID: 0x890d, type: 0x21}, + valueMin: 100, + valueMax: 10000, + description: 'Linear error ratio coefficient of LUX measurement (100‰-10000‰, default: 1000‰)', + access: 'ALL', + }), + numeric({ + name: 'fixed_deviation_of_lux_measurement', + cluster: 'genBasic', + attribute: {ID: 0x890e, type: 0x29}, + valueMin: -100, + valueMax: 100, + description: 'Fixed deviation of LUX measurement (-100~100, default: 0)', + access: 'ALL', + }), + deviceEndpoints({endpoints: {'1': 1, '2': 2, '3': 3}}), + light(), + occupancy(), + illuminance({endpointNames: ['3']}), + commandsOnOff(), + commandsLevelCtrl(), + ], + meta: {multiEndpoint: true}, + toZigbee: [sunricher.tz.setModel], + exposes: [e.enum('model', ea.SET, ['HK-DIM', 'ZG9030A-MW']).withDescription('Model of the device')], + }, { zigbeeModel: ['HK-ZRC-K5&RS-E'], model: 'SR-ZG2836D5-Pro', @@ -708,6 +900,10 @@ const definitions: DefinitionWithExtend[] = [ description: 'LED dimmable driver', extend: [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')], }, { zigbeeModel: ['SR-ZG9040A-S'], diff --git a/src/lib/sunricher.ts b/src/lib/sunricher.ts new file mode 100644 index 0000000000000..5644a76269c1a --- /dev/null +++ b/src/lib/sunricher.ts @@ -0,0 +1,13 @@ +import {Tz} from './types'; + +const tz = { + setModel: { + key: ['model'], + convertSet: async (entity, key, value, meta) => { + await entity.write('genBasic', {modelId: value}); + return {state: {model: value}}; + }, + } satisfies Tz.Converter, +}; + +export {tz}; diff --git a/test/index.test.js b/test/index.test.js index 7a00fa1fd4e2e..87d054402db0b 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -13,6 +13,7 @@ import { removeExternalDefinitions, } from '../src/index'; import {access as _access, enum as _enum, list as _list, composite, numeric, presets} from '../src/lib/exposes'; +import * as sunricher from '../src/lib/sunricher'; import {tz} from '../src/lib/tuya'; import {getFromLookup, toNumber} from '../src/lib/utils'; import {COLORTEMP_RANGE_MISSING_ALLOWED} from './colortemp_range_missing_allowed'; @@ -421,6 +422,9 @@ describe('index.js', () => { // tuya.tz.datapoints is generic, keys cannot be used to determine expose access if (device.toZigbee.includes(tz.datapoints)) return; + // sunricher.tz.setModel is used to switch modelId for devices with conflicting modelId, skip expose access check + if (device.toZigbee.includes(sunricher.tz.setModel)) return; + const toCheck = []; const expss = typeof device.exposes == 'function' ? device.exposes() : device.exposes; for (const expose of expss) {