Weiter zum Inhalt

Visualisierung

9.5k Themen 105.1k Beiträge

Hilfe zu Visualisierungen

NEWS

  • VIS / VIS-2 Übersicht Widgetkompatibilität

    Angeheftet vis
    41
    15 Stimmen
    41 Beiträge
    19k Aufrufe
    OliverIOO
    @bongo Jeder view hat eine url. Ob die von vis1 oder vis2 ausgeliefert wird ist dem Browser egal
  • Liste mit Geräten und deren Auflösung für VIS

    Angeheftet Verschoben vis
    102
    3
    1 Stimmen
    102 Beiträge
    53k Aufrufe
    NegaleinN
    @Longbow sagte: welche Auflösung wäre bei einem iPhone 17 Pro Max ? testen wie im 1. Beitrag beschrieben
  • Test Adapter Aura

    16
    5 Stimmen
    16 Beiträge
    1k Aufrufe
    FredFF
    @dering Cooler Adapter... kommt mir gerade recht, da ich meine Visu neu machen möchte. Beim Mediaplayer Widget habe ich einen Sonos Player eingerichtet. Steuerung usw. funktioniert auch, nur leider wird mir das Cover nicht angezeigt. In der Firefox Browserkonsole (Chromium geht auch nicht) taucht folgende Fehlermeldung auf: [2026-07-08T20:33:29.479Z] Try to connect socket.io.js:55:64 Folgende nicht unterstützte entryTypes werden ignoriert: longtask. index-CFAO0wS4.js:41:205843 Aura v0.22.3 connected http://192.168.10.30:8098 index-CFAO0wS4.js:2:5503 Layout-Darstellung wurde erzwungen, bevor die Seite vollständig geladen war. Falls Stylesheets noch nicht geladen sind, kann dies zu einer kurzzeitigen Darstellung des Inhalts ohne Formatierung führen. index.html Source-Map-Fehler: Error: request failed with status 404 Stack in the worker:networkRequest@resource://devtools/client/shared/source-map-loader/utils/network-request.js:43:9 Ressourcen-Adresse: http://192.168.10.30:8098/socket.io/socket.io.js Source-Map-Adresse: socket.io.js.map Wegen bereits genutzten Port habe ich für Aura statt dem Standardport 8098 eingestellt. Web Adapter läuft auf 8082. Ach ja, ioBroker läuft in einem Docker Container. Unter Vis kann ich die cover url mit dem Basic img src Widget anzegen lassen. Irgendwelche Ideen?
  • Support Adapter Energiefluss-erweitert v0.7.7

    Verschoben vis
    5k
    9
    20 Stimmen
    5k Beiträge
    6m Aufrufe
    hardwarefehlerH
    Hi, was ist eigentlich der Bezugswert einer Überschreibung? Allein die Datenquelle aus den Basic-Einstellungen oder werden die Additionen/Subtraktionen aus der erweiterten Einstellung mit einbezogen?
  • iQontrol Vis Support Thread

    android app vis how-to ios app iqontrol mobile ui vis
    2k
    5
    7 Stimmen
    2k Beiträge
    811k Aufrufe
    M
    @Cephalopod sagte: Festplatte voll? iobroker läuft unter Docker auf meiner Synology. Die Sysology ist nur zu 62% gefüllt
  • Screenshots AURA Adapter

    vis
    1
    21
    7 Stimmen
    1 Beiträge
    142 Aufrufe
    Niemand hat geantwortet
  • Meine neue DIY-Visualisierung für ioBroker

    9
    9
    1 Stimmen
    9 Beiträge
    1k Aufrufe
    MrLarodosM
    @dirkhe Hier die aktuelle Version - nicht als Adapter, sondern momentan Stand-Alone-Script: https://github.com/MrLarodos/LaroVis
  • Sichtbarkeit bei NaN/null

    2
    4
    0 Stimmen
    2 Beiträge
    297 Aufrufe
    arteckA
    @formifan2002 da es ein temperatur wert ist.. denk anders .. zeig es an wenn > 0 ist. oder leg dir ein alias dafür an mit formattierung
  • Google Material 3 / Material You - Vis2

    vis material css
    19
    7
    3 Stimmen
    19 Beiträge
    2k Aufrufe
    M
    @OliverIO Stimmt! // ============================================================ // renderHeizung — M3 Material You Styling (9 Räume Grid) - v1.4 (Binding Fix) // ioBroker TypeScript — Inkl. Luftfeuchtigkeit, SYNC-FIX & MULTI-FENSTER // CHANGELOG v1.4: // - CRITICAL FIX: "Cannot subscribe..." / "undefined" Fehler in vis/vis-2 behoben! // Das ApexCharts-Konfigurationsobjekt wird nun als umgewandelter JSON-String // zusammengebaut und erst zur Laufzeit im Browser per JSON.parse() in ein // Objekt verwandelt. Dadurch findet der ioBroker-Parser keine { ... } // Muster mehr im HTML-Code, die er fälschlicherweise als Bindings interpretieren könnte. // ============================================================ const DP_HEIZUNG_HTML: string = '0_userdata.0.dashboard.heizungHTML'; const DP_PRIVACY: string = '0_userdata.0.dashboard.privacyMode'; const DP_CHART_BRENNER: string = '0_userdata.0.dashboard.chartShowBrenner'; const DP_CHART_VALVE: string = '0_userdata.0.dashboard.chartShowValve'; const DP_CHART_ACT: string = '0_userdata.0.dashboard.chartShowAct'; const DP_CHART_SET: string = '0_userdata.0.dashboard.chartShowSet'; const DP_CHART_WINDOW: string = '0_userdata.0.dashboard.chartShowWindow'; const DP_CHART_HUMID: string = '0_userdata.0.dashboard.chartShowHumid'; const DP_HEIZUNG_AN: string = '0_userdata.0.Heizung.Heizung_an'; const DP_MODULATION: string = 'ems-esp.0.heatSources.actualModulation'; // INSTANZ DES HISTORY-ADAPTERS const HISTORY_INSTANCE: string = 'history.0'; let globalHeatHistory: any[] = []; let roomHistories: Record<string, { act: any[]; set: any[]; valve: any[]; heat: any[]; window: any[]; humid: any[] }> = {}; const M3: Record<string, string> = { primary: 'var(--m3-primary, rgb(18, 66, 24))', onPrimary: 'var(--m3-on-primary, rgb(255, 255, 255))', primaryContainer: 'var(--m3-primary-container, rgb(43, 90, 45))', onPrimaryContainer: 'var(--m3-on-primary-container, rgb(155, 208, 151))', surfaceContainerLow: 'var(--m3-surface-container-low, rgb(243, 244, 237))', surfaceContainerHighest: 'var(--m3-surface-container-highest, rgb(225, 227, 220))', surfaceVariant: 'var(--m3-surface-variant, rgb(221, 229, 216))', onSurface: 'var(--m3-on-surface, rgb(25, 28, 24))', onSurfaceVariant: 'var(--m3-on-surface-variant, rgb(66, 73, 63))', heatPrimary: 'var(--m3-error, rgb(186, 26, 26))', heatContainer: 'var(--m3-error-container, rgb(255, 218, 214))', onHeatContainer: 'var(--m3-on-error-container, rgb(65, 0, 2))', pendingPrimary: 'rgb(176, 96, 0)', pendingContainer: 'rgb(255, 220, 193)', onPendingContainer: 'rgb(43, 20, 0)', }; const ICONS: Record<string, string> = { thermostat: 'M15 13V5A3 3 0 0 0 9 5V13A5 5 0 1 0 15 13M12 4A1 1 0 0 1 13 5V8H11V5A1 1 0 0 1 12 4Z', fire: 'M17.5 11C15 7.5 13.5 8.5 12 6C10.5 8.5 9 7.5 6.5 11C4 14.5 5.5 19 12 19C18.5 19 20 14.5 17.5 11M12 17C9 17 8.5 14 10 12.5C11.5 11 12 12.5 12 12.5C12 12.5 12.5 11 14 12.5C15.5 14 15 17 12 17Z', minus: 'M19 13H5V11H19V13Z', plus: 'M19 13H13V19H11V13H5V11H11V5H13V11H19V13Z', power: 'M16.56 5.44L15.11 6.89C16.84 7.94 18 9.83 18 12A6 6 0 0 1 12 18A6 6 0 0 1 6 12C6 9.83 7.16 7.94 8.88 6.88L7.44 5.44C5.36 6.88 4 9.28 4 12A8 8 0 0 0 12 20A8 8 0 0 0 20 12C20 9.28 18.64 6.88 16.56 5.44M11 3H13V13H11V3Z', window: 'M3 3H21V21H3V3M5 5V11H11V5H5M13 5V11H19V5H13M5 13V19H11V13H5M13 13V19H19V13H13Z', water: 'M12 20a6 6 0 0 1-6-6c0-4 6-10.75 6-10.75S18 10 18 14a6 6 0 0 1-6 6z' }; interface RoomHeater { id: string; label: string; anonLabel?: string; actOid: string; setOid: string; valveOid: string; windowOids?: string[]; humidOid?: string; } const rooms: RoomHeater[] = [ { id: 'wohnzimmer', label: 'Wohnzimmer', actOid: 'alias.0.Heizung.Wohnzimmer.ACTUAL', setOid: 'alias.0.Heizung.Wohnzimmer.SET', valveOid: 'alias.0.Heizung.Wohnzimmer.VALVE', windowOids: ['alias.0.Sicherheit.Wohnzimmer.Fenster.STATE', 'alias.0.Sicherheit.Wohnzimmer.Tuer.STATE'], humidOid: 'alias.0.Klima.Wohnzimmer.HUMIDITY' }, { id: 'schlafzimmer',label: 'Zimmer Mina', anonLabel: 'Schlafzimmer', actOid: 'alias.0.Heizung.Schlafzimmer.ACTUAL', setOid: 'alias.0.Heizung.Schlafzimmer.SET', valveOid: 'alias.0.Heizung.Schlafzimmer.VALVE', windowOids: ['alias.0.Sicherheit.Schlafzimmer.Fenster.STATE'], humidOid: 'alias.0.Klima.Schlafzimmer.HUMIDITY' }, { id: 'kind1', label: 'Zimmer Nova', anonLabel: 'Kinderzimmer 1', actOid: 'alias.0.Heizung.Kind1.ACTUAL', setOid: 'alias.0.Heizung.Kind1.SET', valveOid: 'alias.0.Heizung.Kind1.VALVE', windowOids: ['alias.0.Sicherheit.Kinderzimmer1.Fenster.STATE'], humidOid: 'alias.0.Klima.Kind1.HUMIDITY' }, { id: 'kind2', label: 'Zimmer Vasco', anonLabel: 'Kinderzimmer 2', actOid: 'alias.0.Heizung.Kind2.ACTUAL', setOid: 'alias.0.Heizung.Kind2.SET', valveOid: 'alias.0.Heizung.Kind2.VALVE', windowOids: ['alias.0.Sicherheit.Kinderzimmer2.Fenster.STATE'], humidOid: 'alias.0.Klima.Kind2.HUMIDITY' }, { id: 'bad', label: 'Bad', actOid: 'alias.0.Heizung.Bad.ACTUAL', setOid: 'alias.0.Heizung.Bad.SET', valveOid: 'alias.0.Heizung.Bad.VALVE', windowOids: ['alias.0.Sicherheit.Bad.Fenster.STATE'], humidOid: 'alias.0.Klima.Bad.HUMIDITY' }, { id: 'dachzimmer', label: 'Dachzimmer', actOid: 'alias.0.Heizung.Dachzimmer.ACTUAL', setOid: 'alias.0.Heizung.Dachzimmer.SET', valveOid: 'alias.0.Heizung.Dachzimmer.VALVE', windowOids: ['alias.0.Sicherheit.Dachzimmer.Fenster_Garten.STATE', 'alias.0.Sicherheit.Dachzimmer.Fenster_Strasse.STATE'], humidOid: 'alias.0.Klima.Dachzimmer.HUMIDITY' }, { id: 'flur', label: 'Flur', actOid: 'alias.0.Heizung.Flur.ACTUAL', setOid: 'alias.0.Heizung.Flur.SET', valveOid: 'alias.0.Heizung.Flur.VALVE', windowOids: ['alias.0.Sicherheit.Haustuer.STATE'], humidOid: 'alias.0.Klima.Flur.HUMIDITY' }, { id: 'gaesteklo', label: 'Gäste WC', actOid: 'alias.0.Heizung.GaesteKlo.ACTUAL', setOid: 'alias.0.Heizung.GaesteKlo.SET', valveOid: 'alias.0.Heizung.GaesteKlo.VALVE' }, { id: 'hwr', label: 'HWR', actOid: 'alias.0.Heizung.HWR.ACTUAL', setOid: 'alias.0.Heizung.HWR.SET', valveOid: 'alias.0.Heizung.HWR.VALVE', windowOids: ['alias.0.Sicherheit.HWR.Fenster.STATE'] }, ]; const COL_WIDTH: number = 202; const HERO_WIDTH: number = 636; const CARD_HEIGHT:number = 144; const GUTTER: number = 15; const SHADOW: string = '0px 1px 2px 0px rgba(0,0,0,0.3), 0px 1px 3px 1px rgba(0,0,0,0.15)'; function getSafeVal(oid: string | undefined, fallback: any = 0): any { if (oid && existsState(oid)) { const s = getState(oid); return s ? s.val : fallback; } return fallback; } function svgIcon(name: string, color: string, size: number = 24): string { const path = ICONS[name] || ICONS['thermostat']; return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" style="flex-shrink:0; transition: fill 0.3s ease;"><path fill="${color}" d="${path}"/></svg>`; } async function fetchHistoryData(oid: string | undefined, start: number, end: number, isRaw: boolean = false): Promise<any[]> { if (!oid || !existsState(oid)) return []; try { const queryOptions = isRaw ? { start, end, aggregate: 'none', limit: 400, ignoreNull: true } : { start, end, step: 1800000, aggregate: 'average', ignoreNull: true }; const result = await new Promise<any>(resolve => { sendTo(HISTORY_INSTANCE, 'getHistory', { id: oid, options: queryOptions }, res => resolve(res)); }); if (result && result.result && result.result.length > 0) { let cleanData = result.result .filter((r: any) => r.val !== null) .map((r: any) => { let v = r.val; if (typeof v === 'boolean') v = v ? 100 : 0; else if (typeof v === 'string') v = Number(v) || 0; return [r.ts, Math.round(v * 10) / 10]; }) .filter((r: any) => !isNaN(r[1])); if (isRaw && cleanData.length > 0) { const maxVal = Math.max(...cleanData.map((d: any) => d[1])); if (maxVal <= 1.0 && maxVal > 0) cleanData.forEach((d: any) => (d[1] = d[1] * 100)); } return cleanData; } } catch (e) {} return []; } async function updateHistories(): Promise<void> { const end = new Date().getTime(); const start = end - 24 * 60 * 60 * 1000; globalHeatHistory = await fetchHistoryData(DP_MODULATION, start, end, true); for (const room of rooms) { const [actData, setData, valveData, humidData] = await Promise.all([ fetchHistoryData(room.actOid, start, end, false), fetchHistoryData(room.setOid, start, end, false), fetchHistoryData(room.valveOid, start, end, true), fetchHistoryData(room.humidOid, start, end, false), ]); let allWindowData: any[] = []; if (room.windowOids && room.windowOids.length > 0) { const wResults = await Promise.all(room.windowOids.map(wOid => fetchHistoryData(wOid, start, end, true))); allWindowData = wResults.filter(wData => wData.length > 0); } roomHistories[room.id] = { act: actData, set: setData, valve: valveData, heat: globalHeatHistory, window: allWindowData, humid: humidData }; } renderHeizung(); } function renderHeroCard(activeRoomsCount: number, isZentralAn: boolean): string { const mod = Number(getSafeVal(DP_MODULATION, 0)) || 0; const bg = isZentralAn ? M3.primaryContainer : M3.surfaceContainerLow; const fg = isZentralAn ? M3.onPrimaryContainer : M3.onSurfaceVariant; const iconBg = isZentralAn ? M3.primary : M3.surfaceContainerHighest; const iconFg = isZentralAn ? M3.onPrimary : M3.onSurfaceVariant; return `<div style="width:${HERO_WIDTH}px; height:${CARD_HEIGHT}px; border-radius:12px; padding:20px 24px; box-sizing:border-box; background:${bg}; box-shadow:${SHADOW}; display:flex; align-items:center; justify-content:space-between; font-family:Roboto,sans-serif; transition:all 0.3s ease;"> <div style="display:flex; align-items:center; gap:20px;"> <div style="width:56px; height:56px; border-radius:28px; background:${iconBg}; display:flex; align-items:center; justify-content:center; transition:all 0.3s ease;"> ${svgIcon(isZentralAn ? 'fire' : 'power', iconFg, 32)} </div> <div> <div style="font-size:24px; font-weight:500; color:${fg}; margin-bottom:4px;">Zentralheizung</div> <div style="font-size:15px; font-weight:500; color:${fg}; opacity:0.8;">${isZentralAn ? `Heizbetrieb aktiv • ${mod}% Leistung` : 'Standby / Aus'}</div> </div> </div> <div style="text-align:right;"> <div style="font-size:36px; font-weight:600; color:${isZentralAn ? M3.heatPrimary : fg}; line-height:1;">${activeRoomsCount}</div> <div style="font-size:13px; font-weight:500; color:${fg}; text-transform:uppercase; letter-spacing:0.5px; margin-top:4px;">Räume fordern Wärme</div> </div> </div>`; } function renderRoomCard(room: RoomHeater, isZentralAn: boolean, isPrivacyMode: boolean): string { const actVal = Number(getSafeVal(room.actOid, 0)) || 0; const setVal = Number(getSafeVal(room.setOid, 0)) || 0; const valveRaw = getSafeVal(room.valveOid, 0); const humidVal = room.humidOid ? Number(getSafeVal(room.humidOid, 0)) || 0 : null; const displayName = isPrivacyMode && room.anonLabel ? room.anonLabel : room.label; let windowOpen = false; if (room.windowOids && room.windowOids.length > 0) { windowOpen = room.windowOids.some(oid => { const raw = getSafeVal(oid, false); return raw === true || raw === 1 || raw === '1' || raw === 'true'; }); } const wantsHeat = (typeof valveRaw === 'number' && valveRaw > 0) || (typeof valveRaw === 'string' && Number(valveRaw) > 0) || valveRaw === true || valveRaw === 'true'; let bg: string, fg: string, fgSub: string, iconBg: string, iconFg: string; if (wantsHeat && isZentralAn) { bg = M3.heatContainer; fg = M3.onHeatContainer; fgSub = M3.onHeatContainer; iconBg = M3.heatPrimary; iconFg = '#ffffff'; } else if (wantsHeat && !isZentralAn) { bg = M3.pendingContainer; fg = M3.onPendingContainer; fgSub = M3.onPendingContainer; iconBg = M3.pendingPrimary; iconFg = '#ffffff'; } else { bg = M3.surfaceContainerLow; fg = M3.onSurface; fgSub = M3.onSurfaceVariant; iconBg = M3.surfaceContainerHighest; iconFg = M3.onSurfaceVariant; } const jsMinus = `(function(e, btn){ e.stopPropagation(); var card = btn.closest('.m3-room-card'); var span = card.querySelector('.target-temp'); var current = parseFloat(span.innerText); var newVal = current - 0.5; span.innerText = newVal.toFixed(1) + '°'; if(typeof vis!=='undefined'&&vis.conn&&vis.conn.setState) vis.conn.setState('${room.setOid}', newVal); else if(typeof vis!=='undefined'&&vis.setValue) vis.setValue('${room.setOid}', newVal); })(event, this);`; const jsPlus = `(function(e, btn){ e.stopPropagation(); var card = btn.closest('.m3-room-card'); var span = card.querySelector('.target-temp'); var current = parseFloat(span.innerText); var newVal = current + 0.5; span.innerText = newVal.toFixed(1) + '°'; if(typeof vis!=='undefined'&&vis.conn&&vis.conn.setState) vis.conn.setState('${room.setOid}', newVal); else if(typeof vis!=='undefined'&&vis.setValue) vis.setValue('${room.setOid}', newVal); })(event, this);`; const windowHtml = windowOpen ? `<div style="width:24px; height:24px; display:flex; align-items:center; justify-content:center; animation: blink 2s infinite;">${svgIcon('window', M3.heatPrimary, 20)}</div>` : ''; const humidHtml = (humidVal !== null && humidVal > 0) ? `<div style="position:absolute; top:36px; right:-4px; width:40px; display:flex; justify-content:center; align-items:center; font-size:11px; font-weight:600; color:${fgSub}; opacity:0.85; letter-spacing:-0.5px;">${Math.round(humidVal)}%<svg width="10" height="10" viewBox="0 0 24 24" style="margin-left:1px"><path fill="currentColor" d="M12 20a6 6 0 0 1-6-6c0-4 6-10.75 6-10.75S18 10 18 14a6 6 0 0 1-6 6z"/></svg></div>` : ''; const historyData = roomHistories[room.id] ?? { act: [], set: [], valve: [], heat: [], window: [], humid: [] }; return `<div id="wrapper_${room.id}" style="width:${COL_WIDTH}px; height:${CARD_HEIGHT}px; position:relative; z-index:1;"> <div id="card_${room.id}" class="m3-room-card" data-history='${JSON.stringify(historyData)}' onclick="if(typeof window.toggleCard === 'function') window.toggleCard(this, '${room.id}');" style="position:absolute; top:0; left:0; width:100%; height:100%; border-radius:12px; padding:14px 16px; box-sizing:border-box; background:${bg}; box-shadow:${SHADOW}; display:flex; flex-direction:column; font-family:Roboto,sans-serif; transition:all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); cursor:pointer; overflow:hidden;"> <div style="display:flex; justify-content:space-between; align-items:flex-start; width:100%;"> <div style="font-size:16px; font-weight:500; color:${fg}; max-width:145px; line-height:1.2; overflow:hidden; text-overflow:ellipsis; transition:color 0.3s ease;">${displayName}</div> <div style="display:flex; gap:6px; align-items:flex-start; position:relative;"> ${windowHtml} <div style="width:32px; height:32px; border-radius:16px; background:${iconBg}; display:flex; align-items:center; justify-content:center; flex-shrink:0;">${svgIcon('thermostat', iconFg, 18)}</div> ${humidHtml} </div> </div> <div style="display:flex; align-items:baseline; margin-top:-6px; width:100%;"> <div style="font-size:36px; font-weight:400; color:${fg}; letter-spacing:-1px;">${actVal.toFixed(1)}</div> <div style="font-size:18px; font-weight:500; color:${fgSub}; margin-left:2px; opacity:0.7;">°C</div> </div> <div id="controls_${room.id}" style="display:flex; justify-content:space-between; align-items:center; width:100%; margin-top:auto;"> <div style="display:flex; flex-direction:column;"> <span style="font-size:11px; font-weight:500; text-transform:uppercase; letter-spacing:0.5px; color:${fgSub}; opacity:0.7;">Ziel</span> <span class="target-temp" style="font-size:15px; font-weight:600; color:${fg};">${setVal.toFixed(1)}°</span> </div> <div style="display:flex; gap:6px;"> <div onclick="${jsMinus}" style="width:32px; height:32px; border-radius:16px; background:rgba(0,0,0,0.06); display:flex; align-items:center; justify-content:center; cursor:pointer; user-select:none; transition:opacity 0.1s; -webkit-tap-highlight-color:transparent;">${svgIcon('minus', fg, 20)}</div> <div onclick="${jsPlus}" style="width:32px; height:32px; border-radius:16px; background:rgba(0,0,0,0.06); display:flex; align-items:center; justify-content:center; cursor:pointer; user-select:none; transition:opacity 0.1s; -webkit-tap-highlight-color:transparent;">${svgIcon('plus', fg, 20)}</div> </div> </div> <div id="chart_wrapper_${room.id}" style="opacity:0; height:0px; width:100%; transition:opacity 0.4s ease; margin-top:0px; pointer-events:none;"> <div id="chart_${room.id}" style="width:100%; height:100%;"></div> </div> </div> </div>`; } function renderHeizung(): void { let activeRooms = 0; const isZentralAn = getSafeVal(DP_HEIZUNG_AN, false) === true; const isPrivacyMode = getSafeVal(DP_PRIVACY, false) === true; const showBrenner = getSafeVal(DP_CHART_BRENNER, true) === true; const showValve = getSafeVal(DP_CHART_VALVE, false) === true; const showAct = getSafeVal(DP_CHART_ACT, true) === true; const showSet = getSafeVal(DP_CHART_SET, true) === true; const showWindow = getSafeVal(DP_CHART_WINDOW, true) === true; const showHumid = getSafeVal(DP_CHART_HUMID, true) === true; for (const room of rooms) { const valveRaw = getSafeVal(room.valveOid, 0); if ((typeof valveRaw === 'number' && valveRaw > 0) || (typeof valveRaw === 'string' && Number(valveRaw) > 0) || valveRaw === true || valveRaw === 'true') activeRooms++; } let cardsHtml = renderHeroCard(activeRooms, isZentralAn); for (const room of rooms) cardsHtml += renderRoomCard(room, isZentralAn, isPrivacyMode); const html = ` <style> @keyframes blink { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } } .m3-room-card * { white-space: nowrap; } .apexcharts-legend { padding: 0 !important; margin-top: -10px !important; } .apexcharts-legend-text { color: var(--m3-on-surface-variant) !important; font-family: Roboto, sans-serif !important; font-size: 11px !important; } </style> <div id="m3-dashboard-container" data-cfg-brenner="${showBrenner}" data-cfg-valve="${showValve}" data-cfg-act="${showAct}" data-cfg-set="${showSet}" data-cfg-window="${showWindow}" data-cfg-humid="${showHumid}" style="position: relative; background: var(--m3-surface-container-low, rgb(237, 239, 232)); border-radius: 0 0 28px 28px; padding: 24px; display: flex; flex-wrap: wrap; align-content: flex-start; gap: ${GUTTER}px; width: 901px; box-sizing: border-box; transition: background-color 0.3s ease;"> ${cardsHtml} </div> <script> window.activeM3CardId = window.activeM3CardId || null; window.toggleCard = function(el, roomId) { if (window.activeM3CardId === roomId) { window.closeCard(el, roomId); return; } if (window.activeM3CardId) { const oldEl = document.getElementById('card_' + window.activeM3CardId); if (oldEl) window.closeCard(oldEl, window.activeM3CardId); } window.openCard(el, roomId); }; window.closeCard = function(el, roomId) { el.style.width = '100%'; el.style.height = '100%'; el.style.boxShadow = '${SHADOW}'; const chartWrapper = document.getElementById('chart_wrapper_' + roomId); if (chartWrapper) { chartWrapper.style.opacity = '0'; setTimeout(() => { chartWrapper.style.height = '0px'; const chartDiv = document.getElementById('chart_' + roomId); if (chartDiv) { if (chartDiv._apexChart) { chartDiv._apexChart.destroy(); delete chartDiv._apexChart; } chartDiv.innerHTML = ''; } }, 400); } setTimeout(() => { el.parentElement.style.zIndex = '1'; }, 400); window.activeM3CardId = null; }; window.openCard = function(el, roomId) { window.activeM3CardId = roomId; const wrapper = el.parentElement; const container = document.getElementById('m3-dashboard-container'); wrapper.style.zIndex = '999'; const wrapperRect = wrapper.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); const expandedWidth = ${COL_WIDTH * 2 + GUTTER}; const expandedHeight = 310; el.style.top = 'auto'; el.style.bottom = 'auto'; el.style.left = 'auto'; el.style.right = 'auto'; if (wrapperRect.left + expandedWidth > containerRect.right - 10) { el.style.right = '0'; } else { el.style.left = '0'; } if (wrapperRect.top + expandedHeight > containerRect.bottom - 10) { el.style.bottom = '0'; } else { el.style.top = '0'; } el.style.width = expandedWidth + 'px'; el.style.height = expandedHeight + 'px'; el.style.boxShadow = '0px 14px 28px rgba(0,0,0,0.35), 0px 10px 10px rgba(0,0,0,0.22)'; const chartWrapper = document.getElementById('chart_wrapper_' + roomId); if (chartWrapper) { chartWrapper.style.height = '140px'; chartWrapper.style.marginTop = '16px'; setTimeout(() => { chartWrapper.style.opacity = '1'; window.renderChart(el, roomId); }, 450); } }; window.renderChart = function(el, roomId) { const chartDiv = el.querySelector('#chart_' + roomId); if (!chartDiv || chartDiv.innerHTML.trim() !== '') return; const rawData = el.getAttribute('data-history'); let chartData = { act: [], set: [], valve: [], heat: [], window: [], humid: [] }; try { chartData = JSON.parse(rawData); } catch(e) {} if (chartData.act.length === 0 && chartData.set.length === 0) { chartDiv.innerHTML = '<div style="color:var(--m3-on-surface-variant); font-size:13px; text-align:center; padding-top:50px; font-weight:500;">Keine Historie</div>'; return; } const rootStyles = getComputedStyle(document.documentElement); const colorIst = rootStyles.getPropertyValue('--m3-primary').trim() || '#4caf50'; const colorSoll = rootStyles.getPropertyValue('--m3-outline').trim() || '#9e9e9e'; const colorVentil = rootStyles.getPropertyValue('--m3-error').trim() || '#ffb4ab'; const colorBrenner = '#ff9800'; const colorFenster = '#00bfff'; const colorHumid = '#2196f3'; const container = el.closest('#m3-dashboard-container'); const cfgShowBrenner = container.getAttribute('data-cfg-brenner') === 'true'; const cfgShowValve = container.getAttribute('data-cfg-valve') === 'true'; const cfgShowAct = container.getAttribute('data-cfg-act') === 'true'; const cfgShowSet = container.getAttribute('data-cfg-set') === 'true'; const cfgShowWindow = container.getAttribute('data-cfg-window') === 'true'; const cfgShowHumid = container.getAttribute('data-cfg-humid') === 'true'; function drawGraph() { chartDiv.innerHTML = ''; var s_series = []; var s_colors = []; var actHasData = chartData.act && chartData.act.length > 0; var setHasData = chartData.set && chartData.set.length > 0; var minTemp = 999; var maxTemp = -999; if (cfgShowAct && actHasData) { for (var i = 0; i < chartData.act.length; i++) { if (chartData.act[i][1] < minTemp) minTemp = chartData.act[i][1]; if (chartData.act[i][1] > maxTemp) maxTemp = chartData.act[i][1]; } } if (cfgShowSet && setHasData) { for (var j = 0; j < chartData.set.length; j++) { if (chartData.set[j][1] < minTemp) minTemp = chartData.set[j][1]; if (chartData.set[j][1] > maxTemp) maxTemp = chartData.set[j][1]; } } if (minTemp === 999) { minTemp = 15; maxTemp = 25; } else { minTemp = Math.floor(minTemp) - 1; maxTemp = Math.ceil(maxTemp) + 1; } if (cfgShowHumid && chartData.humid && chartData.humid.length > 0) { s_series.push({ name: 'Luftfeuchte', type: 'area', data: chartData.humid }); s_colors.push(colorHumid); } if (cfgShowBrenner) { s_series.push({ name: 'Brenner', type: 'area', data: chartData.heat || [] }); s_colors.push(colorBrenner); } if (cfgShowValve) { s_series.push({ name: 'Ventil', type: 'line', data: chartData.valve || [] }); s_colors.push(colorVentil); } if (cfgShowAct) { s_series.push({ name: 'Ist-Temp', type: 'area', data: chartData.act || [] }); s_colors.push(colorIst); } if (cfgShowSet) { s_series.push({ name: 'Ziel-Temp', type: 'line', data: chartData.set || [] }); s_colors.push(colorSoll); } var windowAnnotations = []; if (cfgShowWindow && chartData.window && chartData.window.length > 0) { for (var w = 0; w < chartData.window.length; w++) { var wData = chartData.window[w]; var inWindowOpen = false; var winStart = null; for (var k = 0; k < wData.length; k++) { var ts = wData[k][0]; var val = wData[k][1]; if (val > 0 && !inWindowOpen) { inWindowOpen = true; winStart = ts; } else if (val === 0 && inWindowOpen) { inWindowOpen = false; windowAnnotations.push({ x: winStart, x2: ts, fillColor: colorFenster, opacity: 0.15 }); } } if (inWindowOpen && winStart) windowAnnotations.push({ x: winStart, x2: new Date().getTime(), fillColor: colorFenster, opacity: 0.15 }); } } if (cfgShowWindow) { let safeTime = (actHasData) ? chartData.act[0][0] : new Date().getTime() - 3600000; s_series.push({ name: 'Fenster offen', type: 'area', data: [[safeTime, 0]] }); s_colors.push(colorFenster); } if (s_series.length === 0) { chartDiv.innerHTML = '<div style="color:var(--m3-on-surface-variant); font-size:13px; text-align:center; padding-top:50px; font-weight:500;">Alle Graphen ausgeblendet</div>'; return; } // CRITICAL FIX v1.4: Komplettes Apex-Config-Objekt als JSON-String generieren und parsen var configString = '{"chart":{"type":"line","height":140,"width":"100%","parentHeightOffset":0,"toolbar":{"show":false},"animations":{"enabled":true,"easing":"easeinout","speed":800,"dynamicAnimation":{"enabled":false}}},"stroke":{"curve":["smooth","smooth","stepline","smooth","stepline","stepline"],"width":[0,2,2,3,2,0],"dashArray":[0,0,0,0,4,0]},"fill":{"type":["solid","gradient","solid","gradient","solid","solid"],"opacity":[0.1,1,1,1,1,0],"gradient":{"shadeIntensity":1,"opacityFrom":0.45,"opacityTo":0.0,"stops":[0,100]}},"dataLabels":{"enabled":false},"legend":{"show":true,"position":"top","horizontalAlign":"right"},"tooltip":{"theme":"dark","x":{"format":"HH:mm"}},"xaxis":{"type":"datetime","labels":{"show":false},"axisBorder":{"show":false},"axisTicks":{"show":false},"tooltip":{"enabled":false}},"grid":{"show":false}}'; var options = JSON.parse(configString); options.series = s_series; options.colors = s_colors; options.annotations = { xaxis: windowAnnotations }; options.yaxis = []; if (cfgShowHumid) options.yaxis.push({ min: 0, max: 100, show: false }); if (cfgShowBrenner) options.yaxis.push({ min: 0, max: 100, show: false }); if (cfgShowValve) options.yaxis.push({ min: 0, max: 100, show: false }); if (cfgShowAct) options.yaxis.push({ min: minTemp, max: maxTemp, show: false, tickAmount: 5 }); if (cfgShowSet) options.yaxis.push({ min: minTemp, max: maxTemp, show: false, tickAmount: 5 }); if (cfgShowWindow) options.yaxis.push({ min: minTemp, max: maxTemp, show: false, tickAmount: 5 }); if (chartDiv._apexChart) { chartDiv._apexChart.destroy(); delete chartDiv._apexChart; } try { var chart = new ApexCharts(chartDiv, options); chart.render(); chartDiv._apexChart = chart; } catch(err) { console.error(err); chartDiv.innerHTML = '<div style="color:var(--m3-error, red); font-size:13px; text-align:center; padding-top:50px;">Graphen-Fehler</div>'; } } if (typeof ApexCharts === 'undefined') { chartDiv.innerHTML = '<div style="color:var(--m3-on-surface-variant); font-size:13px; text-align:center; padding-top:50px; font-weight:500;">Lade Chart-Engine...</div>'; if (!window.apexLoadingPromise) { window.apexLoadingPromise = new Promise((resolve) => { const tempDefine = window.define; window.define = undefined; let script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/apexcharts@5.6.0/dist/apexcharts.min.js'; script.onload = () => { window.define = tempDefine; resolve(); }; document.head.appendChild(script); }); } window.apexLoadingPromise.then(drawGraph); } else { drawGraph(); } }; </script>`; setState(DP_HEIZUNG_HTML, html, true); } // === INIT === function startHeizungRenderer(): void { on({ id: [DP_CHART_BRENNER, DP_CHART_VALVE, DP_CHART_ACT, DP_CHART_SET, DP_CHART_WINDOW, DP_CHART_HUMID], change: 'any' }, () => renderHeizung()); on({ id: DP_HEIZUNG_AN, change: 'any' }, () => renderHeizung()); on({ id: DP_MODULATION, change: 'any' }, () => renderHeizung()); on({ id: DP_PRIVACY, change: 'any' }, () => renderHeizung()); rooms.forEach(room => { if (room.actOid) on({ id: room.actOid, change: 'any' }, () => renderHeizung()); if (room.setOid) on({ id: room.setOid, change: 'any' }, () => renderHeizung()); if (room.valveOid) on({ id: room.valveOid, change: 'any' }, () => renderHeizung()); if (room.humidOid) on({ id: room.humidOid, change: 'any' }, () => renderHeizung()); if (room.windowOids) { room.windowOids.forEach(oid => on({ id: oid, change: 'any' }, () => renderHeizung())); } }); updateHistories(); schedule('*/15 * * * *', updateHistories); log('[Heizung] Renderer v1.4 (History Edition & Binding Fix) gestartet.'); } const chartDPs: Array<{ id: string; val: boolean; name: string }> = [ { id: DP_CHART_BRENNER, val: true, name: 'Chart: Brenner anzeigen' }, { id: DP_CHART_VALVE, val: false, name: 'Chart: Ventil anzeigen' }, { id: DP_CHART_ACT, val: true, name: 'Chart: Ist-Temp anzeigen' }, { id: DP_CHART_SET, val: true, name: 'Chart: Ziel-Temp anzeigen' }, { id: DP_CHART_WINDOW, val: true, name: 'Chart: Fenster anzeigen' }, { id: DP_CHART_HUMID, val: true, name: 'Chart: Luftfeuchte anzeigen' }, ]; function ensureChartDPs(index: number, done: () => void): void { if (index >= chartDPs.length) { done(); return; } const dp = chartDPs[index]; if (!existsState(dp.id)) { createState(dp.id, dp.val, { type: 'boolean', name: dp.name, role: 'switch', read: true, write: true }, () => ensureChartDPs(index + 1, done)); } else { ensureChartDPs(index + 1, done); } } ensureChartDPs(0, () => { if (!existsState(DP_HEIZUNG_HTML)) { createState(DP_HEIZUNG_HTML, '', { type: 'string', name: 'Heizung HTML', role: 'html' }, startHeizungRenderer); } else { startHeizungRenderer(); } });
  • vis-2 basic string widget color binding zeigt nichts an

    18
    1
    0 Stimmen
    18 Beiträge
    2k Aufrufe
    Q
    @oliverio Danke für deine Hilfe. Hab jetzt mit deinem css padding fix und dem Basic Bool HTML Widget gelöst.
  • [gelöst] Balkendiagramm für Momentanwerte?

    9
    1
    0 Stimmen
    9 Beiträge
    1k Aufrufe
    U
    @hardwarefehler sagte: Wenn ich das richtig verstehe, steuerst du das Refresh-Intervall mit dem setTimeout() ganz am Ende. das ist nur eine Verzögerung beim Start. Damit beim ersten Rendern die Definition verfügbar ist und die Charts sauber gerendert werden
  • Konvertierung Jarvis -> Aura

    6
    1 Stimmen
    6 Beiträge
    825 Aufrufe
    ioT4dbI
    @Hansi1234 https://forum.iobroker.net/topic/84288/aura-modernes-dashboard-für-iobroker-beta-tester-gesucht?_=1783022679984
  • VIS 2 Inventwo - Problem mit "Sichtbarkeit"

    6
    1
    0 Stimmen
    6 Beiträge
    900 Aufrufe
    jkvarelJ
    Die Ausgaben in der Browserkonsole könnten vielleicht weiter helfen
  • web.0 meldet viele Fehler

    9
    0 Stimmen
    9 Beiträge
    1k Aufrufe
    jkvarelJ
    Du hast anscheinend noch Widgets vom VIS 1 inventwo Adapter auf der View: can't render i-vis-button w000342 on "RechteSeite" can't render i-vis-button w000341 on "RechteSeite" can't render i-vis-checkbox w001276 on "12_Allgemein" Über das Widget Dropdown oben im Menü kannst du die Widgets anhand der ID suchen und dann löschen. Dann sollten auf jeden Fall die Fehler bezogen auf die Widgets weg sein.
  • Dynamische HTML-Progress-Bar

    vis
    1
    1
    0 Stimmen
    1 Beiträge
    243 Aufrufe
    Niemand hat geantwortet
  • DasWetter VIS

    14
    0 Stimmen
    14 Beiträge
    2k Aufrufe
    NegaleinN
    @Longbow sagte: Ja mehre male und Verzeichnisse, nichts. so? [image: 1782888230397-6776d95f-65c3-448e-af37-8a174a8562c2-image.jpeg] [image: 1782888291951-3d5b1c3f-9665-42ba-9f35-7c920be97a42-image.jpeg]
  • lovelace history 48 statt 24h

    1
    0 Stimmen
    1 Beiträge
    226 Aufrufe
    Niemand hat geantwortet
  • Vis 1 Widgets Auswahl

    3
    0 Stimmen
    3 Beiträge
    549 Aufrufe
    skvarelS
    vis-inventwo .... Widgetradius auf 100%, dann hast du ein rundes Widget. In den Einstellungen kannst du dann die passenden Farben (Fläche, Rand, Schatten usw.) vergeben
  • ( gelöst ) Tabs Registerkarten formatierung

    7
    1
    0 Stimmen
    7 Beiträge
    1k Aufrufe
    P
    @OliverIO sagte: .mytabs .MuiTab-root { min-width: inherit; } Hallo, sorry ich bin jetzt erst wieder dazu gekommen Prima es hat funktioniert und es passt sogar noch alles mit den Namen Vielen Dank für deine Hilfe und einen schönen Wochenanfang VG Thomas
  • VIS2 Metro Tile ValueList

    11
    2
    0 Stimmen
    11 Beiträge
    2k Aufrufe
    Q
    @skvarel Danke für den Hinweis, damit klappts super als Alternative :-)

393

Online

33.0k

Benutzer

83.4k

Themen

1.3m

Beiträge