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

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

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

NEWS

  • Jahresrückblick 2025 – unser neuer Blogbeitrag ist online! ✨
    BluefoxB
    Bluefox
    14
    1
    192

  • Neuer Blogbeitrag: Monatsrückblick - Dezember 2025 🎄
    BluefoxB
    Bluefox
    12
    1
    572

  • Weihnachtsangebot 2025! 🎄
    BluefoxB
    Bluefox
    24
    1
    1.8k

Adapter Hyundai (Bluelink) oder KIA (UVO)

Geplant Angeheftet Gesperrt Verschoben Tester
2.4k Beiträge 155 Kommentatoren 910.7k Aufrufe 144 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • Meister MopperM Meister Mopper

    @arteck

    Jetzt haben sie offenbar an der Abfrage der Geo-Daten geschraubt. Diese API ist aktuell wirklich ein Graus.

    bluelink.0	2025-08-13 15:58:00.349	error	Error on API-Request Status, ErrorCount:1
    bluelink.0	2025-08-13 15:58:00.350	error	Cannot read properties of undefined (reading 'GeoCoord')
    
    arteckA Offline
    arteckA Offline
    arteck
    Developer Most Active
    schrieb am zuletzt editiert von
    #2102

    @meister-mopper hast du die neue von GIT geladen ?

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

    Meister MopperM 1 Antwort Letzte Antwort
    0
    • arteckA arteck

      @meister-mopper hast du die neue von GIT geladen ?

      Meister MopperM Online
      Meister MopperM Online
      Meister Mopper
      schrieb am zuletzt editiert von
      #2103

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

      @meister-mopper hast du die neue von GIT geladen ?

      Danke, mit v3.1.20 ist der Fehler weg.

      Proxmox und HA

      1 Antwort Letzte Antwort
      1
      • R Online
        R Online
        RISSN
        schrieb am zuletzt editiert von
        #2104

        aber bei der Version 3.1.20 geht der Login beim Hyundai noch nicht? Ich bin ja von 3.1.19 auf 3.1.17 zurück, da geht es alles

        ilovegymI arteckA 2 Antworten Letzte Antwort
        0
        • R RISSN

          aber bei der Version 3.1.20 geht der Login beim Hyundai noch nicht? Ich bin ja von 3.1.19 auf 3.1.17 zurück, da geht es alles

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

          @rissn

          bei mir ist die letzte die 3.1.16, alles was danach kam, geht nicht.. :)

          Und nein, @arteck ist nicht Merlin :) auch wenn er hier schon viel zaubert :)
          Ueberlegt mal, wieviel verschiedene Fahrzeugtypen es bei Hyundai/Kia gibt, mit zig verschiedenen Software und Hardware variationen..

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

          1 Antwort Letzte Antwort
          1
          • R RISSN

            aber bei der Version 3.1.20 geht der Login beim Hyundai noch nicht? Ich bin ja von 3.1.19 auf 3.1.17 zurück, da geht es alles

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

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

            aber bei der Version 3.1.20 geht der Login beim Hyundai noch nicht? Ich bin ja von 3.1.19 auf 3.1.17 zurück, da geht es alles

            dann lass laufen .. hab schon mal gesagt.. es ändert sich nix an dem adapter ausser für KIA Fahrzeuge die mal wieder was anderes senden..
            wenn eine ältere Version geht (login funktioniert) dann nutze diese .. ich werde nicht eine alternative einbauen ala "welches login willst du haben neu oder alt" das ist zu viel Arbeit.. und so wie es aussieht passiert da gerade sehr viel in der API ..

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

            ilovegymI 1 Antwort Letzte Antwort
            1
            • Peter V.P Online
              Peter V.P Online
              Peter V.
              schrieb am zuletzt editiert von
              #2107

              @arteck
              Danke für deine ganzen Mühen, Version 3.1.20 läuft bei Kia wieder ohne Fehlermeldungen und tut was es soll.
              Hoffen wir mal, das die an der Api/Servern nicht mehr zuviel rumschrauben.

              1 Antwort Letzte Antwort
              0
              • arteckA arteck

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

                aber bei der Version 3.1.20 geht der Login beim Hyundai noch nicht? Ich bin ja von 3.1.19 auf 3.1.17 zurück, da geht es alles

                dann lass laufen .. hab schon mal gesagt.. es ändert sich nix an dem adapter ausser für KIA Fahrzeuge die mal wieder was anderes senden..
                wenn eine ältere Version geht (login funktioniert) dann nutze diese .. ich werde nicht eine alternative einbauen ala "welches login willst du haben neu oder alt" das ist zu viel Arbeit.. und so wie es aussieht passiert da gerade sehr viel in der API ..

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

                @arteck

                Hab mal meinen digitalen Codierknecht :robot_face: solange gequaelt, bis das hier rauskam:
                Screenshot 2025-08-16 at 15.03.35.png

                Ist n einfaches Dashboard, kann in jeder Vis (Vis, Vis-NG, MinuVis, Jarvis und was es noch so gibt) angezeigt werden, legt die Daten in einen State ab.
                Hier das Script dazu, das Hintergrundbild kann natuerlich angepasst werden, muss nich meins von der Nordschleife sein :)
                Eingebaut sind mir alle sinvoll vorgekommenen Werte des Adapters Bluelink, sowie von meiner Wallbox Go-E und ein paar selbst definierte (12V laedt), desweiteren eine Ladehistorie graphisch und tabellar.. :) - responsive Design, kann man auch aufm Smartphone ansehen

                /******************************************************
                * IONIQ 5 N – Bluelink Dashboard (HTML) for ioBroker
                * Version: 1.6.6  (SVG-Chart responsive, volle Kartenbreite & ~Tabelle-Höhe)
                * (c) 2025 Bernd / ilovegym – privat
                ******************************************************/
                
                /* =================== KONFIG =================== */
                const BLUELINK_INST = 'bluelink.0';
                let   VEHICLE_ID    = '';          // leer = Auto-Detect
                const OUT_HTML_DP   = '0_userdata.0.vis.IoniqDashboardHTML';
                
                // Eigene Zusatz-DPs
                const DP_12V_LADEN  = '0_userdata.0.Zaehler.Hyundai_12V';   // true = 12V lädt
                const DP_KM_YESTER  = '0_userdata.0.Zaehler.Hyundai_KM_';   // km gestern
                
                // Wallbox (go-e)
                const WB = {
                 energy:  'go-e.0.loaded_energy_kwh',
                 state:   'go-e.0.car',                // 1 Standby, 2 Laden, 3 Warten auf Auto, 4 Fertig
                 power:   'go-e.0.energy.power',
                 temp1:   'go-e.0.temperatures.temperature1',
                 temp2:   'go-e.0.temperatures.temperature2',
                 allow:   'go-e.0.allow_charging',
                 amp:     'go-e.0.ampere'
                };
                const WB_STATE_TXT = {1:'Standby',2:'Laden',3:'Warte auf Auto',4:'Fertig'};
                
                // Ladehistorie (Samples werden hier gespeichert)
                const HIST_SAMPLES_DP = '0_userdata.0.Ioniq.History.samples';   // string (JSON Array: {t,soc,wb})
                const HIST_LAST_TS_DP = '0_userdata.0.Ioniq.History.lastSample'; // number (ms)
                const SAMPLE_INTERVAL_MS = 5 * 60 * 1000; // alle 5 Minuten
                const MAX_SAMPLES = 3000; // ~7 Tage bei 5-Minuten-Samples
                
                // Debug
                const DEBUG = false;
                
                /* =================== UTIL =================== */
                function ensureState(id, def = '', common = {name:'IONIQ 5 N Dashboard HTML', type:'string', role:'html', read:true, write:false}) {
                 try { if (!existsObject(id)) createState(id, def, true, common); } catch (e) { log('ensureState error ' + e, 'warn'); }
                }
                function ensureDataPoint(id, def, common){ try{ if(!existsObject(id)) createState(id, def, true, common); }catch(e){} }
                function JP(...parts){ return parts.filter(p => p !== '' && p != null).join('.'); }
                function es(id){ try { return !!(id && existsState(id)); } catch(_) { return false; } }
                function gs(id){ try { return es(id)? getState(id).val:undefined; } catch(_) { return undefined; } }
                function ss(id, val){ try { setState(id, val, true); } catch(_){} }
                function firstExisting(paths){ if(!Array.isArray(paths)) return {path:null,val:undefined}; for(const p of paths){ if(es(p)){ const v=gs(p); if(v!==undefined && v!==null) return {path:p,val:v}; } } return {path:null,val:undefined}; }
                function P(...parts){ return JP(BLUELINK_INST, VEHICLE_ID, ...parts); }
                
                /* =================== VIN-AUTODETECT =================== */
                function detectVehicleId(){
                 try{
                   const rows = getObjectView('system','state',{ startkey: BLUELINK_INST+'.', endkey: BLUELINK_INST+'.\u9999' }).rows;
                   const seen = {};
                   for(const r of rows){
                     const id = r.id || '';
                     const seg = id.split('.');
                     if(seg.length>=3){
                       const veh = seg[2];
                       if(veh && !['info','remote','vehicles'].includes(veh)) seen[veh]=true;
                     }
                   }
                   const list = Object.keys(seen);
                   return list.length ? list[0] : '';
                 }catch(e){ log('VIN-Detect: '+e,'warn'); return ''; }
                }
                
                /* =================== KANDIDATEN =================== */
                function candidates(){
                 return {
                   // Identität / Fahrt
                   carName: [ P('general.carName'), P('general.modelName') ],
                   vin:     [ P('general.vin') ],
                   odometer_km: [ P('odometer.value') ],
                   speed:       [ P('vehicleLocation.speed') ],
                
                   // Standort (mit Fallbacks)
                   lat: [ P('vehicleLocation.lat'), P('location.coord.lat') ],
                   lon: [ P('vehicleLocation.lon'), P('location.coord.lon') ],
                   position_text: [ P('vehicleLocation.position_text'), P('location.formattedAddress') ],
                   position_url:  [ P('vehicleLocation.position_url') ],
                
                   // HV & 12V
                   soc_pct:            [ P('vehicleStatus.battery.soc') ],
                   charge_active:      [ P('vehicleStatus.battery.charge') ],
                   minutes_to_charged: [ P('vehicleStatus.battery.minutes_to_charged') ],
                   plugin_code:        [ P('vehicleStatus.battery.plugin') ],
                   soc12v:             [ P('vehicleStatus.battery.soc-12V') ],
                   state12v:           [ P('vehicleStatus.battery.state-12V') ],
                   soh:                [ P('vehicleStatus.battery.soh') ],
                   charge12v:          [ DP_12V_LADEN ],
                
                   // Klima & Komfort
                   hvacOn:     [ P('vehicleStatus.airCtrlOn') ],
                   insideTemp: [ P('vehicleStatus.airTemp') ],
                   airClean:   [ P('vehicleStatusRaw.vehicleStatus.airCleaning.airPurifierStatus') ],
                   defrost:    [ P('vehicleStatusRaw.vehicleStatus.defrost') ],
                   seatFL:     [ P('vehicleStatusRaw.vehicleStatus.seatHeaterVentState.flSeatHeatState') ],
                   seatFR:     [ P('vehicleStatusRaw.vehicleStatus.seatHeaterVentState.frSeatHeatState') ],
                   seatRL:     [ P('vehicleStatusRaw.vehicleStatus.seatHeaterVentState.rlSeatHeatState') ],
                   seatRR:     [ P('vehicleStatusRaw.vehicleStatus.seatHeaterVentState.rrSeatHeatState') ],
                   steerHeat:  [ P('vehicleStatus.steerWheelHeat') ],
                
                   // Öffnungen / Fenster
                   doorFL: [ P('vehicleStatus.doorOpen.frontLeft') ],
                   doorFR: [ P('vehicleStatus.doorOpen.frontRight') ],
                   doorRL: [ P('vehicleStatus.doorOpen.backLeft') ],
                   doorRR: [ P('vehicleStatus.doorOpen.backRight') ],
                   trunk:  [ P('vehicleStatus.trunkOpen') ],
                   frunk:  [ P('vehicleStatus.hoodOpen') ],
                   winFL:  [ P('vehicleStatusRaw.vehicleStatus.windowOpen.frontLeft') ],
                   winFR:  [ P('vehicleStatusRaw.vehicleStatus.windowOpen.frontRight') ],
                   winRL:  [ P('vehicleStatusRaw.vehicleStatus.windowOpen.backLeft') ],
                   winRR:  [ P('vehicleStatusRaw.vehicleStatus.windowOpen.backRight') ],
                
                   // Reifen-Warnlampen
                   tireFL: [ P('vehicleStatusRaw.vehicleStatus.tirePressureLamp.tirePressureLampFL') ],
                   tireFR: [ P('vehicleStatusRaw.vehicleStatus.tirePressureLamp.tirePressureLampFR') ],
                   tireRL: [ P('vehicleStatusRaw.vehicleStatus.tirePressureLamp.tirePressureLampRL') ],
                   tireRR: [ P('vehicleStatusRaw.vehicleStatus.tirePressureLamp.tirePressureLampRR') ],
                
                   // Flüssigkeiten & Warnungen
                   dte:        [ P('vehicleStatusRaw.vehicleStatus.dte.value') ],
                   washer:     [ P('vehicleStatus.washerFluidStatus') ],         // false=OK, true=leer
                   smartKeyBat:[ P('vehicleStatus.smartKeyBatteryWarning') ],     // true=Warnung
                   breakOil:   [ P('vehicleStatus.breakOilStatus') ],             // false=OK, true=Niedrig
                
                   // Wallbox
                   wb_energy: [ WB.energy ],
                   wb_state:  [ WB.state ],
                   wb_power:  [ WB.power ],
                   wb_temp1:  [ WB.temp1 ],
                   wb_temp2:  [ WB.temp2 ],
                   wb_allow:  [ WB.allow ],
                   wb_amp:    [ WB.amp ],
                
                   // Zeit / extra
                   lastUpdate:  [ P('info.lastUpdate'), P('vehicleStatus.updatedAt') ],
                   kmYesterday: [ DP_KM_YESTER ]
                 };
                }
                
                /* =================== CSS =================== */
                function css(){ return `
                <style>
                :root{
                 --bg:#0a0c10; --card:rgba(18,21,28,0.88); --muted:#8a93a6; --text:#e7ecf6;
                 --ok:#33d17a; --warn:#ffbf3c; --err:#ff5c5c; --accent:#60a5fa; --chip:#1b2030; --chipText:#cfe0ff;
                }
                *{box-sizing:border-box}
                .wrap{
                 font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,sans-serif;
                 color:var(--text); padding:16px; min-height:100vh;
                 background:url('http://10.1.1.2:8081/files/0_userdata.0/background/IMG_2721.jpeg') center/cover no-repeat fixed;
                }
                .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:12px;align-items:stretch}
                .card{background:var(--card);border-radius:12px;padding:12px;box-shadow:0 4px 14px rgba(0,0,0,.25);height:100%;display:flex;flex-direction:column}
                .row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
                .title{display:flex;align-items:center;gap:10px;font-weight:700;font-size:18px;margin-bottom:10px}
                .kpi{font-size:26px;font-weight:800}
                .sub{color:var(--muted);font-size:13px}
                .badge{background:#1b2030;color:#cfe0ff;padding:6px 12px;border-radius:10px;font-size:14px;display:inline-block;max-width:100%;white-space:normal;word-break:break-word;text-align:center}
                .stat{display:flex;justify-content:space-between;margin:4px 0;font-size:13px}
                .meter{height:10px;background:#0f1220;border-radius:8px;overflow:hidden}
                .meter>span{display:block;height:100%}
                .kv{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:8px}
                .tile{background:#0f1220;border-radius:8px;padding:6px;font-size:12px;display:flex;align-items:center;gap:6px}
                .ok{color:var(--ok)} .warn{color:var(--warn)} .err{color:var(--err)} .acc{color:var(--accent)}
                .footer{margin-top:8px;color:var(--muted);font-size:12px;text-align:right}
                table{width:100%;border-collapse:collapse;font-size:12px}
                th,td{padding:6px 8px;border-bottom:1px solid #222}
                th{text-align:left;color:#cfe0ff}
                /* Chart Box: gleiche Höhe wie Tabelle (~280px), passt sich Breite an */
                .chartBox{width:100%;height:280px;display:block}
                .chartBox svg{width:100%;height:100%;display:block}
                </style>`; }
                
                /* =================== SVG CHART BUILDER =================== */
                function buildHistorySVG(labels, soc, kwh){
                 // Basisgröße für viewBox (skaliert über CSS auf 100%x100% in .chartBox)
                 const W=720, H=280; // größer als vorher
                 const padL=44, padR=14, padT=14, padB=34;
                 const x0=padL, y0=padT, x1=W-padR, y1=H-padB;
                 const w=x1-x0, h=y1-y0;
                 const n = Math.max(1, labels.length||1);
                 const xAt = (i)=> x0 + (n<=1 ? 0 : (w * i/(n-1)));
                 const ySoc = (v)=> y0 + (1 - (Math.max(0,Math.min(100, +v||0))/100)) * h;
                
                 // Bars scale
                 let maxK=0; for (let i=0;i<kwh.length;i++){ const v=+kwh[i]||0; if (v>maxK) maxK=v; }
                 if (maxK<1) maxK=1;
                 const barW = Math.max(8, Math.min(28, w / (n*1.8)));
                 const bars = [];
                 for (let i=0;i<n;i++){
                   const xx = xAt(i);
                   const kh = ( ( (+kwh[i]||0)/maxK ) * h );
                   const x = xx - barW/2;
                   const y = y1 - kh;
                   const bw = barW;
                   const bh = kh;
                   bars.push(`<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${bw.toFixed(1)}" height="${bh.toFixed(1)}" rx="2" fill="#60a5fa" />`);
                 }
                
                 // SOC path
                 let d='';
                 for (let i=0;i<n;i++){
                   const xx = xAt(i), yy = ySoc(soc[i]||0);
                   d += (i===0? 'M':' L') + xx.toFixed(1) + ' ' + yy.toFixed(1);
                 }
                 const points = soc.map((v,i)=>{
                   const xx=xAt(i), yy=ySoc(v||0);
                   return `<circle cx="${xx.toFixed(1)}" cy="${yy.toFixed(1)}" r="3" fill="#33d17a"/>`;
                 }).join('');
                
                 // grid & labels
                 const grid=[];
                 for (let gy=0; gy<=5; gy++){
                   const yy = y0 + h*(gy/5);
                   grid.push(`<line x1="${x0}" y1="${yy.toFixed(1)}" x2="${x1}" y2="${yy.toFixed(1)}" stroke="rgba(255,255,255,0.15)" stroke-width="1"/>`);
                 }
                 const ylab=[];
                 for (let gy=0; gy<=5; gy++){
                   const val = 100 - gy*20;
                   const yy = y0 + h*(gy/5);
                   ylab.push(`<text x="${x0-8}" y="${yy+4}" fill="#cfe0ff" font-size="12" text-anchor="end">${val}</text>`);
                 }
                 const xlab = labels.map((t,i)=>{
                   const xx = xAt(i);
                   return `<text x="${xx}" y="${y1+18}" fill="#cfe0ff" font-size="12" text-anchor="middle">${String(t)}</text>`;
                 }).join('');
                
                 return `
                 <svg viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Ladehistorie">
                   <rect x="0" y="0" width="${W}" height="${H}" fill="transparent"/>
                   <rect x="${x0}" y="${y0}" width="${w}" height="${h}" rx="6" fill="rgba(255,255,255,0.04)"/>
                   ${grid.join('')}
                   ${ylab.join('')}
                   ${xlab}
                   ${bars.join('')}
                   <path d="${d}" fill="none" stroke="#33d17a" stroke-width="2.5"/>
                   ${points}
                   <!-- Legende -->
                   <g>
                     <rect x="${x0+6}" y="${y0+6}" width="12" height="3" fill="#33d17a"/><text x="${x0+24}" y="${y0+14}" font-size="13" fill="#cfe0ff">SOC %</text>
                     <rect x="${x0+92}" y="${y0+6}" width="12" height="12" fill="#60a5fa"/><text x="${x0+110}" y="${y0+16}" font-size="13" fill="#cfe0ff">kWh/Tag</text>
                   </g>
                 </svg>`;
                }
                
                /* =================== RENDER =================== */
                function renderHTML(d, hist, lastSamples){
                 if (!d || d.__noVin){
                   return `${css()}<div class="wrap"><div class="card"><div class="kpi">Bluelink</div><div class="sub">Bitte VIN (VEHICLE_ID) setzen oder Auto-Detect abwarten.</div></div></div>`;
                 }
                
                 const hvColor = p=>p<75?'#ff5c5c':(p<80?'#ffbf3c':'#33d17a');
                 const socBar = p => (p!=null ? `
                   <div class="meter" style="height:14px;border-radius:7px;overflow:hidden">
                     <span style="width:${Math.max(0,Math.min(100,p))}%;background:${hvColor(p)}"></span>
                   </div>
                   <div style="font-size:12px;text-align:right;margin-top:2px">${p}%</div>` : '–');
                
                 const seatTxt = v => (v===1?'<span class="heatAnim">🔥 Heizen</span>':(v===0?'<span class="coolAnim">❄️ Kühlen</span>':'Aus'));
                 const flag = (v, ok='geschlossen', bad='offen') => v===true?`<b class="err">${bad}</b>`:(v===false?`<b class="ok">${ok}</b>`:'–');
                 const tireTxt = v => (v===true ? '<b class="warn">Warn</b>' : (v===false ? '<b class="ok">OK</b>' : '–'));
                
                 const locStr = d.address || '–';
                 const coords = (d.lat!=null && d.lon!=null) ? `${(+d.lat).toFixed(5)}, ${(+d.lon).toFixed(5)}` : '–';
                 const wbStateTxt = (d.wb_stateNum!=null? (WB_STATE_TXT[d.wb_stateNum]||String(d.wb_stateNum)) : (d.wb_stateText||'–'));
                
                 // Historie-Daten (letzte 7 Tage)
                 const labels = hist.labels || ['Mo','Di','Mi','Do','Fr','Sa','So'];
                 const socLine = hist.dailySoc || [0,0,0,0,0,0,0];
                 const kwhBars = hist.dailyKwh || [0,0,0,0,0,0,0];
                
                 // Tabelle der letzten 10 Einträge
                 let tableRows = '';
                 if (lastSamples && lastSamples.length){
                   const last10 = lastSamples.slice(-10);
                   tableRows = last10.map(s=>{
                     const dt = new Date(s.t).toLocaleString();
                     const soc = (typeof s.soc==='number') ? (Math.round(s.soc*10)/10)+' %' : '–';
                     const wb  = (typeof s.wb ==='number') ? (Math.round(s.wb*100)/100).toFixed(2)+' kWh' : '–';
                     return `<tr><td>${dt}</td><td>${soc}</td><td>${wb}</td></tr>`;
                   }).join('');
                 }
                
                 const svgChart = buildHistorySVG(labels, socLine, kwhBars);
                
                 return `
                 ${css()}
                 <div class="wrap">
                   <div class="title"><span class="kpi">${d.carName||'IONIQ 5 N'}</span> <span class="badge">${d.vin||''}</span></div>
                
                   <div class="grid">
                     <!-- HV Akku -->
                     <div class="card">
                       <div class="row">🔋 <b>State of Charge</b></div>
                       <div class="kpi" style="margin:6px 0">${d.soc!=null? d.soc+' %' : '–'}</div>
                       ${socBar(d.soc)}
                       <div class="stat"><span>Laden</span><span class="${d.charging?'chargeAnim':''}"><b>${d.charging===true?'aktiv':(d.charging===false?'nein':'–')}</b></span></div>
                       <div class="stat"><span>Min. bis voll</span><span><b>${d.minToFull!=null? d.minToFull : '–'}</b></span></div>
                       <div class="stat"><span>Reichweite</span><span><b>${d.dte!=null? d.dte+' km':'–'}</b></span></div>
                     </div>
                
                     <!-- 12V / SOH -->
                     <div class="card">
                       <div class="row">🔋 <b>12V & SOH</b></div>
                       <div class="stat"><span>12V SoC</span><span style="flex:1;margin-left:10px">${socBar(d.soc12v)}</span></div>
                       <div class="stat"><span>12V Status</span><span><b>${d.state12v ?? '–'}</b></span></div>
                       <div class="stat"><span>12V lädt</span><span><b>${d.charge12v===true?'Ja':(d.charge12v===false?'Nein':'–')}</b></span></div>
                       <div class="stat"><span>SOH (HV)</span><span><b>${d.soh!=null? d.soh+' %':'–'}</b></span></div>
                     </div>
                
                     <!-- Fahrzeug -->
                     <div class="card">
                       <div class="row">🚗 <b>Fahrzeug</b></div>
                       <div class="stat"><span>Odometer</span><span><b>${d.odoKm!=null? d.odoKm+' km':'–'}</b></span></div>
                       <div class="stat"><span>Gestern gefahren</span><span><b>${d.kmYesterday!=null? d.kmYesterday+' km':'–'}</b></span></div>
                     </div>
                
                     <!-- Standort -->
                     <div class="card">
                       <div class="row">📍 <b>Standort</b></div>
                       <div class="badge">${locStr}</div>
                       <div class="sub" style="margin-top:8px">Koordinaten: <b>${coords}</b></div>
                     </div>
                
                     <!-- Türen -->
                     <div class="card">
                       <div class="row">🚪 <b>Öffnungen</b></div>
                       <div class="kv">
                         <div class="tile">VL: ${flag(d.doorFL)}</div>
                         <div class="tile">VR: ${flag(d.doorFR)}</div>
                         <div class="tile">HL: ${flag(d.doorRL)}</div>
                         <div class="tile">HR: ${flag(d.doorRR)}</div>
                         <div class="tile">Kofferraum: ${flag(d.trunk)}</div>
                         <div class="tile">Frunk: ${flag(d.frunk)}</div>
                       </div>
                       <div class="sub" style="margin-top:6px">Gesamt: ${ [d.doorFL,d.doorFR,d.doorRL,d.doorRR,d.trunk,d.frunk].some(v=>v===true) ? '<b class="err">offen</b>' : '<b class="ok">alle zu</b>' }</div>
                     </div>
                
                     <!-- Fenster -->
                     <div class="card">
                       <div class="row">🪟 <b>Fenster</b></div>
                       <div class="kv">
                         <div class="tile">VL: ${flag(d.winFL,'zu','offen')}</div>
                         <div class="tile">VR: ${flag(d.winFR,'zu','offen')}</div>
                         <div class="tile">HL: ${flag(d.winRL,'zu','offen')}</div>
                         <div class="tile">HR: ${flag(d.winRR,'zu','offen')}</div>
                       </div>
                     </div>
                
                     <!-- Reifen -->
                     <div class="card">
                       <div class="row">🛞 <b>Reifen</b></div>
                       <div class="kv">
                         <div class="tile">VL: ${tireTxt(d.tireFL)}</div>
                         <div class="tile">VR: ${tireTxt(d.tireFR)}</div>
                         <div class="tile">HL: ${tireTxt(d.tireRL)}</div>
                         <div class="tile">HR: ${tireTxt(d.tireRR)}</div>
                       </div>
                     </div>
                
                     <!-- Klima & Komfort -->
                     <div class="card">
                       <div class="row">❄️ <b>Klima & Komfort</b></div>
                       <div class="stat"><span>Klima</span><span class="${d.hvacOn?'fanAnim':''}"><b>${d.hvacOn===true?'AN':(d.hvacOn===false?'AUS':'–')}</b></span></div>
                       <div class="stat"><span>Innen</span><span><b>${d.tIn!=null? d.tIn+' °C':'–'}</b></span></div>
                       <div class="stat"><span>Luftreiniger</span><span><b>${d.airClean===true?'AN':(d.airClean===false?'AUS':'–')}</b></span></div>
                       <div class="stat"><span>Defrost</span><span><b>${d.defrost===true?'AN':(d.defrost===false?'AUS':'–')}</b></span></div>
                       <div class="stat"><span>Lenkrad</span><span>${d.steerHeat===true?'<span class="heatAnim">🔥</span>':'AUS'}</span></div>
                       <div class="sub" style="margin-top:6px">Sitze:</div>
                       <div class="kv">
                         <div class="tile">VL: <b>${seatTxt(d.seatFL)}</b></div>
                         <div class="tile">VR: <b>${seatTxt(d.seatFR)}</b></div>
                         <div class="tile">HL: <b>${seatTxt(d.seatRL)}</b></div>
                         <div class="tile">HR: <b>${seatTxt(d.seatRR)}</b></div>
                       </div>
                     </div>
                
                     <!-- Fahrzeugstatus -->
                     <div class="card">
                       <div class="row">ℹ️ <b>Fahrzeugstatus</b></div>
                       <div class="kv">
                         <div class="tile">Wischerwasser: ${d.washer===true?'<b class="err">LEER</b>':(d.washer===false?'<b class="ok">OK</b>':'–')}</div>
                         <div class="tile">Bremsöl: ${d.breakOil===true?'<b class="err">NIEDRIG</b>':(d.breakOil===false?'<b class="ok">OK</b>':'–')}</div>
                         <div class="tile">Smartkey: ${d.smartKeyBat===true?'<b class="warn">WARNUNG</b>':(d.smartKeyBat===false?'<b class="ok">OK</b>':'–')}</div>
                       </div>
                     </div>
                
                     <!-- Wallbox -->
                     <div class="card">
                       <div class="row">🔌 <b>Wallbox</b></div>
                       <div class="kv">
                         <div class="tile">Status: <b>${wbStateTxt}</b></div>
                         <div class="tile">Leistung: <b>${d.wb_powerFmt || '–'}</b></div>
                         <div class="tile">Energie: <b>${d.wb_energyFmt || '–'} kWh</b></div>
                         <div class="tile">Ampere: <b>${d.wb_ampere!=null? d.wb_ampere+' A':'–'}</b></div>
                         <div class="tile">Freigabe: <b>${d.wb_allow===true?'Ja':(d.wb_allow===false?'Nein':'–')}</b></div>
                         <div class="tile">Temp1: <b>${d.wb_temp1!=null? d.wb_temp1+' °C':'–'}</b></div>
                         <div class="tile">Temp2: <b>${d.wb_temp2!=null? d.wb_temp2+' °C':'–'}</b></div>
                       </div>
                     </div>
                
                     <!-- Ladehistorie (7 Tage) – reines SVG, volle Breite/Höhe -->
                     <div class="card">
                       <div class="row">📈 <b>Ladehistorie (7 Tage)</b></div>
                       <div class="chartBox">
                         ${svgChart}
                       </div>
                     </div>
                
                     <!-- Ladehistorie Tabelle (letzte 10 Samples) -->
                     <div class="card">
                       <div class="row">⚡ <b>Ladehistorie – letzte 10 Werte</b></div>
                       ${ (lastSamples && lastSamples.length)
                           ? `<table><tr><th>Zeitpunkt</th><th>SOC</th><th>Wallbox</th></tr>${tableRows}</table>`
                           : `<div class="sub">Keine Daten vorhanden.</div>` }
                     </div>
                   </div>
                
                   <div class="footer">Zuletzt aktualisiert: ${d.lastUpdate || new Date().toLocaleString()}</div>
                 </div>`;
                }
                
                /* =================== HISTORIE: DPs + Verarbeitung =================== */
                function ensureHistoryDPs(){
                 ensureDataPoint(HIST_SAMPLES_DP, '[]', {name:'Ioniq History Samples', type:'string', role:'json'});
                 ensureDataPoint(HIST_LAST_TS_DP, 0,    {name:'Ioniq History Last Sample', type:'number', role:'value.time'});
                }
                function _normalizeSamples(arr){
                 const now = Date.now();
                 const twelveH = 12*3600*1000;
                 return arr.map(s=>{
                   if(!s) return null;
                   let t = Number(s.t);
                   if (!isFinite(t)) return null;
                   if (t < 1e12) t = t * 1000; // Sekunden -> ms
                   if (t > now + twelveH) return null; // Zukunft verwerfen
                   const out = { t };
                   if (typeof s.soc === 'number' && isFinite(s.soc)) out.soc = s.soc;
                   if (typeof s.wb  === 'number' && isFinite(s.wb )) out.wb  = s.wb;
                   return out;
                 }).filter(Boolean);
                }
                function loadSamples(){
                 try{
                   const txt=gs(HIST_SAMPLES_DP);
                   if(!txt) return [];
                   const parsed = JSON.parse(txt);
                   const arr = Array.isArray(parsed)? parsed : [];
                   return _normalizeSamples(arr);
                 }catch(_){ return []; }
                }
                function saveSamples(arr){
                 try{
                   if (arr.length>MAX_SAMPLES) arr = arr.slice(arr.length-MAX_SAMPLES);
                   ss(HIST_SAMPLES_DP, JSON.stringify(arr));
                 }catch(_){}
                }
                function trySample(nowMs, data){
                 const lastTs = Number(gs(HIST_LAST_TS_DP) || 0);
                 if (nowMs - lastTs < SAMPLE_INTERVAL_MS) return;
                
                 const soc = data._hist_soc;
                 const wb  = data._hist_wb_energy;
                
                 if (soc==null && wb==null) {
                   ss(HIST_LAST_TS_DP, nowMs);
                   if (DEBUG) log('[IONIQ5N] Sample SKIP: keine Werte (soc/wb beide leer)', 'info');
                   return;
                 }
                
                 let arr = loadSamples();
                 arr.push({ t: nowMs, soc: soc, wb: wb });
                 saveSamples(arr);
                 ss(HIST_LAST_TS_DP, nowMs);
                
                 if (DEBUG) {
                   const socTxt = (soc==null)?'—':String(soc);
                   const wbTxt  = (wb==null)?'—':String(wb);
                   log(`[IONIQ5N] Sample OK @ ${new Date(nowMs).toLocaleString()} | SOC=${socTxt} | WB=${wbTxt}`, 'info');
                 }
                }
                function computeDailyFromSamples(nowMs){
                 const arr = loadSamples();
                 // Labels & Tageskeys (letzte 7 Tage inkl. heute)
                 const labels=[], dayKeys=[];
                 for(let i=6;i>=0;i--){
                   const d=new Date(nowMs - i*24*3600*1000);
                   labels.push(d.toLocaleDateString(undefined,{weekday:'short'}));
                   dayKeys.push(`${d.getFullYear()}-${('0'+(d.getMonth()+1)).slice(-2)}-${('0'+d.getDate()).slice(-2)}`);
                 }
                 if(!arr.length) return {labels, dailySoc:[0,0,0,0,0,0,0], dailyKwh:[0,0,0,0,0,0,0]};
                 const byDay={};
                 for(const s of arr){
                   const d=new Date(s.t);
                   const key=`${d.getFullYear()}-${('0'+(d.getMonth()+1)).slice(-2)}-${('0'+d.getDate()).slice(-2)}`;
                   if(!byDay[key]) byDay[key]={soc:[], wb:[]};
                   if(typeof s.soc==='number') byDay[key].soc.push(s.soc);
                   if(typeof s.wb ==='number') byDay[key].wb.push(s.wb);
                 }
                 const dailySoc=[], dailyKwh=[];
                 for(const key of dayKeys){
                   const g=byDay[key];
                   if(!g){ dailySoc.push(0); dailyKwh.push(0); continue; }
                   const avgSoc = g.soc.length ? Math.round(g.soc.reduce((a,b)=>a+b,0)/g.soc.length) : 0;
                   let kwh=0;
                   if(g.wb.length){
                     const mn=Math.min.apply(null,g.wb), mx=Math.max.apply(null,g.wb);
                     kwh = mx-mn; if(!isFinite(kwh)||kwh<0) kwh=0;
                     kwh = Math.round(kwh*100)/100;
                   }
                   dailySoc.push(avgSoc);
                   dailyKwh.push(kwh);
                 }
                 return {labels, dailySoc, dailyKwh};
                }
                
                /* =================== DATEN SAMMELN =================== */
                function readAll(){
                 const cand = candidates();
                 const pick = (name, map=v=>v)=>{
                   const {val} = firstExisting(cand[name]||[]);
                   if (val===undefined) return undefined;
                   try{
                     if (typeof val === 'string' && !isNaN(val)) return map(Number(val));
                     return map(val);
                   }catch(_){ return val; }
                 };
                 const toBool = (v) => (typeof v==='boolean') ? v : (v!=null ? Number(v)>0 : undefined);
                
                 // Fahrwerte
                 const odoKm = (function(){
                   const v = pick('odometer_km', x=>x);
                   if (v===undefined) return undefined;
                   const num = typeof v==='string' ? parseFloat(v.replace(/[^\d.,]/g,'').replace(',','.')) : Number(v);
                   return isNaN(num) ? undefined : Math.round(num);
                 })();
                
                 // Wallbox Zahlen
                 const wb_stateNum = pick('wb_state', Number);
                 const wb_powerNum = pick('wb_power', Number);
                 const wb_energyNum= pick('wb_energy', Number);
                
                 const data = {
                   // Kopf
                   carName: pick('carName'),
                   vin: pick('vin'),
                
                   // HV / 12V
                   soc: pick('soc_pct', v => Math.round(Number(v))),
                   charging: pick('charge_active', toBool),
                   minToFull: pick('minutes_to_charged', Number),
                   soc12v: pick('soc12v', v => Math.round(Number(v))),
                   state12v: pick('state12v'),
                   soh: pick('soh', v => Math.round(Number(v))),
                   charge12v: pick('charge12v', v => v === true || v === 'true' || Number(v) === 1),
                
                   // Reichweite & Klima
                   dte: pick('dte', v => Math.round(Number(v))),
                   hvacOn: pick('hvacOn', toBool),
                   tIn: pick('insideTemp', v => Math.round(Number(v)*10)/10),
                   airClean: pick('airClean', toBool),
                   defrost: pick('defrost', toBool),
                   seatFL: pick('seatFL', Number),
                   seatFR: pick('seatFR', Number),
                   seatRL: pick('seatRL', Number),
                   seatRR: pick('seatRR', Number),
                   steerHeat: pick('steerHeat', toBool),
                
                   // Öffnungen / Fenster
                   doorFL: pick('doorFL', toBool),
                   doorFR: pick('doorFR', toBool),
                   doorRL: pick('doorRL', toBool),
                   doorRR: pick('doorRR', toBool),
                   trunk: pick('trunk', toBool),
                   frunk: pick('frunk', toBool),
                   winFL: pick('winFL', toBool),
                   winFR: pick('winFR', toBool),
                   winRL: pick('winRL', toBool),
                   winRR: pick('winRR', toBool),
                
                   // Reifenlampen
                   tireFL: pick('tireFL', toBool),
                   tireFR: pick('tireFR', toBool),
                   tireRL: pick('tireRL', toBool),
                   tireRR: pick('tireRR', toBool),
                
                   // Flüssigkeiten & Warnungen
                   washer: pick('washer', toBool),
                   breakOil: pick('breakOil', toBool),
                   smartKeyBat: pick('smartKeyBat', toBool),
                
                   // Standort
                   lat: pick('lat', Number),
                   lon: pick('lon', Number),
                   address: pick('position_text', String),
                   positionUrl: pick('position_url', String),
                
                   // Wallbox – formatierte Strings
                   wb_stateNum,
                   wb_stateText: (wb_stateNum!=null ? (WB_STATE_TXT[wb_stateNum] || String(wb_stateNum)) : undefined),
                   wb_powerFmt:  (wb_powerNum!=null ? (wb_powerNum >= 1000 ? (Math.round(wb_powerNum/100)/10)+' kW' : Math.round(wb_powerNum)+' W') : undefined),
                   wb_energyFmt: (wb_energyNum!=null ? wb_energyNum.toFixed(2) : undefined),
                   wb_ampere: pick('wb_amp', v => Math.round(Number(v))),
                   wb_allow: pick('wb_allow', toBool),
                   wb_temp1: pick('wb_temp1', v => Math.round(Number(v))),
                   wb_temp2: pick('wb_temp2', v => Math.round(Number(v))),
                
                   // Zeit / extra
                   lastUpdate: (function(){
                     const raw = pick('lastUpdate');
                     if (!raw) return '';
                     try{ const d=new Date(raw); return isNaN(d)? String(raw) : d.toLocaleString(); }catch(_){ return String(raw); }
                   })(),
                   kmYesterday: pick('kmYesterday', v=> (v==null? undefined : Math.round(Number(v)))),
                
                   // Rohwerte für Historie-Sampling
                   _hist_soc: pick('soc_pct', Number),
                   _hist_wb_energy: wb_energyNum,
                
                   // Odometer
                   odoKm
                 };
                
                 return data;
                }
                
                /* =================== MAIN =================== */
                ensureState(OUT_HTML_DP);
                ensureHistoryDPs();
                let vinAnnounced = false;
                
                function update(){
                 try{
                   const nowMs = Date.now();
                
                   if (!VEHICLE_ID){
                     VEHICLE_ID = detectVehicleId();
                     if (VEHICLE_ID && !vinAnnounced){ log('[IONIQ5N] Auto-Detected VIN: '+VEHICLE_ID,'info'); vinAnnounced=true; }
                   }
                   if (!VEHICLE_ID){
                     ss(OUT_HTML_DP, renderHTML({__noVin:true}, {labels:[],dailySoc:[],dailyKwh:[]}, []));
                     return;
                   }
                
                   const data = readAll();
                   trySample(nowMs, data);
                   const hist = computeDailyFromSamples(nowMs);
                   const lastSamples = loadSamples();
                   const html = renderHTML(data, hist, lastSamples);
                   ss(OUT_HTML_DP, html);
                 }catch(e){ log('Update error: '+e, 'error'); }
                }
                
                // Initial + Intervall
                update();
                schedule('*/30 * * * * *', update);
                

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

                Wolfgang GaryW 1 Antwort Letzte Antwort
                4
                • ilovegymI ilovegym

                  @arteck

                  Hab mal meinen digitalen Codierknecht :robot_face: solange gequaelt, bis das hier rauskam:
                  Screenshot 2025-08-16 at 15.03.35.png

                  Ist n einfaches Dashboard, kann in jeder Vis (Vis, Vis-NG, MinuVis, Jarvis und was es noch so gibt) angezeigt werden, legt die Daten in einen State ab.
                  Hier das Script dazu, das Hintergrundbild kann natuerlich angepasst werden, muss nich meins von der Nordschleife sein :)
                  Eingebaut sind mir alle sinvoll vorgekommenen Werte des Adapters Bluelink, sowie von meiner Wallbox Go-E und ein paar selbst definierte (12V laedt), desweiteren eine Ladehistorie graphisch und tabellar.. :) - responsive Design, kann man auch aufm Smartphone ansehen

                  /******************************************************
                  * IONIQ 5 N – Bluelink Dashboard (HTML) for ioBroker
                  * Version: 1.6.6  (SVG-Chart responsive, volle Kartenbreite & ~Tabelle-Höhe)
                  * (c) 2025 Bernd / ilovegym – privat
                  ******************************************************/
                  
                  /* =================== KONFIG =================== */
                  const BLUELINK_INST = 'bluelink.0';
                  let   VEHICLE_ID    = '';          // leer = Auto-Detect
                  const OUT_HTML_DP   = '0_userdata.0.vis.IoniqDashboardHTML';
                  
                  // Eigene Zusatz-DPs
                  const DP_12V_LADEN  = '0_userdata.0.Zaehler.Hyundai_12V';   // true = 12V lädt
                  const DP_KM_YESTER  = '0_userdata.0.Zaehler.Hyundai_KM_';   // km gestern
                  
                  // Wallbox (go-e)
                  const WB = {
                   energy:  'go-e.0.loaded_energy_kwh',
                   state:   'go-e.0.car',                // 1 Standby, 2 Laden, 3 Warten auf Auto, 4 Fertig
                   power:   'go-e.0.energy.power',
                   temp1:   'go-e.0.temperatures.temperature1',
                   temp2:   'go-e.0.temperatures.temperature2',
                   allow:   'go-e.0.allow_charging',
                   amp:     'go-e.0.ampere'
                  };
                  const WB_STATE_TXT = {1:'Standby',2:'Laden',3:'Warte auf Auto',4:'Fertig'};
                  
                  // Ladehistorie (Samples werden hier gespeichert)
                  const HIST_SAMPLES_DP = '0_userdata.0.Ioniq.History.samples';   // string (JSON Array: {t,soc,wb})
                  const HIST_LAST_TS_DP = '0_userdata.0.Ioniq.History.lastSample'; // number (ms)
                  const SAMPLE_INTERVAL_MS = 5 * 60 * 1000; // alle 5 Minuten
                  const MAX_SAMPLES = 3000; // ~7 Tage bei 5-Minuten-Samples
                  
                  // Debug
                  const DEBUG = false;
                  
                  /* =================== UTIL =================== */
                  function ensureState(id, def = '', common = {name:'IONIQ 5 N Dashboard HTML', type:'string', role:'html', read:true, write:false}) {
                   try { if (!existsObject(id)) createState(id, def, true, common); } catch (e) { log('ensureState error ' + e, 'warn'); }
                  }
                  function ensureDataPoint(id, def, common){ try{ if(!existsObject(id)) createState(id, def, true, common); }catch(e){} }
                  function JP(...parts){ return parts.filter(p => p !== '' && p != null).join('.'); }
                  function es(id){ try { return !!(id && existsState(id)); } catch(_) { return false; } }
                  function gs(id){ try { return es(id)? getState(id).val:undefined; } catch(_) { return undefined; } }
                  function ss(id, val){ try { setState(id, val, true); } catch(_){} }
                  function firstExisting(paths){ if(!Array.isArray(paths)) return {path:null,val:undefined}; for(const p of paths){ if(es(p)){ const v=gs(p); if(v!==undefined && v!==null) return {path:p,val:v}; } } return {path:null,val:undefined}; }
                  function P(...parts){ return JP(BLUELINK_INST, VEHICLE_ID, ...parts); }
                  
                  /* =================== VIN-AUTODETECT =================== */
                  function detectVehicleId(){
                   try{
                     const rows = getObjectView('system','state',{ startkey: BLUELINK_INST+'.', endkey: BLUELINK_INST+'.\u9999' }).rows;
                     const seen = {};
                     for(const r of rows){
                       const id = r.id || '';
                       const seg = id.split('.');
                       if(seg.length>=3){
                         const veh = seg[2];
                         if(veh && !['info','remote','vehicles'].includes(veh)) seen[veh]=true;
                       }
                     }
                     const list = Object.keys(seen);
                     return list.length ? list[0] : '';
                   }catch(e){ log('VIN-Detect: '+e,'warn'); return ''; }
                  }
                  
                  /* =================== KANDIDATEN =================== */
                  function candidates(){
                   return {
                     // Identität / Fahrt
                     carName: [ P('general.carName'), P('general.modelName') ],
                     vin:     [ P('general.vin') ],
                     odometer_km: [ P('odometer.value') ],
                     speed:       [ P('vehicleLocation.speed') ],
                  
                     // Standort (mit Fallbacks)
                     lat: [ P('vehicleLocation.lat'), P('location.coord.lat') ],
                     lon: [ P('vehicleLocation.lon'), P('location.coord.lon') ],
                     position_text: [ P('vehicleLocation.position_text'), P('location.formattedAddress') ],
                     position_url:  [ P('vehicleLocation.position_url') ],
                  
                     // HV & 12V
                     soc_pct:            [ P('vehicleStatus.battery.soc') ],
                     charge_active:      [ P('vehicleStatus.battery.charge') ],
                     minutes_to_charged: [ P('vehicleStatus.battery.minutes_to_charged') ],
                     plugin_code:        [ P('vehicleStatus.battery.plugin') ],
                     soc12v:             [ P('vehicleStatus.battery.soc-12V') ],
                     state12v:           [ P('vehicleStatus.battery.state-12V') ],
                     soh:                [ P('vehicleStatus.battery.soh') ],
                     charge12v:          [ DP_12V_LADEN ],
                  
                     // Klima & Komfort
                     hvacOn:     [ P('vehicleStatus.airCtrlOn') ],
                     insideTemp: [ P('vehicleStatus.airTemp') ],
                     airClean:   [ P('vehicleStatusRaw.vehicleStatus.airCleaning.airPurifierStatus') ],
                     defrost:    [ P('vehicleStatusRaw.vehicleStatus.defrost') ],
                     seatFL:     [ P('vehicleStatusRaw.vehicleStatus.seatHeaterVentState.flSeatHeatState') ],
                     seatFR:     [ P('vehicleStatusRaw.vehicleStatus.seatHeaterVentState.frSeatHeatState') ],
                     seatRL:     [ P('vehicleStatusRaw.vehicleStatus.seatHeaterVentState.rlSeatHeatState') ],
                     seatRR:     [ P('vehicleStatusRaw.vehicleStatus.seatHeaterVentState.rrSeatHeatState') ],
                     steerHeat:  [ P('vehicleStatus.steerWheelHeat') ],
                  
                     // Öffnungen / Fenster
                     doorFL: [ P('vehicleStatus.doorOpen.frontLeft') ],
                     doorFR: [ P('vehicleStatus.doorOpen.frontRight') ],
                     doorRL: [ P('vehicleStatus.doorOpen.backLeft') ],
                     doorRR: [ P('vehicleStatus.doorOpen.backRight') ],
                     trunk:  [ P('vehicleStatus.trunkOpen') ],
                     frunk:  [ P('vehicleStatus.hoodOpen') ],
                     winFL:  [ P('vehicleStatusRaw.vehicleStatus.windowOpen.frontLeft') ],
                     winFR:  [ P('vehicleStatusRaw.vehicleStatus.windowOpen.frontRight') ],
                     winRL:  [ P('vehicleStatusRaw.vehicleStatus.windowOpen.backLeft') ],
                     winRR:  [ P('vehicleStatusRaw.vehicleStatus.windowOpen.backRight') ],
                  
                     // Reifen-Warnlampen
                     tireFL: [ P('vehicleStatusRaw.vehicleStatus.tirePressureLamp.tirePressureLampFL') ],
                     tireFR: [ P('vehicleStatusRaw.vehicleStatus.tirePressureLamp.tirePressureLampFR') ],
                     tireRL: [ P('vehicleStatusRaw.vehicleStatus.tirePressureLamp.tirePressureLampRL') ],
                     tireRR: [ P('vehicleStatusRaw.vehicleStatus.tirePressureLamp.tirePressureLampRR') ],
                  
                     // Flüssigkeiten & Warnungen
                     dte:        [ P('vehicleStatusRaw.vehicleStatus.dte.value') ],
                     washer:     [ P('vehicleStatus.washerFluidStatus') ],         // false=OK, true=leer
                     smartKeyBat:[ P('vehicleStatus.smartKeyBatteryWarning') ],     // true=Warnung
                     breakOil:   [ P('vehicleStatus.breakOilStatus') ],             // false=OK, true=Niedrig
                  
                     // Wallbox
                     wb_energy: [ WB.energy ],
                     wb_state:  [ WB.state ],
                     wb_power:  [ WB.power ],
                     wb_temp1:  [ WB.temp1 ],
                     wb_temp2:  [ WB.temp2 ],
                     wb_allow:  [ WB.allow ],
                     wb_amp:    [ WB.amp ],
                  
                     // Zeit / extra
                     lastUpdate:  [ P('info.lastUpdate'), P('vehicleStatus.updatedAt') ],
                     kmYesterday: [ DP_KM_YESTER ]
                   };
                  }
                  
                  /* =================== CSS =================== */
                  function css(){ return `
                  <style>
                  :root{
                   --bg:#0a0c10; --card:rgba(18,21,28,0.88); --muted:#8a93a6; --text:#e7ecf6;
                   --ok:#33d17a; --warn:#ffbf3c; --err:#ff5c5c; --accent:#60a5fa; --chip:#1b2030; --chipText:#cfe0ff;
                  }
                  *{box-sizing:border-box}
                  .wrap{
                   font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,sans-serif;
                   color:var(--text); padding:16px; min-height:100vh;
                   background:url('http://10.1.1.2:8081/files/0_userdata.0/background/IMG_2721.jpeg') center/cover no-repeat fixed;
                  }
                  .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:12px;align-items:stretch}
                  .card{background:var(--card);border-radius:12px;padding:12px;box-shadow:0 4px 14px rgba(0,0,0,.25);height:100%;display:flex;flex-direction:column}
                  .row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
                  .title{display:flex;align-items:center;gap:10px;font-weight:700;font-size:18px;margin-bottom:10px}
                  .kpi{font-size:26px;font-weight:800}
                  .sub{color:var(--muted);font-size:13px}
                  .badge{background:#1b2030;color:#cfe0ff;padding:6px 12px;border-radius:10px;font-size:14px;display:inline-block;max-width:100%;white-space:normal;word-break:break-word;text-align:center}
                  .stat{display:flex;justify-content:space-between;margin:4px 0;font-size:13px}
                  .meter{height:10px;background:#0f1220;border-radius:8px;overflow:hidden}
                  .meter>span{display:block;height:100%}
                  .kv{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:8px}
                  .tile{background:#0f1220;border-radius:8px;padding:6px;font-size:12px;display:flex;align-items:center;gap:6px}
                  .ok{color:var(--ok)} .warn{color:var(--warn)} .err{color:var(--err)} .acc{color:var(--accent)}
                  .footer{margin-top:8px;color:var(--muted);font-size:12px;text-align:right}
                  table{width:100%;border-collapse:collapse;font-size:12px}
                  th,td{padding:6px 8px;border-bottom:1px solid #222}
                  th{text-align:left;color:#cfe0ff}
                  /* Chart Box: gleiche Höhe wie Tabelle (~280px), passt sich Breite an */
                  .chartBox{width:100%;height:280px;display:block}
                  .chartBox svg{width:100%;height:100%;display:block}
                  </style>`; }
                  
                  /* =================== SVG CHART BUILDER =================== */
                  function buildHistorySVG(labels, soc, kwh){
                   // Basisgröße für viewBox (skaliert über CSS auf 100%x100% in .chartBox)
                   const W=720, H=280; // größer als vorher
                   const padL=44, padR=14, padT=14, padB=34;
                   const x0=padL, y0=padT, x1=W-padR, y1=H-padB;
                   const w=x1-x0, h=y1-y0;
                   const n = Math.max(1, labels.length||1);
                   const xAt = (i)=> x0 + (n<=1 ? 0 : (w * i/(n-1)));
                   const ySoc = (v)=> y0 + (1 - (Math.max(0,Math.min(100, +v||0))/100)) * h;
                  
                   // Bars scale
                   let maxK=0; for (let i=0;i<kwh.length;i++){ const v=+kwh[i]||0; if (v>maxK) maxK=v; }
                   if (maxK<1) maxK=1;
                   const barW = Math.max(8, Math.min(28, w / (n*1.8)));
                   const bars = [];
                   for (let i=0;i<n;i++){
                     const xx = xAt(i);
                     const kh = ( ( (+kwh[i]||0)/maxK ) * h );
                     const x = xx - barW/2;
                     const y = y1 - kh;
                     const bw = barW;
                     const bh = kh;
                     bars.push(`<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${bw.toFixed(1)}" height="${bh.toFixed(1)}" rx="2" fill="#60a5fa" />`);
                   }
                  
                   // SOC path
                   let d='';
                   for (let i=0;i<n;i++){
                     const xx = xAt(i), yy = ySoc(soc[i]||0);
                     d += (i===0? 'M':' L') + xx.toFixed(1) + ' ' + yy.toFixed(1);
                   }
                   const points = soc.map((v,i)=>{
                     const xx=xAt(i), yy=ySoc(v||0);
                     return `<circle cx="${xx.toFixed(1)}" cy="${yy.toFixed(1)}" r="3" fill="#33d17a"/>`;
                   }).join('');
                  
                   // grid & labels
                   const grid=[];
                   for (let gy=0; gy<=5; gy++){
                     const yy = y0 + h*(gy/5);
                     grid.push(`<line x1="${x0}" y1="${yy.toFixed(1)}" x2="${x1}" y2="${yy.toFixed(1)}" stroke="rgba(255,255,255,0.15)" stroke-width="1"/>`);
                   }
                   const ylab=[];
                   for (let gy=0; gy<=5; gy++){
                     const val = 100 - gy*20;
                     const yy = y0 + h*(gy/5);
                     ylab.push(`<text x="${x0-8}" y="${yy+4}" fill="#cfe0ff" font-size="12" text-anchor="end">${val}</text>`);
                   }
                   const xlab = labels.map((t,i)=>{
                     const xx = xAt(i);
                     return `<text x="${xx}" y="${y1+18}" fill="#cfe0ff" font-size="12" text-anchor="middle">${String(t)}</text>`;
                   }).join('');
                  
                   return `
                   <svg viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Ladehistorie">
                     <rect x="0" y="0" width="${W}" height="${H}" fill="transparent"/>
                     <rect x="${x0}" y="${y0}" width="${w}" height="${h}" rx="6" fill="rgba(255,255,255,0.04)"/>
                     ${grid.join('')}
                     ${ylab.join('')}
                     ${xlab}
                     ${bars.join('')}
                     <path d="${d}" fill="none" stroke="#33d17a" stroke-width="2.5"/>
                     ${points}
                     <!-- Legende -->
                     <g>
                       <rect x="${x0+6}" y="${y0+6}" width="12" height="3" fill="#33d17a"/><text x="${x0+24}" y="${y0+14}" font-size="13" fill="#cfe0ff">SOC %</text>
                       <rect x="${x0+92}" y="${y0+6}" width="12" height="12" fill="#60a5fa"/><text x="${x0+110}" y="${y0+16}" font-size="13" fill="#cfe0ff">kWh/Tag</text>
                     </g>
                   </svg>`;
                  }
                  
                  /* =================== RENDER =================== */
                  function renderHTML(d, hist, lastSamples){
                   if (!d || d.__noVin){
                     return `${css()}<div class="wrap"><div class="card"><div class="kpi">Bluelink</div><div class="sub">Bitte VIN (VEHICLE_ID) setzen oder Auto-Detect abwarten.</div></div></div>`;
                   }
                  
                   const hvColor = p=>p<75?'#ff5c5c':(p<80?'#ffbf3c':'#33d17a');
                   const socBar = p => (p!=null ? `
                     <div class="meter" style="height:14px;border-radius:7px;overflow:hidden">
                       <span style="width:${Math.max(0,Math.min(100,p))}%;background:${hvColor(p)}"></span>
                     </div>
                     <div style="font-size:12px;text-align:right;margin-top:2px">${p}%</div>` : '–');
                  
                   const seatTxt = v => (v===1?'<span class="heatAnim">🔥 Heizen</span>':(v===0?'<span class="coolAnim">❄️ Kühlen</span>':'Aus'));
                   const flag = (v, ok='geschlossen', bad='offen') => v===true?`<b class="err">${bad}</b>`:(v===false?`<b class="ok">${ok}</b>`:'–');
                   const tireTxt = v => (v===true ? '<b class="warn">Warn</b>' : (v===false ? '<b class="ok">OK</b>' : '–'));
                  
                   const locStr = d.address || '–';
                   const coords = (d.lat!=null && d.lon!=null) ? `${(+d.lat).toFixed(5)}, ${(+d.lon).toFixed(5)}` : '–';
                   const wbStateTxt = (d.wb_stateNum!=null? (WB_STATE_TXT[d.wb_stateNum]||String(d.wb_stateNum)) : (d.wb_stateText||'–'));
                  
                   // Historie-Daten (letzte 7 Tage)
                   const labels = hist.labels || ['Mo','Di','Mi','Do','Fr','Sa','So'];
                   const socLine = hist.dailySoc || [0,0,0,0,0,0,0];
                   const kwhBars = hist.dailyKwh || [0,0,0,0,0,0,0];
                  
                   // Tabelle der letzten 10 Einträge
                   let tableRows = '';
                   if (lastSamples && lastSamples.length){
                     const last10 = lastSamples.slice(-10);
                     tableRows = last10.map(s=>{
                       const dt = new Date(s.t).toLocaleString();
                       const soc = (typeof s.soc==='number') ? (Math.round(s.soc*10)/10)+' %' : '–';
                       const wb  = (typeof s.wb ==='number') ? (Math.round(s.wb*100)/100).toFixed(2)+' kWh' : '–';
                       return `<tr><td>${dt}</td><td>${soc}</td><td>${wb}</td></tr>`;
                     }).join('');
                   }
                  
                   const svgChart = buildHistorySVG(labels, socLine, kwhBars);
                  
                   return `
                   ${css()}
                   <div class="wrap">
                     <div class="title"><span class="kpi">${d.carName||'IONIQ 5 N'}</span> <span class="badge">${d.vin||''}</span></div>
                  
                     <div class="grid">
                       <!-- HV Akku -->
                       <div class="card">
                         <div class="row">🔋 <b>State of Charge</b></div>
                         <div class="kpi" style="margin:6px 0">${d.soc!=null? d.soc+' %' : '–'}</div>
                         ${socBar(d.soc)}
                         <div class="stat"><span>Laden</span><span class="${d.charging?'chargeAnim':''}"><b>${d.charging===true?'aktiv':(d.charging===false?'nein':'–')}</b></span></div>
                         <div class="stat"><span>Min. bis voll</span><span><b>${d.minToFull!=null? d.minToFull : '–'}</b></span></div>
                         <div class="stat"><span>Reichweite</span><span><b>${d.dte!=null? d.dte+' km':'–'}</b></span></div>
                       </div>
                  
                       <!-- 12V / SOH -->
                       <div class="card">
                         <div class="row">🔋 <b>12V & SOH</b></div>
                         <div class="stat"><span>12V SoC</span><span style="flex:1;margin-left:10px">${socBar(d.soc12v)}</span></div>
                         <div class="stat"><span>12V Status</span><span><b>${d.state12v ?? '–'}</b></span></div>
                         <div class="stat"><span>12V lädt</span><span><b>${d.charge12v===true?'Ja':(d.charge12v===false?'Nein':'–')}</b></span></div>
                         <div class="stat"><span>SOH (HV)</span><span><b>${d.soh!=null? d.soh+' %':'–'}</b></span></div>
                       </div>
                  
                       <!-- Fahrzeug -->
                       <div class="card">
                         <div class="row">🚗 <b>Fahrzeug</b></div>
                         <div class="stat"><span>Odometer</span><span><b>${d.odoKm!=null? d.odoKm+' km':'–'}</b></span></div>
                         <div class="stat"><span>Gestern gefahren</span><span><b>${d.kmYesterday!=null? d.kmYesterday+' km':'–'}</b></span></div>
                       </div>
                  
                       <!-- Standort -->
                       <div class="card">
                         <div class="row">📍 <b>Standort</b></div>
                         <div class="badge">${locStr}</div>
                         <div class="sub" style="margin-top:8px">Koordinaten: <b>${coords}</b></div>
                       </div>
                  
                       <!-- Türen -->
                       <div class="card">
                         <div class="row">🚪 <b>Öffnungen</b></div>
                         <div class="kv">
                           <div class="tile">VL: ${flag(d.doorFL)}</div>
                           <div class="tile">VR: ${flag(d.doorFR)}</div>
                           <div class="tile">HL: ${flag(d.doorRL)}</div>
                           <div class="tile">HR: ${flag(d.doorRR)}</div>
                           <div class="tile">Kofferraum: ${flag(d.trunk)}</div>
                           <div class="tile">Frunk: ${flag(d.frunk)}</div>
                         </div>
                         <div class="sub" style="margin-top:6px">Gesamt: ${ [d.doorFL,d.doorFR,d.doorRL,d.doorRR,d.trunk,d.frunk].some(v=>v===true) ? '<b class="err">offen</b>' : '<b class="ok">alle zu</b>' }</div>
                       </div>
                  
                       <!-- Fenster -->
                       <div class="card">
                         <div class="row">🪟 <b>Fenster</b></div>
                         <div class="kv">
                           <div class="tile">VL: ${flag(d.winFL,'zu','offen')}</div>
                           <div class="tile">VR: ${flag(d.winFR,'zu','offen')}</div>
                           <div class="tile">HL: ${flag(d.winRL,'zu','offen')}</div>
                           <div class="tile">HR: ${flag(d.winRR,'zu','offen')}</div>
                         </div>
                       </div>
                  
                       <!-- Reifen -->
                       <div class="card">
                         <div class="row">🛞 <b>Reifen</b></div>
                         <div class="kv">
                           <div class="tile">VL: ${tireTxt(d.tireFL)}</div>
                           <div class="tile">VR: ${tireTxt(d.tireFR)}</div>
                           <div class="tile">HL: ${tireTxt(d.tireRL)}</div>
                           <div class="tile">HR: ${tireTxt(d.tireRR)}</div>
                         </div>
                       </div>
                  
                       <!-- Klima & Komfort -->
                       <div class="card">
                         <div class="row">❄️ <b>Klima & Komfort</b></div>
                         <div class="stat"><span>Klima</span><span class="${d.hvacOn?'fanAnim':''}"><b>${d.hvacOn===true?'AN':(d.hvacOn===false?'AUS':'–')}</b></span></div>
                         <div class="stat"><span>Innen</span><span><b>${d.tIn!=null? d.tIn+' °C':'–'}</b></span></div>
                         <div class="stat"><span>Luftreiniger</span><span><b>${d.airClean===true?'AN':(d.airClean===false?'AUS':'–')}</b></span></div>
                         <div class="stat"><span>Defrost</span><span><b>${d.defrost===true?'AN':(d.defrost===false?'AUS':'–')}</b></span></div>
                         <div class="stat"><span>Lenkrad</span><span>${d.steerHeat===true?'<span class="heatAnim">🔥</span>':'AUS'}</span></div>
                         <div class="sub" style="margin-top:6px">Sitze:</div>
                         <div class="kv">
                           <div class="tile">VL: <b>${seatTxt(d.seatFL)}</b></div>
                           <div class="tile">VR: <b>${seatTxt(d.seatFR)}</b></div>
                           <div class="tile">HL: <b>${seatTxt(d.seatRL)}</b></div>
                           <div class="tile">HR: <b>${seatTxt(d.seatRR)}</b></div>
                         </div>
                       </div>
                  
                       <!-- Fahrzeugstatus -->
                       <div class="card">
                         <div class="row">ℹ️ <b>Fahrzeugstatus</b></div>
                         <div class="kv">
                           <div class="tile">Wischerwasser: ${d.washer===true?'<b class="err">LEER</b>':(d.washer===false?'<b class="ok">OK</b>':'–')}</div>
                           <div class="tile">Bremsöl: ${d.breakOil===true?'<b class="err">NIEDRIG</b>':(d.breakOil===false?'<b class="ok">OK</b>':'–')}</div>
                           <div class="tile">Smartkey: ${d.smartKeyBat===true?'<b class="warn">WARNUNG</b>':(d.smartKeyBat===false?'<b class="ok">OK</b>':'–')}</div>
                         </div>
                       </div>
                  
                       <!-- Wallbox -->
                       <div class="card">
                         <div class="row">🔌 <b>Wallbox</b></div>
                         <div class="kv">
                           <div class="tile">Status: <b>${wbStateTxt}</b></div>
                           <div class="tile">Leistung: <b>${d.wb_powerFmt || '–'}</b></div>
                           <div class="tile">Energie: <b>${d.wb_energyFmt || '–'} kWh</b></div>
                           <div class="tile">Ampere: <b>${d.wb_ampere!=null? d.wb_ampere+' A':'–'}</b></div>
                           <div class="tile">Freigabe: <b>${d.wb_allow===true?'Ja':(d.wb_allow===false?'Nein':'–')}</b></div>
                           <div class="tile">Temp1: <b>${d.wb_temp1!=null? d.wb_temp1+' °C':'–'}</b></div>
                           <div class="tile">Temp2: <b>${d.wb_temp2!=null? d.wb_temp2+' °C':'–'}</b></div>
                         </div>
                       </div>
                  
                       <!-- Ladehistorie (7 Tage) – reines SVG, volle Breite/Höhe -->
                       <div class="card">
                         <div class="row">📈 <b>Ladehistorie (7 Tage)</b></div>
                         <div class="chartBox">
                           ${svgChart}
                         </div>
                       </div>
                  
                       <!-- Ladehistorie Tabelle (letzte 10 Samples) -->
                       <div class="card">
                         <div class="row">⚡ <b>Ladehistorie – letzte 10 Werte</b></div>
                         ${ (lastSamples && lastSamples.length)
                             ? `<table><tr><th>Zeitpunkt</th><th>SOC</th><th>Wallbox</th></tr>${tableRows}</table>`
                             : `<div class="sub">Keine Daten vorhanden.</div>` }
                       </div>
                     </div>
                  
                     <div class="footer">Zuletzt aktualisiert: ${d.lastUpdate || new Date().toLocaleString()}</div>
                   </div>`;
                  }
                  
                  /* =================== HISTORIE: DPs + Verarbeitung =================== */
                  function ensureHistoryDPs(){
                   ensureDataPoint(HIST_SAMPLES_DP, '[]', {name:'Ioniq History Samples', type:'string', role:'json'});
                   ensureDataPoint(HIST_LAST_TS_DP, 0,    {name:'Ioniq History Last Sample', type:'number', role:'value.time'});
                  }
                  function _normalizeSamples(arr){
                   const now = Date.now();
                   const twelveH = 12*3600*1000;
                   return arr.map(s=>{
                     if(!s) return null;
                     let t = Number(s.t);
                     if (!isFinite(t)) return null;
                     if (t < 1e12) t = t * 1000; // Sekunden -> ms
                     if (t > now + twelveH) return null; // Zukunft verwerfen
                     const out = { t };
                     if (typeof s.soc === 'number' && isFinite(s.soc)) out.soc = s.soc;
                     if (typeof s.wb  === 'number' && isFinite(s.wb )) out.wb  = s.wb;
                     return out;
                   }).filter(Boolean);
                  }
                  function loadSamples(){
                   try{
                     const txt=gs(HIST_SAMPLES_DP);
                     if(!txt) return [];
                     const parsed = JSON.parse(txt);
                     const arr = Array.isArray(parsed)? parsed : [];
                     return _normalizeSamples(arr);
                   }catch(_){ return []; }
                  }
                  function saveSamples(arr){
                   try{
                     if (arr.length>MAX_SAMPLES) arr = arr.slice(arr.length-MAX_SAMPLES);
                     ss(HIST_SAMPLES_DP, JSON.stringify(arr));
                   }catch(_){}
                  }
                  function trySample(nowMs, data){
                   const lastTs = Number(gs(HIST_LAST_TS_DP) || 0);
                   if (nowMs - lastTs < SAMPLE_INTERVAL_MS) return;
                  
                   const soc = data._hist_soc;
                   const wb  = data._hist_wb_energy;
                  
                   if (soc==null && wb==null) {
                     ss(HIST_LAST_TS_DP, nowMs);
                     if (DEBUG) log('[IONIQ5N] Sample SKIP: keine Werte (soc/wb beide leer)', 'info');
                     return;
                   }
                  
                   let arr = loadSamples();
                   arr.push({ t: nowMs, soc: soc, wb: wb });
                   saveSamples(arr);
                   ss(HIST_LAST_TS_DP, nowMs);
                  
                   if (DEBUG) {
                     const socTxt = (soc==null)?'—':String(soc);
                     const wbTxt  = (wb==null)?'—':String(wb);
                     log(`[IONIQ5N] Sample OK @ ${new Date(nowMs).toLocaleString()} | SOC=${socTxt} | WB=${wbTxt}`, 'info');
                   }
                  }
                  function computeDailyFromSamples(nowMs){
                   const arr = loadSamples();
                   // Labels & Tageskeys (letzte 7 Tage inkl. heute)
                   const labels=[], dayKeys=[];
                   for(let i=6;i>=0;i--){
                     const d=new Date(nowMs - i*24*3600*1000);
                     labels.push(d.toLocaleDateString(undefined,{weekday:'short'}));
                     dayKeys.push(`${d.getFullYear()}-${('0'+(d.getMonth()+1)).slice(-2)}-${('0'+d.getDate()).slice(-2)}`);
                   }
                   if(!arr.length) return {labels, dailySoc:[0,0,0,0,0,0,0], dailyKwh:[0,0,0,0,0,0,0]};
                   const byDay={};
                   for(const s of arr){
                     const d=new Date(s.t);
                     const key=`${d.getFullYear()}-${('0'+(d.getMonth()+1)).slice(-2)}-${('0'+d.getDate()).slice(-2)}`;
                     if(!byDay[key]) byDay[key]={soc:[], wb:[]};
                     if(typeof s.soc==='number') byDay[key].soc.push(s.soc);
                     if(typeof s.wb ==='number') byDay[key].wb.push(s.wb);
                   }
                   const dailySoc=[], dailyKwh=[];
                   for(const key of dayKeys){
                     const g=byDay[key];
                     if(!g){ dailySoc.push(0); dailyKwh.push(0); continue; }
                     const avgSoc = g.soc.length ? Math.round(g.soc.reduce((a,b)=>a+b,0)/g.soc.length) : 0;
                     let kwh=0;
                     if(g.wb.length){
                       const mn=Math.min.apply(null,g.wb), mx=Math.max.apply(null,g.wb);
                       kwh = mx-mn; if(!isFinite(kwh)||kwh<0) kwh=0;
                       kwh = Math.round(kwh*100)/100;
                     }
                     dailySoc.push(avgSoc);
                     dailyKwh.push(kwh);
                   }
                   return {labels, dailySoc, dailyKwh};
                  }
                  
                  /* =================== DATEN SAMMELN =================== */
                  function readAll(){
                   const cand = candidates();
                   const pick = (name, map=v=>v)=>{
                     const {val} = firstExisting(cand[name]||[]);
                     if (val===undefined) return undefined;
                     try{
                       if (typeof val === 'string' && !isNaN(val)) return map(Number(val));
                       return map(val);
                     }catch(_){ return val; }
                   };
                   const toBool = (v) => (typeof v==='boolean') ? v : (v!=null ? Number(v)>0 : undefined);
                  
                   // Fahrwerte
                   const odoKm = (function(){
                     const v = pick('odometer_km', x=>x);
                     if (v===undefined) return undefined;
                     const num = typeof v==='string' ? parseFloat(v.replace(/[^\d.,]/g,'').replace(',','.')) : Number(v);
                     return isNaN(num) ? undefined : Math.round(num);
                   })();
                  
                   // Wallbox Zahlen
                   const wb_stateNum = pick('wb_state', Number);
                   const wb_powerNum = pick('wb_power', Number);
                   const wb_energyNum= pick('wb_energy', Number);
                  
                   const data = {
                     // Kopf
                     carName: pick('carName'),
                     vin: pick('vin'),
                  
                     // HV / 12V
                     soc: pick('soc_pct', v => Math.round(Number(v))),
                     charging: pick('charge_active', toBool),
                     minToFull: pick('minutes_to_charged', Number),
                     soc12v: pick('soc12v', v => Math.round(Number(v))),
                     state12v: pick('state12v'),
                     soh: pick('soh', v => Math.round(Number(v))),
                     charge12v: pick('charge12v', v => v === true || v === 'true' || Number(v) === 1),
                  
                     // Reichweite & Klima
                     dte: pick('dte', v => Math.round(Number(v))),
                     hvacOn: pick('hvacOn', toBool),
                     tIn: pick('insideTemp', v => Math.round(Number(v)*10)/10),
                     airClean: pick('airClean', toBool),
                     defrost: pick('defrost', toBool),
                     seatFL: pick('seatFL', Number),
                     seatFR: pick('seatFR', Number),
                     seatRL: pick('seatRL', Number),
                     seatRR: pick('seatRR', Number),
                     steerHeat: pick('steerHeat', toBool),
                  
                     // Öffnungen / Fenster
                     doorFL: pick('doorFL', toBool),
                     doorFR: pick('doorFR', toBool),
                     doorRL: pick('doorRL', toBool),
                     doorRR: pick('doorRR', toBool),
                     trunk: pick('trunk', toBool),
                     frunk: pick('frunk', toBool),
                     winFL: pick('winFL', toBool),
                     winFR: pick('winFR', toBool),
                     winRL: pick('winRL', toBool),
                     winRR: pick('winRR', toBool),
                  
                     // Reifenlampen
                     tireFL: pick('tireFL', toBool),
                     tireFR: pick('tireFR', toBool),
                     tireRL: pick('tireRL', toBool),
                     tireRR: pick('tireRR', toBool),
                  
                     // Flüssigkeiten & Warnungen
                     washer: pick('washer', toBool),
                     breakOil: pick('breakOil', toBool),
                     smartKeyBat: pick('smartKeyBat', toBool),
                  
                     // Standort
                     lat: pick('lat', Number),
                     lon: pick('lon', Number),
                     address: pick('position_text', String),
                     positionUrl: pick('position_url', String),
                  
                     // Wallbox – formatierte Strings
                     wb_stateNum,
                     wb_stateText: (wb_stateNum!=null ? (WB_STATE_TXT[wb_stateNum] || String(wb_stateNum)) : undefined),
                     wb_powerFmt:  (wb_powerNum!=null ? (wb_powerNum >= 1000 ? (Math.round(wb_powerNum/100)/10)+' kW' : Math.round(wb_powerNum)+' W') : undefined),
                     wb_energyFmt: (wb_energyNum!=null ? wb_energyNum.toFixed(2) : undefined),
                     wb_ampere: pick('wb_amp', v => Math.round(Number(v))),
                     wb_allow: pick('wb_allow', toBool),
                     wb_temp1: pick('wb_temp1', v => Math.round(Number(v))),
                     wb_temp2: pick('wb_temp2', v => Math.round(Number(v))),
                  
                     // Zeit / extra
                     lastUpdate: (function(){
                       const raw = pick('lastUpdate');
                       if (!raw) return '';
                       try{ const d=new Date(raw); return isNaN(d)? String(raw) : d.toLocaleString(); }catch(_){ return String(raw); }
                     })(),
                     kmYesterday: pick('kmYesterday', v=> (v==null? undefined : Math.round(Number(v)))),
                  
                     // Rohwerte für Historie-Sampling
                     _hist_soc: pick('soc_pct', Number),
                     _hist_wb_energy: wb_energyNum,
                  
                     // Odometer
                     odoKm
                   };
                  
                   return data;
                  }
                  
                  /* =================== MAIN =================== */
                  ensureState(OUT_HTML_DP);
                  ensureHistoryDPs();
                  let vinAnnounced = false;
                  
                  function update(){
                   try{
                     const nowMs = Date.now();
                  
                     if (!VEHICLE_ID){
                       VEHICLE_ID = detectVehicleId();
                       if (VEHICLE_ID && !vinAnnounced){ log('[IONIQ5N] Auto-Detected VIN: '+VEHICLE_ID,'info'); vinAnnounced=true; }
                     }
                     if (!VEHICLE_ID){
                       ss(OUT_HTML_DP, renderHTML({__noVin:true}, {labels:[],dailySoc:[],dailyKwh:[]}, []));
                       return;
                     }
                  
                     const data = readAll();
                     trySample(nowMs, data);
                     const hist = computeDailyFromSamples(nowMs);
                     const lastSamples = loadSamples();
                     const html = renderHTML(data, hist, lastSamples);
                     ss(OUT_HTML_DP, html);
                   }catch(e){ log('Update error: '+e, 'error'); }
                  }
                  
                  // Initial + Intervall
                  update();
                  schedule('*/30 * * * * *', update);
                  

                  Wolfgang GaryW Offline
                  Wolfgang GaryW Offline
                  Wolfgang Gary
                  schrieb am zuletzt editiert von
                  #2109

                  Hallo, ich habe einen Kia EV6 und Hyundai Kona.
                  Mit 3.1.20 funktioniert die Anzeige des EV6, aber Login beim Kona schlägt fehl.
                  Mit der Version 3.1.6 geht das Login für den Kona aber dafür für den EV6 nicht.
                  Kann ich 2 unterschiedliche Instanzen installieren?
                  danke & mfg Wolfgang

                  ilovegymI 1 Antwort Letzte Antwort
                  0
                  • Wolfgang GaryW Wolfgang Gary

                    Hallo, ich habe einen Kia EV6 und Hyundai Kona.
                    Mit 3.1.20 funktioniert die Anzeige des EV6, aber Login beim Kona schlägt fehl.
                    Mit der Version 3.1.6 geht das Login für den Kona aber dafür für den EV6 nicht.
                    Kann ich 2 unterschiedliche Instanzen installieren?
                    danke & mfg Wolfgang

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

                    @wolfgang-gary

                    Das geht nicht, müsstest dir einen zweiten Iobroker aufsetzen

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

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

                      @wolfgang-gary war mir klar dass da einer mit um die ecke kommt

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

                      ilovegymI 1 Antwort Letzte Antwort
                      0
                      • arteckA arteck

                        @wolfgang-gary war mir klar dass da einer mit um die ecke kommt

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

                        @arteck

                        Es wird gemunkelt, dass die myhyundai App das Bluelink ablösen könnte… die App gibts bereits für android und iOS..

                        Könnte darauf hindeuten, dass man vielleicht doch die Hersteller trennt.. ?
                        Könnte aber auch garnis bedeuten 😆

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

                        arteckA 1 Antwort Letzte Antwort
                        0
                        • ilovegymI ilovegym

                          @arteck

                          Es wird gemunkelt, dass die myhyundai App das Bluelink ablösen könnte… die App gibts bereits für android und iOS..

                          Könnte darauf hindeuten, dass man vielleicht doch die Hersteller trennt.. ?
                          Könnte aber auch garnis bedeuten 😆

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

                          @ilovegym nee.. es gab immer 2 apps.. bluelink und connect.. die wollen es mehr auf die marke trimmen würde ich mal behaupten..

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

                          1 Antwort Letzte Antwort
                          0
                          • WinniW Offline
                            WinniW Offline
                            Winni
                            schrieb am zuletzt editiert von Winni
                            #2114

                            Die App myHyundai scheint recht neu zu sein und sieht so aus, als wenn da alle Funktionen der Bluelink App integriert sind plus einiges mehr. Macht auf mich jedenfalls erstmal keinen schlechten Eindruck.

                            Edit: Hier der Link ins goingelectric.de Forum über die App:
                            https://www.goingelectric.de/forum/viewtopic.php?f=173&t=98354

                            Es gibt nicht Gutes. Außer man tut es. Erich Kästner

                            meuteM 1 Antwort Letzte Antwort
                            1
                            • WinniW Winni

                              Die App myHyundai scheint recht neu zu sein und sieht so aus, als wenn da alle Funktionen der Bluelink App integriert sind plus einiges mehr. Macht auf mich jedenfalls erstmal keinen schlechten Eindruck.

                              Edit: Hier der Link ins goingelectric.de Forum über die App:
                              https://www.goingelectric.de/forum/viewtopic.php?f=173&t=98354

                              meuteM Offline
                              meuteM Offline
                              meute
                              schrieb am zuletzt editiert von
                              #2115

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

                              Die App myHyundai scheint recht neu zu sein und sieht so aus, als wenn da alle Funktionen der Bluelink App integriert sind plus einiges mehr.

                              Bei Kia gibt es ja schon die Info in der App, dass die App "Kia Connect" eingestellt wird.

                              fraenk for friends Code: MATF103

                              1 Antwort Letzte Antwort
                              0
                              • D Offline
                                D Offline
                                dataeasy
                                schrieb am zuletzt editiert von dataeasy
                                #2116

                                leider geht bei mir mit keiner Version mehr hat einer ggf noch einen Tipp e0dcf30d-af8d-4627-9bf0-cf227df619c5-grafik.png

                                Die logon Daten sind defenitiv correct!

                                P.s. habe eine KIA un einen Hyundai !

                                1 Antwort Letzte Antwort
                                1
                                • S Offline
                                  S Offline
                                  StefanK
                                  schrieb am zuletzt editiert von StefanK
                                  #2117

                                  Ja seit heute morgen klappt auch bei mir der Login nicht mehr mit akt. Version 3.1.20 und KIA EV6 neues Modell

                                  ilovegymI flandsteF 2 Antworten Letzte Antwort
                                  0
                                  • S StefanK

                                    Ja seit heute morgen klappt auch bei mir der Login nicht mehr mit akt. Version 3.1.20 und KIA EV6 neues Modell

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

                                    @stefank @dataeasy

                                    bei mir geht die 3.1.16 immer noch ohne Probleme. ( Hyundai Ioniq 5 N )

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

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

                                      und ich bin in Urlaub bis nächste Woche Sonntag..

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

                                      ilovegymI D I 3 Antworten Letzte Antwort
                                      3
                                      • arteckA arteck

                                        und ich bin in Urlaub bis nächste Woche Sonntag..

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

                                        @arteck Geniess deinen Urlaub !!

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

                                        1 Antwort Letzte Antwort
                                        2
                                        • arteckA arteck

                                          und ich bin in Urlaub bis nächste Woche Sonntag..

                                          D Offline
                                          D Offline
                                          dataeasy
                                          schrieb am zuletzt editiert von
                                          #2121

                                          @arteck dann weiterhin schönes entspannen ;-) P.S habe jetzt nochmal alles Adapter gelöscht und Version 3.1.16 installiert und siehe da Hyundai geht wieder KIA leider noch nicht... Keine Ahnung warum hhabe aber parallel auch auf die neue Hyundai Android App myHyundai gewechselt also die es es bei KIA auch schon gibt

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


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          939

                                          Online

                                          32.5k

                                          Benutzer

                                          81.8k

                                          Themen

                                          1.3m

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

                                          • Du hast noch kein Konto? Registrieren

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