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 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. **Configure API Endpoints** - Select which data to fetch:
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.lastUpdate` - Timestamp of last data update (API or MQTT)
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
@@ -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
- "sm": 6
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
- "sm": 6
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
- "sm": 3
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
- "sm": 3
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
- "sm": 6
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.2.0",
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
@@ -1,4 +1,3 @@
1
- // @ts-expect-error: TypeScript may not recognize the default export of axios
2
1
  const axios = require('axios').default;
3
2
 
4
3
  /**
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.error(`Error stack: ${error.stack}`);
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
- 'Token refresh failed, will retry on next refresh cycle. You can also delete bmw.0.cardataauth.session state to force re-login.',
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('MQTT authentication failed - refreshing token for next auto-reconnect');
894
+ this.log.warn(`MQTT authentication failed - refreshing token for next auto-reconnect`);
861
895
  try {
862
896
  await this.refreshToken();
863
- this.log.info('Token refreshed successfully - MQTT client will auto-reconnect with new credentials');
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('MQTT will retry connection with current token via built-in reconnection');
900
+ this.log.warn(`MQTT will retry connection with current token via built-in reconnection`);
867
901
  }
868
902
  } else {
869
- this.log.debug('Non-authentication MQTT error - letting built-in client handle reconnection');
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('Cleaning up existing ioBroker containers...');
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
- const testVin = this.vinArray[0];
994
- this.log.debug(`Testing container ${this.containerId} with real telematic data fetch for ${testVin}`);
995
-
996
- const telematicData = await this.getTelematicContainer(testVin, this.containerId);
997
- if (telematicData && telematicData.telematicData) {
998
- // Store validation data directly in stream folder to avoid duplicate API call
999
- await this.json2iob.parse(`${testVin}.stream`, telematicData.telematicData, {
1000
- descriptions: this.description,
1001
- forceIndex: true,
1002
- });
1003
-
1004
- // Update lastAPIUpdate timestamp
1005
- await this.setState(`${testVin}.lastStreamViaAPIUpdate`, new Date().toISOString(), true);
1006
-
1007
- this.log.info(
1008
- `Existing container is valid and working - retrieved ${Object.keys(telematicData.telematicData).length} telematic data points`,
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('Existing container exists, reusing it (no vehicles available for telematic test)');
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('Will create a new container');
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('telematic.json file not found');
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
- this.getTelematicContainer(vin, this.containerId);
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('Setting up telematic container...');
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('Failed to setup telematic container');
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('Remote controls not available in BMW CarData (read-only API)');
1314
- this.log.info('BMW CarData only provides vehicle data, no command functionality');
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.2.0",
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.17",
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.6.1",
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"