iobroker.toyota 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/io-package.json +9 -1
- package/lib/tools.js +3 -3
- package/main.js +213 -49
- package/package.json +19 -12
package/io-package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "toyota",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.3.0": {
|
|
7
|
+
"en": "Fix Login and add Eletric Car data",
|
|
8
|
+
"de": "Login gefixt und elektrische Fahrzeugdaten hinzugefügt"
|
|
9
|
+
},
|
|
10
|
+
"0.2.6": {
|
|
11
|
+
"en": "Skip endpoints that are not available",
|
|
12
|
+
"de": "Überspringen von Endpunkten, die nicht verfügbar sind"
|
|
13
|
+
},
|
|
6
14
|
"0.2.5": {
|
|
7
15
|
"en": "Add option to disable trip fetching",
|
|
8
16
|
"de": "Option zum Deaktivieren des Abrufs von Trips hinzugefügt"
|
package/lib/tools.js
CHANGED
|
@@ -64,7 +64,7 @@ async function translateYandex(text, targetLang, apiKey) {
|
|
|
64
64
|
}
|
|
65
65
|
throw new Error('Invalid response for translate request');
|
|
66
66
|
} catch (e) {
|
|
67
|
-
throw new Error(`Could not translate to "${targetLang}": ${e}
|
|
67
|
+
throw new Error(`Could not translate to "${targetLang}": ${e}`, { cause: e });
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -87,9 +87,9 @@ async function translateGoogle(text, targetLang) {
|
|
|
87
87
|
throw new Error('Invalid response for translate request');
|
|
88
88
|
} catch (e) {
|
|
89
89
|
if (e.response && e.response.status === 429) {
|
|
90
|
-
throw new Error(`Could not translate to "${targetLang}": Rate-limited by Google Translate
|
|
90
|
+
throw new Error(`Could not translate to "${targetLang}": Rate-limited by Google Translate`, { cause: e });
|
|
91
91
|
} else {
|
|
92
|
-
throw new Error(`Could not translate to "${targetLang}": ${e}
|
|
92
|
+
throw new Error(`Could not translate to "${targetLang}": ${e}`, { cause: e });
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
}
|
package/main.js
CHANGED
|
@@ -13,6 +13,9 @@ const qs = require('qs');
|
|
|
13
13
|
const tough = require('tough-cookie');
|
|
14
14
|
const { HttpsCookieAgent } = require('http-cookie-agent/http');
|
|
15
15
|
const jwt = require('jsonwebtoken');
|
|
16
|
+
const { v4: uuidv4 } = require('uuid');
|
|
17
|
+
const crypto = require('crypto');
|
|
18
|
+
|
|
16
19
|
class Toyota extends utils.Adapter {
|
|
17
20
|
/**
|
|
18
21
|
* @param {Partial<utils.AdapterOptions>} [options={}]
|
|
@@ -30,6 +33,7 @@ class Toyota extends utils.Adapter {
|
|
|
30
33
|
this.token = null;
|
|
31
34
|
this.uuid = null;
|
|
32
35
|
this.session = {};
|
|
36
|
+
this.blockedEndpoints = {};
|
|
33
37
|
this.cookieJar = new tough.CookieJar();
|
|
34
38
|
this.requestClient = axios.create({
|
|
35
39
|
withCredentials: true,
|
|
@@ -42,8 +46,10 @@ class Toyota extends utils.Adapter {
|
|
|
42
46
|
this.updateInterval = null;
|
|
43
47
|
this.reLoginTimeout = null;
|
|
44
48
|
this.refreshTokenTimeout = null;
|
|
45
|
-
this.hostName = 'myt-agg.toyota-europe.com';
|
|
46
49
|
this.brand = 'T';
|
|
50
|
+
// Aligned with MyToyota APK 2.23.0
|
|
51
|
+
this.CLIENT_VERSION = '2.23.0';
|
|
52
|
+
this.API_VERSION = 'resource=2.1, protocol=1.0';
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
/**
|
|
@@ -64,7 +70,6 @@ class Toyota extends utils.Adapter {
|
|
|
64
70
|
if (this.config.type === 'lexus') {
|
|
65
71
|
this.log.info('Login to Lexus');
|
|
66
72
|
this.brand = 'L';
|
|
67
|
-
this.hostName = 'lexuslink-agg.toyota-europe.com';
|
|
68
73
|
}
|
|
69
74
|
if (!this.config.username || !this.config.password) {
|
|
70
75
|
this.log.error('No username or password set');
|
|
@@ -91,6 +96,7 @@ class Toyota extends utils.Adapter {
|
|
|
91
96
|
}, 3500 * 1000);
|
|
92
97
|
}
|
|
93
98
|
}
|
|
99
|
+
|
|
94
100
|
async login() {
|
|
95
101
|
this.log.info('Login to Toyota');
|
|
96
102
|
const firstResponse = await this.requestClient({
|
|
@@ -100,7 +106,7 @@ class Toyota extends utils.Adapter {
|
|
|
100
106
|
headers: {
|
|
101
107
|
'x-osname': 'iOS',
|
|
102
108
|
'x-brand': this.brand,
|
|
103
|
-
accept: '
|
|
109
|
+
accept: 'application/json',
|
|
104
110
|
'x-channel': 'ONEAPP',
|
|
105
111
|
brand: this.brand,
|
|
106
112
|
'x-region': 'EU',
|
|
@@ -110,8 +116,8 @@ class Toyota extends utils.Adapter {
|
|
|
110
116
|
'accept-language': 'de-DE,de;q=0.9',
|
|
111
117
|
region: 'EU',
|
|
112
118
|
'user-agent': 'Toyota/134 CFNetwork/1410.0.3 Darwin/22.6.0',
|
|
113
|
-
'accept-api-version':
|
|
114
|
-
'x-appversion':
|
|
119
|
+
'accept-api-version': this.API_VERSION,
|
|
120
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
115
121
|
Cookie: 'route=e8e8b55de08efd3c4b34265c0069d319',
|
|
116
122
|
},
|
|
117
123
|
})
|
|
@@ -126,6 +132,7 @@ class Toyota extends utils.Adapter {
|
|
|
126
132
|
this.log.error(JSON.stringify(error.response.data));
|
|
127
133
|
}
|
|
128
134
|
});
|
|
135
|
+
|
|
129
136
|
const secondResponse = await this.requestClient({
|
|
130
137
|
method: 'post',
|
|
131
138
|
maxBodyLength: Infinity,
|
|
@@ -142,10 +149,10 @@ class Toyota extends utils.Adapter {
|
|
|
142
149
|
'x-brand': this.brand,
|
|
143
150
|
'accept-language': 'de-DE,de;q=0.9',
|
|
144
151
|
'x-correlationid': 'BDD02A22-CD76-4877-90A9-196EDA5DC695',
|
|
145
|
-
'x-appversion':
|
|
146
|
-
accept: '
|
|
152
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
153
|
+
accept: 'application/json',
|
|
147
154
|
'content-type': 'application/json',
|
|
148
|
-
'accept-api-version':
|
|
155
|
+
'accept-api-version': this.API_VERSION,
|
|
149
156
|
},
|
|
150
157
|
data: firstResponse,
|
|
151
158
|
})
|
|
@@ -160,10 +167,13 @@ class Toyota extends utils.Adapter {
|
|
|
160
167
|
this.log.error(JSON.stringify(error.response.data));
|
|
161
168
|
}
|
|
162
169
|
});
|
|
170
|
+
|
|
163
171
|
if (!secondResponse || !secondResponse.callbacks) {
|
|
164
172
|
this.log.error('No Second Response');
|
|
173
|
+
return;
|
|
165
174
|
}
|
|
166
175
|
secondResponse.callbacks[0].input[0].value = this.config.username;
|
|
176
|
+
|
|
167
177
|
const thirdResponse = await this.requestClient({
|
|
168
178
|
method: 'post',
|
|
169
179
|
maxBodyLength: Infinity,
|
|
@@ -180,10 +190,10 @@ class Toyota extends utils.Adapter {
|
|
|
180
190
|
'x-brand': this.brand,
|
|
181
191
|
'accept-language': 'de-DE,de;q=0.9',
|
|
182
192
|
'x-correlationid': 'BDD02A22-CD76-4877-90A9-196EDA5DC695',
|
|
183
|
-
'x-appversion':
|
|
184
|
-
accept: '
|
|
193
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
194
|
+
accept: 'application/json',
|
|
185
195
|
'content-type': 'application/json',
|
|
186
|
-
'accept-api-version':
|
|
196
|
+
'accept-api-version': this.API_VERSION,
|
|
187
197
|
},
|
|
188
198
|
data: secondResponse,
|
|
189
199
|
})
|
|
@@ -198,12 +208,15 @@ class Toyota extends utils.Adapter {
|
|
|
198
208
|
this.log.error(JSON.stringify(error.response.data));
|
|
199
209
|
}
|
|
200
210
|
});
|
|
211
|
+
|
|
201
212
|
if (!thirdResponse || !thirdResponse.callbacks) {
|
|
202
213
|
this.log.error('No Third Response');
|
|
214
|
+
return;
|
|
203
215
|
}
|
|
204
216
|
|
|
205
217
|
thirdResponse.callbacks[0].input[0].value = this.config.password;
|
|
206
|
-
|
|
218
|
+
|
|
219
|
+
await this.requestClient({
|
|
207
220
|
method: 'post',
|
|
208
221
|
maxBodyLength: Infinity,
|
|
209
222
|
url: 'https://b2c-login.toyota-europe.com/json/realms/root/realms/tme/authenticate?authIndexType=service&authIndexValue=oneapp',
|
|
@@ -219,10 +232,10 @@ class Toyota extends utils.Adapter {
|
|
|
219
232
|
'x-brand': this.brand,
|
|
220
233
|
'accept-language': 'de-DE,de;q=0.9',
|
|
221
234
|
'x-correlationid': 'BDD02A22-CD76-4877-90A9-196EDA5DC695',
|
|
222
|
-
'x-appversion':
|
|
223
|
-
accept: '
|
|
235
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
236
|
+
accept: 'application/json',
|
|
224
237
|
'content-type': 'application/json',
|
|
225
|
-
'accept-api-version':
|
|
238
|
+
'accept-api-version': this.API_VERSION,
|
|
226
239
|
},
|
|
227
240
|
data: thirdResponse,
|
|
228
241
|
})
|
|
@@ -238,10 +251,21 @@ class Toyota extends utils.Adapter {
|
|
|
238
251
|
}
|
|
239
252
|
});
|
|
240
253
|
|
|
254
|
+
// PKCE S256: random 64-byte verifier, base64url(SHA-256(verifier)) as challenge (APK 2.23.0)
|
|
255
|
+
// Keep verifier local so concurrent login() calls do not overwrite each other.
|
|
256
|
+
const codeVerifier = crypto.randomBytes(64).toString('base64url');
|
|
257
|
+
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
|
|
258
|
+
const authorizeUrl =
|
|
259
|
+
'https://b2c-login.toyota-europe.com/oauth2/realms/root/realms/tme/authorize' +
|
|
260
|
+
'?response_type=code&realm=tme&redirect_uri=com.toyota.oneapp:/oauth2Callback' +
|
|
261
|
+
'&client_id=oneapp&scope=' +
|
|
262
|
+
encodeURIComponent('openid profile write') +
|
|
263
|
+
'&code_challenge_method=S256&code_challenge=' +
|
|
264
|
+
codeChallenge;
|
|
241
265
|
const tokenResponse = await this.requestClient({
|
|
242
266
|
method: 'get',
|
|
243
267
|
maxBodyLength: Infinity,
|
|
244
|
-
url:
|
|
268
|
+
url: authorizeUrl,
|
|
245
269
|
headers: {
|
|
246
270
|
'x-osname': 'iOS',
|
|
247
271
|
'x-brand': this.brand,
|
|
@@ -255,8 +279,8 @@ class Toyota extends utils.Adapter {
|
|
|
255
279
|
'accept-language': 'de-DE,de;q=0.9',
|
|
256
280
|
region: 'EU',
|
|
257
281
|
'user-agent': 'Toyota/134 CFNetwork/1410.0.3 Darwin/22.6.0',
|
|
258
|
-
'accept-api-version':
|
|
259
|
-
'x-appversion':
|
|
282
|
+
'accept-api-version': this.API_VERSION,
|
|
283
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
260
284
|
},
|
|
261
285
|
})
|
|
262
286
|
.then((res) => {
|
|
@@ -271,10 +295,12 @@ class Toyota extends utils.Adapter {
|
|
|
271
295
|
this.log.error(error);
|
|
272
296
|
error.response && this.log.error(JSON.stringify(error.response.data));
|
|
273
297
|
});
|
|
298
|
+
|
|
274
299
|
if (!tokenResponse || !tokenResponse.code) {
|
|
275
300
|
this.log.error('No Token Response');
|
|
276
301
|
return;
|
|
277
302
|
}
|
|
303
|
+
|
|
278
304
|
this.log.info('Start token exchange');
|
|
279
305
|
await this.requestClient({
|
|
280
306
|
method: 'post',
|
|
@@ -293,16 +319,16 @@ class Toyota extends utils.Adapter {
|
|
|
293
319
|
authorization: 'Basic b25lYXBwOm9uZWFwcA==',
|
|
294
320
|
'accept-language': 'de-DE,de;q=0.9',
|
|
295
321
|
'x-correlationid': 'E422A08C-1A04-415E-BB08-2386EE06CF90',
|
|
296
|
-
'x-appversion':
|
|
322
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
297
323
|
accept: '*/*',
|
|
298
324
|
'content-type': 'application/x-www-form-urlencoded',
|
|
299
|
-
'accept-api-version':
|
|
325
|
+
'accept-api-version': this.API_VERSION,
|
|
300
326
|
},
|
|
301
327
|
data: {
|
|
302
328
|
grant_type: 'authorization_code',
|
|
303
329
|
redirect_uri: 'com.toyota.oneapp:/oauth2Callback',
|
|
304
330
|
code: tokenResponse.code,
|
|
305
|
-
code_verifier:
|
|
331
|
+
code_verifier: codeVerifier,
|
|
306
332
|
client_id: 'oneapp',
|
|
307
333
|
},
|
|
308
334
|
})
|
|
@@ -325,6 +351,7 @@ class Toyota extends utils.Adapter {
|
|
|
325
351
|
}
|
|
326
352
|
});
|
|
327
353
|
}
|
|
354
|
+
|
|
328
355
|
async refreshToken() {
|
|
329
356
|
await this.requestClient({
|
|
330
357
|
method: 'post',
|
|
@@ -343,16 +370,15 @@ class Toyota extends utils.Adapter {
|
|
|
343
370
|
authorization: 'Basic b25lYXBwOm9uZWFwcA==',
|
|
344
371
|
'accept-language': 'de-DE,de;q=0.9',
|
|
345
372
|
'x-correlationid': 'E422A08C-1A04-415E-BB08-2386EE06CF90',
|
|
346
|
-
'x-appversion':
|
|
373
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
347
374
|
accept: '*/*',
|
|
348
375
|
'content-type': 'application/x-www-form-urlencoded',
|
|
349
|
-
'accept-api-version':
|
|
376
|
+
'accept-api-version': this.API_VERSION,
|
|
350
377
|
},
|
|
351
378
|
data: {
|
|
352
379
|
grant_type: 'refresh_token',
|
|
353
380
|
redirect_uri: 'com.toyota.oneapp:/oauth2Callback',
|
|
354
381
|
client_id: 'oneapp',
|
|
355
|
-
code_verifier: 'plain',
|
|
356
382
|
refresh_token: this.session.refresh_token,
|
|
357
383
|
},
|
|
358
384
|
})
|
|
@@ -368,6 +394,94 @@ class Toyota extends utils.Adapter {
|
|
|
368
394
|
}
|
|
369
395
|
});
|
|
370
396
|
}
|
|
397
|
+
|
|
398
|
+
generate_hmac_sha256(key, message) {
|
|
399
|
+
return crypto.createHmac('sha256', key).update(message).digest('hex');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Build the standard set of headers used for all ctpa-oneapi calls.
|
|
404
|
+
* @param {string} [vin] optional vin header for vehicle-scoped endpoints
|
|
405
|
+
*/
|
|
406
|
+
buildApiHeaders(vin) {
|
|
407
|
+
const headers = {
|
|
408
|
+
'x-appbrand': this.brand,
|
|
409
|
+
'x-device-timezone': 'CEST',
|
|
410
|
+
'x-osname': 'iOS',
|
|
411
|
+
guid: this.uuid,
|
|
412
|
+
'user-agent': 'Toyota/134 CFNetwork/1410.0.3 Darwin/22.6.0',
|
|
413
|
+
'x-guid': this.uuid,
|
|
414
|
+
region: 'EU',
|
|
415
|
+
brand: this.brand,
|
|
416
|
+
'x-channel': 'ONEAPP',
|
|
417
|
+
'x-osversion': '16.7.2',
|
|
418
|
+
'x-locale': 'de-DE',
|
|
419
|
+
'x-brand': this.brand,
|
|
420
|
+
authorization: 'Bearer ' + this.session.access_token,
|
|
421
|
+
'accept-language': 'de-DE,de;q=0.9',
|
|
422
|
+
accept: '*/*',
|
|
423
|
+
'x-user-region': 'DE',
|
|
424
|
+
'x-api-key': 'tTZipv6liF74PwMfk9Ed68AQ0bISswwf3iHQdqcF',
|
|
425
|
+
API_KEY: 'tTZipv6liF74PwMfk9Ed68AQ0bISswwf3iHQdqcF',
|
|
426
|
+
// x-client-ref: HMAC-SHA256 with the app version as key over the user GUID (APK 2.23.0)
|
|
427
|
+
'x-client-ref': this.generate_hmac_sha256(this.CLIENT_VERSION, this.uuid),
|
|
428
|
+
'x-correlationid': uuidv4(),
|
|
429
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
430
|
+
'x-region': 'EU',
|
|
431
|
+
};
|
|
432
|
+
if (vin) {
|
|
433
|
+
headers.vin = vin;
|
|
434
|
+
}
|
|
435
|
+
return headers;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Wake the car and trigger a fresh vehicle/climate/EV status pull.
|
|
440
|
+
* After ~20s the regular updateDevices() loop reads the now-current data.
|
|
441
|
+
* @param {string} vin
|
|
442
|
+
*/
|
|
443
|
+
async forceRefresh(vin) {
|
|
444
|
+
const refreshEndpoints = [
|
|
445
|
+
{ name: 'status', url: 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v1/remote/status' },
|
|
446
|
+
{
|
|
447
|
+
name: 'climate',
|
|
448
|
+
url: 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v1/remote/refresh-climate-status',
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: 'electric',
|
|
452
|
+
url: 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v1/global/remote/electric/realtime-status',
|
|
453
|
+
},
|
|
454
|
+
];
|
|
455
|
+
for (const ep of refreshEndpoints) {
|
|
456
|
+
await this.requestClient({
|
|
457
|
+
method: 'post',
|
|
458
|
+
url: ep.url,
|
|
459
|
+
headers: this.buildApiHeaders(vin),
|
|
460
|
+
})
|
|
461
|
+
.then((res) => {
|
|
462
|
+
this.log.debug(`force-refresh ${ep.name}: ${JSON.stringify(res.data)}`);
|
|
463
|
+
})
|
|
464
|
+
.catch((error) => {
|
|
465
|
+
// Non-EV cars typically answer the electric realtime-status request with 404 or 422 - log quietly.
|
|
466
|
+
// 401/403 must NOT be swallowed: they signal an auth problem and have to surface like for any other endpoint.
|
|
467
|
+
if (
|
|
468
|
+
ep.name === 'electric' &&
|
|
469
|
+
error.response &&
|
|
470
|
+
(error.response.status === 404 || error.response.status === 422)
|
|
471
|
+
) {
|
|
472
|
+
this.log.debug(`force-refresh electric not supported (${error.response.status})`);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
this.log.warn(`force-refresh ${ep.name} failed: ${error.message}`);
|
|
476
|
+
error.response && this.log.debug(JSON.stringify(error.response.data));
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
this.refreshTimeout && clearTimeout(this.refreshTimeout);
|
|
480
|
+
this.refreshTimeout = setTimeout(async () => {
|
|
481
|
+
await this.updateDevices();
|
|
482
|
+
}, 20 * 1000);
|
|
483
|
+
}
|
|
484
|
+
|
|
371
485
|
async getDeviceList() {
|
|
372
486
|
await this.requestClient({
|
|
373
487
|
method: 'get',
|
|
@@ -380,7 +494,6 @@ class Toyota extends utils.Adapter {
|
|
|
380
494
|
guid: this.uuid,
|
|
381
495
|
'user-agent': 'Toyota/134 CFNetwork/1410.0.3 Darwin/22.6.0',
|
|
382
496
|
'x-guid': this.uuid,
|
|
383
|
-
'x-region': 'EU',
|
|
384
497
|
region: 'EU',
|
|
385
498
|
brand: this.brand,
|
|
386
499
|
'x-channel': 'ONEAPP',
|
|
@@ -389,11 +502,15 @@ class Toyota extends utils.Adapter {
|
|
|
389
502
|
'x-brand': this.brand,
|
|
390
503
|
authorization: 'Bearer ' + this.session.access_token,
|
|
391
504
|
'accept-language': 'de-DE,de;q=0.9',
|
|
392
|
-
'x-correlationid': '7683DC30-D4DA-4FEC-850E-F3557A7DCEF4',
|
|
393
|
-
'x-appversion': '2.4.2',
|
|
394
505
|
accept: '*/*',
|
|
395
506
|
'x-user-region': 'DE',
|
|
396
507
|
'x-api-key': 'tTZipv6liF74PwMfk9Ed68AQ0bISswwf3iHQdqcF',
|
|
508
|
+
API_KEY: 'tTZipv6liF74PwMfk9Ed68AQ0bISswwf3iHQdqcF',
|
|
509
|
+
// x-client-ref: HMAC-SHA256 with the app version as key over the user GUID (APK 2.23.0)
|
|
510
|
+
'x-client-ref': this.generate_hmac_sha256(this.CLIENT_VERSION, this.uuid),
|
|
511
|
+
'x-correlationid': uuidv4(),
|
|
512
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
513
|
+
'x-region': 'EU',
|
|
397
514
|
},
|
|
398
515
|
})
|
|
399
516
|
.then(async (res) => {
|
|
@@ -436,7 +553,8 @@ class Toyota extends utils.Adapter {
|
|
|
436
553
|
|
|
437
554
|
const remoteArray = [
|
|
438
555
|
{ command: 'climate-control', name: 'True = Start, False = Stop' },
|
|
439
|
-
{ command: 'refresh', name: '
|
|
556
|
+
{ command: 'refresh', name: 'Re-poll cached status from cloud' },
|
|
557
|
+
{ command: 'force-refresh', name: 'Wake car and request fresh status, climate and EV data' },
|
|
440
558
|
{ command: 'door', name: 'True = Lock, False = Unlock' },
|
|
441
559
|
];
|
|
442
560
|
for (const remote of remoteArray) {
|
|
@@ -462,6 +580,8 @@ class Toyota extends utils.Adapter {
|
|
|
462
580
|
error.response && this.log.error(JSON.stringify(error.response.data));
|
|
463
581
|
});
|
|
464
582
|
}
|
|
583
|
+
|
|
584
|
+
// ...existing code...
|
|
465
585
|
async cleanOldObjects(vin) {
|
|
466
586
|
const remoteState = await this.getObjectAsync(vin + '.addtionalInfo');
|
|
467
587
|
if (remoteState) {
|
|
@@ -469,11 +589,12 @@ class Toyota extends utils.Adapter {
|
|
|
469
589
|
await this.delObjectAsync(vin, { recursive: true });
|
|
470
590
|
}
|
|
471
591
|
}
|
|
592
|
+
|
|
472
593
|
async updateDevices() {
|
|
473
594
|
const statusArray = [
|
|
474
595
|
{
|
|
475
596
|
path: 'status',
|
|
476
|
-
url: 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v1/
|
|
597
|
+
url: 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v1/vehicle/status',
|
|
477
598
|
desc: 'Status of the car',
|
|
478
599
|
},
|
|
479
600
|
{
|
|
@@ -483,9 +604,24 @@ class Toyota extends utils.Adapter {
|
|
|
483
604
|
},
|
|
484
605
|
{
|
|
485
606
|
path: 'climate',
|
|
486
|
-
url: 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v1/
|
|
607
|
+
url: 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v1/vehicle/climate-status',
|
|
487
608
|
desc: 'Climate of the car',
|
|
488
609
|
},
|
|
610
|
+
{
|
|
611
|
+
path: 'electric',
|
|
612
|
+
url: 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v1/global/remote/electric/status',
|
|
613
|
+
desc: 'Electric/PHEV status (battery, charging, range)',
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
path: 'location',
|
|
617
|
+
url: 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v1/location',
|
|
618
|
+
desc: 'Current vehicle location',
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
path: 'health',
|
|
622
|
+
url: 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v1/vehiclehealth/status',
|
|
623
|
+
desc: 'Vehicle health and service warnings',
|
|
624
|
+
},
|
|
489
625
|
];
|
|
490
626
|
|
|
491
627
|
const headers = {
|
|
@@ -495,7 +631,6 @@ class Toyota extends utils.Adapter {
|
|
|
495
631
|
guid: this.uuid,
|
|
496
632
|
'user-agent': 'Toyota/134 CFNetwork/1410.0.3 Darwin/22.6.0',
|
|
497
633
|
'x-guid': this.uuid,
|
|
498
|
-
'x-region': 'EU',
|
|
499
634
|
region: 'EU',
|
|
500
635
|
brand: this.brand,
|
|
501
636
|
'x-channel': 'ONEAPP',
|
|
@@ -504,14 +639,21 @@ class Toyota extends utils.Adapter {
|
|
|
504
639
|
'x-brand': this.brand,
|
|
505
640
|
authorization: 'Bearer ' + this.session.access_token,
|
|
506
641
|
'accept-language': 'de-DE,de;q=0.9',
|
|
507
|
-
'x-correlationid': '7683DC30-D4DA-4FEC-850E-F3557A7DCEF4',
|
|
508
|
-
'x-appversion': '2.4.2',
|
|
509
642
|
accept: '*/*',
|
|
510
643
|
'x-user-region': 'DE',
|
|
511
644
|
'x-api-key': 'tTZipv6liF74PwMfk9Ed68AQ0bISswwf3iHQdqcF',
|
|
645
|
+
API_KEY: 'tTZipv6liF74PwMfk9Ed68AQ0bISswwf3iHQdqcF',
|
|
646
|
+
// Updated client-ref generation using new key
|
|
647
|
+
'x-client-ref': this.generate_hmac_sha256(this.CLIENT_VERSION, this.uuid),
|
|
648
|
+
'x-correlationid': uuidv4(),
|
|
649
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
650
|
+
'x-region': 'EU',
|
|
512
651
|
};
|
|
513
652
|
for (const vin of this.deviceArray) {
|
|
514
|
-
|
|
653
|
+
for (const element of statusArray) {
|
|
654
|
+
if (this.blockedEndpoints[vin] && this.blockedEndpoints[vin].includes(element.path)) {
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
515
657
|
const url = element.url.replace('$vin', vin);
|
|
516
658
|
headers.vin = vin;
|
|
517
659
|
|
|
@@ -548,12 +690,19 @@ class Toyota extends utils.Adapter {
|
|
|
548
690
|
|
|
549
691
|
return;
|
|
550
692
|
}
|
|
693
|
+
if (error.response.status >= 500) {
|
|
694
|
+
this.log.warn(JSON.stringify(error.response.data));
|
|
695
|
+
this.log.info(element.path + ' receive 500 error. Skip until restart');
|
|
696
|
+
this.blockedEndpoints[vin] = this.blockedEndpoints[vin] || [];
|
|
697
|
+
this.blockedEndpoints[vin].push(element.path);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
551
700
|
}
|
|
552
701
|
this.log.error(url);
|
|
553
702
|
this.log.error(error);
|
|
554
703
|
error.response && this.log.error(JSON.stringify(error.response.data));
|
|
555
704
|
});
|
|
556
|
-
}
|
|
705
|
+
}
|
|
557
706
|
}
|
|
558
707
|
}
|
|
559
708
|
|
|
@@ -572,11 +721,9 @@ class Toyota extends utils.Adapter {
|
|
|
572
721
|
'x-appbrand': this.brand,
|
|
573
722
|
'x-device-timezone': 'CEST',
|
|
574
723
|
'x-osname': 'iOS',
|
|
575
|
-
|
|
576
724
|
guid: this.uuid,
|
|
577
725
|
'user-agent': 'Toyota/134 CFNetwork/1410.0.3 Darwin/22.6.0',
|
|
578
726
|
'x-guid': this.uuid,
|
|
579
|
-
'x-region': 'EU',
|
|
580
727
|
region: 'EU',
|
|
581
728
|
brand: this.brand,
|
|
582
729
|
'x-channel': 'ONEAPP',
|
|
@@ -585,11 +732,15 @@ class Toyota extends utils.Adapter {
|
|
|
585
732
|
'x-brand': this.brand,
|
|
586
733
|
authorization: 'Bearer ' + this.session.access_token,
|
|
587
734
|
'accept-language': 'de-DE,de;q=0.9',
|
|
588
|
-
'x-correlationid': '7683DC30-D4DA-4FEC-850E-F3557A7DCEF4',
|
|
589
|
-
'x-appversion': '2.4.2',
|
|
590
735
|
accept: '*/*',
|
|
591
736
|
'x-user-region': 'DE',
|
|
592
737
|
'x-api-key': 'tTZipv6liF74PwMfk9Ed68AQ0bISswwf3iHQdqcF',
|
|
738
|
+
API_KEY: 'tTZipv6liF74PwMfk9Ed68AQ0bISswwf3iHQdqcF',
|
|
739
|
+
// Updated client-ref generation using new key
|
|
740
|
+
'x-client-ref': this.generate_hmac_sha256(this.CLIENT_VERSION, this.uuid),
|
|
741
|
+
'x-correlationid': uuidv4(),
|
|
742
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
743
|
+
'x-region': 'EU',
|
|
593
744
|
};
|
|
594
745
|
for (const vin of this.deviceArray) {
|
|
595
746
|
statusArray.forEach(async (element) => {
|
|
@@ -607,6 +758,12 @@ class Toyota extends utils.Adapter {
|
|
|
607
758
|
return;
|
|
608
759
|
}
|
|
609
760
|
const data = res.data.payload;
|
|
761
|
+
for (const trip of data.trips) {
|
|
762
|
+
delete trip.route;
|
|
763
|
+
}
|
|
764
|
+
for (const summary of data.summary) {
|
|
765
|
+
delete summary.histograms;
|
|
766
|
+
}
|
|
610
767
|
this.json2iob.parse(vin + '.' + element.path, data, {
|
|
611
768
|
forceIndex: true,
|
|
612
769
|
channelName: element.desc,
|
|
@@ -632,6 +789,7 @@ class Toyota extends utils.Adapter {
|
|
|
632
789
|
});
|
|
633
790
|
}
|
|
634
791
|
}
|
|
792
|
+
|
|
635
793
|
/**
|
|
636
794
|
* Is called when adapter shuts down - callback has to be called under any circumstances!
|
|
637
795
|
* @param {() => void} callback
|
|
@@ -645,7 +803,7 @@ class Toyota extends utils.Adapter {
|
|
|
645
803
|
this.updateInterval && clearInterval(this.updateInterval);
|
|
646
804
|
this.refreshTokenInterval && clearInterval(this.refreshTokenInterval);
|
|
647
805
|
callback();
|
|
648
|
-
} catch
|
|
806
|
+
} catch {
|
|
649
807
|
callback();
|
|
650
808
|
}
|
|
651
809
|
}
|
|
@@ -667,10 +825,14 @@ class Toyota extends utils.Adapter {
|
|
|
667
825
|
this.updateDevices();
|
|
668
826
|
return;
|
|
669
827
|
}
|
|
828
|
+
if (path === 'force-refresh') {
|
|
829
|
+
await this.forceRefresh(deviceId);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
670
832
|
const data = {};
|
|
671
833
|
let url = 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v1/global/remote/command';
|
|
672
834
|
if (path === 'climate-control') {
|
|
673
|
-
url = 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/
|
|
835
|
+
url = 'https://ctpa-oneapi.tceu-ctp-prd.toyotaconnectedeurope.io/v2/remote/climate-control';
|
|
674
836
|
data.command = state.val ? 'engine-start' : 'engine-stop';
|
|
675
837
|
}
|
|
676
838
|
if (path === 'door') {
|
|
@@ -686,25 +848,27 @@ class Toyota extends utils.Adapter {
|
|
|
686
848
|
'x-appbrand': this.brand,
|
|
687
849
|
'x-device-timezone': 'CEST',
|
|
688
850
|
'x-osname': 'iOS',
|
|
689
|
-
guid:
|
|
851
|
+
guid: this.uuid,
|
|
690
852
|
'user-agent': 'Toyota/134 CFNetwork/1410.0.3 Darwin/22.6.0',
|
|
691
|
-
'x-guid':
|
|
692
|
-
'x-region': 'EU',
|
|
853
|
+
'x-guid': this.uuid,
|
|
693
854
|
region: 'EU',
|
|
694
855
|
brand: this.brand,
|
|
695
856
|
'x-channel': 'ONEAPP',
|
|
696
|
-
vin:
|
|
857
|
+
vin: deviceId,
|
|
697
858
|
'x-osversion': '16.7.2',
|
|
698
859
|
'x-locale': 'de-DE',
|
|
699
860
|
'x-brand': this.brand,
|
|
700
861
|
authorization: 'Bearer ' + this.session.access_token,
|
|
701
862
|
'accept-language': 'de-DE,de;q=0.9',
|
|
702
|
-
'x-correlationid': 'D7F048C1-F0A1-4920-AA37-264C8A1FB4A3',
|
|
703
|
-
'x-appversion': '2.4.2',
|
|
704
863
|
accept: '*/*',
|
|
705
|
-
'content-type': 'application/json',
|
|
706
|
-
'x-api-key': 'tTZipv6liF74PwMfk9Ed68AQ0bISswwf3iHQdqcF',
|
|
707
864
|
'x-user-region': 'DE',
|
|
865
|
+
'x-api-key': 'tTZipv6liF74PwMfk9Ed68AQ0bISswwf3iHQdqcF',
|
|
866
|
+
API_KEY: 'tTZipv6liF74PwMfk9Ed68AQ0bISswwf3iHQdqcF',
|
|
867
|
+
// Updated client-ref generation using new key
|
|
868
|
+
'x-client-ref': this.generate_hmac_sha256(this.CLIENT_VERSION, this.uuid),
|
|
869
|
+
'x-correlationid': uuidv4(),
|
|
870
|
+
'x-appversion': this.CLIENT_VERSION,
|
|
871
|
+
'x-region': 'EU',
|
|
708
872
|
},
|
|
709
873
|
data: data,
|
|
710
874
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.toyota",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Adapter for myT Toyota/Lexus App",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TA2k",
|
|
@@ -23,21 +23,27 @@
|
|
|
23
23
|
"url": "https://github.com/TA2k/ioBroker.toyota"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@iobroker/adapter-core": "^3.
|
|
27
|
-
"axios": "^1.
|
|
28
|
-
"http-cookie-agent": "^
|
|
29
|
-
"json2iob": "^2.6.
|
|
30
|
-
"jsonwebtoken": "^9.0.
|
|
31
|
-
"qs": "^6.
|
|
32
|
-
"tough-cookie": "^
|
|
26
|
+
"@iobroker/adapter-core": "^3.4.1",
|
|
27
|
+
"axios": "^1.18.0",
|
|
28
|
+
"http-cookie-agent": "^8.0.0",
|
|
29
|
+
"json2iob": "^2.6.25",
|
|
30
|
+
"jsonwebtoken": "^9.0.3",
|
|
31
|
+
"qs": "^6.15.2",
|
|
32
|
+
"tough-cookie": "^6.0.1",
|
|
33
|
+
"uuid": "^14.0.1"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
38
|
-
"
|
|
36
|
+
"@eslint/js": "^10.0.1",
|
|
37
|
+
"@iobroker/testing": "^5.2.2",
|
|
38
|
+
"@types/node": "^26.0.0",
|
|
39
|
+
"eslint": "^10.5.0",
|
|
40
|
+
"globals": "^17.6.0",
|
|
41
|
+
"typescript": "~6.0.3"
|
|
39
42
|
},
|
|
40
43
|
"main": "main.js",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=22.13.0"
|
|
46
|
+
},
|
|
41
47
|
"files": [
|
|
42
48
|
"admin{,/!(src)/**}/!(tsconfig|tsconfig.*).json",
|
|
43
49
|
"admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}",
|
|
@@ -60,4 +66,5 @@
|
|
|
60
66
|
"url": "https://github.com/TA2k/ioBroker.toyota/issues"
|
|
61
67
|
},
|
|
62
68
|
"readmeFilename": "README.md"
|
|
69
|
+
|
|
63
70
|
}
|