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 +11 -0
- package/admin/index_m.html +48 -1
- package/admin/words.js +50 -49
- package/eslint.config.cjs +50 -0
- package/io-package.json +35 -22
- package/lib/extractKeys.js +2 -0
- package/main.js +140 -41
- package/package.json +12 -9
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
|
package/admin/index_m.html
CHANGED
|
@@ -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
|
-
|
|
3
|
+
'use strict';
|
|
3
4
|
|
|
4
5
|
systemDictionary = {
|
|
5
|
-
|
|
6
|
-
en:
|
|
7
|
-
de:
|
|
8
|
-
ru:
|
|
9
|
-
pt:
|
|
10
|
-
nl:
|
|
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:
|
|
14
|
-
pl:
|
|
15
|
-
|
|
14
|
+
es: 'Ajustes del adaptador para bmw',
|
|
15
|
+
pl: 'Ustawienia adaptera dla bmw',
|
|
16
|
+
'zh-cn': 'bmw2的适配器设置',
|
|
16
17
|
},
|
|
17
|
-
|
|
18
|
-
en:
|
|
19
|
-
de:
|
|
20
|
-
ru:
|
|
21
|
-
pt:
|
|
22
|
-
nl:
|
|
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:
|
|
26
|
-
pl:
|
|
27
|
-
|
|
26
|
+
es: 'Correo electrónico de la aplicación',
|
|
27
|
+
pl: 'E-mail aplikacji',
|
|
28
|
+
'zh-cn': '应用电子邮件',
|
|
28
29
|
},
|
|
29
|
-
|
|
30
|
-
en:
|
|
31
|
-
de:
|
|
32
|
-
ru:
|
|
33
|
-
pt:
|
|
34
|
-
nl:
|
|
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:
|
|
38
|
-
pl:
|
|
39
|
-
|
|
38
|
+
es: 'Contraseña de la aplicación',
|
|
39
|
+
pl: 'Hasło do aplikacji',
|
|
40
|
+
'zh-cn': '应用密码',
|
|
40
41
|
},
|
|
41
|
-
|
|
42
|
-
en:
|
|
43
|
-
de:
|
|
44
|
-
ru:
|
|
45
|
-
pt:
|
|
46
|
-
nl:
|
|
47
|
-
fr:
|
|
48
|
-
it:
|
|
49
|
-
es:
|
|
50
|
-
pl:
|
|
51
|
-
|
|
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:
|
|
55
|
-
de:
|
|
56
|
-
ru:
|
|
57
|
-
pt:
|
|
58
|
-
nl:
|
|
59
|
-
fr:
|
|
60
|
-
it:
|
|
61
|
-
es:
|
|
62
|
-
pl:
|
|
63
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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": [
|
package/lib/extractKeys.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
438
|
+
name: vehicleName,
|
|
394
439
|
},
|
|
395
440
|
native: {},
|
|
396
441
|
});
|
|
397
|
-
await this.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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 + '¤tDate=' + 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.
|
|
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
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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];
|
|
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.
|
|
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.
|
|
19
|
+
"@iobroker/adapter-core": "^3.2.2",
|
|
20
20
|
"axios": "^1.7.7",
|
|
21
|
-
"http-cookie-agent": "^
|
|
21
|
+
"http-cookie-agent": "^6.0.6",
|
|
22
22
|
"json-bigint": "^1.0.0",
|
|
23
23
|
"json2iob": "^2.6.12",
|
|
24
|
-
"qs": "^6.13.
|
|
25
|
-
"tough-cookie": "^
|
|
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
|
-
"@
|
|
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": "^
|
|
35
|
-
"eslint": "^
|
|
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.
|
|
42
|
+
"typescript": "~5.6.3"
|
|
40
43
|
},
|
|
41
44
|
"main": "main.js",
|
|
42
45
|
"scripts": {
|