Skip to content

Tester

In dieser Kategorie werden Adapter Versionen vorgestellt die der Entwickler zum Testen Frei gibt.

1.1k Themen 194.3k Beiträge

NEWS

  • Test VIS 2 inventwo Widgets

    151
    2
    9 Stimmen
    151 Beiträge
    27k Aufrufe
    BananaJoeB
    @alexathome naja, du könntest ja auch einfach den JSON der da rauskommt mit einem Skirpt nochmal bearbeiten und die bearbeitete Version dann verwenden. Beim "bearbeiten" könnte dann schon ein Suchen & Ersetzen reichen, ersetze alle ERROR mit <formatierung>ERROR</> oder ähnlich
  • [Neuer Adapter] Homepilot20

    Verschoben adapter
    268
    1
    2 Stimmen
    268 Beiträge
    49k Aufrufe
    H
    @rogni hallo ja, ich kann ja dann mal schauen, ob und wie ich den integrieren kann. sicherlich sehr hilfreich - DANKESCHÖN
  • [Tester gesucht] Visual Studio Code Extension für ioBroker

    Verschoben vscode javascript
    154
    7 Stimmen
    154 Beiträge
    21k Aufrufe
    icebearI
    Ich hab das jetzt bei mir auch mal implementiert, aber warum seh ich wenn ich ganz oben auf den Explorer gehe in dem Ordner den ich angelegt hab, die Ordnerstruktur so wie sie in iobroker ist, hab ich noch irgendwo was vergessen? [image: 1762586458598-vsstudio.png] Edit: Habs grad selber rausgefunden, ich hatte vergessen bei den Scripten auf 'Download All' zu klicken, jetzt sind die Ordner und Scripte local da.
  • 1 Stimmen
    12 Beiträge
    686 Aufrufe
    R
    @mcm1957 Ok, habs glaube ich. Nach der Auswahl von Typical kommt die Abfrage ob installiert werden soll. Wenn ich hier auf back gehe kommt das Fenster zur Eingabe der Host und des PW. Das PW konnte ich jetzt während der Installation vergeben Aber ich finde keinen Hinweis zum User. Der Adapter wird schonmal grü, ohne Benutzername aber mit PW. Verbindung ist false. Login gefunden "admin" Adapter bekommt Daten aber online bleibt false. Per WebUi bin ich jetzt auch drin. IP statt localhost hat geholfen.
  • Test Adapter harmony v2.0.x

    harmony mcm1957
    27
    3 Stimmen
    27 Beiträge
    813 Aufrufe
    mcm1957M
    @icebear Bitte ggF Issue und Bluefox auf Telegramm pingen.
  • Test Adapter Shelly 10.4.x (LATEST / BETA)

    mcm1957 shelly
    54
    6 Stimmen
    54 Beiträge
    6k Aufrufe
    haus-automatisierungH
    @stealthangel sagte in Test Adapter Shelly 10.4.x (LATEST / BETA): Schade ist allerdings, dass der Füllstand in % nicht übertragen wird. Der wird ja wahrscheinlich einfach in der App berechnet. Also mit einem Alias und einem eigenen Datenpunkt ganz ohne Scripts im ioBroker ebenfalls lösbar.
  • Test Adapter Log Parser 2.4.x Latest

    108
    2 Stimmen
    108 Beiträge
    17k Aufrufe
    NegaleinN
    Hallo Seit gestern wird nichts von Alexa vom Logparser 2.4.0 geloggt. Einstellungen sind identisch wie vorher. Was kann da hängen? [image: 1762365722744-b265638c-f0f7-4b6d-8c82-2fc33dd4b92f-image.png] [image: 1762365740536-a5b4c176-808e-413c-8d65-4963801abb79-image-resized.png] /****************************************************************************************************** * JavaScript Adapter Script for Log-Parser-Adapter: https://github.com/Mic-M/ioBroker.logparser * -------------------------------------------------------------- * Purpose: Get the history of all spoken Alexa commands into the log * Required: Alexa adapter - https://github.com/Apollon77/ioBroker.alexa2 * Source: https://github.com/Mic-M/ioBroker.logparser * Autor: Mic-M (Github) | Mic (ioBroker) * -------------------------------------------------------------------------------------- * Change Log: * 1.0.0 ciddi89 * Added try/catch to fix error * 1.0.0 Mic-M * Adoption to Log Parser Adapter * 0.1.0 Mic-M * Initial release (for Log Script - https://github.com/Mic-M/iobroker.logfile-script). ******************************************************************************************************/ /****************************************************************************************************** * Script Settings ******************************************************************************************************/ // **Capitalize First Letters** // The spoken command to Alexa is being returned in lowercase, like "turn on kitchen light". // With this option set to true, the output will be "Turn On Kitchen Light" (so capitalized first letter of each word) const g_capFirstLetters = true; /****************************************************************************************************** * End of Script Settings. Please do not change anything below here. ******************************************************************************************************/ main(); function main() { // All Alexa adapter instances, so alexa2.0.History.json, alexa2.1.History.json, alexa2.2.History.json, etc. on({ id: /^alexa2\.\d\.History\.json$/, change: 'any' }, function (obj) { try { // obj.state.val: JSON string of oject. // Like: {"name":"Alexa Flur","serialNumber":"xxxxxxxxxx","summary":"Wohnlicht an","creationTime":1582843794820, ... } let objHistory = JSON.parse(obj.state.val); // ignore alexa keywords or empty value. if (!['', 'alexa', 'echo', 'computer', 'ziggy'].includes(objHistory['summary'])) { // ignore "sprich mir nach" if (!objHistory['summary'].includes('sprich mir nach ')) { log('[Alexa-Log-Script] ##{"message":"' + formatAlexaSummary(objHistory['summary']) + '", "from":"' + objHistory['name'] + '"}##'); } } } catch (error) { log(error); } }); } /** * Formats the Alexa summary text accordingly. * @param {string} summaryText The summary text * @return {string} the formatted summary */ function formatAlexaSummary(summaryText) { if (g_capFirstLetters) summaryText = summaryText.replace(/(^|\s)\S/g, (l) => l.toUpperCase()); // Capitalize if set. https://stackoverflow.com/questions/2332811/ return summaryText; } EDIT: komisch, nachdem ich den Adapter auf Debug und wieder retour auf Info stellte, gehts wieder
  • Test Adapter Z-Wave 2 v3.0.x

    test z-wave zwave2
    187
    1
    0 Stimmen
    187 Beiträge
    34k Aufrufe
    PackElendP
    @martin_olw said in Test Adapter Z-Wave 2 v3.0.x: Hoppe eFenstergriff ConnectSense, Die habe ich auch 🙂 zusammen mit Shelly Wave Shutter. Ist bei dir die Batterielaufzeit gut, ich habe das Gefühl das die original Batterie nach nem Jahr leer ist. Ich habe sie noch nicht richtig eingebunden, auch wegen fehlenden Weiterentwicklung des adapters (beim maintainer gab es meines Wissens familiäre Änderungen, da ist die Zeit knapp) Wenn ich die zeit finde mache ich den Wechsel auf https://github.com/z-wave-JS und binde es über MQTT ein
  • Test DreameHome: 3D-Visualisierung und Alexa-Steuerung

    86
    4
    2 Stimmen
    86 Beiträge
    7k Aufrufe
    M
    moin moin, was ich bei dem anderem Dreame Adapter noch gesehen habe: unter "status" sind noch folgende Werte abrufbar: total-clean-times total-clean-time total-clean-area first-clean-time gibt es auch noch, der muss aber nicht wirklich sein. Ggf. kannst du die Werte ja einbauen. Leider zeigt bei mir CurrentRoomCleaningName und CurrentRoomCleaningNumber nichts an. Liegt wohl bei mir an der nicht lesbaren Karte. Die Raumreiniegung starte ich über StartCustom [ { "piid": 1, "value": 18 }, { "piid": 10, "value": "{\"selects\":[[XXX,1,1,2,1]]}" } ] wobei XXX dann die Raum ID ist. Wiederholungen, Saugleistung und Saugen oder wischen lässt sich beim D10 Plus GEN2 nicht übertragen. Da nimmt er die Werte, welche in der APP als "standart" hinterlegt sind.
  • 8 Stimmen
    266 Beiträge
    49k Aufrufe
    icebearI
    Nur der Vollständigkeit halber, ich hab jetzt eine für mich akzeptable Lösung gefunden, mit der ich leben kann. [image: 1762339273613-flexchart_bat_work.png] Und hier das Script: // Importiere javascript-stringify const stringify = require('javascript-stringify'); // --- KONFIGURATION --- const INFLUXDB_INSTANCE = 'influxdb.0'; const FLEXCHARTS_TARGET_STATE = '0_userdata.0.Flexcharts.Battery24Hours'; // --- ZEIT-KONFIGURATION --- const TIME_RANGE_MS = 24 * 3600 * 1000; // 24 Stunden (00:00 bis 24:00) const STEP_5M_MS = 5 * 60 * 1000; // 5 Minuten Auflösung für die Leistung (Basis für X-Achse) const STEP_1M_MS = 60 * 1000; // 1 Minuten Auflösung für den Ladestand (Basis: 60s Speicherung) // --- FIXE LEISTUNGSGRENZEN (kW) --- const MAX_DISCHARGE_KW = 2.5; // 2500 W Entladung (negativer Achsenbereich) const MAX_CHARGE_KW = 1.25; // 1250 W Ladung (positiver Achsenbereich) // ------------------------------------ // Definiere die Datenpunkte und ihre Zuordnung zu den Y-Achsen const DATAPOINTS_CONFIG = [ // 0. Ladung (kW) - GRÜN { id: 'senec.0.ENERGY.GUI_BAT_DATA_POWER', name: 'Laden (kW)', type: 'bar', color: '#2ECC40', is_charge: true, yAxisIndex: 0, unit: 'kW', step_ms: STEP_5M_MS, smooth: false, lineWidth: 3 }, // 1. Entladung (kW) - ROT { id: 'senec.0.ENERGY.GUI_BAT_DATA_POWER', name: 'Entladen (kW)', type: 'bar', color: '#FF4136', is_discharge: true, yAxisIndex: 0, unit: 'kW', step_ms: STEP_5M_MS, smooth: false, lineWidth: 3 }, // 2. Ladestand (%) - BLAU { id: 'senec.0.ENERGY.GUI_BAT_DATA_FUEL_CHARGE', name: 'Ladestand', type: 'line', color: '#0040ff', is_fuel_charge: true, yAxisIndex: 0, unit: '%', step_ms: STEP_1M_MS, smooth: true, lineWidth: 2 }, ]; // ---------------------------------------------------------------------- // Starte das Skript alle 5 Minuten schedule('*/5 * * * *', async () => { await createAndSetChartData(); }); // Starte das Skript sofort beim Start createAndSetChartData(); /** * Hilfsfunktion zum Abfragen der History-Daten. */ function queryHistoryData(id: string, options: any): Promise<any> { return new Promise<any>((resolve, reject) => { sendTo(INFLUXDB_INSTANCE, 'getHistory', { id: id, options: options }, (result) => { if (result && (result as any).error) { reject(new Error(result.error)); } else { resolve(result); } }); }); } /** * Normalisiert die History-Antwort. */ function extractDataArray(result: any): any[] { if (Array.isArray(result)) return result; if (result && Array.isArray(result.result)) return result.result; if (result && Array.isArray(result.data)) return result.data; return []; } /** * Generiert alle X-Achsen Zeitstempel von 00:00 bis 23:55 (heute). */ function generateFullDayTimestamps(startOfTodayMs: number): string[] { const timestamps: string[] = []; const endOfTodayMs = startOfTodayMs + TIME_RANGE_MS; // Die X-Achse bleibt bei 5-Minuten-Schritten für die Übersichtlichkeit for (let ts = startOfTodayMs; ts < endOfTodayMs; ts += STEP_5M_MS) { const date = new Date(ts); timestamps.push(date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })); } return timestamps; } // Hauptfunktion zur Abfrage der Daten und Erstellung des Charts async function createAndSetChartData(): Promise<void> { // 1. ZEITRAUM BERECHNUNG const now = Date.now(); const todayStart = new Date().setHours(0, 0, 0, 0); const endTimeHistory = now; // Erzeuge die X-Achse für den kompletten Tag (00:00 bis 23:55) const fullDayTimestamps = generateFullDayTimestamps(todayStart); try { // --- 2. DATENABFRAGE --- // Wir fragen nur die UNIQUE IDs ab, um History-Anfragen zu sparen const uniqueIds = Array.from(new Set(DATAPOINTS_CONFIG.map(dp => dp.id))); const historyPromises = uniqueIds.map(id => { const dpConfig = DATAPOINTS_CONFIG.find(dp => dp.id === id); // Nutze die erste Konfiguration zur Optionsbestimmung const step_ms = dpConfig.is_fuel_charge ? STEP_1M_MS : STEP_5M_MS; const historyOptions = { start: todayStart, end: endTimeHistory, aggregate: 'average', step: step_ms, limit: 5000, removeBorderValues: false }; return queryHistoryData(id, historyOptions).catch(e => { log(`FEHLER bei History-Abfrage ${id}: ${e.message}`, 'error'); return { result: [] }; }); }); const historyResults = await Promise.all(historyPromises); // Mappe Ergebnisse zu IDs const historyResultsMap = new Map<string, any>(); uniqueIds.forEach((id, index) => { historyResultsMap.set(id, extractDataArray(historyResults[index])); }); // 3. DATEN MAPPING & TRANSFORMATION // Leistung (kW) und Ladestand (%) Daten separat aus den History-Ergebnissen extrahieren const rawPowerData = historyResultsMap.get('senec.0.ENERGY.GUI_BAT_DATA_POWER') || []; const rawChargeData = historyResultsMap.get('senec.0.ENERGY.GUI_BAT_DATA_FUEL_CHARGE') || []; const powerDataByTimeKey = new Map<number, number>(); rawPowerData.forEach((entry: any) => { if (entry.val !== null && typeof entry.val === 'number') { // WATT ZU KILOWATT KONVERTIERUNG powerDataByTimeKey.set( Math.floor(entry.ts / STEP_5M_MS) * STEP_5M_MS, parseFloat((entry.val / 1000).toFixed(2)) ); } }); const chargeDataByTimeKey = new Map<number, number>(); let lastValidChargeValue: number | null = 0; rawChargeData.forEach((entry: any) => { if (entry.val !== null && typeof entry.val === 'number') { chargeDataByTimeKey.set( Math.floor(entry.ts / STEP_1M_MS) * STEP_1M_MS, parseFloat(entry.val.toFixed(2)) ); } }); // ZWEI neue Leistungs-Arrays + EIN Ladestand-Array const data_charge: (number | null)[] = []; // Grün: > 0 const data_discharge: (number | null)[] = []; // Rot: < 0 const data_fuel_charge: (number | null)[] = []; // Blau: % let currentTimelineMs = todayStart; for (let i = 0; i < fullDayTimestamps.length; i++) { const timeKey = Math.floor(currentTimelineMs / STEP_5M_MS) * STEP_5M_MS; const powerValue = powerDataByTimeKey.get(timeKey) || 0; // Standard 0 kW wenn kein Wert // --- 1. Leistung aufteilen --- if (currentTimelineMs > now) { // Zukunft: Daten beenden data_charge.push(null); data_discharge.push(null); } else { if (powerValue > 0) { data_charge.push(powerValue); data_discharge.push(0); } else if (powerValue < 0) { data_charge.push(0); data_discharge.push(powerValue); // Entladung ist negativ } else { data_charge.push(0); data_discharge.push(0); } } // --- 2. Ladestand hinzufügen --- const chargeTimeKey = Math.floor(currentTimelineMs / STEP_1M_MS) * STEP_1M_MS; let chargeValue = chargeDataByTimeKey.get(chargeTimeKey); if (currentTimelineMs <= now) { if (chargeValue !== undefined && chargeValue !== null) { lastValidChargeValue = chargeValue; } else { chargeValue = lastValidChargeValue; } data_fuel_charge.push(chargeValue); } else { data_fuel_charge.push(null); } currentTimelineMs += STEP_5M_MS; } // Füllen der seriesDataMap mit den aufgeteilten Daten const seriesDataMap = new Map<string, (number | null)[]>(); seriesDataMap.set('Laden (kW)', data_charge); seriesDataMap.set('Entladen (kW)', data_discharge); seriesDataMap.set('Ladestand', data_fuel_charge); // Asymmetrische Achsengrenzen mit Puffer (0.5 kW) const maxLimit = MAX_CHARGE_KW + 0.5; const minLimit = MAX_DISCHARGE_KW + 0.5; // *** TRANSFORMATION DES LADESTANDS *** const fuelChargeData = seriesDataMap.get('Ladestand'); if (fuelChargeData) { const transformedData = fuelChargeData.map(val => { if (val === null) return null; // Skalierung von 0 bis maxLimit für Ladestand return (val / 100) * maxLimit; }); seriesDataMap.set('Ladestand (Skaliert)', transformedData); } // ************************************ // 4. CHART-DEFINITION (ECharts Option) const series = DATAPOINTS_CONFIG.map(dp => { const seriesName = dp.is_fuel_charge ? 'Ladestand (Skaliert)' : dp.name; const seriesData = seriesDataMap.get(seriesName) || []; const seriesConfig: any = { name: dp.name, type: dp.type, yAxisIndex: 0, data: seriesData, itemStyle: { color: dp.color }, lineStyle: { width: dp.lineWidth || 1 }, smooth: dp.smooth || false, showSymbol: false, z: dp.is_fuel_charge ? 10 : 1, // Animation für Säulen (type: 'bar') animationDelay: dp.type === 'bar' ? function (idx: number) { return idx * 10; } : undefined, animationDelayUpdate: dp.type === 'bar' ? function (idx: number) { return idx * 5; } : undefined, }; return seriesConfig; }); // Wir verwenden nur noch EINE Y-Achse const yAxis = [ { type: 'value', name: 'Leistung (kW)', min: -minLimit, // -3.0 kW max: maxLimit, // 1.75 kW axisLabel: { formatter: (value: number) => value.toFixed(1) + ' kW', color: '#FFFFFF' }, axisLine: { onZero: true, lineStyle: { color: '#888' } }, splitLine: { show: true, lineStyle: { color: '#333333' } } } ]; // *** VISUAL MAP IST HIER ENTFERNT *** const option = { backgroundColor: 'rgb(17, 18, 23, 0.0)', title: { text: 'Batterieübersicht (Laden/Entladen & Ladestand)' }, // visualMap fehlt hier absichtlich tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, formatter: function (params: any[]) { let res = params[0].name + '<br/>'; const chargeMax = maxLimit; // Sortiere die Tooltip-Einträge: Ladestand zuletzt, dann Entladen, dann Laden params.sort((a, b) => { const nameA = a.seriesName; const nameB = b.seriesName; if (nameA.includes('Ladestand')) return 1; if (nameB.includes('Ladestand')) return -1; if (nameA.includes('Entladen')) return 1; // Entladen vor Laden if (nameB.includes('Entladen')) return -1; return 0; }); params.forEach((item: any) => { const dpConfig = DATAPOINTS_CONFIG.find(d => d.name === item.seriesName); if (dpConfig && item.value !== null && item.value !== 0) { // Null- oder Nullwerte der unsichtbaren Serie ignorieren let value = item.value; let seriesName = item.seriesName; let unit = dpConfig.unit; if (dpConfig.is_fuel_charge) { value = (value / chargeMax) * 100; seriesName = 'Ladestand'; unit = '%'; } else { // Ladung/Entladung werden mit ihren echten Namen angezeigt unit = 'kW'; } res += item.marker + seriesName + ': **' + value.toFixed(dpConfig.unit === 'kW' ? 2 : 1) + ' ' + unit + '**<br/>'; } }); // Wenn keine Leistung angezeigt wird, zeige nur Ladestand an if (res.endsWith('<br/>')) { res = res.substring(0, res.lastIndexOf('<br/>')); } return res; } }, legend: { data: DATAPOINTS_CONFIG.map(item => item.name), bottom: 0, textStyle: { color: '#FFFFFF' } }, grid: { left: '2%', right: '5%', bottom: '15%', top: '10%', containLabel: true }, xAxis: { type: 'category', data: fullDayTimestamps, axisLabel: { color: '#FFFFFF' } }, yAxis: yAxis, series: series, animationEasing: 'elasticOut', }; // 5. SPEICHERUNG IM FLEXCHARTS-STATE setState(FLEXCHARTS_TARGET_STATE, stringify.stringify(option), true); log(`ECharts-Optionen (Bar/Bar/Line Kombi mit Farb-Fix) erfolgreich in State ${FLEXCHARTS_TARGET_STATE} geschrieben.`, 'info'); } catch (e) { log(`Allgemeiner Fehler im Skript oder bei der History-Abfrage: ${e.message}`, 'error'); } }
  • Test Adapter Omada TP-Link

    81
    4
    3 Stimmen
    81 Beiträge
    12k Aufrufe
    L
    sorry falsch
  • Test Dreame Home Adapter

    241
    4
    1 Stimmen
    241 Beiträge
    49k Aufrufe
    M
    mir ist aufgefallen das es gar keine Daten für die Hauptbürste gibt.
  • Tester gesucht: Zigbee 3.2.x

    3.2 alpha zigbee
    37
    11
    6 Stimmen
    37 Beiträge
    1k Aufrufe
    apollon77A
    Ich hab mal https://github.com/ioBroker/ioBroker.type-detector/issues/155 angelegt. Gern dort Meinungen rein hauen
  • Neuer Adapter pi-hole2 für pihole>=V6

    36
    7 Stimmen
    36 Beiträge
    3k Aufrufe
    OliverIOO
    Hier mal ein größerer Anwendungsfall für den pihole2 Adapter. Bitte um Rückmeldungen, positive wie negative Erfahrungen. Evtl baue ich das dann als widgets in den Adapter ein. Bitte auch die Verwendung des Adapters vis-jsontemplate 4.1.3 weiter unten beachten https://forum.iobroker.net/topic/31521/test-widget-json-template/32?_=1762189668521 Ich habe immer wieder den folgenden Anwendungsfall, der sich mit pihole Bordmitteln nicht so einfach lösen lässt. Ich habe immer wieder mal Seiten, die aufgrund eines pihole Blocks nicht funktionieren. Darüber hinaus funken hier mehrere Geräte wirklich sehr oft nach Hause, deren requests durch pihole geblockt werden. Dies verhindert leider effizient die Suche nach den betroffenen domains des blocks, der wieder aufgehoben werden soll, da das logfile relativ voll ist. Auch interessiert mich immer wieder mal, wer hier so nach Hause telefoniert. Diejenigen domains, die ich aber schon mal geprüft habe will ich nicht erneut prüfen. Daher nun der folgende Versuch einer Lösung mittels 2er Widgets die ich mit dem jsontemplate Adapter erstellt habe. Widget 1 listet alle domains absteigend nach vorkommen über alle clients dar. jede einzelne domain kann dann auf die Merkliste gesetzt werden. die Liste kann nach einem Suchbegriff oder Regex gefiltert werden. Mehrere Sichtbare domains können über ein Sammel-Checkbox ebenfalls in einem Schritt gemerkt werden. Das widfget besteht 2 Reitern, einmal alle gemerkten domains, sowie alle die Nicht gemerkt worden sind. [image: 1762209464861-2b733665-5606-4cb2-915a-a319c511ef3c-image.png] Die Konfiguration erfolgt im ersten Abschnitt des Templates // config area // adapter instance const pihole_adapter_instance = 0; //Nummer der Instanz const domain_count=200; //maximale Anzahl der abzufragenden domains const domain_blocked=true; //Abfrage der geblockten Domains im standard, Alternativ geht das auch mit den nicht geblockten domänen Das Template muss im entsprechenden Feld eingetragen werden. Zusätzlich muss noch ein Datenpunkt vom typ String angelegt werden und wie hier verknüpft werden. Darin werden die bekannten domänen gespeichert. [image: 1762208927464-9cf63a60-9a51-458d-b42a-c7a063d0b1aa-image.png] Template Spoiler <% // config area // adapter instance const pihole_adapter_instance = 0; const domain_count=200; const domain_blocked=true; %> <% //javascript code der per ejs interpretiert wird const knownDP = Object.keys(dp)[0] || ""; const knownValue = dp[knownDP] || "[]"; const knownValueSet = new Set(JSON.parse(knownValue)); const adapterinstance = "pi-hole2."+pihole_adapter_instance; //debugger; const cookieKey = widgetID+"selectedTab"; const selectedTab = localStorage.getItem(cookieKey)||"tab-all"; // Abruf der Daten vom pihole2 adapter const apiresult = await getTopDomains(domain_count,domain_blocked); // Aufbau Index const domainCountMap = new Map((apiresult.domains || []).map(d => [d.domain, d.count])); // Aufbau der Tabellendaten knownDomains const savedList = [...knownValueSet].map(domain => ({ domain, count: domainCountMap.get(domain) || 0 })); // Hilfsfunktion für async SendTo async function sendToAsync(instance, command, sendData) { return new Promise((resolve, reject) => { try { vis.conn.sendTo(instance, command, sendData, function (receiveData) { resolve(receiveData); }); } catch (error) { reject(error); } }); } //Hilfsfunktion Abruf der TopDomains blocked/not blocked async function getTopDomains(count,blocked) { const blockedText = blocked ? "true":"false"; return await sendToAsync(adapterinstance,"piholeapi", { method: 'GET', endpoint: "/stats/top_domains?count="+count+"&blocked="+blockedText, }); }; %> <style> .pihole.select .tabs { display: flex; gap: .1rem; } .pihole.select .tabs button { padding: .4rem .8rem; border: 1px solid #ccc; background: #f7f7f7; cursor: pointer; } .pihole.select .tabs button.active { background: #e9eefc; border-color: #8aa3ff; } .pihole.select .tabpanel { display: none; } .pihole.select .tabpanel.active { display: block; } .pihole.select table { width: 100%; border-collapse: collapse; } .pihole.select th, .pihole.select td { padding: .3rem .4rem; xborder-bottom: 1px solid #eee; } .pihole.select .check { text-align: center; width: 10%; } .pihole.select .domain { text-align: left; xwidth: 70%; display: flex; align-items: center; gap: .4rem; /* optional */ } .pihole.select .domain .filter-all { flex: 1; /* <-- restliche Breite */ min-width: 0; /* wichtig für Firefox/Edge */ box-sizing: border-box; } .pihole.select .count { text-align: end; width: 20%; } .muted { color: #888; font-size: .9em; } .pihole.select th .filter { display:block; margin-top:.25rem; } .pihole.select th .filter input { width:100%; box-sizing:border-box; padding:.25rem .35rem; border:1px solid #ccc; border-radius:4px; font-size:.9rem; } .pihole.select th .filter small { color:#666; } </style> <div class="pihole select"> <div class="tabs" role="tablist"> <button type="button" role="tab" aria-controls="tab-all" class="tabbtn <%= selectedTab=="tab-all"?"active":""%>">Alle Domains</button> <button type="button" role="tab" aria-controls="tab-saved" class="tabbtn <%= selectedTab=="tab-saved"?"active":""%>">Gemerkte Domains</button> </div> <div id="tab-all" class="tabpanel <%= selectedTab=="tab-all"?"active":""%>" role="tabpanel" aria-label="Alle Domains"> <table data-table="all"> <thead> <tr> <th class="check"> <span><input class="bulk" type="checkbox" data-action="check" checked></span> </th> <th class="domain"> <span>domain</span> <input type="text" autofocus value="" class="filter-all"> </th> <th class="count"><span>#</span></th> </tr> </thead> <tbody> <% (apiresult.domains || []).forEach(domain => { if (!knownValueSet.has(domain.domain)) { %> <tr data-domain="<%= domain.domain %>"> <td class="check"> <input type="checkbox" data-id="<%= domain.domain %>"> </td> <td class="domain"><%= domain.domain %></td> <td class="count"><%= domain.count %></td> </tr> <% } }) %> </tbody> </table> </div> <div id="tab-saved" class="tabpanel <%= selectedTab=="tab-saved"?"active":""%>" role="tabpanel" aria-label="Gemerkte Domains"> <table data-table="saved"> <thead> <tr> <th class="check"> <input class="bulk" type="checkbox" data-action="uncheck"> </th> <th class="domain"> <span>domain</span> <input type="text" autofocus value="" class="filter-all"> </th> <th class="count">#</th> </tr> </thead> <tbody> <% savedList.forEach(item => { %> <tr data-domain="<%= item.domain %>"> <td class="check"> <input type="checkbox" data-id="<%= item.domain %>" checked> </td> <td class="domain"> <%= item.domain %> <% if (!domainCountMap.has(item.domain)) { %> <span class="muted">(derzeit nicht in Liste)</span> <% } %> </td> <td class="count"><%= item.count %></td> </tr> <% }) %> </tbody> </table> </div> </div> <script> //Javascript code der im browser läuft (async () => { // Funktionen für Tabs umschalten const cookieKey = "<%= widgetID %>selectedTab"; const selectedTab = localStorage.getItem(cookieKey)||"tab-all"; const $tabs = $(".tabbtn"); const $panels = $(".tabpanel"); $tabs.on("click", function() { //debugger; $tabs.removeClass("active"); $(this).addClass("active"); const id = $(this).attr("aria-controls"); $panels.removeClass("active"); $("#"+id).addClass("active"); localStorage.setItem(cookieKey,id); }); // Daten aus den Datenpunkt wird ins Skriptn übernommen //debugger; const knownDP = "<%- knownDP %>"; const initial = JSON.parse(atob("<%= btoa(knownValue) %>")); const knownSet = new Set(initial); // Helpers function writeDP() { //debugger; vis.setValue(knownDP, JSON.stringify(Array.from(knownSet))); } function makeRow(domain, count, checked=true) { return $(` <tr data-domain="${domain}"> <td class="check"><input type="checkbox" data-id="${domain}" ${checked ? "checked":""}></td> <td class="domain">${domain}</td> <td class="count">${Number.isFinite(count) ? count : 0}</td> </tr> `); } // Eventhandler Merken einer Domain bzw auch wieder entfernen $('.pihole').on("change", 'tbody .check input', function(evt) { const id = $(evt.target).data("id"); const isChecked = $(evt.target).is(":checked"); const $row = $(evt.target).closest('tr'); //hinzufügen oder entfernen der gewählten informationen if (isChecked) { // nach "saved" verschieben knownSet.add(id); // Count aus aktueller Zeile holen const cnt = parseInt($row.find('.count').text(), 10) || 0; // In saved hinzufügen, falls noch nicht vorhanden if ($('[data-table="saved"] tr[data-domain="'+id+'"]').length === 0) { $('[data-table="saved"] tbody').append(makeRow(id, cnt, true)); } else { // sicherstellen, dass dort gecheckt ist $('[data-table="saved"] tr[data-domain="'+id+'"] input[type="checkbox"]').prop('checked', true); } // Falls Änderung aus "all" kam: Zeile dort entfernen (weil nun gemerkt) if ($row.closest('table').attr('data-table') === 'all') { $row.remove(); } } else { // -> zurück nach "all" verschieben knownSet.delete(id); // Count aus aktueller Zeile holen const cnt = parseInt($row.find('.count').text(), 10) || 0; // In all hinzufügen, falls noch nicht vorhanden if ($('[data-table="all"] tr[data-domain="'+id+'"]').length === 0) { $('[data-table="all"] tbody').append(makeRow(id, cnt, false)); } else { // sicherstellen, dass dort ungecheckt ist $('[data-table="all"] tr[data-domain="'+id+'"] input[type="checkbox"]').prop('checked', false); } // Falls Änderung aus "saved" kam: Zeile dort entfernen if ($row.closest('table').attr('data-table') === 'saved') { $row.remove(); } } writeDP(); }); // Ereignishandler zum hinzufügen/entfernen aller sichtbaren Elemente $('.pihole').on('click', 'thead .bulk', function() { //debugger; const action = $(this).data('action'); // "check" | "uncheck" const $table = $(this).closest('table'); const ids = $table.find('tbody tr:visible .check input').map((_,el)=>$(el).data('id')).get(); //debugger; if (!ids.length) return; if (action === 'check') { ids.forEach(id => knownSet.add(id)); } else { ids.forEach(id => knownSet.delete(id)); } writeDP(); }); // Domain Filter const filterKey = "<%= widgetID %>filterAll"; const $filter = $('.filter-all'); const $rowsAll = $('[data-table="all"] tbody tr'); const $visCount = $('#all-visible-count'); //Funktion zum filtern function applyAllFilter(qRaw,table) { //debugger; const q = (qRaw || '').trim(); let shown = 0; // Modus erkennen: /regex/ oder Wildcards let isRegex = false, re = null; if (q.startsWith('/') && q.endsWith('/') && q.length > 2) { try { re = new RegExp(q.slice(1, -1), 'i'); isRegex = true; } catch(e) { /* fallback unten */ } } if (!isRegex) { // * als Wildcard; rest escapen const esc = q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*'); re = new RegExp(esc, 'i'); } $('[data-table="'+table+'"] tbody tr').each((_, tr) => { const dom = tr.getAttribute('data-domain') || tr.querySelector('.domain')?.textContent || ''; const match = q === '' ? true : re.test(dom); tr.style.display = match ? '' : 'none'; if (match) shown++; }); $visCount.text(shown); } // Entprellen der Eingabe let fto; $filter.on('input', function() { const v = this.value; clearTimeout(fto); const table = $(this.closest("table")).data("table"); fto = setTimeout(() => { localStorage.setItem(filterKey, v); applyAllFilter(v,table); }, 120); }); // Beim Umschalten auf den Tab „Alle“ Filter erneut anwenden (falls DOM neu geändert wurde) $('.tabbtn[aria-controls="tab-all"]').on('click', () => applyAllFilter($filter.val())); // Persistierten Filter laden & anwenden const initialFilter = localStorage.getItem(filterKey) || ""; $filter.val(initialFilter); applyAllFilter(initialFilter); })(); </script> Widget2 Dieses Widget zeigt alle geblockten requests an. Auch hier können ein paar Konfigurationen vorgenommen werden. Aufgrund dieser Lösung wurde noch ein kleiner glitch im jsontemplate entdeckt. Daher muss hier die Version 4.1.3 verwendet werden, ansonsten werden im 2.widget die Liste nicht korrekt dargestellt. [image: 1762209540229-d6c55515-a712-475f-80b8-2f417be28632-image.png] Das Template muss im entsprechenden Feld eingetragen werden. Zusätzlich muss noch ein Datenpunkt vom typ String angelegt werden und wie hier verknüpft werden. Darin werden die bekannten domänen gespeichert. <% // config area // adapter instance const pihole_adapter_instance = 0; //Adapter Instanz const blocked = true; //Wie oben, ob geblockte oder nicht geblockte requests abgerufen werden soll const request_count=500; // Anzahl der letzten requests, also ab dem jetzigen Zeitpunkt die letzten 500 requests. %> [image: 1762209687123-943fa6bf-e513-494e-a836-249b47749fb1-image.png] Template Spoiler <% // config area // adapter instance const pihole_adapter_instance = 0; const blocked = true; const request_count=500; %> <% const knownDP = Object.keys(dp)[0] || ""; const knownValue = dp[knownDP] || "[]"; %> <style> .pihole.requests { font-size: 0.8em; } .pihole.requests .col { text-align: left; } .pihole.requests .col.time { width: 15%; } .pihole.requests .col.status { width: 5%; } .pihole.requests .col.type { width: 5%; } .pihole.requests .col.domain { width: 20%; } .pihole.requests .col.client_name { width: 20%; } .pihole.requests .col.client_ip { width: 10%; } </style> <div class="pihole requests"> <div> <select class="clientsb" name="clients"> <option value="">no Clients</option> </select> </div> <table data-table="all"> <thead> <tr> <th class="col time">Time</th> <th class="col status">Status</th> <th class="col type">Type</th> <th class="col domain">Domain</th> <th class="col client_name">Client Name</th> <th class="col client_ip">Client IP</th> </tr> </thead> <tbody> </tbody> </table> </div> <script> //Javascript code der im browser läuft (async () => { const pihole_adapter_instance = 0; const adapterinstance = "pi-hole2."+pihole_adapter_instance; const blocked = <%- blocked %>; const request_count=<%- request_count %>; const knownDP = "<%- knownDP %>"; const knownValue = atob("<%= btoa(knownValue) %>"); const knownValueSet = new Set(JSON.parse(knownValue)); let fto; let $table=$(".pihole.requests tbody"); await fillClients(); function fillTable() { let table=$(".pihole.requests tbody"); } async function fillClients() { const key = "<%= widgetID %>selectedclient"; const selectedClient = localStorage.getItem(key)||""; let clients = await getClients(); let $clientselect = $(".pihole.requests .clientsb"); const clientlist = Object.entries(clients.clients) .map(([ip, v]) => ({ ip, ...v })) .sort((a, b) => b.total - a.total); $clientselect.empty(); $clientselect.append($('<option value="" '+isSelected(selectedClient,"")+'>nothing selected</option>')); clientlist.map(el=>$clientselect.append($('<option value="'+el.ip+'" '+isSelected(selectedClient,el.ip)+'>'+el.name+'/'+el.ip+'</option>'))); let that=this; $clientselect.on('change', function() { const v = this.value; clearTimeout(fto); fto = setTimeout(() => { localStorage.setItem(key, v); applySelect(v); }, 120); }); if (selectedClient) { applySelect(selectedClient); } async function applySelect(client) { let requests = await getFilteredRequests(client /*"192.168.1.232"*/,request_count); $table.empty(); requests.queries .filter(req=>!knownValueSet.has(req.domain)) .map(req=>$table.append(makeRow(req))); } } function findIPv4(adresses) { let ips = adresses||[]; return (adresses||"").split(",").filter(ip=>ip.split(".").length-1)[0]||""; } function isSelected(v1,v2) { return (v1==v2) ? "selected":""; } function makeRow(item) { return $(` <tr> <td class="col time">`+ formatDate(item.time)+`</th> <td class="col status">`+ item.status+`</th> <td class="col type">`+ item.type+`</th> <td class="col domain">`+ item.domain+`</th> <td class="col client_name">`+ item.client.name+`</th> <td class="col client_ip">`+ item.client.ip+`</th> </tr> `); } async function getData(client) { if (!client) { client = clients[0]; } return await getFilteredRequests(client /*"192.168.1.232"*/,request_count); } async function sendToAsync(instance, command, sendData) { return new Promise((resolve, reject) => { try { vis.conn.sendTo(instance, command, sendData, function (receiveData) { resolve(receiveData); }); } catch (error) { reject(error); } }); } async function getClients() { return await sendToAsync(adapterinstance,"piholeapi", { method: 'GET', endpoint: "/history/clients?N=200", }); }; async function getFilteredRequests(client_ip,count) { return await sendToAsync(adapterinstance,"piholeapi", { method: 'GET', endpoint: "/queries?client_ip="+client_ip+"&upstream=blocklist&order%5B0%5D%5Bdir%5D=desc&start=0&length="+count, }); }; function formatDate(ts) { const date = new Date(ts * 1000); return date.toISOString(); } })(); </script>
  • ioBroker App 2023 [Android & iOS] - jetzt erhältlich

    app iobroker visu jarvis lovelace material vis
    938
    1
    22 Stimmen
    938 Beiträge
    288k Aufrufe
    W
    Hänge mich jetzt auch mal hier dran. Habe ein Samsung A7 seit drei Jahren hier liegen und heute in Betrieb genommen. Nach 6-7 Updates ist es nun bei Android 13 angekommen. Jungfräulich. ioBroker App 1.4.2 installiert. Jetzt bekomme ich nur noch die Meldung, SSL Error. Ja, da steht, mit selbst signiert geht nicht. Ich habe im Android mein root Zertifikat (ca) hinterlegt. Ich würde nun erwarten, dass die App mein Zertifikat nun als rechtens empfindet? Oder wird eine andere Web engine genutzt und ich muss es woanders hinterlegen? Oder was muss ich sonst machen? Kann doch nicht der Einzige mit dem Problem sein? Danke :confused: Weissnicht
  • Test Adapter HeatingControl v2.12.x

    Verschoben heating
    3k
    1
    14 Stimmen
    3k Beiträge
    2m Aufrufe
    D
    Hallo, irgendwie konnte ich nicht das richtige finden. Ich habe gerade neue Zigbee Festersensoren installiert. Wollte diese nun den Thermostaten zuordnen und dem Zimmer. Beim automatischen Suchen findet erst keine. Es muss ja ein RAUM und Funktion zugeordnet werden. Habe ich, denke das es die falsche Funktion ist. Welches ist dir richtig? Cu Deta
  • Test Adapter wireless-mbus v0.10.x

    mbus wireless wm-bus wmbus
    568
    2 Stimmen
    568 Beiträge
    191k Aufrufe
    I
    Hallo, für den Fall, dass mal noch wer anders das mit dem HCA e2 versucht, hier ein Update, da es am Ende geklappt hat. Mit der Einstellung "Kurzes Telegramm" konnten zunächst auch keine Signale empfangen und auch im C-Mode ging es weder mit kurzem noch langem Telegramm. Bei einem Kollegen konnte ich aber erfolgreich die Signale seiner Qundis-HKVs empfangen. Ich hab den HCA e2 dann noch eine Chance gegeben, da sie deutlich besser in der Sendehäufigkeit konfigurierbar sind, also praktisch beliebig wenig Signale, was auch die Akku schont (die Qundis senden 10 Stunden am Tag alle 2 min, was ich für meine Anwendung nicht brauche). Schließlich hat geholfen, die IMST GmbH vom Stick iU891A-XL anzuschreiben. Die haben mir ein Firmware-Update (beta-Version) zum Testen gegeben und tatsächlich konnte ich damit direkt Signale im C-Mode empfangen, sowohl mit kurzem als auch mit langem Telegramm. Auch verschlüsselt hat es mit dem wireless m-bus-Adapter funktioniert. Die Signalstärke der HCA e2 ist auch ziemlich gut, reicht vom 3. Stock in den Keller. Und falls das noch für jemanden interessant sein könnte, auch mit folgender kostenloser Software ging das Auslesen (auch verschlüsselt) problemlos: https://www.m-bus.de/software.php Viele Grüße
  • Test Adapter hoymiles-ms v0.2.x

    hoymiles-ms mcm1957
    26
    2 Stimmen
    26 Beiträge
    1k Aufrufe
    mcm1957M
    @mcm1957 said in Test Adapter hoymiles-ms v0.2.x: @andabraha Danke kanns mir wahracheinlich erst morgen abends od Sa ansehen. Sorry Sorry - gestern null Kapazität. Und so wies heute aussieht eher auch nicht (Familie / Enkel sind da). Ich schau aber dass ich asap ne Alpha bau wo das abgefangen wird.
  • Test Adapter ioBroker.n8n

    28
    1
    6 Stimmen
    28 Beiträge
    2k Aufrufe
    B
    @bluefox said in Test Adapter ioBroker.n8n: Was ist unklar, was fehlt, was wäre noch nützlich? Moin. Ich habe jetzt doch einen Punkt, der mir nicht klar ist. Bisher habe ich nicht mit Kategorien, Räumen und Funktionen gearbeitet. Der Abruf von "iobroker Devices" über den n8n-Workflow erfordert aber, dass die Geräte dort gepflegt sind. Das habe ich auch gemacht und es funktioniert alles super! ABER: Wenn ich im iobroker.iot - Adapter (neuste Version gerade installiert) unter "Intelligente Aufzählung" Funktionen (z.B. Licht) deaktiviere, werden mir diese Geräte auch beim Abruf von Geräten über den n8n-Workflow nicht mehr angezeigt. (?) Das kann ich mir nicht erklären. Eigentlich sollte das doch nichts miteinander zu tun haben, oder? Ich möchte die Geräte zwar im n8n-Adapter verwenden, nicht aber an Alexa weitergeben. EDIT: Das funktioniert nun, wenn ich in iobroker.iot --> "Intelligente Aufzählung" die Funktion aktiviere , die Räume aber deaktiviere. Dann werden die Geräte in n8n ordentlich abgerufen, stehen aber als Alexa-Geräte nicht zur Verfügung. Trotzdem verstehe ich die Logik dahinter nicht. Danke für eine kurze Info (Bug oder Missverständnis?) und Gruß, Bastian
  • Test adapter tractive-gps 2.0.x

    mcm1957 tractive-gps
    14
    0 Stimmen
    14 Beiträge
    977 Aufrufe
    MedizinPumPerM
    @mcm1957 Ich hab deine Empfehlung mal aufgegriffen und einen PR hereingegeben

598

Online

32.4k

Benutzer

81.4k

Themen

1.3m

Beiträge