iobroker.bmw 2.8.3 → 2.8.4
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/README.md +7 -0
- package/admin/index_m.html +12 -1
- package/io-package.json +17 -6
- package/main.js +130 -59
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,6 +25,13 @@ bmw.0.VIN.properties
|
|
|
25
25
|
bmw.0.VIN.remotev2
|
|
26
26
|
|
|
27
27
|
## Changelog
|
|
28
|
+
|
|
29
|
+
### 2.8.4 (2024-11-21)
|
|
30
|
+
|
|
31
|
+
- improved charging session parsing
|
|
32
|
+
- added remote to fetch charging session from a specific month
|
|
33
|
+
- added raw JSON of charging session for export
|
|
34
|
+
|
|
28
35
|
### 2.8.3 (2024-11-18)
|
|
29
36
|
|
|
30
37
|
- login fixed
|
package/admin/index_m.html
CHANGED
|
@@ -126,7 +126,18 @@
|
|
|
126
126
|
<label for="brand" class="translate">Brand</label>
|
|
127
127
|
</div>
|
|
128
128
|
</div>
|
|
129
|
-
|
|
129
|
+
<div class="row">
|
|
130
|
+
<div class="col s6 input-field">
|
|
131
|
+
<input type="checkbox" class="value" id="fetchChargeStats" />
|
|
132
|
+
<label for="fetchChargeStats" class="translate">Fetching Charging Statistics (Disable to save quota)</label>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="row">
|
|
136
|
+
<div class="col s6 input-field">
|
|
137
|
+
<input type="checkbox" class="value" id="fetchChargeSessions" />
|
|
138
|
+
<label for="fetchChargeSessions" class="translate">Fetching Charging Sessions (Disable to save quota)</label>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
130
141
|
<div class="row">
|
|
131
142
|
<div class="col s2 input-field">
|
|
132
143
|
<input type="number" class="value" id="interval" />
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "bmw",
|
|
4
|
-
"version": "2.8.
|
|
4
|
+
"version": "2.8.4",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.8.4": {
|
|
7
|
+
"en": "improved charging sessios parsing\nadded remote to fetch charging session from a specific month\nadd raw JSON of charging session for export",
|
|
8
|
+
"de": "verbesserte charging sessios Import\nRemote hinzugefügt, um charging sessios aus einem bestimmten Monat abzurufen\nRohes JSON der charging sessios für den Export hinzugefügt",
|
|
9
|
+
"ru": "улучшенный сеанс зарядки\nдобавить удаленное к сеансу зарядки с определенного месяца\nдобавить сырой JSON зарядной сессии для экспорта",
|
|
10
|
+
"pt": "análise de sessão de carregamento melhorada\nadicionar remoto para buscar sessão de carregamento de um mês específico\nadicionar JSON cru da sessão de carregamento para exportação",
|
|
11
|
+
"nl": "verbeterde laadsessie ontleden\nremote toevoegen aan opladen van een bepaalde maand\nrauw JSON toevoegen van laadsessie voor export",
|
|
12
|
+
"fr": "amélioration de l'analyse des sessions de chargement\najouter distant pour récupérer la session de chargement à partir d'un mois spécifique\najouter JSON brut de la session de tarification pour l'exportation",
|
|
13
|
+
"it": "migliore sessione di ricarica\naggiungere la sessione di ricarica remota a fetch da un mese specifico\naggiungere grezzo JSON di sessione di ricarica per l'esportazione",
|
|
14
|
+
"es": "mejora de la sesión de carga\nañadir remoto para buscar sesión de carga de un mes específico\nañadir JSON crudo de la sesión de carga para la exportación",
|
|
15
|
+
"pl": "ulepszona sesja pobierania opłat\ndodaj zdalny do pobierania sesji ładowania z określonego miesiąca\ndodać surowy JSON sesji pobierania opłat za eksport",
|
|
16
|
+
"uk": "поліпшення зарядки сеансу\nдодати дистанційне сеанс зарядки fetch з конкретного місяця\nдодати сиру JSON на зарядку для експорту",
|
|
17
|
+
"zh-cn": "改进充值会话解析\n添加远程以获取特定月的充电会话\n添加用于导出的充电会话的原始 JSON"
|
|
18
|
+
},
|
|
6
19
|
"2.8.3": {
|
|
7
20
|
"en": "login fixed",
|
|
8
21
|
"de": "Anmeldung gefixt",
|
|
@@ -53,10 +66,6 @@
|
|
|
53
66
|
"2.7.0": {
|
|
54
67
|
"en": "Improve rate limit handling",
|
|
55
68
|
"de": "Rate Limit Handling verbessert"
|
|
56
|
-
},
|
|
57
|
-
"2.6.3": {
|
|
58
|
-
"en": "Add start and stop charging remotes",
|
|
59
|
-
"de": "Start und Stop Charging Remotes hinzugefügt"
|
|
60
69
|
}
|
|
61
70
|
},
|
|
62
71
|
"titleLang": {
|
|
@@ -125,7 +134,9 @@
|
|
|
125
134
|
"interval": 5,
|
|
126
135
|
"brand": "bmw",
|
|
127
136
|
"ignorelist": "",
|
|
128
|
-
"captcha": ""
|
|
137
|
+
"captcha": "",
|
|
138
|
+
"fetchChargeStats": true,
|
|
139
|
+
"fetchChargeSessions": true
|
|
129
140
|
},
|
|
130
141
|
"objects": [],
|
|
131
142
|
"instanceObjects": [
|
package/main.js
CHANGED
|
@@ -209,20 +209,29 @@ class Bmw extends utils.Adapter {
|
|
|
209
209
|
await this.updateDemands();
|
|
210
210
|
await this.sleep(5000);
|
|
211
211
|
await this.updateTrips();
|
|
212
|
-
this.updateInterval = setInterval(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
212
|
+
this.updateInterval = setInterval(
|
|
213
|
+
async () => {
|
|
214
|
+
await this.sleep(2000);
|
|
215
|
+
await this.updateDevices();
|
|
216
|
+
},
|
|
217
|
+
this.config.interval * 60 * 1000,
|
|
218
|
+
);
|
|
219
|
+
this.demandInterval = setInterval(
|
|
220
|
+
async () => {
|
|
221
|
+
await this.sleep(2000);
|
|
222
|
+
await this.updateDemands();
|
|
223
|
+
await this.sleep(5000);
|
|
224
|
+
await this.updateTrips();
|
|
225
|
+
},
|
|
226
|
+
24 * 60 * 60 * 1000,
|
|
227
|
+
);
|
|
228
|
+
this.refreshTokenInterval = setInterval(
|
|
229
|
+
async () => {
|
|
230
|
+
await this.refreshToken();
|
|
231
|
+
await this.sleep(5000);
|
|
232
|
+
},
|
|
233
|
+
(this.session.expires_in - 123) * 1000,
|
|
234
|
+
);
|
|
226
235
|
}
|
|
227
236
|
}
|
|
228
237
|
async login() {
|
|
@@ -272,16 +281,22 @@ class Bmw extends utils.Adapter {
|
|
|
272
281
|
this.log.error('Start relogin in 5min');
|
|
273
282
|
|
|
274
283
|
this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
|
|
275
|
-
this.reLoginTimeout = setTimeout(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
284
|
+
this.reLoginTimeout = setTimeout(
|
|
285
|
+
async () => {
|
|
286
|
+
//get adapter settings and set captcha to null
|
|
287
|
+
const adapterSettings = await this.getForeignObjectAsync('system.adapter.' + this.namespace);
|
|
288
|
+
adapterSettings.native.captcha = null;
|
|
289
|
+
await this.setForeignObjectAsync('system.adapter.' + this.namespace, adapterSettings);
|
|
290
|
+
},
|
|
291
|
+
5000 * 60 * 1,
|
|
292
|
+
);
|
|
281
293
|
}
|
|
282
294
|
if (error.response && error.response.status === 400) {
|
|
283
295
|
this.log.error('Please check username and password');
|
|
284
296
|
}
|
|
297
|
+
if (error.response && error.response.status === 429) {
|
|
298
|
+
this.log.error('Login Rate Limit exceeded, please wait 5 minutes');
|
|
299
|
+
}
|
|
285
300
|
});
|
|
286
301
|
if (!authUrl || !authUrl.redirect_to) {
|
|
287
302
|
this.log.error(JSON.stringify(authUrl));
|
|
@@ -413,14 +428,18 @@ class Bmw extends utils.Adapter {
|
|
|
413
428
|
}
|
|
414
429
|
}
|
|
415
430
|
this.vinArray.push(vehicle.vin);
|
|
416
|
-
|
|
431
|
+
let vehicleName = vehicle.model;
|
|
432
|
+
if (!vehicleName && vehicle.attributes) {
|
|
433
|
+
vehicleName = vehicle.attributes.model;
|
|
434
|
+
}
|
|
435
|
+
await this.extendObject(vehicle.vin, {
|
|
417
436
|
type: 'device',
|
|
418
437
|
common: {
|
|
419
|
-
name:
|
|
438
|
+
name: vehicleName,
|
|
420
439
|
},
|
|
421
440
|
native: {},
|
|
422
441
|
});
|
|
423
|
-
await this.
|
|
442
|
+
await this.extendObject(vehicle.vin + '.state', {
|
|
424
443
|
type: 'channel',
|
|
425
444
|
common: {
|
|
426
445
|
name: 'Current status of the car v4',
|
|
@@ -446,14 +465,22 @@ class Bmw extends utils.Adapter {
|
|
|
446
465
|
{ command: 'start-charging' },
|
|
447
466
|
{ command: 'stop-charging' },
|
|
448
467
|
{ command: 'force-refresh', name: 'Force Refresh' },
|
|
468
|
+
{
|
|
469
|
+
command: 'fetch-charges',
|
|
470
|
+
name: 'Fetch Charge Sessions/Statistics for month',
|
|
471
|
+
type: 'string',
|
|
472
|
+
role: 'text',
|
|
473
|
+
def: '2024-06',
|
|
474
|
+
},
|
|
449
475
|
];
|
|
450
476
|
remoteArray.forEach((remote) => {
|
|
451
|
-
this.
|
|
477
|
+
this.extendObject(vehicle.vin + '.remotev2.' + remote.command, {
|
|
452
478
|
type: 'state',
|
|
453
479
|
common: {
|
|
454
480
|
name: remote.name || '',
|
|
455
481
|
type: remote.type || 'boolean',
|
|
456
482
|
role: remote.role || 'boolean',
|
|
483
|
+
def: remote.def == null ? false : remote.def,
|
|
457
484
|
write: true,
|
|
458
485
|
read: true,
|
|
459
486
|
},
|
|
@@ -463,9 +490,10 @@ class Bmw extends utils.Adapter {
|
|
|
463
490
|
this.json2iob.parse(vehicle.vin, vehicle, {
|
|
464
491
|
forceIndex: true,
|
|
465
492
|
descriptions: this.description,
|
|
493
|
+
channelName: vehicleName,
|
|
466
494
|
});
|
|
467
495
|
|
|
468
|
-
await this.updateChargingSessionv2(vehicle.vin);
|
|
496
|
+
await this.updateChargingSessionv2(vehicle.vin, 200);
|
|
469
497
|
}
|
|
470
498
|
})
|
|
471
499
|
.catch((error) => {
|
|
@@ -477,9 +505,12 @@ class Bmw extends utils.Adapter {
|
|
|
477
505
|
}
|
|
478
506
|
this.log.info('Adapter will retry in 3 minutes to get vehicles');
|
|
479
507
|
this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
|
|
480
|
-
this.reLoginTimeout = setTimeout(
|
|
481
|
-
|
|
482
|
-
|
|
508
|
+
this.reLoginTimeout = setTimeout(
|
|
509
|
+
() => {
|
|
510
|
+
this.getVehiclesv2();
|
|
511
|
+
},
|
|
512
|
+
1000 * 60 * 3,
|
|
513
|
+
);
|
|
483
514
|
});
|
|
484
515
|
await this.sleep(5000);
|
|
485
516
|
}
|
|
@@ -498,7 +529,8 @@ class Bmw extends utils.Adapter {
|
|
|
498
529
|
headers['bmw-vin'] = vin;
|
|
499
530
|
await this.requestClient({
|
|
500
531
|
method: 'get',
|
|
501
|
-
url:
|
|
532
|
+
url:
|
|
533
|
+
'https://cocoapi.bmwgroup.com/eadrax-vcs/v4/vehicles/state?apptimezone=120&appDateTime=' + Date.now() + '&tireGuardMode=ENABLED',
|
|
502
534
|
headers: headers,
|
|
503
535
|
})
|
|
504
536
|
.then(async (res) => {
|
|
@@ -564,7 +596,7 @@ class Bmw extends utils.Adapter {
|
|
|
564
596
|
},
|
|
565
597
|
native: {},
|
|
566
598
|
});
|
|
567
|
-
await this.
|
|
599
|
+
await this.setState(vin + '.servicedemands.json', JSON.stringify(res.data), true);
|
|
568
600
|
})
|
|
569
601
|
.catch(async (error) => {
|
|
570
602
|
if (error.response && error.response.status === 429) {
|
|
@@ -650,11 +682,11 @@ class Bmw extends utils.Adapter {
|
|
|
650
682
|
sleep(ms) {
|
|
651
683
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
652
684
|
}
|
|
653
|
-
async updateChargingSessionv2(vin) {
|
|
685
|
+
async updateChargingSessionv2(vin, maxResults = 40, dateInput) {
|
|
654
686
|
if (this.nonChargingHistory[vin]) {
|
|
655
687
|
return;
|
|
656
688
|
}
|
|
657
|
-
if (Date.now() - this.lastChargingSessionUpdate < 1000 * 60 * 60 * 6) {
|
|
689
|
+
if (Date.now() - this.lastChargingSessionUpdate < 1000 * 60 * 60 * 6 && !dateInput) {
|
|
658
690
|
this.log.debug('updateChargingSessionv2 to early ' + vin);
|
|
659
691
|
return;
|
|
660
692
|
}
|
|
@@ -670,30 +702,40 @@ class Bmw extends utils.Adapter {
|
|
|
670
702
|
};
|
|
671
703
|
|
|
672
704
|
const d = new Date();
|
|
673
|
-
|
|
705
|
+
let dateFormatted =
|
|
674
706
|
d.getFullYear().toString() +
|
|
675
707
|
'-' +
|
|
676
708
|
((d.getMonth() + 1).toString().length == 2 ? (d.getMonth() + 1).toString() : '0' + (d.getMonth() + 1).toString());
|
|
677
709
|
// const day = d.getDate().toString().length == 2 ? d.getDate().toString() : "0" + d.getDate().toString();
|
|
678
|
-
|
|
710
|
+
let fullDate = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60000).toISOString().replace('Z', '000');
|
|
679
711
|
|
|
712
|
+
if (dateInput) {
|
|
713
|
+
dateFormatted = dateInput;
|
|
714
|
+
const tempDate = new Date(dateInput + '-01T00:00:00.000Z');
|
|
715
|
+
fullDate = new Date(tempDate.getTime() - tempDate.getTimezoneOffset() * 60000).toISOString().replace('Z', '000');
|
|
716
|
+
}
|
|
680
717
|
const urlArray = [];
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
718
|
+
if (this.config.fetchChargeSessions) {
|
|
719
|
+
urlArray.push({
|
|
720
|
+
url:
|
|
721
|
+
'https://cocoapi.bmwgroup.com/eadrax-chs/v2/charging-sessions?vin=' +
|
|
722
|
+
vin +
|
|
723
|
+
'&next_token&date=' +
|
|
724
|
+
dateFormatted +
|
|
725
|
+
'-01T00%3A00%3A00.000Z&maxResults=' +
|
|
726
|
+
maxResults +
|
|
727
|
+
'&include_date_picker=false',
|
|
728
|
+
path: '.chargingSessions.',
|
|
729
|
+
name: 'chargingSessions',
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
if (this.config.fetchChargeStats) {
|
|
733
|
+
urlArray.push({
|
|
734
|
+
url: 'https://cocoapi.bmwgroup.com/eadrax-chs/v2/charging-statistics?vin=' + vin + '¤tDate=' + fullDate,
|
|
735
|
+
path: '.charging-statistics.',
|
|
736
|
+
name: 'charging statistics',
|
|
737
|
+
});
|
|
738
|
+
}
|
|
697
739
|
for (const element of urlArray) {
|
|
698
740
|
await this.sleep(10000);
|
|
699
741
|
this.log.debug('update ' + vin + element.path);
|
|
@@ -708,7 +750,7 @@ class Bmw extends utils.Adapter {
|
|
|
708
750
|
if (data.chargingSessions) {
|
|
709
751
|
data = data.chargingSessions;
|
|
710
752
|
}
|
|
711
|
-
await this.
|
|
753
|
+
await this.extendObject(vin + element.path + dateFormatted, {
|
|
712
754
|
type: 'channel',
|
|
713
755
|
common: {
|
|
714
756
|
name: element.name + ' of the car v2',
|
|
@@ -729,13 +771,22 @@ class Bmw extends utils.Adapter {
|
|
|
729
771
|
session.id = session.id.split('_')[1] ? session.id.split('_')[1] : session.id;
|
|
730
772
|
session.timestamp = new Date(session.date).valueOf();
|
|
731
773
|
if (session.energyCharged.replace) {
|
|
732
|
-
session.energy = session.energyCharged.replace('~', '').trim().split(' ')[0];
|
|
733
|
-
session.unit = session.energyCharged.replace('~', '').trim().split(' ')[1];
|
|
774
|
+
session.energy = session.energyCharged.replace('~', '').replace('<', '').trim().split(' ')[0];
|
|
775
|
+
session.unit = session.energyCharged.replace('~', '').replace('<', '').trim().split(' ')[1];
|
|
734
776
|
}
|
|
735
777
|
if (session.subtitle.replace) {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
778
|
+
//subtitle = Zuhause • 2h 16min • ~ 5,97 EUR
|
|
779
|
+
//remove all tildes
|
|
780
|
+
let cleanedSubtitle = session.subtitle.replace(/~/g, '');
|
|
781
|
+
//remove all small than
|
|
782
|
+
cleanedSubtitle = cleanedSubtitle.replace(/</g, '');
|
|
783
|
+
//split array on dots
|
|
784
|
+
cleanedSubtitle = cleanedSubtitle.split('•');
|
|
785
|
+
// const cleanedSubtitle = session.subtitle.replace('~', '').replace('•', '').replace(' ', ' ').replace(' ', ' ').trim();
|
|
786
|
+
session.location = cleanedSubtitle[0].trim();
|
|
787
|
+
session.duration = cleanedSubtitle[1].trim();
|
|
788
|
+
session.cost = cleanedSubtitle[2].trim().split(' ')[0];
|
|
789
|
+
session.currency = cleanedSubtitle[2].trim().split(' ')[1];
|
|
739
790
|
}
|
|
740
791
|
newSessions.push(session);
|
|
741
792
|
} catch (error) {
|
|
@@ -743,6 +794,18 @@ class Bmw extends utils.Adapter {
|
|
|
743
794
|
}
|
|
744
795
|
}
|
|
745
796
|
data.sessions = newSessions;
|
|
797
|
+
await this.extendObject(vin + element.path + dateFormatted + '.raw', {
|
|
798
|
+
type: 'state',
|
|
799
|
+
common: {
|
|
800
|
+
name: 'Raw Data as JSON',
|
|
801
|
+
type: 'string',
|
|
802
|
+
role: 'json',
|
|
803
|
+
write: false,
|
|
804
|
+
read: true,
|
|
805
|
+
},
|
|
806
|
+
native: {},
|
|
807
|
+
});
|
|
808
|
+
await this.setState(vin + element.path + dateFormatted + '.raw', JSON.stringify(data), true);
|
|
746
809
|
await this.json2iob.parse(vin + element.path + dateFormatted, data, { preferedArrayName: 'date' });
|
|
747
810
|
try {
|
|
748
811
|
const datal = data.sessions[0];
|
|
@@ -853,9 +916,12 @@ class Bmw extends utils.Adapter {
|
|
|
853
916
|
error.response && this.log.error(JSON.stringify(error.response.data));
|
|
854
917
|
this.log.error('Start relogin in 1min');
|
|
855
918
|
this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
|
|
856
|
-
this.reLoginTimeout = setTimeout(
|
|
857
|
-
|
|
858
|
-
|
|
919
|
+
this.reLoginTimeout = setTimeout(
|
|
920
|
+
() => {
|
|
921
|
+
this.login();
|
|
922
|
+
},
|
|
923
|
+
1000 * 60 * 1,
|
|
924
|
+
);
|
|
859
925
|
});
|
|
860
926
|
}
|
|
861
927
|
|
|
@@ -904,6 +970,11 @@ class Bmw extends utils.Adapter {
|
|
|
904
970
|
this.updateDevices();
|
|
905
971
|
return;
|
|
906
972
|
}
|
|
973
|
+
if (command === 'fetch-charges') {
|
|
974
|
+
this.log.info('fetch charges');
|
|
975
|
+
await this.updateChargingSessionv2(vin, 200, state.val);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
907
978
|
const action = command.split('_')[1];
|
|
908
979
|
command = command.split('_')[0];
|
|
909
980
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.bmw",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.4",
|
|
4
4
|
"description": "Adapter for BMW",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TA2k",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@eslint/js": "^9.15.0",
|
|
34
34
|
"@iobroker/testing": "^5.0.0",
|
|
35
35
|
"@tsconfig/node16": "^16.1.3",
|
|
36
|
-
"@types/node": "^22.9.
|
|
36
|
+
"@types/node": "^22.9.1",
|
|
37
37
|
"eslint": "^9.15.0",
|
|
38
38
|
"eslint-config-prettier": "^9.1.0",
|
|
39
39
|
"eslint-plugin-prettier": "^5.2.1",
|