From ac784442339878382aa413cd1e264ea8e3e8ab2f Mon Sep 17 00:00:00 2001 From: Pekka Vainio Date: Wed, 12 Oct 2022 16:47:10 +0300 Subject: [PATCH] Support more features for Sunricher ZG9092A and fix time sync (#4776) - Push time to device every 24h (from Namron branded same device) - Read seMetering multiplier and divisor on init for energy reporting to work - Add keypad lock, brightness, display auto off - Tune reporting intervals to avoid constant traffic - Fix display auto off boolean reversed value for Namron (same device) --- converters/fromZigbee.js | 3 +- converters/toZigbee.js | 2 +- devices/sunricher.js | 61 ++++++++++++++++++++++++++++++++++------ 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/converters/fromZigbee.js b/converters/fromZigbee.js index b136635275e55..1c64e013bc1d9 100644 --- a/converters/fromZigbee.js +++ b/converters/fromZigbee.js @@ -1760,8 +1760,7 @@ const converters = { result.hysterersis = precisionRound(data[0x100A], 2) / 10; } if (data.hasOwnProperty(0x100B)) { // DisplayAutoOffEnable - const lookup = {0: 'enabled', 1: 'disabled'}; - result.display_auto_off_enabled = lookup[data[0x100B]]; + result.display_auto_off_enabled = data[0x100B] ? 'enabled' : 'disabled'; } if (data.hasOwnProperty(0x2001)) { // AlarmAirTempOverValue result.alarm_airtemp_overvalue = data[0x2001]; diff --git a/converters/toZigbee.js b/converters/toZigbee.js index 60bbfb6f6f829..933250ad58279 100644 --- a/converters/toZigbee.js +++ b/converters/toZigbee.js @@ -3182,7 +3182,7 @@ const converters = { const payload = {0x100A: {value: value * 10, type: 0x20}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); } else if (key==='display_auto_off_enabled') { - const lookup = {'enabled': 0, 'disabled': 1}; + const lookup = {'disabled': 0, 'enabled': 1}; const payload = {0x100B: {value: lookup[value], type: herdsman.Zcl.DataType.enum8}}; await entity.write('hvacThermostat', payload, manufacturerOptions.sunricher); } else if (key==='alarm_airtemp_overvalue') { diff --git a/devices/sunricher.js b/devices/sunricher.js index 9aec41384fe79..f520106e660de 100644 --- a/devices/sunricher.js +++ b/devices/sunricher.js @@ -2,6 +2,7 @@ const exposes = require('../lib/exposes'); const fz = {...require('../converters/fromZigbee'), legacy: require('../lib/legacy').fromZigbee}; const tz = require('../converters/toZigbee'); const reporting = require('../lib/reporting'); +const globalStore = require('../lib/store'); const constants = require('../lib/constants'); const extend = require('../lib/extend'); const utils = require('../lib/utils'); @@ -40,6 +41,14 @@ const fzLocal = { }, }; +function syncTime(endpoint) { + try { + const time = Math.round(((new Date()).getTime() - constants.OneJanuary2000) / 1000 + ((new Date()).getTimezoneOffset() * -1) * 60); + const values = {time: time}; + endpoint.write('genTime', values); + } catch (error) {/* Do nothing*/} +} + module.exports = [ { zigbeeModel: ['SR-ZG9023A-EU'], @@ -351,13 +360,12 @@ module.exports = [ model: 'SR-ZG9092A', vendor: 'Sunricher', description: 'Touch thermostat', - fromZigbee: [fz.thermostat, fz.namron_thermostat, fz.metering, fz.electrical_measurement], + fromZigbee: [fz.thermostat, fz.namron_thermostat, fz.metering, fz.electrical_measurement, fz.namron_hvac_user_interface], toZigbee: [tz.thermostat_occupied_heating_setpoint, tz.thermostat_unoccupied_heating_setpoint, tz.thermostat_occupancy, tz.thermostat_local_temperature_calibration, tz.thermostat_local_temperature, tz.thermostat_outdoor_temperature, tz.thermostat_system_mode, tz.thermostat_control_sequence_of_operation, tz.thermostat_running_state, - tz.namron_thermostat], + tz.namron_thermostat, tz.namron_thermostat_child_lock], exposes: [ - e.local_temperature(), exposes.numeric('outdoor_temperature', ea.STATE_GET).withUnit('°C') .withDescription('Current temperature measured from the floor sensor'), exposes.climate() @@ -369,7 +377,11 @@ module.exports = [ .withRunningState(['idle', 'heat']), exposes.binary('away_mode', ea.ALL, 'ON', 'OFF') .withDescription('Enable/disable away mode'), + exposes.binary('child_lock', ea.ALL, 'UNLOCK', 'LOCK') + .withDescription('Enables/disables physical input on the device'), e.power(), e.current(), e.voltage(), e.energy(), + exposes.enum('lcd_brightness', ea.ALL, ['low', 'mid', 'high']) + .withDescription('OLED brightness when operating the buttons. Default: Medium.'), exposes.enum('button_vibration_level', ea.ALL, ['off', 'low', 'high']) .withDescription('Key beep volume and vibration level. Default: Low.'), exposes.enum('floor_sensor_type', ea.ALL, ['10k', '15k', '50k', '100k', '12k']) @@ -398,11 +410,24 @@ module.exports = [ .withUnit('°C') .withValueMin(0.5).withValueMax(2).withValueStep(0.1) .withDescription('Hysteresis setting, between 0.5 and 2 in 0.1 °C. Default: 0.5.'), + exposes.enum('display_auto_off_enabled', ea.ALL, ['disabled', 'enabled']), exposes.numeric('alarm_airtemp_overvalue', ea.ALL) .withUnit('°C') .withValueMin(20).withValueMax(60) .withDescription('Room temperature alarm threshold, between 20 and 60 in °C. 0 means disabled. Default: 45.'), ], + onEvent: async (type, data, device, options) => { + if (type === 'stop') { + clearInterval(globalStore.getValue(device, 'time')); + globalStore.clearValue(device, 'time'); + } else if (!globalStore.hasValue(device, 'time')) { + const endpoint = device.getEndpoint(1); + const hours24 = 1000 * 60 * 60 * 24; + // Device does not ask for the time with binding, therefore we write the time every 24 hours + const interval = setInterval(async () => syncTime(endpoint), hours24); + globalStore.putValue(device, 'time', interval); + } + }, configure: async (device, coordinatorEndpoint, logger) => { const endpoint = device.getEndpoint(1); const binds = [ @@ -415,6 +440,7 @@ module.exports = [ await reporting.thermostatTemperature(endpoint); await reporting.thermostatOccupiedHeatingSetpoint(endpoint); await reporting.thermostatUnoccupiedHeatingSetpoint(endpoint); + await reporting.thermostatKeypadLockMode(endpoint); await endpoint.configureReporting('hvacThermostat', [{ attribute: 'ocupancy', @@ -425,16 +451,24 @@ module.exports = [ await endpoint.read('haElectricalMeasurement', ['acVoltageMultiplier', 'acVoltageDivisor', 'acCurrentMultiplier']); await endpoint.read('haElectricalMeasurement', ['acCurrentDivisor']); + await endpoint.read('seMetering', ['multiplier', 'divisor']); - await reporting.activePower(endpoint); - await reporting.rmsCurrent(endpoint, {min: 10, change: 10}); - await reporting.rmsVoltage(endpoint, {min: 10}); + await reporting.activePower(endpoint, {min: 30, change: 10}); // Min report change 10W + await reporting.rmsCurrent(endpoint, {min: 30, change: 50}); // Min report change 0.05A + await reporting.rmsVoltage(endpoint, {min: 30, change: 20}); // Min report change 2V + await reporting.readMeteringMultiplierDivisor(endpoint); await reporting.currentSummDelivered(endpoint); // Custom attributes const options = {manufacturerCode: 0x1224}; // Sunricher Manufacturer Code - // OperateDisplayLcdBrightnesss - removed as it has no effect + // OperateDisplayLcdBrightnesss + await endpoint.configureReporting('hvacThermostat', [{ + attribute: {ID: 0x1000, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null}], + options); // ButtonVibrationLevel await endpoint.configureReporting('hvacThermostat', [{ attribute: {ID: 0x1001, type: 0x30}, @@ -507,6 +541,14 @@ module.exports = [ reportableChange: 0}], options); + // DisplayAutoOffEnable + await endpoint.configureReporting('hvacThermostat', [{ + attribute: {ID: 0x100B, type: 0x30}, + minimumReportInterval: 0, + maximumReportInterval: constants.repInterval.HOUR, + reportableChange: null}], + options); + // AlarmAirTempOverValue await endpoint.configureReporting('hvacThermostat', [{ attribute: {ID: 0x2001, type: 0x20}, @@ -523,12 +565,13 @@ module.exports = [ options); // Device does not asks for the time with binding, we need to write time during configure + syncTime(endpoint); // Trigger initial read await endpoint.read('hvacThermostat', ['systemMode', 'runningState', 'occupiedHeatingSetpoint']); - await endpoint.read('hvacThermostat', [0x1001, 0x1002, 0x1003], options); + await endpoint.read('hvacThermostat', [0x1000, 0x1001, 0x1002, 0x1003], options); await endpoint.read('hvacThermostat', [0x1004, 0x1005, 0x1006, 0x1007], options); - await endpoint.read('hvacThermostat', [0x1008, 0x1009, 0x100A], options); + await endpoint.read('hvacThermostat', [0x1008, 0x1009, 0x100A, 0x100B], options); await endpoint.read('hvacThermostat', [0x2001, 0x2002], options); }, },