Weiter zum Inhalt

NEWS

  • Test Adapter public-transport v0.1.x GitHub/npm

    Tester
    1
    3
    4 Stimmen
    1 Beiträge
    41 Aufrufe
    Niemand hat geantwortet
  • ioBroker Prozess- & Gesundheitsmonitor + Grafana Dashboard

    JavaScript javascript monitoring
    30
    4
    3 Stimmen
    30 Beiträge
    785 Aufrufe
    crunchipC
    Dashboard für Flux oben im ersten Beitrag ergänzt, bitte testen
  • Google Material 3 / Material You - Vis2

    Visualisierung vis material css
    11
    7
    3 Stimmen
    11 Beiträge
    366 Aufrufe
    M
    Hier die Einstellungen: Was macht dieses Skript? Dieses Skript generiert ein komplettes Einstellungs-Menü (Setup-Tab) im Material 3 Design für euer vis-Dashboard. Es rendert sauberes HTML und bringt drei Hauptfunktionen mit: System & Automatiken: Eine elegante Listenansicht mit Schaltern, um z.B. Skripte (Rollladenautomatik, Urlaubsmodus) ein- und auszuschalten. Graphen-Filter (Chips): Interaktive Buttons (Chips), mit denen man Datenpunkte für ein Diagramm (z.B. Heizung) ein- und ausblenden kann. Dynamische Wallpaper-Auswahl: Das absolute Highlight! Das Skript liest einen festgelegten Ordner eures ioBrokers aus und generiert automatisch ein Popup-Menü mit allen darin enthaltenen Bildern (.jpg, .png, .webp). Wählt ihr ein Bild aus, wird der Pfad in einen Datenpunkt geschrieben (perfekt für die Kombination mit meinem "M3 Theme Master" Skript). Was muss vorbereitet werden? Damit das Skript auf euren ioBroker-Dateispeicher zugreifen darf, müsst ihr einen wichtigen Haken setzen: Geht in die Instanz-Einstellungen eures JavaScript-Adapters. Setzt den Haken bei "Erlaube das Kommando exec" (falls noch nicht geschehen) und tragt unter Zusätzliche NPM-Module das Modul fs ein, falls es dort nicht standardmäßig aktiv ist. Alternativ reicht es oft schon, in den JS-Adapter-Einstellungen den Haken bei "Erlaube Zugriff auf Dateisystem" zu setzen. Wallpaper-Ordner: Legt im ioBroker Dateimanager (unter vis.0 oder vis-2.0) einen Ordner an und ladet dort ein paar Bilder hoch. Passt den Pfad im Skript (WALLPAPER_WEB_DIR) entsprechend an. Eure Geräte eintragen: Schaut im Skript in die Funktion getListItems(). Dort tragt ihr einfach die Datenpunkte eurer eigenen ioBroker-Skripte oder Schalter ein. Wie wendet man das an? Das Skript erstellt alle nötigen Datenpunkte automatisch (standardmäßig unter 0_userdata.0.dashboard.). Startet das Skript. Zieht ein "Basic - String" (oder "Basic - HTML") Widget in eure vis. Tragt unter HTML / Inhalt den Datenpunkt-Namen in geschweiften Klammern ein: {0_userdata.0.dashboard.setupHTML} Zieht das Widget so groß wie nötig, das Skript bringt eine eigene, stylische M3-Scrollbar mit, falls der Platz nicht reicht. Das Skript (TypeScript) // ============================================================ // renderSetup — M3 Material You Styling (Kompakt & Robust-Scroll) // ============================================================ const fs = require('fs'); // === KONFIGURATION ========================================================== const BASE_PATH = '0_userdata.0.dashboard'; const DP_SETUP_HTML = `${BASE_PATH}.setupHTML`; const DP_IMAGE_PATH = `${BASE_PATH}.themeImagePath`; const DP_PRIVACY = `${BASE_PATH}.privacyMode`; // GRAPHEN-FILTER (Beispiele für eine Heizungs-Ansicht) const DP_CHART_BRENNER = `${BASE_PATH}.chartShowBrenner`; const DP_CHART_VALVE = `${BASE_PATH}.chartShowValve`; const DP_CHART_ACT = `${BASE_PATH}.chartShowAct`; const DP_CHART_SET = `${BASE_PATH}.chartShowSet`; const DP_CHART_WINDOW = `${BASE_PATH}.chartShowWindow`; const DP_CHART_HUMID = `${BASE_PATH}.chartShowHumid`; // ORDNER FÜR DIE HINTERGRUNDBILDER // Wichtig: Der Ordner muss im ioBroker Dateimanager existieren! const WALLPAPER_WEB_DIR = '/vis-2.0/Material_You/img/wallpaper/'; const WALLPAPER_SYS_DIR = '/opt/iobroker/iobroker-data/files' + WALLPAPER_WEB_DIR; // ============================================================================ // === AB HIER NUR ANPASSEN, WENN EIGENE LISTEN-EINTRÄGE GEWÜNSCHT SIND === // ============================================================================ const M3 = { primary: 'var(--m3-primary)', onPrimary: 'var(--m3-on-primary)', primaryContainer: 'var(--m3-primary-container)', onPrimaryContainer: 'var(--m3-on-primary-container)', surfaceContainerLow: 'var(--m3-surface-container-low)', surfaceContainerHighest:'var(--m3-surface-container-highest)', surfaceVariant: 'var(--m3-surface-variant)', onSurface: 'var(--m3-on-surface)', onSurfaceVariant: 'var(--m3-on-surface-variant)', outline: 'var(--m3-outline)', outlineVariant: 'var(--m3-outline-variant)' }; const ICONS: Record<string, string> = { 'party': 'M2 22l5-14 9 9-14 5zm3.3-3.3l7.05-2.5-4.55-4.55-2.5 7.05zM14.55 12.55l-1.05-1.05 5.6-5.6q.8-.8 1.925-.8t1.925.8l.6.6-1.05 1.05-.6-.6q-.35-.35-.875-.35t-.875.35l-5.6 5.6zM10.55 8.55l-1.05-1.05.6-.6q.35-.35.35-.85t-.35-.85l-.65-.65 1.05-1.05.65.65q.8.8.8 1.9t-.8 1.9l-.6.6zm2 2l-1.05-1.05 3.6-3.6q.35-.35.35-.875t-.35-.875l-1.6-1.6 1.05-1.05 1.6 1.6q.8.8.8 1.925t-.8 1.925l-3.6 3.6zm4 4l-1.05-1.05 1.6-1.6q.8-.8 1.925-.8t1.925.8l1.6 1.6-1.05 1.05-1.6-1.6q-.35-.35-.875-.35t-.875.35l-1.6 1.6z', 'guests': 'M16 11C17.66 11 18.99 9.66 18.99 8C18.99 6.34 17.66 5 16 5C14.34 5 13 6.34 13 8C13 9.66 14.34 11 16 11M8 11C9.66 11 10.99 9.66 10.99 8C10.99 6.34 9.66 5 8 5C6.34 5 5 6.34 5 8C5 9.66 6.34 11 8 11M8 13C5.67 13 1 14.17 1 16.5V19H15V16.5C15 14.17 10.33 13 8 13M16 13C15.71 13 15.38 13.02 15.03 13.05C16.19 13.89 17 15.02 17 16.5V19H23V16.5C23 14.17 18.33 13 16 13Z', 'sun': 'M20 14H18L14.8 23H16.7L17.4 21H20.6L21.3 23H23.2L20 14M17.8 19.7L19 16L20.2 19.7H17.8M7 9H15V11H7V9M7 12H15V14H7V12M7 15H15V16.5L14.8 17H7V15M13.7 20H7V18H14.5L13.7 20M16 8H6V20H4V8H2V4H20V8H18V12H16.6L16.1 13.3L16 13.7V8Z', 'alarm': 'M12 20A7 7 0 0 1 5 13A7 7 0 0 1 12 6A7 7 0 0 1 19 13A7 7 0 0 1 12 20M12 4A9 9 0 0 0 3 13A9 9 0 0 0 12 22A9 9 0 0 0 21 13A9 9 0 0 0 12 4M7.88 3.39L6.6 1.86L2 5.71L3.29 7.24L7.88 3.39M22 5.72L17.4 1.86L16.11 3.39L20.71 7.25L22 5.72M12.5 8H11V14L15.75 16.85L16.5 15.62L12.5 13.25V8Z', 'person': 'M12 4A4 4 0 0 1 16 8A4 4 0 0 1 12 12A4 4 0 0 1 8 8A4 4 0 0 1 12 4M12 14C16.42 14 20 15.79 20 18V20H4V18C4 15.79 7.58 14 12 14Z', 'moon': 'M12 21q-3.75 0-6.375-2.625T3 12q0-3.75 2.625-6.375T12 3q.35 0 .688.025t.662.075q-1.025.725-1.637 1.888T11.1 7.5q0 2.25 1.575 3.825T16.5 12.9q1.375 0 2.525-.612t1.875-1.638q.05.325.075.663T21 12q0 3.75-2.625 6.375T12 21Zm0-2q2.2 0 3.95-1.212T18.5 14.625q-.5.125-1 .2t-1 .075q-3.075 0-5.238-2.162T9.1 7.5q0-.5.075-1t.2-1q-1.95.8-3.162 2.55T5 12q0 2.9 2.05 4.95T12 19Zm-.25-6.75Z', 'auto': 'M11 19q1.3 0 2.47-.52t2.03-1.5q-3.2-.2-5.35-2.49T8 9q0-.32.02-.64t.08-.61q-1.42.8-2.26 2.2T5 13q0 2.5 1.75 4.25T11 19Zm0 2q-3.35 0-5.67-2.32t-2.33-5.68q0-3.35 2.32-5.67t5.68-2.33q.13 0 .25.01t.25.01q-.72.8-1.11 1.83T10 9q0 2.5 1.75 4.25T16 15q.78 0 1.51-.19t1.4-.56q-.45 2.95-2.7 4.85T11 21Zm2.8-10 3.2-9h2l3.2 9h-1.9l-.7-2H17.4l-.7 2h-1.9Zm3.05-3.35h2.3l-1.15-3.65-1.15 3.65ZM10.18-9.53Z', 'palette': 'M12 22q-2.05 0-3.875-.788t-3.187-2.15Q3.575 17.7 2.788 15.875T2 12q0-2.075.812-3.9t2.2-3.175Q6.4 3.575 8.25 2.788T12.2 2q2 0 3.775.688t3.112 1.9q1.338 1.212 2.125 2.875T22 10.95q0 2.875-1.75 4.413T16 17h-1.85q-.225 0-.313.125t-.087.275q0 .3 0.375.863T14.5 20.3q0 1.25-.688 1.85T12 22Zm0-10Zm-4.425.575q.425-.425.425-1.075t-.425-1.075q-.425-.425-1.075-.425t-1.075.425q-.425.425-.425 1.075t.425 1.075q.425.425 1.075.425t1.075-.425Zm3-4q.425-.425.425-1.075t-.425-1.075q-.425-.425-1.075-.425t-1.075.425q-.425.425-.425 1.075t.425 1.075q.425.425 1.075.425t1.075-.425Zm5 0q.425-.425.425-1.075t-.425-1.075q-.425-.425-1.075-.425t-1.075.425q-.425.425-.425 1.075t.425 1.075q.425.425 1.075.425t1.075-.425Zm3 4q.425-.425.425-1.075t-.425-1.075q-.425-.425-1.075-.425t-1.075.425q-.425.425-.425 1.075t.425 1.075q.425.425 1.075.425t1.075-.425ZM12 20q.225 0 .363-.125t.137-.325q0-.35-.375-.825T11.75 17.3q0-1.05.725-1.675T14.25 15h1.75q1.65 0 2.825-.963T20 10.95q0-3.025-2.312-5.038T12.2 3.9q-3.4 0-5.8 2.325T4 11.95q0 3.325 2.338 5.663T12 20Z', 'dropdown': 'M7 10l5 5 5-5H7z', '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' }; interface ListItem { label: string; description: string; icon: string; oid: string; disableIf?: string; category: 'system' | 'appearance'; } // === EURE LISTEN-EINTRÄGE === function getListItems(): ListItem[] { const isPrivacy = getSafeVal(DP_PRIVACY, false) === true; const nameUser1 = isPrivacy ? 'Bewohner 1' : 'Eigener Name'; return [ { category: 'system', label: 'Rollladen Automatik', description: 'Die Rollladen werden nach Sonnenuntergang automatisch geschlossen.', icon: 'sun', oid: 'javascript.0.routines.auto_blinds' }, { category: 'system', label: 'WakeUp-Automatik', description: 'Bei Bewegung nach 6 Uhr am Morgen werden die Rolladen geöffnet.', icon: 'alarm', oid: 'javascript.0.routines.wakeup' }, { category: 'system', label: 'Partymodus', description: 'Schlaf-Taste bleibt verborgen. Automatiken werden pausiert.', icon: 'party', oid: '0_userdata.0.System.Partymodus' }, { category: 'system', label: 'Gäste im Haus', description: 'Verhindert das automatische Ausschalten in bestimmten Bereichen.', icon: 'guests', oid: '0_userdata.0.System.Gaeste' }, { category: 'system', label: nameUser1, description: `Für ${nameUser1} wird eine individuelle Routine aktiviert.`, icon: 'person', oid: 'javascript.0.routines.user1_routine' }, { category: 'appearance', label: 'Privacy Mode (Demo)', description: 'Anonymisiert Namen und Bilder für Screenshots.', icon: 'person', oid: DP_PRIVACY }, { category: 'appearance', label: 'Google JSON Theme', description: 'Aktiv: Nutzt Custom-JSON. Inaktiv: Farben aus dem Wallpaper.', icon: 'palette', oid: `${BASE_PATH}.themeUseJson` }, { category: 'appearance', label: 'Auto-Theme (Astro)', description: 'Wechselt bei Sonnenuntergang in den Dark-Mode.', icon: 'auto', oid: `${BASE_PATH}.autoTheme` }, { category: 'appearance', label: 'Manueller Dark Mode',description: 'Überschreibt das Farbschema (Wenn Auto-Theme aus ist).', icon: 'moon', oid: `${BASE_PATH}.darkMode`, disableIf: `${BASE_PATH}.autoTheme` } ]; } interface WallpaperItem { label: string; path: string; } function getDynamicWallpapers(): WallpaperItem[] { let wallpapers: WallpaperItem[] = []; try { if (fs.existsSync(WALLPAPER_SYS_DIR)) { const files = fs.readdirSync(WALLPAPER_SYS_DIR); files.forEach((file: string) => { const ext = file.toLowerCase(); if (ext.endsWith('.jpg') || ext.endsWith('.jpeg') || ext.endsWith('.png') || ext.endsWith('.webp')) { let cleanName = file.replace(/\.[^/.]+$/, "").replace(/[_-]/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); wallpapers.push({ label: cleanName, path: WALLPAPER_WEB_DIR + file }); } }); } } catch (e: any) { // Ordner existiert nicht oder fs Modul fehlt } wallpapers.sort((a, b) => a.label.localeCompare(b.label)); return wallpapers; } let DYNAMIC_WALLPAPERS: WallpaperItem[] = getDynamicWallpapers(); function svgIcon(name: string, color: string, size: number = 24): string { const path = ICONS[name] || ICONS['person']; 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>`; } function getSafeVal(oid: string, fallback: any = false): any { if (existsState(oid)) return getState(oid).val; return fallback; } function getWallpaperNameByPath(path: string): string { const found = DYNAMIC_WALLPAPERS.find(wp => wp.path === path); return found ? found.label : 'Auswählen...'; } function renderListItem(item: ListItem): string { const rawVal = getSafeVal(item.oid, false); const active = (rawVal === true || rawVal === 1 || rawVal === 'true'); let disabled = false; if (item.disableIf) disabled = (getSafeVal(item.disableIf, false) === true); const trackBg = active ? M3.primary : M3.surfaceContainerHighest; const trackBorder = active ? M3.primary : M3.outline; const thumbSize = active ? 24 : 16; const thumbColor = active ? M3.onPrimary : M3.outline; const thumbLeft = active ? 'calc(100% - 26px)' : '6px'; const opacity = disabled ? '0.4' : '1'; const pointerEvents = disabled ? 'none' : 'auto'; const clickAction = disabled ? "" : `(function(e){ e.stopPropagation(); if(typeof vis!=='undefined'&&vis.conn&&vis.conn.setState)vis.conn.setState('${item.oid}', ${!active}); else if(typeof vis!=='undefined'&&vis.setValue)vis.setValue('${item.oid}', ${!active}); })(event);`.replace(/\n/g, ' '); const rippleJS = disabled ? "" : `(function(e,el){var evt=e.touches?e.touches[0]:e;if(!evt.clientX)return;var d=Math.max(el.clientWidth,el.clientHeight);var r=d/2;var rect=el.getBoundingClientRect();var x=evt.clientX-rect.left;var y=evt.clientY-rect.top;var c=document.createElement('span');c.style.width=c.style.height=d+'px';c.style.left=(x-r)+'px';c.style.top=(y-r)+'px';c.style.position='absolute';c.style.borderRadius='50%';c.style.backgroundColor='var(--m3-on-surface-variant)';c.style.opacity='0.1';c.style.transform='scale(0)';c.style.animation='m3-ripple-anim 0.5s linear';c.style.pointerEvents='none';el.appendChild(c);setTimeout(function(){c.remove();},500);})(event,this)`; return ` <div onclick="${clickAction}" onmousedown="${rippleJS}" ontouchstart="${rippleJS}" style="position:relative; overflow:hidden; width:100%; border-radius:16px; padding:12px 16px; box-sizing:border-box; background:${M3.surfaceContainerLow}; cursor:${disabled ? 'default' : 'pointer'}; user-select:none; display:flex; align-items:center; opacity:${opacity}; pointer-events:${pointerEvents}; font-family:Roboto,'Segoe UI',system-ui,sans-serif; margin-bottom: 8px; -webkit-tap-highlight-color:transparent; transition: background-color 0.3s ease;"> <div style="flex: 0 0 40px; width:40px; height:40px; border-radius:50%; background:${M3.surfaceVariant}; display:flex; align-items:center; justify-content:center; margin-right:14px;"> ${svgIcon(item.icon, M3.onSurfaceVariant, 20)} </div> <div style="flex: 1 1 0%; min-width: 0; display: block; padding: 2px 0;"> <div style="font-size:16px; font-weight:500; color:${M3.onSurface}; line-height:1.3; margin-bottom:2px; white-space:normal; overflow-wrap:break-word;">${item.label}</div> <div style="font-size:13px; font-weight:400; color:${M3.onSurfaceVariant}; opacity:0.85; line-height:1.4; white-space:normal; overflow-wrap:break-word;">${item.description}</div> </div> <div style="flex: 0 0 52px; width:52px; height:32px; margin-left:12px; border-radius:16px; background:${trackBg}; border:2px solid ${trackBorder}; box-sizing:border-box; position:relative; transition:all 0.25s cubic-bezier(0.2, 0, 0, 1);"> <div style="position:absolute; left:${thumbLeft}; top:50%; transform:translateY(-50%); width:${thumbSize}px; height:${thumbSize}px; border-radius:50%; background:${thumbColor}; transition:all 0.25s cubic-bezier(0.2, 0, 0, 1);"></div> </div> </div>`; } // === RECHTSBÜNDIGE FILTER CHIPS === function renderChartChips(): string { const actVal = getSafeVal(DP_CHART_ACT, true); const setVal = getSafeVal(DP_CHART_SET, true); const winVal = getSafeVal(DP_CHART_WINDOW, true); const brennerVal = getSafeVal(DP_CHART_BRENNER, true); const valveVal = getSafeVal(DP_CHART_VALVE, false); const humidVal = getSafeVal(DP_CHART_HUMID, true); const makeChip = (label: string, active: boolean, dp: string) => { const bg = active ? M3.primaryContainer : 'transparent'; const fg = active ? M3.onPrimaryContainer : M3.onSurfaceVariant; const border = active ? `1px solid ${M3.primaryContainer}` : `1px solid ${M3.outline}`; const iconHtml = active ? `<svg width="18" height="18" viewBox="0 0 24 24" style="margin-right:4px; margin-left:-4px;"><path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>` : ''; const js = `(function(e){ e.stopPropagation(); if(typeof vis!=='undefined'&&vis.conn&&vis.conn.setState)vis.conn.setState('${dp}', ${!active}); else if(typeof vis!=='undefined'&&vis.setValue)vis.setValue('${dp}', ${!active}); })(event);`.replace(/\n/g, ' '); return `<div onclick="${js}" style="display:inline-flex; align-items:center; height:32px; padding:0 12px; border-radius:8px; background:${bg}; border:${border}; color:${fg}; font-size:14px; font-weight:500; cursor:pointer; transition:all 0.2s ease;">${iconHtml}${label}</div>`; }; return ` <div style="width:100%; border-radius:16px; padding:12px 16px; box-sizing:border-box; background:${M3.surfaceContainerLow}; margin-bottom:8px; font-family:Roboto,'Segoe UI',system-ui,sans-serif; display:flex; align-items:flex-start;"> <div style="flex:0 0 40px; width:40px; height:40px; border-radius:50%; background:${M3.surfaceVariant}; display:flex; align-items:center; justify-content:center; margin-right:14px;"> ${svgIcon('thermostat', M3.onSurfaceVariant, 20)} </div> <div style="flex: 1 1 0%; min-width: 0;"> <div style="font-size:16px; font-weight:500; color:${M3.onSurface}; margin-bottom:2px;">Graphen-Anzeige</div> <div style="font-size:13px; color:${M3.onSurfaceVariant}; opacity:0.85; margin-bottom:12px; line-height:1.4;">Wähle aus, welche Linien in den Diagrammen gezeichnet werden.</div> <div style="display:flex; flex-wrap:wrap; gap:8px; justify-content:flex-end;"> ${makeChip('Ist-Temp', actVal, DP_CHART_ACT)} ${makeChip('Soll-Temp', setVal, DP_CHART_SET)} ${makeChip('Luftfeuchte', humidVal, DP_CHART_HUMID)} ${makeChip('Fenster', winVal, DP_CHART_WINDOW)} ${makeChip('Brenner', brennerVal, DP_CHART_BRENNER)} ${makeChip('Ventil', valveVal, DP_CHART_VALVE)} </div> </div> </div>`; } function renderMenuWallpaperItem(wp: WallpaperItem, currentPath: string): string { const active = (wp.path === currentPath); const bg = active ? M3.surfaceVariant : 'transparent'; const textColor = active ? M3.primary : M3.onSurface; const clickAction = `(function(e){ e.stopPropagation(); if(typeof vis!=='undefined'&&vis.conn&&vis.conn.setState)vis.conn.setState('${DP_IMAGE_PATH}', '${wp.path}'); else if(typeof vis!=='undefined'&&vis.setValue)vis.setValue('${DP_IMAGE_PATH}', '${wp.path}'); document.getElementById('m3-wallpaper-menu').style.opacity='0'; document.getElementById('m3-wallpaper-menu').style.pointerEvents='none'; })(event);`.replace(/\n/g, ' '); return `<div onclick="${clickAction}" style="position:relative; width:100%; padding:10px 16px; box-sizing:border-box; border-radius:12px; background:${bg}; cursor:pointer; user-select:none; margin-bottom:2px; display:flex; align-items:center; font-family:Roboto,'Segoe UI',system-ui,sans-serif; -webkit-tap-highlight-color:transparent; transition: background-color 0.2s ease;"> <img src="${wp.path}" style="flex:0 0 44px; width:44px; height:32px; border-radius:6px; object-fit:cover; background:${M3.surfaceVariant}; margin-right:14px;"> <div style="flex:1 1 0%; min-width:0; display:block; font-size:15px; font-weight:500; color:${textColor}; line-height:1.4; white-space:normal; overflow-wrap:break-word;">${wp.label}</div> </div>`; } function renderSetup(): void { const allItems = getListItems(); const systemHtml = allItems.filter(item => item.category === 'system').map(item => renderListItem(item)).join(''); const appearanceHtml = allItems.filter(item => item.category === 'appearance').map(item => renderListItem(item)).join(''); const chartChipsHtml = renderChartChips(); const currentWpPath = getSafeVal(DP_IMAGE_PATH, ''); const currentWpName = getWallpaperNameByPath(currentWpPath); const menuItemsHtml = DYNAMIC_WALLPAPERS.map(wp => renderMenuWallpaperItem(wp, currentWpPath)).join(''); const openMenuJS = `document.getElementById('m3-wallpaper-menu').style.opacity='1'; document.getElementById('m3-wallpaper-menu').style.pointerEvents='auto';`; const closeMenuJS = `document.getElementById('m3-wallpaper-menu').style.opacity='0'; document.getElementById('m3-wallpaper-menu').style.pointerEvents='none';`; const rippleJS = `(function(e,el){var evt=e.touches?e.touches[0]:e;if(!evt.clientX)return;var d=Math.max(el.clientWidth,el.clientHeight);var r=d/2;var rect=el.getBoundingClientRect();var x=evt.clientX-rect.left;var y=evt.clientY-rect.top;var c=document.createElement('span');c.style.width=c.style.height=d+'px';c.style.left=(x-r)+'px';c.style.top=(y-r)+'px';c.style.position='absolute';c.style.borderRadius='50%';c.style.backgroundColor='var(--m3-on-surface-variant)';c.style.opacity='0.1';c.style.transform='scale(0)';c.style.animation='m3-ripple-anim 0.5s linear';c.style.pointerEvents='none';el.appendChild(c);setTimeout(function(){c.remove();},500);})(event,this)`; const html = ` <style> @keyframes m3-ripple-anim { to { transform: scale(4); opacity: 0; } } [id^="vis_widget_"] .vis-view-inner-html-html, [id^="vis_widget_"] .vis-view-inner-html-html > div { height: 100% !important; } .m3-scroll-container::-webkit-scrollbar, .m3-menu-scroll::-webkit-scrollbar { width: 6px; } .m3-scroll-container::-webkit-scrollbar-track, .m3-menu-scroll::-webkit-scrollbar-track { background: transparent; } .m3-scroll-container::-webkit-scrollbar-thumb, .m3-menu-scroll::-webkit-scrollbar-thumb { background: var(--m3-outline-variant); border-radius: 3px; } .m3-scroll-container::-webkit-scrollbar-thumb:hover, .m3-menu-scroll::-webkit-scrollbar-thumb:hover { background: var(--m3-outline); } </style> <div class="m3-scroll-container" style="position: absolute; inset: 0; background: var(--m3-surface-container-low, rgb(237, 239, 232)); border-radius: 0 0 28px 28px; padding: 24px; display: block; width: 100%; box-sizing: border-box; overflow-y: auto; transition: background-color 0.3s ease;"> <div style="font-size:12px; font-weight:700; color:${M3.primary}; text-transform:uppercase; letter-spacing:1.2px; margin: 4px 0 8px 4px;">Erscheinungsbild & Anzeige</div> ${appearanceHtml} ${chartChipsHtml} <div style="position:relative; width:100%; border-radius:16px; padding:12px 16px; box-sizing:border-box; background:${M3.surfaceContainerLow}; user-select:none; margin-bottom:24px; display:flex; align-items:center; font-family:Roboto,'Segoe UI',system-ui,sans-serif; -webkit-tap-highlight-color:transparent; transition: all 0.3s ease;"> <div style="flex:0 0 40px; width:40px; height:40px; border-radius:50%; background:${M3.surfaceVariant}; display:flex; align-items:center; justify-content:center; margin-right:14px;">${svgIcon('palette', M3.onSurfaceVariant, 20)}</div> <div style="flex:1 1 0%; min-width:0; display:block;"><div style="font-size:16px; font-weight:500; color:${M3.onSurface}; line-height:normal;">Hintergrundbild</div></div> <div onclick="${openMenuJS}" onmousedown="${rippleJS}" ontouchstart="${rippleJS}" style="flex:0 0 auto; margin-left:12px; position:relative; overflow:hidden; display:flex; align-items:center; gap:8px; background:${M3.surfaceVariant}; padding:8px 16px; border-radius:20px; cursor:pointer; color:${M3.onSurfaceVariant}; transition: background-color 0.2s ease;"> <span style="font-size:14px; font-weight:500; line-height:normal;">${currentWpName}</span>${svgIcon('dropdown', M3.onSurfaceVariant, 20)} </div> </div> <div style="font-size:12px; font-weight:700; color:${M3.primary}; text-transform:uppercase; letter-spacing:1.2px; margin: 0 0 8px 4px;">System & Automatiken</div> ${systemHtml} <div style="height: 24px; flex-shrink: 0;"></div> </div> <div id="m3-wallpaper-menu" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 999999; display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transition: opacity 0.2s;"> <div onclick="${closeMenuJS}" style="position: absolute; inset: 0; background: rgba(0,0,0,0.4);"></div> <div style="position: relative; width: 380px; max-height: 80vh; background: var(--m3-surface-container-highest, #e1e3dc); border-radius: 20px; box-shadow: 0 12px 32px rgba(0,0,0,0.3); display: flex; flex-direction: column; overflow: hidden; font-family:Roboto,'Segoe UI',system-ui,sans-serif;"> <div style="padding: 20px 24px 12px 24px; font-size: 15px; font-weight: 600; color:${M3.onSurfaceVariant}; border-bottom: 1px solid ${M3.outlineVariant};">Hintergrundbild auswählen</div> <div class="m3-menu-scroll" style="overflow-y: auto; padding: 12px; display:flex; flex-direction:column;"> ${DYNAMIC_WALLPAPERS.length > 0 ? menuItemsHtml : `<div style="padding:20px; text-align:center; color:${M3.onSurfaceVariant}; font-size:14px;">Keine Bilder gefunden. Ordner prüfen!</div>`} </div> </div> </div>`; setState(DP_SETUP_HTML, html, true); } // === INIT & SUBSCRIPTIONS ====================================================== // Verschachtelte createState Aufrufe stellen sicher, dass alle Datenpunkte existieren, bevor abonniert wird. createState(DP_SETUP_HTML, '', { type: 'string', name: 'Setup HTML (M3)', role: 'html' }, () => { createState(DP_CHART_BRENNER, true, { type: 'boolean', name: 'Show Brenner Chart', role: 'switch' }, () => { createState(DP_CHART_VALVE, false, { type: 'boolean', name: 'Show Valve Chart', role: 'switch' }, () => { createState(DP_CHART_ACT, true, { type: 'boolean', name: 'Show Actual Temp Chart', role: 'switch' }, () => { createState(DP_CHART_SET, true, { type: 'boolean', name: 'Show Set Temp Chart', role: 'switch' }, () => { createState(DP_CHART_WINDOW, true, { type: 'boolean', name: 'Show Window Chart', role: 'switch' }, () => { createState(DP_CHART_HUMID, true, { type: 'boolean', name: 'Show Humidity Chart', role: 'switch' }, () => { createState(DP_PRIVACY, false, { type: 'boolean', name: 'Privacy Mode Toggle', role: 'switch' }, () => { createState(DP_IMAGE_PATH, '', { type: 'string', name: 'Theme Image Path', role: 'text' }, () => { getListItems().forEach(item => { if (existsState(item.oid)) on({ id: item.oid, change: 'any' }, () => renderSetup()); if (item.disableIf && existsState(item.disableIf)) on({ id: item.disableIf, change: 'any' }, () => renderSetup()); }); on({ id: [DP_CHART_BRENNER, DP_CHART_VALVE, DP_CHART_ACT, DP_CHART_SET, DP_CHART_WINDOW, DP_CHART_HUMID], change: 'any' }, () => renderSetup()); on({ id: DP_IMAGE_PATH, change: 'any' }, () => renderSetup()); renderSetup(); }); }); });});});});});}); }); [image: 1773605233417-setup2.jpg] [image: 1773605233425-setup1.jpg]
  • [TEST] Mammotion – Adapter für Mammotion Luba / Yuka

    Verschoben Tester
    10
    3 Stimmen
    10 Beiträge
    256 Aufrufe
    E
    Hi, danke für diesen Adapter. Ich habe einen Yuka Mini 2025 und evtl liegt es daran, dass der Yuka andere Befehle erwartet wie der Luba. Z.b. kann der Yuka Mini keine Höhenverstellung und keine Messerdrehzahl. Ausserdem ist mir aufgefallen, dass die Schnittbahnbreite in der App zwischen 8 und 11 cm zu verstellen ist. In deinem Adapter von 20 -35.
  • ioBroker vs. Home Assistant -> Z-Wave im Vergleich

    Off Topic
    5
    3 Stimmen
    5 Beiträge
    128 Aufrufe
    W
    Reinfuchsen musst Du Dich eh überall erst einmal. Ja, ich verwende Smarthome-Systeme seit mehreren Jahren. Aber iobroker und Home Assistant zu verbinden ist kein Hexenwerk. Es gibt ja den HASS-Adapter mit dem Du dann alles aus HA in iobroker hast. Und, wie gesagt, es schadet nix, über den Tellerrand zu schauen und sich auch mal andere Systeme zu installieren. Ist ja alles ein kann, kein MUSS. Gruss, Jürgen
  • LAN Cert Manager - Lokale Domain Verwaltung einfach

    ioBroker Allgemein
    4
    9
    3 Stimmen
    4 Beiträge
    180 Aufrufe
    Dr. BakteriusD
    Werde ich auf jeden Fall testen sobald ich dafür wieder einmal Zeit finde. Danke!
  • Langzeitvergleich Regensensoren Ecowitt WS90 vs WH40H

    Off Topic
    2
    1
    3 Stimmen
    2 Beiträge
    104 Aufrufe
    S
    Update - Stand 31.3.26 [image: 1774976566892-screenshot-2026-03-31-190200.png]
  • [Test Adapter] open-meteo-pv-forecast v1.1.x GitHub/NPM

    Tester
    32
    2 Stimmen
    32 Beiträge
    843 Aufrufe
    mcm1957M
    Nein - mir ist nicht fad :-) Ein (zwingendes) Review gibt es nur für die Erstaufnahme im Latest. Neuere Versionen gehen dann automatisch ind BETA / Latest Repo. Aufnahme ind STable wird vom Dev bestellt und dabei erfolgen nur die automatisierten Prüfungend es Repocheckers. Außerdem muss der Adapetr eine (kurze) Zeit im Latest angeboten werden. Und dass ein Dev bei bekannten groben Mängel im Adapter die Release nichts ins Stable schickt nehmen wir an.
  • Test Adapter Navimow

    Tester
    31
    4
    2 Stimmen
    31 Beiträge
    729 Aufrufe
    Merlin123M
    @c1olli jup. bei mir auch... Wollte ich noch melden...
  • Einzelnes Skript aus Backup wiederherstellen

    Off Topic
    9
    2
    2 Stimmen
    9 Beiträge
    166 Aufrufe
    H
    @David-G. Alles klar, Danke
  • Cache-Probleme seit sayit v5.1.0

    Ungelöst Error/Bug
    5
    1
    2 Stimmen
    5 Beiträge
    114 Aufrufe
    Meister MopperM
    @bahnuhr sagte: Das mag sein; das Problem bleibt halt. Oder man legt selbst Hand an, wie im Eingangsthread beschrieben.
  • Test: ioBroker.script-restore

    Tester
    3
    2 Stimmen
    3 Beiträge
    13 Aufrufe
    NegaleinN
    @David-G. kannst du noch weitere Quellen hinzufügen? Wie beim Backitup (FTP, Dropbox).
  • [Test Adapter] AlarmManager ,e*Message Pager

    Tester
    3
    2 Stimmen
    3 Beiträge
    108 Aufrufe
    T
    @sigi234 erledigt.
  • ioBroker Object Explorer: Dashboard zum Verwalten von DPs

    Entwicklung
    1
    1
    2 Stimmen
    1 Beiträge
    87 Aufrufe
    Niemand hat geantwortet
  • DeConz startet nicht mehr

    Ungelöst Error/Bug
    22
    1 Stimmen
    22 Beiträge
    229 Aufrufe
    I
    heute auf 1.7.4 update - alles okay :)
  • nodejs Downgrade durch "sudo apt full-upgrade"

    Pflege des Betriebssystems apt upgrade nodejs
    14
    1 Stimmen
    14 Beiträge
    281 Aufrufe
    Thomas BraunT
    @Rushmed sagte in nodejs Downgrade durch "sudo apt full-upgrade": Mein ioBroker sagt auch dass 22.22.2 die neuste ist. Ja, ist es im Upstream von nodejs.org ja auch. Nur im Downstream via nodesource nicht.
  • Axios kompromittiert

    Pflege des Betriebssystems malware npm kompromittiert
    11
    1 Stimmen
    11 Beiträge
    299 Aufrufe
    HomoranH
    @Thomas-Braun sagte: falls user genau in dem Zeitraum Adapter installiert haben, die eine ungepinnte axios-Version einbinden, dann könnte es sein, Kann man eigentlich die zusätzlichen npm-Module in der Javascript Instanz auch pinnen? Z.b. indem man eingibt modulName@1.2.3? Wie finde ich denn deren geladene Versionsnummern? Hab jetzt auch unter /opt/iobroker versucht mit npm list modulName, da kommt nur empty.
  • 1 Stimmen
    11 Beiträge
    486 Aufrufe
    J
    Und dann sehe ich auch z.B. das in dem DB Datenpunkt: "95": [ { "alias.0.Bad.Deckenlampe_Bad.ON": { "on": true, "bri": 100 }, "alias.0.Büro.Deckenlampe_Büro.ON": { "on": false }, "alias.0.Esszimmer.Deckenlampe_Esszimmer_1.ON": { "on": false }, "alias.0.Flur.Hue1_Decke.ON": { "on": true, "bri": 50 }, "alias.0.Küche.Deckenlampe_Küche.ON": { "on": false }, "alias.0.Schlafzimmer.Deckenlampe_Schlafzimmer.ON": { "on": false }, "alias.0.Wohnzimmer.Deckenlampe_Wohnzimmer.ON": { "on": false }, "alias.0.Wohnzimmer.Ikea_Stehlampe.ON": { "on": false }, "hue.0.Hue_Esszimmer_1": { "on": false }, "hue.0.Hue_Flur_1": { "on": false }, "hue.0.Hue_Küche": { "on": false }, "hue.0.Hue_Schlafzimmer": { "on": false }, "hue.0.Hue_Wohnzimmer": { "on": false }, "hue.0.Hue_go_Wohnzimmer_links": { "on": false }, "hue.0.Hue_go_Wohnzimmer_rechts": { "on": false }, "hue.0.Ikea_Stehlampe": { "on": false }, "hue.0.Hue_Bad": { "on": false }, "hue.0.Hue_Büro": { "on": false } } ] } Hier der Teil meiner Konfig: const LIGHTS: string[] = [ 'alias.0.Bad.Deckenlampe_Bad.ON', // <-- Trage hier alle Lampen ein, die 'alias.0.Büro.Deckenlampe_Büro.ON', // von der Straße aus sichtbar sind. 'alias.0.Esszimmer.Deckenlampe_Esszimmer_1.ON', 'alias.0.Flur.Hue1_Decke.ON', 'alias.0.Küche.Deckenlampe_Küche.ON', 'alias.0.Schlafzimmer.Deckenlampe_Schlafzimmer.ON', 'alias.0.Wohnzimmer.Deckenlampe_Wohnzimmer.ON', 'alias.0.Wohnzimmer.Ikea_Stehlampe.ON' ]; Es ist aber so, dass zum Zeitpunkt des Schreibens in den DP das Licht in der Küche und im Esszimmer an war. Dennoch sagen die Daten im DP etwas anderes. Liegt es an einer falschen Konfig von mir? Danke!
  • neue Ikea Matter Devices

    ioBroker Allgemein
    9
    1 Stimmen
    9 Beiträge
    219 Aufrufe
    D
    Ich habe eine Grillplats heute per DPD bekommen. Ich bin mehr als begeistert. Das vorsintflutliche Nest Hub2 System (kein aktuelles Mattersystem) reicht Werte die es selber nicht anzeigen kann trotzdem an den ioBroker durch. Die Verbauchs- und Energiewerte werden im Broker angezeigt und im Ghome nicht. Günstige Thread Router, perfekt.
  • [Neuer Adapter] Hoymiles HMS Wechselrichter

    Verschoben Tester
    8
    1 Stimmen
    8 Beiträge
    178 Aufrufe
    EisteeE
    @mcm1957 die 'Wechselrichter senden per default 1x alle 5min an die S-Miles Cloud. Genau so oft fragt der Adapter die API auch ab da sich die Daten dort nicht häufiger ändern werden. Ich denke das ist total unkritisch. Es gibt die Möglichkeit zu sagen welchen Wechselrichter man genau abfragen will indem man die Seriennummer angibt. Dies ist aber auch nur erforderlich wenn keine lokale Verbindung genutzt wird. Es können grundsätzlich mehrere Wechselrichter in einem oder auch in mehreren Accounts genutzt werden. Das Cloud Relay ahmt nur die Verbindung nach die der Wechselrichter zur Cloud aufrecht erhält solang keine lokale Verbindung hergestellt ist. Die Wechselrichter können nur eine TCP Verbindung und die lokale Verbindung unterbricht somit den Cloud Upload den dann der Adapter 1:1 übernimmt wie es sonst der Wechselrichter machen würde. Hier ist also ohnehin pro Wechselrichter eine Verbindung notwendig.

625

Online

32.8k

Benutzer

82.7k

Themen

1.3m

Beiträge