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

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

NEWS

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

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

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

Adapter Hyundai (Bluelink) oder KIA (UVO)

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

    @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 Offline
    ilovegymI Offline
    ilovegym
    schrieb am zuletzt editiert von ilovegym
    #2108

    @arteck

    Hab mal meinen digitalen Codierknecht 🤖 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 1 Antwort Letzte Antwort
    4
    • ilovegymI ilovegym

      @arteck

      Hab mal meinen digitalen Codierknecht 🤖 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 Offline
        ilovegymI Offline
        ilovegym
        schrieb am zuletzt editiert von
        #2110

        @wolfgang-gary

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

        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 Offline
            ilovegymI Offline
            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 😆

            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 Offline
                        ilovegymI Offline
                        ilovegym
                        schrieb am zuletzt editiert von
                        #2118

                        @stefank @dataeasy

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

                        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 Offline
                            ilovegymI Offline
                            ilovegym
                            schrieb am zuletzt editiert von
                            #2120

                            @arteck Geniess deinen Urlaub !!

                            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
                              • D dataeasy

                                @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 Online
                                S Online
                                schmuh
                                schrieb am zuletzt editiert von
                                #2122

                                @dataeasy
                                Zur Info, bei EVCC gibt es auch schon ein Issue deswegen. https://github.com/evcc-io/evcc/issues/23147

                                S 1 Antwort Letzte Antwort
                                0
                                • S schmuh

                                  @dataeasy
                                  Zur Info, bei EVCC gibt es auch schon ein Issue deswegen. https://github.com/evcc-io/evcc/issues/23147

                                  S Offline
                                  S Offline
                                  StefanK
                                  schrieb am zuletzt editiert von
                                  #2123

                                  oje hört sich nicht gut an:
                                  "The problem was and still is that Kia introduced a Captcha during login which can't be solved by the login routines used by evcc."

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

                                    @stefank in der Kia App ist jetzt auch der Captcha

                                    S 1 Antwort Letzte Antwort
                                    0
                                    • arteckA arteck

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

                                      I Offline
                                      I Offline
                                      irdeto
                                      schrieb am zuletzt editiert von
                                      #2125

                                      @arteck
                                      lasst arteck mal seinen verdienten Urlaub bitte.

                                      Die Koreaner meinen halt sie sind der Mittelpunkt der Erde...

                                      Bis Sie irgendwann merken das es dort zu heiß ist.👙

                                      VG und schönen Urlaub

                                      1 Antwort Letzte Antwort
                                      1
                                      • I Offline
                                        I Offline
                                        Ibanese
                                        schrieb am zuletzt editiert von
                                        #2126

                                        Hallo zusammen,

                                        ihr hattet bestimmt schon x Anfragen zu dem Thema, aber der Faden ist inzwischen echt lang und unübersichtlicher geworfen und so richtig werde ich mit der Suche nicht fündig.

                                        Bin ganz frisch beim Thema Elektroauto und habe einen Hyundai Ioniq FL. Bluelink läuft alles soweit, Adapter auch. Aber ich wenn ich Daten abholen will, gibt es doch immer wieder mal Fehler und ich hab das Schema dahinter noch nicht ganz verstanden.

                                        Kann mir bitte mal jemand erklären was es mit diesen Schaltern genau auf sich und wann man welchen genau zu benutzen hat. Irgendwie überschneiden die sich nach meinem Empfinden auch:

                                        • force_checkDriveInfo
                                        • force_login
                                        • force_refresh
                                        • force_refresh_from_car
                                        • force_refresh_from_server
                                        • force_update

                                        Ich habe das ganze noch nicht in eine Automatisierung eingebaut und nutze für den Moment einfach nur die Informationen und Schalter aus dem Adapter einzeln für sich.

                                        Wie gesagt, manchmal gibt es Fehler 400. Wenn ich force_refresh_from_car mache gibts manchmal gar keine angezeigten Änderungen (aber auch keine Fehler), manchmal ändern sich ein paar Werte, aber nicht alle. Hab das Gefühl ich muss danach nochmal einen anderen Schalter nutzen, z.B. force_login. Die Location wird oft garnicht sinnvoll aktualisiert, obwohl sich der Standort des Autos definitiv deutlich geändert hat.

                                        Könnt ihr erklären was es mit den einzelnen Schaltern auf sich hat? Und gibt es eine Art Ablauf die man nutzen muss, wenn man sich damit eine Automatisierung bauen will? Eine Reihenfolge die man einhalten soll? Wie ist das mit Warte Zeiten zwischen Kommandos?

                                        Beispiel: Ich will die Klima starten und dann abfragen ob sie wirklich an ist. Wie gehe ich da am besten vor? Auch möchte ich eigentlich während des Ladens gerne mehr aktuelle Infos zum Ladestand haben. Da die Boardelektronik eh wach ist und die 12V entsprechend gestützt sind, sollte häufiges Abfragen vom Auto (z.B. alle 10min) doch kein Problem sein oder? Aktuell bekomme ich nicht mal sinnvoll mit, dass ein Ladegerät angesteckt wurde, wenn ich nicht manuell vom Auto abfrage. Das sehe ich auch in der Bluelink App, dass die letzte Aktualisierung zb. x Stunden her ist.

                                        Danke vorab schon für eure Hilfe 😉

                                        Gruß Ibanese

                                        ilovegymI S 3 Antworten Letzte Antwort
                                        0
                                        • I Ibanese

                                          Hallo zusammen,

                                          ihr hattet bestimmt schon x Anfragen zu dem Thema, aber der Faden ist inzwischen echt lang und unübersichtlicher geworfen und so richtig werde ich mit der Suche nicht fündig.

                                          Bin ganz frisch beim Thema Elektroauto und habe einen Hyundai Ioniq FL. Bluelink läuft alles soweit, Adapter auch. Aber ich wenn ich Daten abholen will, gibt es doch immer wieder mal Fehler und ich hab das Schema dahinter noch nicht ganz verstanden.

                                          Kann mir bitte mal jemand erklären was es mit diesen Schaltern genau auf sich und wann man welchen genau zu benutzen hat. Irgendwie überschneiden die sich nach meinem Empfinden auch:

                                          • force_checkDriveInfo
                                          • force_login
                                          • force_refresh
                                          • force_refresh_from_car
                                          • force_refresh_from_server
                                          • force_update

                                          Ich habe das ganze noch nicht in eine Automatisierung eingebaut und nutze für den Moment einfach nur die Informationen und Schalter aus dem Adapter einzeln für sich.

                                          Wie gesagt, manchmal gibt es Fehler 400. Wenn ich force_refresh_from_car mache gibts manchmal gar keine angezeigten Änderungen (aber auch keine Fehler), manchmal ändern sich ein paar Werte, aber nicht alle. Hab das Gefühl ich muss danach nochmal einen anderen Schalter nutzen, z.B. force_login. Die Location wird oft garnicht sinnvoll aktualisiert, obwohl sich der Standort des Autos definitiv deutlich geändert hat.

                                          Könnt ihr erklären was es mit den einzelnen Schaltern auf sich hat? Und gibt es eine Art Ablauf die man nutzen muss, wenn man sich damit eine Automatisierung bauen will? Eine Reihenfolge die man einhalten soll? Wie ist das mit Warte Zeiten zwischen Kommandos?

                                          Beispiel: Ich will die Klima starten und dann abfragen ob sie wirklich an ist. Wie gehe ich da am besten vor? Auch möchte ich eigentlich während des Ladens gerne mehr aktuelle Infos zum Ladestand haben. Da die Boardelektronik eh wach ist und die 12V entsprechend gestützt sind, sollte häufiges Abfragen vom Auto (z.B. alle 10min) doch kein Problem sein oder? Aktuell bekomme ich nicht mal sinnvoll mit, dass ein Ladegerät angesteckt wurde, wenn ich nicht manuell vom Auto abfrage. Das sehe ich auch in der Bluelink App, dass die letzte Aktualisierung zb. x Stunden her ist.

                                          Danke vorab schon für eure Hilfe 😉

                                          Gruß Ibanese

                                          ilovegymI Offline
                                          ilovegymI Offline
                                          ilovegym
                                          schrieb am zuletzt editiert von
                                          #2127

                                          @ibanese

                                          Hi, im Wiki https://github.com/Newan/ioBroker.bluelink/wiki. bis ganz nach unten lesen, da hast du schon einige Fragen beantwortet.

                                          Zur Zeit baut der Hersteller an den Servern rum, kann sein, dass es geht, kann auch nicht sein, kann auch sein, dass nur ein paar Werte kommen.. zuverlaessig ist was anderes, da kann aber iobroker nix fuer, musst dich bei Hyundai beschweren.. 🙂

                                          Zu deinen Fragen noch.. zwischen Abfragen immer ein paar Sekunden warten, ist in der App ja genauso, da gibts auch nicht gleich eine Bestaetigung.. und das Auto nicht zu oft abfragen, schau auf welchem Status force_update steht

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


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          642

                                          Online

                                          32.4k

                                          Benutzer

                                          81.4k

                                          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