Skip to content
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo
  1. ioBroker Community Home
  2. Deutsch
  3. Tester
  4. Adapter Hyundai (Bluelink) oder KIA (UVO)

NEWS

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    8.1k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    13
    1
    1.8k

  • Neues Video "KI im Smart Home" - ioBroker plus n8n
    BluefoxB
    Bluefox
    15
    1
    2.1k

Adapter Hyundai (Bluelink) oder KIA (UVO)

Geplant Angeheftet Gesperrt Verschoben Tester
2.3k Beiträge 149 Kommentatoren 872.0k Aufrufe 139 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • arteckA arteck

    @gargano sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

    Hat Kia wieder etwas verändert ?

    woher soll ich das wissen ??

    geht es den mit neuen Token ?

    ilovegymI Online
    ilovegymI Online
    ilovegym
    schrieb am zuletzt editiert von
    #2200

    @arteck @Meister-Mopper und alle anderen.. 🙂

    Habe hier das oben von mir gerade erfundene Script, das den naechsten HPC und Ladeleistung etc raussucht, ausrechnet und in einem Dashboard darstellt, mal gemacht. Laeuft hier unter javascript 8.9.2 ohne irgendwelche Plugins ohne warnings/errors, auch die States werden quiet angelegt ( outHPCRoot und outEnergyRoot und das Dashboard kann am Anfang vom Script konfiguriert werden. Ebenso die States vom bluelink-Adapter, koennen auch aehnlich sein, je nach Fahrzeugtyp.)
    Zur Abfrage von OpenchargeMap sollte ein kostenloser Api-Key eingetragen sein, link im Script.

    Das Dashboard stelle ich mit MinuVis (Widget HTML auf den State) dar. Geht auch garantiert in allen anderen Vis-Varianten.

    Hier das Script:

    /**************************************************************
    * IONIQ 5 / Bluelink – HPC Nearby + Energy Integration + Dashboard
    * ioBroker JavaScript-Adapter >= 8.9.2 (Node 18+)
    * Version: 1.3.0 (selftest map centering, energy tiles, vis path)
    * (c) by ilovegym66
    **************************************************************/
    'use strict';
    
    const https = require('https');
    
    /*** ===== KONFIG ===== ***/
    const CFG = {
     bluelink: {
       power:    'bluelink.0.KMHKRxxxx.vehicleStatusRaw.ccs2Status.state.Vehicle.Green.Electric.SmartGrid.RealTimePower',
       charging: 'bluelink.0.KMHKRxxxx.vehicleStatus.battery.charge',
       soc:      'bluelink.0.KMHKRxxxx.vehicleStatus.battery.soc',
       lat:      'bluelink.0.KMHKRxxxx.vehicleLocation.lat',
       lon:      'bluelink.0.KMHKRxxxx.vehicleLocation.lon',
       latAlt:   'bluelink.0.KMHKRxxxx.vehicleLocation.latitude',
       lonAlt:   'bluelink.0.KMHKRxxxx.vehicleLocation.longitude'
     },
    
     hpc: {
       thresholdKW: 0,        // Suche auch ohne hohe Ladeleistung erlauben
       hpcMinKW:    149,      // Client-Filter Mindestleistung (kaskadiert runter falls 0 Treffer)
       radiusKm:    20,
       maxResults:  200,
       coordMinMoveM: 30,
       minCheckIntervalSec: 30,
       preferredOperators: ['ionity','enbw','aral pulse','fastned','shell recharge','allego','mer','ewe go','totalenergies','eviny','maingau','entega','pfalzwerke','tesla'],
       ocmEndpoint: 'https://api.openchargemap.io/v3/poi/',
       ocmApiKey:   'DEIN-OCM-KEY-HIER', // <— eintragen oder per State lesen (siehe unten)
       requireChargingForSearch: false,
    
       // Serverseitige Filter (erste Stufe „strict“)
       apiFilterFastDC: true,     // Level 3 + DC
       apiMinPowerKW:   120,
    
       // Kaskade bei 0 Treffern
       cascadeIfZero: true,
       cascadeMinKWClient: 120
     },
    
     energy: {
       powerIsWattAuto:    true,
       sessionPowerMinKW:  0.5,
       integTickSec:       10,
       sessionIdleEndSec:  180,
       usableCapacityKWh:  84
     },
    
     // Dashboard-Ausgabe (zurück in vis-Pfad)
     dash: {
       htmlState: '0_userdata.0.vis.Dashboards.HPC.HTML', // <- hier landet das fertige HTML
       allowScroll: false,    // MinuVis: kein Scroll/Wheel
       heightPx: 420          // Kartenhöhe
     },
    
     // Ausgabe-Roots (States)
     outHpcRoot:    '0_userdata.0.Cars.HPCNearby',
     outEnergyRoot: '0_userdata.0.Cars.Energy',
    
     debug: true
    };
    
    // OPTIONAL: OCM-Key aus State laden (wenn gewünscht)
    // try { const s=getState('0_userdata.0.secrets.ocmApiKey'); if (s && s.val) CFG.hpc.ocmApiKey = String(s.val); } catch(e){}
    
    /*** ===== Utils ===== ***/
    const idJ = (...a)=>a.join('.');
    const logI = m => log(`[Ioniq-HPC+Energy] ${m}`, 'info');
    const logD = m => CFG.debug && log(`[Ioniq-HPC+Energy] ${m}`, 'debug');
    const nowSec = ()=>Math.floor(Date.now()/1000);
    
    function exObj(id){ try{ return existsObject(id); }catch(e){ return false; } }
    function exState(id){ try{ return existsState(id); }catch(e){ return false; } }
    function g(id){
     try{
       if (!exState(id)) return undefined;
       const s = getState(id);
       return s ? s.val : undefined;
     }catch(e){ return undefined; }
    }
    async function es(id, common, init){ try{ if (!exObj(id)) await createStateAsync(id, common, init ?? null); }catch(e){} }
    async function ss(id, val){ try{ await setStateAsync(id, {val, ack:true}); }catch(e){} }
    function toNum(x, d=0){ const n = Number(x); return Number.isFinite(n) ? n : d; }
    function toBool(x){ return x===true || x===1 || x==='1' || String(x).toLowerCase()==='true'; }
    
    /*** ===== Haversine (m) ===== ***/
    function haversineMeters(lat1, lon1, lat2, lon2){
     const R=6371000, rad=d=>d*Math.PI/180;
     const dLat=rad(lat2-lat1), dLon=rad(lon2-lon1);
     const a=Math.sin(dLat/2)**2+Math.cos(rad(lat1))*Math.cos(rad(lat2))*Math.sin(dLon/2)**2;
     return 2*R*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));
    }
    
    /*** ====== OUTPUT STATES: HPC ====== ***/
    const HPC = {
     ROOT:     CFG.outHpcRoot,
     HAS:      idJ(CFG.outHpcRoot,'hasHPCNearby'),
     COUNT:    idJ(CFG.outHpcRoot,'count'),
     NAME:     idJ(CFG.outHpcRoot,'nearest.name'),
     DISTM:    idJ(CFG.outHpcRoot,'nearest.distance_m'),
     KW:       idJ(CFG.outHpcRoot,'nearest.maxPower_kW'),
     OP:       idJ(CFG.outHpcRoot,'nearest.operator'),
     LASTJSON: idJ(CFG.outHpcRoot,'lastResultJson'),
     LASTCHK:  idJ(CFG.outHpcRoot,'lastCheck'),
     LASTWHY:  idJ(CFG.outHpcRoot,'lastReason'),
     LASTERR:  idJ(CFG.outHpcRoot,'lastError'),
     MISSING:  idJ(CFG.outHpcRoot,'debug.missingStates'),
    
     // Debug
     DBG_URL:   idJ(CFG.outHpcRoot,'debug.lastQueryUrl'),
     DBG_URL2:  idJ(CFG.outHpcRoot,'debug.lastQueryUrl_swapped'),
     DBG_URL3:  idJ(CFG.outHpcRoot,'debug.lastQueryUrl_wide'),
     DBG_RAW:   idJ(CFG.outHpcRoot,'debug.rawCount'),
     DBG_FIL:   idJ(CFG.outHpcRoot,'debug.filteredCount'),
     DBG_SAMPLE:idJ(CFG.outHpcRoot,'debug.sampleJson'),
     DBG_COORD_LAT: idJ(CFG.outHpcRoot,'debug.lastLat'),
     DBG_COORD_LON: idJ(CFG.outHpcRoot,'debug.lastLon'),
     DBG_HTTP:      idJ(CFG.outHpcRoot,'debug.lastHttpStatus'),
     DBG_ERRSHORT:  idJ(CFG.outHpcRoot,'debug.lastErrorShort'),
    
     // Commands
     CMD_TEST:      idJ(CFG.outHpcRoot,'cmd.TestSearch'),
     TEST_RADIUS:   idJ(CFG.outHpcRoot,'cmd.TestRadiusKm'),
     CMD_TEST_FFM:  idJ(CFG.outHpcRoot,'cmd.SelfTest_Frankfurt'),
     CMD_TEST_CGN:  idJ(CFG.outHpcRoot,'cmd.SelfTest_Koeln'),
     CMD_TEST_MUC:  idJ(CFG.outHpcRoot,'cmd.SelfTest_Muenchen')
    };
    
    async function ensureHpcStates(){
     await es(HPC.HAS,{type:'boolean',role:'indicator'},false);
     await es(HPC.COUNT,{type:'number',role:'value'},0);
     await es(HPC.NAME,{type:'string',role:'text'},'');
     await es(HPC.DISTM,{type:'number',role:'value'},0);
     await es(HPC.KW,{type:'number',role:'value'},0);
     await es(HPC.OP,{type:'string',role:'text'},'');
     await es(HPC.LASTJSON,{type:'string',role:'json'},'[]');
     await es(HPC.LASTCHK,{type:'string',role:'text'},'');
     await es(HPC.LASTWHY,{type:'string',role:'text'},'');
     await es(HPC.LASTERR,{type:'string',role:'text'},'');
     await es(HPC.MISSING,{type:'string',role:'text'},'');
    
     await es(HPC.DBG_URL,{type:'string',role:'text'},'');
     await es(HPC.DBG_URL2,{type:'string',role:'text'},'');
     await es(HPC.DBG_URL3,{type:'string',role:'text'},'');
     await es(HPC.DBG_RAW,{type:'number',role:'value'},0);
     await es(HPC.DBG_FIL,{type:'number',role:'value'},0);
     await es(HPC.DBG_SAMPLE,{type:'string',role:'json'},'');
     await es(HPC.DBG_COORD_LAT,{type:'number',role:'value.gps'},0);
     await es(HPC.DBG_COORD_LON,{type:'number',role:'value.gps'},0);
     await es(HPC.DBG_HTTP,{type:'string',role:'text'},'');
     await es(HPC.DBG_ERRSHORT,{type:'string',role:'text'},'');
    
     await es(HPC.CMD_TEST,{type:'boolean',role:'button'},false);
     await es(HPC.TEST_RADIUS,{type:'number',role:'value'},NaN);
     await es(HPC.CMD_TEST_FFM,{type:'boolean',role:'button'},false);
     await es(HPC.CMD_TEST_CGN,{type:'boolean',role:'button'},false);
     await es(HPC.CMD_TEST_MUC,{type:'boolean',role:'button'},false);
    }
    
    /*** ====== OUTPUT STATES: ENERGY ====== ***/
    const EN = {
     ROOT:          CFG.outEnergyRoot,
     ACTIVE:        idJ(CFG.outEnergyRoot,'session.active'),
     START_TS:      idJ(CFG.outEnergyRoot,'session.startTs'),
     END_TS:        idJ(CFG.outEnergyRoot,'session.endTs'),
     START_SOC:     idJ(CFG.outEnergyRoot,'session.startSoC'),
     END_SOC:       idJ(CFG.outEnergyRoot,'session.endSoC'),
     ENERGY_KWH:    idJ(CFG.outEnergyRoot,'session.energy_kWh'),
     ENERGY_SOC_KWH:idJ(CFG.outEnergyRoot,'session.energySoc_kWh'),
     LAST_ENERGY:   idJ(CFG.outEnergyRoot,'lastSession.energy_kWh'),
     LAST_START:    idJ(CFG.outEnergyRoot,'lastSession.startTs'),
     LAST_END:      idJ(CFG.outEnergyRoot,'lastSession.endTs'),
     TODAY_KWH:     idJ(CFG.outEnergyRoot,'today.energy_kWh'),
     TOTAL_KWH:     idJ(CFG.outEnergyRoot,'total.energy_kWh'),
     LAST_REASON:   idJ(CFG.outEnergyRoot,'debug.lastReason'),
     LAST_ERR:      idJ(CFG.outEnergyRoot,'debug.lastError')
    };
    async function ensureEnergyStates(){
     await es(EN.ACTIVE,        {type:'boolean', role:'indicator'}, false);
     await es(EN.START_TS,      {type:'string',  role:'text'}, '');
     await es(EN.END_TS,        {type:'string',  role:'text'}, '');
     await es(EN.START_SOC,     {type:'number',  role:'value'}, null);
     await es(EN.END_SOC,       {type:'number',  role:'value'}, null);
     await es(EN.ENERGY_KWH,    {type:'number',  role:'value.energy'}, 0);
     await es(EN.ENERGY_SOC_KWH,{type:'number',  role:'value.energy'}, 0);
     await es(EN.LAST_ENERGY,   {type:'number',  role:'value.energy'}, 0);
     await es(EN.LAST_START,    {type:'string',  role:'text'}, '');
     await es(EN.LAST_END,      {type:'string',  role:'text'}, '');
     await es(EN.TODAY_KWH,     {type:'number',  role:'value.energy'}, 0);
     await es(EN.TOTAL_KWH,     {type:'number',  role:'value.energy'}, 0);
     await es(EN.LAST_REASON,   {type:'string',  role:'text'}, '');
     await es(EN.LAST_ERR,      {type:'string',  role:'text'}, '');
    }
    
    /*** ===== OCM Helper ===== ***/
    function isPreferredOperator(op){
     if (!op) return false;
     const t = String(op).toLowerCase();
     return CFG.hpc.preferredOperators.some(x => t.includes(String(x).toLowerCase()));
    }
    function powerFromConn(c){
     if (!c) return 0;
     let pk = toNum(c.PowerKW, NaN);
     if (!Number.isFinite(pk)) pk = toNum(c.RatedPowerKW, NaN);
     if (!Number.isFinite(pk)) pk = toNum(c.Power, NaN);
     if (Number.isFinite(pk) && pk > 0) return pk;
    
     let v = toNum(c.Voltage, NaN); if (!Number.isFinite(v)) v = toNum(c.Volts, NaN);
     let a = toNum(c.Amps, NaN);    if (!Number.isFinite(a)) a = toNum(c.Current, NaN);
     if (Number.isFinite(v) && Number.isFinite(a) && v>0 && a>0) return (v*a)/1000;
    
     const levelId  = toNum(c.LevelID, NaN) || toNum(c?.Level?.ID, NaN) || 0;
     const currentId= toNum(c.CurrentTypeID, NaN) || toNum(c?.CurrentType?.ID, NaN) || 0;
     const lvlTitle = String(c?.Level?.Title||'').toLowerCase();
     const curTitle = String(c?.CurrentType?.Title||'').toLowerCase();
     const lvlFast  = levelId >= 3 || lvlTitle.includes('3');
     const isDC     = currentId === 30 || curTitle.includes('dc');
     if (lvlFast && isDC) return 150; // Fallback
     return 0;
    }
    function extractMaxPowerKW(poi){
     const arr = Array.isArray(poi?.Connections) ? poi.Connections : [];
     let max = 0;
     for (const c of arr){ const p = powerFromConn(c); if (p > max) max = p; }
     return max;
    }
    
    /*** HTTP ***/
    function buildOcmUrlStr(lat, lon, radiusKm, maxResults, opts){
     opts = opts || {}; // { fastDC?:boolean, minPowerKW?:number }
     function add(q, k, v){ if (v===undefined||v===null||v==='') return q; q.push(encodeURIComponent(k)+'='+encodeURIComponent(String(v))); return q; }
     const base = String(CFG.hpc.ocmEndpoint||'https://api.openchargemap.io/v3/poi/').replace(/\?+.*/, '');
     const params = [];
     add(params,'output','json');
     add(params,'latitude', lat);
     add(params,'longitude', lon);
     add(params,'distance', radiusKm);
     add(params,'distanceunit','KM');
     add(params,'maxresults', maxResults || CFG.hpc.maxResults);
     add(params,'compact','false'); add(params,'verbose','true');
     if (opts.fastDC){ add(params,'levelid',3); add(params,'currenttypeid',30); if (opts.minPowerKW!=null) add(params,'minpowerkw', opts.minPowerKW); }
     if (CFG.hpc.ocmApiKey && CFG.hpc.ocmApiKey.trim()){ add(params,'key', CFG.hpc.ocmApiKey.trim()); }
     return base + '?' + params.join('&');
    }
    function httpGetJson(urlStr){
     return new Promise((resolve, reject) => {
       const headers = { 'User-Agent':'ioBroker-HPC-Nearby/1.3 (+contact:local)' };
       // keinen X-API-Key Header verwenden – Key steckt in URL
       const req = https.get(urlStr, { headers, timeout: 12000 }, (res) => {
         let data=''; res.on('data', c=>data+=c);
         res.on('end', ()=>{ if (res.statusCode>=200 && res.statusCode<300) { try{ resolve({json:JSON.parse(data), status:res.statusCode}); } catch(e){ reject(new Error('OCM JSON parse error: '+e.message)); } } else reject(new Error('OCM HTTP '+res.statusCode)); });
       });
       req.on('timeout', ()=>req.destroy(new Error('timeout')));
       req.on('error', reject);
     });
    }
    async function fetchOCM(lat, lon, radiusKm, maxResults, mode){
     let url;
     if (mode==='strict') url = buildOcmUrlStr(lat,lon,radiusKm,maxResults,{fastDC:true,minPowerKW:CFG.hpc.apiMinPowerKW});
     else if (mode==='dcOnly') url = buildOcmUrlStr(lat,lon,radiusKm,maxResults,{fastDC:true});
     else url = buildOcmUrlStr(lat,lon,radiusKm,maxResults,{});
     const r = await httpGetJson(url);
     return { mode, url, status:r.status, json:r.json };
    }
    
    /*** ===== HPC intern ===== ***/
    let lastCheckSec = 0, lastLat = null, lastLon = null;
    function readLatLon(){
     let lat = g(CFG.bluelink.lat), lon = g(CFG.bluelink.lon);
     if ((lat===undefined||lat===null) && exState(CFG.bluelink.latAlt)) lat = g(CFG.bluelink.latAlt);
     if ((lon===undefined||lon===null) && exState(CFG.bluelink.lonAlt)) lon = g(CFG.bluelink.lonAlt);
     return {lat: toNum(lat, 0), lon: toNum(lon, 0)};
    }
    function listMissing(ids){ return ids.filter(id => !exState(id)); }
    
    async function hpcNo(reason){
     await ss(HPC.HAS,false); await ss(HPC.COUNT,0);
     await ss(HPC.NAME,''); await ss(HPC.DISTM,0); await ss(HPC.KW,0); await ss(HPC.OP,'');
     await ss(HPC.LASTJSON,'[]'); await ss(HPC.LASTCHK,new Date().toISOString());
     await ss(HPC.LASTERR,''); await ss(HPC.LASTWHY,reason||'');
     await ss(HPC.DBG_RAW,0); await ss(HPC.DBG_FIL,0);
     await ss(HPC.DBG_URL,''); await ss(HPC.DBG_URL2,''); await ss(HPC.DBG_URL3,''); await ss(HPC.DBG_HTTP,''); await ss(HPC.DBG_ERRSHORT,'');
    }
    
    function enrichAndFilter(json, lat, lon){
     const enriched=[];
     for (const poi of Array.isArray(json)?json:[]){
       const pMax = extractMaxPowerKW(poi);
       const conns = Array.isArray(poi?.Connections)?poi.Connections:[];
       const isDCfast = conns.some(c=>{
         const lvl = toNum(c?.LevelID, NaN) || toNum(c?.Level?.ID, NaN) || 0;
         const cur = toNum(c?.CurrentTypeID, NaN) || toNum(c?.CurrentType?.ID, NaN) || 0;
         const lvlTitle = String(c?.Level?.Title||'').toLowerCase();
         const curTitle = String(c?.CurrentType?.Title||'').toLowerCase();
         const lvlFast = lvl >= 3 || lvlTitle.includes('3'); const isDC = cur === 30 || curTitle.includes('dc');
         return lvlFast && isDC;
       });
       if (!(pMax >= CFG.hpc.hpcMinKW || (pMax === 0 && isDCfast))) continue;
    
       const op   = poi?.OperatorInfo?.Title || '';
       const name = poi?.AddressInfo?.Title || '';
       const plat = toNum(poi?.AddressInfo?.Latitude, NaN);
       const plon = toNum(poi?.AddressInfo?.Longitude, NaN);
       const distM = (Number.isFinite(plat)&&Number.isFinite(plon))?Math.round(haversineMeters(lat,lon,plat,plon)):null;
       enriched.push({
         id: poi?.ID, operator: op, preferred: isPreferredOperator(op), name,
         maxPower_kW: Math.round(pMax), distance_m: distM,
         lat: plat, lon: plon
       });
     }
     enriched.sort((a,b)=>{
       if (a.preferred!==b.preferred) return a.preferred?-1:1;
       const da=a.distance_m??9e9, db=b.distance_m??9e9;
       if (da!==db) return da-db;
       return (b.maxPower_kW||0)-(a.maxPower_kW||0);
     });
     return enriched;
    }
    
    async function maybeCheckHPC(reason, opts){
     opts = opts||{};
     await ss(HPC.LASTWHY, reason||'');
     await ss(HPC.DBG_ERRSHORT, '');
     try{
       const reqIds=[CFG.bluelink.power, CFG.bluelink.charging];
       const missing=listMissing(reqIds); if(missing.length){ await ss(HPC.MISSING,missing.join(', ')); return; } else await ss(HPC.MISSING,'');
    
       let powerKW = toNum(g(CFG.bluelink.power), 0);
       if (CFG.energy.powerIsWattAuto && Math.abs(powerKW)>1000) powerKW/=1000; powerKW=Math.abs(powerKW);
       const charging = toBool(g(CFG.bluelink.charging));
       const {lat,lon}=readLatLon();
       await ss(HPC.DBG_COORD_LAT, lat); await ss(HPC.DBG_COORD_LON, lon);
    
       if (!opts.force){
         if (CFG.hpc.requireChargingForSearch && !charging) return hpcNo('charging=false');
         if (!Number.isFinite(powerKW) || powerKW <= CFG.hpc.thresholdKW) return hpcNo(`power ${powerKW.toFixed(1)} <= ${CFG.hpc.thresholdKW}`);
       }
       if (!Number.isFinite(lat)||!Number.isFinite(lon)||lat===0||lon===0) return hpcNo('invalid coords');
    
       const tNow=nowSec(), since=tNow-lastCheckSec;
       if (!opts.force && lastLat!=null && lastLon!=null){
         const dist=haversineMeters(lastLat,lastLon,lat,lon);
         if (dist<CFG.hpc.coordMinMoveM && since<CFG.hpc.minCheckIntervalSec){ logD(`HPC skip: moved ${Math.round(dist)}m, since ${since}s`); return; }
       }
    
       // Stufe 1: strict
       let q = await fetchOCM(lat,lon,(opts.radiusKmOverride&&isFinite(opts.radiusKmOverride))?Number(opts.radiusKmOverride):CFG.hpc.radiusKm, CFG.hpc.maxResults, 'strict');
       lastCheckSec=tNow; lastLat=lat; lastLon=lon;
       await ss(HPC.DBG_URL, q.url); await ss(HPC.DBG_HTTP, `strict:${q.status}`);
       await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
    
       let enriched = enrichAndFilter(q.json, lat, lon);
       await ss(HPC.DBG_FIL, enriched.length);
    
       // Stufe 2: dcOnly
       if (CFG.hpc.cascadeIfZero && enriched.length===0){
         q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,CFG.hpc.maxResults,'dcOnly');
         await ss(HPC.DBG_URL2, q.url); await ss(HPC.DBG_HTTP, `dcOnly:${q.status}`);
         await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
         enriched = enrichAndFilter(q.json, lat, lon);
         await ss(HPC.DBG_FIL, enriched.length);
       }
       // Stufe 3: all (client ≥ 120 kW)
       if (CFG.hpc.cascadeIfZero && enriched.length===0){
         const old=CFG.hpc.hpcMinKW; CFG.hpc.hpcMinKW=CFG.hpc.cascadeMinKWClient||120;
         q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,Math.max(CFG.hpc.maxResults,120),'all');
         await ss(HPC.DBG_URL3, q.url); await ss(HPC.DBG_HTTP, `all:${q.status}`);
         await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
         enriched = enrichAndFilter(q.json, lat, lon);
         await ss(HPC.DBG_FIL, enriched.length);
         CFG.hpc.hpcMinKW=old;
       }
    
       await ss(HPC.COUNT, enriched.length);
       await ss(HPC.HAS, enriched.length>0);
       await ss(HPC.LASTJSON, JSON.stringify(enriched));
       await ss(HPC.LASTCHK, new Date().toISOString());
       await ss(HPC.LASTERR, '');
    
       if (enriched.length){
         const n=enriched[0];
         await ss(HPC.NAME, n.name||'');
         await ss(HPC.DISTM, n.distance_m||0);
         await ss(HPC.KW, n.maxPower_kW||0);
         await ss(HPC.OP, n.operator||'');
         logI(`HPC nearby: ${n.name} (${n.operator||'–'}), ${n.maxPower_kW} kW, ~${n.distance_m} m`);
       } else {
         await ss(HPC.NAME,''); await ss(HPC.DISTM,0); await ss(HPC.KW,0); await ss(HPC.OP,'');
         logD('HPC: none in radius');
       }
    
       // Dashboard neu rendern
       renderDashboard();
    
     } catch(err){
       await ss(HPC.LASTERR, String(err?.message||err));
       await ss(HPC.DBG_ERRSHORT, String(err?.message||err));
       await ss(HPC.LASTCHK, new Date().toISOString());
       renderDashboard();
     }
    }
    
    /*** ===== ENERGY intern ===== ***/
    let lastTickTs = Date.now(); let idleSince = null;
    function readPowerKW(){ let p=toNum(g(CFG.bluelink.power),0); if (CFG.energy.powerIsWattAuto && Math.abs(p)>1000) p/=1000; return Math.abs(p); }
    function readSocPct(){ const s=g(CFG.bluelink.soc); return s===undefined?null:toNum(s,null); }
    function readChargingActive(){ return toBool(g(CFG.bluelink.charging)); }
    
    async function sessionStart(){
     await ss(EN.ACTIVE,true); await ss(EN.START_TS,new Date().toISOString());
     await ss(EN.END_TS,''); await ss(EN.START_SOC, readSocPct());
     await ss(EN.END_SOC,null); await ss(EN.ENERGY_KWH,0); await ss(EN.ENERGY_SOC_KWH,0);
     renderDashboard();
    }
    async function sessionFinish(reason){
     const nowIso=new Date().toISOString(); const energy=toNum(g(EN.ENERGY_KWH),0);
     await ss(EN.LAST_ENERGY,energy); await ss(EN.LAST_START, g(EN.START_TS)||''); await ss(EN.LAST_END,nowIso);
     await ss(EN.END_TS,nowIso); await ss(EN.ACTIVE,false); await ss(EN.LAST_REASON,`finish:${reason||''}`);
    
     const today=new Date().toISOString().slice(0,10); const keyToday=idJ(EN.ROOT,'daily',today);
     if (!exObj(keyToday)) await es(keyToday,{type:'number',role:'value.energy'},0);
     const curDay=toNum(g(keyToday),0)+energy;
     await ss(keyToday,curDay); await ss(EN.TODAY_KWH,curDay);
     await ss(EN.TOTAL_KWH, toNum(g(EN.TOTAL_KWH),0)+energy);
     renderDashboard();
    }
    async function updateSocEstimate(){
     if (CFG.energy.usableCapacityKWh<=0) return;
     const s0=g(EN.START_SOC), s1=readSocPct(); if (s0==null || s1==null) return;
     const est=((toNum(s1,0)-toNum(s0,0))/100)*CFG.energy.usableCapacityKWh;
     await ss(EN.END_SOC,s1); await ss(EN.ENERGY_SOC_KWH, Math.max(0,est));
    }
    async function integrationTick(){
     try{
       const need=[CFG.bluelink.power, CFG.bluelink.charging]; if (listMissing(need).length) return;
       const active=toBool(g(EN.ACTIVE)), charging=readChargingActive(), powerKW=readPowerKW();
       const now=Date.now(); const dt_h=Math.max(0,(now-lastTickTs)/3600000); lastTickTs=now;
    
       if (!active && charging && powerKW>=CFG.energy.sessionPowerMinKW){ idleSince=null; await sessionStart(); }
       if (active){
         const eAdd=powerKW*dt_h; const eNow=Math.max(0, toNum(g(EN.ENERGY_KWH),0)+eAdd);
         await ss(EN.ENERGY_KWH, eNow); await updateSocEstimate();
         if (powerKW < CFG.energy.sessionPowerMinKW || !charging){
           if (idleSince===null) idleSince=now; const idleSec=(now-idleSince)/1000;
           if (idleSec>=CFG.energy.sessionIdleEndSec){ await sessionFinish(!charging?'charging=false':'power<min'); idleSince=null; }
         } else idleSince=null;
       }
       const todayKey=idJ(EN.ROOT,'daily', new Date().toISOString().slice(0,10));
       if (exObj(todayKey)) await ss(EN.TODAY_KWH, toNum(g(todayKey),0));
     } catch(err){ await ss(EN.LAST_ERR, String(err?.message||err)); }
     renderDashboard();
    }
    
    /*** ===== Dashboard ===== ***/
    async function ensureDashState(){ await es(CFG.dash.htmlState, {type:'string', role:'html'}, ''); }
    
    // Mini Map-Engine (OSM Tiles im Iframe; kein Scroll/Wheel)
    function buildMapIframeHTML(points, centerHint){
     // points: [{lat,lon,label,type:'car'|'hpc'}]
     // centerHint: {lat,lon} optional
     const W=100, H=CFG.dash.heightPx; // Breite 100% via CSS
     const payload = { points: points||[], centerHint: centerHint||null, lockScroll: !CFG.dash.allowScroll };
     const css =
       'html,body{margin:0;height:100%;background:#0b1020}#root{position:relative;width:100%;height:100%}'+
       '.tile{position:absolute}canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}'+
       '.mk{position:absolute;transform:translate(-50%,-100%);padding:3px 6px;border-radius:8px;border:1px solid rgba(148,163,184,.5);background:rgba(13,23,45,.9);color:#e5e7eb;font:12px system-ui,sans-serif;white-space:nowrap}'+
       '.dot{position:absolute;width:10px;height:10px;border-radius:50%;box-shadow:0 0 0 2px rgba(96,165,250,.35)}'+
       '.dot.car{background:#34d399} .dot.hpc{background:#60a5fa} .attr{position:absolute;right:6px;bottom:4px;font:11px system-ui;color:#93a3b8;background:rgba(0,0,0,.35);padding:2px 6px;border-radius:6px}';
     const js =
       '(function(){var DATA=window.__PAYLOAD__||{points:[],centerHint:null,lockScroll:true};'+
       'var root=document.getElementById("root");var W=root.clientWidth,H=root.clientHeight;'+
       'function rad(d){return d*Math.PI/180;} function lat2y(lat){var s=Math.sin(rad(lat));return 0.5-Math.log((1+s)/(1-s))/(4*Math.PI);} function lon2x(lon){return lon/360+0.5;}'+
       'function project(lat,lon,z){var s=256*Math.pow(2,z);return {x:lon2x(lon)*s,y:lat2y(lat)*s};} function deproject(x,y,z){var s=256*Math.pow(2,z);var lon=(x/s-0.5)*360;var n=Math.PI-2*Math.PI*(y/s-0.5);var lat=180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n)));return {lat:lat,lon:lon};}'+
       'function fitBounds(pts){ if(!pts||!pts.length){ return {c:(DATA.centerHint||{lat:51,lon:10}), z:8}; } var minLat=90,maxLat=-90,minLon=180,maxLon=-180; pts.forEach(function(p){if(p.lat<minLat)minLat=p.lat;if(p.lat>maxLat)maxLat=p.lat;if(p.lon<minLon)minLon=p.lon;if(p.lon>maxLon)maxLon=p.lon;}); var c={lat:(minLat+maxLat)/2,lon:(minLon+maxLon)/2}; var z=6; for(var zz=6;zz<=19;zz++){var a=project(maxLat,minLon,zz), b=project(minLat,maxLon,zz); var w=Math.abs(b.x-a.x), h=Math.abs(b.y-a.y); if(w<=W*0.85 && h<=H*0.85) z=zz; else break;} return {c:c,z:z}; }'+
       'var pts=DATA.points||[]; var f=fitBounds(pts.length?pts:(DATA.centerHint?[DATA.centerHint]:[])); var center=f.c, zoom=f.z;'+
       'var world=project(center.lat,center.lon,zoom); var originX=world.x-W/2, originY=world.y-H/2;'+
       'var tiles=document.createElement("div"); tiles.style.position="absolute"; root.appendChild(tiles); var ctx=document.createElement("canvas"); ctx.width=W; ctx.height=H; root.appendChild(ctx); var g=ctx.getContext("2d");'+
       'function wrapX(x,n){return ((x%n)+n)%n;} function toXY(lat,lon){var p=project(lat,lon,zoom); return {x:p.x-originX,y:p.y-originY};}'+
       'function draw(){ tiles.innerHTML=""; var n=Math.pow(2,zoom); var x0=Math.floor(originX/256), y0=Math.floor(originY/256); var x1=Math.floor((originX+W)/256), y1=Math.floor((originY+H)/256); for(var ty=y0;ty<=y1;ty++){ if(ty<0||ty>=n) continue; for(var tx=x0;tx<=x1;tx++){ var wx=wrapX(tx,n); var img=new Image(); img.className="tile"; img.src="https://tile.openstreetmap.org/"+zoom+"/"+wx+"/"+ty+".png"; img.width=256; img.height=256; img.style.left=(tx*256-originX)+"px"; img.style.top=(ty*256-originY)+"px"; tiles.appendChild(img);} } g.clearRect(0,0,W,H);'+
       '  (DATA.points||[]).forEach(function(p){ var q=toXY(p.lat,p.lon); var d=document.createElement("div"); d.className="dot "+(p.type||"hpc"); d.style.left=q.x+"px"; d.style.top=q.y+"px"; root.appendChild(d); var m=document.createElement("div"); m.className="mk"; m.style.left=q.x+"px"; m.style.top=(q.y-12)+"px"; m.textContent=p.label||""; root.appendChild(m); });'+
       '  var attr=document.createElement("div"); attr.className="attr"; attr.textContent="© OpenStreetMap-Mitwirkende"; root.appendChild(attr);'+
       '} draw();'+
       (CFG.dash.allowScroll ? '' : ' root.addEventListener("wheel", function(ev){ev.preventDefault();}, {passive:false}); ') +
       '})();';
     const srcdoc = '<!doctype html><html><head><meta charset="utf-8"><style>'+css+'</style></head><body><div id="root" style="width:100%;height:100%"></div><script>window.__PAYLOAD__='+JSON.stringify(payload)+'<\/script><script>'+js+'<\/script></body></html>';
     return '<iframe style="width:100%;height:'+H+'px;border:0;border-radius:12px;overflow:hidden;background:#0b1020" srcdoc="'+
            String(srcdoc).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')+'"></iframe>';
    }
    
    function fmtEnergy(n){ return (n==null||isNaN(n)) ? '0.0' : Number(n).toFixed(2); }
    function fmtDist(m){ if (m==null||isNaN(m)) return '-'; return (m>=1000)?(m/1000).toFixed(2)+' km': Math.round(m)+' m'; }
    
    // Render: liest aktuelle States + erzeugt HTML in CFG.dash.htmlState
    async function renderDashboard(centerOverride){
     try{
       await ensureDashState();
    
       const car = readLatLon();
       const nearestName = String(g(HPC.NAME)||'–');
       const nearestOp   = String(g(HPC.OP)||'');
       const nearestKW   = toNum(g(HPC.KW),0);
       const nearestDist = toNum(g(HPC.DISTM),0);
       const count       = toNum(g(HPC.COUNT),0);
    
       const sessActive  = toBool(g(EN.ACTIVE));
       const eSess       = fmtEnergy(toNum(g(EN.ENERGY_KWH),0));
       const eSoc        = fmtEnergy(toNum(g(EN.ENERGY_SOC_KWH),0));
       const eToday      = fmtEnergy(toNum(g(EN.TODAY_KWH),0));
       const eTotal      = fmtEnergy(toNum(g(EN.TOTAL_KWH),0));
    
       // Punkte für Map: Auto + bis zu 12 HPC
       let list=[]; try{ list = JSON.parse(String(g(HPC.LASTJSON)||'[]')); }catch(e){ list=[]; }
       const top = (Array.isArray(list)?list:[]).slice(0,12);
       const points = [];
       if (Number.isFinite(car.lat) && Number.isFinite(car.lon) && car.lat && car.lon){
         points.push({lat:car.lat, lon:car.lon, label:'Car', type:'car'});
       }
       for (const x of top){
         if (!Number.isFinite(x.lat)||!Number.isFinite(x.lon)) continue;
         const lbl = (x.name||'HPC') + ' · ' + (x.maxPower_kW||'?') + ' kW';
         points.push({lat:x.lat, lon:x.lon, label:lbl, type:'hpc'});
       }
    
       // SelfTest-Fix: falls centerOverride gesetzt -> nehmen. Sonst fitBounds macht’s automatisch auf Car+HPC
       const map = buildMapIframeHTML(points, centerOverride || null);
    
       const css =
         '.wrap{color:#e5e7eb;font:14px system-ui,-apple-system,Segoe UI,Roboto;line-height:1.4;background:#0b1020;padding:10px;border-radius:14px;border:1px solid #334155}'+
         '.row{display:flex;flex-wrap:wrap;gap:10px;margin:0 0 10px}'+
         '.chip{background:#111827;border:1px solid #374151;border-radius:999px;padding:6px 10px;font-weight:700}'+
         '.muted{opacity:.8;font-weight:600} .title{font-weight:800;font-size:14px}'+
         '.list{margin-top:10px;border-top:1px solid #334155;padding-top:6px} .item{padding:6px 0;border-bottom:1px dashed #334155}'+
         '.item:last-child{border-bottom:0} .badge{font-weight:700} .ok{color:#34d399} .warn{color:#f59e0b}';
    
       const listHtml = top.map(o=>{
         const pref = o.preferred ? ' • ★' : '';
         return `<div class="item">• <span class="title">${o.name||'-'}</span> <span class="muted">(${o.operator||'–'}${pref})</span><br/>
           <span class="muted">${o.maxPower_kW||'?'} kW · ${fmtDist(o.distance_m)}</span></div>`;
       }).join('');
    
       const headChips =
         `<div class="row">
           <span class="chip">Nearest: <span class="badge">${nearestName}</span> <span class="muted">(${nearestOp||'–'})</span></span>
           <span class="chip">Power: ${nearestKW||0} kW</span>
           <span class="chip">Distance: ${fmtDist(nearestDist)}</span>
           <span class="chip">Spots: ${count}</span>
           <span class="chip">Session: ${eSess} kWh</span>
           <span class="chip">Today: ${eToday} kWh</span>
           <span class="chip">Total: ${eTotal} kWh</span>
           <span class="chip">Car Pos: ${Number(car.lat||0).toFixed(5)}, ${Number(car.lon||0).toFixed(5)}</span>
           <span class="chip">Active: <span class="${sessActive?'ok':'warn'}">${sessActive?'yes':'no'}</span></span>
         </div>`;
    
       const html = `<div class="wrap">${headChips}${map}<div class="list">${listHtml||'<div class="muted">Keine Stationen.</div>'}</div></div>`;
       await ss(CFG.dash.htmlState, `<style>${css}</style>` + html);
     } catch(e){ /* noop */ }
    }
    
    /*** ===== Subscriptions / Scheduler ===== ***/
    function attachTriggers(){
     // HPC triggers
     on({id: CFG.bluelink.power,    change:'ne'}, ()=> maybeCheckHPC('power_changed'));
     on({id: CFG.bluelink.charging, change:'ne'}, ()=> maybeCheckHPC('charging_changed'));
     on({id: CFG.bluelink.lat,      change:'ne'}, ()=> maybeCheckHPC('lat_changed'));
     on({id: CFG.bluelink.lon,      change:'ne'}, ()=> maybeCheckHPC('lon_changed'));
     on({id: CFG.bluelink.latAlt,   change:'ne'}, ()=> maybeCheckHPC('lat_changed_alt'));
     on({id: CFG.bluelink.lonAlt,   change:'ne'}, ()=> maybeCheckHPC('lon_changed_alt'));
    
     // Energy ticker
     schedule(`*/${CFG.energy.integTickSec} * * * * *`, integrationTick);
    
     // Periodische HPC-Abfrage
     schedule('*/5 * * * *', ()=> maybeCheckHPC('scheduled'));
    
     // Test-Button
     on({id: HPC.CMD_TEST, change:'ne'}, async (s)=>{
       if (s && s.state && s.state.val===true){
         await ss(HPC.CMD_TEST,false);
         const rOverride = toNum(g(HPC.TEST_RADIUS), NaN);
         await maybeCheckHPC('manual_test', { force:true, radiusKmOverride: isFinite(rOverride)?rOverride:undefined });
       }
     });
    
     // SelfTests – **fixes map centering**: wir übergeben centerOverride
     function selfTest(lat, lon, label){
       return async (s)=>{
         if (s && s.state && s.state.val===true){
           await ss(s.id,false);
           // Debug-Anzeige der Test-Geo
           await ss(HPC.DBG_COORD_LAT, lat); await ss(HPC.DBG_COORD_LON, lon);
           // Direkte OCM-Abfrage (wie maybeCheckHPC, aber ohne Rate-Limit & mit Center-Override)
           try{
             let q = await fetchOCM(lat,lon,Math.max(20,CFG.hpc.radiusKm),Math.max(80,CFG.hpc.maxResults),'strict');
             await ss(HPC.DBG_URL, q.url); await ss(HPC.DBG_HTTP, `self:${q.status}`); await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
             let enriched = enrichAndFilter(q.json, lat, lon);
             if (enriched.length===0){
               q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,CFG.hpc.maxResults,'dcOnly');
               enriched = enrichAndFilter(q.json, lat, lon);
             }
             if (enriched.length===0){
               const old=CFG.hpc.hpcMinKW; CFG.hpc.hpcMinKW=CFG.hpc.cascadeMinKWClient||120;
               q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,Math.max(CFG.hpc.maxResults,120),'all');
               enriched = enrichAndFilter(q.json, lat, lon);
               CFG.hpc.hpcMinKW=old;
             }
             await ss(HPC.COUNT, enriched.length);
             await ss(HPC.HAS, enriched.length>0);
             await ss(HPC.LASTJSON, JSON.stringify(enriched));
             if (enriched.length){
               const n=enriched[0];
               await ss(HPC.NAME, n.name||''); await ss(HPC.DISTM, n.distance_m||0);
               await ss(HPC.KW, n.maxPower_kW||0); await ss(HPC.OP, n.operator||'');
             } else {
               await ss(HPC.NAME,''); await ss(HPC.DISTM,0); await ss(HPC.KW,0); await ss(HPC.OP,'');
             }
             await ss(HPC.LASTCHK, new Date().toISOString()); await ss(HPC.LASTERR,''); await ss(HPC.LASTWHY, 'selftest_'+label);
             // **WICHTIG**: Karte explizit um die Test-Geo zentrieren
             renderDashboard({lat:lat, lon:lon});
           } catch(err){
             await ss(HPC.LASTERR, String(err?.message||err));
             await ss(HPC.DBG_ERRSHORT, String(err?.message||err));
             renderDashboard({lat:lat, lon:lon});
           }
         }
       };
     }
     on({id:HPC.CMD_TEST_FFM,change:'ne'}, selfTest(50.1109, 8.6821, 'FFM'));
     on({id:HPC.CMD_TEST_CGN,change:'ne'}, selfTest(50.9375, 6.9603, 'CGN'));
     on({id:HPC.CMD_TEST_MUC,change:'ne'}, selfTest(48.1372,11.5756, 'MUC'));
    }
    
    /*** ===== Bootstrap ===== ***/
    async function init(){
     await ensureHpcStates();
     await ensureEnergyStates();
     await ensureDashState();
     // kleiner Delay, dann initial laden + rendern
     setTimeout(()=>{ maybeCheckHPC('startup'); integrationTick(); }, 1200);
     attachTriggers();
     logI('Init: ready');
    }
    init();
    
    

    Meister MopperM 1 Antwort Letzte Antwort
    1
    • ilovegymI ilovegym

      @arteck @Meister-Mopper und alle anderen.. 🙂

      Habe hier das oben von mir gerade erfundene Script, das den naechsten HPC und Ladeleistung etc raussucht, ausrechnet und in einem Dashboard darstellt, mal gemacht. Laeuft hier unter javascript 8.9.2 ohne irgendwelche Plugins ohne warnings/errors, auch die States werden quiet angelegt ( outHPCRoot und outEnergyRoot und das Dashboard kann am Anfang vom Script konfiguriert werden. Ebenso die States vom bluelink-Adapter, koennen auch aehnlich sein, je nach Fahrzeugtyp.)
      Zur Abfrage von OpenchargeMap sollte ein kostenloser Api-Key eingetragen sein, link im Script.

      Das Dashboard stelle ich mit MinuVis (Widget HTML auf den State) dar. Geht auch garantiert in allen anderen Vis-Varianten.

      Hier das Script:

      /**************************************************************
      * IONIQ 5 / Bluelink – HPC Nearby + Energy Integration + Dashboard
      * ioBroker JavaScript-Adapter >= 8.9.2 (Node 18+)
      * Version: 1.3.0 (selftest map centering, energy tiles, vis path)
      * (c) by ilovegym66
      **************************************************************/
      'use strict';
      
      const https = require('https');
      
      /*** ===== KONFIG ===== ***/
      const CFG = {
       bluelink: {
         power:    'bluelink.0.KMHKRxxxx.vehicleStatusRaw.ccs2Status.state.Vehicle.Green.Electric.SmartGrid.RealTimePower',
         charging: 'bluelink.0.KMHKRxxxx.vehicleStatus.battery.charge',
         soc:      'bluelink.0.KMHKRxxxx.vehicleStatus.battery.soc',
         lat:      'bluelink.0.KMHKRxxxx.vehicleLocation.lat',
         lon:      'bluelink.0.KMHKRxxxx.vehicleLocation.lon',
         latAlt:   'bluelink.0.KMHKRxxxx.vehicleLocation.latitude',
         lonAlt:   'bluelink.0.KMHKRxxxx.vehicleLocation.longitude'
       },
      
       hpc: {
         thresholdKW: 0,        // Suche auch ohne hohe Ladeleistung erlauben
         hpcMinKW:    149,      // Client-Filter Mindestleistung (kaskadiert runter falls 0 Treffer)
         radiusKm:    20,
         maxResults:  200,
         coordMinMoveM: 30,
         minCheckIntervalSec: 30,
         preferredOperators: ['ionity','enbw','aral pulse','fastned','shell recharge','allego','mer','ewe go','totalenergies','eviny','maingau','entega','pfalzwerke','tesla'],
         ocmEndpoint: 'https://api.openchargemap.io/v3/poi/',
         ocmApiKey:   'DEIN-OCM-KEY-HIER', // <— eintragen oder per State lesen (siehe unten)
         requireChargingForSearch: false,
      
         // Serverseitige Filter (erste Stufe „strict“)
         apiFilterFastDC: true,     // Level 3 + DC
         apiMinPowerKW:   120,
      
         // Kaskade bei 0 Treffern
         cascadeIfZero: true,
         cascadeMinKWClient: 120
       },
      
       energy: {
         powerIsWattAuto:    true,
         sessionPowerMinKW:  0.5,
         integTickSec:       10,
         sessionIdleEndSec:  180,
         usableCapacityKWh:  84
       },
      
       // Dashboard-Ausgabe (zurück in vis-Pfad)
       dash: {
         htmlState: '0_userdata.0.vis.Dashboards.HPC.HTML', // <- hier landet das fertige HTML
         allowScroll: false,    // MinuVis: kein Scroll/Wheel
         heightPx: 420          // Kartenhöhe
       },
      
       // Ausgabe-Roots (States)
       outHpcRoot:    '0_userdata.0.Cars.HPCNearby',
       outEnergyRoot: '0_userdata.0.Cars.Energy',
      
       debug: true
      };
      
      // OPTIONAL: OCM-Key aus State laden (wenn gewünscht)
      // try { const s=getState('0_userdata.0.secrets.ocmApiKey'); if (s && s.val) CFG.hpc.ocmApiKey = String(s.val); } catch(e){}
      
      /*** ===== Utils ===== ***/
      const idJ = (...a)=>a.join('.');
      const logI = m => log(`[Ioniq-HPC+Energy] ${m}`, 'info');
      const logD = m => CFG.debug && log(`[Ioniq-HPC+Energy] ${m}`, 'debug');
      const nowSec = ()=>Math.floor(Date.now()/1000);
      
      function exObj(id){ try{ return existsObject(id); }catch(e){ return false; } }
      function exState(id){ try{ return existsState(id); }catch(e){ return false; } }
      function g(id){
       try{
         if (!exState(id)) return undefined;
         const s = getState(id);
         return s ? s.val : undefined;
       }catch(e){ return undefined; }
      }
      async function es(id, common, init){ try{ if (!exObj(id)) await createStateAsync(id, common, init ?? null); }catch(e){} }
      async function ss(id, val){ try{ await setStateAsync(id, {val, ack:true}); }catch(e){} }
      function toNum(x, d=0){ const n = Number(x); return Number.isFinite(n) ? n : d; }
      function toBool(x){ return x===true || x===1 || x==='1' || String(x).toLowerCase()==='true'; }
      
      /*** ===== Haversine (m) ===== ***/
      function haversineMeters(lat1, lon1, lat2, lon2){
       const R=6371000, rad=d=>d*Math.PI/180;
       const dLat=rad(lat2-lat1), dLon=rad(lon2-lon1);
       const a=Math.sin(dLat/2)**2+Math.cos(rad(lat1))*Math.cos(rad(lat2))*Math.sin(dLon/2)**2;
       return 2*R*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));
      }
      
      /*** ====== OUTPUT STATES: HPC ====== ***/
      const HPC = {
       ROOT:     CFG.outHpcRoot,
       HAS:      idJ(CFG.outHpcRoot,'hasHPCNearby'),
       COUNT:    idJ(CFG.outHpcRoot,'count'),
       NAME:     idJ(CFG.outHpcRoot,'nearest.name'),
       DISTM:    idJ(CFG.outHpcRoot,'nearest.distance_m'),
       KW:       idJ(CFG.outHpcRoot,'nearest.maxPower_kW'),
       OP:       idJ(CFG.outHpcRoot,'nearest.operator'),
       LASTJSON: idJ(CFG.outHpcRoot,'lastResultJson'),
       LASTCHK:  idJ(CFG.outHpcRoot,'lastCheck'),
       LASTWHY:  idJ(CFG.outHpcRoot,'lastReason'),
       LASTERR:  idJ(CFG.outHpcRoot,'lastError'),
       MISSING:  idJ(CFG.outHpcRoot,'debug.missingStates'),
      
       // Debug
       DBG_URL:   idJ(CFG.outHpcRoot,'debug.lastQueryUrl'),
       DBG_URL2:  idJ(CFG.outHpcRoot,'debug.lastQueryUrl_swapped'),
       DBG_URL3:  idJ(CFG.outHpcRoot,'debug.lastQueryUrl_wide'),
       DBG_RAW:   idJ(CFG.outHpcRoot,'debug.rawCount'),
       DBG_FIL:   idJ(CFG.outHpcRoot,'debug.filteredCount'),
       DBG_SAMPLE:idJ(CFG.outHpcRoot,'debug.sampleJson'),
       DBG_COORD_LAT: idJ(CFG.outHpcRoot,'debug.lastLat'),
       DBG_COORD_LON: idJ(CFG.outHpcRoot,'debug.lastLon'),
       DBG_HTTP:      idJ(CFG.outHpcRoot,'debug.lastHttpStatus'),
       DBG_ERRSHORT:  idJ(CFG.outHpcRoot,'debug.lastErrorShort'),
      
       // Commands
       CMD_TEST:      idJ(CFG.outHpcRoot,'cmd.TestSearch'),
       TEST_RADIUS:   idJ(CFG.outHpcRoot,'cmd.TestRadiusKm'),
       CMD_TEST_FFM:  idJ(CFG.outHpcRoot,'cmd.SelfTest_Frankfurt'),
       CMD_TEST_CGN:  idJ(CFG.outHpcRoot,'cmd.SelfTest_Koeln'),
       CMD_TEST_MUC:  idJ(CFG.outHpcRoot,'cmd.SelfTest_Muenchen')
      };
      
      async function ensureHpcStates(){
       await es(HPC.HAS,{type:'boolean',role:'indicator'},false);
       await es(HPC.COUNT,{type:'number',role:'value'},0);
       await es(HPC.NAME,{type:'string',role:'text'},'');
       await es(HPC.DISTM,{type:'number',role:'value'},0);
       await es(HPC.KW,{type:'number',role:'value'},0);
       await es(HPC.OP,{type:'string',role:'text'},'');
       await es(HPC.LASTJSON,{type:'string',role:'json'},'[]');
       await es(HPC.LASTCHK,{type:'string',role:'text'},'');
       await es(HPC.LASTWHY,{type:'string',role:'text'},'');
       await es(HPC.LASTERR,{type:'string',role:'text'},'');
       await es(HPC.MISSING,{type:'string',role:'text'},'');
      
       await es(HPC.DBG_URL,{type:'string',role:'text'},'');
       await es(HPC.DBG_URL2,{type:'string',role:'text'},'');
       await es(HPC.DBG_URL3,{type:'string',role:'text'},'');
       await es(HPC.DBG_RAW,{type:'number',role:'value'},0);
       await es(HPC.DBG_FIL,{type:'number',role:'value'},0);
       await es(HPC.DBG_SAMPLE,{type:'string',role:'json'},'');
       await es(HPC.DBG_COORD_LAT,{type:'number',role:'value.gps'},0);
       await es(HPC.DBG_COORD_LON,{type:'number',role:'value.gps'},0);
       await es(HPC.DBG_HTTP,{type:'string',role:'text'},'');
       await es(HPC.DBG_ERRSHORT,{type:'string',role:'text'},'');
      
       await es(HPC.CMD_TEST,{type:'boolean',role:'button'},false);
       await es(HPC.TEST_RADIUS,{type:'number',role:'value'},NaN);
       await es(HPC.CMD_TEST_FFM,{type:'boolean',role:'button'},false);
       await es(HPC.CMD_TEST_CGN,{type:'boolean',role:'button'},false);
       await es(HPC.CMD_TEST_MUC,{type:'boolean',role:'button'},false);
      }
      
      /*** ====== OUTPUT STATES: ENERGY ====== ***/
      const EN = {
       ROOT:          CFG.outEnergyRoot,
       ACTIVE:        idJ(CFG.outEnergyRoot,'session.active'),
       START_TS:      idJ(CFG.outEnergyRoot,'session.startTs'),
       END_TS:        idJ(CFG.outEnergyRoot,'session.endTs'),
       START_SOC:     idJ(CFG.outEnergyRoot,'session.startSoC'),
       END_SOC:       idJ(CFG.outEnergyRoot,'session.endSoC'),
       ENERGY_KWH:    idJ(CFG.outEnergyRoot,'session.energy_kWh'),
       ENERGY_SOC_KWH:idJ(CFG.outEnergyRoot,'session.energySoc_kWh'),
       LAST_ENERGY:   idJ(CFG.outEnergyRoot,'lastSession.energy_kWh'),
       LAST_START:    idJ(CFG.outEnergyRoot,'lastSession.startTs'),
       LAST_END:      idJ(CFG.outEnergyRoot,'lastSession.endTs'),
       TODAY_KWH:     idJ(CFG.outEnergyRoot,'today.energy_kWh'),
       TOTAL_KWH:     idJ(CFG.outEnergyRoot,'total.energy_kWh'),
       LAST_REASON:   idJ(CFG.outEnergyRoot,'debug.lastReason'),
       LAST_ERR:      idJ(CFG.outEnergyRoot,'debug.lastError')
      };
      async function ensureEnergyStates(){
       await es(EN.ACTIVE,        {type:'boolean', role:'indicator'}, false);
       await es(EN.START_TS,      {type:'string',  role:'text'}, '');
       await es(EN.END_TS,        {type:'string',  role:'text'}, '');
       await es(EN.START_SOC,     {type:'number',  role:'value'}, null);
       await es(EN.END_SOC,       {type:'number',  role:'value'}, null);
       await es(EN.ENERGY_KWH,    {type:'number',  role:'value.energy'}, 0);
       await es(EN.ENERGY_SOC_KWH,{type:'number',  role:'value.energy'}, 0);
       await es(EN.LAST_ENERGY,   {type:'number',  role:'value.energy'}, 0);
       await es(EN.LAST_START,    {type:'string',  role:'text'}, '');
       await es(EN.LAST_END,      {type:'string',  role:'text'}, '');
       await es(EN.TODAY_KWH,     {type:'number',  role:'value.energy'}, 0);
       await es(EN.TOTAL_KWH,     {type:'number',  role:'value.energy'}, 0);
       await es(EN.LAST_REASON,   {type:'string',  role:'text'}, '');
       await es(EN.LAST_ERR,      {type:'string',  role:'text'}, '');
      }
      
      /*** ===== OCM Helper ===== ***/
      function isPreferredOperator(op){
       if (!op) return false;
       const t = String(op).toLowerCase();
       return CFG.hpc.preferredOperators.some(x => t.includes(String(x).toLowerCase()));
      }
      function powerFromConn(c){
       if (!c) return 0;
       let pk = toNum(c.PowerKW, NaN);
       if (!Number.isFinite(pk)) pk = toNum(c.RatedPowerKW, NaN);
       if (!Number.isFinite(pk)) pk = toNum(c.Power, NaN);
       if (Number.isFinite(pk) && pk > 0) return pk;
      
       let v = toNum(c.Voltage, NaN); if (!Number.isFinite(v)) v = toNum(c.Volts, NaN);
       let a = toNum(c.Amps, NaN);    if (!Number.isFinite(a)) a = toNum(c.Current, NaN);
       if (Number.isFinite(v) && Number.isFinite(a) && v>0 && a>0) return (v*a)/1000;
      
       const levelId  = toNum(c.LevelID, NaN) || toNum(c?.Level?.ID, NaN) || 0;
       const currentId= toNum(c.CurrentTypeID, NaN) || toNum(c?.CurrentType?.ID, NaN) || 0;
       const lvlTitle = String(c?.Level?.Title||'').toLowerCase();
       const curTitle = String(c?.CurrentType?.Title||'').toLowerCase();
       const lvlFast  = levelId >= 3 || lvlTitle.includes('3');
       const isDC     = currentId === 30 || curTitle.includes('dc');
       if (lvlFast && isDC) return 150; // Fallback
       return 0;
      }
      function extractMaxPowerKW(poi){
       const arr = Array.isArray(poi?.Connections) ? poi.Connections : [];
       let max = 0;
       for (const c of arr){ const p = powerFromConn(c); if (p > max) max = p; }
       return max;
      }
      
      /*** HTTP ***/
      function buildOcmUrlStr(lat, lon, radiusKm, maxResults, opts){
       opts = opts || {}; // { fastDC?:boolean, minPowerKW?:number }
       function add(q, k, v){ if (v===undefined||v===null||v==='') return q; q.push(encodeURIComponent(k)+'='+encodeURIComponent(String(v))); return q; }
       const base = String(CFG.hpc.ocmEndpoint||'https://api.openchargemap.io/v3/poi/').replace(/\?+.*/, '');
       const params = [];
       add(params,'output','json');
       add(params,'latitude', lat);
       add(params,'longitude', lon);
       add(params,'distance', radiusKm);
       add(params,'distanceunit','KM');
       add(params,'maxresults', maxResults || CFG.hpc.maxResults);
       add(params,'compact','false'); add(params,'verbose','true');
       if (opts.fastDC){ add(params,'levelid',3); add(params,'currenttypeid',30); if (opts.minPowerKW!=null) add(params,'minpowerkw', opts.minPowerKW); }
       if (CFG.hpc.ocmApiKey && CFG.hpc.ocmApiKey.trim()){ add(params,'key', CFG.hpc.ocmApiKey.trim()); }
       return base + '?' + params.join('&');
      }
      function httpGetJson(urlStr){
       return new Promise((resolve, reject) => {
         const headers = { 'User-Agent':'ioBroker-HPC-Nearby/1.3 (+contact:local)' };
         // keinen X-API-Key Header verwenden – Key steckt in URL
         const req = https.get(urlStr, { headers, timeout: 12000 }, (res) => {
           let data=''; res.on('data', c=>data+=c);
           res.on('end', ()=>{ if (res.statusCode>=200 && res.statusCode<300) { try{ resolve({json:JSON.parse(data), status:res.statusCode}); } catch(e){ reject(new Error('OCM JSON parse error: '+e.message)); } } else reject(new Error('OCM HTTP '+res.statusCode)); });
         });
         req.on('timeout', ()=>req.destroy(new Error('timeout')));
         req.on('error', reject);
       });
      }
      async function fetchOCM(lat, lon, radiusKm, maxResults, mode){
       let url;
       if (mode==='strict') url = buildOcmUrlStr(lat,lon,radiusKm,maxResults,{fastDC:true,minPowerKW:CFG.hpc.apiMinPowerKW});
       else if (mode==='dcOnly') url = buildOcmUrlStr(lat,lon,radiusKm,maxResults,{fastDC:true});
       else url = buildOcmUrlStr(lat,lon,radiusKm,maxResults,{});
       const r = await httpGetJson(url);
       return { mode, url, status:r.status, json:r.json };
      }
      
      /*** ===== HPC intern ===== ***/
      let lastCheckSec = 0, lastLat = null, lastLon = null;
      function readLatLon(){
       let lat = g(CFG.bluelink.lat), lon = g(CFG.bluelink.lon);
       if ((lat===undefined||lat===null) && exState(CFG.bluelink.latAlt)) lat = g(CFG.bluelink.latAlt);
       if ((lon===undefined||lon===null) && exState(CFG.bluelink.lonAlt)) lon = g(CFG.bluelink.lonAlt);
       return {lat: toNum(lat, 0), lon: toNum(lon, 0)};
      }
      function listMissing(ids){ return ids.filter(id => !exState(id)); }
      
      async function hpcNo(reason){
       await ss(HPC.HAS,false); await ss(HPC.COUNT,0);
       await ss(HPC.NAME,''); await ss(HPC.DISTM,0); await ss(HPC.KW,0); await ss(HPC.OP,'');
       await ss(HPC.LASTJSON,'[]'); await ss(HPC.LASTCHK,new Date().toISOString());
       await ss(HPC.LASTERR,''); await ss(HPC.LASTWHY,reason||'');
       await ss(HPC.DBG_RAW,0); await ss(HPC.DBG_FIL,0);
       await ss(HPC.DBG_URL,''); await ss(HPC.DBG_URL2,''); await ss(HPC.DBG_URL3,''); await ss(HPC.DBG_HTTP,''); await ss(HPC.DBG_ERRSHORT,'');
      }
      
      function enrichAndFilter(json, lat, lon){
       const enriched=[];
       for (const poi of Array.isArray(json)?json:[]){
         const pMax = extractMaxPowerKW(poi);
         const conns = Array.isArray(poi?.Connections)?poi.Connections:[];
         const isDCfast = conns.some(c=>{
           const lvl = toNum(c?.LevelID, NaN) || toNum(c?.Level?.ID, NaN) || 0;
           const cur = toNum(c?.CurrentTypeID, NaN) || toNum(c?.CurrentType?.ID, NaN) || 0;
           const lvlTitle = String(c?.Level?.Title||'').toLowerCase();
           const curTitle = String(c?.CurrentType?.Title||'').toLowerCase();
           const lvlFast = lvl >= 3 || lvlTitle.includes('3'); const isDC = cur === 30 || curTitle.includes('dc');
           return lvlFast && isDC;
         });
         if (!(pMax >= CFG.hpc.hpcMinKW || (pMax === 0 && isDCfast))) continue;
      
         const op   = poi?.OperatorInfo?.Title || '';
         const name = poi?.AddressInfo?.Title || '';
         const plat = toNum(poi?.AddressInfo?.Latitude, NaN);
         const plon = toNum(poi?.AddressInfo?.Longitude, NaN);
         const distM = (Number.isFinite(plat)&&Number.isFinite(plon))?Math.round(haversineMeters(lat,lon,plat,plon)):null;
         enriched.push({
           id: poi?.ID, operator: op, preferred: isPreferredOperator(op), name,
           maxPower_kW: Math.round(pMax), distance_m: distM,
           lat: plat, lon: plon
         });
       }
       enriched.sort((a,b)=>{
         if (a.preferred!==b.preferred) return a.preferred?-1:1;
         const da=a.distance_m??9e9, db=b.distance_m??9e9;
         if (da!==db) return da-db;
         return (b.maxPower_kW||0)-(a.maxPower_kW||0);
       });
       return enriched;
      }
      
      async function maybeCheckHPC(reason, opts){
       opts = opts||{};
       await ss(HPC.LASTWHY, reason||'');
       await ss(HPC.DBG_ERRSHORT, '');
       try{
         const reqIds=[CFG.bluelink.power, CFG.bluelink.charging];
         const missing=listMissing(reqIds); if(missing.length){ await ss(HPC.MISSING,missing.join(', ')); return; } else await ss(HPC.MISSING,'');
      
         let powerKW = toNum(g(CFG.bluelink.power), 0);
         if (CFG.energy.powerIsWattAuto && Math.abs(powerKW)>1000) powerKW/=1000; powerKW=Math.abs(powerKW);
         const charging = toBool(g(CFG.bluelink.charging));
         const {lat,lon}=readLatLon();
         await ss(HPC.DBG_COORD_LAT, lat); await ss(HPC.DBG_COORD_LON, lon);
      
         if (!opts.force){
           if (CFG.hpc.requireChargingForSearch && !charging) return hpcNo('charging=false');
           if (!Number.isFinite(powerKW) || powerKW <= CFG.hpc.thresholdKW) return hpcNo(`power ${powerKW.toFixed(1)} <= ${CFG.hpc.thresholdKW}`);
         }
         if (!Number.isFinite(lat)||!Number.isFinite(lon)||lat===0||lon===0) return hpcNo('invalid coords');
      
         const tNow=nowSec(), since=tNow-lastCheckSec;
         if (!opts.force && lastLat!=null && lastLon!=null){
           const dist=haversineMeters(lastLat,lastLon,lat,lon);
           if (dist<CFG.hpc.coordMinMoveM && since<CFG.hpc.minCheckIntervalSec){ logD(`HPC skip: moved ${Math.round(dist)}m, since ${since}s`); return; }
         }
      
         // Stufe 1: strict
         let q = await fetchOCM(lat,lon,(opts.radiusKmOverride&&isFinite(opts.radiusKmOverride))?Number(opts.radiusKmOverride):CFG.hpc.radiusKm, CFG.hpc.maxResults, 'strict');
         lastCheckSec=tNow; lastLat=lat; lastLon=lon;
         await ss(HPC.DBG_URL, q.url); await ss(HPC.DBG_HTTP, `strict:${q.status}`);
         await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
      
         let enriched = enrichAndFilter(q.json, lat, lon);
         await ss(HPC.DBG_FIL, enriched.length);
      
         // Stufe 2: dcOnly
         if (CFG.hpc.cascadeIfZero && enriched.length===0){
           q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,CFG.hpc.maxResults,'dcOnly');
           await ss(HPC.DBG_URL2, q.url); await ss(HPC.DBG_HTTP, `dcOnly:${q.status}`);
           await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
           enriched = enrichAndFilter(q.json, lat, lon);
           await ss(HPC.DBG_FIL, enriched.length);
         }
         // Stufe 3: all (client ≥ 120 kW)
         if (CFG.hpc.cascadeIfZero && enriched.length===0){
           const old=CFG.hpc.hpcMinKW; CFG.hpc.hpcMinKW=CFG.hpc.cascadeMinKWClient||120;
           q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,Math.max(CFG.hpc.maxResults,120),'all');
           await ss(HPC.DBG_URL3, q.url); await ss(HPC.DBG_HTTP, `all:${q.status}`);
           await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
           enriched = enrichAndFilter(q.json, lat, lon);
           await ss(HPC.DBG_FIL, enriched.length);
           CFG.hpc.hpcMinKW=old;
         }
      
         await ss(HPC.COUNT, enriched.length);
         await ss(HPC.HAS, enriched.length>0);
         await ss(HPC.LASTJSON, JSON.stringify(enriched));
         await ss(HPC.LASTCHK, new Date().toISOString());
         await ss(HPC.LASTERR, '');
      
         if (enriched.length){
           const n=enriched[0];
           await ss(HPC.NAME, n.name||'');
           await ss(HPC.DISTM, n.distance_m||0);
           await ss(HPC.KW, n.maxPower_kW||0);
           await ss(HPC.OP, n.operator||'');
           logI(`HPC nearby: ${n.name} (${n.operator||'–'}), ${n.maxPower_kW} kW, ~${n.distance_m} m`);
         } else {
           await ss(HPC.NAME,''); await ss(HPC.DISTM,0); await ss(HPC.KW,0); await ss(HPC.OP,'');
           logD('HPC: none in radius');
         }
      
         // Dashboard neu rendern
         renderDashboard();
      
       } catch(err){
         await ss(HPC.LASTERR, String(err?.message||err));
         await ss(HPC.DBG_ERRSHORT, String(err?.message||err));
         await ss(HPC.LASTCHK, new Date().toISOString());
         renderDashboard();
       }
      }
      
      /*** ===== ENERGY intern ===== ***/
      let lastTickTs = Date.now(); let idleSince = null;
      function readPowerKW(){ let p=toNum(g(CFG.bluelink.power),0); if (CFG.energy.powerIsWattAuto && Math.abs(p)>1000) p/=1000; return Math.abs(p); }
      function readSocPct(){ const s=g(CFG.bluelink.soc); return s===undefined?null:toNum(s,null); }
      function readChargingActive(){ return toBool(g(CFG.bluelink.charging)); }
      
      async function sessionStart(){
       await ss(EN.ACTIVE,true); await ss(EN.START_TS,new Date().toISOString());
       await ss(EN.END_TS,''); await ss(EN.START_SOC, readSocPct());
       await ss(EN.END_SOC,null); await ss(EN.ENERGY_KWH,0); await ss(EN.ENERGY_SOC_KWH,0);
       renderDashboard();
      }
      async function sessionFinish(reason){
       const nowIso=new Date().toISOString(); const energy=toNum(g(EN.ENERGY_KWH),0);
       await ss(EN.LAST_ENERGY,energy); await ss(EN.LAST_START, g(EN.START_TS)||''); await ss(EN.LAST_END,nowIso);
       await ss(EN.END_TS,nowIso); await ss(EN.ACTIVE,false); await ss(EN.LAST_REASON,`finish:${reason||''}`);
      
       const today=new Date().toISOString().slice(0,10); const keyToday=idJ(EN.ROOT,'daily',today);
       if (!exObj(keyToday)) await es(keyToday,{type:'number',role:'value.energy'},0);
       const curDay=toNum(g(keyToday),0)+energy;
       await ss(keyToday,curDay); await ss(EN.TODAY_KWH,curDay);
       await ss(EN.TOTAL_KWH, toNum(g(EN.TOTAL_KWH),0)+energy);
       renderDashboard();
      }
      async function updateSocEstimate(){
       if (CFG.energy.usableCapacityKWh<=0) return;
       const s0=g(EN.START_SOC), s1=readSocPct(); if (s0==null || s1==null) return;
       const est=((toNum(s1,0)-toNum(s0,0))/100)*CFG.energy.usableCapacityKWh;
       await ss(EN.END_SOC,s1); await ss(EN.ENERGY_SOC_KWH, Math.max(0,est));
      }
      async function integrationTick(){
       try{
         const need=[CFG.bluelink.power, CFG.bluelink.charging]; if (listMissing(need).length) return;
         const active=toBool(g(EN.ACTIVE)), charging=readChargingActive(), powerKW=readPowerKW();
         const now=Date.now(); const dt_h=Math.max(0,(now-lastTickTs)/3600000); lastTickTs=now;
      
         if (!active && charging && powerKW>=CFG.energy.sessionPowerMinKW){ idleSince=null; await sessionStart(); }
         if (active){
           const eAdd=powerKW*dt_h; const eNow=Math.max(0, toNum(g(EN.ENERGY_KWH),0)+eAdd);
           await ss(EN.ENERGY_KWH, eNow); await updateSocEstimate();
           if (powerKW < CFG.energy.sessionPowerMinKW || !charging){
             if (idleSince===null) idleSince=now; const idleSec=(now-idleSince)/1000;
             if (idleSec>=CFG.energy.sessionIdleEndSec){ await sessionFinish(!charging?'charging=false':'power<min'); idleSince=null; }
           } else idleSince=null;
         }
         const todayKey=idJ(EN.ROOT,'daily', new Date().toISOString().slice(0,10));
         if (exObj(todayKey)) await ss(EN.TODAY_KWH, toNum(g(todayKey),0));
       } catch(err){ await ss(EN.LAST_ERR, String(err?.message||err)); }
       renderDashboard();
      }
      
      /*** ===== Dashboard ===== ***/
      async function ensureDashState(){ await es(CFG.dash.htmlState, {type:'string', role:'html'}, ''); }
      
      // Mini Map-Engine (OSM Tiles im Iframe; kein Scroll/Wheel)
      function buildMapIframeHTML(points, centerHint){
       // points: [{lat,lon,label,type:'car'|'hpc'}]
       // centerHint: {lat,lon} optional
       const W=100, H=CFG.dash.heightPx; // Breite 100% via CSS
       const payload = { points: points||[], centerHint: centerHint||null, lockScroll: !CFG.dash.allowScroll };
       const css =
         'html,body{margin:0;height:100%;background:#0b1020}#root{position:relative;width:100%;height:100%}'+
         '.tile{position:absolute}canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}'+
         '.mk{position:absolute;transform:translate(-50%,-100%);padding:3px 6px;border-radius:8px;border:1px solid rgba(148,163,184,.5);background:rgba(13,23,45,.9);color:#e5e7eb;font:12px system-ui,sans-serif;white-space:nowrap}'+
         '.dot{position:absolute;width:10px;height:10px;border-radius:50%;box-shadow:0 0 0 2px rgba(96,165,250,.35)}'+
         '.dot.car{background:#34d399} .dot.hpc{background:#60a5fa} .attr{position:absolute;right:6px;bottom:4px;font:11px system-ui;color:#93a3b8;background:rgba(0,0,0,.35);padding:2px 6px;border-radius:6px}';
       const js =
         '(function(){var DATA=window.__PAYLOAD__||{points:[],centerHint:null,lockScroll:true};'+
         'var root=document.getElementById("root");var W=root.clientWidth,H=root.clientHeight;'+
         'function rad(d){return d*Math.PI/180;} function lat2y(lat){var s=Math.sin(rad(lat));return 0.5-Math.log((1+s)/(1-s))/(4*Math.PI);} function lon2x(lon){return lon/360+0.5;}'+
         'function project(lat,lon,z){var s=256*Math.pow(2,z);return {x:lon2x(lon)*s,y:lat2y(lat)*s};} function deproject(x,y,z){var s=256*Math.pow(2,z);var lon=(x/s-0.5)*360;var n=Math.PI-2*Math.PI*(y/s-0.5);var lat=180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n)));return {lat:lat,lon:lon};}'+
         'function fitBounds(pts){ if(!pts||!pts.length){ return {c:(DATA.centerHint||{lat:51,lon:10}), z:8}; } var minLat=90,maxLat=-90,minLon=180,maxLon=-180; pts.forEach(function(p){if(p.lat<minLat)minLat=p.lat;if(p.lat>maxLat)maxLat=p.lat;if(p.lon<minLon)minLon=p.lon;if(p.lon>maxLon)maxLon=p.lon;}); var c={lat:(minLat+maxLat)/2,lon:(minLon+maxLon)/2}; var z=6; for(var zz=6;zz<=19;zz++){var a=project(maxLat,minLon,zz), b=project(minLat,maxLon,zz); var w=Math.abs(b.x-a.x), h=Math.abs(b.y-a.y); if(w<=W*0.85 && h<=H*0.85) z=zz; else break;} return {c:c,z:z}; }'+
         'var pts=DATA.points||[]; var f=fitBounds(pts.length?pts:(DATA.centerHint?[DATA.centerHint]:[])); var center=f.c, zoom=f.z;'+
         'var world=project(center.lat,center.lon,zoom); var originX=world.x-W/2, originY=world.y-H/2;'+
         'var tiles=document.createElement("div"); tiles.style.position="absolute"; root.appendChild(tiles); var ctx=document.createElement("canvas"); ctx.width=W; ctx.height=H; root.appendChild(ctx); var g=ctx.getContext("2d");'+
         'function wrapX(x,n){return ((x%n)+n)%n;} function toXY(lat,lon){var p=project(lat,lon,zoom); return {x:p.x-originX,y:p.y-originY};}'+
         'function draw(){ tiles.innerHTML=""; var n=Math.pow(2,zoom); var x0=Math.floor(originX/256), y0=Math.floor(originY/256); var x1=Math.floor((originX+W)/256), y1=Math.floor((originY+H)/256); for(var ty=y0;ty<=y1;ty++){ if(ty<0||ty>=n) continue; for(var tx=x0;tx<=x1;tx++){ var wx=wrapX(tx,n); var img=new Image(); img.className="tile"; img.src="https://tile.openstreetmap.org/"+zoom+"/"+wx+"/"+ty+".png"; img.width=256; img.height=256; img.style.left=(tx*256-originX)+"px"; img.style.top=(ty*256-originY)+"px"; tiles.appendChild(img);} } g.clearRect(0,0,W,H);'+
         '  (DATA.points||[]).forEach(function(p){ var q=toXY(p.lat,p.lon); var d=document.createElement("div"); d.className="dot "+(p.type||"hpc"); d.style.left=q.x+"px"; d.style.top=q.y+"px"; root.appendChild(d); var m=document.createElement("div"); m.className="mk"; m.style.left=q.x+"px"; m.style.top=(q.y-12)+"px"; m.textContent=p.label||""; root.appendChild(m); });'+
         '  var attr=document.createElement("div"); attr.className="attr"; attr.textContent="© OpenStreetMap-Mitwirkende"; root.appendChild(attr);'+
         '} draw();'+
         (CFG.dash.allowScroll ? '' : ' root.addEventListener("wheel", function(ev){ev.preventDefault();}, {passive:false}); ') +
         '})();';
       const srcdoc = '<!doctype html><html><head><meta charset="utf-8"><style>'+css+'</style></head><body><div id="root" style="width:100%;height:100%"></div><script>window.__PAYLOAD__='+JSON.stringify(payload)+'<\/script><script>'+js+'<\/script></body></html>';
       return '<iframe style="width:100%;height:'+H+'px;border:0;border-radius:12px;overflow:hidden;background:#0b1020" srcdoc="'+
              String(srcdoc).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')+'"></iframe>';
      }
      
      function fmtEnergy(n){ return (n==null||isNaN(n)) ? '0.0' : Number(n).toFixed(2); }
      function fmtDist(m){ if (m==null||isNaN(m)) return '-'; return (m>=1000)?(m/1000).toFixed(2)+' km': Math.round(m)+' m'; }
      
      // Render: liest aktuelle States + erzeugt HTML in CFG.dash.htmlState
      async function renderDashboard(centerOverride){
       try{
         await ensureDashState();
      
         const car = readLatLon();
         const nearestName = String(g(HPC.NAME)||'–');
         const nearestOp   = String(g(HPC.OP)||'');
         const nearestKW   = toNum(g(HPC.KW),0);
         const nearestDist = toNum(g(HPC.DISTM),0);
         const count       = toNum(g(HPC.COUNT),0);
      
         const sessActive  = toBool(g(EN.ACTIVE));
         const eSess       = fmtEnergy(toNum(g(EN.ENERGY_KWH),0));
         const eSoc        = fmtEnergy(toNum(g(EN.ENERGY_SOC_KWH),0));
         const eToday      = fmtEnergy(toNum(g(EN.TODAY_KWH),0));
         const eTotal      = fmtEnergy(toNum(g(EN.TOTAL_KWH),0));
      
         // Punkte für Map: Auto + bis zu 12 HPC
         let list=[]; try{ list = JSON.parse(String(g(HPC.LASTJSON)||'[]')); }catch(e){ list=[]; }
         const top = (Array.isArray(list)?list:[]).slice(0,12);
         const points = [];
         if (Number.isFinite(car.lat) && Number.isFinite(car.lon) && car.lat && car.lon){
           points.push({lat:car.lat, lon:car.lon, label:'Car', type:'car'});
         }
         for (const x of top){
           if (!Number.isFinite(x.lat)||!Number.isFinite(x.lon)) continue;
           const lbl = (x.name||'HPC') + ' · ' + (x.maxPower_kW||'?') + ' kW';
           points.push({lat:x.lat, lon:x.lon, label:lbl, type:'hpc'});
         }
      
         // SelfTest-Fix: falls centerOverride gesetzt -> nehmen. Sonst fitBounds macht’s automatisch auf Car+HPC
         const map = buildMapIframeHTML(points, centerOverride || null);
      
         const css =
           '.wrap{color:#e5e7eb;font:14px system-ui,-apple-system,Segoe UI,Roboto;line-height:1.4;background:#0b1020;padding:10px;border-radius:14px;border:1px solid #334155}'+
           '.row{display:flex;flex-wrap:wrap;gap:10px;margin:0 0 10px}'+
           '.chip{background:#111827;border:1px solid #374151;border-radius:999px;padding:6px 10px;font-weight:700}'+
           '.muted{opacity:.8;font-weight:600} .title{font-weight:800;font-size:14px}'+
           '.list{margin-top:10px;border-top:1px solid #334155;padding-top:6px} .item{padding:6px 0;border-bottom:1px dashed #334155}'+
           '.item:last-child{border-bottom:0} .badge{font-weight:700} .ok{color:#34d399} .warn{color:#f59e0b}';
      
         const listHtml = top.map(o=>{
           const pref = o.preferred ? ' • ★' : '';
           return `<div class="item">• <span class="title">${o.name||'-'}</span> <span class="muted">(${o.operator||'–'}${pref})</span><br/>
             <span class="muted">${o.maxPower_kW||'?'} kW · ${fmtDist(o.distance_m)}</span></div>`;
         }).join('');
      
         const headChips =
           `<div class="row">
             <span class="chip">Nearest: <span class="badge">${nearestName}</span> <span class="muted">(${nearestOp||'–'})</span></span>
             <span class="chip">Power: ${nearestKW||0} kW</span>
             <span class="chip">Distance: ${fmtDist(nearestDist)}</span>
             <span class="chip">Spots: ${count}</span>
             <span class="chip">Session: ${eSess} kWh</span>
             <span class="chip">Today: ${eToday} kWh</span>
             <span class="chip">Total: ${eTotal} kWh</span>
             <span class="chip">Car Pos: ${Number(car.lat||0).toFixed(5)}, ${Number(car.lon||0).toFixed(5)}</span>
             <span class="chip">Active: <span class="${sessActive?'ok':'warn'}">${sessActive?'yes':'no'}</span></span>
           </div>`;
      
         const html = `<div class="wrap">${headChips}${map}<div class="list">${listHtml||'<div class="muted">Keine Stationen.</div>'}</div></div>`;
         await ss(CFG.dash.htmlState, `<style>${css}</style>` + html);
       } catch(e){ /* noop */ }
      }
      
      /*** ===== Subscriptions / Scheduler ===== ***/
      function attachTriggers(){
       // HPC triggers
       on({id: CFG.bluelink.power,    change:'ne'}, ()=> maybeCheckHPC('power_changed'));
       on({id: CFG.bluelink.charging, change:'ne'}, ()=> maybeCheckHPC('charging_changed'));
       on({id: CFG.bluelink.lat,      change:'ne'}, ()=> maybeCheckHPC('lat_changed'));
       on({id: CFG.bluelink.lon,      change:'ne'}, ()=> maybeCheckHPC('lon_changed'));
       on({id: CFG.bluelink.latAlt,   change:'ne'}, ()=> maybeCheckHPC('lat_changed_alt'));
       on({id: CFG.bluelink.lonAlt,   change:'ne'}, ()=> maybeCheckHPC('lon_changed_alt'));
      
       // Energy ticker
       schedule(`*/${CFG.energy.integTickSec} * * * * *`, integrationTick);
      
       // Periodische HPC-Abfrage
       schedule('*/5 * * * *', ()=> maybeCheckHPC('scheduled'));
      
       // Test-Button
       on({id: HPC.CMD_TEST, change:'ne'}, async (s)=>{
         if (s && s.state && s.state.val===true){
           await ss(HPC.CMD_TEST,false);
           const rOverride = toNum(g(HPC.TEST_RADIUS), NaN);
           await maybeCheckHPC('manual_test', { force:true, radiusKmOverride: isFinite(rOverride)?rOverride:undefined });
         }
       });
      
       // SelfTests – **fixes map centering**: wir übergeben centerOverride
       function selfTest(lat, lon, label){
         return async (s)=>{
           if (s && s.state && s.state.val===true){
             await ss(s.id,false);
             // Debug-Anzeige der Test-Geo
             await ss(HPC.DBG_COORD_LAT, lat); await ss(HPC.DBG_COORD_LON, lon);
             // Direkte OCM-Abfrage (wie maybeCheckHPC, aber ohne Rate-Limit & mit Center-Override)
             try{
               let q = await fetchOCM(lat,lon,Math.max(20,CFG.hpc.radiusKm),Math.max(80,CFG.hpc.maxResults),'strict');
               await ss(HPC.DBG_URL, q.url); await ss(HPC.DBG_HTTP, `self:${q.status}`); await ss(HPC.DBG_RAW, Array.isArray(q.json)?q.json.length:0);
               let enriched = enrichAndFilter(q.json, lat, lon);
               if (enriched.length===0){
                 q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,CFG.hpc.maxResults,'dcOnly');
                 enriched = enrichAndFilter(q.json, lat, lon);
               }
               if (enriched.length===0){
                 const old=CFG.hpc.hpcMinKW; CFG.hpc.hpcMinKW=CFG.hpc.cascadeMinKWClient||120;
                 q = await fetchOCM(lat,lon,CFG.hpc.radiusKm,Math.max(CFG.hpc.maxResults,120),'all');
                 enriched = enrichAndFilter(q.json, lat, lon);
                 CFG.hpc.hpcMinKW=old;
               }
               await ss(HPC.COUNT, enriched.length);
               await ss(HPC.HAS, enriched.length>0);
               await ss(HPC.LASTJSON, JSON.stringify(enriched));
               if (enriched.length){
                 const n=enriched[0];
                 await ss(HPC.NAME, n.name||''); await ss(HPC.DISTM, n.distance_m||0);
                 await ss(HPC.KW, n.maxPower_kW||0); await ss(HPC.OP, n.operator||'');
               } else {
                 await ss(HPC.NAME,''); await ss(HPC.DISTM,0); await ss(HPC.KW,0); await ss(HPC.OP,'');
               }
               await ss(HPC.LASTCHK, new Date().toISOString()); await ss(HPC.LASTERR,''); await ss(HPC.LASTWHY, 'selftest_'+label);
               // **WICHTIG**: Karte explizit um die Test-Geo zentrieren
               renderDashboard({lat:lat, lon:lon});
             } catch(err){
               await ss(HPC.LASTERR, String(err?.message||err));
               await ss(HPC.DBG_ERRSHORT, String(err?.message||err));
               renderDashboard({lat:lat, lon:lon});
             }
           }
         };
       }
       on({id:HPC.CMD_TEST_FFM,change:'ne'}, selfTest(50.1109, 8.6821, 'FFM'));
       on({id:HPC.CMD_TEST_CGN,change:'ne'}, selfTest(50.9375, 6.9603, 'CGN'));
       on({id:HPC.CMD_TEST_MUC,change:'ne'}, selfTest(48.1372,11.5756, 'MUC'));
      }
      
      /*** ===== Bootstrap ===== ***/
      async function init(){
       await ensureHpcStates();
       await ensureEnergyStates();
       await ensureDashState();
       // kleiner Delay, dann initial laden + rendern
       setTimeout(()=>{ maybeCheckHPC('startup'); integrationTick(); }, 1200);
       attachTriggers();
       logI('Init: ready');
      }
      init();
      
      

      Meister MopperM Abwesend
      Meister MopperM Abwesend
      Meister Mopper
      schrieb am zuletzt editiert von
      #2201

      @ilovegym

      Du machst bald die Adapterentwickler arbeitsfrei.

      Ab dann biste aber Skriptentwickler for all 😂

      Proxmox und HA

      1 Antwort Letzte Antwort
      1
      • GarganoG Gargano

        @arteck sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

        geht es den mit neuen Token

        Weder mit dem bisherigen Token, noch mit einem neu generierten.
        Ich wollte auch nicht nerven, sondern nur Bescheid geben.

        Meister MopperM Abwesend
        Meister MopperM Abwesend
        Meister Mopper
        schrieb am zuletzt editiert von
        #2202

        @gargano sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

        @arteck sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

        geht es den mit neuen Token

        Weder mit dem bisherigen Token, noch mit einem neu generierten.
        Ich wollte auch nicht nerven, sondern nur Bescheid geben.

        Siehe oben, hatte ich auch.

        Haste mal das Prozedere erneut durchgeführt und einen neuen refresh token geholt?

        Proxmox und HA

        1 Antwort Letzte Antwort
        0
        • GarganoG Offline
          GarganoG Offline
          Gargano
          schrieb am zuletzt editiert von Gargano
          #2203

          @meister-mopper sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

          Haste mal das Prozedere erneut durchgeführt und einen neuen refresh token geholt?

          Hab die komplette Prozedur wiederholt und jetzt gehts. Mal schauen wie lange.

          [Edit]
          Hat nicht lange gehalten, heute wieder derselbe Fehler, Login nicht möglich.
          Ich habs jetzt deaktiviert. Evtl. gibt es ja mal eine dauerhafte Lösung.

          1 Antwort Letzte Antwort
          0
          • B Offline
            B Offline
            baeckerman83
            schrieb am zuletzt editiert von baeckerman83
            #2204

            Ich musste den Adapter Neuinstallieren und komme jetzt gar nicht mehr rein.
            Ich habe einen KIA, die Login Daten gehen auf der Homepage.
            Ich nutze die Version 3.1.3, was muss ich tun, damit der Adapter sich wieder einloggen kann?

            Im Adapter kommt aber immer bluelink.0
            2025-10-07 22:15:12.179 error Server is not available or login credentials are wrong
            bluelink.0
            2025-10-07 22:15:12.178 error ManagedBluelinkyError: @EuropeController.login: [401] Unauthorized on [POST] https://prd.eu-ccapi.kia.com:8080/api/v1/user/language - "{"errId":"f3a462c3-3150-4ecb-a740-7194f635abc4","errCode":"4010","errMsg":"Require authentication"}\n"

            arteckA 1 Antwort Letzte Antwort
            0
            • B baeckerman83

              Ich musste den Adapter Neuinstallieren und komme jetzt gar nicht mehr rein.
              Ich habe einen KIA, die Login Daten gehen auf der Homepage.
              Ich nutze die Version 3.1.3, was muss ich tun, damit der Adapter sich wieder einloggen kann?

              Im Adapter kommt aber immer bluelink.0
              2025-10-07 22:15:12.179 error Server is not available or login credentials are wrong
              bluelink.0
              2025-10-07 22:15:12.178 error ManagedBluelinkyError: @EuropeController.login: [401] Unauthorized on [POST] https://prd.eu-ccapi.kia.com:8080/api/v1/user/language - "{"errId":"f3a462c3-3150-4ecb-a740-7194f635abc4","errCode":"4010","errMsg":"Require authentication"}\n"

              arteckA Offline
              arteckA Offline
              arteck
              Developer Most Active
              schrieb am zuletzt editiert von
              #2205

              @baeckerman83 lesen
              https://forum.iobroker.net/topic/43592/adapter-hyundai-bluelink-oder-kia-uvo/2158?_=1759834665758

              zigbee hab ich, zwave auch, nuc's genauso und HA auch

              1 Antwort Letzte Antwort
              0
              • B Offline
                B Offline
                baeckerman83
                schrieb am zuletzt editiert von
                #2206

                @arteck Das habe ich versucht. Habe auch einen Refreshtoken bekommen, bekomme aber weiterhin die gleiche Fehlermeldung.
                Welche Version sollte er denn aus GIT installieren?
                Welche Node Version benötige ich dafür?

                arteckA 1 Antwort Letzte Antwort
                0
                • B baeckerman83

                  @arteck Das habe ich versucht. Habe auch einen Refreshtoken bekommen, bekomme aber weiterhin die gleiche Fehlermeldung.
                  Welche Version sollte er denn aus GIT installieren?
                  Welche Node Version benötige ich dafür?

                  arteckA Offline
                  arteckA Offline
                  arteck
                  Developer Most Active
                  schrieb am zuletzt editiert von
                  #2207

                  @baeckerman83 sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                  Welche Version sollte er denn aus GIT installieren?

                  es gibt nur eine 3.1.21

                  Node ist egal.. das was standard installiert ist

                  zigbee hab ich, zwave auch, nuc's genauso und HA auch

                  B 1 Antwort Letzte Antwort
                  0
                  • arteckA arteck

                    @baeckerman83 sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                    Welche Version sollte er denn aus GIT installieren?

                    es gibt nur eine 3.1.21

                    Node ist egal.. das was standard installiert ist

                    B Offline
                    B Offline
                    baeckerman83
                    schrieb am zuletzt editiert von baeckerman83
                    #2208

                    @arteck Danke für deine Hilfe.
                    Habe es jetzt auf meinem Test iobroker zum laufen bekommen und muss dann mal schauen warum es auf dem anderen nicht geht.

                    Komisch ist, dass er nicht mehr alle Daten lädt. Reichweite und Ladung in % fehlt zum Beispiel. Da muss ich noch schauen.

                    In meinem Docker Container bekomme ich es nicht installiert. Er will esbuild Versiuon 0.25.8 installieren, findet aber nur 0.20.2, warum auch immer. Habe sogar auf Node 22 aktualisiert. Keine Besserung.

                    1 Antwort Letzte Antwort
                    0
                    • H Offline
                      H Offline
                      hoepps2802
                      schrieb am zuletzt editiert von hoepps2802
                      #2209

                      Hallo Zusammen,

                      hab die Version 3.1.21 installiert, einen neuen Token generiert und muss zusätzlich den USER PIN eintragen.
                      Funktioniert ohne Fehlermeldung mit meinen KIA CEED SW PHEV

                      
                      bluelink.0
                      2025-10-12 11:17:30.195	info	Update for U5XXXXXXXL005240 successfull
                      
                      bluelink.0
                      2025-10-12 11:17:29.980	info	State value to set for "bluelink.0.U5XXXXXXXL005240.vehicleStatus.steerWheelHeat" has to be type "number" but received type "boolean"
                      
                      bluelink.0
                      2025-10-12 11:17:19.306	info	Read new update for U5XXXXXXXL005240directly from the car
                      
                      bluelink.0
                      2025-10-12 11:17:18.928	info	1 Vehicles found
                      
                      bluelink.0
                      2025-10-12 11:17:17.468	info	Login to api
                      
                      bluelink.0
                      2025-10-12 11:17:17.418	info	starting. Version 3.1.21 (non-npm: Newan/ioBroker.bluelink) in /opt/iobroker/node_modules/iobroker.bluelink, node: v20.19.3, js-controller: 7.0.6
                      
                      host.raspberrypi
                      2025-10-12 11:17:12.741	info	instance system.adapter.bluelink.0 in version "3.1.21" (non-npm: Newan/ioBroker.bluelink) started with pid 529176
                      

                      mit 3.1.30 bekomme ich wieder den Login Fehler

                      
                      bluelink.0
                      2025-10-12 11:15:34.978	error	next auto login attempt in 1 hour or restart adapter manual
                      
                      bluelink.0
                      2025-10-12 11:15:34.977	error	Server is not available or login credentials are wrong
                      
                      bluelink.0
                      2025-10-12 11:15:34.975	error	ManagedBluelinkyError: @EuropeController.login: [401] Unauthorized on [POST] https://prd.eu-ccapi.kia.com:8080/api/v1/user/language - "{\"errId\":\"a3384d70-0c45-4655-a1c6-cd08f30b2c83\",\"errCode\":\"4010\",\"errMsg\":\"Require authentication\"}\n"
                      
                      bluelink.0
                      2025-10-12 11:15:33.798	info	Login to api
                      
                      bluelink.0
                      2025-10-12 11:15:33.752	info	starting. Version 3.1.3 in /opt/iobroker/node_modules/iobroker.bluelink, node: v20.19.3, js-controller: 7.0.6
                      
                      host.raspberrypi
                      2025-10-12 11:15:29.468	info	instance system.adapter.bluelink.0 in version "3.1.3" started with pid 528234
                      

                      Gruß Matze

                      1 Antwort Letzte Antwort
                      0
                      • arteckA Offline
                        arteckA Offline
                        arteck
                        Developer Most Active
                        schrieb am zuletzt editiert von
                        #2210

                        @hoepps2802 sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                        mit 3.1.30 bekomme ich wieder den Login Fehler

                        wovon redest du ??? was den für eine 3.1.30

                        zigbee hab ich, zwave auch, nuc's genauso und HA auch

                        H 1 Antwort Letzte Antwort
                        0
                        • arteckA arteck

                          @hoepps2802 sagte in Adapter Hyundai (Bluelink) oder KIA (UVO):

                          mit 3.1.30 bekomme ich wieder den Login Fehler

                          wovon redest du ??? was den für eine 3.1.30

                          H Offline
                          H Offline
                          hoepps2802
                          schrieb am zuletzt editiert von
                          #2211

                          @arteck sorry sehe es auch gerade 🤦 3.1.3 nicht 3.1.30 😰

                          1 Antwort Letzte Antwort
                          0
                          • meuteM Offline
                            meuteM Offline
                            meute
                            schrieb am zuletzt editiert von
                            #2212

                            Von dem Problem für Hyundai-Fahrer habe ich gerade im GoingElectric-Forum gelesen.
                            https://github.com/Hyundai-Kia-Connect/hyundai_kia_connect_api/issues/919

                            Tritt das Problem bei Hyundai-Fahrer hier auch auf?

                            fraenk for friends Code: MATF103

                            M M 2 Antworten Letzte Antwort
                            0
                            • meuteM meute

                              Von dem Problem für Hyundai-Fahrer habe ich gerade im GoingElectric-Forum gelesen.
                              https://github.com/Hyundai-Kia-Connect/hyundai_kia_connect_api/issues/919

                              Tritt das Problem bei Hyundai-Fahrer hier auch auf?

                              M Offline
                              M Offline
                              ManfredHi
                              schrieb am zuletzt editiert von ManfredHi
                              #2213

                              Bei mir hat bis um 19.30 Uhr bluelink 3.1.8 ganz normal funktioniert. Gibt es für Hyundai auch schon eine LÖsung wie bei Kia?

                              
                              bluelink.0
                              2025-10-14 20:25:09.410	error	next auto login attempt in 1 hour or restart adapter manual
                              
                              bluelink.0
                              2025-10-14 20:25:09.410	error	Server is not available or login credentials are wrong
                              
                              bluelink.0
                              2025-10-14 20:25:09.409	error	ManagedBluelinkyError: @EuropeController.login: [400] Bad Request on [POST] https://prd.eu-ccapi.hyundai.com:8080/api/v1/user/signin - {"errId":"58d79e3d-2138-4aaf-9776-af1de9188d7a","errCode":"4003","errMsg":"Invalid values"}
                              
                              1 Antwort Letzte Antwort
                              0
                              • meuteM meute

                                Von dem Problem für Hyundai-Fahrer habe ich gerade im GoingElectric-Forum gelesen.
                                https://github.com/Hyundai-Kia-Connect/hyundai_kia_connect_api/issues/919

                                Tritt das Problem bei Hyundai-Fahrer hier auch auf?

                                M Offline
                                M Offline
                                Michi_Pi
                                schrieb am zuletzt editiert von
                                #2214

                                @meute ja hab auch das Problem mit dem Login

                                R 1 Antwort Letzte Antwort
                                0
                                • M Michi_Pi

                                  @meute ja hab auch das Problem mit dem Login

                                  R Online
                                  R Online
                                  RISSN
                                  schrieb am zuletzt editiert von
                                  #2215

                                  @michi_pi hier geht auch nichts mehr, kann man auch mit Leben, was solls

                                  1 Antwort Letzte Antwort
                                  0
                                  • meuteM Offline
                                    meuteM Offline
                                    meute
                                    schrieb am zuletzt editiert von meute
                                    #2216

                                    Hier die Lösung für Hyundai auch aus dem GoingElectric-Forum.
                                    Man braucht den Refresh Token für Hyundai.

                                    Für Kia hat es @arteck hier beschrieben.

                                    Für Hyundai ist der Ablauf genau so.
                                    Man braucht nur ein anderes Python-Script.

                                    Ablauf für Kia beachten:
                                    https://github.com/Hyundai-Kia-Connect/hyundai_kia_connect_api/wiki/Kia-Europe-Login-Flow

                                    Das Python-Script "HyundaiFetchApiTokens . py" für Hyundai gibt es hier:
                                    https://gist.github.com/Maaxion/22a38ba8fb06937da18482ddf35171ac

                                    fraenk for friends Code: MATF103

                                    arteckA 1 Antwort Letzte Antwort
                                    0
                                    • meuteM meute

                                      Hier die Lösung für Hyundai auch aus dem GoingElectric-Forum.
                                      Man braucht den Refresh Token für Hyundai.

                                      Für Kia hat es @arteck hier beschrieben.

                                      Für Hyundai ist der Ablauf genau so.
                                      Man braucht nur ein anderes Python-Script.

                                      Ablauf für Kia beachten:
                                      https://github.com/Hyundai-Kia-Connect/hyundai_kia_connect_api/wiki/Kia-Europe-Login-Flow

                                      Das Python-Script "HyundaiFetchApiTokens . py" für Hyundai gibt es hier:
                                      https://gist.github.com/Maaxion/22a38ba8fb06937da18482ddf35171ac

                                      arteckA Offline
                                      arteckA Offline
                                      arteck
                                      Developer Most Active
                                      schrieb am zuletzt editiert von arteck
                                      #2217

                                      @meute so einfach ist das nicht...

                                      da die Anpassung an den hyundai Server in der 3.1.21 nicht da ist..

                                      aber jetzt..3.1.22 ... neue Version von GIT laden.. der Fix ist nur für Hyundai also alle die KIA haben => ihr braucht das nicht da reicht die 3.1.21

                                      was ist zu tun

                                      https://github.com/Newan/ioBroker.bluelink/tree/master/py

                                      zigbee hab ich, zwave auch, nuc's genauso und HA auch

                                      ilovegymI F M 3 Antworten Letzte Antwort
                                      2
                                      • arteckA arteck

                                        @meute so einfach ist das nicht...

                                        da die Anpassung an den hyundai Server in der 3.1.21 nicht da ist..

                                        aber jetzt..3.1.22 ... neue Version von GIT laden.. der Fix ist nur für Hyundai also alle die KIA haben => ihr braucht das nicht da reicht die 3.1.21

                                        was ist zu tun

                                        https://github.com/Newan/ioBroker.bluelink/tree/master/py

                                        ilovegymI Online
                                        ilovegymI Online
                                        ilovegym
                                        schrieb am zuletzt editiert von
                                        #2218

                                        @arteck
                                        erstmal vielen Dank fuer die Muehe!
                                        Seit heute geht die Version 3.1.6 / en bei mir nicht mehr.

                                        Also das bluelinky verzeichnis geloescht, Adapter V3.1.22 von git gezogen, nicht gestartet.
                                        Verzeichnis auf meinem testpi angelegt, ein venv erzeugt, und das script gestartet, das geht auch, leider ist der Link hier nicht moeglich, ich lande auf einer Hyundai-Seite mit franzoesischer Fehlermeldung 400, wenn ich in dem Link anstatt fr auf de oder en stelle, das gleiche.. geht nicht.

                                        Somit kein Token - keine Daten. Ist wie, keine Arme, keine Kekse 🙂

                                        arteckA 1 Antwort Letzte Antwort
                                        0
                                        • ilovegymI ilovegym

                                          @arteck
                                          erstmal vielen Dank fuer die Muehe!
                                          Seit heute geht die Version 3.1.6 / en bei mir nicht mehr.

                                          Also das bluelinky verzeichnis geloescht, Adapter V3.1.22 von git gezogen, nicht gestartet.
                                          Verzeichnis auf meinem testpi angelegt, ein venv erzeugt, und das script gestartet, das geht auch, leider ist der Link hier nicht moeglich, ich lande auf einer Hyundai-Seite mit franzoesischer Fehlermeldung 400, wenn ich in dem Link anstatt fr auf de oder en stelle, das gleiche.. geht nicht.

                                          Somit kein Token - keine Daten. Ist wie, keine Arme, keine Kekse 🙂

                                          arteckA Offline
                                          arteckA Offline
                                          arteck
                                          Developer Most Active
                                          schrieb am zuletzt editiert von
                                          #2219

                                          @ilovegym gut dass ich deine Daten noch habe
                                          guckmal PN

                                          zigbee hab ich, zwave auch, nuc's genauso und HA auch

                                          F 1 Antwort Letzte Antwort
                                          0
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          492

                                          Online

                                          32.4k

                                          Benutzer

                                          81.4k

                                          Themen

                                          1.3m

                                          Beiträge
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
                                          ioBroker Community 2014-2025
                                          logo
                                          • Anmelden

                                          • Du hast noch kein Konto? Registrieren

                                          • Anmelden oder registrieren, um zu suchen
                                          • Erster Beitrag
                                            Letzter Beitrag
                                          0
                                          • Aktuell
                                          • Tags
                                          • Ungelesen 0
                                          • Kategorien
                                          • Unreplied
                                          • Beliebt
                                          • GitHub
                                          • Docu
                                          • Hilfe