iobroker.bmw 4.1.0 → 4.1.1
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 +2 -1
- package/io-package.json +4 -8
- package/main.js +110 -94
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -213,7 +213,8 @@ If you're not seeing expected data in `VIN.api.*`:
|
|
|
213
213
|
This adapter is available at: [https://github.com/TA2k/ioBroker.bmw](https://github.com/TA2k/ioBroker.bmw)
|
|
214
214
|
|
|
215
215
|
## Changelog
|
|
216
|
-
|
|
216
|
+
|
|
217
|
+
### 4.1.1 (2025-10-03)
|
|
217
218
|
|
|
218
219
|
- Add API fetching via Container and move other apis to manually fetching
|
|
219
220
|
|
package/io-package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "bmw",
|
|
4
|
-
"version": "4.1.
|
|
4
|
+
"version": "4.1.1",
|
|
5
5
|
"news": {
|
|
6
|
-
"4.1.
|
|
6
|
+
"4.1.1": {
|
|
7
7
|
"en": "Add API fetching via Container and move other apis to manually fetching",
|
|
8
8
|
"de": "Fügen Sie API-Fetching über Container hinzu und verschieben Sie andere Apis, um manuelles Abholen",
|
|
9
9
|
"ru": "Добавьте извлечение API через контейнер и переместите другие apis для ручного извлечения",
|
|
@@ -121,12 +121,8 @@
|
|
|
121
121
|
"uk": "Adapter for BMW CarData API з потоком MQTT в реальному часі",
|
|
122
122
|
"zh-cn": "带有实时MQTT流的BMW CarData API适配器"
|
|
123
123
|
},
|
|
124
|
-
"authors": [
|
|
125
|
-
|
|
126
|
-
],
|
|
127
|
-
"keywords": [
|
|
128
|
-
"BMW"
|
|
129
|
-
],
|
|
124
|
+
"authors": ["TA2k <tombox2020@gmail.com>"],
|
|
125
|
+
"keywords": ["BMW"],
|
|
130
126
|
"licenseInformation": {
|
|
131
127
|
"license": "MIT",
|
|
132
128
|
"type": "free"
|
package/main.js
CHANGED
|
@@ -129,62 +129,72 @@ class Bmw extends utils.Adapter {
|
|
|
129
129
|
// Connect MQTT after successful auth
|
|
130
130
|
await this.connectMQTT();
|
|
131
131
|
// Start periodic token refresh (every 45 minutes)
|
|
132
|
-
this.refreshTokenInterval = setInterval(
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
this.refreshTokenInterval = setInterval(
|
|
133
|
+
async () => {
|
|
134
|
+
await this.refreshToken();
|
|
135
|
+
},
|
|
136
|
+
56 * 60 * 1000,
|
|
137
|
+
);
|
|
135
138
|
|
|
136
139
|
// Start periodic telematic data updates (respecting quota limits)
|
|
137
140
|
if (this.vinArray.length > 0 && this.config.interval > 0) {
|
|
138
|
-
this.log.info(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (!
|
|
148
|
-
this.log.
|
|
149
|
-
|
|
141
|
+
this.log.info(
|
|
142
|
+
`Setting up periodic telematic data updates every ${this.config.interval} minutes for ${this.vinArray.length} vehicle(s)`,
|
|
143
|
+
);
|
|
144
|
+
this.updateInterval = setInterval(
|
|
145
|
+
async () => {
|
|
146
|
+
// Update quota states (expired calls removed automatically)
|
|
147
|
+
this.updateQuotaStates();
|
|
148
|
+
|
|
149
|
+
// Periodic telematic data refresh - MQTT provides real-time updates
|
|
150
|
+
if (!this.containerId) {
|
|
151
|
+
this.log.warn('No container ID available for periodic telematic data fetch, setting up container...');
|
|
152
|
+
const setupSuccess = await this.setupTelematicContainer();
|
|
153
|
+
if (!setupSuccess) {
|
|
154
|
+
this.log.error('Failed to setup telematic container for periodic updates');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
150
157
|
}
|
|
151
|
-
}
|
|
152
158
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
159
|
+
for (const vin of this.vinArray) {
|
|
160
|
+
this.log.debug(`Periodic telematic data refresh for ${vin}`);
|
|
161
|
+
try {
|
|
162
|
+
const telematicData = await this.getTelematicContainer(vin, this.containerId);
|
|
163
|
+
if (telematicData && telematicData.telematicData) {
|
|
164
|
+
// Store telematic data directly in stream folder
|
|
165
|
+
await this.json2iob.parse(`${vin}.stream`, telematicData.telematicData, {
|
|
166
|
+
descriptions: this.description,
|
|
167
|
+
forceIndex: true,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Update lastAPIUpdate timestamp
|
|
171
|
+
await this.extendObject(`${vin}.stream.lastAPIUpdate`, {
|
|
172
|
+
type: 'state',
|
|
173
|
+
common: {
|
|
174
|
+
name: 'Last Telematic API Update',
|
|
175
|
+
type: 'string',
|
|
176
|
+
role: 'date',
|
|
177
|
+
read: true,
|
|
178
|
+
write: false,
|
|
179
|
+
},
|
|
180
|
+
native: {},
|
|
181
|
+
});
|
|
182
|
+
await this.setState(`${vin}.stream.lastAPIUpdate`, new Date().toISOString(), true);
|
|
183
|
+
|
|
184
|
+
this.log.debug(
|
|
185
|
+
`✓ Periodic telematic data update for ${vin}: ${Object.keys(telematicData.telematicData).length} data points`,
|
|
186
|
+
);
|
|
187
|
+
} else {
|
|
188
|
+
this.log.warn(`No telematic data retrieved for ${vin} during periodic update`);
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
this.log.error(`Periodic telematic data fetch failed for ${vin}: ${error.message}`);
|
|
181
192
|
}
|
|
182
|
-
|
|
183
|
-
this.log.error(`Periodic telematic data fetch failed for ${vin}: ${error.message}`);
|
|
193
|
+
break; // Only one vehicle per interval to conserve quota
|
|
184
194
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
195
|
+
},
|
|
196
|
+
this.config.interval * 60 * 1000,
|
|
197
|
+
);
|
|
188
198
|
} else if (this.config.interval === 0) {
|
|
189
199
|
this.log.info('Periodic telematic data updates disabled (interval = 0)');
|
|
190
200
|
}
|
|
@@ -194,7 +204,7 @@ class Bmw extends utils.Adapter {
|
|
|
194
204
|
this.log.info(
|
|
195
205
|
`API quota: ${
|
|
196
206
|
API_QUOTA_LIMIT - this.apiCalls.length
|
|
197
|
-
}/${API_QUOTA_LIMIT} calls remaining for
|
|
207
|
+
}/${API_QUOTA_LIMIT} calls remaining for API calls. Updates via MQTT do not count against quota.`,
|
|
198
208
|
);
|
|
199
209
|
} else {
|
|
200
210
|
this.log.error('BMW CarData authentication failed');
|
|
@@ -235,11 +245,11 @@ class Bmw extends utils.Adapter {
|
|
|
235
245
|
},
|
|
236
246
|
data: requestData,
|
|
237
247
|
})
|
|
238
|
-
.then(
|
|
248
|
+
.then(res => {
|
|
239
249
|
this.log.debug(`Device code response: ${JSON.stringify(res.data)}`);
|
|
240
250
|
return res;
|
|
241
251
|
})
|
|
242
|
-
.catch(
|
|
252
|
+
.catch(error => {
|
|
243
253
|
this.log.error(`Device code request failed: ${error.message}`);
|
|
244
254
|
this.log.error(`Error stack: ${error.stack}`);
|
|
245
255
|
if (error.response) {
|
|
@@ -260,7 +270,9 @@ class Bmw extends utils.Adapter {
|
|
|
260
270
|
this.log.error('To fix this issue:');
|
|
261
271
|
this.log.error('1. Visit BMW ConnectedDrive portal: https://www.bmw.de/de-de/mybmw/vehicle-overview');
|
|
262
272
|
this.log.error('2. Go to CarData section');
|
|
263
|
-
this.log.error(
|
|
273
|
+
this.log.error(
|
|
274
|
+
'3. Check if CarData API and CarData Streaming are both activated. Sometimes it needs 30s to save the selection',
|
|
275
|
+
);
|
|
264
276
|
this.log.error('4. If not activated, enable both services');
|
|
265
277
|
this.log.error('5. If already activated, delete and recreate your Client ID');
|
|
266
278
|
this.log.error('6. Update the adapter configuration with the new Client ID');
|
|
@@ -273,7 +285,7 @@ class Bmw extends utils.Adapter {
|
|
|
273
285
|
method: error.request.method,
|
|
274
286
|
url: error.request.url,
|
|
275
287
|
headers: error.request._headers,
|
|
276
|
-
})}
|
|
288
|
+
})}`,
|
|
277
289
|
);
|
|
278
290
|
}
|
|
279
291
|
return false; // Return false instead of throwing
|
|
@@ -392,7 +404,7 @@ class Bmw extends utils.Adapter {
|
|
|
392
404
|
method: error.request.method,
|
|
393
405
|
url: error.request.url,
|
|
394
406
|
headers: error.request._headers,
|
|
395
|
-
})}
|
|
407
|
+
})}`,
|
|
396
408
|
);
|
|
397
409
|
}
|
|
398
410
|
return false;
|
|
@@ -413,9 +425,9 @@ class Bmw extends utils.Adapter {
|
|
|
413
425
|
url: `${this.carDataApiBase}/customers/vehicles/mappings`,
|
|
414
426
|
headers: headers,
|
|
415
427
|
},
|
|
416
|
-
'fetch vehicle mappings'
|
|
428
|
+
'fetch vehicle mappings',
|
|
417
429
|
)
|
|
418
|
-
.then(async
|
|
430
|
+
.then(async res => {
|
|
419
431
|
this.log.debug(JSON.stringify(res.data));
|
|
420
432
|
const mappings = res.data;
|
|
421
433
|
|
|
@@ -484,7 +496,7 @@ class Bmw extends utils.Adapter {
|
|
|
484
496
|
}
|
|
485
497
|
}
|
|
486
498
|
})
|
|
487
|
-
.catch(
|
|
499
|
+
.catch(error => {
|
|
488
500
|
this.log.error(`BMW CarData vehicle discovery failed: ${error.message}`);
|
|
489
501
|
if (error.response) {
|
|
490
502
|
this.log.error(`Response: ${JSON.stringify(error.response.data)}`);
|
|
@@ -606,7 +618,7 @@ class Bmw extends utils.Adapter {
|
|
|
606
618
|
|
|
607
619
|
// Remove calls older than 24h
|
|
608
620
|
const originalLength = this.apiCalls.length;
|
|
609
|
-
this.apiCalls = this.apiCalls.filter(
|
|
621
|
+
this.apiCalls = this.apiCalls.filter(time => now - time < 24 * 60 * 60 * 1000);
|
|
610
622
|
|
|
611
623
|
// Save history if calls were removed due to expiration
|
|
612
624
|
if (this.apiCalls.length !== originalLength) {
|
|
@@ -637,7 +649,7 @@ class Bmw extends utils.Adapter {
|
|
|
637
649
|
return true;
|
|
638
650
|
}
|
|
639
651
|
|
|
640
|
-
this.log.warn(`API quota for
|
|
652
|
+
this.log.warn(`API quota for api data exhausted: ${used}/${API_QUOTA_LIMIT} calls used in last 24h. Stream data still working.`);
|
|
641
653
|
return false;
|
|
642
654
|
}
|
|
643
655
|
|
|
@@ -676,7 +688,7 @@ class Bmw extends utils.Adapter {
|
|
|
676
688
|
}
|
|
677
689
|
|
|
678
690
|
sleep(ms) {
|
|
679
|
-
return new Promise(
|
|
691
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
680
692
|
}
|
|
681
693
|
|
|
682
694
|
async cleanObjects(vin) {
|
|
@@ -750,23 +762,36 @@ class Bmw extends utils.Adapter {
|
|
|
750
762
|
},
|
|
751
763
|
data: qs.stringify(refreshData),
|
|
752
764
|
})
|
|
753
|
-
.then(async
|
|
765
|
+
.then(async res => {
|
|
754
766
|
// Store refreshed tokens (keep existing session structure)
|
|
755
767
|
this.session = res.data;
|
|
756
768
|
this.setState('cardataauth.session', JSON.stringify(this.session), true);
|
|
757
769
|
this.setState('info.connection', true, true);
|
|
758
|
-
this.log.debug('Tokens refreshed successfully - MQTT will auto-reconnect with new credentials
|
|
759
|
-
|
|
770
|
+
this.log.debug('Tokens refreshed successfully - MQTT will auto-reconnect with new credentials');
|
|
771
|
+
this.mqtt?.options && (this.mqtt.options.password = this.session.id_token);
|
|
760
772
|
return res.data;
|
|
761
773
|
})
|
|
762
|
-
.catch(async
|
|
774
|
+
.catch(async error => {
|
|
763
775
|
// Log complete error object first
|
|
764
|
-
this.log.error(
|
|
765
|
-
|
|
766
|
-
|
|
776
|
+
this.log.error(error);
|
|
777
|
+
|
|
778
|
+
// HTTP response errors - check status code
|
|
779
|
+
if (error.response) {
|
|
780
|
+
this.log.error(`Response status: ${JSON.stringify(error.response)}`);
|
|
781
|
+
const status = error.response.status;
|
|
782
|
+
if (status >= 400 && status < 500) {
|
|
783
|
+
// 4xx errors indicate authentication problems - reset needed
|
|
784
|
+
this.log.error(`Token refresh failed with HTTP ${status} auth error - starting new device flow`);
|
|
785
|
+
this.setState('info.connection', false, true);
|
|
786
|
+
return await this.login();
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
this.log.warn(
|
|
791
|
+
'Token refresh failed, will retry on next refresh cycle. You can also delete bmw.0.cardataauth.session state to force re-login.',
|
|
792
|
+
);
|
|
767
793
|
this.setState('info.connection', false, true);
|
|
768
|
-
|
|
769
|
-
return await this.login();
|
|
794
|
+
return;
|
|
770
795
|
});
|
|
771
796
|
}
|
|
772
797
|
|
|
@@ -784,14 +809,6 @@ class Bmw extends utils.Adapter {
|
|
|
784
809
|
|
|
785
810
|
const mqtt = require('mqtt');
|
|
786
811
|
|
|
787
|
-
// Transform function to refresh credentials on each reconnection attempt
|
|
788
|
-
const transformWsUrl = (url, options, client) => {
|
|
789
|
-
// Update password with the latest id_token on each reconnect
|
|
790
|
-
client.options.password = this.session.id_token;
|
|
791
|
-
this.log.debug('MQTT transformWsUrl: Updated credentials with latest token');
|
|
792
|
-
return url; // URL stays the same, just update credentials
|
|
793
|
-
};
|
|
794
|
-
|
|
795
812
|
const options = {
|
|
796
813
|
host: 'customer.streaming-cardata.bmwgroup.com',
|
|
797
814
|
port: 9000,
|
|
@@ -803,7 +820,6 @@ class Bmw extends utils.Adapter {
|
|
|
803
820
|
rejectUnauthorized: true,
|
|
804
821
|
reconnectPeriod: 30000, // Built-in reconnection every 30 seconds
|
|
805
822
|
connectTimeout: 30000,
|
|
806
|
-
transformWsUrl: transformWsUrl, // Hook to refresh credentials on reconnect
|
|
807
823
|
};
|
|
808
824
|
|
|
809
825
|
this.log.debug(`Connecting to BMW MQTT: ${options.host}:${options.port}`);
|
|
@@ -816,7 +832,7 @@ class Bmw extends utils.Adapter {
|
|
|
816
832
|
|
|
817
833
|
// Subscribe to all vehicle topics for this CarData Streaming username
|
|
818
834
|
const topic = `${this.config.cardataStreamingUsername}/+`;
|
|
819
|
-
this.mqtt.subscribe(topic,
|
|
835
|
+
this.mqtt.subscribe(topic, err => {
|
|
820
836
|
if (err) {
|
|
821
837
|
this.log.error(`MQTT subscription failed: ${err.message}`);
|
|
822
838
|
} else {
|
|
@@ -829,7 +845,7 @@ class Bmw extends utils.Adapter {
|
|
|
829
845
|
this.handleMQTTMessage(topic, message);
|
|
830
846
|
});
|
|
831
847
|
|
|
832
|
-
this.mqtt.on('error', async
|
|
848
|
+
this.mqtt.on('error', async error => {
|
|
833
849
|
this.log.error(`MQTT error: ${error}`);
|
|
834
850
|
this.setState('info.mqttConnected', false, true);
|
|
835
851
|
|
|
@@ -961,7 +977,7 @@ class Bmw extends utils.Adapter {
|
|
|
961
977
|
url: `${this.carDataApiBase}/customers/containers`,
|
|
962
978
|
headers: headers,
|
|
963
979
|
},
|
|
964
|
-
'list containers'
|
|
980
|
+
'list containers',
|
|
965
981
|
);
|
|
966
982
|
|
|
967
983
|
const containers = response.data.containers || [];
|
|
@@ -978,7 +994,7 @@ class Bmw extends utils.Adapter {
|
|
|
978
994
|
url: `${this.carDataApiBase}/customers/containers/${container.id}`,
|
|
979
995
|
headers: headers,
|
|
980
996
|
},
|
|
981
|
-
`delete container ${container.id}
|
|
997
|
+
`delete container ${container.id}`,
|
|
982
998
|
);
|
|
983
999
|
this.log.debug(`Deleted ioBroker container: ${container.id} (${container.name})`);
|
|
984
1000
|
} catch (error) {
|
|
@@ -1038,7 +1054,7 @@ class Bmw extends utils.Adapter {
|
|
|
1038
1054
|
await this.setState(`${testVin}.stream.lastAPIUpdate`, new Date().toISOString(), true);
|
|
1039
1055
|
|
|
1040
1056
|
this.log.info(
|
|
1041
|
-
`Existing container is valid and working - retrieved ${Object.keys(telematicData.telematicData).length} telematic data points
|
|
1057
|
+
`Existing container is valid and working - retrieved ${Object.keys(telematicData.telematicData).length} telematic data points`,
|
|
1042
1058
|
);
|
|
1043
1059
|
return true;
|
|
1044
1060
|
} else {
|
|
@@ -1074,7 +1090,7 @@ class Bmw extends utils.Adapter {
|
|
|
1074
1090
|
const telematicData = JSON.parse(fs.readFileSync(telematicPath, 'utf8'));
|
|
1075
1091
|
|
|
1076
1092
|
// Extract all technical identifiers and ensure no trailing commas in JSON
|
|
1077
|
-
const technicalDescriptors = telematicData.map(
|
|
1093
|
+
const technicalDescriptors = telematicData.map(item => item.technical_identifier).filter(identifier => identifier); // Remove any undefined/null values
|
|
1078
1094
|
|
|
1079
1095
|
this.log.info(`Creating container with ${technicalDescriptors.length} technical identifiers from telematic.json`);
|
|
1080
1096
|
|
|
@@ -1091,7 +1107,7 @@ class Bmw extends utils.Adapter {
|
|
|
1091
1107
|
headers: headers,
|
|
1092
1108
|
data: containerData,
|
|
1093
1109
|
},
|
|
1094
|
-
'create telematic container'
|
|
1110
|
+
'create telematic container',
|
|
1095
1111
|
);
|
|
1096
1112
|
|
|
1097
1113
|
this.containerId = response.data.containerId;
|
|
@@ -1157,7 +1173,7 @@ class Bmw extends utils.Adapter {
|
|
|
1157
1173
|
containerId: containerId,
|
|
1158
1174
|
},
|
|
1159
1175
|
},
|
|
1160
|
-
`get telematic data for ${vin}
|
|
1176
|
+
`get telematic data for ${vin}`,
|
|
1161
1177
|
);
|
|
1162
1178
|
|
|
1163
1179
|
// Filter out telematic data entries with null timestamps (not relevant for the car)
|
|
@@ -1177,7 +1193,7 @@ class Bmw extends utils.Adapter {
|
|
|
1177
1193
|
this.log.info(
|
|
1178
1194
|
`Telematic data retrieved for ${vin}: ${filteredCount} relevant data points (${
|
|
1179
1195
|
originalCount - filteredCount
|
|
1180
|
-
} null timestamp entries filtered out)
|
|
1196
|
+
} null timestamp entries filtered out)`,
|
|
1181
1197
|
);
|
|
1182
1198
|
} else {
|
|
1183
1199
|
this.log.info(`Telematic data retrieved successfully for ${vin} (no telematicData in response)`);
|
|
@@ -1234,7 +1250,7 @@ class Bmw extends utils.Adapter {
|
|
|
1234
1250
|
headers: headers,
|
|
1235
1251
|
params: params,
|
|
1236
1252
|
},
|
|
1237
|
-
`fetch charging history for ${vin}${nextToken ? ' (paginated)' : ''}
|
|
1253
|
+
`fetch charging history for ${vin}${nextToken ? ' (paginated)' : ''}`,
|
|
1238
1254
|
);
|
|
1239
1255
|
|
|
1240
1256
|
const chargingData = response.data;
|
|
@@ -1407,7 +1423,7 @@ class Bmw extends utils.Adapter {
|
|
|
1407
1423
|
url: `${this.carDataApiBase}/customers/vehicles/${vin}/basicData`,
|
|
1408
1424
|
headers: headers,
|
|
1409
1425
|
},
|
|
1410
|
-
`fetch basicData for ${vin}
|
|
1426
|
+
`fetch basicData for ${vin}`,
|
|
1411
1427
|
);
|
|
1412
1428
|
|
|
1413
1429
|
await this.json2iob.parse(`${vin}.api.basicData`, basicResponse.data, {
|
|
@@ -1473,7 +1489,7 @@ class Bmw extends utils.Adapter {
|
|
|
1473
1489
|
url: `${this.carDataApiBase}/customers/vehicles/${vin}/image`,
|
|
1474
1490
|
headers: headers,
|
|
1475
1491
|
},
|
|
1476
|
-
`fetch image for ${vin}
|
|
1492
|
+
`fetch image for ${vin}`,
|
|
1477
1493
|
);
|
|
1478
1494
|
|
|
1479
1495
|
await this.json2iob.parse(`${vin}.api.image`, imageResponse.data, {
|
|
@@ -1493,7 +1509,7 @@ class Bmw extends utils.Adapter {
|
|
|
1493
1509
|
url: `${this.carDataApiBase}/customers/vehicles/${vin}/locationBasedChargingSettings`,
|
|
1494
1510
|
headers: headers,
|
|
1495
1511
|
},
|
|
1496
|
-
`fetch locationBasedChargingSettings for ${vin}
|
|
1512
|
+
`fetch locationBasedChargingSettings for ${vin}`,
|
|
1497
1513
|
);
|
|
1498
1514
|
|
|
1499
1515
|
await this.json2iob.parse(`${vin}.api.locationBasedChargingSettings`, locationResponse.data, {
|
|
@@ -1513,7 +1529,7 @@ class Bmw extends utils.Adapter {
|
|
|
1513
1529
|
url: `${this.carDataApiBase}/customers/vehicles/${vin}/smartMaintenanceTyreDiagnosis`,
|
|
1514
1530
|
headers: headers,
|
|
1515
1531
|
},
|
|
1516
|
-
`fetch smartMaintenanceTyreDiagnosis for ${vin}
|
|
1532
|
+
`fetch smartMaintenanceTyreDiagnosis for ${vin}`,
|
|
1517
1533
|
);
|
|
1518
1534
|
|
|
1519
1535
|
await this.json2iob.parse(`${vin}.api.smartMaintenanceTyreDiagnosis`, tyreResponse.data, {
|
|
@@ -1551,7 +1567,7 @@ if (require.main !== module) {
|
|
|
1551
1567
|
/**
|
|
1552
1568
|
* @param {Partial<utils.AdapterOptions>} [options] - Optional adapter configuration options.
|
|
1553
1569
|
*/
|
|
1554
|
-
module.exports =
|
|
1570
|
+
module.exports = options => new Bmw(options);
|
|
1555
1571
|
} else {
|
|
1556
1572
|
// otherwise start the instance directly
|
|
1557
1573
|
new Bmw();
|