iobroker.bmw 4.2.0 → 4.3.0
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 +20 -18
- package/admin/jsonConfig.json +49 -9
- package/io-package.json +15 -14
- package/lib/tools.js +0 -1
- package/main.js +129 -43
- package/package.json +3 -3
- package/telematic.json +2942 -2241
package/README.md
CHANGED
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
|
|
29
29
|
This adapter integrates BMW vehicles into ioBroker using the new BMW CarData API with OAuth2 authentication and real-time MQTT streaming. It provides comprehensive vehicle data monitoring for all BMW models linked to your BMW account.
|
|
30
30
|
|
|
31
|
+
## Data Updata while charging
|
|
32
|
+
|
|
33
|
+
While charging it can happens that the battery level is not updated via stream because the car is sleeping/standby when turn on the car the data will be updated. You can trigger an update via API `bmw.0.vin.remote.fetchViaAPI`
|
|
34
|
+
|
|
35
|
+
## Datapoint Description
|
|
36
|
+
|
|
37
|
+
A detailed datapoint description you can find here [telematic.json](telematic.json)
|
|
38
|
+
|
|
31
39
|
## Setup Instructions
|
|
32
40
|
|
|
33
41
|
### 1. BMW ConnectedDrive Portal Setup
|
|
@@ -63,7 +71,7 @@ After creating your Client ID, configure streaming:
|
|
|
63
71
|
4. Click **"Datenauswahl ändern"** (Change Data Selection) button
|
|
64
72
|
5. **Select ALL categories** (Vehicle Status, Charging, Trip Data, etc.)
|
|
65
73
|
6. **Manually check ALL 244 individual data points**
|
|
66
|
-
7. Or enter this in Google Developer Console `document.querySelectorAll('label.chakra-checkbox:not([data-checked])').forEach(l => l.click());`
|
|
74
|
+
7. Or enter this in Google Developer Console press F12 `document.querySelectorAll('label.chakra-checkbox:not([data-checked])').forEach(l => l.click());`
|
|
67
75
|
8. Save your configuration by clicking **"Stream löschen"** if needed to reset, then reconfigure
|
|
68
76
|
|
|
69
77
|
**Without selecting all data points, MQTT streaming will not provide complete data!**
|
|
@@ -74,16 +82,7 @@ After creating your Client ID, configure streaming:
|
|
|
74
82
|
2. Enter your **CarData Streaming Username** (found in BMW portal under CarData > Streaming section)
|
|
75
83
|
3. Select your vehicle **brand** (BMW, Mini, Toyota Supra)
|
|
76
84
|
4. Set **update interval** (minimum 10 minutes due to API quota)
|
|
77
|
-
5.
|
|
78
|
-
- **Basic Data** ✅ - Essential vehicle information (recommended)
|
|
79
|
-
- **Charging History** ✅ - Charging sessions and history (recommended)
|
|
80
|
-
- **Vehicle Image** - Vehicle image for display purposes
|
|
81
|
-
- **Location Based Charging Settings** - Location-specific charging preferences
|
|
82
|
-
- **Smart Maintenance Tyre Diagnosis** - Tyre condition and diagnosis data
|
|
83
|
-
- **Telematic Data** - Trip information and driving behavior analytics
|
|
84
|
-
6. Configure **VIN ignore list** if needed
|
|
85
|
-
|
|
86
|
-
**💡 Tip:** Only enable endpoints you actually need to conserve your 50 API calls per 24-hour quota. MQTT streaming provides real-time data without using quota.
|
|
85
|
+
5. Configure **VIN ignore list** if needed
|
|
87
86
|
|
|
88
87
|
### 4. Authentication Process
|
|
89
88
|
|
|
@@ -100,14 +99,11 @@ Vehicle data is organized under `bmw.0.VIN.*` where `VIN` represents your Vehicl
|
|
|
100
99
|
### Main Folder Structure
|
|
101
100
|
|
|
102
101
|
- **`bmw.0.VIN.api.*`** - API Data (Periodic Updates)
|
|
103
|
-
- Data fetched via BMW CarData REST API
|
|
102
|
+
- Data fetched via BMW CarData REST API via .remote.
|
|
104
103
|
- Uses API quota (50 calls per 24 hours)
|
|
105
|
-
- Updated based on configured interval
|
|
106
|
-
- Only includes endpoints you've enabled in settings
|
|
107
104
|
|
|
108
105
|
- **`bmw.0.VIN.stream.*`** - Stream Data (Real-time MQTT)
|
|
109
|
-
- Data received via real-time MQTT streaming
|
|
110
|
-
- No API quota consumption
|
|
106
|
+
- Data received via real-time MQTT streaming or remote.fetchViaAPI
|
|
111
107
|
- Instant updates when vehicle data changes
|
|
112
108
|
- Includes all 244 configured data points
|
|
113
109
|
|
|
@@ -120,17 +116,17 @@ You can enable/disable these endpoints in adapter settings (BMW CarData API v1):
|
|
|
120
116
|
- `bmw.0.VIN.api.image.*` - Vehicle image for display purposes
|
|
121
117
|
- `bmw.0.VIN.api.locationBasedChargingSettings.*` - Location-specific charging preferences and settings
|
|
122
118
|
- `bmw.0.VIN.api.smartMaintenanceTyreDiagnosis.*` - Smart maintenance system tyre condition and diagnosis
|
|
123
|
-
- `bmw.0.VIN.api.telematicData.*` - Vehicle telematic data including trip information and driving behavior
|
|
124
119
|
|
|
125
120
|
### Metadata
|
|
126
121
|
|
|
127
|
-
- `bmw.0.VIN.
|
|
122
|
+
- `bmw.0.VIN.lastStreamViaAPIUpdate` - Timestamp of last data update (API)
|
|
128
123
|
- `bmw.0.VIN.lastStreamUpdate` - Timestamp of last MQTT stream update
|
|
129
124
|
|
|
130
125
|
## Real-time Updates
|
|
131
126
|
|
|
132
127
|
The adapter receives real-time updates via MQTT streaming when:
|
|
133
128
|
|
|
129
|
+
- The car is not in sleep/standby
|
|
134
130
|
- Vehicle status changes (doors, windows, lights)
|
|
135
131
|
- Charging status updates
|
|
136
132
|
- Location changes during driving
|
|
@@ -213,6 +209,12 @@ If you're not seeing expected data in `VIN.api.*`:
|
|
|
213
209
|
This adapter is available at: [https://github.com/TA2k/ioBroker.bmw](https://github.com/TA2k/ioBroker.bmw)
|
|
214
210
|
|
|
215
211
|
## Changelog
|
|
212
|
+
### 4.3.0 (2025-10-09)
|
|
213
|
+
|
|
214
|
+
- improve logs
|
|
215
|
+
- add autocast
|
|
216
|
+
- add descriptions
|
|
217
|
+
|
|
216
218
|
### 4.2.0 (2025-10-04)
|
|
217
219
|
|
|
218
220
|
- improve token refresh
|
package/admin/jsonConfig.json
CHANGED
|
@@ -21,7 +21,12 @@
|
|
|
21
21
|
"pl": "Ten adapter używa nowego BMW CarData API z uwierzytelnianiem OAuth2 i strumieniem MQTT w czasie rzeczywistym.",
|
|
22
22
|
"zh-cn": "此适配器使用新的BMW CarData API,具有OAuth2身份验证和实时MQTT流。"
|
|
23
23
|
},
|
|
24
|
-
"newLine": true
|
|
24
|
+
"newLine": true,
|
|
25
|
+
"xs": 12,
|
|
26
|
+
"sm": 12,
|
|
27
|
+
"md": 12,
|
|
28
|
+
"lg": 12,
|
|
29
|
+
"xl": 12
|
|
25
30
|
},
|
|
26
31
|
"_setup": {
|
|
27
32
|
"type": "staticText",
|
|
@@ -41,7 +46,12 @@
|
|
|
41
46
|
"fontSize": "smaller",
|
|
42
47
|
"fontStyle": "italic"
|
|
43
48
|
},
|
|
44
|
-
"newLine": true
|
|
49
|
+
"newLine": true,
|
|
50
|
+
"xs": 12,
|
|
51
|
+
"sm": 12,
|
|
52
|
+
"md": 12,
|
|
53
|
+
"lg": 6,
|
|
54
|
+
"xl": 6
|
|
45
55
|
},
|
|
46
56
|
"_cardata_setup": {
|
|
47
57
|
"type": "staticText",
|
|
@@ -65,7 +75,12 @@
|
|
|
65
75
|
"padding": "10px",
|
|
66
76
|
"borderRadius": "4px"
|
|
67
77
|
},
|
|
68
|
-
"newLine": true
|
|
78
|
+
"newLine": true,
|
|
79
|
+
"xs": 12,
|
|
80
|
+
"sm": 12,
|
|
81
|
+
"md": 12,
|
|
82
|
+
"lg": 6,
|
|
83
|
+
"xl": 6
|
|
69
84
|
},
|
|
70
85
|
"clientId": {
|
|
71
86
|
"type": "text",
|
|
@@ -95,7 +110,11 @@
|
|
|
95
110
|
},
|
|
96
111
|
"placeholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
97
112
|
"newLine": true,
|
|
98
|
-
"
|
|
113
|
+
"xs": 12,
|
|
114
|
+
"sm": 12,
|
|
115
|
+
"md": 12,
|
|
116
|
+
"lg": 6,
|
|
117
|
+
"xl": 6
|
|
99
118
|
},
|
|
100
119
|
"cardataStreamingUsername": {
|
|
101
120
|
"type": "text",
|
|
@@ -125,7 +144,11 @@
|
|
|
125
144
|
},
|
|
126
145
|
"placeholder": "streaming_username_from_bmw_portal",
|
|
127
146
|
"newLine": true,
|
|
128
|
-
"
|
|
147
|
+
"xs": 12,
|
|
148
|
+
"sm": 12,
|
|
149
|
+
"md": 12,
|
|
150
|
+
"lg": 6,
|
|
151
|
+
"xl": 6
|
|
129
152
|
},
|
|
130
153
|
"brand": {
|
|
131
154
|
"type": "select",
|
|
@@ -156,7 +179,11 @@
|
|
|
156
179
|
}
|
|
157
180
|
],
|
|
158
181
|
"newLine": true,
|
|
159
|
-
"
|
|
182
|
+
"xs": 12,
|
|
183
|
+
"sm": 12,
|
|
184
|
+
"md": 12,
|
|
185
|
+
"lg": 6,
|
|
186
|
+
"xl": 6
|
|
160
187
|
},
|
|
161
188
|
"interval": {
|
|
162
189
|
"type": "number",
|
|
@@ -188,7 +215,11 @@
|
|
|
188
215
|
"max": 1440,
|
|
189
216
|
"default": 60,
|
|
190
217
|
"newLine": true,
|
|
191
|
-
"
|
|
218
|
+
"xs": 12,
|
|
219
|
+
"sm": 12,
|
|
220
|
+
"md": 12,
|
|
221
|
+
"lg": 6,
|
|
222
|
+
"xl": 6
|
|
192
223
|
},
|
|
193
224
|
"ignorelist": {
|
|
194
225
|
"type": "text",
|
|
@@ -218,7 +249,11 @@
|
|
|
218
249
|
},
|
|
219
250
|
"placeholder": "VIN1,VIN2,VIN3",
|
|
220
251
|
"newLine": true,
|
|
221
|
-
"
|
|
252
|
+
"xs": 12,
|
|
253
|
+
"sm": 12,
|
|
254
|
+
"md": 6,
|
|
255
|
+
"lg": 6,
|
|
256
|
+
"xl": 6
|
|
222
257
|
},
|
|
223
258
|
"_api_endpoints": {
|
|
224
259
|
"type": "header",
|
|
@@ -255,7 +290,12 @@
|
|
|
255
290
|
"fontStyle": "italic",
|
|
256
291
|
"color": "#666"
|
|
257
292
|
},
|
|
258
|
-
"newLine": true
|
|
293
|
+
"newLine": true,
|
|
294
|
+
"xs": 12,
|
|
295
|
+
"sm": 12,
|
|
296
|
+
"md": 12,
|
|
297
|
+
"lg": 12,
|
|
298
|
+
"xl": 12
|
|
259
299
|
}
|
|
260
300
|
}
|
|
261
301
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "bmw",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.3.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"4.3.0": {
|
|
7
|
+
"en": "improve logs\nadd autocast\nadd descriptions",
|
|
8
|
+
"de": "logs verbessern\nautocast hinzufügen\nbeschreibungen hinzufügen",
|
|
9
|
+
"ru": "улучшить бревна\nдобавить autocast\nдобавить описания",
|
|
10
|
+
"pt": "melhorar logs\nadicionar autocast\nadicionar descrições",
|
|
11
|
+
"nl": "logs verbeteren\nautocast toevoegen\nbeschrijvingen toevoegen",
|
|
12
|
+
"fr": "améliorer les registres\najouter autocast\najouter des descriptions",
|
|
13
|
+
"it": "migliorare i registri\naggiungere autocast\naggiungere descrizioni",
|
|
14
|
+
"es": "mejorar los registros\nañadir autocast\nañadir descripciones",
|
|
15
|
+
"pl": "poprawić logi\ndodaj autocast\ndodaj opisy",
|
|
16
|
+
"uk": "поліпшення колод\nadd autocast\nдодати описи",
|
|
17
|
+
"zh-cn": "改进日志\n添加自动播报\n添加说明"
|
|
18
|
+
},
|
|
6
19
|
"4.2.0": {
|
|
7
20
|
"en": "improve token refresh\nfix image fetching",
|
|
8
21
|
"de": "verbessern sie die token-erfrischung\nbild-feeding fixieren",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "aktualizacja axios\nproblemy z ustawianiem wykryte przez kontroler repozytorium (# 88)\nniektóre małe czystki kodu / modernizacje\ndodaj / przetłumacz opis\naktualizacja logo",
|
|
81
94
|
"uk": "оновлення axios\nфіксація питань, виявлених репозиторійною перевіркою (#88)\nдеякі невеликі очищення коду / модернізації\nопис\nоновлення логотипу",
|
|
82
95
|
"zh-cn": "更新轴线\n修复仓库检查器检测到的问题(#88)\n一些小代码清理/现代化\n添加/翻译说明\n更新标志"
|
|
83
|
-
},
|
|
84
|
-
"2.9.4": {
|
|
85
|
-
"en": "fix for Mitbenutzer Feature",
|
|
86
|
-
"de": "fix für Mitbenutzer Feature",
|
|
87
|
-
"ru": "обсуждение Mitbenutzer Feature",
|
|
88
|
-
"pt": "correção para recurso Mitbenutzer",
|
|
89
|
-
"nl": "fix voor Mitbenutzer Feature",
|
|
90
|
-
"fr": "correction pour la fonctionnalité Mitbenutzer",
|
|
91
|
-
"it": "fix per la funzione Mitbenutzer",
|
|
92
|
-
"es": "fijado para Mitbenutzer Feature",
|
|
93
|
-
"pl": "fix dla Mitbenutzer Feature",
|
|
94
|
-
"uk": "фіксатор для функції Mitbenutzer",
|
|
95
|
-
"zh-cn": "用于 Mitbenutzer 特性的固定"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
|
@@ -166,6 +166,7 @@
|
|
|
166
166
|
"protectedNative": [],
|
|
167
167
|
"native": {
|
|
168
168
|
"clientId": "",
|
|
169
|
+
"cardataStreamingUsername": "",
|
|
169
170
|
"interval": 60,
|
|
170
171
|
"brand": "bmw",
|
|
171
172
|
"ignorelist": ""
|
package/lib/tools.js
CHANGED
package/main.js
CHANGED
|
@@ -8,6 +8,8 @@ const crypto = require('crypto');
|
|
|
8
8
|
const qs = require('qs');
|
|
9
9
|
const Json2iob = require('json2iob');
|
|
10
10
|
const axiosRetry = require('axios-retry').default;
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
11
13
|
|
|
12
14
|
// BMW CarData API quota limit (calls per 24 hours)
|
|
13
15
|
const API_QUOTA_LIMIT = 50;
|
|
@@ -48,6 +50,10 @@ class Bmw extends utils.Adapter {
|
|
|
48
50
|
// Flag to track initial login (not adapter restart)
|
|
49
51
|
this.initialLogin = false;
|
|
50
52
|
|
|
53
|
+
// Initialize descriptions and states from telematic.json
|
|
54
|
+
this.description = {};
|
|
55
|
+
this.states = {};
|
|
56
|
+
|
|
51
57
|
this.requestClient = axios.create({
|
|
52
58
|
withCredentials: true,
|
|
53
59
|
});
|
|
@@ -80,6 +86,8 @@ class Bmw extends utils.Adapter {
|
|
|
80
86
|
|
|
81
87
|
this.subscribeStates('*');
|
|
82
88
|
|
|
89
|
+
this.initializeTelematicData();
|
|
90
|
+
|
|
83
91
|
// Initialize API quota tracking - restore from saved history
|
|
84
92
|
const apiCallsHistoryState = await this.getStateAsync('info.apiCallsHistory');
|
|
85
93
|
if (apiCallsHistoryState?.val && typeof apiCallsHistoryState.val === 'string') {
|
|
@@ -164,6 +172,10 @@ class Bmw extends utils.Adapter {
|
|
|
164
172
|
// Store telematic data directly in stream folder
|
|
165
173
|
await this.json2iob.parse(`${vin}.stream`, telematicData.telematicData, {
|
|
166
174
|
descriptions: this.description,
|
|
175
|
+
states: this.states,
|
|
176
|
+
autoCast: true,
|
|
177
|
+
|
|
178
|
+
useCompletePathForDescriptionsAndStates: true,
|
|
167
179
|
forceIndex: true,
|
|
168
180
|
});
|
|
169
181
|
|
|
@@ -179,7 +191,6 @@ class Bmw extends utils.Adapter {
|
|
|
179
191
|
} catch (error) {
|
|
180
192
|
this.log.error(`Periodic telematic data fetch failed for ${vin}: ${error.message}`);
|
|
181
193
|
}
|
|
182
|
-
break; // Only one vehicle per interval to conserve quota
|
|
183
194
|
}
|
|
184
195
|
},
|
|
185
196
|
this.config.interval * 60 * 1000,
|
|
@@ -240,7 +251,7 @@ class Bmw extends utils.Adapter {
|
|
|
240
251
|
})
|
|
241
252
|
.catch(error => {
|
|
242
253
|
this.log.error(`Device code request failed: ${error.message}`);
|
|
243
|
-
this.log.
|
|
254
|
+
this.log.debug(`Error stack: ${error.stack}`);
|
|
244
255
|
if (error.response) {
|
|
245
256
|
this.log.error(`Response status: ${error.response.status}`);
|
|
246
257
|
this.log.error(`Response headers: ${JSON.stringify(error.response.headers)}`);
|
|
@@ -303,6 +314,7 @@ class Bmw extends utils.Adapter {
|
|
|
303
314
|
this.log.debug(`Starting token polling, will timeout in ${expires_in}s`);
|
|
304
315
|
while (Date.now() - startTime < expires_in * 1000) {
|
|
305
316
|
this.log.debug(`Waiting ${interval}s before next token poll...`);
|
|
317
|
+
this.log.debug(`Visit: ${verification_uri_complete}`);
|
|
306
318
|
await this.sleep(interval * 1000);
|
|
307
319
|
|
|
308
320
|
try {
|
|
@@ -688,6 +700,28 @@ class Bmw extends utils.Adapter {
|
|
|
688
700
|
}
|
|
689
701
|
}
|
|
690
702
|
|
|
703
|
+
/**
|
|
704
|
+
* Initialize descriptions and states from telematic.json
|
|
705
|
+
*/
|
|
706
|
+
initializeTelematicData() {
|
|
707
|
+
try {
|
|
708
|
+
const telematicData = JSON.parse(fs.readFileSync(path.join(__dirname, 'telematic.json'), 'utf8'));
|
|
709
|
+
|
|
710
|
+
telematicData.forEach(item => {
|
|
711
|
+
if (item.technical_identifier && item.cardata_element) {
|
|
712
|
+
this.description[item.technical_identifier] = item.cardata_element;
|
|
713
|
+
if (Array.isArray(item.typical_value_range)) {
|
|
714
|
+
this.states[item.technical_identifier] = item.typical_value_range;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
this.log.info(`Initialized ${Object.keys(this.description).length} descriptions, ${Object.keys(this.states).length} states`);
|
|
720
|
+
} catch (error) {
|
|
721
|
+
this.log.error(`Error initializing telematic data: ${error.message}`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
691
725
|
sleep(ms) {
|
|
692
726
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
693
727
|
}
|
|
@@ -778,7 +812,7 @@ class Bmw extends utils.Adapter {
|
|
|
778
812
|
|
|
779
813
|
// HTTP response errors - check status code
|
|
780
814
|
if (error.response) {
|
|
781
|
-
this.log.error(`Response status: ${JSON.stringify(error.response)}`);
|
|
815
|
+
this.log.error(`Response status: ${JSON.stringify(error.response.data)}`);
|
|
782
816
|
const status = error.response.status;
|
|
783
817
|
if (status >= 400 && status < 500) {
|
|
784
818
|
// 4xx errors indicate authentication problems - reset needed
|
|
@@ -789,7 +823,7 @@ class Bmw extends utils.Adapter {
|
|
|
789
823
|
}
|
|
790
824
|
|
|
791
825
|
this.log.warn(
|
|
792
|
-
|
|
826
|
+
`Token refresh failed, will retry on next refresh cycle. You can also delete bmw.0.cardataauth.session state to force re-login.`,
|
|
793
827
|
);
|
|
794
828
|
this.setState('info.connection', false, true);
|
|
795
829
|
return;
|
|
@@ -857,16 +891,16 @@ class Bmw extends utils.Adapter {
|
|
|
857
891
|
error.message.includes('Connection refused') ||
|
|
858
892
|
error.message.includes('Not authorized'))
|
|
859
893
|
) {
|
|
860
|
-
this.log.warn(
|
|
894
|
+
this.log.warn(`MQTT authentication failed - refreshing token for next auto-reconnect`);
|
|
861
895
|
try {
|
|
862
896
|
await this.refreshToken();
|
|
863
|
-
this.log.
|
|
897
|
+
this.log.debug(`Token refreshed - MQTT client will auto-reconnect with new credentials`);
|
|
864
898
|
} catch (refreshError) {
|
|
865
899
|
this.log.error(`Token refresh failed: ${refreshError}`);
|
|
866
|
-
this.log.warn(
|
|
900
|
+
this.log.warn(`MQTT will retry connection with current token via built-in reconnection`);
|
|
867
901
|
}
|
|
868
902
|
} else {
|
|
869
|
-
this.log.debug(
|
|
903
|
+
this.log.debug(`Non-authentication MQTT error - letting built-in client handle reconnection`);
|
|
870
904
|
}
|
|
871
905
|
});
|
|
872
906
|
|
|
@@ -908,7 +942,10 @@ class Bmw extends utils.Adapter {
|
|
|
908
942
|
await this.json2iob.parse(`${vin}.stream`, data.data, {
|
|
909
943
|
forceIndex: true,
|
|
910
944
|
descriptions: this.description,
|
|
945
|
+
states: this.states,
|
|
911
946
|
channelName: 'MQTT Stream Data',
|
|
947
|
+
autoCast: true,
|
|
948
|
+
useCompletePathForDescriptionsAndStates: true,
|
|
912
949
|
});
|
|
913
950
|
|
|
914
951
|
await this.setState(`${vin}.lastStreamUpdate`, new Date().toISOString(), true);
|
|
@@ -930,7 +967,7 @@ class Bmw extends utils.Adapter {
|
|
|
930
967
|
Accept: 'application/json',
|
|
931
968
|
};
|
|
932
969
|
|
|
933
|
-
this.log.info(
|
|
970
|
+
this.log.info(`Cleaning up existing ioBroker containers...`);
|
|
934
971
|
|
|
935
972
|
// Get all existing containers
|
|
936
973
|
const response = await this.makeCarDataApiRequest(
|
|
@@ -990,34 +1027,49 @@ class Bmw extends utils.Adapter {
|
|
|
990
1027
|
try {
|
|
991
1028
|
// Container exists, now test with real telematic data fetching if we have vehicles
|
|
992
1029
|
if (this.vinArray && this.vinArray.length > 0) {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1030
|
+
this.log.debug(`Testing container ${this.containerId} with real telematic data fetch`);
|
|
1031
|
+
|
|
1032
|
+
// Try to validate container by fetching data for any available vehicle
|
|
1033
|
+
let containerValid = false;
|
|
1034
|
+
for (const vin of this.vinArray) {
|
|
1035
|
+
try {
|
|
1036
|
+
const telematicData = await this.getTelematicContainer(vin, this.containerId);
|
|
1037
|
+
if (telematicData && telematicData.telematicData) {
|
|
1038
|
+
// Store validation data directly in stream folder to avoid duplicate API call
|
|
1039
|
+
await this.json2iob.parse(`${vin}.stream`, telematicData.telematicData, {
|
|
1040
|
+
descriptions: this.description,
|
|
1041
|
+
states: this.states,
|
|
1042
|
+
autoCast: true,
|
|
1043
|
+
forceIndex: true,
|
|
1044
|
+
useCompletePathForDescriptionsAndStates: true,
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
// Update lastAPIUpdate timestamp
|
|
1048
|
+
await this.setState(`${vin}.lastStreamViaAPIUpdate`, new Date().toISOString(), true);
|
|
1049
|
+
|
|
1050
|
+
this.log.info(
|
|
1051
|
+
`Existing container is valid and working - retrieved ${Object.keys(telematicData.telematicData).length} telematic data points`,
|
|
1052
|
+
);
|
|
1053
|
+
containerValid = true;
|
|
1054
|
+
break; // Container is valid, no need to test other vehicles
|
|
1055
|
+
}
|
|
1056
|
+
} catch (vinError) {
|
|
1057
|
+
this.log.debug(`Container validation failed for ${vin}: ${vinError.message}`);
|
|
1058
|
+
// Continue testing with other vehicles
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (containerValid) {
|
|
1010
1063
|
return true;
|
|
1011
|
-
} else {
|
|
1012
|
-
this.log.warn('Container exists but failed to retrieve telematic data, will recreate');
|
|
1013
1064
|
}
|
|
1065
|
+
this.log.warn(`Container exists but failed to retrieve telematic data for any vehicle, will recreate`);
|
|
1014
1066
|
} else {
|
|
1015
|
-
this.log.info(
|
|
1067
|
+
this.log.info(`Existing container exists, reusing it (no vehicles available for telematic test)`);
|
|
1016
1068
|
return true;
|
|
1017
1069
|
}
|
|
1018
1070
|
} catch (validationError) {
|
|
1019
1071
|
this.log.warn(`Existing container ID ${this.containerId} validation failed: ${validationError.message}`);
|
|
1020
|
-
this.log.info(
|
|
1072
|
+
this.log.info(`Will create a new container`);
|
|
1021
1073
|
}
|
|
1022
1074
|
}
|
|
1023
1075
|
|
|
@@ -1034,7 +1086,7 @@ class Bmw extends utils.Adapter {
|
|
|
1034
1086
|
const telematicPath = path.join(__dirname, 'telematic.json');
|
|
1035
1087
|
|
|
1036
1088
|
if (!fs.existsSync(telematicPath)) {
|
|
1037
|
-
this.log.error(
|
|
1089
|
+
this.log.error(`telematic.json file not found`);
|
|
1038
1090
|
return false;
|
|
1039
1091
|
}
|
|
1040
1092
|
|
|
@@ -1087,9 +1139,33 @@ class Bmw extends utils.Adapter {
|
|
|
1087
1139
|
});
|
|
1088
1140
|
|
|
1089
1141
|
await this.setState('containerInfo.containerId', this.containerId, true);
|
|
1142
|
+
|
|
1143
|
+
// Fetch initial telematic data for all vehicles with new container
|
|
1090
1144
|
for (const vin of this.vinArray) {
|
|
1091
1145
|
this.log.info(`Fetching initial telematic data for ${vin} using new container`);
|
|
1092
|
-
|
|
1146
|
+
try {
|
|
1147
|
+
const telematicData = await this.getTelematicContainer(vin, this.containerId);
|
|
1148
|
+
if (telematicData && telematicData.telematicData) {
|
|
1149
|
+
// Store telematic data directly in stream folder
|
|
1150
|
+
await this.json2iob.parse(`${vin}.stream`, telematicData.telematicData, {
|
|
1151
|
+
descriptions: this.description,
|
|
1152
|
+
states: this.states,
|
|
1153
|
+
autoCast: true,
|
|
1154
|
+
|
|
1155
|
+
useCompletePathForDescriptionsAndStates: true,
|
|
1156
|
+
forceIndex: true,
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
// Update lastAPIUpdate timestamp
|
|
1160
|
+
await this.setState(`${vin}.lastStreamViaAPIUpdate`, new Date().toISOString(), true);
|
|
1161
|
+
|
|
1162
|
+
this.log.info(`✓ Initial telematic data fetched for ${vin}: ${Object.keys(telematicData.telematicData).length} data points`);
|
|
1163
|
+
} else {
|
|
1164
|
+
this.log.warn(`No initial telematic data retrieved for ${vin}`);
|
|
1165
|
+
}
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
this.log.error(`Failed to fetch initial telematic data for ${vin}: ${error.message}`);
|
|
1168
|
+
}
|
|
1093
1169
|
}
|
|
1094
1170
|
return true;
|
|
1095
1171
|
} catch (error) {
|
|
@@ -1136,7 +1212,7 @@ class Bmw extends utils.Adapter {
|
|
|
1136
1212
|
const filteredTelematicData = {};
|
|
1137
1213
|
|
|
1138
1214
|
for (const [key, data] of Object.entries(response.data.telematicData)) {
|
|
1139
|
-
if (data.timestamp !== null) {
|
|
1215
|
+
if (data.timestamp !== null || data.value !== null) {
|
|
1140
1216
|
filteredTelematicData[key] = data;
|
|
1141
1217
|
}
|
|
1142
1218
|
}
|
|
@@ -1238,7 +1314,7 @@ class Bmw extends utils.Adapter {
|
|
|
1238
1314
|
* Setup telematic container by reusing existing one or creating a new one
|
|
1239
1315
|
*/
|
|
1240
1316
|
async setupTelematicContainer() {
|
|
1241
|
-
this.log.info(
|
|
1317
|
+
this.log.info(`Setting up telematic container...`);
|
|
1242
1318
|
|
|
1243
1319
|
// Try to create/reuse container (cleanup only happens if existing container is invalid)
|
|
1244
1320
|
const createSuccess = await this.createTelematicContainer();
|
|
@@ -1248,7 +1324,7 @@ class Bmw extends utils.Adapter {
|
|
|
1248
1324
|
// Container validation and data storage already handled in createTelematicContainer()
|
|
1249
1325
|
// No duplicate API call needed here - saving quota
|
|
1250
1326
|
} else {
|
|
1251
|
-
this.log.error(
|
|
1327
|
+
this.log.error(`Failed to setup telematic container`);
|
|
1252
1328
|
}
|
|
1253
1329
|
|
|
1254
1330
|
return createSuccess;
|
|
@@ -1310,8 +1386,8 @@ class Bmw extends utils.Adapter {
|
|
|
1310
1386
|
}
|
|
1311
1387
|
|
|
1312
1388
|
// For other states: BMW CarData API is read-only, no remote controls available
|
|
1313
|
-
this.log.warn(
|
|
1314
|
-
this.log.info(
|
|
1389
|
+
this.log.warn(`Remote controls not available in BMW CarData (read-only API)`);
|
|
1390
|
+
this.log.info(`BMW CarData only provides vehicle data, no command functionality`);
|
|
1315
1391
|
|
|
1316
1392
|
// Reset the state to acknowledge it
|
|
1317
1393
|
this.setState(id, state.val, true);
|
|
@@ -1320,6 +1396,7 @@ class Bmw extends utils.Adapter {
|
|
|
1320
1396
|
|
|
1321
1397
|
/**
|
|
1322
1398
|
* Handle remote API calls generically based on button name
|
|
1399
|
+
*
|
|
1323
1400
|
* @param {string} vin - Vehicle VIN
|
|
1324
1401
|
* @param {string} buttonName - Name of the button pressed
|
|
1325
1402
|
*/
|
|
@@ -1331,7 +1408,7 @@ class Bmw extends utils.Adapter {
|
|
|
1331
1408
|
};
|
|
1332
1409
|
|
|
1333
1410
|
switch (buttonName) {
|
|
1334
|
-
case 'fetchViaAPI':
|
|
1411
|
+
case 'fetchViaAPI': {
|
|
1335
1412
|
// Handle telematic data fetching
|
|
1336
1413
|
if (!this.containerId) {
|
|
1337
1414
|
this.log.warn('No container ID available, setting up telematic container first...');
|
|
@@ -1347,6 +1424,9 @@ class Bmw extends utils.Adapter {
|
|
|
1347
1424
|
await this.json2iob.parse(`${vin}.stream`, telematicData.telematicData, {
|
|
1348
1425
|
descriptions: this.description,
|
|
1349
1426
|
forceIndex: true,
|
|
1427
|
+
states: this.states,
|
|
1428
|
+
autoCast: true,
|
|
1429
|
+
useCompletePathForDescriptionsAndStates: true,
|
|
1350
1430
|
});
|
|
1351
1431
|
|
|
1352
1432
|
// Update lastAPIUpdate timestamp
|
|
@@ -1357,8 +1437,9 @@ class Bmw extends utils.Adapter {
|
|
|
1357
1437
|
this.log.warn(`No telematic data retrieved for vehicle ${vin}`);
|
|
1358
1438
|
}
|
|
1359
1439
|
break;
|
|
1440
|
+
}
|
|
1360
1441
|
|
|
1361
|
-
case 'basicData':
|
|
1442
|
+
case 'basicData': {
|
|
1362
1443
|
// Handle basicData endpoint
|
|
1363
1444
|
const basicResponse = await this.makeCarDataApiRequest(
|
|
1364
1445
|
{
|
|
@@ -1395,8 +1476,9 @@ class Bmw extends utils.Adapter {
|
|
|
1395
1476
|
|
|
1396
1477
|
this.log.info(`Successfully fetched basic data for ${vin}`);
|
|
1397
1478
|
break;
|
|
1479
|
+
}
|
|
1398
1480
|
|
|
1399
|
-
case 'chargingHistory':
|
|
1481
|
+
case 'chargingHistory': {
|
|
1400
1482
|
// Handle charging history endpoint with pagination
|
|
1401
1483
|
const now = new Date();
|
|
1402
1484
|
const fromDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days ago
|
|
@@ -1423,8 +1505,9 @@ class Bmw extends utils.Adapter {
|
|
|
1423
1505
|
throw new Error('Failed to fetch charging history');
|
|
1424
1506
|
}
|
|
1425
1507
|
break;
|
|
1508
|
+
}
|
|
1426
1509
|
|
|
1427
|
-
case 'image':
|
|
1510
|
+
case 'image': {
|
|
1428
1511
|
// Handle vehicle image endpoint
|
|
1429
1512
|
//special request to receive raw image data
|
|
1430
1513
|
const imageResponse = await this.requestClient({
|
|
@@ -1456,8 +1539,9 @@ class Bmw extends utils.Adapter {
|
|
|
1456
1539
|
|
|
1457
1540
|
this.log.info(`Successfully fetched vehicle image for ${vin}`);
|
|
1458
1541
|
break;
|
|
1542
|
+
}
|
|
1459
1543
|
|
|
1460
|
-
case 'locationBasedChargingSettings':
|
|
1544
|
+
case 'locationBasedChargingSettings': {
|
|
1461
1545
|
// Handle location-based charging settings endpoint
|
|
1462
1546
|
const locationResponse = await this.makeCarDataApiRequest(
|
|
1463
1547
|
{
|
|
@@ -1476,8 +1560,9 @@ class Bmw extends utils.Adapter {
|
|
|
1476
1560
|
|
|
1477
1561
|
this.log.info(`Successfully fetched location-based charging settings for ${vin}`);
|
|
1478
1562
|
break;
|
|
1563
|
+
}
|
|
1479
1564
|
|
|
1480
|
-
case 'smartMaintenanceTyreDiagnosis':
|
|
1565
|
+
case 'smartMaintenanceTyreDiagnosis': {
|
|
1481
1566
|
// Handle smart maintenance tyre diagnosis endpoint
|
|
1482
1567
|
const tyreResponse = await this.makeCarDataApiRequest(
|
|
1483
1568
|
{
|
|
@@ -1496,6 +1581,7 @@ class Bmw extends utils.Adapter {
|
|
|
1496
1581
|
|
|
1497
1582
|
this.log.info(`Successfully fetched smart maintenance tyre diagnosis for ${vin}`);
|
|
1498
1583
|
break;
|
|
1584
|
+
}
|
|
1499
1585
|
|
|
1500
1586
|
default:
|
|
1501
1587
|
this.log.warn(`Unknown remote button: ${buttonName}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.bmw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "Adapter for BMW",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TA2k",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@iobroker/adapter-core": "^3.3.2",
|
|
25
25
|
"axios": "^1.12.2",
|
|
26
26
|
"axios-retry": "^4.5.0",
|
|
27
|
-
"json2iob": "^2.6.
|
|
27
|
+
"json2iob": "^2.6.20",
|
|
28
28
|
"mqtt": "^5.14.1",
|
|
29
29
|
"qs": "^6.14.0"
|
|
30
30
|
},
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@iobroker/eslint-config": "^2.2.0",
|
|
37
37
|
"@iobroker/testing": "^5.1.1",
|
|
38
38
|
"@tsconfig/node20": "^20.1.6",
|
|
39
|
-
"@types/node": "^24.
|
|
39
|
+
"@types/node": "^24.7.0",
|
|
40
40
|
"@types/qs": "^6.14.0",
|
|
41
41
|
"globals": "^16.4.0",
|
|
42
42
|
"typescript": "~5.9.3"
|