iobroker.leapmotor 0.6.7 → 0.6.8
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/LICENSE +1 -1
- package/README.md +9 -1
- package/io-package.json +14 -14
- package/lib/leapmotor-client.js +16 -1
- package/main.js +34 -16
- package/package.json +2 -2
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026 Henrik Schönhofen (backfisch88)
|
|
3
|
+
Copyright (c) 2026 Henrik Schönhofen (backfisch88) <henrik.schoenhofen@icloud.com>
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -148,6 +148,12 @@ Which comfort commands actually appear depends on the detected vehicle model —
|
|
|
148
148
|
|
|
149
149
|
### **WORK IN PROGRESS**
|
|
150
150
|
|
|
151
|
+
### 0.6.8 (2026-09-19)
|
|
152
|
+
- Fix: the 0.6.7 re-login fix correctly detected an expired session token, but retried login using the same device identity every time - which the cloud started rejecting after the first failure, leaving the adapter stuck until a manual restart. A fresh device identity is now generated on every login attempt.
|
|
153
|
+
- Fix: remote pre-conditioning (heating/cooling the car before getting in, which turns the ignition on without the car moving) no longer gets misdetected as the start of a trip
|
|
154
|
+
- Fix: a completed trip's recorded end time now uses the vehicle's own reported timestamp instead of when our next poll happened to notice the stop, giving more accurate trip duration and a better match for the cloud's energy-breakdown time window
|
|
155
|
+
- Chore: added diagnostic logging of raw login/energy-breakdown responses to aid future troubleshooting
|
|
156
|
+
|
|
151
157
|
### 0.6.7 (2026-09-18)
|
|
152
158
|
- Fix: the automatic re-login on an expired session token was case-sensitive and never triggered for the cloud's "TOKEN_NOT_AVAILABLE" error, causing all polling to silently fail until a manual adapter restart
|
|
153
159
|
- Fix: a trip now only ends once the ignition is actually off (not just when speed reaches 0), and only after a 10-minute confirmation grace period - a brief stop at a light or curb no longer splits one drive into several
|
|
@@ -205,4 +211,6 @@ Older changes can be found in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
|
205
211
|
|
|
206
212
|
MIT License
|
|
207
213
|
|
|
208
|
-
Copyright (c) 2026 Henrik Schönhofen (backfisch88)
|
|
214
|
+
Copyright (c) 2026 Henrik Schönhofen (backfisch88) <henrik.schoenhofen@icloud.com>
|
|
215
|
+
|
|
216
|
+
See [LICENSE](LICENSE) for the full license text.
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "leapmotor",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.8",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.6.8": {
|
|
7
|
+
"en": "Fix: the 0.6.7 re-login fix correctly detected an expired session token, but retried login using the same device identity every time - which the cloud started rejecting after the first failure, leaving the adapter stuck until a manual restart. A fresh device identity is now generated on every login attempt. Fix: remote pre-conditioning (heating/cooling the car before getting in, which turns the ignition on without the car moving) no longer gets misdetected as the start of a trip. Fix: a completed trip's recorded end time now uses the vehicle's own reported timestamp instead of when our next poll happened to notice the stop, giving more accurate trip duration and a better match for the cloud's energy-breakdown time window. Chore: added diagnostic logging of raw login/energy-breakdown responses to aid future troubleshooting.",
|
|
8
|
+
"de": "Fix: Der Re-Login-Fix aus 0.6.7 hat einen abgelaufenen Sitzungs-Token korrekt erkannt, den erneuten Login-Versuch aber immer mit derselben Geraete-Identitaet wiederholt - die die Cloud nach dem ersten Fehlschlag ablehnte, wodurch der Adapter bis zum manuellen Neustart haengen blieb. Bei jedem Login-Versuch wird jetzt eine neue Geraete-Identitaet erzeugt. Fix: Fernvorheizen/-kuehlen des Autos (Zuendung an, ohne dass gefahren wird) wird nicht mehr faelschlich als Fahrtbeginn erkannt. Fix: Der gespeicherte Endzeitpunkt einer Fahrt nutzt jetzt den vom Fahrzeug selbst gemeldeten Zeitstempel statt des Zeitpunkts, an dem der naechste Poll den Stopp zufaellig bemerkt hat - genauere Fahrtdauer und bessere Passung zum Energie-Abfragefenster der Cloud. Chore: Diagnose-Logging fuer rohe Login-/Energie-Antworten ergaenzt.",
|
|
9
|
+
"ru": "Исправление: исправление повторного входа из 0.6.7 корректно определяло истёкший токен сессии, но повторяло вход с той же идентичностью устройства - которую облако начинало отклонять после первой неудачи. Теперь при каждой попытке входа генерируется новая идентичность устройства. Исправление: удалённый предварительный обогрев/охлаждение автомобиля больше не определяется ошибочно как начало поездки. Исправление: записанное время окончания поездки теперь использует собственную метку времени автомобиля.",
|
|
10
|
+
"pt": "Correção: a correção de novo login da 0.6.7 detectava corretamente um token expirado, mas repetia o login com a mesma identidade de dispositivo - que a nuvem rejeitava após a primeira falha. Agora é gerada uma nova identidade a cada tentativa. Correção: o pré-condicionamento remoto (aquecer/arrefecer antes de entrar) não é mais detectado erroneamente como início de viagem. Correção: o horário de término registrado de uma viagem agora usa o timestamp reportado pelo próprio veículo.",
|
|
11
|
+
"nl": "Fix: de heraanmeldingsfix uit 0.6.7 detecteerde een verlopen sessietoken correct, maar probeerde steeds opnieuw in te loggen met dezelfde apparaat-identiteit - die de cloud na de eerste mislukking begon te weigeren. Er wordt nu bij elke inlogpoging een nieuwe apparaat-identiteit gegenereerd. Fix: op afstand voorverwarmen/-koelen van de auto wordt niet meer onterecht gezien als het begin van een rit. Fix: het geregistreerde eindtijdstip van een rit gebruikt nu de eigen tijdstempel van het voertuig.",
|
|
12
|
+
"fr": "Correction : le correctif de reconnexion de la 0.6.7 détectait correctement un jeton expiré, mais retentait la connexion avec la même identité d'appareil - que le cloud rejetait après le premier échec. Une nouvelle identité d'appareil est désormais générée à chaque tentative. Correction : le préconditionnement à distance (chauffer/refroidir avant de monter) n'est plus détecté à tort comme le début d'un trajet. Correction : l'heure de fin enregistrée d'un trajet utilise désormais l'horodatage propre du véhicule.",
|
|
13
|
+
"it": "Correzione: la correzione del re-login della 0.6.7 rilevava correttamente un token scaduto, ma ritentava il login con la stessa identità del dispositivo - che il cloud rifiutava dopo il primo fallimento. Ora viene generata una nuova identità a ogni tentativo. Correzione: il preriscaldamento/preraffreddamento remoto dell'auto non viene più rilevato erroneamente come inizio di un viaggio. Correzione: l'ora di fine registrata di un viaggio ora usa il timestamp riportato dal veicolo stesso.",
|
|
14
|
+
"es": "Corrección: la corrección de reinicio de sesión de la 0.6.7 detectaba correctamente un token expirado, pero reintentaba el inicio de sesión con la misma identidad de dispositivo - que la nube rechazaba tras el primer fallo. Ahora se genera una nueva identidad en cada intento. Corrección: el precondicionamiento remoto del vehículo ya no se detecta erróneamente como el inicio de un viaje. Corrección: la hora de finalización registrada de un viaje ahora usa la marca de tiempo propia del vehículo.",
|
|
15
|
+
"pl": "Poprawka: poprawka ponownego logowania z 0.6.7 poprawnie wykrywała wygasły token, ale ponawiała logowanie z tą samą tożsamością urządzenia - którą chmura odrzucała po pierwszej porażce. Teraz generowana jest nowa tożsamość przy każdej próbie logowania. Poprawka: zdalne podgrzewanie/chłodzenie pojazdu nie jest już błędnie wykrywane jako początek podróży. Poprawka: zapisany czas zakończenia podróży używa teraz własnego znacznika czasu pojazdu.",
|
|
16
|
+
"uk": "Виправлення: виправлення повторного входу з 0.6.7 коректно виявляло прострочений токен, але повторювало вхід з тією ж ідентичністю пристрою - яку хмара відхиляла після першої невдачі. Тепер генерується нова ідентичність при кожній спробі входу. Виправлення: дистанційний прогрів/охолодження автомобіля більше не визначається помилково як початок поїздки. Виправлення: записаний час завершення поїздки тепер використовує власну позначку часу автомобіля.",
|
|
17
|
+
"zh-cn": "修复:0.6.7 的重新登录修复能正确检测过期令牌,但每次都使用相同设备身份重试登录——云端在首次失败后开始拒绝该身份。现在每次登录都会生成新的设备身份。修复:远程预热/预冷车辆(点火但未行驶)不再被误判为行程开始。修复:行程记录的结束时间现在使用车辆自身报告的时间戳,而非我们下次轮询偶然发现停车的时间。杂项:新增原始登录/能耗分解响应的诊断日志。"
|
|
18
|
+
},
|
|
6
19
|
"0.6.7": {
|
|
7
20
|
"en": "Fix: the automatic re-login on an expired session token was case-sensitive and never triggered for the cloud's 'TOKEN_NOT_AVAILABLE' error, causing all polling to silently fail until a manual adapter restart. Fix: a trip now only ends once the ignition is actually off (not just when speed reaches 0), and only after a 10-minute confirmation grace period - a brief stop at a light or curb no longer splits one drive into several. Fix: the trip energy-breakdown retry queue now survives adapter restarts instead of leaving trips stuck showing 'not yet available' forever; trips whose data never arrives are now clearly marked unavailable after the retry budget is exhausted.",
|
|
8
21
|
"de": "Fix: Der automatische Re-Login bei abgelaufenem Sitzungs-Token war gross-/kleinschreibungsabhaengig und wurde bei der Cloud-Fehlermeldung 'TOKEN_NOT_AVAILABLE' nie ausgeloest, wodurch das Polling stillschweigend fehlschlug bis zum manuellen Adapter-Neustart. Fix: eine Fahrt endet jetzt erst, wenn die Zuendung tatsaechlich aus ist (nicht nur bei Geschwindigkeit 0), und erst nach einer 10-minuetigen Bestaetigungsfrist - ein kurzer Stopp an der Ampel oder am Strassenrand trennt eine Fahrt nicht mehr in mehrere auf. Fix: die Warteschlange fuer die Fahrt-Energieaufteilung uebersteht jetzt Adapter-Neustarts, statt Fahrten fuer immer auf 'noch nicht verfuegbar' haengen zu lassen; Fahrten ohne jemals eintreffende Daten werden nach Ausschoepfen der Wiederholungsversuche klar als nicht verfuegbar markiert.",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Poprawka: model B10 poprawnie korzysta teraz z chmurowego punktu końcowego statusu c10 (potwierdzone przez społeczność), co rozwiązuje puste punkty danych status/trips/charging. Poprawka: włączono pełne i18n dla jsonConfig.json, ponieważ pliki tłumaczeń admin/i18n obejmują teraz wszystkie klucze.",
|
|
81
94
|
"uk": "Виправлення: модель B10 тепер коректно використовує хмарну кінцеву точку статусу c10 (підтверджено спільнотою), що вирішує проблему порожніх точок даних status/trips/charging. Виправлення: увімкнено повну локалізацію i18n для jsonConfig.json, оскільки файли перекладу admin/i18n тепер охоплюють усі ключі.",
|
|
82
95
|
"zh-cn": "修复:B10 车型现在正确使用 c10 云状态端点(已由社区确认),解决了 status/trips/charging 数据点为空的问题。修复:由于 admin/i18n 翻译文件现已覆盖所有键,为 jsonConfig.json 启用了完整的 i18n 支持。"
|
|
83
|
-
},
|
|
84
|
-
"0.6.1": {
|
|
85
|
-
"en": "Fix: repository checker findings - use node: prefix for built-in modules, remove raw setTimeout fallback, include admin-tab i18n source in npm package, trim news list to 7 entries.",
|
|
86
|
-
"de": "Fix: Repository-Checker-Befunde behoben - node:-Praefix fuer eingebaute Module verwendet, rohen setTimeout-Fallback entfernt, admin-tab-i18n-Quellcode ins npm-Paket aufgenommen, News-Liste auf 7 Eintraege gekuerzt.",
|
|
87
|
-
"ru": "Исправление: устранены замечания проверки репозитория - использован префикс node: для встроенных модулей, удалён резервный вызов setTimeout, исходный код i18n admin-tab включён в npm-пакет, список новостей сокращён до 7 записей.",
|
|
88
|
-
"pt": "Correção: correções de verificação do repositório - uso do prefixo node: para módulos integrados, remoção do fallback bruto de setTimeout, inclusão do código-fonte i18n do admin-tab no pacote npm, lista de notícias reduzida para 7 entradas.",
|
|
89
|
-
"nl": "Fix: bevindingen van de repository-checker verholpen - node:-prefix gebruikt voor ingebouwde modules, ruwe setTimeout-fallback verwijderd, admin-tab i18n broncode opgenomen in npm-pakket, nieuwslijst teruggebracht tot 7 items.",
|
|
90
|
-
"fr": "Correction : corrections du vérificateur de dépôt - utilisation du préfixe node: pour les modules intégrés, suppression du repli setTimeout brut, inclusion du code source i18n de l'admin-tab dans le paquet npm, liste des actualités réduite à 7 entrées.",
|
|
91
|
-
"it": "Correzione: risolti i rilievi del controllo del repository - usato il prefisso node: per i moduli integrati, rimosso il fallback grezzo di setTimeout, incluso il codice sorgente i18n dell'admin-tab nel pacchetto npm, elenco notizie ridotto a 7 voci.",
|
|
92
|
-
"es": "Corrección: se resolvieron los hallazgos del verificador del repositorio - se usó el prefijo node: para los módulos integrados, se eliminó el fallback bruto de setTimeout, se incluyó el código fuente i18n del admin-tab en el paquete npm, se redujo la lista de noticias a 7 entradas.",
|
|
93
|
-
"pl": "Poprawka: rozwiązano ustalenia weryfikatora repozytorium - użyto prefiksu node: dla modułów wbudowanych, usunięto surowy fallback setTimeout, dołączono kod źródłowy i18n admin-tab do pakietu npm, skrócono listę nowości do 7 pozycji.",
|
|
94
|
-
"uk": "Виправлення: усунено зауваження перевірки репозиторію - використано префікс node: для вбудованих модулів, видалено резервний виклик setTimeout, вихідний код i18n admin-tab включено до npm-пакета, список новин скорочено до 7 записів.",
|
|
95
|
-
"zh-cn": "修复:解决仓库检查器发现的问题 - 为内置模块使用 node: 前缀,移除了原始的 setTimeout 回退,将 admin-tab i18n 源代码包含在 npm 包中,新闻列表精简至 7 条。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
package/lib/leapmotor-client.js
CHANGED
|
@@ -185,7 +185,7 @@ class LeapmotorClient{
|
|
|
185
185
|
get _signKey(){if(!this.signIkm)throw new Error('Not authenticated');return Buffer.from(deriveSignKey(this.signIkm,this.signSalt,this.signInfo))}
|
|
186
186
|
_authHeaders(){if(!this.userId||!this.token)throw new Error('Not authenticated');return{userId:this.userId,token:this.token}}
|
|
187
187
|
_h(h){return{nonce:h.nonce,deviceId:h.deviceId,timestamp:h.timestamp,sign:h.sign,acceptLanguage:h.acceptLanguage,channel:'1',source:'leapmotor',deviceType:'1',version:'1.12.3'}}
|
|
188
|
-
_parseResponse(data,label){if(data.code!==0){const msg=String(data.message||JSON.stringify(data));throw new Error(`${label} failed: ${msg}`)}return data}
|
|
188
|
+
_parseResponse(data,label){if(data.code!==0){const msg=String(data.message||JSON.stringify(data));throw new Error(`${label} failed: ${msg} (code=${data.code})`)}return data}
|
|
189
189
|
_loadAccountCert(ld){
|
|
190
190
|
const p12Der=Buffer.from(String(ld.base64Cert||''),'base64');
|
|
191
191
|
const p12Asn1=forge.asn1.fromDer(forge.util.createBuffer(p12Der));
|
|
@@ -196,10 +196,25 @@ class LeapmotorClient{
|
|
|
196
196
|
throw new Error('Could not open account certificate');
|
|
197
197
|
}
|
|
198
198
|
async login(){
|
|
199
|
+
// Fresh device identity on every login attempt (not just the very
|
|
200
|
+
// first one). Reusing the same deviceId across retries after a
|
|
201
|
+
// failed login can apparently get that identity rejected/poisoned
|
|
202
|
+
// server-side - this mirrors what a full adapter restart does
|
|
203
|
+
// (mints a brand-new random deviceId), without requiring one.
|
|
204
|
+
this.deviceId=crypto.randomUUID().replace(/-/g,'');
|
|
199
205
|
const h=this._h(buildLoginHeaders({deviceId:this.deviceId,username:this.config.username,password:this.config.password,language:this.config.language}));
|
|
200
206
|
const body=new URLSearchParams({isRecoverAcct:'0',password:this.config.password,policyId:'20260204',loginMethod:'1',email:this.config.username}).toString();
|
|
201
207
|
const loginHttp=this.appHttp||this.http;
|
|
202
208
|
const resp=await loginHttp.post('/carownerservice/oversea/acct/v1/login',body,{headers:h});
|
|
209
|
+
// Log the full raw response (minus sensitive fields) at debug level
|
|
210
|
+
// whenever login isn't immediately successful - useful for diagnosing
|
|
211
|
+
// upstream API changes (e.g. policy/version bumps) without exposing
|
|
212
|
+
// the token/certs themselves.
|
|
213
|
+
if(resp.data?.code!==0){
|
|
214
|
+
const safe={...resp.data};
|
|
215
|
+
if(safe.data)safe.data={...safe.data,base64Cert:safe.data.base64Cert?'[REDACTED]':undefined,token:safe.data.token?'[REDACTED]':undefined};
|
|
216
|
+
this._adapter?.log?.debug(`Login non-success raw response: ${JSON.stringify(safe)}`);
|
|
217
|
+
}
|
|
203
218
|
const data=this._parseResponse(resp.data,'login');const ld=data.data||{};
|
|
204
219
|
this.userId=String(ld.id);this.token=String(ld.token);
|
|
205
220
|
this.deviceId=deriveSessionDeviceId(this.token,this.deviceId);
|
package/main.js
CHANGED
|
@@ -104,7 +104,7 @@ class LeapmotorAdapter extends utils.Adapter{
|
|
|
104
104
|
const pollTime=new Date().toLocaleString('de-DE',{timeZone:'Europe/Berlin'});
|
|
105
105
|
await this.setStateAsync(`${vehicle.vin}.status.last_poll_time`,{val:pollTime,ack:true});
|
|
106
106
|
try{await this.updateDailyMileage(vehicle.vin,s.totalMileage)}catch(e){this.log.debug(`Daily mileage error: ${e}`)}
|
|
107
|
-
try{await this.updateTripDetection(vehicle,s.totalMileage,s.speed,s.soc,s.bcmKeyPositionOn1||s.bcmKeyPositionOn3)}catch(e){this.log.debug(`Trip detection error: ${e}`)}
|
|
107
|
+
try{await this.updateTripDetection(vehicle,s.totalMileage,s.speed,s.soc,s.bcmKeyPositionOn1||s.bcmKeyPositionOn3,s.collectTimeMs)}catch(e){this.log.debug(`Trip detection error: ${e}`)}
|
|
108
108
|
try{await this.resolvePendingTripEnergy(vehicle)}catch(e){this.log.debug(`Pending trip energy error: ${e}`)}
|
|
109
109
|
try{await this.updateChargingCost(vehicle.vin,s.soc,s.chargeState)}catch(e){this.log.debug(`Charging cost error: ${e}`)}
|
|
110
110
|
this.log.debug(`${vehicle.vin}: SOC=${s.soc}% Range=${s.expectedMileage}km Temp=${s.outdoorTemp}°C Locked=${s.driverDoorLockStatus} AC=${s.acSwitch}`);
|
|
@@ -151,16 +151,30 @@ class LeapmotorAdapter extends utils.Adapter{
|
|
|
151
151
|
await this.setStateAsync(`${vin}.trips.today_km`,{val:todayEntry.km,ack:true});
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
async updateTripDetection(vehicle,totalMileage,speed,soc,keyPosition){
|
|
154
|
+
async updateTripDetection(vehicle,totalMileage,speed,soc,keyPosition,vehicleTimeMs){
|
|
155
155
|
const vin=vehicle.vin;
|
|
156
156
|
if(totalMileage==null)return;
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
|
|
157
|
+
// Note: the grace-period elapsed-time logic below deliberately uses
|
|
158
|
+
// our OWN poll wall-clock (Date.now()), not vehicleTimeMs. The
|
|
159
|
+
// vehicle's own reported timestamp can go long stretches (observed:
|
|
160
|
+
// hours) without updating once the car is parked/asleep, so gating
|
|
161
|
+
// the 10-minute countdown on it risked trips hanging open far longer
|
|
162
|
+
// than intended. vehicleTimeMs is only used for the trip's recorded
|
|
163
|
+
// end time (see below), for an accurate energy-breakdown query window.
|
|
164
|
+
// A trip is only ever STARTED by actual movement (speed>0), never by
|
|
165
|
+
// ignition alone - otherwise remote pre-conditioning (heating/cooling
|
|
166
|
+
// the car before getting in, which turns the ignition/key on without
|
|
167
|
+
// the car moving) gets misdetected as the trip beginning, inflating
|
|
168
|
+
// the recorded duration with pure preheat time.
|
|
169
|
+
const isMoving=speed!=null&&speed>0;
|
|
170
|
+
// Once a trip IS already active, it stays active as long as EITHER
|
|
171
|
+
// the car is moving OR the ignition/key is still on - not just
|
|
172
|
+
// speed>0. Without the ignition check, a brief stop (traffic light,
|
|
173
|
+
// waiting at the curb) with the engine still running would end the
|
|
174
|
+
// trip right there, splitting one continuous drive into several and
|
|
175
|
+
// mis-attributing part of it to "Sonstige" (undetected) km once it
|
|
176
|
+
// resumes.
|
|
177
|
+
const isDriving=isMoving||keyPosition===true;
|
|
164
178
|
// Grace period before actually closing a trip once isDriving goes
|
|
165
179
|
// false: complements the ignition check above as a backstop for
|
|
166
180
|
// cases where key_position briefly misreports (or a model doesn't
|
|
@@ -173,7 +187,7 @@ class LeapmotorAdapter extends utils.Adapter{
|
|
|
173
187
|
const prev=this._tripStates[vin]||{wasActive:false,startMileage:null,startTime:null,startSoc:null,pendingEndSince:null};
|
|
174
188
|
const lastMileage=this._lastKnownMileage[vin];
|
|
175
189
|
|
|
176
|
-
if(
|
|
190
|
+
if(isMoving&&!prev.wasActive){
|
|
177
191
|
// Die Fahrt wird erst jetzt erkannt, aber der Kilometerstand kann sich
|
|
178
192
|
// bereits seit dem letzten Poll (bis zu 5 Minuten zuvor) erhoeht haben,
|
|
179
193
|
// ohne dass wir es als Fahrt erfasst hatten ("Sonstige" km im Dashboard).
|
|
@@ -206,8 +220,11 @@ class LeapmotorAdapter extends utils.Adapter{
|
|
|
206
220
|
}else if(!isDriving&&prev.wasActive){
|
|
207
221
|
if(!prev.pendingEndSince){
|
|
208
222
|
// First poll where the car looks stopped: start the grace
|
|
209
|
-
// countdown instead of ending the trip immediately.
|
|
210
|
-
|
|
223
|
+
// countdown instead of ending the trip immediately. Capture
|
|
224
|
+
// the vehicle's own reported time now (if available) - this
|
|
225
|
+
// is when the car actually stopped, and becomes the trip's
|
|
226
|
+
// recorded end time once the grace period elapses below.
|
|
227
|
+
this._tripStates[vin]={...prev,pendingEndSince:Date.now(),pendingEndVehicleTime:vehicleTimeMs||null};
|
|
211
228
|
this._lastKnownMileage[vin]={mileage:totalMileage,ts:Date.now()};
|
|
212
229
|
return;
|
|
213
230
|
}
|
|
@@ -217,11 +234,12 @@ class LeapmotorAdapter extends utils.Adapter{
|
|
|
217
234
|
return;
|
|
218
235
|
}
|
|
219
236
|
const km=Math.max(0,totalMileage-(prev.startMileage??totalMileage));
|
|
220
|
-
const
|
|
237
|
+
const accurateEndTimeMs=prev.pendingEndVehicleTime||prev.pendingEndSince;
|
|
238
|
+
const durationMin=Math.round((accurateEndTimeMs-(prev.startTime??accurateEndTimeMs))/60000);
|
|
221
239
|
const socUsed=prev.startSoc!=null&&soc!=null?Math.max(0,prev.startSoc-soc):null;
|
|
222
240
|
if(km>=0.5){
|
|
223
|
-
const startTimeMs=prev.startTime??
|
|
224
|
-
const endTimeMs=
|
|
241
|
+
const startTimeMs=prev.startTime??accurateEndTimeMs;
|
|
242
|
+
const endTimeMs=accurateEndTimeMs;
|
|
225
243
|
const trip={
|
|
226
244
|
date:new Date(startTimeMs).toISOString().slice(0,10),
|
|
227
245
|
startTime:new Date(startTimeMs).toLocaleString('de-DE',{timeZone:'Europe/Berlin'}),
|
|
@@ -265,7 +283,7 @@ class LeapmotorAdapter extends utils.Adapter{
|
|
|
265
283
|
this._pendingEnergyTrips[vin].push({startTimeMs,endTimeMs,date:trip.date,startTime:trip.startTime,attempts:0});
|
|
266
284
|
}
|
|
267
285
|
}
|
|
268
|
-
this._tripStates[vin]={wasActive:false,startMileage:null,startTime:null,startSoc:null,pendingEndSince:null};
|
|
286
|
+
this._tripStates[vin]={wasActive:false,startMileage:null,startTime:null,startSoc:null,pendingEndSince:null,pendingEndVehicleTime:null};
|
|
269
287
|
await this.setStateAsync(`${vin}.trips.current_trip_active`,{val:false,ack:true});
|
|
270
288
|
}
|
|
271
289
|
this._lastKnownMileage[vin]={mileage:totalMileage,ts:Date.now()};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.leapmotor",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "Leapmotor electric vehicle integration for ioBroker",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Henrik Schönhofen",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@alcalzone/release-script-plugin-manual-review": "^5.2.0",
|
|
39
39
|
"@iobroker/adapter-dev": "^1.5.0",
|
|
40
40
|
"@iobroker/eslint-config": "^2.3.4",
|
|
41
|
-
"@iobroker/testing": "^6.
|
|
41
|
+
"@iobroker/testing": "^6.2.2",
|
|
42
42
|
"@tsconfig/node22": "^22.0.6",
|
|
43
43
|
"@types/node": "^22.0.0"
|
|
44
44
|
},
|