Weiter zum Inhalt

Hardware

5.6k Themen 88.3k Beiträge

Hardware für ioBroker oder Einbindung von Hardware in ioBroker

NEWS

  • Wall Display

    6
    0 Stimmen
    6 Beiträge
    83 Aufrufe
    M
    Ich habe ja auch lange mit den verschiedensten Tablets experimentiert und bin am Ende bei einem uralten INTEL NUC mit Windows 10 angekommen, an dem ein 15 Zoll Touchscreen angeschlossen ist. Der NUC läuft durch, der Screen schaltet nach einer Weile dunkel und wird durch einen HM Bewegungssensor wieder aufgeweckt. Auf dem NUC läuft nur ein Browser und ein SIP Client für die Gegensprechanlage der Torstation. Und es gibt keinen Ärger oder Aufwand wegen dem Akku. Seit ich mir die Screens für VIS von der KI erstellen lasse (vielen Dank @ro75 für den Hinweis), habe ich auch keine Sorgen mehr wegen der Performance bei zu vielen Widgets, weil einfach keine benutzt werden. Zum Schluss sieht es dann so aus: [image: grafik.jpeg] [image: grafik.jpeg]
  • Landis+Gyr ULTRAHEAT T550 mit Wemos D1 mini

    21
    -1 Stimmen
    21 Beiträge
    4k Aufrufe
    M
    Hallo, Ich habe auch einen Hichi Lese-Schreibkopf (den Wispr Wifi). Nach Einrichten von WLAN (erst Accesspoint, dann WLAN eingragen) dann Weboberfläche: dort unter Tools, Script: Haken setzten und einfach folgendes Skript einkopieren (keine Vorlage auswählen) und auf speichern klicken. Wird bei anderen Tasmota IR-Schrei/Leseköpfen ähnlich sein. >D scnt=-1 res=0 done=0 >B ->sensor53 r >S ; jede Sekunde: einmal täglich in der Stunde 03:xx die Abfrage anstoßen if hours==3 then if done==0 then done=1 scnt=0 endif else done=0 endif >F ; alle 100 ms, aktiv nur während einer Abfrage (scnt>=0) if scnt>=0 then scnt+=1 switch scnt case 10 res=sml(1 0 300) res=sml(1 1 "0000000000000000000000000000000000000000") res=sml(1 1 "0000000000000000000000000000000000000000") res=sml(1 1 "2F3F210D0A") case 28 res=sml(1 0 2400) case 600 scnt=-1 ends endif >M 1 +1,3,o,0,2400,T550,1 1,=so2,0 1,6.8(@1,Zählerstand,MWh,kwh_count,3 1,6.6(@1,Max. Heizleistung,kWh,kwh_max_heatperf,1 1,6.26(@1,Durchlauf,m³,m3_throughput,2 1,6.33(@1,Max. Durchlauf per h,m³h,m3h_max_throughput,3 1,9.4(@1,Max. Temp. V-/R-Lauf,°C,c_max_temp_fb,1 # Das Script liest einmal am Tag den Zählerstand. Der Ultraheat T550 hat eine Batterie, daher habe ich das Beschränkt. Wer will, kann das im Block nach dem >S ändern. Bei meiner Variante ist es um 3:00 Uhr. Zum Einrichten / Testen kann man das auf die aktuelle Stunde anpassen und speichern, dann wird der Wert sofort ausgelesen. Danach wieder zurücksetzten. Der Lesekopf musste bei mir mit dem Kabel nach oben angebracht werden (bzw. habe ich jetzt den Zähler umgedreht, da der Magnet nicht so wahnsinnig stark ist). Der Zähler bei mir ist ein Wärmemengenzähler der Stadwerke München (SWM) und liefert mit obigem Script T550 Zählerstand xx,xxx MWh T550 Max. Heizleistung xx,x kWh T550 Durchlauf xxx,xx m³ T550 Max. Durchlauf per h 0,xxx m³h T550 Max. Temp. V-/R-Lauf xxx,x °C Versand per MQTT problemlos.
  • Compleo Wallbox nimmt ModBus-Parameter nicht an

    10
    3
    0 Stimmen
    10 Beiträge
    1k Aufrufe
    fuzzy1955F
    @luas sagte: Noch eine Erkenntnis Hallo Luas, Ich habe auch seit 2 Tage eine "Compleo eBox smart" und plage mich noch mit den beschreibbaren Registern herum. Mit welchen Registern startest und beendest du den Ladevorgang? So funktioniert es bei mir nicht: **Laden starten:** setState('modbus.3.holdingRegisters.1.1012_StromSollL1',16,true); setState('modbus.3.holdingRegisters.1.1014_StromSollL2',16,true); setState('modbus.3.holdingRegisters.1.1016_StromSollL3',16,true); **Laden beenden:** setState('modbus.3.holdingRegisters.1.1012_StromSollL1',6,true); //oder 0 setState('modbus.3.holdingRegisters.1.1014_StromSollL2',6,true); //oder 0 setState('modbus.3.holdingRegisters.1.1016_StromSollL3',6,true); //oder 0 Vorweg danke für deine Unterstützung! Gruß, Fuzzy Meine Einstellungen: [image: image.jpeg]
  • OBI - energy-tracker

    4
    0 Stimmen
    4 Beiträge
    239 Aufrufe
    T
    Ich habe mit copilot einen Weg gesucht, dass bei LIVE daten nicht ständig die Batterien getauscht werden müssen, Deshalb habe ich es so gelöst: Generell ist LIVE an, aber nach erfolgreicher Datenübermittlung stellt sich LIVE auf false und wartet 5 Minuten. Dan geht er wieder online übermittelt und geht wieder für 5 Minuten schlafen. Das sollte reichen, um die Batterie zu schonen. Man kann auch kürze Intervalle einstellen. // Abstand zwischen Live-Aktivierungen liveCycleIntervalMinutes: 5, Einstellen was ihr mögt. Vielleicht kann es ja jemand gebrauchen. /* * OBI Energy Tracker * Batteriesparmodus mit zyklischer Live-Aktualisierung * * Live wird kurzzeitig aktiviert und nach Empfang * eines Datensatzes wieder deaktiviert. * * Benötigte Module: * axios * ws */ const CONFIG = { email: "deine mail", password: "dein passwort", hhId: "OBI-Bridge", midId: "OBI-Tracker", stateRoot: "0_userdata.0.obi-energy", debug: false, // Historische Daten pollCron: "*/5 * * * *", historicalDurationMinutes: 15, // Zyklischer Live-Betrieb liveCycleEnabled: true, // Sensorintervall während Live liveUploadIntervalSeconds: 2, // Sensorintervall im Batteriesparmodus normalUploadIntervalSeconds: 300, // Abstand zwischen Live-Aktivierungen liveCycleIntervalMinutes: 5, // Maximale Dauer eines Live-Versuchs liveAttemptTimeoutSeconds: 45, liveWriteMinIntervalMs: 2000, liveReconnectDelayMs: 10000, liveStaleAfterMs: 90000 }; // ============================================================================ // MODULE // ============================================================================ const axios = require("axios"); const WebSocket = require("ws"); // ============================================================================ // API // ============================================================================ const LOGIN_URL = "https://www.obi.de/regi/auth/api/public/login"; const API_BASE_URL = "https://energy-tracking-backend.prod-eks.dbs.obi.solutions"; const HISTORICAL_URL = `${API_BASE_URL}/historical-data`; const SENSOR_URL = `${API_BASE_URL}/sensors`; const LIVE_DATA_URL = "wss://energy-tracking-livemode.prod-eks.dbs.obi.solutions/retrieving"; const API_KEY = "Rh57q3vtOPYTf6FtArVN1boy2AyEiIqaGEmnMks7"; const USER_AGENT = "heyOBI APP / iPhone17,2 / 4.9.1 / 560"; const LIVE_USER_AGENT = "app_client"; const ACCEPT_LANGUAGE = "de-DE,de;q=0.9"; const ACCEPT_HISTORICAL = "application/vnd.obi.companion.energy-tracking.historical-record.v1+json"; const ACCEPT_SENSOR = "application/vnd.obi.companion.energy-tracking.sensor.v1+json"; const TOKEN_MAX_AGE_MS = 55 * 60 * 1000; const REQUEST_TIMEOUT_MS = 30000; // ============================================================================ // VARIABLES // ============================================================================ let token = null; let tokenObtainedAt = 0; let loginPromise = null; let pollRunning = false; let stopping = false; let liveSocket = null; let liveSocketConnecting = false; let liveReconnectTimer = null; let liveWatchdogTimer = null; let liveCycleTimer = null; let liveAttemptTimer = null; let liveCycleActive = false; let liveMessageReceived = false; let lastLiveMessageAt = 0; let lastLiveStateWriteAt = 0; const http = axios.create({ timeout: REQUEST_TIMEOUT_MS, validateStatus: () => true }); // ============================================================================ // HILFSFUNKTIONEN // ============================================================================ function stateId(name) { return `${CONFIG.stateRoot}.${name}`; } function truncate(value, length = 500) { const text = String(value ?? ""); return text.length <= length ? text : text.substring(0, length) + "..."; } function getErrorMessage(error) { if (error?.response) { return "HTTP " + error.response.status + ": " + truncate(JSON.stringify(error.response.data)); } return error?.message || String(error); } function debugLog(message) { if (CONFIG.debug) { log("OBI DEBUG: " + message, "debug"); } } function berlinTime() { return new Date().toLocaleString("de-DE", { timeZone: "Europe/Berlin" }); } // ============================================================================ // KONFIGURATION PRÜFEN // ============================================================================ function validateConfiguration() { if ( !CONFIG.email || !CONFIG.password ) { throw new Error("OBI Zugangsdaten konfigurieren."); } if ( !CONFIG.hhId || !CONFIG.midId ) { throw new Error("OBI HH_ID und MID_ID konfigurieren."); } } // ============================================================================ // LOGIN // ============================================================================ function tokenIsStale() { return ( !token || !tokenObtainedAt || Date.now() - tokenObtainedAt > TOKEN_MAX_AGE_MS ); } async function performLogin() { validateConfiguration(); const response = await http.post( LOGIN_URL, { password: CONFIG.password, country: "de", email: CONFIG.email }, { headers: { "Content-Type": "application/json", Accept: "*/*", "User-Agent": USER_AGENT, "Accept-Language": ACCEPT_LANGUAGE, Cookie: "obi_storeid=527", Origin: "https://www.obi.de", Referer: "https://www.obi.de/" } } ); if (response.status < 200 || response.status >= 300) { throw new Error( "OBI Login fehlgeschlagen: " + response.status ); } if (!response.data?.token) { throw new Error("OBI Token fehlt."); } token = response.data.token; tokenObtainedAt = Date.now(); log("OBI Energy: Login erfolgreich.", "info"); } async function ensureLoggedIn( forceRefresh = false, failedToken = null ) { if (stopping) { throw new Error("Login während Stop verhindert."); } if (!forceRefresh && !tokenIsStale()) { return token; } if ( forceRefresh && failedToken && token && token !== failedToken && !tokenIsStale() ) { return token; } if (loginPromise) { await loginPromise; return token; } loginPromise = performLogin(); try { await loginPromise; return token; } finally { loginPromise = null; } } // ============================================================================ // API HEADER // ============================================================================ function apiHeaders(accept) { return { Authorization: `Bearer ${token}`, "X-Api-Key": API_KEY, "X-App-Type": "b2c", Accept: accept, "Accept-Language": ACCEPT_LANGUAGE, "User-Agent": USER_AGENT, "Cache-Control": "no-cache", Pragma: "no-cache" }; } // ============================================================================ // AUTHENTICATED GET // ============================================================================ async function authenticatedGet( url, accept, retry = true ) { await ensureLoggedIn(); const requestToken = token; const response = await http.get(url, { headers: apiHeaders(accept) }); if (response.status === 401 && retry) { await ensureLoggedIn(true, requestToken); return authenticatedGet(url, accept, false); } if (response.status < 200 || response.status >= 300) { throw new Error( "OBI API Fehler " + response.status + " - " + truncate(JSON.stringify(response.data)) ); } return response.data; } // ============================================================================ // SENSOR UPLOAD INTERVALL SETZEN // ============================================================================ async function setSensorUploadInterval( midId, uploadInterval ) { if (stopping) { throw new Error("Sensorintervall-Änderung während Stop verhindert."); } await ensureLoggedIn(); const failedToken = token; const payload = { id: midId, uploadInterval: uploadInterval }; const headers = { Authorization: `Bearer ${token}`, Accept: ACCEPT_SENSOR, "Content-Type": ACCEPT_SENSOR, "User-Agent": LIVE_USER_AGENT, "X-Platform": "iOS", "X-Lib-Version": "26.6.9" }; let response = await http.patch( `${SENSOR_URL}/${encodeURIComponent(midId)}`, payload, { headers } ); if (response.status === 401) { await ensureLoggedIn(true, failedToken); response = await http.patch( `${SENSOR_URL}/${encodeURIComponent(midId)}`, payload, { headers: { ...headers, Authorization: `Bearer ${token}` } } ); } if (response.status < 200 || response.status >= 300) { throw new Error( "Sensorintervall konnte nicht gesetzt werden: " + response.status + " - " + truncate(JSON.stringify(response.data)) ); } await setStateAsync(stateId("live_upload_interval"), { val: uploadInterval, ack: true }); log( "OBI Energy: Sensorintervall auf " + uploadInterval + " Sekunden gesetzt.", "info" ); } // ============================================================================ // HISTORISCHE DATEN // ============================================================================ function isoTimestampMinutesAgo(minutes) { return new Date( Date.now() - minutes * 60000 ).toISOString(); } async function getHistoricalData(hhId, midId) { const url = `${HISTORICAL_URL}/` + `${encodeURIComponent(hhId)}/` + `${encodeURIComponent(midId)}/meter`; const start = isoTimestampMinutesAgo( CONFIG.historicalDurationMinutes ); const duration = `PT${CONFIG.historicalDurationMinutes}M`; const query = `duration=${encodeURIComponent(start + "/" + duration)}` + `&measures=${encodeURIComponent("energy,negative_energy")}`; return authenticatedGet( `${url}?${query}`, ACCEPT_HISTORICAL ); } // ============================================================================ // HISTORISCHE DATEN AUSWERTEN // ============================================================================ function normalizeHistoricalRecords(data) { if (Array.isArray(data)) return data; if (Array.isArray(data?.records)) return data.records; if (Array.isArray(data?.measurements)) return data.measurements; if (Array.isArray(data?.data)) return data.data; if (Array.isArray(data?.results)) return data.results; return []; } function measurementTimestamp(record) { return ( record?.time ?? record?.timestamp ?? record?.dateTime ?? record?.datetime ?? "" ); } function latestMeasurement(records, measure) { const result = records.filter(item => item && item.measure === measure && item.value !== null && item.value !== undefined ); if (!result.length) return null; result.sort((a, b) => String(measurementTimestamp(a)).localeCompare( String(measurementTimestamp(b)) ) ); return result[result.length - 1]; } // ============================================================================ // HISTORISCHE DATEN ABRUFEN // ============================================================================ async function pollHistoricalData() { if (stopping || pollRunning) return; pollRunning = true; try { const rawData = await getHistoricalData( CONFIG.hhId, CONFIG.midId ); const records = normalizeHistoricalRecords(rawData); debugLog("Historische Datensätze: " + records.length); if (!records.length) { throw new Error( "OBI Historical API liefert keine Datensätze." ); } // VERBRAUCH const energy = latestMeasurement(records, "energy"); if (energy) { const value = Number(energy.value); if (Number.isFinite(value)) { await setStateAsync(stateId("energy_wh"), { val: value, ack: true }); await setStateAsync(stateId("energy_kwh"), { val: value / 1000, ack: true }); await setStateAsync(stateId("energy_time"), { val: measurementTimestamp(energy), ack: true }); await setStateAsync(stateId("last_record_received"), { val: measurementTimestamp(energy), ack: true }); } } // EINSPEISUNG const feedIn = latestMeasurement( records, "negative_energy" ); if (feedIn) { const value = Number(feedIn.value); if (Number.isFinite(value)) { await setStateAsync(stateId("negative_energy_wh"), { val: value, ack: true }); await setStateAsync(stateId("negative_energy_kwh"), { val: value / 1000, ack: true }); await setStateAsync(stateId("negative_energy_time"), { val: measurementTimestamp(feedIn), ack: true }); } } // AKTUALISIERUNG await setStateAsync(stateId("last_update"), { val: berlinTime(), ack: true }); await setStateAsync(stateId("last_error"), { val: "", ack: true }); log( "OBI Energy: Historische Daten aktualisiert.", "info" ); } catch (error) { const message = getErrorMessage(error); log( "OBI Energy Poll Fehler: " + message, "error" ); await setStateAsync(stateId("last_error"), { val: message, ack: true }); } finally { pollRunning = false; } } // ENDE TEIL 1 // ============================================================================ // LIVE MESSAGE PARSER // ============================================================================ async function handleLiveJsonMessage(message) { if (!message || typeof message !== "object") return false; if (message.event && message.event !== "mqttMessage") { return false; } let payload = message.data; if (typeof payload === "string") { try { payload = JSON.parse(payload); } catch { return false; } } if ( payload && typeof payload === "object" && payload.payload !== undefined ) { let nested = payload.payload; if (typeof nested === "string") { try { nested = JSON.parse(nested); } catch { nested = null; } } if (nested && typeof nested === "object") { payload = nested; } } if (!payload || typeof payload !== "object") return false; const rawPower = payload.power ?? payload.currentPower ?? payload.activePower ?? payload.powerW ?? payload.power_w ?? payload.value?.power ?? payload.values?.power; const rawRssi = payload.rssi ?? payload.signalStrength ?? payload.value?.rssi; const rawBattery = payload.battery ?? payload.batteryLevel ?? payload.value?.battery; const power = Number(rawPower); const rssi = Number(rawRssi); const battery = Number(rawBattery); lastLiveMessageAt = Date.now(); await setStateAsync(stateId("sensor_online"), { val: true, ack: true }); const now = Date.now(); if ( now - lastLiveStateWriteAt < CONFIG.liveWriteMinIntervalMs ) { return true; } lastLiveStateWriteAt = now; let validPower = false; if (Number.isFinite(power)) { await setStateAsync(stateId("power_w"), { val: power, ack: true }); await setStateAsync(stateId("power_available"), { val: true, ack: true }); validPower = true; } else { await setStateAsync(stateId("power_available"), { val: false, ack: true }); } if (Number.isFinite(rssi)) { await setStateAsync(stateId("rssi"), { val: rssi, ack: true }); } if (Number.isFinite(battery)) { await setStateAsync(stateId("battery"), { val: battery, ack: true }); } await setStateAsync(stateId("live_last_update"), { val: berlinTime(), ack: true }); await setStateAsync(stateId("live_stale"), { val: false, ack: true }); log( "OBI LIVE: Power=" + (validPower ? power + " W" : "nicht vorhanden") + ", Batterie=" + (Number.isFinite(battery) ? battery + "%" : "unbekannt"), "info" ); // Sobald ein gültiger Live-Datensatz empfangen wurde, // wird der Live-Zyklus beendet. if (liveCycleActive && !liveMessageReceived) { liveMessageReceived = true; log( "OBI LIVE: Datensatz empfangen. Live wird beendet.", "info" ); setTimeout(() => { if (!stopping && liveCycleActive) { finishLiveCycle(); } }, 1000); } return true; } // ============================================================================ // JSON MESSAGE SPLITTER // ============================================================================ function splitJsonMessages(text) { const messages = []; try { messages.push(JSON.parse(text)); return messages; } catch {} let start = -1; let depth = 0; let inString = false; let escaped = false; for (let i = 0; i < text.length; i++) { const char = text[i]; if (inString) { if (escaped) { escaped = false; } else if (char === "\\") { escaped = true; } else if (char === '"') { inString = false; } continue; } if (char === '"') { inString = true; continue; } if (char === "{") { if (depth === 0) start = i; depth++; } if (char === "}") { depth--; if (depth === 0 && start >= 0) { try { messages.push( JSON.parse(text.substring(start, i + 1)) ); } catch {} start = -1; } } } return messages; } // ============================================================================ // LIVE RAW MESSAGE // ============================================================================ async function handleLiveRawMessage(raw) { const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw); const messages = splitJsonMessages(text); for (const msg of messages) { await handleLiveJsonMessage(msg); } } // ============================================================================ // LIVE SOCKET SCHLIESSEN // ============================================================================ function closeLiveSocket() { if (liveSocket) { const socket = liveSocket; liveSocket = null; try { socket.close(); } catch {} } liveSocketConnecting = false; } // ============================================================================ // LIVE ZYKLUS BEENDEN // ============================================================================ async function finishLiveCycle() { if (stopping || !liveCycleActive) return; // Doppelte Aufrufe verhindern liveCycleActive = false; if (liveAttemptTimer) { clearTimeout(liveAttemptTimer); liveAttemptTimer = null; } if (liveReconnectTimer) { clearTimeout(liveReconnectTimer); liveReconnectTimer = null; } log( "OBI LIVE: Zurück in den Batteriesparmodus.", "info" ); // Verbindung schließen closeLiveSocket(); // Sensor auf 300 Sekunden zurückstellen try { await setSensorUploadInterval( CONFIG.midId, CONFIG.normalUploadIntervalSeconds ); await setStateAsync(stateId("live_connected"), { val: false, ack: true }); await setStateAsync(stateId("live_cycle_active"), { val: false, ack: true }); log( "OBI Energy: Batteriesparmodus aktiv. Nächster Live-Zyklus in " + CONFIG.liveCycleIntervalMinutes + " Minuten.", "info" ); } catch (error) { log( "OBI LIVE: Fehler beim Zurückschalten: " + getErrorMessage(error), "error" ); await setStateAsync(stateId("last_error"), { val: getErrorMessage(error), ack: true }); } } // ============================================================================ // LIVE TIMEOUT // ============================================================================ function startLiveAttemptTimeout() { if (liveAttemptTimer) { clearTimeout(liveAttemptTimer); } liveAttemptTimer = setTimeout(() => { if (stopping || !liveCycleActive) return; log( "OBI LIVE: Zeitlimit erreicht. Live wird beendet.", "warn" ); finishLiveCycle(); }, CONFIG.liveAttemptTimeoutSeconds * 1000); } // ============================================================================ // LIVE VERBINDUNG // ============================================================================ async function startLiveConnection() { if ( stopping || !liveCycleActive || liveSocket || liveSocketConnecting ) { return; } liveSocketConnecting = true; try { await ensureLoggedIn(); const url = `${LIVE_DATA_URL}` + `?bridgeId=${encodeURIComponent(CONFIG.hhId)}` + `&sensorId=${encodeURIComponent(CONFIG.midId)}`; const socket = new WebSocket(url, { headers: { Authorization: `Bearer ${token}`, "User-Agent": LIVE_USER_AGENT, "Accept-Language": ACCEPT_LANGUAGE }, perMessageDeflate: true }); liveSocket = socket; socket.on("open", async () => { liveSocketConnecting = false; if (stopping || !liveCycleActive) { socket.close(); return; } await setStateAsync(stateId("live_connected"), { val: true, ack: true }); log( "OBI Energy: Live WebSocket verbunden.", "info" ); }); socket.on("message", raw => { handleLiveRawMessage(raw).catch(error => { log( "OBI Live Fehler: " + getErrorMessage(error), "error" ); }); }); socket.on("error", error => { log( "OBI WebSocket Fehler: " + getErrorMessage(error), "warn" ); }); socket.on("close", () => { if (liveSocket === socket) { liveSocket = null; } liveSocketConnecting = false; if ( !stopping && liveCycleActive && !liveMessageReceived ) { scheduleLiveReconnect(); } }); } catch (error) { liveSocket = null; liveSocketConnecting = false; throw error; } } // ============================================================================ // LIVE RECONNECT // ============================================================================ function scheduleLiveReconnect() { if ( stopping || !liveCycleActive || liveMessageReceived || liveReconnectTimer ) { return; } liveReconnectTimer = setTimeout(async () => { liveReconnectTimer = null; if ( stopping || !liveCycleActive || liveMessageReceived ) { return; } try { await startLiveConnection(); } catch (error) { log( "OBI Reconnect Fehler: " + getErrorMessage(error), "warn" ); scheduleLiveReconnect(); } }, CONFIG.liveReconnectDelayMs); } // ============================================================================ // LIVE ZYKLUS STARTEN // ============================================================================ async function startLiveCycle() { if ( stopping || !CONFIG.liveCycleEnabled || liveCycleActive ) { return; } liveCycleActive = true; liveMessageReceived = false; log( "OBI LIVE: Neuer Live-Zyklus wird gestartet.", "info" ); await setStateAsync(stateId("live_cycle_active"), { val: true, ack: true }); try { // Sensor auf 2 Sekunden setzen await setSensorUploadInterval( CONFIG.midId, CONFIG.liveUploadIntervalSeconds ); // WebSocket verbinden await startLiveConnection(); // Maximale Live-Dauer überwachen startLiveAttemptTimeout(); } catch (error) { log( "OBI LIVE Startfehler: " + getErrorMessage(error), "error" ); await finishLiveCycle(); } } // ============================================================================ // LIVE ZYKLUS PLANEN // ============================================================================ function scheduleNextLiveCycle() { if (stopping || !CONFIG.liveCycleEnabled) return; if (liveCycleTimer) { clearTimeout(liveCycleTimer); liveCycleTimer = null; } liveCycleTimer = setTimeout(async () => { liveCycleTimer = null; if (stopping) return; await startLiveCycle(); }, CONFIG.liveCycleIntervalMinutes * 60 * 1000); } // ============================================================================ // LIVE WATCHDOG // ============================================================================ function startLiveWatchdog() { if (liveWatchdogTimer) { clearInterval(liveWatchdogTimer); } liveWatchdogTimer = setInterval(async () => { if (stopping) return; const stale = !lastLiveMessageAt || Date.now() - lastLiveMessageAt > CONFIG.liveStaleAfterMs; await setStateAsync(stateId("live_stale"), { val: stale, ack: true }); }, 15000); } // ============================================================================ // DATENPUNKTE ERSTELLEN // ============================================================================ async function createStateIfMissing(name, value, common) { const id = stateId(name); if (!existsState(id)) { await createStateAsync( id, value, { ...common, read: true, write: false } ); } } // ============================================================================ // ALLE DATENPUNKTE // ============================================================================ async function ensureStates() { const states = { power_w: [0, { name: "Live Leistung", type: "number", unit: "W" }], power_available: [false, { name: "Leistung verfügbar", type: "boolean" }], energy_wh: [0, { name: "Verbrauch", type: "number", unit: "Wh" }], energy_kwh: [0, { name: "Verbrauch", type: "number", unit: "kWh" }], negative_energy_wh: [0, { name: "Einspeisung", type: "number", unit: "Wh" }], negative_energy_kwh: [0, { name: "Einspeisung", type: "number", unit: "kWh" }], rssi: [0, { name: "RSSI", type: "number", unit: "dBm" }], battery: [0, { name: "Batterie", type: "number", unit: "%" }], sensor_online: [false, { name: "Sensor Online", type: "boolean" }], connection_strength: ["", { name: "Verbindungsqualität", type: "string" }], last_record_received: ["", { name: "Letzter Sensorwert", type: "string" }], live_last_update: ["", { name: "Letztes Live Signal", type: "string" }], live_connected: [false, { name: "Live Verbindung", type: "boolean" }], live_stale: [true, { name: "Live Daten veraltet", type: "boolean" }], live_upload_interval: [300, { name: "Upload Intervall", type: "number", unit: "s" }], live_cycle_active: [false, { name: "Live Zyklus aktiv", type: "boolean" }], bridge_id: [CONFIG.hhId, { name: "Bridge ID", type: "string" }], sensor_id: [CONFIG.midId, { name: "Sensor ID", type: "string" }], energy_time: ["", { name: "Zeit Verbrauch", type: "string" }], negative_energy_time: ["", { name: "Zeit Einspeisung", type: "string" }], last_update: ["", { name: "Letzte Aktualisierung", type: "string" }], last_error: ["", { name: "Letzter Fehler", type: "string" }] }; for (const key in states) { await createStateIfMissing( key, states[key][0], states[key][1] ); } } // ============================================================================ // START // ============================================================================ async function start() { validateConfiguration(); await ensureStates(); await setStateAsync(stateId("bridge_id"), { val: CONFIG.hhId, ack: true }); await setStateAsync(stateId("sensor_id"), { val: CONFIG.midId, ack: true }); // Zuerst Batteriesparmodus aktivieren await setSensorUploadInterval( CONFIG.midId, CONFIG.normalUploadIntervalSeconds ); await setStateAsync(stateId("live_cycle_active"), { val: false, ack: true }); await setStateAsync(stateId("live_connected"), { val: false, ack: true }); await setStateAsync(stateId("live_stale"), { val: true, ack: true }); // Historische Daten laden await pollHistoricalData(); // Watchdog starten startLiveWatchdog(); // Ersten Live-Zyklus starten if (CONFIG.liveCycleEnabled) { await startLiveCycle(); } else { log( "OBI Energy: Zyklischer Live-Betrieb deaktiviert.", "info" ); } log( "OBI Energy: Skript erfolgreich gestartet.", "info" ); } // ============================================================================ // START AUSFÜHREN // ============================================================================ start().catch(error => { log( "OBI Start Fehler: " + getErrorMessage(error), "error" ); setStateAsync(stateId("last_error"), { val: getErrorMessage(error), ack: true }).catch(() => {}); }); // ============================================================================ // HISTORISCHE DATEN ZEITPLAN // ============================================================================ schedule(CONFIG.pollCron, () => { if (!stopping) { pollHistoricalData(); } }); // ============================================================================ // LIVE ZYKLUS WIEDERHOLEN // ============================================================================ // Nach jedem abgeschlossenen Live-Zyklus wird der nächste // Live-Zyklus für 5 Minuten später vorgemerkt. const originalFinishLiveCycle = finishLiveCycle; finishLiveCycle = async function() { await originalFinishLiveCycle(); if (!stopping && CONFIG.liveCycleEnabled) { scheduleNextLiveCycle(); } }; // ============================================================================ // SKRIPT BEENDEN // ============================================================================ onStop(async () => { stopping = true; log( "OBI Energy: Skript wird beendet.", "info" ); // Alle Timer stoppen if (liveCycleTimer) { clearTimeout(liveCycleTimer); liveCycleTimer = null; } if (liveAttemptTimer) { clearTimeout(liveAttemptTimer); liveAttemptTimer = null; } if (liveReconnectTimer) { clearTimeout(liveReconnectTimer); liveReconnectTimer = null; } if (liveWatchdogTimer) { clearInterval(liveWatchdogTimer); liveWatchdogTimer = null; } // Live-Zustand beenden liveCycleActive = false; // WebSocket schließen closeLiveSocket(); // Sensor auf 300 Sekunden zurücksetzen. // Nur wenn noch ein gültiger Token vorhanden ist. if (token && !tokenIsStale()) { try { const response = await http.patch( `${SENSOR_URL}/${encodeURIComponent(CONFIG.midId)}`, { id: CONFIG.midId, uploadInterval: CONFIG.normalUploadIntervalSeconds }, { headers: { Authorization: `Bearer ${token}`, Accept: ACCEPT_SENSOR, "Content-Type": ACCEPT_SENSOR, "User-Agent": LIVE_USER_AGENT, "X-Platform": "iOS", "X-Lib-Version": "26.6.9" } } ); if ( response.status >= 200 && response.status < 300 ) { await setStateAsync( stateId("live_upload_interval"), { val: CONFIG.normalUploadIntervalSeconds, ack: true } ); log( "OBI Energy: Sensorintervall beim Beenden auf 300 Sekunden zurückgesetzt.", "info" ); } else { log( "OBI Restore Fehler: HTTP " + response.status, "warn" ); } } catch (error) { log( "OBI Restore Fehler: " + getErrorMessage(error), "warn" ); } } else { log( "OBI Restore nicht möglich: Kein gültiger Token.", "warn" ); } await setStateAsync(stateId("live_connected"), { val: false, ack: true }); await setStateAsync(stateId("live_cycle_active"), { val: false, ack: true }); // Zugangsdaten im Speicher freigeben token = null; tokenObtainedAt = 0; loginPromise = null; }, 15000);
  • Rauchmelder Welche?

    32
    0 Stimmen
    32 Beiträge
    894 Aufrufe
    D
    So der Vermieter hat sich entschieden und die XSense XS01-M bestellt mit Hub. Bin ich mal gespannt.
  • welches Gerät als Signalgeber für Alarmanlage?

    8
    0 Stimmen
    8 Beiträge
    339 Aufrufe
    MartinPM
    Bei einem Alarm, der Einbrecher verschrecken soll, ist eine Mindestlaustärke erforderlich, die ihn zur Annahme bringt, dass auch der Nachbar ihn ggfs. gehört hat. Wenn das Alarmgeräusch so weit "schallgedämpft" ist, dass man außerhalb des Hauses nicht hört, wird kein Einbrecher die Flucht ergreifen .... Und wenn man "Polizei ist alarmiert, Ihr Foto ist an die Polizei übermittelt" als Ansage erfolgt, könnte das den Einbrecher vor der Flucht dazu motivieren, erstmal alle Elektronik, die er schnell in Klump hauen kann zu "behandeln".... vorausgesetzt der "Wegwerf-Einbrecher" aus dem ehemaligen Ostblock versteht überhaupt Deutsch...
  • Zigbee Koordinator Tausch

    8
    0 Stimmen
    8 Beiträge
    215 Aufrufe
    AsgothianA
    Solange die oben angegebenen Bedingungen erfüllt sind und dein neuer Koordinator keine nicht-löschbaren Voreinstellungen hat - ja. A.
  • BroadLink RM MAX in ioBroker funktioniert!

    7
    0 Stimmen
    7 Beiträge
    265 Aufrufe
    apollon77A
    @drloksoft Also der BigInt Fehler ist da und wird gefixt, aber ich wundere mich warum diese Funktion überhaupt genutzt wird weil die an sich gar nicht vom normalen App Flow aufgerufen wird. Wie kommt Ihr denn da ran? Dann gab es in den Issues noch den zweiten Teil wo ein QR Code mit "nur BLE" vom Adapter abgelehnt wird wenn BLE deaktiviert ist ... Auch hier ist die Frage wo so ein QR Code bei einem "ist bereits im IP Network" überhaupt herkommt. Wäre cool hier mal Logs zu sehen weil die fehlen mir bei der obigen (AI) Analyse leider vollständig und damit ist das alles "nur Deine Sicht" und ich habe da einige Fragen zu den Root Causes. Danke für weitere Infos und Logs bitte in https://github.com/ioBroker/ioBroker.matter/pull/919
  • Tuya-Gerät: Create Windcalm Deckenventilator

    1
    0 Stimmen
    1 Beiträge
    61 Aufrufe
    Niemand hat geantwortet
  • Cannot Read Disk Size, Windows PC

    22
    1
    0 Stimmen
    22 Beiträge
    2k Aufrufe
    JB_SullivanJ
    Jo - https://github.com/ioBroker/ioBroker/issues/775
  • SONOFF NSPanel mit Lovelace UI (TypeScript Version)

    lovelace ui nspanel sonoff
    8k
    12
    17 Stimmen
    8k Beiträge
    11m Aufrufe
    madingM
    @beetle-fan sagte: Ja, da stehen aber ne ganze Menge IP und MAC Adressen mit drin, weiß nicht, ob ich das hier veröffentliche möchte (?) Das sind ja sicher lokale IP-Adressen, daher nicht sicherheitsrelevant. Das gleiche gilt für die MAC: die identifiziert ja nur jedes Netzwerkgerät eindeutig. Es gibt aber keine Registrierung auf deinen Namen. MAC kann man auch problemlos auf einem PC umschreiben und damit „fälschen“. Hatte ich damals an der FH gemacht, damit mein Notebook ins Internet konnte :P
  • Zuverlässige Lebenszeichenüberwachung von Zigbee-Geräten

    9
    0 Stimmen
    9 Beiträge
    392 Aufrufe
    JLegJ
    @Asgothian sagte: Es gibt Geräte die von sich aus nie etwas melden (Steckdosen ohne leistungsmessung zum Beispiel). Da gibt es keinen DP der von sich aus aktualisiert wird. ...oder auch die Tradfri-Knackfrösche - sind grundsätzlich "unavailable" bei mir, aber schalten tun sie trotzdem... :)
  • shelly update probleme

    Verschoben shelly update
    3
    0 Stimmen
    3 Beiträge
    169 Aufrufe
    da_WoodyD
    @mcm1957 habs heute früh geschafft und auch schon aw bekommen... Falls ECO-MODE aktiv ist bitte deaktiviere ihn und versuche erneut zu updaten hat geklappert! bedenklich find ichs schon. hmm, seit anfang bei shelly dabei, (mussten noch über eigenes wlan einbinden) sowas ist hardcore. vllt hilft's noch wem...
  • Hanazeder hlc 10 LAN

    hanazeder hlc 10
    5
    0 Stimmen
    5 Beiträge
    571 Aufrufe
    Samson71S
    @Jochen-Scholler Du solltest IHM eine Mail schreiben. Nebenbei würde ich Dir dringend empfehlen Deine hier für alle Welt offen stehende Mailadresse wieder rauszunehmen, es sei denn Du möchtest zukünftig jede Menge ungebetene Post bekommen.
  • Anleitung Pool Wärmepumpe per Modbus anbinden

    19
    10
    1 Stimmen
    19 Beiträge
    8k Aufrufe
    J
    Hi zusammen, ich habe mittels RS485 meine Poolsana InverPOWER ULTRA 12 angebunden. Die Platine ist eine "SP.KYZ1.5-4.1" Die oben erwähnte pdf mit den Registeradressen passt zwar nicht zu meiner aber ich habe viele relevante Adressen mittels KI / try&error herausgefunden. Wo ich nicht weiter komme: ich möchte die Wärmepumpe nicht nur zwischen den Modi Silent/Auto/Power hin und herschalten sondern selbst stufenlos regeln. Hab schon versucht: die Compressorfrequenz zu überschreiben, der Wert ändert sich aber nicht (Auch nicht bei Silent und Power) das Frequenzlimit zu überschreiben (bei Silent liegt der Wert bei 70, bei Power bei 95.) Auch diese Adresse reagiert nicht auf Schreibversuche. Den Wert für Modus Silent (werkseitig 70) abzuändern, sodass im Modus Silent ein anderer Wert geladen wird aber da passte die Adressierung nicht. generell Werte über Modbus lesen und auch schreiben funktioniert aber. Jemand eine Idee welche Werte dafür geändert werden sollten? Gibt es einen "versteckten" Modus, also nicht nur Silent/Power/Smart sondern auch "custom"? und dann lassen sich die Werte auch ändern? Vielleicht ist für Custom ein festen Wert hinterlegt, der sich dann ändern lässt? Was passiert im Modus "Smart"? Evtl. lassen sich aktuelle Temperaturen manipulieren oder ich ändere die Soll Temp ab, so dass die Leistung runterfährt? Ziel wären so auf elektrisch 500W runterregeln zu können. Das ganze läuft über Loxone PV-Überschuss geführt Die Adressliste wäre auch sicherlich sehr hilfreich Besten Dank und Beste Grüße
  • HS1SA-E wieder ans Laufen kriegen

    2
    0 Stimmen
    2 Beiträge
    134 Aufrufe
    arteckA
    im ernst .. wenn der zu ist was willst du da rumfrikeln.. das Ding kann Leben retten also ERSETZTEN.. fettisch
  • Nous A1Z Strommessung funktioniert nicht

    9
    0 Stimmen
    9 Beiträge
    223 Aufrufe
    WintermuteW
    Schon probiert, tut (außer an/aus) nicht. Hab gerade ein passendes Issue bei Github gefunden - liegt also (mal wieder) an deconz...
  • Blink Kamerasystem

    blink alexa2.0 adapter
    64
    0 Stimmen
    64 Beiträge
    8k Aufrufe
    M
    Ich weiß, es gibt mittlerweile auch einen Adapter für das Blink-System. Bei der von mir genannten Lösung mit blink2video steht die fertige UI im Vordergrund, welche auch Stand-Alone genutzt werden kann.
  • WMZ Engelmann Sensostar Mqtt keine Aktualisierung

    4
    0 Stimmen
    4 Beiträge
    217 Aufrufe
    crunchipC
    @Ciwa sagte: Ich gehe davon aus das es an der Software von der WMZ Zusatzplatine liegt (ESPhome) Wenn das so wäre, würden ja die Daten in den Objekten ebenfalls nicht aktualisiert werden.
  • Inkbird Poolthermometer mit ESP32 per mqtt an IOBroker

    inkbird ibs-p01r poolthermometer mqtt
    4
    0 Stimmen
    4 Beiträge
    326 Aufrufe
    K
    Hi, Ich hätte Interesse an einen "Fertigbau". Nachbau ist nicht so Meins ☺️

338

Online

33.1k

Benutzer

84.2k

Themen

1.4m

Beiträge