Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. Deutsch
    3. Tester
    4. Adapter Hyundai (Bluelink) oder KIA (UVO)

    NEWS

    • Wir empfehlen: Node.js 22.x

    • Neuer Blog: Fotos und Eindrücke aus Solingen

    • ioBroker goes Matter ... Matter Adapter in Stable

    Adapter Hyundai (Bluelink) oder KIA (UVO)

    This topic has been deleted. Only users with topic management privileges can see it.
    • Peter V.
      Peter V. last edited by

      @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 Reply Last reply Reply Quote 0
      • ilovegym
        ilovegym @arteck last edited by 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 Gary 1 Reply Last reply Reply Quote 2
        • Wolfgang Gary
          Wolfgang Gary @ilovegym last edited by

          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

          ilovegym 1 Reply Last reply Reply Quote 0
          • ilovegym
            ilovegym @Wolfgang Gary last edited by

            @wolfgang-gary

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

            1 Reply Last reply Reply Quote 0
            • arteck
              arteck Developer Most Active last edited by

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

              ilovegym 1 Reply Last reply Reply Quote 0
              • ilovegym
                ilovegym @arteck last edited by

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

                arteck 1 Reply Last reply Reply Quote 0
                • arteck
                  arteck Developer Most Active @ilovegym last edited by

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

                  1 Reply Last reply Reply Quote 0
                  • Winni
                    Winni last edited by 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

                    meute 1 Reply Last reply Reply Quote 1
                    • meute
                      meute @Winni last edited by

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

                      1 Reply Last reply Reply Quote 0
                      • D
                        dataeasy last edited by dataeasy

                        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 Reply Last reply Reply Quote 1
                        • S
                          StefanK last edited by StefanK

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

                          ilovegym 1 Reply Last reply Reply Quote 0
                          • ilovegym
                            ilovegym @StefanK last edited by

                            @stefank @dataeasy

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

                            1 Reply Last reply Reply Quote 0
                            • First post
                              Last post

                            Support us

                            ioBroker
                            Community Adapters
                            Donate

                            879
                            Online

                            32.0k
                            Users

                            80.4k
                            Topics

                            1.3m
                            Posts

                            134
                            2118
                            754574
                            Loading More Posts
                            • Oldest to Newest
                            • Newest to Oldest
                            • Most Votes
                            Reply
                            • Reply as topic
                            Log in to reply
                            Community
                            Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
                            The ioBroker Community 2014-2023
                            logo