@@ -3430,60 +3430,74 @@ const definitions: DefinitionWithExtend[] = [
3430
3430
description: 'FCU thermostat temperature controller',
3431
3431
fromZigbee: [tuya.fz.datapoints],
3432
3432
toZigbee: [tuya.tz.datapoints],
3433
- onEvent: tuya.onEventSetTime ,
3433
+ onEvent: tuya.onEventSetLocalTime ,
3434
3434
configure: tuya.configureMagicPacket,
3435
- exposes: [
3436
- e
3437
- .climate()
3438
- .withLocalTemperature(ea.STATE)
3439
- .withSystemMode(['off', 'cool', 'heat', 'fan_only'], ea.STATE_SET)
3440
- .withFanMode(['low', 'medium', 'high', 'auto'], ea.STATE_SET)
3441
- .withSetpoint('current_heating_setpoint', 5, 35, 1, ea.STATE_SET)
3442
- .withPreset(['auto', 'manual'])
3443
- .withLocalTemperatureCalibration(-3, 3, 1, ea.STATE_SET),
3444
- e
3445
- .numeric('deadzone_temperature', ea.STATE_SET)
3446
- .withUnit('°C')
3447
- .withValueMax(5)
3448
- .withValueMin(1)
3449
- .withValueStep(1)
3450
- .withPreset('default', 1, 'Default value')
3451
- .withDescription('The delta between local_temperature and current_heating_setpoint to trigger activity'),
3452
- e.child_lock(),
3435
+ options: [
3453
3436
e
3454
- .text('schedule', ea.STATE_SET)
3455
- .withDescription(
3456
- 'Schedule will work with "auto" preset. In this mode, the device executes ' +
3457
- 'a preset week programming temperature time and temperature. Schedule can contains 12 segments. ' +
3458
- 'All 12 segments should be defined. It should be defined in the following format: "hh:mm/tt". ' +
3459
- 'Segments should be divided by space symbol. ' +
3460
- 'Example: "06:00/20 11:30/21 13:30/22 17:30/23 06:00/24 12:00/23 14:30/22 17:30/21 06:00/19 12:30/20 14:30/21 18:30/20"',
3461
- ),
3437
+ .enum('control_sequence_of_operation', ea.SET, ['cooling_only', 'cooling_and_heating_4-pipes'])
3438
+ .withDescription('Operating environment of the thermostat'),
3462
3439
],
3440
+ exposes: (device, options) => {
3441
+ const system_modes = ['off', 'cool', 'heat', 'fan_only'];
3442
+
3443
+ // Device can operate either in 2-pipe or 4-pipe configuration
3444
+ // For 2-pipe configurations remove 'heat' mode
3445
+ switch (options?.control_sequence_of_operation) {
3446
+ case 'cooling_only':
3447
+ system_modes.splice(2, 1);
3448
+ break;
3449
+ }
3450
+
3451
+ return [
3452
+ e
3453
+ .climate()
3454
+ .withLocalTemperature(ea.STATE)
3455
+ .withSystemMode(system_modes, ea.STATE_SET)
3456
+ .withFanMode(['low', 'medium', 'high', 'auto'], ea.STATE_SET)
3457
+ .withSetpoint('current_heating_setpoint', 5, 35, 1, ea.STATE_SET)
3458
+ .withPreset(['auto', 'manual'])
3459
+ .withLocalTemperatureCalibration(-3, 3, 1, ea.STATE_SET),
3460
+ e.child_lock(),
3461
+ e
3462
+ .composite('schedule', 'schedule', ea.STATE_SET)
3463
+ .withFeature(e.text('weekdays', ea.SET).withDescription('Schedule (1-5), 4 periods in format "hh:mm/tt".'))
3464
+ .withFeature(e.text('saturday', ea.SET).withDescription('Schedule (6), 4 periods in format "hh:mm/tt".'))
3465
+ .withFeature(e.text('sunday', ea.SET).withDescription('Schedule (7), 4 periods in format "hh:mm/tt".'))
3466
+ .withDescription('Auto-mode schedule, 4 periods each per category. Example: "06:00/20 11:30/21 13:30/22 17:30/23.5".'),
3467
+ e.max_temperature().withValueMin(35).withValueMax(45).withPreset('default', 35, 'Default value'),
3468
+ e
3469
+ .numeric('deadzone_temperature', ea.STATE_SET)
3470
+ .withUnit('°C')
3471
+ .withValueMax(5)
3472
+ .withValueMin(1)
3473
+ .withValueStep(1)
3474
+ .withPreset('default', 1, 'Default value')
3475
+ .withDescription('The delta between local_temperature and current_heating_setpoint to trigger activity'),
3476
+ ];
3477
+ },
3463
3478
meta: {
3479
+ publishDuplicateTransaction: true,
3464
3480
tuyaDatapoints: [
3465
3481
[
3466
3482
1,
3467
3483
null,
3468
3484
{
3469
3485
from: (v, meta) => {
3470
3486
return v === true
3471
- ? {state: 'ON', system_mode: meta.state.system_mode_device ? meta.state.system_mode_device : 'cool'}
3472
- : {state: 'OFF', system_mode: 'off'};
3487
+ ? {system_mode: meta.state.system_mode_device ? meta.state.system_mode_device : 'cool'}
3488
+ : {system_mode: 'off'};
3473
3489
},
3474
3490
},
3475
3491
],
3476
3492
[
3477
- null ,
3493
+ 2 ,
3478
3494
'system_mode',
3479
3495
{
3480
3496
// Extend system_mode to support 'off' in addition to 'cool', 'heat' and 'fan_only'
3481
3497
to: async (v: string, meta) => {
3482
3498
const entity = meta.device.endpoints[0];
3483
-
3484
3499
// Power State
3485
3500
await tuya.sendDataPointBool(entity, 1, v !== 'off', 'dataRequest', 1);
3486
-
3487
3501
switch (v) {
3488
3502
case 'cool':
3489
3503
await tuya.sendDataPointEnum(entity, 2, 0, 'dataRequest', 1);
@@ -3496,35 +3510,16 @@ const definitions: DefinitionWithExtend[] = [
3496
3510
break;
3497
3511
}
3498
3512
},
3499
- },
3500
- ],
3501
- [
3502
- 2,
3503
- null,
3504
- {
3505
- // Map system_mode back to both 'state' and 'system_mode'
3506
3513
from: (v: number, meta) => {
3507
3514
const modes = ['cool', 'heat', 'fan_only'];
3508
-
3509
- return {
3510
- system_mode: modes[v],
3511
- system_mode_device: modes[v],
3512
- };
3515
+ meta.state.system_mode_device = modes[v];
3516
+ return modes[v];
3513
3517
},
3514
3518
},
3515
3519
],
3516
3520
[4, 'preset', tuya.valueConverterBasic.lookup({manual: true, auto: false})],
3517
- [16, 'current_cooling_setpoint', tuya.valueConverter.raw],
3518
3521
[16, 'current_heating_setpoint', tuya.valueConverter.raw],
3519
- [
3520
- 16,
3521
- null,
3522
- {
3523
- from: (v, meta) => {
3524
- return {current_cooling_setpoint: v, current_heating_setpoint: v};
3525
- },
3526
- },
3527
- ],
3522
+ [19, 'max_temperature', tuya.valueConverter.raw],
3528
3523
[24, 'local_temperature', tuya.valueConverter.divideBy10],
3529
3524
[26, 'deadzone_temperature', tuya.valueConverter.raw],
3530
3525
[27, 'local_temperature_calibration', tuya.valueConverter.localTemperatureCalibration],
@@ -3534,33 +3529,50 @@ const definitions: DefinitionWithExtend[] = [
3534
3529
101,
3535
3530
'schedule',
3536
3531
{
3537
- to: (v: string, meta) => {
3538
- const regex = /((?<h>[01][0-9]|2[0-3]):(?<m>[0-5][0-9])\/(?<t>[0-3][0-9](\.[0,5]|)))/gm;
3539
- const matches = [...v.matchAll(regex)];
3532
+ to: (v: {weekdays: string; saturday: string; sunday: string}, meta) => {
3533
+ const periods = (value: string) => {
3534
+ const regex = /((?<h>[01][0-9]|2[0-3]):(?<m>[0-5][0-9])\/(?<t>[0-3][0-9](\.[0,5]|)))/gm;
3535
+ const matches = [...value.matchAll(regex)];
3540
3536
3541
- if (matches.length == 12 ) {
3542
- return matches.reduce((arr, m) => {
3543
- arr.push(parseInt(m.groups.h));
3544
- arr.push(parseInt(m.groups.m));
3545
- arr.push(parseFloat(m.groups.t) * 2);
3546
- return arr;
3547
- }, []);
3548
- }
3537
+ if (matches.length == 4 ) {
3538
+ return matches.reduce((arr, m) => {
3539
+ arr.push(parseInt(m.groups.h));
3540
+ arr.push(parseInt(m.groups.m));
3541
+ arr.push(parseFloat(m.groups.t) * 2);
3542
+ return arr;
3543
+ }, []);
3544
+ }
3549
3545
3550
- logger.warning('Ignoring invalid or incomplete schedule', NS);
3551
- },
3552
- from: (v: number[], meta) => {
3553
- let r = '';
3546
+ logger.warning('Ignoring invalid or incomplete schedule', NS);
3547
+ };
3554
3548
3555
- for (let i = 0; i < 12; i++) {
3556
- r += `${v[i * 3].toString().padStart(2, '0')}:${v[i * 3 + 1].toString().padStart(2, '0')}/${v[i * 3 + 2] / 2}`;
3549
+ const schedule = [...periods(v['weekdays']), ...periods(v['saturday']), ...periods(v['sunday'])];
3557
3550
3558
- if (i < 11) {
3559
- r += ' ';
3560
- }
3561
- }
3551
+ return schedule;
3552
+ },
3553
+ from: (v: number[], meta) => {
3554
+ const format = (data: number[]) => {
3555
+ return data.reduce((a, v, i) => {
3556
+ switch (i % 3) {
3557
+ // Hour
3558
+ case 0:
3559
+ return `${a}${i > 0 ? ' ' : ''}${v.toString().padStart(2, '0')}`;
3560
+ // Minute
3561
+ case 1:
3562
+ return `${a}:${v.toString().padStart(2, '0')}`;
3563
+ break;
3564
+ // Setpoint
3565
+ case 2:
3566
+ return `${a}/${v / 2}`;
3567
+ }
3568
+ }, '');
3569
+ };
3562
3570
3563
- return r;
3571
+ return {
3572
+ weekdays: format(v.slice(0, 12)),
3573
+ saturday: format(v.slice(1 * 12, 2 * 12)),
3574
+ sunday: format(v.slice(2 * 12, 3 * 12)),
3575
+ };
3564
3576
},
3565
3577
},
3566
3578
],
0 commit comments