Skip to content

Commit 1df9729

Browse files
authored
Use 'capabilities'-resource to verify allowed values for API call
1 parent 0913370 commit 1df9729

File tree

4 files changed

+130
-112
lines changed

4 files changed

+130
-112
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ Thumbs.db
1010

1111
# i18n intermediate files
1212
admin/i18n/flat.txt
13-
admin/i18n/*/flat.txt
13+
admin/i18n/*/flat.txt
14+
capabilities.json

README.md

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,6 @@ If you like my work, please feel free to provide a personal donation
2020
(this is an personal Donate link for DutchmanNL, no relation to the ioBroker Project !
2121
[![Donate](https://raw.githubusercontent.com/DrozmotiX/ioBroker.tado/main/admin/button.png)](http://paypal.me/DutchmanNL)
2222

23-
## Breaking changes in v0.3.x
24-
Recommendation: If possible, delete old adapter installation first or delete all states so that no unsupported states keep in the installation.
25-
Upgrade from 0.2.x to v0.3.x includes a technical re-factioring with breaking changes. Some states changed there name/path, e.g.
26-
27-
| v0.2.x | v0.3.x |
28-
| ------ | ------ |
29-
| tado.[x].[yyyyy].Rooms.[z].setting.temperature |tado.[x].[yyyyy].Rooms.[z].setting.temperature.celsius |
30-
| tado.[x].[yyyyy].Rooms.[z].overlay.clearZoneOverlay | tado.[x].[yyyyy].Rooms.[z].overlayClearZone |
31-
| tado.[x].[yyyyy].Rooms.[z].Actual_Temperature | tado.[x].[yyyyy].Rooms.[z].sensorDataPoints.insideTemperature.celsius |
32-
| tado.[x].[yyyyy].Rooms.[z].Actual_Humidity | tado.[x].[yyyyy].Rooms.[z].sensorDataPoints.humidity.percentage |
33-
| tado.[x].[yyyyy].Rooms.[z].heatingPower | tado.[x].[yyyyy].Rooms.[z]..activityDataPoints.heatingPower.percentage |
34-
| tado.[x].[yyyyy].Weather.solarIntensity | tado.[x].[yyyyy].Weather.solarIntensity.percentage |
35-
| tado.[x].[yyyyy].Weather.outsideTemperature | tado.[x].[yyyyy].Weather.outsideTemperature.celsius |
36-
37-
In general vaules are now NULL if API sends NULL or just nothing. In v0.2.x sometimes the old value was kept, sometimes replaced with 0 sometimes NULL was used.
38-
3923
## Things you can steer
4024
| State | Description |
4125
| ----- | ----------- |
@@ -49,17 +33,17 @@ In general vaules are now NULL if API sends NULL or just nothing. In v0.2.x some
4933
| tado.[x].[yyyyyy].Home.state.presence | Set HOME or AWAY mode |
5034
| tado.[x].[yyyyyy].Home.masterswitch | Turn all devices on/off |
5135
| tado.[x].[yyyyyy].Rooms.[z].setting.mode | AC mode (only AC devices) |
52-
| tado.[x].[yyyyyy].Rooms.[z].setting.fanspeed | Fanspeed (only AC devices with **old** version) |
53-
| tado.[x].[yyyyyy].Rooms.[z].setting.fanLebel | Fanlebel (only AC devices with **new** version) |
54-
| tado.[x].[yyyyyy].Rooms.[z].setting.verticalSwing | Vertical swing (only AC devices with **new** version) |
55-
| tado.[x].[yyyyyy].Rooms.[z].setting.horizontalSwing | Horizontal swing (only AC devices with **new** version) |
36+
| tado.[x].[yyyyyy].Rooms.[z].setting.fanspeed | Fanspeed (only AC devices with **V3 and older** version) |
37+
| tado.[x].[yyyyyy].Rooms.[z].setting.fanLebel | Fanlebel (only AC devices with **V3+** version) |
38+
| tado.[x].[yyyyyy].Rooms.[z].setting.verticalSwing | Vertical swing (only AC devices with **V3+** version) |
39+
| tado.[x].[yyyyyy].Rooms.[z].setting.horizontalSwing | Horizontal swing (only AC devices with **V3 and older** version) |
5640

5741
## Changelog
5842
<!--
5943
Placeholder for the next version (at the beginning of the line):
6044
### __WORK IN PROGRESS__
6145
-->
62-
### 0.3.13-alpha.7 (2021-12-19)
46+
### __WORK IN PROGRESS__
6347
* (HGlab01) Optimize internet-check by using isOnline-library
6448
* (HGlab01) Support Smart AC Control V3+ (issue #403)
6549
* (HGlab01) Fix issue 'fan level not allowed in mode DRY'

lib/state_attr.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,18 @@ const state_attrb = {
311311
'type': 'string',
312312
'write': true
313313
},
314+
'max': {
315+
'name': 'max. Temperature',
316+
'role': 'value.temperature',
317+
'type': 'number',
318+
'unit': '°C'
319+
},
320+
'min': {
321+
'name': 'min. Temperature',
322+
'role': 'value.temperature',
323+
'type': 'number',
324+
'unit': '°C'
325+
},
314326
'minimumAwayTemperature': {
315327
'name': 'MinimumAway Temperature',
316328
'role': 'value.temperature',
@@ -682,6 +694,12 @@ const state_attrb = {
682694
'role': 'state',
683695
'type': 'string'
684696
},
697+
'step': {
698+
'name': 'Temperature step',
699+
'role': 'info',
700+
'type': 'number',
701+
'unit': '°C'
702+
},
685703
'supported': {
686704
'name': 'Supported',
687705
'role': 'state',

main.js

Lines changed: 105 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class Tado extends utils.Adapter {
4545
this.apiCallinExecution = false;
4646
JsonExplorer.init(this, state_attr);
4747
this.intervall_time = 60 * 1000;
48+
this.roomCapabilities = {};
4849
}
4950

5051
/**
@@ -397,7 +398,6 @@ class Tado extends utils.Adapter {
397398
*/
398399
async setZoneOverlay(home_id, zone_id, power, temperature, typeSkillBasedApp, durationInSeconds, type, acMode, fanLevel, horizontalSwing, verticalSwing, fanSpeed) {
399400
power = power.toUpperCase();
400-
if (!temperature) temperature = 20;
401401
typeSkillBasedApp = typeSkillBasedApp.toUpperCase();
402402
durationInSeconds = Math.max(10, durationInSeconds);
403403
type = type.toUpperCase();
@@ -406,87 +406,15 @@ class Tado extends utils.Adapter {
406406
fanLevel = fanLevel.toUpperCase();
407407
horizontalSwing = horizontalSwing.toUpperCase();
408408
verticalSwing = verticalSwing.toUpperCase();
409-
410-
if (power != 'ON' && power != 'OFF') {
411-
this.log.error(`Invalid value '${power}' for state 'power'. Allowed values are ON and OFF`);
412-
return;
413-
}
414-
if (typeSkillBasedApp != 'TIMER' && typeSkillBasedApp != 'MANUAL' && typeSkillBasedApp != 'NEXT_TIME_BLOCK' && typeSkillBasedApp != 'NO_OVERLAY') {
415-
this.log.error(`Invalid value '${typeSkillBasedApp}' for state 'typeSkillBasedApp'. Allowed values are TIMER, MANUAL and NEXT_TIME_BLOCK`);
416-
return;
417-
}
418-
if (horizontalSwing != 'ON' && horizontalSwing != 'OFF' && horizontalSwing != 'NOT_AVAILABLE') {
419-
this.log.error(`Invalid value '${horizontalSwing}' for state 'horizontalSwing'. Allowed values are ON and OFF`);
420-
return;
421-
}
422-
if (verticalSwing != 'ON' && verticalSwing != 'OFF' && verticalSwing != 'NOT_AVAILABLE') {
423-
this.log.error(`Invalid value '${verticalSwing}' for state 'verticalSwing'. Allowed values are ON and OFF`);
424-
return;
425-
}
426-
if (type != 'HEATING' && type != 'AIR_CONDITIONING' && type != 'HOT_WATER') {
427-
this.log.error(`Invalid value '${type}' for state 'type'. Allowed values are HOT_WATER, AIR_CONDITIONING and HEATING`);
428-
return;
429-
}
430-
if (fanSpeed != 'AUTO' && fanSpeed != 'HIGH' && fanSpeed != 'MIDDLE' && fanSpeed != 'LOW' && fanSpeed != 'NOT_AVAILABLE') {
431-
this.log.error(`Invalid value '${fanSpeed}' for state 'fanSpeed'. Allowed values are HIGH, MIDDLE, LOW and AUTO`);
432-
return;
433-
}
434-
if (fanLevel != 'AUTO' && fanLevel != 'SILENT' && fanLevel != 'LEVEL1' && fanLevel != 'LEVEL2' && fanLevel != 'LEVEL3' && fanLevel != 'LEVEL4' && fanLevel != 'LEVEL5' && fanLevel != 'NOT_AVAILABLE') {
435-
this.log.error(`Invalid value '${fanLevel}' for state 'fanLevel'. Allowed values are AUTO, SILENT, LEVEL1, LEVEL2, LEVEL3, LEVEL4 and LEVEL5`);
436-
return;
437-
}
438-
if (acMode != 'AUTO' && acMode != 'COOL' && acMode != 'DRY' && acMode != 'FAN' && acMode != 'HEAT' && acMode != 'NOT_AVAILABLE') {
439-
this.log.error(`Invalid value '${acMode}' for state 'acMode'. Allowed values are AUTO, COOL, DRY, FAN and HEAT`);
440-
return;
441-
}
442-
443-
const config = {
409+
if (!temperature) temperature = 21;
410+
let config = {
444411
setting: {
445412
type: type,
446413
}
447414
};
448415

449416
try {
450-
if (type == 'HEATING') {
451-
//Temp range is 5-25
452-
if (temperature > 25) {
453-
this.log.info(`Temperature set to 25° instead of ${temperature}° for HEATING device`);
454-
temperature = 25;
455-
} else if (temperature < 5) {
456-
this.log.info(`Temperature set to 5° instead of ${temperature}° for HEATING device`);
457-
temperature = 5;
458-
}
459-
}
460-
if (type == 'AIR_CONDITIONING') {
461-
//Temp range is 16-30
462-
if (temperature > 30) {
463-
this.log.info(`Temperature set to 30° instead of ${temperature}° for AIR_CONDITIONING device`);
464-
temperature = 30;
465-
} else if (temperature < 16) {
466-
this.log.info(`Temperature set to 16° instead of ${temperature}° for AIR_CONDITIONING device`);
467-
temperature = 16;
468-
}
469-
//acMode is always needed if AIR_CONDITION
470-
if (acMode != 'NOT_AVAILABLE') config.setting.mode = acMode;
471-
else config.setting.mode = 'COOL';
472-
if (verticalSwing != 'NOT_AVAILABLE') config.setting.verticalSwing = verticalSwing;
473-
if (horizontalSwing != 'NOT_AVAILABLE') config.setting.horizontalSwing = horizontalSwing;
474-
//fan level not allowed in mode DRY
475-
if (fanLevel != 'NOT_AVAILABLE' && acMode != 'DRY') config.setting.fanLevel = fanLevel;
476-
if (fanSpeed != 'NOT_AVAILABLE' && acMode != 'DRY') config.setting.fanSpeed = fanSpeed;
477-
}
478-
if (power == 'ON') {
479-
config.setting.power = 'ON';
480-
//temperature required for mode AUTO
481-
//temperature required for mode DRY
482-
//Temperature not for aircondition if mode is FAN, DRY(deactivated!) and not for HOT_WATER
483-
if (!(type == 'HOT_WATER' || (type == 'AIR_CONDITIONING' && (acMode == 'DRY_DEACTIVATED!!' || acMode == 'FAN')))) {
484-
config.setting.temperature = {};
485-
config.setting.temperature.celsius = temperature;
486-
}
487-
} else {
488-
config.setting.power = 'OFF';
489-
}
417+
config.setting.power = power;
490418
if (typeSkillBasedApp != 'NO_OVERLAY') {
491419
config.termination = {};
492420
config.termination.typeSkillBasedApp = typeSkillBasedApp;
@@ -497,6 +425,89 @@ class Tado extends utils.Adapter {
497425
config.termination.durationInSeconds = durationInSeconds;
498426
}
499427
}
428+
if (type != 'HEATING' && type != 'AIR_CONDITIONING' && type != 'HOT_WATER') {
429+
this.log.error(`Invalid value '${type}' for state 'type'. Supported values are HOT_WATER, AIR_CONDITIONING and HEATING`);
430+
return;
431+
}
432+
if (power != 'ON' && power != 'OFF') {
433+
this.log.error(`Invalid value '${power}' for state 'power'. Supported values are ON and OFF`);
434+
return;
435+
}
436+
if (typeSkillBasedApp != 'TIMER' && typeSkillBasedApp != 'MANUAL' && typeSkillBasedApp != 'NEXT_TIME_BLOCK' && typeSkillBasedApp != 'NO_OVERLAY') {
437+
this.log.error(`Invalid value '${typeSkillBasedApp}' for state 'typeSkillBasedApp'. Allowed values are TIMER, MANUAL and NEXT_TIME_BLOCK`);
438+
return;
439+
}
440+
let capType = this.roomCapabilities[zone_id].type;
441+
if (capType && capType != type) {
442+
this.log.error(`Type ${type} not valid. Type ${capType} expected.`);
443+
return;
444+
}
445+
446+
if (type == 'HEATING' && power == 'ON') {
447+
let capMinTemp = this.roomCapabilities[zone_id].temperatures.celsius.min;
448+
let capMaxTemp = this.roomCapabilities[zone_id].temperatures.celsius.max;
449+
450+
if (capMinTemp && capMaxTemp) {
451+
if (temperature > capMaxTemp || temperature < capMinTemp) {
452+
this.log.error(`Temperature of ${temperature}°C outside supported range of ${capMinTemp}°C to ${capMaxTemp}°C`);
453+
return;
454+
}
455+
config.setting.temperature = {};
456+
config.setting.temperature.celsius = temperature;
457+
}
458+
}
459+
460+
if (type == 'AIR_CONDITIONING' && power == 'ON') {
461+
if (!this.roomCapabilities[zone_id][acMode]) {
462+
this.log.error(`AC-Mode ${acMode} not supported!`);
463+
return;
464+
}
465+
config.setting.mode = acMode;
466+
let capMinTemp = this.roomCapabilities[zone_id][acMode].temperatures.celsius.min;
467+
let capMaxTemp = this.roomCapabilities[zone_id][acMode].temperatures.celsius.max;
468+
let capHorizontalSwing = this.roomCapabilities[zone_id][acMode].horizontalSwing;
469+
let capVerticalSwing = this.roomCapabilities[zone_id][acMode].verticalSwing;
470+
let capFanSpeed = this.roomCapabilities[zone_id][acMode].fanSpeed;
471+
let capFanLevel = this.roomCapabilities[zone_id][acMode].fanLevel;
472+
473+
if (capMinTemp && capMaxTemp) {
474+
if (temperature > capMaxTemp || temperature < capMinTemp) {
475+
this.log.error(`Temperature of ${temperature}°C outside supported range of ${capMinTemp}°C to ${capMaxTemp}°C`);
476+
return;
477+
}
478+
config.setting.temperature = {};
479+
config.setting.temperature.celsius = temperature;
480+
}
481+
if (capHorizontalSwing) {
482+
if (!capHorizontalSwing.includes(horizontalSwing)) {
483+
this.log.error(`Invalid value '${horizontalSwing}' for state 'horizontalSwing'. Allowed values are ${JSON.stringify(capHorizontalSwing)}`);
484+
return;
485+
}
486+
config.setting.horizontalSwing = horizontalSwing;
487+
}
488+
if (capVerticalSwing) {
489+
if (!capVerticalSwing.includes(verticalSwing)) {
490+
this.log.error(`Invalid value '${verticalSwing}' for state 'verticalSwing'. Allowed values are ${JSON.stringify(capVerticalSwing)}`);
491+
return;
492+
}
493+
config.setting.verticalSwing = verticalSwing;
494+
}
495+
if (capFanSpeed) {
496+
if (!capFanSpeed.includes(fanSpeed)) {
497+
this.log.error(`Invalid value '${fanSpeed}' for state 'fanSpeed'. Allowed values are ${JSON.stringify(capFanSpeed)}`);
498+
return;
499+
}
500+
config.setting.fanSpeed = fanSpeed;
501+
}
502+
if (capFanLevel) {
503+
if (!capFanLevel.includes(fanLevel)) {
504+
this.log.error(`Invalid value '${fanLevel}' for state 'fanLevel'. Allowed values are ${JSON.stringify(capFanLevel)}`);
505+
return;
506+
}
507+
config.setting.fanLevel = fanLevel;
508+
}
509+
}
510+
500511
let result = await this.poolApiCall(home_id, zone_id, config);
501512
this.log.debug(`API 'ZoneOverlay' for home '${home_id}' and zone '${zone_id}' with body ${JSON.stringify(config)} called.`);
502513

@@ -770,9 +781,11 @@ class Tado extends utils.Adapter {
770781
JsonExplorer.TraverseJson(this.Zones_data, `${HomeId}.Rooms`, true, true, 0, 0);
771782

772783
for (const i in this.Zones_data) {
773-
await this.DoZoneStates(HomeId, this.Zones_data[i].id);
774-
await this.DoAwayConfiguration(HomeId, this.Zones_data[i].id);
775-
await this.DoTimeTables(HomeId, this.Zones_data[i].id);
784+
let zoneId = this.Zones_data[i].id;
785+
await this.DoZoneStates(HomeId, zoneId);
786+
if (!this.roomCapabilities[zoneId]) await this.DoCapabilities(HomeId, zoneId);
787+
await this.DoAwayConfiguration(HomeId, zoneId);
788+
await this.DoTimeTables(HomeId, zoneId);
776789
}
777790
}
778791

@@ -788,6 +801,14 @@ class Tado extends utils.Adapter {
788801
JsonExplorer.TraverseJson(ZonesState_data, HomeId + '.Rooms.' + ZoneId, true, true, 0, 2);
789802
}
790803

804+
async DoCapabilities(HomeId, ZoneId) {
805+
const capabilities_data = await this.getCapabilities(HomeId, ZoneId);
806+
this.roomCapabilities[ZoneId] = capabilities_data;
807+
this.log.debug(`Capabilities_data result for room '${ZoneId}' is ${JSON.stringify(capabilities_data)}`);
808+
this.DoWriteJsonRespons(HomeId, 'Stage_09_Capabilities_data_' + ZoneId, capabilities_data);
809+
JsonExplorer.TraverseJson(capabilities_data, HomeId + '.Rooms.' + ZoneId + '.capabilities', true, true, 0, 2);
810+
}
811+
791812
/**
792813
* @param {string} HomeId
793814
* @param {string} ZoneId
@@ -809,7 +830,7 @@ class Tado extends utils.Adapter {
809830
const AwayConfiguration_data = await this.getAwayConfiguration(HomeId, ZoneId);
810831
this.log.debug('AwayConfiguration_data Result: ' + JSON.stringify(AwayConfiguration_data));
811832
this.DoWriteJsonRespons(HomeId, 'Stage_10_AwayConfiguration_' + ZoneId, AwayConfiguration_data);
812-
JsonExplorer.TraverseJson(AwayConfiguration_data, HomeId + '.Rooms.' + ZoneId + '.AwayConfig', true, true, 0, 2);
833+
JsonExplorer.TraverseJson(AwayConfiguration_data, HomeId + '.Rooms.' + ZoneId + '.awayConfig', true, true, 0, 2);
813834
}
814835

815836
async DoWriteJsonRespons(HomeId, state_name, value) {
@@ -840,7 +861,6 @@ class Tado extends utils.Adapter {
840861
//////////////////////////////////////////////////////////////////////
841862
/* MASTERSWITCH */
842863
//////////////////////////////////////////////////////////////////////
843-
844864
async setMasterSwitch(masterswitch) {
845865
masterswitch = masterswitch.toUpperCase();
846866
if (!(masterswitch == 'ON' || masterswitch == 'OFF')) throw new Error(`Masterswitch value 'ON' or 'OFF' expected but received '${masterswitch}'`);
@@ -1072,6 +1092,10 @@ class Tado extends utils.Adapter {
10721092
return this.apiCall(`/api/v2/homes/${home_id}/zones/${zone_id}/state`);
10731093
}
10741094

1095+
getCapabilities(home_id, zone_id) {
1096+
return this.apiCall(`/api/v2/homes/${home_id}/zones/${zone_id}/capabilities`);
1097+
}
1098+
10751099
getAwayConfiguration(home_id, zone_id) {
10761100
return this.apiCall(`/api/v2/homes/${home_id}/zones/${zone_id}/awayConfiguration`);
10771101
}
@@ -1093,19 +1117,10 @@ class Tado extends utils.Adapter {
10931117
return this.apiCall(`/api/v2/homes/${home_id}/devices`);
10941118
}*/
10951119

1096-
/*getTimeTable(home_id, zone_id, timetable_id) {
1097-
return this.apiCall(`/api/v2/homes/${home_id}/zones/${zone_id}/schedule/timetables/${timetable_id}/blocks`);
1098-
}*/
1099-
11001120
/*getZoneOverlay(home_id, zone_id) {
11011121
return this.apiCall(`/api/v2/homes/${home_id}/zones/${zone_id}/overlay`);
11021122
}*/
11031123

1104-
/*
1105-
getZoneCapabilities(home_id, zone_id) {
1106-
return this.apiCall(`/api/v2/homes/${home_id}/zones/${zone_id}/capabilities`);
1107-
}*/
1108-
11091124
/*getInstallations(home_id) {
11101125
return this.apiCall(`/api/v2/homes/${home_id}/installations`);
11111126
}*/

0 commit comments

Comments
 (0)