iobroker.bmw 2.8.2 → 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,17 @@ 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
+
35
+ ### 2.8.3 (2024-11-18)
36
+
37
+ - login fixed
38
+
28
39
  ### 2.8.2 (2024-10-05)
29
40
 
30
41
  - fix error getvehicles v2 failed
@@ -81,16 +81,63 @@
81
81
  <label for="password" class="translate">App Password</label>
82
82
  </div>
83
83
  </div>
84
+ <div class="row">
85
+ <div class="col s6 input-field" id="captchaResponse">
86
+ Check Captcha Box and press Submit. Save the form.
87
+ <div>
88
+ <form id="captcha_form" action="#" method="post">
89
+ <!-- hCaptcha widget -->
90
+ <div class="h-captcha" data-sitekey="7244955f-8f30-4445-adff-4fefe059f815"></div>
91
+ <br />
92
+ <button type="submit" class="btn">Submit</button>
93
+ </form>
94
+
95
+ <!-- hCaptcha script -->
96
+ <script src="https://hcaptcha.com/1/api.js" async defer></script>
97
+ </div>
98
+ </div>
99
+ <p></p>
100
+ <script>
101
+ document.getElementById('captcha_form').addEventListener('submit', function (event) {
102
+ event.preventDefault(); // Prevent the default form submission
103
+
104
+ const hCaptchaResponse = document.querySelector('[name="h-captcha-response"]').value;
105
+ const responseElement = document.getElementById('captchaResponse');
106
+
107
+ if (hCaptchaResponse) {
108
+ document.getElementById('captcha').value = hCaptchaResponse;
109
+ //trigger change event
110
+ var event = new Event('change');
111
+ document.getElementById('captcha').dispatchEvent(event);
112
+ }
113
+ });
114
+ </script>
115
+
116
+ <input type="text" class="value" id="captcha" />
117
+ <label for="captcha" class="translate">Captcha</label>
118
+ </div>
84
119
  <div class="row">
85
120
  <div class="col s2 input-field">
86
121
  <select id="brand" class="value">
87
122
  <option value="bmw">BMW</option>
88
123
  <option value="mini">Mini</option>
124
+ <option value="toyota">Toyota Supra</option>
89
125
  </select>
90
126
  <label for="brand" class="translate">Brand</label>
91
127
  </div>
92
128
  </div>
93
-
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>
94
141
  <div class="row">
95
142
  <div class="col s2 input-field">
96
143
  <input type="number" class="value" id="interval" />
package/admin/words.js CHANGED
@@ -1,65 +1,66 @@
1
+ // eslint-disable-next-line no-unused-vars
1
2
  /*global systemDictionary:true */
2
- "use strict";
3
+ 'use strict';
3
4
 
4
5
  systemDictionary = {
5
- "bmw adapter settings": {
6
- en: "Adapter settings for bmw",
7
- de: "Adaptereinstellungen für bmw",
8
- ru: "Настройки адаптера для bmw",
9
- pt: "Configurações do adaptador para bmw",
10
- nl: "Adapterinstellingen voor bmw",
6
+ 'bmw adapter settings': {
7
+ en: 'Adapter settings for bmw',
8
+ de: 'Adaptereinstellungen für bmw',
9
+ ru: 'Настройки адаптера для bmw',
10
+ pt: 'Configurações do adaptador para bmw',
11
+ nl: 'Adapterinstellingen voor bmw',
11
12
  fr: "Paramètres d'adaptateur pour bmw",
12
13
  it: "Impostazioni dell'adattatore per bmw",
13
- es: "Ajustes del adaptador para bmw",
14
- pl: "Ustawienia adaptera dla bmw",
15
- "zh-cn": "bmw2的适配器设置",
14
+ es: 'Ajustes del adaptador para bmw',
15
+ pl: 'Ustawienia adaptera dla bmw',
16
+ 'zh-cn': 'bmw2的适配器设置',
16
17
  },
17
- "App Email": {
18
- en: "App Email",
19
- de: "App-E-Mail",
20
- ru: "Электронная почта приложения",
21
- pt: "Email do aplicativo",
22
- nl: "App-e-mail",
18
+ 'App Email': {
19
+ en: 'App Email',
20
+ de: 'App-E-Mail',
21
+ ru: 'Электронная почта приложения',
22
+ pt: 'Email do aplicativo',
23
+ nl: 'App-e-mail',
23
24
  fr: "Courriel de l'application",
24
25
  it: "E-mail dell'app",
25
- es: "Correo electrónico de la aplicación",
26
- pl: "E-mail aplikacji",
27
- "zh-cn": "应用电子邮件",
26
+ es: 'Correo electrónico de la aplicación',
27
+ pl: 'E-mail aplikacji',
28
+ 'zh-cn': '应用电子邮件',
28
29
  },
29
- "App Password": {
30
- en: "App Password",
31
- de: "App-Passwort",
32
- ru: "Пароль приложения",
33
- pt: "Senha de app",
34
- nl: "App-wachtwoord",
30
+ 'App Password': {
31
+ en: 'App Password',
32
+ de: 'App-Passwort',
33
+ ru: 'Пароль приложения',
34
+ pt: 'Senha de app',
35
+ nl: 'App-wachtwoord',
35
36
  fr: "Mot de passe de l'application",
36
37
  it: "Password dell'app",
37
- es: "Contraseña de la aplicación",
38
- pl: "Hasło do aplikacji",
39
- "zh-cn": "应用密码",
38
+ es: 'Contraseña de la aplicación',
39
+ pl: 'Hasło do aplikacji',
40
+ 'zh-cn': '应用密码',
40
41
  },
41
- "Update interval (in minutes)": {
42
- en: "Update interval (in minutes)",
43
- de: "Aktualisierungsintervall (in Minuten)",
44
- ru: "Интервал обновления (в минутах)",
45
- pt: "Intervalo de atualização (em minutos)",
46
- nl: "Update-interval (in minuten)",
47
- fr: "Intervalle de mise à jour (en minutes)",
48
- it: "Intervallo di aggiornamento (in minuti)",
49
- es: "Intervalo de actualización (en minutos)",
50
- pl: "Interwał aktualizacji (w minutach)",
51
- "zh-cn": "更新间隔(分钟)",
42
+ 'Update interval (in minutes)': {
43
+ en: 'Update interval (in minutes)',
44
+ de: 'Aktualisierungsintervall (in Minuten)',
45
+ ru: 'Интервал обновления (в минутах)',
46
+ pt: 'Intervalo de atualização (em minutos)',
47
+ nl: 'Update-interval (in minuten)',
48
+ fr: 'Intervalle de mise à jour (en minutes)',
49
+ it: 'Intervallo di aggiornamento (in minuti)',
50
+ es: 'Intervalo de actualización (en minutos)',
51
+ pl: 'Interwał aktualizacji (w minutach)',
52
+ 'zh-cn': '更新间隔(分钟)',
52
53
  },
53
54
  Brand: {
54
- en: "Brand",
55
- de: "Marke",
56
- ru: "Марка",
57
- pt: "Marca",
58
- nl: "Brand",
59
- fr: "Marque",
60
- it: "Marca",
61
- es: "Marca",
62
- pl: "Brandy",
63
- "zh-cn": "陪审团",
55
+ en: 'Brand',
56
+ de: 'Marke',
57
+ ru: 'Марка',
58
+ pt: 'Marca',
59
+ nl: 'Brand',
60
+ fr: 'Marque',
61
+ it: 'Marca',
62
+ es: 'Marca',
63
+ pl: 'Brandy',
64
+ 'zh-cn': '陪审团',
64
65
  },
65
66
  };
@@ -0,0 +1,50 @@
1
+ const globals = require('globals');
2
+ const js = require('@eslint/js');
3
+
4
+ const {
5
+ FlatCompat,
6
+ } = require('@eslint/eslintrc');
7
+
8
+ const compat = new FlatCompat({
9
+ baseDirectory: __dirname,
10
+ recommendedConfig: js.configs.recommended,
11
+ allConfig: js.configs.all
12
+ });
13
+
14
+ module.exports = [...compat.extends('eslint:recommended'), {
15
+ plugins: {},
16
+
17
+ languageOptions: {
18
+ globals: {
19
+ ...globals.node,
20
+ ...globals.mocha,
21
+ },
22
+
23
+ ecmaVersion: 2020,
24
+ sourceType: 'commonjs',
25
+ },
26
+
27
+ rules: {
28
+ indent: ['error', 2, {
29
+ SwitchCase: 1,
30
+ }],
31
+
32
+ 'no-console': 'off',
33
+
34
+ 'no-unused-vars': ['error', {
35
+ ignoreRestSiblings: true,
36
+ argsIgnorePattern: '^_',
37
+ }],
38
+
39
+ 'no-var': 'error',
40
+ 'no-trailing-spaces': 'error',
41
+ 'prefer-const': 'error',
42
+
43
+ quotes: ['error', 'single', {
44
+ avoidEscape: true,
45
+ allowTemplateLiterals: true,
46
+ }],
47
+
48
+ semi: ['error', 'always'],
49
+ },
50
+ }];
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "bmw",
4
- "version": "2.8.2",
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
+ },
19
+ "2.8.3": {
20
+ "en": "login fixed",
21
+ "de": "Anmeldung gefixt",
22
+ "ru": "фиксированный",
23
+ "pt": "login fixo",
24
+ "nl": "login vast",
25
+ "fr": "login corrigé",
26
+ "it": "login fisso",
27
+ "es": "login fijo",
28
+ "pl": "login stały",
29
+ "uk": "логін",
30
+ "zh-cn": "登录已固定"
31
+ },
6
32
  "2.8.2": {
7
33
  "en": "fix error getvehicles v2 failed",
8
34
  "de": "fehlerbehebung v2 versagt",
@@ -40,14 +66,6 @@
40
66
  "2.7.0": {
41
67
  "en": "Improve rate limit handling",
42
68
  "de": "Rate Limit Handling verbessert"
43
- },
44
- "2.6.3": {
45
- "en": "Add start and stop charging remotes",
46
- "de": "Start und Stop Charging Remotes hinzugefügt"
47
- },
48
- "2.6.2": {
49
- "en": "Fix Charging response parsing",
50
- "de": "Fix Charging Antwort Verarbeitung"
51
69
  }
52
70
  },
53
71
  "titleLang": {
@@ -76,12 +94,8 @@
76
94
  "uk": "Adapter for BMW",
77
95
  "zh-cn": "宝马适配器"
78
96
  },
79
- "authors": [
80
- "TA2k <tombox2020@gmail.com>"
81
- ],
82
- "keywords": [
83
- "BMW"
84
- ],
97
+ "authors": ["TA2k <tombox2020@gmail.com>"],
98
+ "keywords": ["BMW"],
85
99
  "license": "MIT",
86
100
  "platform": "Javascript/Node.js",
87
101
  "main": "main.js",
@@ -112,18 +126,17 @@
112
126
  }
113
127
  ]
114
128
  },
115
- "encryptedNative": [
116
- "password"
117
- ],
118
- "protectedNative": [
119
- "password"
120
- ],
129
+ "encryptedNative": ["password"],
130
+ "protectedNative": ["password"],
121
131
  "native": {
122
132
  "username": "",
123
133
  "password": "",
124
134
  "interval": 5,
125
135
  "brand": "bmw",
126
- "ignorelist": ""
136
+ "ignorelist": "",
137
+ "captcha": "",
138
+ "fetchChargeStats": true,
139
+ "fetchChargeSessions": true
127
140
  },
128
141
  "objects": [],
129
142
  "instanceObjects": [
@@ -215,7 +215,9 @@ function extractArray(adapter, element, key, path, write, preferedArrayName, for
215
215
  function isJsonString(str) {
216
216
  try {
217
217
  JSON.parse(str);
218
+ // eslint-disable-next-line no-unused-vars
218
219
  } catch (e) {
220
+
219
221
  return false;
220
222
  }
221
223
  return true;
package/main.js CHANGED
@@ -186,7 +186,20 @@ class Bmw extends utils.Adapter {
186
186
  this.log.error('Please set username and password');
187
187
  return;
188
188
  }
189
- await this.login();
189
+ const sessionState = await this.getStateAsync('auth.session');
190
+ if (sessionState && sessionState.val) {
191
+ this.session = JSON.parse(sessionState.val);
192
+ this.log.info('Session found. If the login fails please delete bmw.0.auth.session and restart the adapter');
193
+ this.log.debug(JSON.stringify(this.session));
194
+ await this.refreshToken();
195
+ } else {
196
+ if (!this.config.captcha) {
197
+ this.log.error('Please generate a captcha in the instance settings');
198
+ return;
199
+ }
200
+
201
+ await this.login();
202
+ }
190
203
 
191
204
  if (this.session.access_token) {
192
205
  this.log.info(`Start getting ${this.config.brand} vehicles`);
@@ -228,6 +241,7 @@ class Bmw extends utils.Adapter {
228
241
  'Mozilla/5.0 (iPhone; CPU iPhone OS 12_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Mobile/15E148 Safari/604.1',
229
242
  'Accept-Language': 'de-de',
230
243
  'Content-Type': 'application/x-www-form-urlencoded',
244
+ hcaptchatoken: this.config.captcha,
231
245
  };
232
246
  const [code_verifier, codeChallenge] = this.getCodeChallenge();
233
247
  const data = {
@@ -251,24 +265,28 @@ class Bmw extends utils.Adapter {
251
265
  data: qs.stringify(data),
252
266
  withCredentials: true,
253
267
  })
254
- .then((res) => {
268
+ .then(async (res) => {
255
269
  this.log.debug(JSON.stringify(res.data));
256
270
  return res.data;
257
271
  })
258
- .catch((error) => {
272
+ .catch(async (error) => {
259
273
  this.log.error('Login failed');
260
274
  this.log.error(error);
261
275
  if (error.response) {
262
276
  this.log.error(JSON.stringify(error.response.data));
263
277
  }
264
278
  if (error.response && error.response.status === 401) {
265
- this.log.error('Please check username and password or too many logins in 5 minutes');
266
-
279
+ this.log.error('Please check username and password or generate a new captcha in the instance settings');
280
+ this.log.error('Please wait 5 minutes before trying again');
267
281
  this.log.error('Start relogin in 5min');
282
+
268
283
  this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
269
284
  this.reLoginTimeout = setTimeout(
270
- () => {
271
- this.login();
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);
272
290
  },
273
291
  5000 * 60 * 1,
274
292
  );
@@ -276,6 +294,9 @@ class Bmw extends utils.Adapter {
276
294
  if (error.response && error.response.status === 400) {
277
295
  this.log.error('Please check username and password');
278
296
  }
297
+ if (error.response && error.response.status === 429) {
298
+ this.log.error('Login Rate Limit exceeded, please wait 5 minutes');
299
+ }
279
300
  });
280
301
  if (!authUrl || !authUrl.redirect_to) {
281
302
  this.log.error(JSON.stringify(authUrl));
@@ -326,9 +347,29 @@ class Bmw extends utils.Adapter {
326
347
  },
327
348
  data: 'code=' + code + '&redirect_uri=com.bmw.connected://oauth&grant_type=authorization_code&code_verifier=' + code_verifier,
328
349
  })
329
- .then((res) => {
350
+ .then(async (res) => {
330
351
  this.log.debug(JSON.stringify(res.data));
331
352
  this.session = res.data;
353
+ await this.extendObject('auth', {
354
+ type: 'channel',
355
+ common: {
356
+ name: 'Authentification Information',
357
+ },
358
+ native: {},
359
+ });
360
+ await this.extendObject('auth.session', {
361
+ type: 'state',
362
+ common: {
363
+ name: 'Session Token',
364
+ type: 'string',
365
+ role: 'value',
366
+ read: true,
367
+ write: false,
368
+ },
369
+ native: {},
370
+ });
371
+
372
+ this.setState('auth.session', JSON.stringify(this.session), true);
332
373
  this.setState('info.connection', true, true);
333
374
  return res.data;
334
375
  })
@@ -387,14 +428,18 @@ class Bmw extends utils.Adapter {
387
428
  }
388
429
  }
389
430
  this.vinArray.push(vehicle.vin);
390
- 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, {
391
436
  type: 'device',
392
437
  common: {
393
- name: vehicle.model || vehicle.attributes?.model,
438
+ name: vehicleName,
394
439
  },
395
440
  native: {},
396
441
  });
397
- await this.extendObjectAsync(vehicle.vin + '.state', {
442
+ await this.extendObject(vehicle.vin + '.state', {
398
443
  type: 'channel',
399
444
  common: {
400
445
  name: 'Current status of the car v4',
@@ -420,14 +465,22 @@ class Bmw extends utils.Adapter {
420
465
  { command: 'start-charging' },
421
466
  { command: 'stop-charging' },
422
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
+ },
423
475
  ];
424
476
  remoteArray.forEach((remote) => {
425
- this.setObjectNotExists(vehicle.vin + '.remotev2.' + remote.command, {
477
+ this.extendObject(vehicle.vin + '.remotev2.' + remote.command, {
426
478
  type: 'state',
427
479
  common: {
428
480
  name: remote.name || '',
429
481
  type: remote.type || 'boolean',
430
482
  role: remote.role || 'boolean',
483
+ def: remote.def == null ? false : remote.def,
431
484
  write: true,
432
485
  read: true,
433
486
  },
@@ -437,9 +490,10 @@ class Bmw extends utils.Adapter {
437
490
  this.json2iob.parse(vehicle.vin, vehicle, {
438
491
  forceIndex: true,
439
492
  descriptions: this.description,
493
+ channelName: vehicleName,
440
494
  });
441
495
 
442
- await this.updateChargingSessionv2(vehicle.vin);
496
+ await this.updateChargingSessionv2(vehicle.vin, 200);
443
497
  }
444
498
  })
445
499
  .catch((error) => {
@@ -542,7 +596,7 @@ class Bmw extends utils.Adapter {
542
596
  },
543
597
  native: {},
544
598
  });
545
- await this.setStateAsync(vin + '.servicedemands.json', JSON.stringify(res.data), true);
599
+ await this.setState(vin + '.servicedemands.json', JSON.stringify(res.data), true);
546
600
  })
547
601
  .catch(async (error) => {
548
602
  if (error.response && error.response.status === 429) {
@@ -628,11 +682,11 @@ class Bmw extends utils.Adapter {
628
682
  sleep(ms) {
629
683
  return new Promise((resolve) => setTimeout(resolve, ms));
630
684
  }
631
- async updateChargingSessionv2(vin) {
685
+ async updateChargingSessionv2(vin, maxResults = 40, dateInput) {
632
686
  if (this.nonChargingHistory[vin]) {
633
687
  return;
634
688
  }
635
- if (Date.now() - this.lastChargingSessionUpdate < 1000 * 60 * 60 * 6) {
689
+ if (Date.now() - this.lastChargingSessionUpdate < 1000 * 60 * 60 * 6 && !dateInput) {
636
690
  this.log.debug('updateChargingSessionv2 to early ' + vin);
637
691
  return;
638
692
  }
@@ -648,30 +702,40 @@ class Bmw extends utils.Adapter {
648
702
  };
649
703
 
650
704
  const d = new Date();
651
- const dateFormatted =
705
+ let dateFormatted =
652
706
  d.getFullYear().toString() +
653
707
  '-' +
654
708
  ((d.getMonth() + 1).toString().length == 2 ? (d.getMonth() + 1).toString() : '0' + (d.getMonth() + 1).toString());
655
709
  // const day = d.getDate().toString().length == 2 ? d.getDate().toString() : "0" + d.getDate().toString();
656
- 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');
657
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
+ }
658
717
  const urlArray = [];
659
- urlArray.push({
660
- url:
661
- 'https://cocoapi.bmwgroup.com/eadrax-chs/v2/charging-sessions?vin=' +
662
- vin +
663
- '&next_token&date=' +
664
- dateFormatted +
665
- '-01T00%3A00%3A00.000Z&maxResults=40&include_date_picker=true',
666
- path: '.chargingSessions.',
667
- name: 'chargingSessions',
668
- });
669
-
670
- urlArray.push({
671
- url: 'https://cocoapi.bmwgroup.com/eadrax-chs/v2/charging-statistics?vin=' + vin + '&currentDate=' + fullDate,
672
- path: '.charging-statistics.',
673
- name: 'charging statistics',
674
- });
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
+ }
675
739
  for (const element of urlArray) {
676
740
  await this.sleep(10000);
677
741
  this.log.debug('update ' + vin + element.path);
@@ -686,7 +750,7 @@ class Bmw extends utils.Adapter {
686
750
  if (data.chargingSessions) {
687
751
  data = data.chargingSessions;
688
752
  }
689
- await this.extendObjectAsync(vin + element.path + dateFormatted, {
753
+ await this.extendObject(vin + element.path + dateFormatted, {
690
754
  type: 'channel',
691
755
  common: {
692
756
  name: element.name + ' of the car v2',
@@ -707,13 +771,22 @@ class Bmw extends utils.Adapter {
707
771
  session.id = session.id.split('_')[1] ? session.id.split('_')[1] : session.id;
708
772
  session.timestamp = new Date(session.date).valueOf();
709
773
  if (session.energyCharged.replace) {
710
- session.energy = session.energyCharged.replace('~', '').trim().split(' ')[0];
711
- 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];
712
776
  }
713
777
  if (session.subtitle.replace) {
714
- const cleanedSubtitle = session.subtitle.replace('~', '').replace('', '').replace(' ', ' ').replace(' ', ' ').trim();
715
- session.duration = cleanedSubtitle.split(' ')[1];
716
- 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];
717
790
  }
718
791
  newSessions.push(session);
719
792
  } catch (error) {
@@ -721,6 +794,18 @@ class Bmw extends utils.Adapter {
721
794
  }
722
795
  }
723
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);
724
809
  await this.json2iob.parse(vin + element.path + dateFormatted, data, { preferedArrayName: 'date' });
725
810
  try {
726
811
  const datal = data.sessions[0];
@@ -820,6 +905,8 @@ class Bmw extends utils.Adapter {
820
905
  .then((res) => {
821
906
  this.log.debug(JSON.stringify(res.data));
822
907
  this.session = res.data;
908
+
909
+ this.setState('auth.session', JSON.stringify(this.session), true);
823
910
  this.setState('info.connection', true, true);
824
911
  return res.data;
825
912
  })
@@ -842,15 +929,22 @@ class Bmw extends utils.Adapter {
842
929
  * Is called when adapter shuts down - callback has to be called under any circumstances!
843
930
  * @param {() => void} callback
844
931
  */
845
- onUnload(callback) {
932
+ async onUnload(callback) {
846
933
  try {
847
934
  clearTimeout(this.refreshTimeout);
848
935
  clearTimeout(this.reLoginTimeout);
849
936
  clearInterval(this.updateInterval);
850
937
  clearInterval(this.refreshTokenInterval);
851
938
  this.demandInterval && clearInterval(this.demandInterval);
939
+ //get adapter settings and set captcha to null
940
+ if (this.config.captcha) {
941
+ const adapterSettings = await this.getForeignObjectAsync('system.adapter.' + this.namespace);
942
+ adapterSettings.native.captcha = null;
943
+ await this.setForeignObjectAsync('system.adapter.' + this.namespace, adapterSettings);
944
+ }
852
945
  callback();
853
946
  } catch (e) {
947
+ this.log.error(e);
854
948
  callback();
855
949
  }
856
950
  }
@@ -876,6 +970,11 @@ class Bmw extends utils.Adapter {
876
970
  this.updateDevices();
877
971
  return;
878
972
  }
973
+ if (command === 'fetch-charges') {
974
+ this.log.info('fetch charges');
975
+ await this.updateChargingSessionv2(vin, 200, state.val);
976
+ return;
977
+ }
879
978
  const action = command.split('_')[1];
880
979
  command = command.split('_')[0];
881
980
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.bmw",
3
- "version": "2.8.2",
3
+ "version": "2.8.4",
4
4
  "description": "Adapter for BMW",
5
5
  "author": {
6
6
  "name": "TA2k",
@@ -16,27 +16,30 @@
16
16
  "url": "https://github.com/TA2k/ioBroker.bmw"
17
17
  },
18
18
  "dependencies": {
19
- "@iobroker/adapter-core": "^3.2.1",
19
+ "@iobroker/adapter-core": "^3.2.2",
20
20
  "axios": "^1.7.7",
21
- "http-cookie-agent": "^5.0.4",
21
+ "http-cookie-agent": "^6.0.6",
22
22
  "json-bigint": "^1.0.0",
23
23
  "json2iob": "^2.6.12",
24
- "qs": "^6.13.0",
25
- "tough-cookie": "^4.1.4"
24
+ "qs": "^6.13.1",
25
+ "tough-cookie": "^5.0.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@alcalzone/release-script": "^3.8.0",
29
29
  "@alcalzone/release-script-plugin-iobroker": "^3.7.2",
30
30
  "@alcalzone/release-script-plugin-license": "^3.7.0",
31
31
  "@alcalzone/release-script-plugin-manual-review": "^3.7.0",
32
- "@iobroker/testing": "^4.1.3",
32
+ "@eslint/eslintrc": "^3.2.0",
33
+ "@eslint/js": "^9.15.0",
34
+ "@iobroker/testing": "^5.0.0",
33
35
  "@tsconfig/node16": "^16.1.3",
34
- "@types/node": "^20.16.10",
35
- "eslint": "^8.57.1",
36
+ "@types/node": "^22.9.1",
37
+ "eslint": "^9.15.0",
36
38
  "eslint-config-prettier": "^9.1.0",
37
39
  "eslint-plugin-prettier": "^5.2.1",
40
+ "globals": "^15.12.0",
38
41
  "prettier": "^3.3.3",
39
- "typescript": "~5.6.2"
42
+ "typescript": "~5.6.3"
40
43
  },
41
44
  "main": "main.js",
42
45
  "scripts": {