iobroker.bmw 4.0.2 → 4.0.3

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
@@ -44,9 +44,9 @@ This adapter integrates BMW vehicles into ioBroker using the new BMW CarData API
44
44
 
45
45
  ![CarData Client Setup](img/cardata-client-setup.png)
46
46
 
47
- **CRITICAL**: Click one service and wait 20 seconds if you see an error message, then click again
47
+ # **CRITICAL**: Click one service and wait 20 seconds if you see an error message, then click again. Don't press on "Gerät Authentifizieren/Devict authentication" Enter the client_id in iobroker settings.
48
48
 
49
- ### 2. ⚠️ CRITICAL: CarData Streaming Configuration
49
+ ### 2. CarData Streaming Configuration
50
50
 
51
51
  **YOU MUST CONFIGURE CARDATA STREAMING AND SELECT ALL 244 DATA POINTS**
52
52
 
@@ -74,13 +74,10 @@ After creating your Client ID, configure streaming:
74
74
  5. **Configure API Endpoints** - Select which data to fetch:
75
75
  - **Basic Data** ✅ - Essential vehicle information (recommended)
76
76
  - **Charging History** ✅ - Charging sessions and history (recommended)
77
- - **Vehicle State** - Current vehicle status (recommended)
78
- - **Charging Profile** - Charging preferences and profiles
79
- - **Charging Sessions** - Detailed charging session data
80
- - **Climate Now** - Current climate control status
81
- - **Destination Information** - Navigation and destination data
82
- - **Location** - GPS position and location services
83
- - **Statistics** - Driving statistics and analytics
77
+ - **Vehicle Image** - Vehicle image for display purposes
78
+ - **Location Based Charging Settings** - Location-specific charging preferences
79
+ - **Smart Maintenance Tyre Diagnosis** - Tyre condition and diagnosis data
80
+ - **Telematic Data** - Trip information and driving behavior analytics
84
81
  6. Configure **VIN ignore list** if needed
85
82
 
86
83
  **💡 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.
@@ -100,7 +97,6 @@ Vehicle data is organized under `bmw.0.VIN.*` where `VIN` represents your Vehicl
100
97
  ### Main Folder Structure
101
98
 
102
99
  - **`bmw.0.VIN.api.*`** - API Data (Periodic Updates)
103
-
104
100
  - Data fetched via BMW CarData REST API
105
101
  - Uses API quota (50 calls per 24 hours)
106
102
  - Updated based on configured interval
@@ -114,17 +110,14 @@ Vehicle data is organized under `bmw.0.VIN.*` where `VIN` represents your Vehicl
114
110
 
115
111
  ### Available API Endpoints (Configurable)
116
112
 
117
- You can enable/disable these endpoints in adapter settings:
113
+ You can enable/disable these endpoints in adapter settings (BMW CarData API v1):
118
114
 
119
115
  - `bmw.0.VIN.api.basicData.*` - Vehicle information, model, brand, series ✅ **(Default: Enabled)**
120
116
  - `bmw.0.VIN.api.chargingHistory.*` - Charging sessions and history ✅ **(Default: Enabled)**
121
- - `bmw.0.VIN.api.chargingProfile.*` - Charging preferences and profiles
122
- - `bmw.0.VIN.api.chargingSessions.*` - Detailed charging session data
123
- - `bmw.0.VIN.api.climateNow.*` - Climate control status
124
- - `bmw.0.VIN.api.destinationInformation.*` - Navigation and destination data
125
- - `bmw.0.VIN.api.location.*` - GPS position and location services
126
- - `bmw.0.VIN.api.statistics.*` - Driving statistics and analytics
127
- - `bmw.0.VIN.api.vehicleState.*` - Current vehicle status and conditions ✅ **(Default: Enabled)**
117
+ - `bmw.0.VIN.api.image.*` - Vehicle image for display purposes
118
+ - `bmw.0.VIN.api.locationBasedChargingSettings.*` - Location-specific charging preferences and settings
119
+ - `bmw.0.VIN.api.smartMaintenanceTyreDiagnosis.*` - Smart maintenance system tyre condition and diagnosis
120
+ - `bmw.0.VIN.api.telematicData.*` - Vehicle telematic data including trip information and driving behavior
128
121
 
129
122
  ### Metadata
130
123
 
@@ -196,7 +189,7 @@ This adapter is available at: [https://github.com/TA2k/ioBroker.bmw](https://git
196
189
 
197
190
  ## Changelog
198
191
 
199
- ### 4.0.2 (2025-10-01)
192
+ ### 4.0.3 (2025-10-01)
200
193
 
201
194
  - **BREAKING:** Complete migration to BMW CarData API with OAuth2 Device Flow authentication
202
195
  - **BREAKING:** Removed username/password authentication (deprecated by BMW)
@@ -318,270 +318,125 @@
318
318
  "newLine": false,
319
319
  "sm": 4
320
320
  },
321
- "fetchChargingProfile": {
321
+ "fetchImage": {
322
322
  "type": "checkbox",
323
323
  "label": {
324
- "en": "Fetch Charging Profile",
325
- "de": "Ladeprofil abrufen",
326
- "ru": "Получать профиль зарядки",
327
- "pt": "Buscar perfil de carregamento",
328
- "nl": "Laadprofiel ophalen",
329
- "fr": "Récupérer le profil de charge",
330
- "it": "Recupera profilo ricarica",
331
- "es": "Obtener perfil de carga",
332
- "pl": "Pobierz profil ładowania",
333
- "zh-cn": "获取充电配置文件"
324
+ "en": "Fetch Vehicle Image",
325
+ "de": "Fahrzeugbild abrufen",
326
+ "ru": "Получать изображение автомобиля",
327
+ "pt": "Buscar imagem do veículo",
328
+ "nl": "Voertuigafbeelding ophalen",
329
+ "fr": "Récupérer l'image du véhicule",
330
+ "it": "Recupera immagine veicolo",
331
+ "es": "Obtener imagen del vehículo",
332
+ "pl": "Pobierz obraz pojazdu",
333
+ "zh-cn": "获取车辆图像"
334
334
  },
335
335
  "help": {
336
- "en": "Charging preferences and profiles",
337
- "de": "Ladepräferenzen und -profile",
338
- "ru": "Предпочтения и профили зарядки",
339
- "pt": "Preferências e perfis de carregamento",
340
- "nl": "Laadvoorkeuren en -profielen",
341
- "fr": "Préférences et profils de charge",
342
- "it": "Preferenze e profili di ricarica",
343
- "es": "Preferencias y perfiles de carga",
344
- "pl": "Preferencje i profile ładowania",
345
- "zh-cn": "充电偏好和配置文件"
336
+ "en": "Vehicle image for display purposes",
337
+ "de": "Fahrzeugbild für Anzeigezwecke",
338
+ "ru": "Изображение автомобиля для отображения",
339
+ "pt": "Imagem do veículo para fins de exibição",
340
+ "nl": "Voertuigafbeelding voor weergavedoeleinden",
341
+ "fr": "Image du véhicule à des fins d'affichage",
342
+ "it": "Immagine del veicolo per scopi di visualizzazione",
343
+ "es": "Imagen del vehículo para fines de visualización",
344
+ "pl": "Obraz pojazdu do celów wyświetlania",
345
+ "zh-cn": "用于显示目的的车辆图像"
346
346
  },
347
347
  "default": false,
348
348
  "newLine": false,
349
349
  "sm": 4
350
350
  },
351
- "fetchChargingSessions": {
351
+ "fetchLocationBasedChargingSettings": {
352
352
  "type": "checkbox",
353
353
  "label": {
354
- "en": "Fetch Charging Sessions",
355
- "de": "Ladesitzungen abrufen",
356
- "ru": "Получать сессии зарядки",
357
- "pt": "Buscar sessões de carregamento",
358
- "nl": "Laadsessies ophalen",
359
- "fr": "Récupérer les sessions de charge",
360
- "it": "Recupera sessioni ricarica",
361
- "es": "Obtener sesiones de carga",
362
- "pl": "Pobierz sesje ładowania",
363
- "zh-cn": "获取充电会话"
354
+ "en": "Fetch Location Based Charging Settings",
355
+ "de": "Standortbasierte Ladeeinstellungen abrufen",
356
+ "ru": "Получать настройки зарядки на основе местоположения",
357
+ "pt": "Buscar configurações de carregamento baseadas em localização",
358
+ "nl": "Locatiegebaseerde laadinstellingen ophalen",
359
+ "fr": "Récupérer les paramètres de charge basés sur l'emplacement",
360
+ "it": "Recupera impostazioni ricarica basate sulla posizione",
361
+ "es": "Obtener configuraciones de carga basadas en ubicación",
362
+ "pl": "Pobierz ustawienia ładowania oparte na lokalizacji",
363
+ "zh-cn": "获取基于位置的充电设置"
364
364
  },
365
365
  "help": {
366
- "en": "Detailed charging session data",
367
- "de": "Detaillierte Ladesitzungsdaten",
368
- "ru": "Подробные данные сессий зарядки",
369
- "pt": "Dados detalhados de sessões de carregamento",
370
- "nl": "Gedetailleerde laadsessiegegevens",
371
- "fr": "Données détaillées des sessions de charge",
372
- "it": "Dati dettagliati sessioni ricarica",
373
- "es": "Datos detallados de sesiones de carga",
374
- "pl": "Szczegółowe dane sesji ładowania",
375
- "zh-cn": "详细的充电会话数据"
366
+ "en": "Location-specific charging preferences and settings",
367
+ "de": "Standortspezifische Ladepräferenzen und -einstellungen",
368
+ "ru": "Предпочтения и настройки зарядки для конкретных местоположений",
369
+ "pt": "Preferências e configurações de carregamento específicas do local",
370
+ "nl": "Locatiespecifieke laadvoorkeuren en -instellingen",
371
+ "fr": "Préférences et paramètres de charge spécifiques à l'emplacement",
372
+ "it": "Preferenze e impostazioni di ricarica specifiche per la posizione",
373
+ "es": "Preferencias y configuraciones de carga específicas de ubicación",
374
+ "pl": "Preferencje i ustawienia ładowania specyficzne dla lokalizacji",
375
+ "zh-cn": "特定位置的充电偏好和设置"
376
376
  },
377
377
  "default": false,
378
378
  "newLine": true,
379
379
  "sm": 4
380
380
  },
381
- "fetchClimateNow": {
381
+ "fetchSmartMaintenanceTyreDiagnosis": {
382
382
  "type": "checkbox",
383
383
  "label": {
384
- "en": "Fetch Climate Now",
385
- "de": "Klima jetzt abrufen",
386
- "ru": "Получать климат сейчас",
387
- "pt": "Buscar clima agora",
388
- "nl": "Klimaat nu ophalen",
389
- "fr": "Récupérer le climat maintenant",
390
- "it": "Recupera clima ora",
391
- "es": "Obtener clima ahora",
392
- "pl": "Pobierz klimat teraz",
393
- "zh-cn": "获取当前气候"
384
+ "en": "Fetch Smart Maintenance Tyre Diagnosis",
385
+ "de": "Smart Maintenance Reifendiagnose abrufen",
386
+ "ru": "Получать диагностику шин Smart Maintenance",
387
+ "pt": "Buscar diagnóstico de pneus Smart Maintenance",
388
+ "nl": "Smart Maintenance bandendiagnose ophalen",
389
+ "fr": "Récupérer le diagnostic des pneus Smart Maintenance",
390
+ "it": "Recupera diagnosi pneumatici Smart Maintenance",
391
+ "es": "Obtener diagnóstico de neumáticos Smart Maintenance",
392
+ "pl": "Pobierz diagnozę opon Smart Maintenance",
393
+ "zh-cn": "获取智能维护轮胎诊断"
394
394
  },
395
395
  "help": {
396
- "en": "Current climate control status",
397
- "de": "Aktueller Klimaanlagenstatus",
398
- "ru": "Текущий статус климат-контроля",
399
- "pt": "Status atual do controle climático",
400
- "nl": "Huidige klimaatregelingsstatus",
401
- "fr": "État actuel du contrôle climatique",
402
- "it": "Stato attuale controllo clima",
403
- "es": "Estado actual del control climático",
404
- "pl": "Aktualny stan kontroli klimatu",
405
- "zh-cn": "当前气候控制状态"
396
+ "en": "Smart maintenance system tyre condition and diagnosis data",
397
+ "de": "Smart Maintenance System Reifenzustand und Diagnosedaten",
398
+ "ru": "Данные состояния и диагностики шин системы Smart Maintenance",
399
+ "pt": "Dados de condição e diagnóstico de pneus do sistema Smart Maintenance",
400
+ "nl": "Smart Maintenance systeem bandentoestand en diagnosegegevens",
401
+ "fr": "Données de condition et de diagnostic des pneus du système Smart Maintenance",
402
+ "it": "Dati di condizione e diagnosi pneumatici del sistema Smart Maintenance",
403
+ "es": "Datos de condición y diagnóstico de neumáticos del sistema Smart Maintenance",
404
+ "pl": "Dane stanu i diagnozy opon systemu Smart Maintenance",
405
+ "zh-cn": "智能维护系统轮胎状况和诊断数据"
406
406
  },
407
407
  "default": false,
408
408
  "newLine": false,
409
409
  "sm": 4
410
410
  },
411
- "fetchDestinationInformation": {
411
+ "fetchTelematicData": {
412
412
  "type": "checkbox",
413
413
  "label": {
414
- "en": "Fetch Destination Information",
415
- "de": "Zielinformationen abrufen",
416
- "ru": "Получать информацию о пункте назначения",
417
- "pt": "Buscar informações de destino",
418
- "nl": "Bestemmingsinformatie ophalen",
419
- "fr": "Récupérer les informations de destination",
420
- "it": "Recupera informazioni destinazione",
421
- "es": "Obtener información de destino",
422
- "pl": "Pobierz informacje o miejscu docelowym",
423
- "zh-cn": "获取目的地信息"
414
+ "en": "Fetch Telematic Data",
415
+ "de": "Telematikdaten abrufen",
416
+ "ru": "Получать телематические данные",
417
+ "pt": "Buscar dados telemáticos",
418
+ "nl": "Telematicagegevens ophalen",
419
+ "fr": "Récupérer les données télématiques",
420
+ "it": "Recupera dati telematici",
421
+ "es": "Obtener datos telemáticos",
422
+ "pl": "Pobierz dane telematyczne",
423
+ "zh-cn": "获取远程信息处理数据"
424
424
  },
425
425
  "help": {
426
- "en": "Navigation and destination data",
427
- "de": "Navigations- und Zieldaten",
428
- "ru": "Данные навигации и пункта назначения",
429
- "pt": "Dados de navegação e destino",
430
- "nl": "Navigatie- en bestemmingsgegevens",
431
- "fr": "Données de navigation et de destination",
432
- "it": "Dati navigazione e destinazione",
433
- "es": "Datos de navegación y destino",
434
- "pl": "Dane nawigacji i miejsca docelowego",
435
- "zh-cn": "导航和目的地数据"
426
+ "en": "Vehicle telematic data including trip information and driving behavior",
427
+ "de": "Fahrzeugtelematikdaten einschließlich Reiseinformationen und Fahrverhalten",
428
+ "ru": "Телематические данные автомобиля, включая информацию о поездках и поведении при вождении",
429
+ "pt": "Dados telemáticos do veículo incluindo informações de viagem e comportamento de direção",
430
+ "nl": "Voertuigtelematicagegevens inclusief reisinformatie en rijgedrag",
431
+ "fr": "Données télématiques du véhicule incluant les informations de voyage et le comportement de conduite",
432
+ "it": "Dati telematici del veicolo incluse informazioni di viaggio e comportamento di guida",
433
+ "es": "Datos telemáticos del vehículo incluyendo información de viaje y comportamiento de conducción",
434
+ "pl": "Dane telematyczne pojazdu obejmujące informacje o podróży i zachowanie podczas jazdy",
435
+ "zh-cn": "车辆远程信息处理数据,包括行程信息和驾驶行为"
436
436
  },
437
437
  "default": false,
438
438
  "newLine": false,
439
439
  "sm": 4
440
- },
441
- "fetchLocation": {
442
- "type": "checkbox",
443
- "label": {
444
- "en": "Fetch Location",
445
- "de": "Standort abrufen",
446
- "ru": "Получать местоположение",
447
- "pt": "Buscar localização",
448
- "nl": "Locatie ophalen",
449
- "fr": "Récupérer la localisation",
450
- "it": "Recupera posizione",
451
- "es": "Obtener ubicación",
452
- "pl": "Pobierz lokalizację",
453
- "zh-cn": "获取位置"
454
- },
455
- "help": {
456
- "en": "GPS position and location services",
457
- "de": "GPS-Position und Standortdienste",
458
- "ru": "GPS позиция и службы определения местоположения",
459
- "pt": "Posição GPS e serviços de localização",
460
- "nl": "GPS positie en locatiediensten",
461
- "fr": "Position GPS et services de localisation",
462
- "it": "Posizione GPS e servizi di localizzazione",
463
- "es": "Posición GPS y servicios de ubicación",
464
- "pl": "Pozycja GPS i usługi lokalizacji",
465
- "zh-cn": "GPS位置和位置服务"
466
- },
467
- "default": false,
468
- "newLine": true,
469
- "sm": 4
470
- },
471
- "fetchStatistics": {
472
- "type": "checkbox",
473
- "label": {
474
- "en": "Fetch Statistics",
475
- "de": "Statistiken abrufen",
476
- "ru": "Получать статистику",
477
- "pt": "Buscar estatísticas",
478
- "nl": "Statistieken ophalen",
479
- "fr": "Récupérer les statistiques",
480
- "it": "Recupera statistiche",
481
- "es": "Obtener estadísticas",
482
- "pl": "Pobierz statystyki",
483
- "zh-cn": "获取统计信息"
484
- },
485
- "help": {
486
- "en": "Driving statistics and analytics",
487
- "de": "Fahrstatistiken und Analysen",
488
- "ru": "Статистика вождения и аналитика",
489
- "pt": "Estatísticas de direção e análises",
490
- "nl": "Rijstatistieken en analyses",
491
- "fr": "Statistiques de conduite et analyses",
492
- "it": "Statistiche di guida e analisi",
493
- "es": "Estadísticas de conducción y análisis",
494
- "pl": "Statystyki jazdy i analizy",
495
- "zh-cn": "驾驶统计和分析"
496
- },
497
- "default": false,
498
- "newLine": false,
499
- "sm": 4
500
- },
501
- "fetchVehicleState": {
502
- "type": "checkbox",
503
- "label": {
504
- "en": "Fetch Vehicle State",
505
- "de": "Fahrzeugstatus abrufen",
506
- "ru": "Получать состояние автомобиля",
507
- "pt": "Buscar estado do veículo",
508
- "nl": "Voertuigstatus ophalen",
509
- "fr": "Récupérer l'état du véhicule",
510
- "it": "Recupera stato veicolo",
511
- "es": "Obtener estado del vehículo",
512
- "pl": "Pobierz stan pojazdu",
513
- "zh-cn": "获取车辆状态"
514
- },
515
- "help": {
516
- "en": "Current vehicle status and conditions",
517
- "de": "Aktueller Fahrzeugstatus und -zustand",
518
- "ru": "Текущий статус и состояние автомобиля",
519
- "pt": "Status e condições atuais do veículo",
520
- "nl": "Huidige voertuigstatus en -condities",
521
- "fr": "État et conditions actuels du véhicule",
522
- "it": "Stato e condizioni attuali del veicolo",
523
- "es": "Estado y condiciones actuales del vehículo",
524
- "pl": "Aktualny stan i warunki pojazdu",
525
- "zh-cn": "当前车辆状态和条件"
526
- },
527
- "default": true,
528
- "newLine": false,
529
- "sm": 4
530
- },
531
- "_breaking": {
532
- "type": "header",
533
- "text": {
534
- "en": "⚠️ Breaking Changes in v4.0",
535
- "de": "⚠️ Breaking Changes in v4.0",
536
- "ru": "⚠️ Критические изменения в v4.0",
537
- "pt": "⚠️ Mudanças críticas na v4.0",
538
- "nl": "⚠️ Breaking changes in v4.0",
539
- "fr": "⚠️ Changements critiques en v4.0",
540
- "it": "⚠️ Cambiamenti critici nella v4.0",
541
- "es": "⚠️ Cambios críticos en v4.0",
542
- "pl": "⚠️ Krytyczne zmiany w v4.0",
543
- "zh-cn": "⚠️ v4.0中的重大变更"
544
- },
545
- "size": 5,
546
- "color": "orange"
547
- },
548
- "_removed": {
549
- "type": "staticText",
550
- "text": {
551
- "en": "❌ Removed: Username/password login, remote controls (lock/unlock, climate, charging), second user support, captcha requirement",
552
- "de": "❌ Entfernt: Benutzername/Passwort-Login, Fernbedienung (Verriegeln/Entriegeln, Klima, Laden), zweiter Benutzer, Captcha-Anforderung",
553
- "ru": "❌ Удалено: Логин по имени пользователя/паролю, удаленное управление (блокировка/разблокировка, климат, зарядка), поддержка второго пользователя, требование капчи",
554
- "pt": "❌ Removido: Login com nome de usuário/senha, controles remotos (trancar/destrancar, clima, carregamento), suporte ao segundo usuário, requisito de captcha",
555
- "nl": "❌ Verwijderd: Gebruikersnaam/wachtwoord login, remote controls (vergrendelen/ontgrendelen, klimaat, laden), tweede gebruiker ondersteuning, captcha vereiste",
556
- "fr": "❌ Supprimé: Connexion nom d'utilisateur/mot de passe, contrôles à distance (verrouiller/déverrouiller, climatisation, charge), support du deuxième utilisateur, exigence de captcha",
557
- "it": "❌ Rimosso: Login username/password, controlli remoti (bloccare/sbloccare, clima, ricarica), supporto secondo utente, requisito captcha",
558
- "es": "❌ Eliminado: Login de usuario/contraseña, controles remotos (bloquear/desbloquear, clima, carga), soporte de segundo usuario, requisito de captcha",
559
- "pl": "❌ Usunięto: Login nazwa użytkownika/hasło, zdalne sterowanie (blokowanie/odblokowanie, klimat, ładowanie), obsługa drugiego użytkownika, wymóg captcha",
560
- "zh-cn": "❌ 已移除:用户名/密码登录,远程控制(锁定/解锁,气候,充电),第二用户支持,验证码要求"
561
- },
562
- "style": {
563
- "color": "red",
564
- "fontSize": "smaller"
565
- }
566
- },
567
- "_added": {
568
- "type": "staticText",
569
- "text": {
570
- "en": "✅ Added: OAuth2 device flow authentication, real-time MQTT streaming, 50 API calls per 24h quota management, comprehensive data from all CarData endpoints",
571
- "de": "✅ Hinzugefügt: OAuth2 Device Flow Authentifizierung, Echtzeit MQTT Streaming, 50 API Aufrufe pro 24h Quota Management, umfassende Daten von allen CarData Endpunkten",
572
- "ru": "✅ Добавлено: OAuth2 device flow аутентификация, MQTT поток в реальном времени, управление квотой 50 API вызовов за 24ч, всесторонние данные со всех CarData конечных точек",
573
- "pt": "✅ Adicionado: Autenticação OAuth2 device flow, streaming MQTT em tempo real, gerenciamento de quota de 50 chamadas API por 24h, dados abrangentes de todos os endpoints CarData",
574
- "nl": "✅ Toegevoegd: OAuth2 device flow authenticatie, realtime MQTT streaming, 50 API oproepen per 24u quota beheer, uitgebreide data van alle CarData endpoints",
575
- "fr": "✅ Ajouté: Authentification OAuth2 device flow, streaming MQTT en temps réel, gestion de quota de 50 appels API par 24h, données complètes de tous les endpoints CarData",
576
- "it": "✅ Aggiunto: Autenticazione OAuth2 device flow, streaming MQTT in tempo reale, gestione quota 50 chiamate API per 24h, dati completi da tutti gli endpoint CarData",
577
- "es": "✅ Agregado: Autenticación OAuth2 device flow, streaming MQTT en tiempo real, gestión de cuota de 50 llamadas API por 24h, datos completos de todos los endpoints CarData",
578
- "pl": "✅ Dodano: Uwierzytelnianie OAuth2 device flow, streaming MQTT w czasie rzeczywistym, zarządzanie kwotą 50 wywołań API na 24h, kompleksowe dane ze wszystkich punktów końcowych CarData",
579
- "zh-cn": "✅ 已添加:OAuth2设备流身份验证,实时MQTT流,每24小时50次API调用配额管理,来自所有CarData端点的全面数据"
580
- },
581
- "style": {
582
- "color": "green",
583
- "fontSize": "smaller"
584
- }
585
440
  }
586
441
  }
587
442
  }
package/io-package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "bmw",
4
- "version": "4.0.2",
4
+ "version": "4.0.3",
5
5
  "news": {
6
- "4.0.2": {
6
+ "4.0.3": {
7
7
  "en": "BREAKING: Complete migration to BMW CarData API\nBREAKING: OAuth2 device flow authentication (no more username/password)\nBREAKING: Remote controls removed (CarData API is read-only)\nNEW: Real-time MQTT streaming for instant updates\nNEW: 50 API calls per 24h quota management\nNEW: Comprehensive data collection from all CarData endpoints",
8
8
  "de": "BREAKING: Vollständige Migration zur BMW CarData API\nBREAKING: OAuth2 Device Flow Authentifizierung (keine Benutzername/Passwort mehr)\nBREAKING: Fernbedienungen entfernt (CarData API ist nur lesend)\nNEU: Echtzeit MQTT Streaming für sofortige Updates\nNEU: 50 API Aufrufe pro 24h Quota Management\nNEU: Umfassende Datensammlung von allen CarData Endpunkten"
9
9
  },
@@ -125,12 +125,8 @@
125
125
  "uk": "Adapter for BMW CarData API з потоком MQTT в реальному часі",
126
126
  "zh-cn": "带有实时MQTT流的BMW CarData API适配器"
127
127
  },
128
- "authors": [
129
- "TA2k <tombox2020@gmail.com>"
130
- ],
131
- "keywords": [
132
- "BMW"
133
- ],
128
+ "authors": ["TA2k <tombox2020@gmail.com>"],
129
+ "keywords": ["BMW"],
134
130
  "licenseInformation": {
135
131
  "license": "MIT",
136
132
  "type": "free"
package/lib/tools.js CHANGED
@@ -7,11 +7,11 @@ const axios = require('axios').default;
7
7
  * @returns {it is Record<string, any>} True if the variable is a real object, false otherwise.
8
8
  */
9
9
  function isObject(it) {
10
- // This is necessary because:
11
- // typeof null === 'object'
12
- // typeof [] === 'object'
13
- // [] instanceof Object === true
14
- return Object.prototype.toString.call(it) === '[object Object]';
10
+ // This is necessary because:
11
+ // typeof null === 'object'
12
+ // typeof [] === 'object'
13
+ // [] instanceof Object === true
14
+ return Object.prototype.toString.call(it) === '[object Object]';
15
15
  }
16
16
 
17
17
  /**
@@ -21,10 +21,10 @@ function isObject(it) {
21
21
  * @returns {it is any[]} True if the variable is an array, false otherwise.
22
22
  */
23
23
  function isArray(it) {
24
- if (typeof Array.isArray === 'function') {
25
- return Array.isArray(it);
26
- }
27
- return Object.prototype.toString.call(it) === '[object Array]';
24
+ if (typeof Array.isArray === 'function') {
25
+ return Array.isArray(it);
26
+ }
27
+ return Object.prototype.toString.call(it) === '[object Array]';
28
28
  }
29
29
 
30
30
  /**
@@ -36,15 +36,15 @@ function isArray(it) {
36
36
  * @returns {Promise<string>} A promise that resolves to the translated text.
37
37
  */
38
38
  async function translateText(text, targetLang, yandexApiKey) {
39
- if (targetLang === 'en') {
40
- return text;
41
- } else if (!text) {
42
- return '';
43
- }
44
- if (yandexApiKey) {
45
- return translateYandex(text, targetLang, yandexApiKey);
46
- }
47
- return translateGoogle(text, targetLang);
39
+ if (targetLang === 'en') {
40
+ return text;
41
+ } else if (!text) {
42
+ return '';
43
+ }
44
+ if (yandexApiKey) {
45
+ return translateYandex(text, targetLang, yandexApiKey);
46
+ }
47
+ return translateGoogle(text, targetLang);
48
48
  }
49
49
 
50
50
  /**
@@ -56,21 +56,21 @@ async function translateText(text, targetLang, yandexApiKey) {
56
56
  * @returns {Promise<string>} A promise that resolves to the translated text.
57
57
  */
58
58
  async function translateYandex(text, targetLang, apiKey) {
59
- if (targetLang === 'zh-cn') {
60
- targetLang = 'zh';
61
- }
62
- try {
63
- const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(
64
- text,
65
- )}&lang=en-${targetLang}`;
66
- const response = await axios({ url, timeout: 15000 });
67
- if (response.data && response.data.text && isArray(response.data.text)) {
68
- return response.data.text[0];
69
- }
70
- throw new Error('Invalid response for translate request');
71
- } catch (e) {
72
- throw new Error(`Could not translate to "${targetLang}": ${e}`);
73
- }
59
+ if (targetLang === 'zh-cn') {
60
+ targetLang = 'zh';
61
+ }
62
+ try {
63
+ const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(
64
+ text,
65
+ )}&lang=en-${targetLang}`;
66
+ const response = await axios({ url, timeout: 15000 });
67
+ if (response.data && response.data.text && isArray(response.data.text)) {
68
+ return response.data.text[0];
69
+ }
70
+ throw new Error('Invalid response for translate request');
71
+ } catch (e) {
72
+ throw new Error(`Could not translate to "${targetLang}": ${e}`);
73
+ }
74
74
  }
75
75
 
76
76
  /**
@@ -81,27 +81,27 @@ async function translateYandex(text, targetLang, apiKey) {
81
81
  * @returns {Promise<string>} A promise that resolves to the translated text.
82
82
  */
83
83
  async function translateGoogle(text, targetLang) {
84
- try {
85
- const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(
86
- text,
87
- )}&ie=UTF-8&oe=UTF-8`;
88
- const response = await axios({ url, timeout: 15000 });
89
- if (isArray(response.data)) {
90
- // we got a valid response
91
- return response.data[0][0][0];
92
- }
93
- throw new Error('Invalid response for translate request');
94
- } catch (e) {
95
- if (e.response && e.response.status === 429) {
96
- throw new Error(`Could not translate to "${targetLang}": Rate-limited by Google Translate`);
97
- } else {
98
- throw new Error(`Could not translate to "${targetLang}": ${e}`);
99
- }
100
- }
84
+ try {
85
+ const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(
86
+ text,
87
+ )}&ie=UTF-8&oe=UTF-8`;
88
+ const response = await axios({ url, timeout: 15000 });
89
+ if (isArray(response.data)) {
90
+ // we got a valid response
91
+ return response.data[0][0][0];
92
+ }
93
+ throw new Error('Invalid response for translate request');
94
+ } catch (e) {
95
+ if (e.response && e.response.status === 429) {
96
+ throw new Error(`Could not translate to "${targetLang}": Rate-limited by Google Translate`);
97
+ } else {
98
+ throw new Error(`Could not translate to "${targetLang}": ${e}`);
99
+ }
100
+ }
101
101
  }
102
102
 
103
103
  module.exports = {
104
- isArray,
105
- isObject,
106
- translateText,
104
+ isArray,
105
+ isObject,
106
+ translateText,
107
107
  };
package/main.js CHANGED
@@ -81,6 +81,7 @@ class Bmw extends utils.Adapter {
81
81
  this.apiCalls = JSON.parse(apiCallsHistoryState.val);
82
82
  this.log.debug(`Restored ${this.apiCalls.length} API call timestamps from history`);
83
83
  } catch (error) {
84
+ this.log.error(`Failed to parse API calls history: ${error.message}`);
84
85
  this.log.warn('Failed to parse API calls history, starting fresh');
85
86
  this.apiCalls = [];
86
87
  }
@@ -101,7 +102,7 @@ class Bmw extends utils.Adapter {
101
102
  // Try to refresh tokens
102
103
  await this.refreshToken();
103
104
  } catch (error) {
104
- this.log.warn('Failed to parse stored session, starting new login');
105
+ this.log.warn(`Failed to parse stored session, starting new login ${error.message}`);
105
106
  await this.login();
106
107
  }
107
108
  } else {
@@ -118,30 +119,36 @@ class Bmw extends utils.Adapter {
118
119
  // Connect MQTT after successful auth
119
120
  await this.connectMQTT();
120
121
  // Start periodic token refresh (every 45 minutes)
121
- this.refreshTokenInterval = setInterval(async () => {
122
- await this.refreshToken();
123
- }, 45 * 60 * 1000);
122
+ this.refreshTokenInterval = setInterval(
123
+ async () => {
124
+ await this.refreshToken();
125
+ },
126
+ 45 * 60 * 1000,
127
+ );
124
128
 
125
129
  // Start periodic API updates (respecting quota limits)
126
130
  if (this.vinArray.length > 0) {
127
131
  this.log.info(`Setting up periodic updates every ${this.config.interval} minutes for ${this.vinArray.length} vehicle(s)`);
128
- this.updateInterval = setInterval(async () => {
129
- // Update quota states (expired calls removed automatically)
130
- this.updateQuotaStates();
131
-
132
- // Periodic API data refresh - MQTT provides real-time updates
133
- const headers = {
134
- Authorization: `Bearer ${this.session.access_token}`,
135
- 'x-version': 'v1',
136
- Accept: 'application/json',
137
- };
138
-
139
- for (const vin of this.vinArray) {
140
- this.log.debug(`Periodic API refresh for ${vin}`);
141
- await this.fetchAllVehicleData(vin, headers);
142
- break; // Only one vehicle per interval to conserve quota
143
- }
144
- }, this.config.interval * 60 * 1000);
132
+ this.updateInterval = setInterval(
133
+ async () => {
134
+ // Update quota states (expired calls removed automatically)
135
+ this.updateQuotaStates();
136
+
137
+ // Periodic API data refresh - MQTT provides real-time updates
138
+ const headers = {
139
+ Authorization: `Bearer ${this.session.access_token}`,
140
+ 'x-version': 'v1',
141
+ Accept: 'application/json',
142
+ };
143
+
144
+ for (const vin of this.vinArray) {
145
+ this.log.debug(`Periodic API refresh for ${vin}`);
146
+ await this.fetchAllVehicleData(vin, headers);
147
+ break; // Only one vehicle per interval to conserve quota
148
+ }
149
+ },
150
+ this.config.interval * 60 * 1000,
151
+ );
145
152
  }
146
153
 
147
154
  this.log.info('BMW CarData adapter startup complete');
@@ -175,7 +182,7 @@ class Bmw extends utils.Adapter {
175
182
  code_challenge: codeChallenge,
176
183
  code_challenge_method: 'S256',
177
184
  };
178
- this.log.debug('Device code request data: ' + JSON.stringify(requestData));
185
+ this.log.debug(`Device code request data: ${JSON.stringify(requestData)}`);
179
186
 
180
187
  const deviceResponse = await this.requestClient({
181
188
  method: 'post',
@@ -186,17 +193,17 @@ class Bmw extends utils.Adapter {
186
193
  },
187
194
  data: requestData,
188
195
  })
189
- .then((res) => {
190
- this.log.debug('Device code response: ' + JSON.stringify(res.data));
196
+ .then(res => {
197
+ this.log.debug(`Device code response: ${JSON.stringify(res.data)}`);
191
198
  return res;
192
199
  })
193
- .catch((error) => {
194
- this.log.error('Device code request failed: ' + error.message);
195
- this.log.error('Error stack: ' + error.stack);
200
+ .catch(error => {
201
+ this.log.error(`Device code request failed: ${error.message}`);
202
+ this.log.error(`Error stack: ${error.stack}`);
196
203
  if (error.response) {
197
- this.log.error('Response status: ' + error.response.status);
198
- this.log.error('Response headers: ' + JSON.stringify(error.response.headers));
199
- this.log.error('Response data: ' + JSON.stringify(error.response.data));
204
+ this.log.error(`Response status: ${error.response.status}`);
205
+ this.log.error(`Response headers: ${JSON.stringify(error.response.headers)}`);
206
+ this.log.error(`Response data: ${JSON.stringify(error.response.data)}`);
200
207
 
201
208
  // Special handling for 400 Bad Request - likely client configuration issue
202
209
  if (error.response.status === 400) {
@@ -211,7 +218,9 @@ class Bmw extends utils.Adapter {
211
218
  this.log.error('To fix this issue:');
212
219
  this.log.error('1. Visit BMW ConnectedDrive portal: https://www.bmw.de/de-de/mybmw/vehicle-overview');
213
220
  this.log.error('2. Go to CarData section');
214
- this.log.error('3. Check if CarData API and CarData Streaming are both activated. Sometimes it needs 30s to save the selection');
221
+ this.log.error(
222
+ '3. Check if CarData API and CarData Streaming are both activated. Sometimes it needs 30s to save the selection',
223
+ );
215
224
  this.log.error('4. If not activated, enable both services');
216
225
  this.log.error('5. If already activated, delete and recreate your Client ID');
217
226
  this.log.error('6. Update the adapter configuration with the new Client ID');
@@ -220,12 +229,11 @@ class Bmw extends utils.Adapter {
220
229
  }
221
230
  if (error.request) {
222
231
  this.log.error(
223
- 'Request details: ' +
224
- JSON.stringify({
225
- method: error.request.method,
226
- url: error.request.url,
227
- headers: error.request._headers,
228
- })
232
+ `Request details: ${JSON.stringify({
233
+ method: error.request.method,
234
+ url: error.request.url,
235
+ headers: error.request._headers,
236
+ })}`,
229
237
  );
230
238
  }
231
239
  return false; // Return false instead of throwing
@@ -263,7 +271,7 @@ class Bmw extends utils.Adapter {
263
271
  device_code: device_code,
264
272
  code_verifier: codeVerifier,
265
273
  };
266
- this.log.debug('Token request data: ' + JSON.stringify(tokenRequestData));
274
+ this.log.debug(`Token request data: ${JSON.stringify(tokenRequestData)}`);
267
275
 
268
276
  const tokenResponse = await this.requestClient({
269
277
  method: 'post',
@@ -302,7 +310,7 @@ class Bmw extends utils.Adapter {
302
310
  return true;
303
311
  } catch (error) {
304
312
  const errorCode = error.response?.data?.error;
305
- this.log.debug('Token polling error: ' + (errorCode || error.message));
313
+ this.log.debug(`Token polling error: ${errorCode || error.message}`);
306
314
 
307
315
  if (errorCode === 'authorization_pending') {
308
316
  this.log.debug('Authorization still pending, continuing to poll...');
@@ -315,10 +323,10 @@ class Bmw extends utils.Adapter {
315
323
  this.log.error('Authorization code expired, please restart adapter');
316
324
  return false;
317
325
  } else {
318
- this.log.error('Token request failed: ' + (errorCode || error.message));
326
+ this.log.error(`Token request failed: ${errorCode || error.message}`);
319
327
  if (error.response) {
320
- this.log.error('Token response status: ' + error.response.status);
321
- this.log.error('Token response data: ' + JSON.stringify(error.response.data));
328
+ this.log.error(`Token response status: ${error.response.status}`);
329
+ this.log.error(`Token response data: ${JSON.stringify(error.response.data)}`);
322
330
  }
323
331
  return false;
324
332
  }
@@ -359,7 +367,7 @@ class Bmw extends utils.Adapter {
359
367
  url: `${this.carDataApiBase}/customers/vehicles/mappings`,
360
368
  headers: headers,
361
369
  })
362
- .then(async (res) => {
370
+ .then(async res => {
363
371
  this.log.debug(JSON.stringify(res.data));
364
372
  const mappings = res.data;
365
373
 
@@ -373,7 +381,7 @@ class Bmw extends utils.Adapter {
373
381
  }
374
382
 
375
383
  for (const mapping of mappings) {
376
- if (mapping.mappingType === 'PRIMARY' && mapping.vin) {
384
+ if (mapping.vin) {
377
385
  const vin = mapping.vin;
378
386
 
379
387
  // Check ignore list
@@ -406,10 +414,10 @@ class Bmw extends utils.Adapter {
406
414
  }
407
415
  }
408
416
  })
409
- .catch((error) => {
410
- this.log.error('BMW CarData vehicle discovery failed: ' + error.message);
417
+ .catch(error => {
418
+ this.log.error(`BMW CarData vehicle discovery failed: ${error.message}`);
411
419
  if (error.response) {
412
- this.log.error('Response: ' + JSON.stringify(error.response.data));
420
+ this.log.error(`Response: ${JSON.stringify(error.response.data)}`);
413
421
  if (error.response.status === 403 || error.response.status === 429) {
414
422
  this.log.warn('Rate limit exceeded or access denied');
415
423
  }
@@ -438,7 +446,7 @@ class Bmw extends utils.Adapter {
438
446
  native: {},
439
447
  });
440
448
 
441
- // Define available API endpoints with config mapping
449
+ // Define available API endpoints with config mapping (BMW CarData API v1)
442
450
  const apiEndpoints = [
443
451
  {
444
452
  name: 'basicData',
@@ -453,51 +461,33 @@ class Bmw extends utils.Adapter {
453
461
  channel: 'Charging History',
454
462
  },
455
463
  {
456
- name: 'chargingProfile',
457
- configKey: 'fetchChargingProfile',
458
- url: `/customers/vehicles/${vin}/chargingProfile`,
459
- channel: 'Charging Profile',
460
- },
461
- {
462
- name: 'chargingSessions',
463
- configKey: 'fetchChargingSessions',
464
- url: `/customers/vehicles/${vin}/chargingSessions`,
465
- channel: 'Charging Sessions',
466
- },
467
- {
468
- name: 'climateNow',
469
- configKey: 'fetchClimateNow',
470
- url: `/customers/vehicles/${vin}/climateNow`,
471
- channel: 'Climate Control',
472
- },
473
- {
474
- name: 'destinationInformation',
475
- configKey: 'fetchDestinationInformation',
476
- url: `/customers/vehicles/${vin}/destinationInformation`,
477
- channel: 'Destination Information',
464
+ name: 'image',
465
+ configKey: 'fetchImage',
466
+ url: `/customers/vehicles/${vin}/image`,
467
+ channel: 'Vehicle Image',
478
468
  },
479
469
  {
480
- name: 'location',
481
- configKey: 'fetchLocation',
482
- url: `/customers/vehicles/${vin}/location`,
483
- channel: 'Vehicle Location',
470
+ name: 'locationBasedChargingSettings',
471
+ configKey: 'fetchLocationBasedChargingSettings',
472
+ url: `/customers/vehicles/${vin}/locationBasedChargingSettings`,
473
+ channel: 'Location Based Charging Settings',
484
474
  },
485
475
  {
486
- name: 'statistics',
487
- configKey: 'fetchStatistics',
488
- url: `/customers/vehicles/${vin}/statistics`,
489
- channel: 'Statistics',
476
+ name: 'smartMaintenanceTyreDiagnosis',
477
+ configKey: 'fetchSmartMaintenanceTyreDiagnosis',
478
+ url: `/customers/vehicles/${vin}/smartMaintenanceTyreDiagnosis`,
479
+ channel: 'Smart Maintenance Tyre Diagnosis',
490
480
  },
491
481
  {
492
- name: 'vehicleState',
493
- configKey: 'fetchVehicleState',
494
- url: `/customers/vehicles/${vin}/vehicleState`,
495
- channel: 'Vehicle State',
482
+ name: 'telematicData',
483
+ configKey: 'fetchTelematicData',
484
+ url: `/customers/vehicles/${vin}/telematicData`,
485
+ channel: 'Telematic Data',
496
486
  },
497
487
  ];
498
488
 
499
489
  // Filter endpoints based on user configuration
500
- const enabledEndpoints = apiEndpoints.filter((endpoint) => this.config[endpoint.configKey] === true);
490
+ const enabledEndpoints = apiEndpoints.filter(endpoint => this.config[endpoint.configKey] === true);
501
491
 
502
492
  this.log.info(`Fetching ${enabledEndpoints.length} configured API endpoints for ${vin}...`);
503
493
 
@@ -572,11 +562,13 @@ class Bmw extends utils.Adapter {
572
562
 
573
563
  updateQuotaStates() {
574
564
  const now = Date.now();
575
- if (!this.apiCalls) this.apiCalls = [];
565
+ if (!this.apiCalls) {
566
+ this.apiCalls = [];
567
+ }
576
568
 
577
569
  // Remove calls older than 24h
578
570
  const originalLength = this.apiCalls.length;
579
- this.apiCalls = this.apiCalls.filter((time) => now - time < 24 * 60 * 60 * 1000);
571
+ this.apiCalls = this.apiCalls.filter(time => now - time < 24 * 60 * 60 * 1000);
580
572
 
581
573
  // Save history if calls were removed due to expiration
582
574
  if (this.apiCalls.length !== originalLength) {
@@ -612,7 +604,7 @@ class Bmw extends utils.Adapter {
612
604
  }
613
605
 
614
606
  sleep(ms) {
615
- return new Promise((resolve) => setTimeout(resolve, ms));
607
+ return new Promise(resolve => setTimeout(resolve, ms));
616
608
  }
617
609
 
618
610
  async cleanObjects(vin) {
@@ -676,7 +668,7 @@ class Bmw extends utils.Adapter {
676
668
  refresh_token: this.session.refresh_token,
677
669
  client_id: this.config.clientId,
678
670
  };
679
- this.log.debug('Refresh request data: ' + JSON.stringify(refreshData));
671
+ this.log.debug(`Refresh request data: ${JSON.stringify(refreshData)}`);
680
672
 
681
673
  await this.requestClient({
682
674
  method: 'post',
@@ -686,7 +678,7 @@ class Bmw extends utils.Adapter {
686
678
  },
687
679
  data: qs.stringify(refreshData),
688
680
  })
689
- .then(async (res) => {
681
+ .then(async res => {
690
682
  // Store refreshed tokens (keep existing session structure)
691
683
  this.session = res.data;
692
684
  this.setState('cardataauth.session', JSON.stringify(this.session), true);
@@ -703,7 +695,7 @@ class Bmw extends utils.Adapter {
703
695
 
704
696
  return res.data;
705
697
  })
706
- .catch(async (error) => {
698
+ .catch(async error => {
707
699
  this.log.error('Token refresh failed:', error.message);
708
700
  this.log.error('Error stack:', error.stack);
709
701
  if (error.response) {
@@ -760,9 +752,9 @@ class Bmw extends utils.Adapter {
760
752
 
761
753
  // Subscribe to all vehicle topics for this CarData Streaming username
762
754
  const topic = `${this.config.cardataStreamingUsername}/+`;
763
- this.mqtt.subscribe(topic, (err) => {
755
+ this.mqtt.subscribe(topic, err => {
764
756
  if (err) {
765
- this.log.error('MQTT subscription failed: ' + err.message);
757
+ this.log.error(`MQTT subscription failed: ${err.message}`);
766
758
  } else {
767
759
  this.log.debug(`Subscribed to MQTT topic: ${topic}`);
768
760
  }
@@ -773,8 +765,8 @@ class Bmw extends utils.Adapter {
773
765
  this.handleMQTTMessage(topic, message);
774
766
  });
775
767
 
776
- this.mqtt.on('error', (error) => {
777
- this.log.error('MQTT error: ' + error.message);
768
+ this.mqtt.on('error', error => {
769
+ this.log.error(`MQTT error: ${error.message}`);
778
770
  this.setState('info.mqttConnected', false, true);
779
771
  });
780
772
 
@@ -797,7 +789,9 @@ class Bmw extends utils.Adapter {
797
789
  const topicParts = topic.split('/');
798
790
 
799
791
  if (topicParts.length >= 2) {
800
- const entityId = topicParts[0];
792
+ /*
793
+ 34ddc38-93220-423330-93101-fcd292373/WBY11HH: {"vin":"WBY11HH","entityId":"34ddc38-93220-423330-93101-fcd292373","topic":"WBY11HH","timestamp":"2025-10-01T10:12:40.809Z","data":{"vehicle.powertrain.electric.battery.charging.preferenceSmartCharging":{"timestamp":"2025-10-01T10:12:39Z","value":"PRICE_OPTIMIZED"}}}
794
+ */
801
795
  const vin = topicParts[1];
802
796
 
803
797
  if (data.vin && data.data) {
@@ -860,7 +854,7 @@ class Bmw extends utils.Adapter {
860
854
  }
861
855
  }
862
856
  } catch (error) {
863
- this.log.warn('Failed to parse MQTT message: ' + error.message);
857
+ this.log.warn(`Failed to parse MQTT message: ${error.message}`);
864
858
  }
865
859
  }
866
860
 
@@ -915,7 +909,7 @@ if (require.main !== module) {
915
909
  /**
916
910
  * @param {Partial<utils.AdapterOptions>} [options] - Optional adapter configuration options.
917
911
  */
918
- module.exports = (options) => new Bmw(options);
912
+ module.exports = options => new Bmw(options);
919
913
  } else {
920
914
  // otherwise start the instance directly
921
915
  new Bmw();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.bmw",
3
- "version": "4.0.2",
3
+ "version": "4.0.3",
4
4
  "description": "Adapter for BMW",
5
5
  "author": {
6
6
  "name": "TA2k",