diff --git a/converters/fromZigbee.js b/converters/fromZigbee.js index 5d8f2e376b674..8a88057df7433 100644 --- a/converters/fromZigbee.js +++ b/converters/fromZigbee.js @@ -18,6 +18,7 @@ const tuya = require('../lib/tuya'); const globalStore = require('../lib/store'); const constants = require('../lib/constants'); const libColor = require('../lib/color'); +const utils = require('../lib/utils'); const converters = { // #region Generic/recommended converters @@ -5570,6 +5571,53 @@ const converters = { return result; }, }, + schneider_ui_action: { + cluster: 'wiserDeviceInfo', + type: 'attributeReport', + convert: (model, msg, publish, options, meta) => { + if (hasAlreadyProcessedMessage(msg)) return; + + const data = msg.data['deviceInfo'].split(','); + if (data[0] === 'UI' && data[1]) { + const result = {action: utils.toSnakeCase(data[1])}; + + let screenAwake = globalStore.getValue(msg.endpoint, 'screenAwake'); + screenAwake = screenAwake != undefined ? screenAwake : false; + let keypadLocked = msg.endpoint.getClusterAttributeValue('hvacUserInterfaceCfg', 'keypadLockout'); + keypadLocked = keypadLocked != undefined ? keypadLocked != 0 : false; + + // Emulate UI temperature update + if (data[1] === 'ScreenWake') { + globalStore.putValue(msg.endpoint, 'screenAwake', true); + } else if (data[1] === 'ScreenSleep') { + globalStore.putValue(msg.endpoint, 'screenAwake', false); + } else if (screenAwake && !keypadLocked) { + let occupiedHeatingSetpoint = msg.endpoint.getClusterAttributeValue('hvacThermostat', 'occupiedHeatingSetpoint'); + occupiedHeatingSetpoint = occupiedHeatingSetpoint != null ? occupiedHeatingSetpoint : 400; + + if (data[1] === 'ButtonPressMinusDown') { + occupiedHeatingSetpoint -= 50; + } else if (data[1] === 'ButtonPressPlusDown') { + occupiedHeatingSetpoint += 50; + } + + msg.endpoint.saveClusterAttributeKeyValue('hvacThermostat', {occupiedHeatingSetpoint: occupiedHeatingSetpoint}); + result.occupied_heating_setpoint = occupiedHeatingSetpoint/100; + } + + return result; + } + }, + }, + schneider_temperature: { + cluster: 'msTemperatureMeasurement', + type: ['attributeReport', 'readResponse'], + convert: (model, msg, publish, options, meta) => { + const temperature = parseFloat(msg.data['measuredValue']) / 100.0; + const property = postfixWithEndpointName('local_temperature', msg, model); + return {[property]: calibrateAndPrecisionRoundOptions(temperature, options, 'temperature')}; + }, + }, // #endregion diff --git a/converters/toZigbee.js b/converters/toZigbee.js index cc3924a2304f1..201c8c8e9a4dd 100644 --- a/converters/toZigbee.js +++ b/converters/toZigbee.js @@ -4886,6 +4886,52 @@ const converters = { await entity.read('schneiderSpecificPilotMode', ['pilotMode'], {manufacturerCode: 0x105e}); }, }, + schneider_temperature_measured_value: { + key: ['temperature_measured_value'], + convertSet: async (entity, key, value, meta) => { + await entity.report('msTemperatureMeasurement', {'measuredValue': Math.round(value*100)}); + }, + }, + schneider_thermostat_system_mode: { + key: ['system_mode'], + convertSet: async (entity, key, value, meta) => { + const systemMode = utils.getKey(constants.thermostatSystemModes, value, undefined, Number); + entity.saveClusterAttributeKeyValue('hvacThermostat', {systemMode: systemMode}); + return {state: {system_mode: value}}; + }, + }, + schneider_thermostat_occupied_heating_setpoint: { + key: ['occupied_heating_setpoint'], + convertSet: async (entity, key, value, meta) => { + const occupiedHeatingSetpoint = (Math.round((value * 2).toFixed(1)) / 2).toFixed(1) * 100; + entity.saveClusterAttributeKeyValue('hvacThermostat', {occupiedHeatingSetpoint: occupiedHeatingSetpoint}); + return {state: {occupied_heating_setpoint: value}}; + }, + }, + schneider_thermostat_control_sequence_of_operation: { + key: ['control_sequence_of_operation'], + convertSet: async (entity, key, value, meta) => { + const val = utils.getKey(constants.thermostatControlSequenceOfOperations, value, value, Number); + entity.saveClusterAttributeKeyValue('hvacThermostat', {ctrlSeqeOfOper: val}); + return {state: {control_sequence_of_operation: value}}; + }, + }, + schneider_thermostat_pi_heating_demand: { + key: ['pi_heating_demand'], + convertSet: async (entity, key, value, meta) => { + entity.saveClusterAttributeKeyValue('hvacThermostat', {pIHeatingDemand: value}); + return {state: {pi_heating_demand: value}}; + }, + }, + schneider_thermostat_keypad_lockout: { + key: ['keypad_lockout'], + convertSet: async (entity, key, value, meta) => { + const keypadLockout = utils.getKey(constants.keypadLockoutMode, value, value, Number); + entity.write('hvacUserInterfaceCfg', {keypadLockout}, {sendWhenActive: true}); + entity.saveClusterAttributeKeyValue('hvacUserInterfaceCfg', {keypadLockout}); + return {state: {keypad_lockout: value}}; + }, + }, ZNCJMB14LM: { key: ['theme', 'standby_enabled', diff --git a/devices/schneider_electric.js b/devices/schneider_electric.js index f49d597c44480..d84ec17f36a57 100644 --- a/devices/schneider_electric.js +++ b/devices/schneider_electric.js @@ -197,10 +197,12 @@ module.exports = [ vendor: 'Schneider Electric', description: 'Heating thermostat', fromZigbee: [fz.thermostat, fz.metering, fz.schneider_pilot_mode], - toZigbee: [tz.thermostat_system_mode, tz.thermostat_running_state, tz.thermostat_local_temperature, - tz.thermostat_occupied_heating_setpoint, tz.thermostat_control_sequence_of_operation, tz.schneider_pilot_mode], + toZigbee: [tz.schneider_temperature_measured_value, tz.thermostat_system_mode, tz.thermostat_running_state, + tz.thermostat_local_temperature, tz.thermostat_occupied_heating_setpoint, tz.thermostat_control_sequence_of_operation, + tz.schneider_pilot_mode, tz.schneider_temperature_measured_value], exposes: [e.power(), e.energy(), - exposes.enum('schneider_pilot_mode', ea.ALL, ['relay', 'pilot']).withDescription('Controls piloting mode'), + exposes.enum('schneider_pilot_mode', ea.ALL, ['contactor', 'pilot']).withDescription('Controls piloting mode'), + exposes.numeric('temperature_measured_value', ea.SET), exposes.climate().withSetpoint('occupied_heating_setpoint', 4, 30, 0.5).withLocalTemperature() .withSystemMode(['off', 'auto', 'heat']).withRunningState(['idle', 'heat']).withPiHeatingDemand()], configure: async (device, coordinatorEndpoint, logger) => { @@ -214,4 +216,30 @@ module.exports = [ await reporting.currentSummDelivered(endpoint2, {min: 0, max: 60, change: 1}); }, }, + { + fingerprint: [{modelID: 'Thermostat', manufacturerName: 'Schneider Electric'}], + model: 'CCTFR6400', + vendor: 'Schneider Electric', + description: 'Temperature/Humidity measurement with thermostat interface', + fromZigbee: [fz.battery, fz.schneider_temperature, fz.humidity, fz.thermostat, fz.schneider_ui_action], + toZigbee: [tz.schneider_thermostat_system_mode, tz.schneider_thermostat_occupied_heating_setpoint, + tz.schneider_thermostat_control_sequence_of_operation, tz.schneider_thermostat_pi_heating_demand, + tz.schneider_thermostat_keypad_lockout], + exposes: [e.keypad_lockout().withAccess(ea.STATE_SET), e.humidity(), e.battery(), e.battery_voltage(), + e.action(['screen_sleep', 'screen_wake', 'button_press_plus_down', 'button_press_center_down', 'button_press_minus_down']), + exposes.climate().withSetpoint('occupied_heating_setpoint', 4, 30, 0.5, ea.SET).withLocalTemperature(ea.STATE) + .withPiHeatingDemand(ea.SET)], + meta: {battery: {dontDividePercentage: true}}, + configure: async (device, coordinatorEndpoint, logger) => { + const endpoint1 = device.getEndpoint(1); + await reporting.bind(endpoint1, coordinatorEndpoint, + ['genPowerCfg', 'hvacThermostat', 'msTemperatureMeasurement', 'msRelativeHumidity']); + await reporting.temperature(endpoint1); + await reporting.humidity(endpoint1); + await reporting.batteryPercentageRemaining(endpoint1); + endpoint1.saveClusterAttributeKeyValue('genBasic', {zclVersion: 3}); + endpoint1.saveClusterAttributeKeyValue('hvacThermostat', {schneiderWiserSpecific: 1, systemMode: 4, ctrlSeqeOfOper: 2}); + endpoint1.saveClusterAttributeKeyValue('hvacUserInterfaceCfg', {keypadLockout: 0}); + }, + }, ];