@@ -67,6 +67,7 @@ const EXPIRATION_WINDOW_IN_SECONDS = 300;
67
67
68
68
const tado_auth_url = "https://auth.tado.com" ;
69
69
const tado_url = "https://my.tado.com" ;
70
+ const tado_x_url = "https://hops.tado.com" ;
70
71
const tado_config = {
71
72
client : {
72
73
id : "tado-web-app" ,
@@ -111,11 +112,15 @@ export class Tado {
111
112
#accessToken?: AccessToken | undefined ;
112
113
#username?: string ;
113
114
#password?: string ;
115
+ #firstLogin: boolean ;
116
+ #isX: boolean ;
114
117
115
118
constructor ( username ?: string , password ?: string ) {
116
119
this . #username = username ;
117
120
this . #password = password ;
118
121
this . #httpsAgent = new Agent ( { keepAlive : true } ) ;
122
+ this . #firstLogin = true ;
123
+ this . #isX = false ;
119
124
}
120
125
121
126
async #login( ) : Promise < void > {
@@ -130,6 +135,20 @@ export class Tado {
130
135
} ;
131
136
132
137
this . #accessToken = await client . getToken ( tokenParams ) ;
138
+
139
+ if ( this . #firstLogin) {
140
+ try {
141
+ const me = await this . getMe ( ) ;
142
+ if ( me . homes . length > 0 ) {
143
+ const home_id = me . homes [ 0 ] . id ;
144
+ const home = await this . getHome ( home_id ) ;
145
+ this . #isX = home . generation == "LINE_X" ;
146
+ }
147
+ } catch ( err ) {
148
+ console . error ( `Could not determine TadoX status: ${ err } ` ) ;
149
+ }
150
+ this . #firstLogin = false ;
151
+ }
133
152
}
134
153
135
154
/**
@@ -168,6 +187,10 @@ export class Tado {
168
187
return this . #accessToken;
169
188
}
170
189
190
+ get isX ( ) : boolean {
191
+ return this . #isX;
192
+ }
193
+
171
194
/**
172
195
* Authenticates a user using the provided public client credentials, username and password.
173
196
* For more information see
@@ -217,6 +240,21 @@ export class Tado {
217
240
return response . data as R ;
218
241
}
219
242
243
+ /**
244
+ * Makes an API call to the provided TadoX URL with the specified method and data.
245
+ *
246
+ * @typeParam R - The type of the response
247
+ * @typeParam T - The type of the request body
248
+ * @param url - The endpoint to which the request is sent. If the URL contains "https", it will be used as is.
249
+ * @param method - The HTTP method to use for the request (e.g., "get", "post").
250
+ * @param data - The payload to send with the request, if applicable.
251
+ * @returns A promise that resolves to the response data.
252
+ */
253
+ async apiCallX < R , T = unknown > ( url : string , method : Method = "get" , data ?: T ) : Promise < R > {
254
+ const callUrl = tado_x_url + url ;
255
+ return this . apiCall ( callUrl , method , data ) ;
256
+ }
257
+
220
258
/**
221
259
* Fetches the current user data.
222
260
*
@@ -315,7 +353,11 @@ export class Tado {
315
353
* @returns A promise that resolves to an array of Device objects.
316
354
*/
317
355
getDevices ( home_id : number ) : Promise < Device [ ] > {
318
- return this . apiCall ( `/api/v2/homes/${ home_id } /devices` ) ;
356
+ if ( this . #isX) {
357
+ return this . apiCallX ( `/homes/${ home_id } /roomsAndDevices` ) ;
358
+ } else {
359
+ return this . apiCall ( `/api/v2/homes/${ home_id } /devices` ) ;
360
+ }
319
361
}
320
362
321
363
/**
@@ -542,7 +584,11 @@ export class Tado {
542
584
* @returns A promise that resolves to an array of Zone objects.
543
585
*/
544
586
getZones ( home_id : number ) : Promise < Zone [ ] > {
545
- return this . apiCall ( `/api/v2/homes/${ home_id } /zones` ) ;
587
+ if ( this . #isX) {
588
+ return this . apiCallX ( `/homes/${ home_id } /rooms` ) ;
589
+ } else {
590
+ return this . apiCall ( `/api/v2/homes/${ home_id } /zones` ) ;
591
+ }
546
592
}
547
593
548
594
/**
@@ -553,7 +599,11 @@ export class Tado {
553
599
* @returns A promise that resolves to the state of the specified zone.
554
600
*/
555
601
getZoneState ( home_id : number , zone_id : number ) : Promise < ZoneState > {
556
- return this . apiCall ( `/api/v2/homes/${ home_id } /zones/${ zone_id } /state` ) ;
602
+ if ( this . #isX) {
603
+ return this . apiCallX ( `/homes/${ home_id } /rooms/${ zone_id } ` ) ;
604
+ } else {
605
+ return this . apiCall ( `/api/v2/homes/${ home_id } /zones/${ zone_id } /state` ) ;
606
+ }
557
607
}
558
608
559
609
/**
@@ -785,7 +835,11 @@ export class Tado {
785
835
* @deprecated Use {@link clearZoneOverlays} instead.
786
836
*/
787
837
clearZoneOverlay ( home_id : number , zone_id : number ) : Promise < void > {
788
- return this . apiCall ( `/api/v2/homes/${ home_id } /zones/${ zone_id } /overlay` , "delete" ) ;
838
+ if ( this . #isX) {
839
+ return this . apiCallX ( `/homes/${ home_id } /rooms/${ zone_id } /resumeSchedule` , "post" , { } ) ;
840
+ } else {
841
+ return this . apiCall ( `/api/v2/homes/${ home_id } /zones/${ zone_id } /overlay` , "delete" ) ;
842
+ }
789
843
}
790
844
791
845
/**
@@ -903,7 +957,15 @@ export class Tado {
903
957
} ;
904
958
}
905
959
906
- return this . apiCall ( `/api/v2/homes/${ home_id } /zones/${ zone_id } /overlay` , "put" , config ) ;
960
+ if ( this . #isX) {
961
+ return this . apiCallX (
962
+ `/api/v2/homes/${ home_id } /rooms/${ zone_id } /manualControl` ,
963
+ "post" ,
964
+ config ,
965
+ ) ;
966
+ } else {
967
+ return this . apiCall ( `/api/v2/homes/${ home_id } /zones/${ zone_id } /overlay` , "put" , config ) ;
968
+ }
907
969
}
908
970
909
971
/**
@@ -914,8 +976,14 @@ export class Tado {
914
976
* @returns A promise that resolves when the overlays are cleared.
915
977
*/
916
978
async clearZoneOverlays ( home_id : number , zone_ids : number [ ] ) : Promise < void > {
917
- const rooms = zone_ids . join ( "," ) ;
918
- return this . apiCall ( `/api/v2/homes/${ home_id } /overlay?rooms=${ rooms } ` , "delete" ) ;
979
+ if ( this . #isX) {
980
+ for ( const zone_id of zone_ids ) {
981
+ return this . apiCallX ( `/homes/${ home_id } /rooms/${ zone_id } /resumeSchedule` , "post" , { } ) ;
982
+ }
983
+ } else {
984
+ const rooms = zone_ids . join ( "," ) ;
985
+ return this . apiCall ( `/api/v2/homes/${ home_id } /overlay?rooms=${ rooms } ` , "delete" ) ;
986
+ }
919
987
}
920
988
921
989
/**
@@ -1054,7 +1122,17 @@ export class Tado {
1054
1122
config . push ( overlay_config ) ;
1055
1123
}
1056
1124
1057
- return this . apiCall ( `/api/v2/homes/${ home_id } /overlay` , "post" , { overlays : config } ) ;
1125
+ if ( this . #isX) {
1126
+ for ( const c of config ) {
1127
+ return this . apiCallX (
1128
+ `/api/v2/homes/${ home_id } /rooms/${ c . room } /manualControl` ,
1129
+ "post" ,
1130
+ c . overlay ,
1131
+ ) ;
1132
+ }
1133
+ } else {
1134
+ return this . apiCall ( `/api/v2/homes/${ home_id } /overlay` , "post" , { overlays : config } ) ;
1135
+ }
1058
1136
}
1059
1137
1060
1138
/**
0 commit comments