From 2fe8e40dd3d7be381fbe5b4b6943fddf191c8d75 Mon Sep 17 00:00:00 2001 From: Odd-Roar Wangen Date: Thu, 7 Apr 2022 17:36:57 +0200 Subject: [PATCH] Add 4500994 (#4098) * Added support for Namron Touch Termostat model 4512737 New device from Namron supported https://www.namron.com/products/namron-zigbee-touch-termostat-16a-hvit/ * Removed duplicated code * Update namron.js * Added support for Namron Touch Termostat model 4512737 New device from Namron supported https://www.namron.com/products/namron-zigbee-touch-termostat-16a-hvit/ * Removed duplicated code * Update namron.js * Added basic support for Connecte thermostat Added fromzigbee support * Cleaned up exposes and fromzigbee Fixed exposes and some attributes * Fixing exposes take 2 Need to comment out preset and sensor until i get tozigbee working. * Added support for Connecte Smart Thermostat * Fixed failing unittests * Update connecte.js Co-authored-by: Koen Kanters --- converters/fromZigbee.js | 49 +++++++++++++++++++++++++++++++ converters/toZigbee.js | 62 ++++++++++++++++++++++++++++++++++++++++ devices/connecte.js | 50 ++++++++++++++++++++++++++++++++ lib/tuya.js | 14 +++++++++ 4 files changed, 175 insertions(+) create mode 100644 devices/connecte.js diff --git a/converters/fromZigbee.js b/converters/fromZigbee.js index 3d4c5e12ea89b..2b15105dd5138 100644 --- a/converters/fromZigbee.js +++ b/converters/fromZigbee.js @@ -4272,6 +4272,55 @@ const converters = { } }, }, + connecte_thermostat: { + cluster: 'manuSpecificTuya', + type: ['commandDataResponse', 'commandDataReport'], + convert: (model, msg, publish, options, meta) => { + const dpValue = tuya.firstDpValue(msg, meta, 'connecte_thermostat'); + const dp = dpValue.dp; + const value = tuya.getDataValue(dpValue); + + switch (dp) { + case tuya.dataPoints.connecteState: + return {state: value ? 'ON' : 'OFF'}; + case tuya.dataPoints.connecteMode: + switch (value) { + case 0: // manual + return {system_mode: 'heat', away_mode: 'OFF'}; + case 1: // home (auto) + return {system_mode: 'auto', away_mode: 'OFF'}; + case 2: // away (auto) + return {system_mode: 'auto', away_mode: 'ON'}; + } + break; + case tuya.dataPoints.connecteHeatingSetpoint: + return {current_heating_setpoint: value}; + case tuya.dataPoints.connecteLocalTemp: + return {local_temperature: value}; + case tuya.dataPoints.connecteTempCalibration: + return {local_temperature_calibration: value}; + case tuya.dataPoints.connecteChildLock: + return {child_lock: value ? 'LOCK' : 'UNLOCK'}; + case tuya.dataPoints.connecteTempFloor: + return {external_temperature: value}; + case tuya.dataPoints.connecteSensorType: + return {sensor: {0: 'internal', 1: 'external', 2: 'both'}[value]}; + case tuya.dataPoints.connecteHysteresis: + return {hysteresis: value}; + case tuya.dataPoints.connecteRunningState: + return {running_state: value ? 'heat' : 'idle'}; + case tuya.dataPoints.connecteTempProgram: + break; + case tuya.dataPoints.connecteOpenWindow: + return {window_detection: value ? 'ON' : 'OFF'}; + case tuya.dataPoints.connecteMaxProtectTemp: + return {max_temperature_protection: value}; + default: + meta.logger.warn(`zigbee-herdsman-converters:connecte_thermostat: Unrecognized DP #${ + dp} with data ${JSON.stringify(dpValue)}`); + } + }, + }, saswell_thermostat: { cluster: 'manuSpecificTuya', type: ['commandDataResponse', 'commandDataReport'], diff --git a/converters/toZigbee.js b/converters/toZigbee.js index dec7c11232574..ad5352342901c 100644 --- a/converters/toZigbee.js +++ b/converters/toZigbee.js @@ -3005,6 +3005,68 @@ const converters = { await entity.read('closuresWindowCovering', [isPosition ? 'currentPositionLiftPercentage' : 'currentPositionTiltPercentage']); }, }, + connecte_thermostat: { + key: [ + 'child_lock', 'current_heating_setpoint', 'local_temperature_calibration', 'max_temperature_protection', 'window_detection', + 'hysteresis', 'state', 'away_mode', 'sensor', 'system_mode', + ], + convertSet: async (entity, key, value, meta) => { + switch (key) { + case 'state': + await tuya.sendDataPointBool(entity, tuya.dataPoints.connecteState, value === 'ON'); + break; + case 'child_lock': + await tuya.sendDataPointBool(entity, tuya.dataPoints.connecteChildLock, value === 'LOCK'); + break; + case 'local_temperature_calibration': + if (value < 0) value = 0xFFFFFFFF + value + 1; + await tuya.sendDataPointValue(entity, tuya.dataPoints.connecteTempCalibration, value); + break; + case 'hysteresis': + // value = Math.round(value * 10); + await tuya.sendDataPointValue(entity, tuya.dataPoints.connecteHysteresis, value); + break; + case 'max_temperature_protection': + await tuya.sendDataPointValue(entity, tuya.dataPoints.connecteMaxProtectTemp, Math.round(value)); + break; + case 'current_heating_setpoint': + await tuya.sendDataPointValue(entity, tuya.dataPoints.connecteHeatingSetpoint, value); + break; + case 'sensor': + await tuya.sendDataPointEnum( + entity, + tuya.dataPoints.connecteSensorType, + {'internal': 0, 'external': 1, 'both': 2}[value]); + break; + case 'system_mode': + switch (value) { + case 'heat': + await tuya.sendDataPointEnum(entity, tuya.dataPoints.connecteMode, 0 /* manual */); + break; + case 'auto': + await tuya.sendDataPointEnum(entity, tuya.dataPoints.connecteMode, 1 /* auto */); + break; + } + break; + case 'away_mode': + switch (value) { + case 'ON': + await tuya.sendDataPointEnum(entity, tuya.dataPoints.connecteMode, 2 /* auto */); + break; + case 'OFF': + await tuya.sendDataPointEnum(entity, tuya.dataPoints.connecteMode, 0 /* manual */); + break; + } + break; + case 'window_detection': + await tuya.sendDataPointBool(entity, tuya.dataPoints.connecteOpenWindow, value === 'ON'); + break; + default: // Unknown key + throw new Error(`Unhandled key toZigbee.connecte_thermostat ${key}`); + } + }, + }, + moes_thermostat_child_lock: { key: ['child_lock'], convertSet: async (entity, key, value, meta) => { diff --git a/devices/connecte.js b/devices/connecte.js new file mode 100644 index 0000000000000..0a7d8ccaeba42 --- /dev/null +++ b/devices/connecte.js @@ -0,0 +1,50 @@ +const exposes = require('../lib/exposes'); +const fz = {...require('../converters/fromZigbee'), legacy: require('../lib/legacy').fromZigbee}; +const tz = require('../converters/toZigbee'); +const tuya = require('../lib/tuya'); +const e = exposes.presets; +const ea = exposes.access; + +module.exports = [ + { + fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_4hbx5cvx'}], + model: '4500994', + vendor: 'Connecte', + description: 'Smart thermostat', + fromZigbee: [fz.connecte_thermostat], + toZigbee: [tz.connecte_thermostat], + onEvent: tuya.onEventSetTime, + configure: async (device, coordinatorEndpoint, logger) => { + const endpoint = device.getEndpoint(1); + // Do a "magic" read on the basic cluster to trigger the thermostat start reporting. + await endpoint.read('genBasic', ['manufacturerName', 'zclVersion', 'appVersion', 'modelId', 'powerSource', 0xfffe]); + }, + exposes: [ + exposes.binary('state', ea.STATE_SET, 'ON', 'OFF') + .withDescription('On/off state of the switch'), + e.child_lock(), + e.window_detection(), + exposes.climate() + .withSetpoint('current_heating_setpoint', 5, 35, 1, ea.STATE_SET) + .withLocalTemperature(ea.STATE) + .withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET) + .withSystemMode(['heat', 'auto'], ea.STATE_SET) + .withRunningState(['idle', 'heat'], ea.STATE) + .withAwayMode() + .withSensor(['internal', 'external', 'both']), + exposes.numeric('external_temperature', ea.STATE) + .withUnit('°C') + .withDescription('Current temperature measured on the external sensor (floor)'), + exposes.numeric('hysteresis', ea.STATE_SET) + .withDescription('The difference between the temperature at which the thermostat switches off, ' + + 'and the temperature at which it switches on again.') + .withValueMin(1) + .withValueMax(9), + exposes.numeric('max_temperature_protection', ea.STATE_SET) + .withUnit('°C') + .withDescription('Max guarding temperature') + .withValueMin(20) + .withValueMax(95), + ], + }, +]; diff --git a/lib/tuya.js b/lib/tuya.js index dd0c745309474..3698366425cbc 100644 --- a/lib/tuya.js +++ b/lib/tuya.js @@ -645,6 +645,20 @@ const dataPoints = { // Moes switch with optional neutral moesSwitchPowerOnBehavior: 14, moesSwitchIndicateLight: 15, + // Connecte thermostat + connecteState: 1, + connecteMode: 2, + connecteHeatingSetpoint: 16, + connecteLocalTemp: 24, + connecteTempCalibration: 28, + connecteChildLock: 30, + connecteTempFloor: 101, + connecteSensorType: 102, + connecteHysteresis: 103, + connecteRunningState: 104, + connecteTempProgram: 105, + connecteOpenWindow: 106, + connecteMaxProtectTemp: 107, }; const thermostatWeekFormat = {