iobroker.bmw 4.0.2 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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.4 (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.4",
5
5
  "news": {
6
- "4.0.2": {
6
+ "4.0.4": {
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 {
@@ -175,7 +176,7 @@ class Bmw extends utils.Adapter {
175
176
  code_challenge: codeChallenge,
176
177
  code_challenge_method: 'S256',
177
178
  };
178
- this.log.debug('Device code request data: ' + JSON.stringify(requestData));
179
+ this.log.debug(`Device code request data: ${JSON.stringify(requestData)}`);
179
180
 
180
181
  const deviceResponse = await this.requestClient({
181
182
  method: 'post',
@@ -187,16 +188,16 @@ class Bmw extends utils.Adapter {
187
188
  data: requestData,
188
189
  })
189
190
  .then((res) => {
190
- this.log.debug('Device code response: ' + JSON.stringify(res.data));
191
+ this.log.debug(`Device code response: ${JSON.stringify(res.data)}`);
191
192
  return res;
192
193
  })
193
194
  .catch((error) => {
194
- this.log.error('Device code request failed: ' + error.message);
195
- this.log.error('Error stack: ' + error.stack);
195
+ this.log.error(`Device code request failed: ${error.message}`);
196
+ this.log.error(`Error stack: ${error.stack}`);
196
197
  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));
198
+ this.log.error(`Response status: ${error.response.status}`);
199
+ this.log.error(`Response headers: ${JSON.stringify(error.response.headers)}`);
200
+ this.log.error(`Response data: ${JSON.stringify(error.response.data)}`);
200
201
 
201
202
  // Special handling for 400 Bad Request - likely client configuration issue
202
203
  if (error.response.status === 400) {
@@ -220,12 +221,11 @@ class Bmw extends utils.Adapter {
220
221
  }
221
222
  if (error.request) {
222
223
  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
- })
224
+ `Request details: ${JSON.stringify({
225
+ method: error.request.method,
226
+ url: error.request.url,
227
+ headers: error.request._headers,
228
+ })}`
229
229
  );
230
230
  }
231
231
  return false; // Return false instead of throwing
@@ -263,7 +263,7 @@ class Bmw extends utils.Adapter {
263
263
  device_code: device_code,
264
264
  code_verifier: codeVerifier,
265
265
  };
266
- this.log.debug('Token request data: ' + JSON.stringify(tokenRequestData));
266
+ this.log.debug(`Token request data: ${JSON.stringify(tokenRequestData)}`);
267
267
 
268
268
  const tokenResponse = await this.requestClient({
269
269
  method: 'post',
@@ -302,7 +302,7 @@ class Bmw extends utils.Adapter {
302
302
  return true;
303
303
  } catch (error) {
304
304
  const errorCode = error.response?.data?.error;
305
- this.log.debug('Token polling error: ' + (errorCode || error.message));
305
+ this.log.debug(`Token polling error: ${errorCode || error.message}`);
306
306
 
307
307
  if (errorCode === 'authorization_pending') {
308
308
  this.log.debug('Authorization still pending, continuing to poll...');
@@ -315,10 +315,10 @@ class Bmw extends utils.Adapter {
315
315
  this.log.error('Authorization code expired, please restart adapter');
316
316
  return false;
317
317
  } else {
318
- this.log.error('Token request failed: ' + (errorCode || error.message));
318
+ this.log.error(`Token request failed: ${errorCode || error.message}`);
319
319
  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));
320
+ this.log.error(`Token response status: ${error.response.status}`);
321
+ this.log.error(`Token response data: ${JSON.stringify(error.response.data)}`);
322
322
  }
323
323
  return false;
324
324
  }
@@ -373,7 +373,7 @@ class Bmw extends utils.Adapter {
373
373
  }
374
374
 
375
375
  for (const mapping of mappings) {
376
- if (mapping.mappingType === 'PRIMARY' && mapping.vin) {
376
+ if (mapping.vin) {
377
377
  const vin = mapping.vin;
378
378
 
379
379
  // Check ignore list
@@ -407,9 +407,9 @@ class Bmw extends utils.Adapter {
407
407
  }
408
408
  })
409
409
  .catch((error) => {
410
- this.log.error('BMW CarData vehicle discovery failed: ' + error.message);
410
+ this.log.error(`BMW CarData vehicle discovery failed: ${error.message}`);
411
411
  if (error.response) {
412
- this.log.error('Response: ' + JSON.stringify(error.response.data));
412
+ this.log.error(`Response: ${JSON.stringify(error.response.data)}`);
413
413
  if (error.response.status === 403 || error.response.status === 429) {
414
414
  this.log.warn('Rate limit exceeded or access denied');
415
415
  }
@@ -438,7 +438,7 @@ class Bmw extends utils.Adapter {
438
438
  native: {},
439
439
  });
440
440
 
441
- // Define available API endpoints with config mapping
441
+ // Define available API endpoints with config mapping (BMW CarData API v1)
442
442
  const apiEndpoints = [
443
443
  {
444
444
  name: 'basicData',
@@ -453,46 +453,28 @@ class Bmw extends utils.Adapter {
453
453
  channel: 'Charging History',
454
454
  },
455
455
  {
456
- name: 'chargingProfile',
457
- configKey: 'fetchChargingProfile',
458
- url: `/customers/vehicles/${vin}/chargingProfile`,
459
- channel: 'Charging Profile',
456
+ name: 'image',
457
+ configKey: 'fetchImage',
458
+ url: `/customers/vehicles/${vin}/image`,
459
+ channel: 'Vehicle Image',
460
460
  },
461
461
  {
462
- name: 'chargingSessions',
463
- configKey: 'fetchChargingSessions',
464
- url: `/customers/vehicles/${vin}/chargingSessions`,
465
- channel: 'Charging Sessions',
462
+ name: 'locationBasedChargingSettings',
463
+ configKey: 'fetchLocationBasedChargingSettings',
464
+ url: `/customers/vehicles/${vin}/locationBasedChargingSettings`,
465
+ channel: 'Location Based Charging Settings',
466
466
  },
467
467
  {
468
- name: 'climateNow',
469
- configKey: 'fetchClimateNow',
470
- url: `/customers/vehicles/${vin}/climateNow`,
471
- channel: 'Climate Control',
468
+ name: 'smartMaintenanceTyreDiagnosis',
469
+ configKey: 'fetchSmartMaintenanceTyreDiagnosis',
470
+ url: `/customers/vehicles/${vin}/smartMaintenanceTyreDiagnosis`,
471
+ channel: 'Smart Maintenance Tyre Diagnosis',
472
472
  },
473
473
  {
474
- name: 'destinationInformation',
475
- configKey: 'fetchDestinationInformation',
476
- url: `/customers/vehicles/${vin}/destinationInformation`,
477
- channel: 'Destination Information',
478
- },
479
- {
480
- name: 'location',
481
- configKey: 'fetchLocation',
482
- url: `/customers/vehicles/${vin}/location`,
483
- channel: 'Vehicle Location',
484
- },
485
- {
486
- name: 'statistics',
487
- configKey: 'fetchStatistics',
488
- url: `/customers/vehicles/${vin}/statistics`,
489
- channel: 'Statistics',
490
- },
491
- {
492
- name: 'vehicleState',
493
- configKey: 'fetchVehicleState',
494
- url: `/customers/vehicles/${vin}/vehicleState`,
495
- channel: 'Vehicle State',
474
+ name: 'telematicData',
475
+ configKey: 'fetchTelematicData',
476
+ url: `/customers/vehicles/${vin}/telematicData`,
477
+ channel: 'Telematic Data',
496
478
  },
497
479
  ];
498
480
 
@@ -572,7 +554,9 @@ class Bmw extends utils.Adapter {
572
554
 
573
555
  updateQuotaStates() {
574
556
  const now = Date.now();
575
- if (!this.apiCalls) this.apiCalls = [];
557
+ if (!this.apiCalls) {
558
+ this.apiCalls = [];
559
+ }
576
560
 
577
561
  // Remove calls older than 24h
578
562
  const originalLength = this.apiCalls.length;
@@ -676,7 +660,7 @@ class Bmw extends utils.Adapter {
676
660
  refresh_token: this.session.refresh_token,
677
661
  client_id: this.config.clientId,
678
662
  };
679
- this.log.debug('Refresh request data: ' + JSON.stringify(refreshData));
663
+ this.log.debug(`Refresh request data: ${JSON.stringify(refreshData)}`);
680
664
 
681
665
  await this.requestClient({
682
666
  method: 'post',
@@ -746,7 +730,7 @@ class Bmw extends utils.Adapter {
746
730
  keepalive: 30,
747
731
  clean: true,
748
732
  rejectUnauthorized: true,
749
- reconnectPeriod: 5000,
733
+ reconnectPeriod: 30000, // Increased from 5000ms to 30000ms (30 seconds)
750
734
  connectTimeout: 30000,
751
735
  };
752
736
 
@@ -762,7 +746,7 @@ class Bmw extends utils.Adapter {
762
746
  const topic = `${this.config.cardataStreamingUsername}/+`;
763
747
  this.mqtt.subscribe(topic, (err) => {
764
748
  if (err) {
765
- this.log.error('MQTT subscription failed: ' + err.message);
749
+ this.log.error(`MQTT subscription failed: ${err.message}`);
766
750
  } else {
767
751
  this.log.debug(`Subscribed to MQTT topic: ${topic}`);
768
752
  }
@@ -773,18 +757,46 @@ class Bmw extends utils.Adapter {
773
757
  this.handleMQTTMessage(topic, message);
774
758
  });
775
759
 
776
- this.mqtt.on('error', (error) => {
777
- this.log.error('MQTT error: ' + error.message);
760
+ this.mqtt.on('error', async (error) => {
761
+ this.log.error(`MQTT error: ${error.message}`);
778
762
  this.setState('info.mqttConnected', false, true);
763
+
764
+ // Check if it's an authentication error indicating expired token
765
+ if (
766
+ error.message &&
767
+ (error.message.includes('Bad username or password') ||
768
+ error.message.includes('Connection refused') ||
769
+ error.message.includes('Not authorized'))
770
+ ) {
771
+ this.log.warn('MQTT authentication failed - attempting token refresh');
772
+ try {
773
+ await this.refreshToken();
774
+ this.log.info('Token refreshed successfully');
775
+
776
+ // Close current MQTT connection and reconnect with new token
777
+ if (this.mqtt) {
778
+ this.mqtt.end(false); // Force close without waiting
779
+ }
780
+
781
+ // Reconnect with fresh credentials after a short delay
782
+ setTimeout(async () => {
783
+ this.log.debug('Reconnecting MQTT with refreshed token');
784
+ await this.connectMQTT();
785
+ }, 5000);
786
+ } catch (refreshError) {
787
+ this.log.error('Token refresh failed: ' + refreshError.message);
788
+ this.log.warn('Will retry MQTT connection with current token');
789
+ }
790
+ }
779
791
  });
780
792
 
781
793
  this.mqtt.on('close', () => {
782
- this.log.warn('MQTT connection closed');
794
+ this.log.info('MQTT connection closed');
783
795
  this.setState('info.mqttConnected', false, true);
784
796
  });
785
797
 
786
798
  this.mqtt.on('reconnect', () => {
787
- this.log.debug('MQTT reconnecting...');
799
+ this.log.info('MQTT reconnecting...');
788
800
  });
789
801
 
790
802
  return true;
@@ -797,7 +809,9 @@ class Bmw extends utils.Adapter {
797
809
  const topicParts = topic.split('/');
798
810
 
799
811
  if (topicParts.length >= 2) {
800
- const entityId = topicParts[0];
812
+ /*
813
+ 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"}}}
814
+ */
801
815
  const vin = topicParts[1];
802
816
 
803
817
  if (data.vin && data.data) {
@@ -860,7 +874,7 @@ class Bmw extends utils.Adapter {
860
874
  }
861
875
  }
862
876
  } catch (error) {
863
- this.log.warn('Failed to parse MQTT message: ' + error.message);
877
+ this.log.warn(`Failed to parse MQTT message: ${error.message}`);
864
878
  }
865
879
  }
866
880
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.bmw",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "description": "Adapter for BMW",
5
5
  "author": {
6
6
  "name": "TA2k",