iobroker.bmw 2.5.5 → 2.5.7

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
@@ -26,6 +26,9 @@ bmw.0.VIN.remotev2
26
26
 
27
27
  ## Changelog
28
28
 
29
+ ### 2.5.6
30
+
31
+ - Fix charging message
29
32
  ### 2.5.5
30
33
 
31
34
  - Fix login
@@ -71,20 +71,29 @@
71
71
  <div class="row">
72
72
  <div class="col s6 input-field">
73
73
  <input type="text" class="value" id="username" />
74
- <label for="username" class="translate">BMW App Email</label>
74
+ <label for="username" class="translate">App Email</label>
75
75
  </div>
76
76
  </div>
77
77
  <div class="row">
78
78
  <div class="col s6 input-field">
79
79
  <input type="password" class="value" id="password" />
80
- <label for="password" class="translate">BMW App Password</label>
80
+ <label for="password" class="translate">App Password</label>
81
+ </div>
82
+ </div>
83
+ <div class="row">
84
+ <div class="col s2 input-field">
85
+ <select id="brand" class="value">
86
+ <option value="bmw">BMW</option>
87
+ <option value="mini">Mini</option>
88
+ </select>
89
+ <label for="brand" class="translate">Brand</label>
81
90
  </div>
82
91
  </div>
83
92
 
84
93
  <div class="row">
85
94
  <div class="col s2 input-field">
86
95
  <input type="number" class="value" id="interval" />
87
- <label for="interval" class="translate">Update interval in minutes</label>
96
+ <label for="interval" class="translate">Update interval (in minutes)"</label>
88
97
  </div>
89
98
  </div>
90
99
  </div>
package/admin/words.js CHANGED
@@ -2,40 +2,64 @@
2
2
  "use strict";
3
3
 
4
4
  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",
11
- fr: "Paramètres d'adaptateur pour bmw",
12
- it: "Impostazioni dell'adattatore per bmw",
13
- es: "Ajustes del adaptador para bmw",
14
- pl: "Ustawienia adaptera dla bmw",
15
- "zh-cn": "bmw2的适配器设置",
16
- },
17
- option1: {
18
- en: "option1",
19
- de: "Option 1",
20
- ru: "Опция 1",
21
- pt: "Opção 1",
22
- nl: "Optie 1",
23
- fr: "Option 1",
24
- it: "opzione 1",
25
- es: "Opción 1",
26
- pl: "opcja 1",
27
- "zh-cn": "选项1",
28
- },
29
- option2: {
30
- en: "option2",
31
- de: "Option 2",
32
- ru: "option2",
33
- pt: "opção 2",
34
- nl: "Optie 2",
35
- fr: "Option 2",
36
- it: "opzione 2",
37
- es: "opcion 2",
38
- pl: "Opcja 2",
39
- "zh-cn": "选项2",
40
- },
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",
11
+ fr: "Paramètres d'adaptateur pour bmw",
12
+ it: "Impostazioni dell'adattatore per bmw",
13
+ es: "Ajustes del adaptador para bmw",
14
+ pl: "Ustawienia adaptera dla bmw",
15
+ "zh-cn": "bmw2的适配器设置",
16
+ },
17
+ "App Email": {
18
+ en: "App Email",
19
+ de: "App-E-Mail",
20
+ ru: "Электронная почта приложения",
21
+ pt: "Email do aplicativo",
22
+ nl: "App-e-mail",
23
+ fr: "Courriel de l'application",
24
+ it: "E-mail dell'app",
25
+ es: "Correo electrónico de la aplicación",
26
+ pl: "E-mail aplikacji",
27
+ "zh-cn": "应用电子邮件",
28
+ },
29
+ "App Password": {
30
+ en: "App Password",
31
+ de: "App-Passwort",
32
+ ru: "Пароль приложения",
33
+ pt: "Senha de app",
34
+ nl: "App-wachtwoord",
35
+ fr: "Mot de passe de l'application",
36
+ it: "Password dell'app",
37
+ es: "Contraseña de la aplicación",
38
+ pl: "Hasło do aplikacji",
39
+ "zh-cn": "应用密码",
40
+ },
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": "更新间隔(分钟)",
52
+ },
53
+ 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": "陪审团",
64
+ },
41
65
  };
package/io-package.json CHANGED
@@ -1,8 +1,15 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "bmw",
4
- "version": "2.5.5",
4
+ "version": "2.5.7",
5
5
  "news": {
6
+ "2.5.7": {
7
+ "en": "Fix Quota problem",
8
+ "de": "Quota Problem es muss jetzt explizit die Marke BMW oder Mini in den Optionen gewählt werden."
9
+ },
10
+ "2.5.6": {
11
+ "en": "Fix charging message"
12
+ },
6
13
  "2.5.5": {
7
14
  "en": "Fix login"
8
15
  },
@@ -109,7 +116,8 @@
109
116
  "native": {
110
117
  "username": "",
111
118
  "password": "",
112
- "interval": 5
119
+ "interval": 5,
120
+ "brand": "bmw"
113
121
  },
114
122
  "objects": [],
115
123
  "instanceObjects": [
package/main.js CHANGED
@@ -7,9 +7,9 @@
7
7
  // The adapter-core module gives you access to the core ioBroker functions
8
8
  // you need to create an adapter
9
9
  const utils = require("@iobroker/adapter-core");
10
- const axios = require("axios");
10
+ const axios = require("axios").default;
11
11
 
12
- const { HttpsCookieAgent } = require("http-cookie-agent");
12
+ const { HttpsCookieAgent } = require("http-cookie-agent/http");
13
13
  const crypto = require("crypto");
14
14
  const qs = require("qs");
15
15
  const { extractKeys } = require("./lib/extractKeys");
@@ -29,6 +29,20 @@ class Bmw extends utils.Adapter {
29
29
  this.userAgent = "My%20BMW/8932 CFNetwork/978.0.7 Darwin/18.7.0";
30
30
  this.userAgentDart = "Dart/2.14 (dart:io)";
31
31
  this.xuserAgent = "android(SP1A.210812.016.C1);brand;99.0.0(99999);row";
32
+ this.updateInterval;
33
+ this.reLoginTimeout;
34
+ this.refreshTokenInterval;
35
+ this.extractKeys = extractKeys;
36
+ this.vinArray = [];
37
+ this.session = {};
38
+ this.statusBlock = {};
39
+ this.nonChargingHistory = {};
40
+ this.cookieJar = new tough.CookieJar(null, { ignoreError: true });
41
+
42
+ this.requestClient = axios.create({
43
+ withCredentials: true,
44
+ httpsAgent: new HttpsCookieAgent({ cookies: { jar: this.cookieJar } }),
45
+ });
32
46
  }
33
47
 
34
48
  /**
@@ -41,23 +55,7 @@ class Bmw extends utils.Adapter {
41
55
  this.log.info("Set interval to minimum 0.5");
42
56
  this.config.interval = 0.5;
43
57
  }
44
- this.cookieJar = new tough.CookieJar(null, { ignoreError: true });
45
58
 
46
- this.requestClient = axios.create({
47
- jar: this.cookieJar,
48
- withCredentials: true,
49
- httpsAgent: new HttpsCookieAgent({
50
- jar: this.cookieJar,
51
- }),
52
- });
53
- this.updateInterval = null;
54
- this.reLoginTimeout = null;
55
- this.refreshTokenTimeout = null;
56
- this.extractKeys = extractKeys;
57
- this.vinArray = [];
58
- this.session = {};
59
- this.statusBlock = {};
60
- this.nonChargingHistory = {};
61
59
  this.subscribeStates("*");
62
60
  if (!this.config.username || !this.config.password) {
63
61
  this.log.error("Please set username and password");
@@ -67,6 +65,8 @@ class Bmw extends utils.Adapter {
67
65
  if (this.session.access_token) {
68
66
  // await this.getVehicles(); //old depracted api
69
67
  await this.cleanObjects();
68
+
69
+ this.log.info(`Start getting ${this.config.brand} vehicles`);
70
70
  await this.getVehiclesv2();
71
71
  this.updateInterval = setInterval(async () => {
72
72
  await this.getVehiclesv2();
@@ -104,7 +104,6 @@ class Bmw extends utils.Adapter {
104
104
  url: "https://customer.bmwgroup.com/gcdm/oauth/authenticate",
105
105
  headers: headers,
106
106
  data: qs.stringify(data),
107
- jar: this.cookieJar,
108
107
  withCredentials: true,
109
108
  })
110
109
  .then((res) => {
@@ -144,7 +143,6 @@ class Bmw extends utils.Adapter {
144
143
  url: "https://customer.bmwgroup.com/gcdm/oauth/authenticate",
145
144
  headers: headers,
146
145
  data: qs.stringify(data),
147
- jar: this.cookieJar,
148
146
  withCredentials: true,
149
147
  maxRedirects: 0,
150
148
  })
@@ -170,8 +168,6 @@ class Bmw extends utils.Adapter {
170
168
  await this.requestClient({
171
169
  method: "post",
172
170
  url: "https://customer.bmwgroup.com/gcdm/oauth/token",
173
-
174
- jar: this.cookieJar,
175
171
  withCredentials: true,
176
172
  headers: {
177
173
  "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
@@ -255,85 +251,86 @@ class Bmw extends utils.Adapter {
255
251
  });
256
252
  }
257
253
  async getVehiclesv2() {
258
- const brands = ["bmw", "mini"];
259
- for (const brand of brands) {
260
- this.log.debug(`Start getting ${brand} vehicles`);
261
- const headers = {
262
- "user-agent": this.userAgentDart,
263
- "x-user-agent": this.xuserAgent.replace(";brand;", `;${brand};`),
264
- authorization: "Bearer " + this.session.access_token,
265
- "accept-language": "de-DE",
266
- host: "cocoapi.bmwgroup.com",
267
- "24-hour-format": "true",
268
- };
254
+ const brand = this.config.brand;
255
+ const headers = {
256
+ "user-agent": this.userAgentDart,
257
+ "x-user-agent": this.xuserAgent.replace(";brand;", `;${brand};`),
258
+ authorization: "Bearer " + this.session.access_token,
259
+ "accept-language": "de-DE",
260
+ host: "cocoapi.bmwgroup.com",
261
+ "24-hour-format": "true",
262
+ };
269
263
 
270
- await this.requestClient({
271
- method: "get",
272
- url: "https://cocoapi.bmwgroup.com/eadrax-vcs/v1/vehicles?apptimezone=120&appDateTime=" + Date.now() + "&tireGuardMode=ENABLED",
273
- headers: headers,
274
- })
275
- .then(async (res) => {
276
- this.log.debug(JSON.stringify(res.data));
264
+ await this.requestClient({
265
+ method: "get",
266
+ url: "https://cocoapi.bmwgroup.com/eadrax-vcs/v1/vehicles?apptimezone=120&appDateTime=" + Date.now() + "&tireGuardMode=ENABLED",
267
+ headers: headers,
268
+ })
269
+ .then(async (res) => {
270
+ this.log.debug(JSON.stringify(res.data));
271
+ this.log.info(`Found ${res.data.length} ${brand} vehicles`);
272
+ if (res.data.length === 0) {
273
+ this.log.info(`No ${brand} vehicles found please check brand in instance settings`);
274
+ return;
275
+ }
276
+ for (const vehicle of res.data) {
277
+ await this.setObjectNotExistsAsync(vehicle.vin, {
278
+ type: "device",
279
+ common: {
280
+ name: vehicle.model,
281
+ },
282
+ native: {},
283
+ });
277
284
 
278
- for (const vehicle of res.data) {
279
- await this.setObjectNotExistsAsync(vehicle.vin, {
280
- type: "device",
281
- common: {
282
- name: vehicle.model,
283
- },
284
- native: {},
285
- });
285
+ await this.setObjectNotExistsAsync(vehicle.vin + ".properties", {
286
+ type: "channel",
287
+ common: {
288
+ name: "Current status of the car v2",
289
+ },
290
+ native: {},
291
+ });
292
+ await this.setObjectNotExistsAsync(vehicle.vin + ".remotev2", {
293
+ type: "channel",
294
+ common: {
295
+ name: "Remote Controls",
296
+ },
297
+ native: {},
298
+ });
286
299
 
287
- await this.setObjectNotExistsAsync(vehicle.vin + ".properties", {
288
- type: "channel",
300
+ const remoteArray = [
301
+ { command: "door-lock" },
302
+ { command: "door-unlock" },
303
+ { command: "horn-blow" },
304
+ { command: "light-flash" },
305
+ { command: "vehicle-finder" },
306
+ { command: "climate-now_START" },
307
+ { command: "climate-now_STOP" },
308
+ { command: "force-refresh", name: "Force Refresh" },
309
+ ];
310
+ remoteArray.forEach((remote) => {
311
+ this.setObjectNotExists(vehicle.vin + ".remotev2." + remote.command, {
312
+ type: "state",
289
313
  common: {
290
- name: "Current status of the car v2",
314
+ name: remote.name || "",
315
+ type: remote.type || "boolean",
316
+ role: remote.role || "boolean",
317
+ write: true,
318
+ read: true,
291
319
  },
292
320
  native: {},
293
321
  });
294
- await this.setObjectNotExistsAsync(vehicle.vin + ".remotev2", {
295
- type: "channel",
296
- common: {
297
- name: "Remote Controls",
298
- },
299
- native: {},
300
- });
301
-
302
- const remoteArray = [
303
- { command: "door-lock" },
304
- { command: "door-unlock" },
305
- { command: "horn-blow" },
306
- { command: "light-flash" },
307
- { command: "vehicle-finder" },
308
- { command: "climate-now_START" },
309
- { command: "climate-now_STOP" },
310
- { command: "force-refresh", name: "Force Refresh" },
311
- ];
312
- remoteArray.forEach((remote) => {
313
- this.setObjectNotExists(vehicle.vin + ".remotev2." + remote.command, {
314
- type: "state",
315
- common: {
316
- name: remote.name || "",
317
- type: remote.type || "boolean",
318
- role: remote.role || "boolean",
319
- write: true,
320
- read: true,
321
- },
322
- native: {},
323
- });
324
- });
325
- this.extractKeys(this, vehicle.vin, vehicle, null, true);
326
- await this.sleep(5000);
327
- this.updateChargingSessionv2(vehicle.vin);
328
- }
329
- })
330
- .catch((error) => {
331
- this.log.error("getvehicles v2 failed");
332
- this.log.error(error);
333
- error.response && this.log.error(JSON.stringify(error.response.data));
334
- });
335
- await this.sleep(5000);
336
- }
322
+ });
323
+ this.extractKeys(this, vehicle.vin, vehicle, null, true);
324
+ await this.sleep(5000);
325
+ this.updateChargingSessionv2(vehicle.vin);
326
+ }
327
+ })
328
+ .catch((error) => {
329
+ this.log.error("getvehicles v2 failed");
330
+ this.log.error(error);
331
+ error.response && this.log.error(JSON.stringify(error.response.data));
332
+ });
333
+ await this.sleep(5000);
337
334
  }
338
335
  sleep(ms) {
339
336
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -344,7 +341,7 @@ class Bmw extends utils.Adapter {
344
341
  }
345
342
  const headers = {
346
343
  "user-agent": this.userAgentDart,
347
- "x-user-agent": this.xuserAgent.replace(";brand;", `;bmw;`),
344
+ "x-user-agent": this.xuserAgent.replace(";brand;", `;${this.config.brand};`),
348
345
  authorization: "Bearer " + this.session.access_token,
349
346
  "accept-language": "de-DE",
350
347
  "24-hour-format": "true",
@@ -397,8 +394,8 @@ class Bmw extends utils.Adapter {
397
394
  this.extractKeys(this, vin + element.path + dateFormatted, data);
398
395
  })
399
396
  .catch((error) => {
400
- if (error.response && (error.response.status === 422 || error.response.status === 403)) {
401
- this.log.info("No charging session available. Ignore " + vin);
397
+ if (error.response) {
398
+ this.log.info("No charging session available. Ignore " + vin + "until restart");
402
399
  this.nonChargingHistory[vin] = true;
403
400
  return;
404
401
  }
@@ -451,7 +448,6 @@ class Bmw extends utils.Adapter {
451
448
  await this.requestClient({
452
449
  method: "post",
453
450
  url: "https://customer.bmwgroup.com/gcdm/oauth/token",
454
- jar: this.cookieJar,
455
451
  withCredentials: true,
456
452
  headers: {
457
453
  "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
@@ -486,7 +482,6 @@ class Bmw extends utils.Adapter {
486
482
  try {
487
483
  clearTimeout(this.refreshTimeout);
488
484
  clearTimeout(this.reLoginTimeout);
489
- clearTimeout(this.refreshTokenTimeout);
490
485
  clearInterval(this.updateInterval);
491
486
  clearInterval(this.refreshTokenInterval);
492
487
  callback();
@@ -521,7 +516,7 @@ class Bmw extends utils.Adapter {
521
516
 
522
517
  const headers = {
523
518
  "user-agent": this.userAgentDart,
524
- "x-user-agent": this.xuserAgent.replace(";brand;", `;bmw;`),
519
+ "x-user-agent": this.xuserAgent.replace(";brand;", `;${this.config.brand};`),
525
520
  authorization: "Bearer " + this.session.access_token,
526
521
  "accept-language": "de-DE",
527
522
  host: "cocoapi.bmwgroup.com",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.bmw",
3
- "version": "2.5.5",
3
+ "version": "2.5.7",
4
4
  "description": "Adapter for BMW",
5
5
  "author": {
6
6
  "name": "TA2k",
@@ -16,30 +16,30 @@
16
16
  "url": "https://github.com/TA2k/ioBroker.bmw"
17
17
  },
18
18
  "dependencies": {
19
- "@iobroker/adapter-core": "^2.6.0",
20
- "axios": "^0.26.1",
21
- "http-cookie-agent": "^1.0.5",
19
+ "@iobroker/adapter-core": "^2.6.6",
20
+ "axios": "^0.27.2",
21
+ "http-cookie-agent": "^4.0.2",
22
22
  "json-bigint": "^1.0.0",
23
- "qs": "^6.10.3",
24
- "tough-cookie": "^4.0.0"
23
+ "qs": "^6.11.0",
24
+ "tough-cookie": "^4.1.2"
25
25
  },
26
26
  "devDependencies": {
27
- "@iobroker/testing": "^2.5.6",
28
- "@types/chai": "^4.3.0",
27
+ "@iobroker/testing": "^4.1.0",
28
+ "@types/chai": "^4.3.3",
29
29
  "@types/chai-as-promised": "^7.1.5",
30
- "@types/mocha": "^9.1.0",
31
- "@types/node": "^14.18.12",
30
+ "@types/mocha": "^9.1.1",
31
+ "@types/node": "^18.7.18",
32
32
  "@types/proxyquire": "^1.3.28",
33
- "@types/sinon": "^10.0.11",
33
+ "@types/sinon": "^10.0.13",
34
34
  "@types/sinon-chai": "^3.2.8",
35
35
  "chai": "^4.3.6",
36
36
  "chai-as-promised": "^7.1.1",
37
- "eslint": "^8.13.0",
38
- "mocha": "^9.2.2",
37
+ "eslint": "^8.23.1",
38
+ "mocha": "^10.0.0",
39
39
  "proxyquire": "^2.1.3",
40
- "sinon": "^13.0.1",
40
+ "sinon": "^14.0.0",
41
41
  "sinon-chai": "^3.7.0",
42
- "typescript": "^4.6.3"
42
+ "typescript": "^4.8.3"
43
43
  },
44
44
  "main": "main.js",
45
45
  "scripts": {