Navigation

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

    NEWS

    • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?

    • Monatsrückblick – September 2025

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

    • Profile
    • Following 4
    • Followers 1
    • Topics 2
    • Posts 166
    • Best 49
    • Groups 2

    ilovegym

    @ilovegym

    68
    Reputation
    30
    Profile views
    166
    Posts
    1
    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.7.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
    • RE: Problem mit REST-API 3.xxx

      @fastfoot sagte in Problem mit REST-API 3.xxx:

      Bin da ganz bei dir!

      posted in ioBroker Allgemein
      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: https://nuudel.digitalcourage.de/DOKbZ4JISpom46Cq


      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: 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: 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: Neuer Alexa-Skill "iobroker.assistant"

      @merasil sagte in Neuer Alexa-Skill "iobroker.assistant":

      Ich hoffe ich bin in diesem thread richtig. Wie kann ich denn, wenn ich in iobroker im iot adapter neue geräte hinzugefügt hab, nachdem ich den skill und den adapter in alexa verbunden habe, diese in der alexa app sichtbar machen? Er scheint nur einmal beim verbinden des skills alle geräte zu importieren. Danach ist es mir etwas schleierhaft wie ich eine neue "suche nach geräten" anstoßen soll

      Die Quasselbuechse hoert doch auf Sprache, also einfach " Alexa, suche neue Geraete" dann 2-3 Minuten warten.. in der App nachschauen, sich freuen.. meist sagt sie dir sogar, was gefunden wurde, auf den Echos mit Bildschirm bekommst du sie angezeigt und kannst die dann weiter konfigurieren (Gruppieren oder zu Routinen hinzufuegen.. )

      posted in Cloud Dienste
      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: Neuer Alexa-Skill "iobroker.assistant"

      @gonzokalle

      Ja wie du selbst andere auch fragst: alexa welche fenster sind offen usw wie ist das wetter wie warm ist es im Wohnzimmer.. außerdem kannste die quasselbüchse ja fragen, wie du fragen kannst..

      posted in Cloud Dienste
      ilovegym
      ilovegym
    • RE: Umfrage zum Thema VLAN im Heimnetz

      @martinp in der heutigen Zeit kann man da keinem trauen, von daher.. fuer den Heimgebrauch ausreichend.

      Abgesehen davon koennte man den Laenderfilter umgehen, indem man einen deutschen Server nimmt, der dann wieder rum ... 🙂

      Ansonsten gilt fuer Firewalls : alles dicht, nur das, was gebraucht wird, wird aufgemacht.

      posted in Plauderecke
      ilovegym
      ilovegym
    • RE: Umfrage zum Thema VLAN im Heimnetz

      @dr-bakterius sagte in Umfrage zum Thema VLAN im Heimnetz:

      @martinp Ich habe alles von Ubiquiti (unifi: UCG Ultra; US 16 PoE 150W; USW Flex; US 8; AC LR; AC Lite; AC Mesh). Dabei habe ich drei VLAN und entsprechende SSIDs. Eines für meine PCs, Handys, Tabletts, Proxmox, usw., eines für IoT und eines für Alexa. Dazu entsprechende Firewall-Regeln um manchen Geräten den Internetzugang zu sperren oder Brücken zwischen den VLAN.

      genauso so und noch etwas mehr auseinandergedroeselt hab ich es auch, noch eins fuer security, storage, development, work-1/2 ... kann man machen, wenn Networking Hobby ist, muss man aber nicht. 🙂
      Bei mir mit 600 Devices notwendig und durch Unifi leicht zu controllen.

      Wer nur n paar ( ich sag mal unter 50 ) Devices hat, kann die Echos ins Gastnetzwerk haengen und den Rest in einem Netz, fertig.

      Subnetting waere noch ein Thema, um was abzugrenzen durch routing, aber das ist kein eigenes Lan(oder vlan)

      posted in Plauderecke
      ilovegym
      ilovegym
    • RE: [gelöst] Nuos A8T - ESP32 Chipsatz - Scripte

      @dojodojo

      Sorry es geht nicht, wenn der pc auf dem Docker und ioBroker läuft, aus ist, geht da nix mehr..

      posted in Skripten / Logik
      ilovegym
      ilovegym
    • RE: Neuer Alexa-Skill "iobroker.assistant"

      @merasil sagte in Neuer Alexa-Skill "iobroker.assistant":

      Ich hoffe ich bin in diesem thread richtig. Wie kann ich denn, wenn ich in iobroker im iot adapter neue geräte hinzugefügt hab, nachdem ich den skill und den adapter in alexa verbunden habe, diese in der alexa app sichtbar machen? Er scheint nur einmal beim verbinden des skills alle geräte zu importieren. Danach ist es mir etwas schleierhaft wie ich eine neue "suche nach geräten" anstoßen soll

      Die Quasselbuechse hoert doch auf Sprache, also einfach " Alexa, suche neue Geraete" dann 2-3 Minuten warten.. in der App nachschauen, sich freuen.. meist sagt sie dir sogar, was gefunden wurde, auf den Echos mit Bildschirm bekommst du sie angezeigt und kannst die dann weiter konfigurieren (Gruppieren oder zu Routinen hinzufuegen.. )

      posted in Cloud Dienste
      ilovegym
      ilovegym
    • RE: [gelöst] Nuos A8T - ESP32 Chipsatz - Scripte

      @dojodojo

      naja, vor dem shutdown wuerd ich erstmal iobroker stoppen "iob stop" dann,falls Redis ein "redis-cli bgsave"
      anschliessend shutdown der Kiste.

      Aber wenn iobroker nicht mehr laeuft, kannste ja ihnnichtmehr automatisch starten..

      von daher.. was ist der Sinn dahinter? oder hast du noch einen ?
      Dazu dann mehr Infos..
      und einfacher gehts z.bsp. mit dem Linux-Adapter dann per remote..
      Oder was du auch immer wie machen willst... 🙂

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

      @tippy88 keine ahnung, ich kann dir nur sagen, dass es bei mir so funktioniert. Probiers doch mal aus, mehr wie alles zerschiessen und dir dein Server loeschen, abfackeln und sprengen kann sowieso nicht passieren.. 🙂

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

      @tippy88

      im Ping-Adapter "Erweiterte Informationen" angeklickt?

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

      @chris_71 sagte in Betatest NSPanel-lovelace-ui v0.6.x:

      @thomas-braun

      Nach der Deinstallation von 2 alten NPM Modulen (vom Radar2 Adapter V1.x) konnte der Adapter ohne Fehlermeldung installiert werden.

      Tu dir aber selbst n gefallen, und mach n Backup, setz das System am besten gleich mit dem aktuellen Trixie neu auf, damit du wieder uptodate bist. So schleppst du dir nur den Murks mit rum.. wird ja nicht besser und irgendwann geht dann gar nix mehr..

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

      @meister-mopper

      Kia, richtig?
      Der Fehlermeldung nach ist der Token abgelaufen, mach dir n neuen.. mit dem Script..

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