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 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
@@ -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.3",
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(async () => {
213
- await this.sleep(2000);
214
- await this.updateDevices();
215
- }, this.config.interval * 60 * 1000);
216
- this.demandInterval = setInterval(async () => {
217
- await this.sleep(2000);
218
- await this.updateDemands();
219
- await this.sleep(5000);
220
- await this.updateTrips();
221
- }, 24 * 60 * 60 * 1000);
222
- this.refreshTokenInterval = setInterval(async () => {
223
- await this.refreshToken();
224
- await this.sleep(5000);
225
- }, (this.session.expires_in - 123) * 1000);
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(async () => {
276
- //get adapter settings and set captcha to null
277
- const adapterSettings = await this.getForeignObjectAsync('system.adapter.' + this.namespace);
278
- adapterSettings.native.captcha = null;
279
- await this.setForeignObjectAsync('system.adapter.' + this.namespace, adapterSettings);
280
- }, 5000 * 60 * 1);
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
- await this.extendObjectAsync(vehicle.vin, {
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: vehicle.model || vehicle.attributes?.model,
438
+ name: vehicleName,
420
439
  },
421
440
  native: {},
422
441
  });
423
- await this.extendObjectAsync(vehicle.vin + '.state', {
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.setObjectNotExists(vehicle.vin + '.remotev2.' + remote.command, {
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
- this.getVehiclesv2();
482
- }, 1000 * 60 * 3);
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: 'https://cocoapi.bmwgroup.com/eadrax-vcs/v4/vehicles/state?apptimezone=120&appDateTime=' + Date.now() + '&tireGuardMode=ENABLED',
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.setStateAsync(vin + '.servicedemands.json', JSON.stringify(res.data), true);
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
- const dateFormatted =
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
- const fullDate = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60000).toISOString().replace('Z', '000');
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
- urlArray.push({
682
- url:
683
- 'https://cocoapi.bmwgroup.com/eadrax-chs/v2/charging-sessions?vin=' +
684
- vin +
685
- '&next_token&date=' +
686
- dateFormatted +
687
- '-01T00%3A00%3A00.000Z&maxResults=40&include_date_picker=true',
688
- path: '.chargingSessions.',
689
- name: 'chargingSessions',
690
- });
691
-
692
- urlArray.push({
693
- url: 'https://cocoapi.bmwgroup.com/eadrax-chs/v2/charging-statistics?vin=' + vin + '&currentDate=' + fullDate,
694
- path: '.charging-statistics.',
695
- name: 'charging statistics',
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 + '&currentDate=' + 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.extendObjectAsync(vin + element.path + dateFormatted, {
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
- const cleanedSubtitle = session.subtitle.replace('~', '').replace('', '').replace(' ', ' ').replace(' ', ' ').trim();
737
- session.duration = cleanedSubtitle.split(' ')[1];
738
- session.cost = cleanedSubtitle.split(' ')[2];
778
+ //subtitle = Zuhause2h 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
- this.login();
858
- }, 1000 * 60 * 1);
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",
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.0",
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",