Skip to content
  • Home
  • Recent
  • Tags
  • 0 Unread 0
  • Categories
  • Unreplied
  • Popular
  • 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

  • Default (No Skin)
  • No Skin
Collapse
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Tester
  4. Adapter Hyundai (Bluelink) oder KIA (UVO)

NEWS

  • Monatsrückblick Januar/Februar 2026 ist online!
    BluefoxB
    Bluefox
    18
    1
    720

  • Jahresrückblick 2025 – unser neuer Blogbeitrag ist online! ✨
    BluefoxB
    Bluefox
    18
    1
    5.9k

  • Neuer Blogbeitrag: Monatsrückblick - Dezember 2025 🎄
    BluefoxB
    Bluefox
    13
    1
    1.5k

Adapter Hyundai (Bluelink) oder KIA (UVO)

Scheduled Pinned Locked Moved Tester
2.4k Posts 157 Posters 956.7k Views 144 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Meister MopperM Meister Mopper

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

    Ioniq5N

    Das Kfz ist ja Teufelszeug, damit könnte ich meine Lady nie fahren lassen, die würde allen BMW und Audi Losern den Beschleunigungskrieg erklären.

    ilovegymI Offline
    ilovegymI Offline
    ilovegym
    wrote on last edited by
    #2196

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

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

    Ioniq5N

    Das Kfz ist ja Teufelszeug, damit könnte ich meine Lady nie fahren lassen, die würde allen BMW und Audi Losern den Beschleunigungskrieg erklären.

    … und das macht sowas von Spaß… 😆😂🙈😇😇😇 wäre bei meiner aber auch, nur wenn se nebendran sitzt, darf ich die 200 nicht überschreiten.. 🙈😅

    ilovegym66 – ioBroker Projekte & Automationen
    GitHub: https://github.com/Ilovegym66 | Austausch im Discord: https://discord.gg/yC65zjr5uq

    1 Reply Last reply
    0
    • GarganoG Offline
      GarganoG Offline
      Gargano
      wrote on last edited by
      #2197

      @arteck Der Workaround hat jetzt 5 Tage funktioniert und jetzt wieder der Fehler mit Login. Hat Kia wieder etwas verändert ?

      arteckA 1 Reply Last reply
      0
      • GarganoG Gargano

        @arteck Der Workaround hat jetzt 5 Tage funktioniert und jetzt wieder der Fehler mit Login. Hat Kia wieder etwas verändert ?

        arteckA Offline
        arteckA Offline
        arteck
        Developer Most Active
        wrote on last edited by arteck
        #2198

        @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 ?

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

        ilovegymI 1 Reply Last reply
        1
        • GarganoG Offline
          GarganoG Offline
          Gargano
          wrote on last edited by
          #2199

          @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 1 Reply Last reply
          0
          • 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 Offline
            ilovegymI Offline
            ilovegym
            wrote on last edited by
            #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();
            
            

            ilovegym66 – ioBroker Projekte & Automationen
            GitHub: https://github.com/Ilovegym66 | Austausch im Discord: https://discord.gg/yC65zjr5uq

            Meister MopperM 1 Reply Last reply
            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 Online
              Meister MopperM Online
              Meister Mopper
              wrote on last edited by
              #2201

              @ilovegym

              Du machst bald die Adapterentwickler arbeitsfrei.

              Ab dann biste aber Skriptentwickler for all 😂

              Proxmox und HA - dank KI/AI endlich "blocklyfrei"

              1 Reply Last reply
              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 Online
                Meister MopperM Online
                Meister Mopper
                wrote on last edited by
                #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 - dank KI/AI endlich "blocklyfrei"

                1 Reply Last reply
                0
                • GarganoG Offline
                  GarganoG Offline
                  Gargano
                  wrote on last edited by 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 Reply Last reply
                  0
                  • B Offline
                    B Offline
                    baeckerman83
                    wrote on last edited by 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 Reply Last reply
                    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
                      wrote on last edited by
                      #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 Reply Last reply
                      0
                      • B Offline
                        B Offline
                        baeckerman83
                        wrote on last edited by
                        #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 Reply Last reply
                        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
                          wrote on last edited by
                          #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 Reply Last reply
                          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
                            wrote on last edited by 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 Reply Last reply
                            0
                            • H Offline
                              H Offline
                              hoepps2802
                              wrote on last edited by 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 Reply Last reply
                              0
                              • arteckA Offline
                                arteckA Offline
                                arteck
                                Developer Most Active
                                wrote on last edited by
                                #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 Reply Last reply
                                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
                                  wrote on last edited by
                                  #2211

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

                                  1 Reply Last reply
                                  0
                                  • meuteM Offline
                                    meuteM Offline
                                    meute
                                    wrote on last edited by
                                    #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 Replies Last reply
                                    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
                                      wrote on last edited by 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 Reply Last reply
                                      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
                                        wrote on last edited by
                                        #2214

                                        @meute ja hab auch das Problem mit dem Login

                                        R 1 Reply Last reply
                                        0
                                        • M Michi_Pi

                                          @meute ja hab auch das Problem mit dem Login

                                          R Online
                                          R Online
                                          RISSN
                                          wrote on last edited by
                                          #2215

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

                                          1 Reply Last reply
                                          0

                                          Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                                          Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                                          With your input, this post could be even better 💗

                                          Register Login
                                          Reply
                                          • Reply as topic
                                          Log in to reply
                                          • Oldest to Newest
                                          • Newest to Oldest
                                          • Most Votes


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          510

                                          Online

                                          32.7k

                                          Users

                                          82.6k

                                          Topics

                                          1.3m

                                          Posts
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                          ioBroker Community 2014-2025
                                          logo
                                          • Login

                                          • Don't have an account? Register

                                          • Login or register to search.
                                          • First post
                                            Last post
                                          0
                                          • Home
                                          • Recent
                                          • Tags
                                          • Unread 0
                                          • Categories
                                          • Unreplied
                                          • Popular
                                          • GitHub
                                          • Docu
                                          • Hilfe