iobroker.renault 0.0.24 → 0.0.25

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
@@ -9,6 +9,8 @@
9
9
 
10
10
  **Tests:** ![Test and Release](https://github.com/TA2k/ioBroker.renault/workflows/Test%20and%20Release/badge.svg)
11
11
 
12
+ **This adapter uses Sentry libraries to automatically report exceptions and code errors to the developers.** For more details and for information how to disable the error reporting see [Sentry-Plugin Documentation](https://github.com/ioBroker/plugin-sentry#plugin-sentry)! Sentry reporting is used starting with js-controller 3.0.
13
+
12
14
  ## Renault / Dacia / Alpine adapter for ioBroker
13
15
 
14
16
  This adapter connects ioBroker to the My Renault / My Dacia / My Alpine cloud and exposes vehicle status data (battery, charging, HVAC, mileage, ...) as well as remote commands (HVAC start, charging start/stop, force refresh) for compatible Renault, Dacia and Alpine models such as the Renault Zoe, Megane E-Tech, Kangoo E-Tech, the Dacia Spring and the Alpine A290.
@@ -48,6 +50,16 @@ ioBroker forum: <https://forum.iobroker.net/topic/48074/test-adapter-renault-v0-
48
50
  ### **WORK IN PROGRESS**
49
51
  -->
50
52
 
53
+ ### 0.0.25
54
+
55
+ - (typhosj) retry the connection with growing delay (5 to 60 minutes) when login or vehicle list fail at startup; a login rejected by the account service is not retried
56
+ - (typhosj) `info.connection` is true only after the account was found and turns false when the token refresh fails
57
+ - (typhosj) a temporary server error (5xx) on the first poll no longer disables that endpoint until restart
58
+ - (typhosj) fix crash for vehicles without vehicle details and for the refresh button before login
59
+ - (typhosj) no longer write password, session cookie, id token or account data into the log
60
+ - (typhosj) add missing admin translations, remove unused dependencies
61
+ - (typhosj) require Node.js 22 or newer, test with Node.js 26, update dependencies
62
+
51
63
  ### 0.0.24
52
64
 
53
65
  - (TA2k) add Alpine support: brand selection (Alpine accounts use the same Renault Gigya/Kamereon tenant, only product/account type MYALPINE differs)
@@ -12,5 +12,7 @@
12
12
  "API Key": "API-Schlüssel",
13
13
  "API Key (leave empty for auto-detect)": "API-Schlüssel (leer lassen für automatische Erkennung)",
14
14
  "If empty, the API key will be set automatically": "Wenn leer, wird der API-Schlüssel automatisch gesetzt",
15
- "Click here for current API Key": "Hier klicken für aktuellen API-Schlüssel"
15
+ "Click here for current API Key": "Hier klicken für aktuellen API-Schlüssel",
16
+ "Charge history entries to keep": "Aufzubewahrende Einträge der Ladehistorie",
17
+ "Number of most recent entries kept per charge-history endpoint (0 = unlimited)": "Anzahl der neuesten Einträge, die je Ladehistorie-Endpunkt behalten werden (0 = unbegrenzt)"
16
18
  }
@@ -12,5 +12,7 @@
12
12
  "API Key": "API Key",
13
13
  "API Key (leave empty for auto-detect)": "API Key (leave empty for auto-detect)",
14
14
  "If empty, the API key will be set automatically": "If empty, the API key will be set automatically",
15
- "Click here for current API Key": "Click here for current API Key"
15
+ "Click here for current API Key": "Click here for current API Key",
16
+ "Charge history entries to keep": "Charge history entries to keep",
17
+ "Number of most recent entries kept per charge-history endpoint (0 = unlimited)": "Number of most recent entries kept per charge-history endpoint (0 = unlimited)"
16
18
  }
@@ -3,6 +3,8 @@
3
3
  "App Email": "Correo electrónico de la app",
4
4
  "App Password": "Contraseña de la app",
5
5
  "Country": "País",
6
+ "Brand": "Marca",
7
+ "Renault/Dacia and Alpine use different accounts and API keys": "Renault/Dacia y Alpine usan cuentas y claves API diferentes",
6
8
  "Two-letter country code, e.g. de, fr, it, es": "Código de país de dos letras, p. ej. de, fr, it, es",
7
9
  "Polling": "Sondeo",
8
10
  "Update interval in minutes": "Intervalo de actualización en minutos",
@@ -10,5 +12,7 @@
10
12
  "API Key": "Clave API",
11
13
  "API Key (leave empty for auto-detect)": "Clave API (déjelo vacío para detección automática)",
12
14
  "If empty, the API key will be set automatically": "Si está vacío, la clave API se establecerá automáticamente",
13
- "Click here for current API Key": "Haga clic aquí para la clave API actual"
15
+ "Click here for current API Key": "Haga clic aquí para la clave API actual",
16
+ "Charge history entries to keep": "Entradas del historial de carga a conservar",
17
+ "Number of most recent entries kept per charge-history endpoint (0 = unlimited)": "Número de entradas más recientes que se conservan por endpoint del historial de carga (0 = ilimitado)"
14
18
  }
@@ -3,6 +3,8 @@
3
3
  "App Email": "Email de l'application",
4
4
  "App Password": "Mot de passe de l'application",
5
5
  "Country": "Pays",
6
+ "Brand": "Marque",
7
+ "Renault/Dacia and Alpine use different accounts and API keys": "Renault/Dacia et Alpine utilisent des comptes et des clés API différents",
6
8
  "Two-letter country code, e.g. de, fr, it, es": "Code pays à deux lettres, par exemple de, fr, it, es",
7
9
  "Polling": "Interrogation",
8
10
  "Update interval in minutes": "Intervalle de mise à jour en minutes",
@@ -10,5 +12,7 @@
10
12
  "API Key": "Clé API",
11
13
  "API Key (leave empty for auto-detect)": "Clé API (laisser vide pour détection automatique)",
12
14
  "If empty, the API key will be set automatically": "Si vide, la clé API sera définie automatiquement",
13
- "Click here for current API Key": "Cliquez ici pour la clé API actuelle"
15
+ "Click here for current API Key": "Cliquez ici pour la clé API actuelle",
16
+ "Charge history entries to keep": "Entrées de l'historique de charge à conserver",
17
+ "Number of most recent entries kept per charge-history endpoint (0 = unlimited)": "Nombre d'entrées les plus récentes conservées par point d'accès de l'historique de charge (0 = illimité)"
14
18
  }
@@ -3,6 +3,8 @@
3
3
  "App Email": "Email dell'app",
4
4
  "App Password": "Password dell'app",
5
5
  "Country": "Paese",
6
+ "Brand": "Marchio",
7
+ "Renault/Dacia and Alpine use different accounts and API keys": "Renault/Dacia e Alpine usano account e chiavi API diversi",
6
8
  "Two-letter country code, e.g. de, fr, it, es": "Codice paese di due lettere, ad esempio de, fr, it, es",
7
9
  "Polling": "Polling",
8
10
  "Update interval in minutes": "Intervallo di aggiornamento in minuti",
@@ -10,5 +12,7 @@
10
12
  "API Key": "Chiave API",
11
13
  "API Key (leave empty for auto-detect)": "Chiave API (lascia vuoto per il rilevamento automatico)",
12
14
  "If empty, the API key will be set automatically": "Se vuoto, la chiave API verrà impostata automaticamente",
13
- "Click here for current API Key": "Fai clic qui per la chiave API attuale"
15
+ "Click here for current API Key": "Fai clic qui per la chiave API attuale",
16
+ "Charge history entries to keep": "Voci della cronologia di ricarica da conservare",
17
+ "Number of most recent entries kept per charge-history endpoint (0 = unlimited)": "Numero di voci più recenti conservate per ogni endpoint della cronologia di ricarica (0 = illimitato)"
14
18
  }
@@ -3,6 +3,8 @@
3
3
  "App Email": "App E-mail",
4
4
  "App Password": "App Wachtwoord",
5
5
  "Country": "Land",
6
+ "Brand": "Merk",
7
+ "Renault/Dacia and Alpine use different accounts and API keys": "Renault/Dacia en Alpine gebruiken verschillende accounts en API-sleutels",
6
8
  "Two-letter country code, e.g. de, fr, it, es": "Tweeletterige landcode, bijv. de, fr, it, es",
7
9
  "Polling": "Polling",
8
10
  "Update interval in minutes": "Update interval in minuten",
@@ -10,5 +12,7 @@
10
12
  "API Key": "API-sleutel",
11
13
  "API Key (leave empty for auto-detect)": "API-sleutel (laat leeg voor auto-detectie)",
12
14
  "If empty, the API key will be set automatically": "Indien leeg, wordt de API-sleutel automatisch ingesteld",
13
- "Click here for current API Key": "Klik hier voor de huidige API-sleutel"
15
+ "Click here for current API Key": "Klik hier voor de huidige API-sleutel",
16
+ "Charge history entries to keep": "Te bewaren items van de laadgeschiedenis",
17
+ "Number of most recent entries kept per charge-history endpoint (0 = unlimited)": "Aantal meest recente items dat per laadgeschiedenis-endpoint wordt bewaard (0 = onbeperkt)"
14
18
  }
@@ -3,6 +3,8 @@
3
3
  "App Email": "Email aplikacji",
4
4
  "App Password": "Hasło aplikacji",
5
5
  "Country": "Kraj",
6
+ "Brand": "Marka",
7
+ "Renault/Dacia and Alpine use different accounts and API keys": "Renault/Dacia i Alpine używają różnych kont i kluczy API",
6
8
  "Two-letter country code, e.g. de, fr, it, es": "Dwuliterowy kod kraju, np. de, fr, it, es",
7
9
  "Polling": "Odpytywanie",
8
10
  "Update interval in minutes": "Interwał aktualizacji w minutach",
@@ -10,5 +12,7 @@
10
12
  "API Key": "Klucz API",
11
13
  "API Key (leave empty for auto-detect)": "Klucz API (pozostaw puste, aby wykryć automatycznie)",
12
14
  "If empty, the API key will be set automatically": "Jeśli puste, klucz API zostanie ustawiony automatycznie",
13
- "Click here for current API Key": "Kliknij tutaj, aby uzyskać aktualny klucz API"
15
+ "Click here for current API Key": "Kliknij tutaj, aby uzyskać aktualny klucz API",
16
+ "Charge history entries to keep": "Liczba zachowywanych wpisów historii ładowania",
17
+ "Number of most recent entries kept per charge-history endpoint (0 = unlimited)": "Liczba najnowszych wpisów zachowywanych dla każdego endpointu historii ładowania (0 = bez limitu)"
14
18
  }
@@ -3,6 +3,8 @@
3
3
  "App Email": "Email do aplicativo",
4
4
  "App Password": "Senha do aplicativo",
5
5
  "Country": "País",
6
+ "Brand": "Marca",
7
+ "Renault/Dacia and Alpine use different accounts and API keys": "Renault/Dacia e Alpine utilizam contas e chaves de API diferentes",
6
8
  "Two-letter country code, e.g. de, fr, it, es": "Código de país de duas letras, por exemplo de, fr, it, es",
7
9
  "Polling": "Sondagem",
8
10
  "Update interval in minutes": "Intervalo de atualização em minutos",
@@ -10,5 +12,7 @@
10
12
  "API Key": "Chave API",
11
13
  "API Key (leave empty for auto-detect)": "Chave API (deixe em branco para detecção automática)",
12
14
  "If empty, the API key will be set automatically": "Se vazio, a chave API será definida automaticamente",
13
- "Click here for current API Key": "Clique aqui para a chave API atual"
15
+ "Click here for current API Key": "Clique aqui para a chave API atual",
16
+ "Charge history entries to keep": "Entradas do histórico de carregamento a manter",
17
+ "Number of most recent entries kept per charge-history endpoint (0 = unlimited)": "Número de entradas mais recentes mantidas por endpoint do histórico de carregamento (0 = ilimitado)"
14
18
  }
@@ -3,6 +3,8 @@
3
3
  "App Email": "Электронная почта приложения",
4
4
  "App Password": "Пароль приложения",
5
5
  "Country": "Страна",
6
+ "Brand": "Марка",
7
+ "Renault/Dacia and Alpine use different accounts and API keys": "Renault/Dacia и Alpine используют разные учётные записи и API-ключи",
6
8
  "Two-letter country code, e.g. de, fr, it, es": "Двухбуквенный код страны, например de, fr, it, es",
7
9
  "Polling": "Опрос",
8
10
  "Update interval in minutes": "Интервал обновления в минутах",
@@ -10,5 +12,7 @@
10
12
  "API Key": "API ключ",
11
13
  "API Key (leave empty for auto-detect)": "API ключ (оставьте пустым для автоопределения)",
12
14
  "If empty, the API key will be set automatically": "Если пусто, API ключ будет установлен автоматически",
13
- "Click here for current API Key": "Нажмите здесь для получения текущего API ключа"
15
+ "Click here for current API Key": "Нажмите здесь для получения текущего API ключа",
16
+ "Charge history entries to keep": "Сохраняемые записи истории зарядки",
17
+ "Number of most recent entries kept per charge-history endpoint (0 = unlimited)": "Количество последних записей, сохраняемых для каждого эндпоинта истории зарядки (0 = без ограничений)"
14
18
  }
@@ -3,6 +3,8 @@
3
3
  "App Email": "Електронна пошта застосунку",
4
4
  "App Password": "Пароль застосунку",
5
5
  "Country": "Країна",
6
+ "Brand": "Марка",
7
+ "Renault/Dacia and Alpine use different accounts and API keys": "Renault/Dacia та Alpine використовують різні облікові записи та API-ключі",
6
8
  "Two-letter country code, e.g. de, fr, it, es": "Дволітерний код країни, наприклад de, fr, it, es",
7
9
  "Polling": "Опитування",
8
10
  "Update interval in minutes": "Інтервал оновлення в хвилинах",
@@ -10,5 +12,7 @@
10
12
  "API Key": "API ключ",
11
13
  "API Key (leave empty for auto-detect)": "API ключ (залиште порожнім для автовизначення)",
12
14
  "If empty, the API key will be set automatically": "Якщо порожньо, API ключ буде встановлено автоматично",
13
- "Click here for current API Key": "Натисніть тут для отримання поточного API ключа"
15
+ "Click here for current API Key": "Натисніть тут для отримання поточного API ключа",
16
+ "Charge history entries to keep": "Записи історії заряджання, що зберігаються",
17
+ "Number of most recent entries kept per charge-history endpoint (0 = unlimited)": "Кількість останніх записів, що зберігаються для кожного ендпоінта історії заряджання (0 = без обмежень)"
14
18
  }
@@ -3,6 +3,8 @@
3
3
  "App Email": "应用邮箱",
4
4
  "App Password": "应用密码",
5
5
  "Country": "国家",
6
+ "Brand": "品牌",
7
+ "Renault/Dacia and Alpine use different accounts and API keys": "Renault/Dacia 和 Alpine 使用不同的账户和 API 密钥",
6
8
  "Two-letter country code, e.g. de, fr, it, es": "两位字母的国家代码,例如 de, fr, it, es",
7
9
  "Polling": "轮询",
8
10
  "Update interval in minutes": "更新间隔(分钟)",
@@ -10,5 +12,7 @@
10
12
  "API Key": "API 密钥",
11
13
  "API Key (leave empty for auto-detect)": "API 密钥(留空自动检测)",
12
14
  "If empty, the API key will be set automatically": "如果为空,API 密钥将自动设置",
13
- "Click here for current API Key": "点击此处获取当前 API 密钥"
15
+ "Click here for current API Key": "点击此处获取当前 API 密钥",
16
+ "Charge history entries to keep": "保留的充电历史记录条数",
17
+ "Number of most recent entries kept per charge-history endpoint (0 = unlimited)": "每个充电历史接口保留的最新记录条数(0 = 不限制)"
14
18
  }
package/io-package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "renault",
4
- "version": "0.0.24",
4
+ "version": "0.0.25",
5
5
  "news": {
6
+ "0.0.25": {
7
+ "en": "more robust startup with retry, truthful connection state, no secrets in the log, require Node.js 22+",
8
+ "de": "robusterer Start mit Wiederholung, ehrlicher Verbindungsstatus, keine Geheimnisse im Log, benötigt Node.js 22+",
9
+ "ru": "более надёжный запуск с повтором, честный статус соединения, без секретов в логе, требуется Node.js 22+",
10
+ "pt": "arranque mais robusto com nova tentativa, estado de ligação fiável, sem segredos no registo, requer Node.js 22+",
11
+ "nl": "robuustere start met herhaling, eerlijke verbindingsstatus, geen geheimen in het logboek, vereist Node.js 22+",
12
+ "fr": "démarrage plus robuste avec nouvelle tentative, état de connexion fiable, aucun secret dans le journal, nécessite Node.js 22+",
13
+ "it": "avvio più robusto con ripetizione, stato di connessione affidabile, nessun segreto nel log, richiede Node.js 22+",
14
+ "es": "inicio más robusto con reintento, estado de conexión fiable, sin secretos en el registro, requiere Node.js 22+",
15
+ "pl": "solidniejszy start z ponawianiem, prawdziwy status połączenia, bez sekretów w logu, wymaga Node.js 22+",
16
+ "uk": "надійніший запуск із повтором, чесний статус з'єднання, без секретів у журналі, потрібен Node.js 22+",
17
+ "zh-cn": "启动更稳健并带重试、连接状态真实、日志中不含密钥、需要 Node.js 22+"
18
+ },
6
19
  "0.0.24": {
7
20
  "en": "add Alpine support (select brand Alpine in the settings)",
8
21
  "de": "Alpine-Unterstützung hinzugefügt (Marke Alpine in den Einstellungen wählen)",
@@ -80,32 +93,6 @@
80
93
  "pl": "Naprawiono start ładowania i zmniejszono logi błędów",
81
94
  "uk": "Виправлено старт зарядки та зменшено журнали помилок",
82
95
  "zh-cn": "修复充电启动并减少错误日志"
83
- },
84
- "0.0.14": {
85
- "en": "add charging stop",
86
- "de": "Ladestopp hinzugefügt",
87
- "ru": "Добавлена остановка зарядки",
88
- "pt": "Adicionada paragem de carga",
89
- "nl": "Stoppen van het laden toegevoegd",
90
- "fr": "Ajout de l'arrêt de la charge",
91
- "it": "Aggiunto stop della carica",
92
- "es": "Añadida parada de carga",
93
- "pl": "Dodano zatrzymanie ładowania",
94
- "uk": "Додано зупинку зарядки",
95
- "zh-cn": "添加充电停止"
96
- },
97
- "0.0.13": {
98
- "en": "add remote object for force refresh",
99
- "de": "Remote-Objekt für erzwungene Aktualisierung hinzugefügt",
100
- "ru": "Добавлен удалённый объект для принудительного обновления",
101
- "pt": "Adicionado objeto remoto para forçar atualização",
102
- "nl": "Remote-object voor geforceerde verversing toegevoegd",
103
- "fr": "Ajout d'un objet distant pour le rafraîchissement forcé",
104
- "it": "Aggiunto oggetto remoto per il refresh forzato",
105
- "es": "Añadido objeto remoto para actualización forzada",
106
- "pl": "Dodano obiekt zdalny do wymuszonego odświeżenia",
107
- "uk": "Додано віддалений об'єкт для примусового оновлення",
108
- "zh-cn": "添加用于强制刷新的 remote 对象"
109
96
  }
110
97
  },
111
98
  "titleLang": {
package/main.js CHANGED
@@ -29,6 +29,14 @@ class Renault extends utils.Adapter {
29
29
  this.firstUpdate = true;
30
30
  this.requestClient = axios.create();
31
31
  this.userAgent = 'okhttp/5.3.0';
32
+ /** @type {string} */
33
+ this.brand = 'renault';
34
+ /** @type {string[]} */
35
+ this.accountTypes = [];
36
+ this.updateInterval = null;
37
+ this.refreshTokenInterval = null;
38
+ this.startAttempt = 0;
39
+ this.loginRejected = false;
32
40
  }
33
41
 
34
42
  /** APK rI2.smali (WiredHeaderAppVersionInterceptor): build={brand}-android-{version};trId={uuid} on wired Kamereon host */
@@ -38,7 +46,7 @@ class Renault extends utils.Adapter {
38
46
  const v = c === 'x' ? r : (r & 0x3) | 0x8;
39
47
  return v.toString(16);
40
48
  });
41
- const brand = this.brand || 'renault';
49
+ const brand = this.brand;
42
50
  const version = brand === 'alpine' ? '6.10.1' : '6.11.2';
43
51
  return 'build=' + brand + '-android-' + version + ';trId=' + uuid;
44
52
  }
@@ -53,7 +61,6 @@ class Renault extends utils.Adapter {
53
61
  this.log.info('Set interval to minimum 0.5');
54
62
  this.config.interval = 0.5;
55
63
  }
56
- this.updateInterval = null;
57
64
  this.reLoginTimeout = null;
58
65
  this.refreshTokenTimeout = null;
59
66
  this.country = this.config.country || 'de';
@@ -76,17 +83,15 @@ class Renault extends utils.Adapter {
76
83
  })
77
84
 
78
85
  .then((res) => {
79
- this.log.debug(JSON.stringify(res.data));
80
-
81
86
  if (res.data.split('KAMEREON_APIKEY = "')[2] && res.data.split('KAMEREON_APIKEY = "')[2].split('"')[0]) {
82
87
  this.apiKeyUpdate = res.data.split('KAMEREON_APIKEY = "')[2].split('"')[0];
83
88
  }
84
89
  })
85
90
  .catch((error) => {
86
- this.log.debug(error);
91
+ this.log.debug('API key lookup failed: ' + error);
87
92
  });
88
93
  } catch (error) {
89
- this.log.debug(error);
94
+ this.log.debug('API key lookup failed: ' + error);
90
95
  }
91
96
  if (this.config.apiKeyUpdate) {
92
97
  this.apiKeyUpdate = this.config.apiKeyUpdate;
@@ -94,10 +99,16 @@ class Renault extends utils.Adapter {
94
99
 
95
100
  this.subscribeStates('*');
96
101
 
97
- await this.login();
102
+ await this.connectAndPoll();
103
+ }
98
104
 
99
- if (this.session.id_token && this.session_data && this.account) {
100
- await this.getDeviceList();
105
+ /**
106
+ * Log in, load the vehicles and start polling. A network or server failure is retried with
107
+ * growing delay (5 min doubling up to 60 min). A login the account service rejected is not
108
+ * retried, so wrong credentials cannot lock the account.
109
+ */
110
+ async connectAndPoll() {
111
+ if ((await this.login()) && (await this.getDeviceList())) {
101
112
  await this.migrateChargeHistoryV1();
102
113
  await this.updateDevices();
103
114
  this.updateInterval = setInterval(
@@ -109,9 +120,33 @@ class Renault extends utils.Adapter {
109
120
  this.refreshTokenInterval = setInterval(() => {
110
121
  this.refreshToken();
111
122
  }, 3500 * 1000);
123
+ return;
112
124
  }
125
+ if (this.loginRejected) {
126
+ this.log.error('Login rejected. Check email and password in the adapter settings, then restart the instance.');
127
+ return;
128
+ }
129
+ const delayMinutes = Math.min(5 * 2 ** this.startAttempt, 60);
130
+ this.startAttempt++;
131
+ this.log.warn('Connection to the Renault cloud failed. Next attempt in ' + delayMinutes + ' minutes');
132
+ this.setTimeout(
133
+ () => this.connectAndPoll().catch((error) => this.log.error('Connection attempt failed: ' + error)),
134
+ delayMinutes * 60 * 1000,
135
+ );
113
136
  }
137
+
138
+ /**
139
+ * Log in and look up the account. Sets info.connection accordingly.
140
+ *
141
+ * @returns {Promise<boolean>} true when session, id token and account are available
142
+ */
114
143
  async login() {
144
+ const ok = await this.loginSteps();
145
+ this.setState('info.connection', ok, true);
146
+ return ok;
147
+ }
148
+
149
+ async loginSteps() {
115
150
  this.session_data = await this.requestClient({
116
151
  method: 'post',
117
152
  url: 'https://accounts.eu1.gigya.com/accounts.login',
@@ -131,23 +166,24 @@ class Renault extends utils.Adapter {
131
166
  }),
132
167
  })
133
168
  .then((res) => {
134
- this.log.debug(JSON.stringify(res.data));
135
169
  if (res.data.errorMessage) {
170
+ this.loginRejected = true;
136
171
  this.log.error(JSON.stringify(res.data));
137
172
  return;
138
173
  }
139
174
  return res.data.sessionInfo;
140
175
  })
141
176
  .catch((error) => {
142
- this.log.error(error);
177
+ this.log.error('Login failed: ' + error);
143
178
  if (error.response) {
144
179
  this.log.error(JSON.stringify(error.response.data));
145
180
  }
146
181
  });
147
182
  if (!this.session_data) {
148
183
  this.log.error('No session found for this account. Please login in the app. Maybe a new password is needed.');
149
- return;
184
+ return false;
150
185
  }
186
+ this.session = {};
151
187
  await this.requestClient({
152
188
  method: 'post',
153
189
  url: 'https://accounts.eu1.gigya.com/accounts.getJWT',
@@ -168,17 +204,18 @@ class Renault extends utils.Adapter {
168
204
  }),
169
205
  })
170
206
  .then((res) => {
171
- this.log.debug(JSON.stringify(res.data));
172
207
  this.session = res.data;
173
- this.setState('info.connection', true, true);
174
208
  })
175
209
  .catch((error) => {
176
- this.log.error(error);
210
+ this.log.error('Getting the id token failed: ' + error);
177
211
  if (error.response) {
178
212
  this.log.error(JSON.stringify(error.response.data));
179
213
  }
180
214
  });
181
- await this.requestClient({
215
+ if (!this.session.id_token) {
216
+ return false;
217
+ }
218
+ return await this.requestClient({
182
219
  method: 'post',
183
220
  url:
184
221
  'https://apis.renault.com/myr/api/v1/connection?&country=DE&product=' +
@@ -195,34 +232,41 @@ class Renault extends utils.Adapter {
195
232
  },
196
233
  })
197
234
  .then((res) => {
198
- this.log.debug(JSON.stringify(res.data));
199
-
200
235
  const accountTypes = this.accountTypes;
201
236
  const filteredAccounts = res.data.currentUser.accounts.filter(function (el) {
202
237
  return accountTypes.includes(el.accountType) && el.accountStatus === 'ACTIVE';
203
238
  });
204
239
  if (filteredAccounts.length === 0) {
205
240
  this.log.error('No Account found');
206
- this.log.error('All accounts: ' + res.data.currentUser.accounts);
207
- this.log.error('Filtered accounts: ' + filteredAccounts);
208
- return;
241
+ this.log.error(
242
+ 'Accounts of this login: ' +
243
+ JSON.stringify(res.data.currentUser.accounts.map((el) => el.accountType + ' ' + el.accountStatus)),
244
+ );
245
+ return false;
209
246
  }
210
247
 
211
248
  this.account = filteredAccounts[0];
249
+ return true;
212
250
  })
213
251
  .catch((error) => {
214
- this.log.error('Error while getting account');
215
- this.log.error(error);
252
+ this.log.error('Error while getting account: ' + error);
216
253
  if (error.response) {
217
254
  this.log.error(JSON.stringify(error.response.data));
218
255
  if (error.response.data && JSON.stringify(error.response.data).indexOf('apikey') !== -1) {
219
256
  this.log.error('Wrong API Key. Please update API Key in adapter settings');
220
257
  }
221
258
  }
259
+ return false;
222
260
  });
223
261
  }
262
+
263
+ /**
264
+ * Load the vehicles of the account and create their objects.
265
+ *
266
+ * @returns {Promise<boolean>} true when the vehicle list was loaded
267
+ */
224
268
  async getDeviceList() {
225
- await this.requestClient({
269
+ return await this.requestClient({
226
270
  method: 'get',
227
271
  url:
228
272
  'https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/' +
@@ -243,10 +287,11 @@ class Renault extends utils.Adapter {
243
287
  .then(async (res) => {
244
288
  this.log.debug(JSON.stringify(res.data));
245
289
 
290
+ this.deviceArray = [];
246
291
  for (const device of res.data.vehicleLinks) {
247
292
  this.deviceArray.push(device.vin);
248
293
  let name = device.vehicleDetails?.modelSCR || device.brand;
249
- if (device.vehicleDetails.model && device.vehicleDetails.model.label) {
294
+ if (device.vehicleDetails?.model?.label) {
250
295
  name += device.vehicleDetails.model.label;
251
296
  }
252
297
 
@@ -302,11 +347,12 @@ class Renault extends utils.Adapter {
302
347
  delete device.mileage;
303
348
  this.json2iob.parse(device.vin + '.general', device);
304
349
  }
350
+ return true;
305
351
  })
306
352
  .catch((error) => {
307
- this.log.error('Error while getting vehicle list');
308
- this.log.error(error);
353
+ this.log.error('Error while getting vehicle list: ' + error);
309
354
  error.response && this.log.error(JSON.stringify(error.response.data));
355
+ return false;
310
356
  });
311
357
  }
312
358
 
@@ -336,7 +382,7 @@ class Renault extends utils.Adapter {
336
382
  }
337
383
 
338
384
  async updateDevices() {
339
- if (!this.account.accountId) {
385
+ if (!this.account?.accountId) {
340
386
  this.log.error('No accountId found');
341
387
  return;
342
388
  }
@@ -546,20 +592,13 @@ class Renault extends utils.Adapter {
546
592
  return;
547
593
  }
548
594
  if (this.firstUpdate) {
549
- if (
550
- error.response.status === 403 ||
551
- error.response.status === 404 ||
552
- error.response.status === 500 ||
553
- error.response.status === 501 ||
554
- error.response.status === 502 ||
555
- error.response.status === 400
556
- ) {
595
+ if (error.response.status === 400 || error.response.status === 403 || error.response.status === 404) {
557
596
  if (!this.ignoreState[vin]) {
558
597
  this.ignoreState[vin] = [];
559
598
  }
560
599
  this.ignoreState[vin].push(element.path);
561
600
  this.log.info('Feature not found for ' + vin + '. Ignore ' + element.path + ' for updates.');
562
- this.log.debug(error);
601
+ this.log.debug(String(error));
563
602
  error.response && this.log.debug(JSON.stringify(error.response.data));
564
603
  return;
565
604
  }
@@ -570,7 +609,7 @@ class Renault extends utils.Adapter {
570
609
  return;
571
610
  }
572
611
  this.log.error(url);
573
- this.log.error(error);
612
+ this.log.error(String(error));
574
613
  error.response && this.log.error(JSON.stringify(error.response.data));
575
614
  });
576
615
  }
@@ -603,13 +642,12 @@ class Renault extends utils.Adapter {
603
642
  }),
604
643
  })
605
644
  .then((res) => {
606
- this.log.debug(JSON.stringify(res.data));
607
645
  this.session = res.data;
608
646
  this.setState('info.connection', true, true);
609
647
  })
610
648
  .catch((error) => {
611
- this.log.error('refresh token failed');
612
- this.log.error(error);
649
+ this.setState('info.connection', false, true);
650
+ this.log.error('refresh token failed: ' + error);
613
651
  error.response && this.log.error(JSON.stringify(error.response.data));
614
652
  this.log.error('Start relogin in 1min');
615
653
  this.reLoginTimeout = setTimeout(
@@ -620,9 +658,6 @@ class Renault extends utils.Adapter {
620
658
  );
621
659
  });
622
660
  }
623
- sleep(ms) {
624
- return new Promise((resolve) => setTimeout(resolve, ms));
625
- }
626
661
  toCamelCase(string) {
627
662
  if (!string) {
628
663
  return;
@@ -645,7 +680,7 @@ class Renault extends utils.Adapter {
645
680
  this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
646
681
  this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout);
647
682
  this.updateInterval && clearInterval(this.updateInterval);
648
- clearInterval(this.refreshTokenInterval);
683
+ this.refreshTokenInterval && clearInterval(this.refreshTokenInterval);
649
684
  callback();
650
685
  } catch (e) {
651
686
  this.log.error('Error onUnload: ' + e);
@@ -666,15 +701,15 @@ class Renault extends utils.Adapter {
666
701
  if (path === 'hvac-temperature') {
667
702
  return;
668
703
  }
704
+ if (!this.account) {
705
+ this.log.error('No account found');
706
+ return;
707
+ }
669
708
  if (path === 'refresh') {
670
709
  this.log.info('Force refresh');
671
710
  this.updateDevices();
672
711
  return;
673
712
  }
674
- if (!this.account) {
675
- this.log.error('No account found');
676
- return;
677
- }
678
713
  const command = path.split('/')[1];
679
714
  let action = state.val ? 'start' : 'cancel';
680
715
  let midPart = 'kca/car-adapter/v1/cars/';
@@ -722,7 +757,7 @@ class Renault extends utils.Adapter {
722
757
  return res.data;
723
758
  })
724
759
  .catch((error) => {
725
- this.log.error(error);
760
+ this.log.error('Command ' + path + ' failed: ' + error);
726
761
  if (error.response) {
727
762
  this.log.error(JSON.stringify(error.response.data));
728
763
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iobroker.renault",
3
- "version": "0.0.24",
4
- "description": "Adapter for Renault and Renault",
3
+ "version": "0.0.25",
4
+ "description": "Adapter for Renault, Dacia and Alpine",
5
5
  "author": {
6
6
  "name": "TA2k",
7
7
  "email": "tombox2020@gmail.com"
@@ -26,32 +26,29 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@iobroker/adapter-core": "^3.4.3",
29
- "axios": "^1.20.0",
30
- "json-bigint": "^1.0.0",
31
- "json2iob": "^2.6.26",
32
- "qs": "^6.16.0"
29
+ "axios": "^1.16.1",
30
+ "json2iob": "^2.6.22",
31
+ "qs": "^6.15.2"
33
32
  },
34
33
  "devDependencies": {
35
34
  "@alcalzone/release-script": "^5.2.1",
36
35
  "@alcalzone/release-script-plugin-iobroker": "^5.2.0",
37
36
  "@alcalzone/release-script-plugin-license": "^5.2.2",
38
37
  "@alcalzone/release-script-plugin-manual-review": "^5.2.0",
39
- "@eslint/eslintrc": "^3.3.7",
38
+ "@eslint/eslintrc": "^3.3.5",
40
39
  "@eslint/js": "^10.0.1",
41
- "@iobroker/testing": "^5.3.0",
42
- "@tsconfig/node18": "^18.2.7",
43
- "@types/mocha": "^10.0.10",
44
- "@types/node": "^25.9.6",
45
- "eslint": "^10.10.0",
40
+ "@iobroker/testing": "^6.2.2",
41
+ "@types/node": "^22.20.3",
42
+ "eslint": "^10.4.0",
46
43
  "eslint-config-prettier": "^10.1.8",
47
- "eslint-plugin-prettier": "^5.5.6",
48
- "globals": "^17.12.0",
49
- "prettier": "^3.9.6",
44
+ "eslint-plugin-prettier": "^5.5.5",
45
+ "globals": "^17.6.0",
46
+ "prettier": "^3.8.3",
50
47
  "typescript": "~6.0.3"
51
48
  },
52
49
  "main": "main.js",
53
50
  "engines": {
54
- "node": ">=20"
51
+ "node": ">=22"
55
52
  },
56
53
  "files": [
57
54
  "admin{,/!(src)/**}/!(tsconfig|tsconfig.*).json",
@@ -1 +0,0 @@
1
- module.exports = {};