Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. ilovegym

    NEWS

    • Neues Video "KI im Smart Home" - ioBroker plus n8n

    • Neues Video über Aliase, virtuelle Geräte und Kategorien

    • Wir empfehlen: Node.js 22.x

    • Profile
    • Following 4
    • Followers 0
    • Topics 2
    • Posts 116
    • Best 34
    • Groups 2

    ilovegym

    @ilovegym

    49
    Reputation
    21
    Profile views
    116
    Posts
    0
    Followers
    4
    Following
    Joined Last Online

    ilovegym Follow
    Pro Starter

    Best posts made by ilovegym

    • RE: Adapter Hyundai (Bluelink) oder KIA (UVO)

      @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);
      

      posted in Tester
      ilovegym
      ilovegym
    • RE: Betatest NSPanel-lovelace-ui v0.2.x

      @arteck

      denke da gibts keine Probleme, habe meine 13 Stueck seit ca 8 Monaten 24/7 laufen, allerdings dunkelt der Screensaver ja ab, und zwischen 22 und 7 Uhr schalte ich die Displays Helligkeit 0 - gehen also komplett aus. (Spart auch etwas Strom..)

      Die Idee mit dem Screensaver waere natuerlich immer eine gute Option, man weiss ja nie... 🙂

      posted in Tester
      ilovegym
      ilovegym
    • Usertreffen: Rhein-Main Event: 27.9.25 in Mainz!

      Willkommen beim Stammtisch im Raum Rhein-Main-Hessen 🙂


      Meetings:
      Online: jeden 1. Montag im Monat ab 20:30 - https://discord.gg/yC65zjr5uq

      Vor Ort: am 27.9.2025 ab 16.00 Uhr im Zeitlos in Mainz


      Wer Bock hat kann auch gerne zwischendurch in den Discord-Channel schauen 🙂 Einer ist meist online, und hilft bei Fragen gerne!

      posted in Usergroups
      ilovegym
      ilovegym
    • RE: Smartlock empfehlungen Pro/Contra

      @michael-schmitt

      ich hab 9 Nukis, davon 8 aktuell verbaut, seit dem ersten Nuki bin ich dabei, das neueste ist ein Ultra, das beste, meiner Meinung nach, auch wenn man den Zylinder erstmal zusammenbauen muss, und der nicht ueberall passen koennte. Dafuer macht es einen sehr robusten Eindruck.

      Ansteuern tu ich alle Nuki's mit jeweils einem WT32-ETH0 (esp32 mit lan und geflashter nukihub-software), das laeuft 100%ig und nur per bluetooth ans Nuki gekoppelt, was auch Akku spart.
      Das Ultra hab ich seit Feb. und das laeuft noch mit der ersten Akkuladung, ist an der Haustuer und verriegelt nach 2min sofort wieder.(unsere Katzen machen sonst die Tueren auf, deshalb auch (in zweiter Linie) die Nukis.. 🙂 )

      posted in Hardware
      ilovegym
      ilovegym
    • RE: PV - Begriffklärung

      @metaxa

      wenn du netzbezug hast, hast du keine Einspeisung..

      Hausverbrauch = PV + Netzbezug

      oder

      Hausverbrauch = PV - Einspeisung

      Die modernen Zaehler sind saldierend, es geht immer um den Gesamtwert, was eine einzelne Phase macht, ist wurscht.

      posted in Off Topic
      ilovegym
      ilovegym
    • RE: Adapter Hyundai (Bluelink) oder KIA (UVO)

      @blockmove @Winni @RISSN @arteck @Meister-Mopper @MichaelWit @nik82

      Bei HA gabs fuer den Login wohl n Update, kann man sich da was abschauen, @arteck ?

      Screenshot 2025-08-02 at 10.17.08.png

      posted in Tester
      ilovegym
      ilovegym
    • RE: Usertreffen: Rhein-Main Event: 27.9.25 in Mainz!

      @linedancer

      wir vergessen dich nicht.. morgen haben wir wieder Online-Meeting 20.30 im Discord..
      Mal gespannt, was bei

      https://nuudel.digitalcourage.de/MxhkwS3od87PCKu6

      rauskommt.. ich hoffe, du bist dabei, und hoffentlich ein paar mehr 🙂

      Waere schoen, wenn vielleicht @Ahnungsbefreit @amg_666 @chris299 @Homoran auch online oder Vor-Ort dabei waeren..

      posted in Usergroups
      ilovegym
      ilovegym
    • RE: Smartlock empfehlungen Pro/Contra

      @arteck

      hab das mit etwas Rabatt bekommen 🙂

      Und ja, dafuer kann man 2/3x lecker essen gehen, hast schon recht.. 😄
      Ich hab hier halt die Nuki und klar.. wenn ich nochmal komplett neu anfangen wuerde (was ich nicht mache in diesem Leben), dann sowieso alles anders.. erstens mit dem Bagger durch und zweitens alles verkabelt, egal was es kostet..

      Die paar Jahre, die hier noch geplant sind, wirds / muss der Kram halten und dann gehts ins betreute Wohnen, da lass ich dann ne smarte Weiblichkeit kommen, die das Licht aus/an macht 🙂

      posted in Hardware
      ilovegym
      ilovegym
    • RE: Kaufberatung USV

      @stefan341

      Die erste Frage ist immer: Wie hoch ist denn der Verbrauch der beiden Verbraucher? 🙂
      Wenn du das weisst, dann kannst du dir ja ausrechnen, welche Kapazitaet die USV haben muss.

      Also erstmal messen, dann wissen, dann schlau machen und kaufen 🙂

      posted in Off Topic
      ilovegym
      ilovegym
    • RE: Adapter Hyundai (Bluelink) oder KIA (UVO)

      @arteck Geniess deinen Urlaub !!

      posted in Tester
      ilovegym
      ilovegym

    Latest posts made by ilovegym

    • RE: Adapter Hyundai (Bluelink) oder KIA (UVO)

      @peguschwein @Pedder007

      schade! 😞

      posted in Tester
      ilovegym
      ilovegym
    • RE: Adapter Hyundai (Bluelink) oder KIA (UVO)

      @peguschwein
      Hi,
      na bei dem Fehler Landescode auf EN stellen, dann gehts. ( so hab ich s auch, aber mit Version 3.1.6 - Hyundai Ioniq 5N )

      Keiner weiss, ausser Hyundai (und da bin ich mir auch nicht sicher), was die machen und vorhaben und ändern... zur Zeit gibts wohl die Migration von Bluelink zur MyHyundai-App, letztere funktioniert bei mir zumindest mit den Push, die werden auch angezeigt, wenn die App nicht aktiv ist, im Gegensatz zur Bluelink, da kommen erst Meldungen, wenn ich die App aufmache..

      posted in Tester
      ilovegym
      ilovegym
    • RE: Adapter Hyundai (Bluelink) oder KIA (UVO)

      @peguschwein was geht nicht mehr?

      posted in Tester
      ilovegym
      ilovegym
    • RE: Adapter Hyundai (Bluelink) oder KIA (UVO)

      @manfredhi

      ja, wie du hier im Thread lesen kannst, baut zur Zeit Hyundai-Kia an den Servern rum.. wenn du glueck hast, geht irgendeine Version ( evtl mal 3.1.6,? ) ... es gibt leider zu viele Typen ..

      posted in Tester
      ilovegym
      ilovegym
    • RE: Adapter Hyundai (Bluelink) oder KIA (UVO)

      @manfredhi

      ei, das ist ja auch ne Uralt Version 3.1.3 ...

      posted in Tester
      ilovegym
      ilovegym
    • RE: Adapter Hyundai (Bluelink) oder KIA (UVO)

      @flandste

      Process exited with code 0 - alles ok, Adapter ohne Fehler installiert.
      Die Warnings kannste ignorieren, habe keine techn. bedeutung.

      posted in Tester
      ilovegym
      ilovegym
    • RE: Test Adapter Device-Watcher v2.x.x GitHub/Latest

      @fichte sagte in Test Adapter Device-Watcher v2.x.x GitHub/Latest:

      Das system ist eine woche alt ohne Backup zurückspielen

      Dann ging vor ner Woche so einiges schief, deshalb jetzt auch die Probleme.. schau dir die Diagnose doch mal an..

      Mein Tip: iobroker backup starten, datei sichern, komplettes System neu aufsetzen (aber richtig, schau bei Thomas Braun's Tipps) und dann iobroker wieder restoren.

      Edit: ist ne Arbeit von 2h, evtl sogar schneller, je nach System und hardware

      posted in Tester
      ilovegym
      ilovegym
    • RE: Test Adapter Device-Watcher v2.x.x GitHub/Latest

      @fichte na bei sovielen permission denied solltest du mal dein system gerade biegen oder besser neu aufsetzen..da ist grundsaetzlich viel viel schief...

      posted in Tester
      ilovegym
      ilovegym
    • RE: Test Adapter Device-Watcher v2.x.x GitHub/Latest

      @fichte

      zeig bitte mal die Ausgabe von "iob diag" komplett, hier in Code Tags gesetzt.

      posted in Tester
      ilovegym
      ilovegym
    • RE: Test Adapter Device-Watcher v2.x.x GitHub/Latest

      @thomas-braun @arteck

      da die latest seit 24 Tagen raus ist, und keine weiteren Issues auftauchen.. kann die gerne ins stable geschoben werden. Dann bekommt auch @Fichte das Update angezeigt, er ist bestimmt auf stable unterwegs..

      posted in Tester
      ilovegym
      ilovegym
    Community
    Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
    The ioBroker Community 2014-2023
    logo