Skip to content

Commit

Permalink
HY08WE Haozee Wall Thermostat (#1694)
Browse files Browse the repository at this point in the history
* TS0216. Audible Alarm. Sound and Flash Siren
Koenkk/zigbee2mqtt#4655

* Update fromZigbee.js

* Update devices.js

* HY08WE Haozee Wall Thermostat
https://aliexpress.ru/item/1005001397598439.html

* fix lint

* fix lint again

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
  • Loading branch information
kirovilya and Koenkk committed Oct 22, 2020
1 parent 3aabf40 commit 7967aaa
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 0 deletions.
131 changes: 131 additions & 0 deletions converters/fromZigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -6225,6 +6225,137 @@ const converters = {
return result;
},
},
hy_thermostat: {
cluster: 'manuSpecificTuyaDimmer',
type: ['commandSetDataResponse', 'commandGetData'],
convert: (model, msg, publish, options, meta) => {
const dp = msg.data.dp;
const data = msg.data.data;
const dataAsDecNumber = utils.convertMultiByteNumberPayloadToSingleDecimalNumber(data);
let temperature;
let lookup;

switch (dp) {
case 119: // schedule for workdays [5,9,12,8,0,15,10,0,15]
return {workdays: [
{hour: data[0], minute: data[1], temperature: data[2]},
{hour: data[3], minute: data[4], temperature: data[5]},
{hour: data[6], minute: data[7], temperature: data[8]},
], range: 'am'};
case 120: // schedule for workdays [15,0,25,145,2,17,22,50,14]
return {workdays: [
{hour: data[0], minute: data[1], temperature: data[2]},
{hour: data[3], minute: data[4], temperature: data[5]},
{hour: data[6], minute: data[7], temperature: data[8]},
], range: 'pm'};
case 121: // schedule for holidays [5,5,20,8,4,13,11,30,15]
return {holidays: [
{hour: data[0], minute: data[1], temperature: data[2]},
{hour: data[3], minute: data[4], temperature: data[5]},
{hour: data[6], minute: data[7], temperature: data[8]},
], range: 'am'};
case 122: // schedule for holidays [13,30,15,17,0,15,22,0,15]
return {holidays: [
{hour: data[0], minute: data[1], temperature: data[2]},
{hour: data[3], minute: data[4], temperature: data[5]},
{hour: data[6], minute: data[7], temperature: data[8]},
], range: 'pm'};
case 358: // heating
return {heating: (dataAsDecNumber) ? 'ON' : 'OFF'};
case 362: // max temperature protection
return {max_temperature_protection: (dataAsDecNumber) ? 'ON' : 'OFF'};
case 363: // min temperature protection
return {min_temperature_protection: (dataAsDecNumber) ? 'ON' : 'OFF'};
case 381: // 0x017D work state
return {state: (dataAsDecNumber) ? 'ON' : 'OFF'};
case 385: // 0x0181 Changed child lock status
return {child_lock: dataAsDecNumber ? 'LOCKED' : 'UNLOCKED'};
case 615: // external sensor temperature
temperature = (dataAsDecNumber / 10).toFixed(1);
return {external_temperature: temperature};
case 616: // away preset days
return {away_preset_days: dataAsDecNumber};
case 617: // away preset temperature
return {away_preset_temperature: dataAsDecNumber};
case 621: // 0x026D Temperature correction
temperature = (dataAsDecNumber / 10).toFixed(1);
return {local_temperature_calibration: temperature};
case 622: // 0x026E Temperature hysteresis
temperature = (dataAsDecNumber / 10).toFixed(1);
return {hysteresis: temperature};
case 623: // 0x026F Temperature protection hysteresis
return {hysteresis_for_protection: dataAsDecNumber};
case 624: // 0x027A max temperature for protection
return {max_temperature_for_protection: dataAsDecNumber};
case 625: // 0x027B min temperature for protection
return {min_temperature_for_protection: dataAsDecNumber};
case 626: // 0x027C max temperature limit
return {max_temperature: dataAsDecNumber};
case 627: // 0x027D min temperature limit
return {min_temperature: dataAsDecNumber};
case 638: // 0x027E Changed target temperature
temperature = (dataAsDecNumber / 10).toFixed(1);
return {current_heating_setpoint: temperature};
case 639: // 0x027F MCU reporting room temperature
temperature = (dataAsDecNumber / 10).toFixed(1);
return {local_temperature: temperature};
case 1140: // Sensor type
lookup = {
0: 'internal',
1: 'external',
2: 'both',
};
return {sensor_type: lookup[dataAsDecNumber]};
case 1141: // 0x0475 State after power on
lookup = {
0: 'restore',
1: 'off',
2: 'on',
};
return {power_on_behavior: lookup[dataAsDecNumber]};
case 1142: // 0x0476 Week select 0 - 5 days, 1 - 6 days, 2 - 7 days
return {week: common.TuyaThermostatWeekFormat[dataAsDecNumber]};
case 1152: // 0x0480 mode
lookup = {
0: 'manual',
1: 'auto',
2: 'away',
};
return {system_mode: lookup[dataAsDecNumber]};
case 1410: // [16] [0]
return {alarm: (dataAsDecNumber > 0) ? true : false};
default: // The purpose of the codes 1041 & 1043 are still unknown
console.log(`zigbee-herdsman-converters:hy_thermostat: NOT RECOGNIZED DP #${
dp} with data ${JSON.stringify(data)}`);
}
},
},
hy_thermostat_on_set_data: {
cluster: 'manuSpecificTuyaDimmer',
type: 'commandSetDataResponse',
convert: (model, msg, publish, options, meta) => {
return converters.hy_thermostat.convert(model, msg, publish, options, meta);
},
},
hy_set_time_request: {
cluster: 'manuSpecificTuyaDimmer',
type: ['commandSetTimeRequest'],
convert: async (model, msg, publish, options, meta) => {
const OneJanuary2000 = new Date('January 01, 2000 00:00:00 UTC+00:00').getTime();
const currentTime = new Date().getTime();
const utcTime = Math.round((currentTime - OneJanuary2000) / 1000);
const localTime = Math.round(currentTime / 1000) - (new Date()).getTimezoneOffset() * 60;
const endpoint = msg.device.getEndpoint(1);
const payload = {
payloadSize: 8,
payload: [
...utils.convertDecimalValueTo4ByteHexArray(utcTime),
...utils.convertDecimalValueTo4ByteHexArray(localTime),
],
};
await endpoint.command('manuSpecificTuyaDimmer', 'setTime', payload, {});
},
},

// Ignore converters (these message dont need parsing).
ignore_onoff_report: {
Expand Down
75 changes: 75 additions & 0 deletions converters/toZigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -3441,6 +3441,81 @@ const converters = {
);
},
},
hy_thermostat: {
key: [
'child_lock', 'current_heating_setpoint', 'local_temperature_calibration',
'max_temperature_protection', 'min_temperature_protection', 'state',
'hysteresis', 'hysteresis_for_protection',
'max_temperature_for_protection', 'min_temperature_for_protection',
'max_temperature', 'min_temperature',
'sensor_type', 'power_on_behavior', 'week', 'system_mode',
'away_preset_days', 'away_preset_temperature',
],
convertSet: async (entity, key, value, meta) => {
switch (key) {
case 'max_temperature_protection':
sendTuyaCommand(entity, 362, 0, [1, value==='ON' ? 1 : 0]);
break;
case 'min_temperature_protection':
sendTuyaCommand(entity, 363, 0, [1, value==='ON' ? 1 : 0]);
break;
case 'state':
sendTuyaCommand(entity, 381, 0, [1, value==='ON' ? 1 : 0]);
break;
case 'child_lock':
sendTuyaCommand(entity, 385, 0, [1, value==='LOCKED' ? 1 : 0]);
break;
case 'away_preset_days':
sendTuyaCommand(entity, 616, 0, [4, 0, 0, ...utils.convertDecimalValueTo2ByteHexArray(value)]);
break;
case 'away_preset_temperature':
sendTuyaCommand(entity, 617, 0, [4, 0, 0, ...utils.convertDecimalValueTo2ByteHexArray(value)]);
break;
case 'local_temperature_calibration':
value = Math.round(value * 10);
if (value < 0) value = 0xFFFFFFFF + value + 1;
sendTuyaCommand(entity, 621, 0, [4, ...utils.convertDecimalValueTo4ByteHexArray(value)]);
break;
case 'hysteresis':
value = Math.round(value * 10);
sendTuyaCommand(entity, 622, 0, [4, ...utils.convertDecimalValueTo4ByteHexArray(value)]);
break;
case 'hysteresis_for_protection':
sendTuyaCommand(entity, 623, 0, [4, 0, 0, ...utils.convertDecimalValueTo2ByteHexArray(value)]);
break;
case 'max_temperature_for_protection':
sendTuyaCommand(entity, 624, 0, [4, 0, 0, ...utils.convertDecimalValueTo2ByteHexArray(value)]);
break;
case 'min_temperature_for_protection':
sendTuyaCommand(entity, 625, 0, [4, 0, 0, ...utils.convertDecimalValueTo2ByteHexArray(value)]);
break;
case 'max_temperature':
sendTuyaCommand(entity, 626, 0, [4, 0, 0, ...utils.convertDecimalValueTo2ByteHexArray(value)]);
break;
case 'min_temperature':
sendTuyaCommand(entity, 627, 0, [4, 0, 0, ...utils.convertDecimalValueTo2ByteHexArray(value)]);
break;
case 'current_heating_setpoint':
value = Math.round(value * 10);
sendTuyaCommand(entity, 638, 0, [4, 0, 0, ...utils.convertDecimalValueTo2ByteHexArray(value)]);
break;
case 'sensor_type':
sendTuyaCommand(entity, 1140, 0, [1, {'internal': 0, 'external': 1, 'both': 2}[value]]);
break;
case 'power_on_behavior':
sendTuyaCommand(entity, 1141, 0, [1, {'restore': 0, 'off': 1, 'on': 2}[value]]);
break;
case 'week':
sendTuyaCommand(entity, 1142, 0, [1, utils.getKeyByValue(common.TuyaThermostatWeekFormat, value, value)]);
break;
case 'system_mode':
sendTuyaCommand(entity, 1152, 0, [1, {'manual': 0, 'auto': 1, 'away': 2}[value]]);
break;
default: // Unknown key
console.log(`Unhandled key ${key}`);
}
},
},

// Not a converter, can be used by tests to clear the store.
__clearStore__: () => {
Expand Down
12 changes: 12 additions & 0 deletions devices.js
Original file line number Diff line number Diff line change
Expand Up @@ -2040,6 +2040,18 @@ const devices = [
await bind(endpoint, coordinatorEndpoint, ['genPowerCfg']);
},
},
{
fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_znzs7yaw'}],
model: 'HY08WE',
vendor: 'TuYa',
description: 'Wall-mount thermostat',
supports: 'thermostat, temperature',
fromZigbee: [
fz.hy_thermostat, fz.hy_thermostat_on_set_data, fz.ignore_basic_report,
fz.hy_set_time_request,
],
toZigbee: [tz.hy_thermostat],
},

// Neo
{
Expand Down

0 comments on commit 7967aaa

Please sign in to comment.