NEWS
Bayrol Webportal
-
Also ich habe mal ein lauffähiges javascript für den iobroker erstellt.
Was ich ausgeklammert habe ist die Temp, da ich keinen Sensor dran habe, aber PH + Redox wird zuverlässig ausgelesen.
User + PW eintragen und die cid rausfinden (im Browser einmal anmelden dann F12 und dann steht da in der Konsole die cid

ich lese nur zwischen 9 und 21 Uhr aus, alle 10 Min, da sonst meine Umwälzpumpe eh nicht läuft.
Um keine falschen Werte anzuzeigen setze ich sie auf null wenn die Pumpe nicht läuft, aber das ist noch nicht 100%.Also hier das Script:
const https = require('https'); const { URL } = require('url'); // ioBroker JavaScript-Adapter-Instanz const JS_INSTANCE = 'javascript.0'; // Zugangsdaten const USER = 'xxx'; const PASS = 'xxx'; // Bayrol Controller-ID const CID = 'xxxxx'; const BASE = 'https://www.bayrol-poolaccess.de/webview'; const LOGIN_URL = `${BASE}/m/login.php`; const PLANTS_M_URL = `${BASE}/m/plants.php`; const DATA_URL = `${BASE}/getdata.php?cid=${CID}`; // ioBroker States const STATE_PH = `${JS_INSTANCE}.bayrol.ph`; const STATE_REDOX = `${JS_INSTANCE}.bayrol.redox`; const STATE_LAST_UPDATE = `${JS_INSTANCE}.bayrol.lastUpdate`; const STATE_CONNECTED = `${JS_INSTANCE}.bayrol.connected`; const STATE_PUMP_RUNNING = `${JS_INSTANCE}.bayrol.pumpRunning`; const STATE_VALUES_VALID = `${JS_INSTANCE}.bayrol.valuesValid`; let cookies = {}; let loggedIn = false; let pollRunning = false; function ensureObjects() { setObject(STATE_PH, { type: 'state', common: { name: 'Bayrol pH', type: 'number', role: 'value.ph', unit: 'pH', read: true, write: false }, native: {} }); setObject(STATE_REDOX, { type: 'state', common: { name: 'Bayrol Redox', type: 'number', role: 'value', unit: 'mV', read: true, write: false }, native: {} }); setObject(STATE_LAST_UPDATE, { type: 'state', common: { name: 'Bayrol letzte Aktualisierung', type: 'string', role: 'date', read: true, write: false }, native: {} }); setObject(STATE_CONNECTED, { type: 'state', common: { name: 'Bayrol verbunden', type: 'boolean', role: 'indicator.connected', read: true, write: false }, native: {} }); setObject(STATE_PUMP_RUNNING, { type: 'state', common: { name: 'Bayrol Umwälzpumpe läuft', type: 'boolean', role: 'indicator', read: true, write: false }, native: {} }); setObject(STATE_VALUES_VALID, { type: 'state', common: { name: 'Bayrol Messwerte gültig', type: 'boolean', role: 'indicator', read: true, write: false }, native: {} }); } function saveCookies(setCookieHeaders) { if (!setCookieHeaders) return; if (!Array.isArray(setCookieHeaders)) { setCookieHeaders = [setCookieHeaders]; } setCookieHeaders.forEach(cookie => { const firstPart = cookie.split(';')[0]; const eq = firstPart.indexOf('='); if (eq > 0) { const name = firstPart.substring(0, eq).trim(); const value = firstPart.substring(eq + 1).trim(); cookies[name] = value; } }); } function getCookieHeader() { return Object.entries(cookies) .map(([name, value]) => `${name}=${value}`) .join('; '); } function getAttr(tag, attrName) { const regex = new RegExp(attrName + '\\s*=\\s*["\\\']([^"\\\']*)["\\\']', 'i'); const match = tag.match(regex); return match ? match[1] : ''; } function request(method, url, body, extraHeaders = {}) { return new Promise((resolve, reject) => { const u = new URL(url); const headers = { 'User-Agent': 'Mozilla/5.0', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'de-DE,de;q=0.9,en;q=0.8', ...extraHeaders }; const cookieHeader = getCookieHeader(); if (cookieHeader) { headers['Cookie'] = cookieHeader; } let payload = null; if (body !== undefined && body !== null) { payload = String(body); headers['Content-Length'] = Buffer.byteLength(payload); } const options = { protocol: u.protocol, hostname: u.hostname, port: u.port || 443, path: u.pathname + u.search, method, headers }; const req = https.request(options, res => { const chunks = []; saveCookies(res.headers['set-cookie']); res.on('data', chunk => chunks.push(chunk)); res.on('end', () => { resolve({ statusCode: res.statusCode, headers: res.headers, body: Buffer.concat(chunks).toString('utf8') }); }); }); req.on('error', reject); req.setTimeout(15000, () => { req.destroy(new Error('Timeout')); }); if (payload) { req.write(payload); } req.end(); }); } async function requestFollow(method, url, body, headers = {}) { let currentMethod = method; let currentUrl = url; let currentBody = body; let currentHeaders = headers; for (let i = 0; i < 5; i++) { const res = await request(currentMethod, currentUrl, currentBody, currentHeaders); if (![301, 302, 303, 307, 308].includes(res.statusCode) || !res.headers.location) { return res; } const oldUrl = currentUrl; const nextUrl = new URL(res.headers.location, currentUrl).toString(); if (res.statusCode === 307 || res.statusCode === 308) { currentUrl = nextUrl; } else { currentMethod = 'GET'; currentUrl = nextUrl; currentBody = null; currentHeaders = { 'Referer': oldUrl }; } } throw new Error('Zu viele Redirects'); } function parseLoginForm(html) { const form = {}; const inputTags = html.match(/<input\b[^>]*>/gi) || []; let usernameSet = false; let passwordSet = false; inputTags.forEach(tag => { const name = getAttr(tag, 'name'); const type = (getAttr(tag, 'type') || 'text').toLowerCase(); const value = getAttr(tag, 'value'); if (!name) return; form[name] = value || ''; if (!usernameSet && ( type === 'email' || type === 'text' || /user|mail|email|login|benutzer/i.test(name) )) { form[name] = USER; usernameSet = true; } if (!passwordSet && ( type === 'password' || /pass|pwd|kennwort/i.test(name) )) { form[name] = PASS; passwordSet = true; } }); if (!usernameSet) { form.username = USER; } if (!passwordSet) { form.password = PASS; } if (form.autologin !== undefined) { form.autologin = 'on'; } const formMatch = html.match(/<form\b[^>]*action\s*=\s*["']([^"']+)["']/i); const action = formMatch ? new URL(formMatch[1], LOGIN_URL).toString() : `${BASE}/m/login.php?r=reg`; return { action, form }; } function formEncode(obj) { return Object.entries(obj) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) .join('&'); } function cleanHtmlText(value) { return String(value || '') .replace(/<script[\s\S]*?<\/script>/gi, '') .replace(/<style[\s\S]*?<\/style>/gi, '') .replace(/<[^>]*>/g, '') .replace(/ /g, ' ') .replace(/ /g, ' ') .replace(/°/g, '°') .replace(/ä/g, 'ä') .replace(/ö/g, 'ö') .replace(/ü/g, 'ü') .replace(/Ä/g, 'Ä') .replace(/Ö/g, 'Ö') .replace(/Ü/g, 'Ü') .replace(/ß/g, 'ß') .replace(/&/g, '&') .replace(/\s+/g, ' ') .trim(); } function parseNumber(value) { const text = cleanHtmlText(value) .replace(',', '.') .replace(/[^\d.+-]/g, ''); const number = parseFloat(text); return Number.isFinite(number) ? number : null; } function labelToKey(label) { const l = cleanHtmlText(label) .replace(/\[[^\]]*\]/g, '') .replace(/\./g, '') .trim() .toLowerCase(); if (l === 'ph' || l.includes('ph')) { return 'ph'; } if (l.includes('redox') || l.includes('orp') || l.includes('mv')) { return 'redox'; } return null; } function hasCssClass(html, className) { const regex = new RegExp('class\\s*=\\s*["\\\'][^"\\\']*\\b' + className + '\\b[^"\\\']*["\\\']', 'i'); return regex.test(String(html || '')); } function parsePumpRunning(html) { const source = String(html || ''); if (hasCssClass(source, 'gstat_ok')) { return true; } if ( hasCssClass(source, 'gstat_nok') || hasCssClass(source, 'gstat_error') || hasCssClass(source, 'gstat_err') || hasCssClass(source, 'gstat_alarm') || hasCssClass(source, 'gstat_off') ) { return false; } const match = source.match(/class\s*=\s*["'][^"']*\bgstat_[^"']*["']/i); if (match) { log('Bayrol: Unbekannte Statusklasse gefunden: ' + match[0], 'warn'); } else { log('Bayrol: Keine gstat-Statusklasse gefunden', 'warn'); } return false; } function parseValues(html) { const source = String(html || ''); const values = {}; const boxRegex = /<div\b[^>]*class\s*=\s*["'][^"']*tab_box[^"']*["'][^>]*>([\s\S]*?)(?=<div\b[^>]*class\s*=\s*["'][^"']*tab_box|<div\b[^>]*class\s*=\s*["'][^"']*gapp_ase|<\/body>|$)/gi; let boxMatch; while ((boxMatch = boxRegex.exec(source)) !== null) { const boxHtml = boxMatch[1]; const spanMatch = boxHtml.match(/<span\b[^>]*>([\s\S]*?)<\/span>/i); const h1Match = boxHtml.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i); if (!spanMatch || !h1Match) { continue; } const label = cleanHtmlText(spanMatch[1]); const number = parseNumber(h1Match[1]); const key = labelToKey(label); if (key && number !== null) { values[key] = Number(number); } } const spanH1Regex = /<span\b[^>]*>([\s\S]*?)<\/span>[\s\S]{0,400}?<h1\b[^>]*>([\s\S]*?)<\/h1>/gi; let spanH1Match; while ((spanH1Match = spanH1Regex.exec(source)) !== null) { const label = cleanHtmlText(spanH1Match[1]); const number = parseNumber(spanH1Match[2]); const key = labelToKey(label); if (key && number !== null) { values[key] = Number(number); } } if (values.ph === undefined) { const phFallback = source.match(/pH\s*(?:\[[^\]]*\])?[\s\S]{0,500}?<h1[^>]*>\s*([0-9]+(?:[.,][0-9]+)?)\s*<\/h1>/i); if (phFallback) { values.ph = Number(parseFloat(phFallback[1].replace(',', '.'))); } } if (values.redox === undefined) { const redoxFallback = source.match(/Redox\s*(?:\[[^\]]*\])?[\s\S]{0,500}?<h1[^>]*>\s*([0-9]+(?:[.,][0-9]+)?)\s*<\/h1>/i); if (redoxFallback) { values.redox = Number(parseFloat(redoxFallback[1].replace(',', '.'))); } } if (values.redox !== undefined && values.redox < 100) { log('Bayrol: Redox-Wert unplausibel verworfen: ' + values.redox, 'warn'); delete values.redox; } return values; } async function login() { cookies = {}; log('Bayrol: Loginseite abrufen ...'); const loginPage = await requestFollow('GET', LOGIN_URL, null); const parsed = parseLoginForm(loginPage.body); log(`Bayrol: Login-Action: ${parsed.action}`); const loginBody = formEncode(parsed.form); const loginRes = await requestFollow('POST', parsed.action, loginBody, { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': LOGIN_URL }); const body = String(loginRes.body); if ( body.includes('Fehler') || body.includes('Zeit abgelaufen') || body.includes('Captcha') ) { throw new Error('Bayrol Login fehlgeschlagen'); } loggedIn = true; log('Bayrol: Login erfolgreich'); } async function pollBayrol() { if (pollRunning) { log('Bayrol: Abruf läuft noch, überspringe diesen Durchlauf', 'warn'); return; } pollRunning = true; try { if (!loggedIn) { await login(); } const res = await requestFollow('GET', DATA_URL, null, { 'Referer': PLANTS_M_URL }); const html = String(res.body); if (html.includes('Anmeldung') && html.includes('Passwort')) { loggedIn = false; throw new Error('Session abgelaufen, Login wird beim nächsten Lauf erneuert'); } const pumpRunning = parsePumpRunning(html); const values = parseValues(html); setState(STATE_CONNECTED, true, true); setState(STATE_PUMP_RUNNING, pumpRunning, true); setState(STATE_LAST_UPDATE, new Date().toISOString(), true); log(`Bayrol: Antwort Status ${res.statusCode}, Länge ${html.length}, Pumpe ${pumpRunning}, Werte ${JSON.stringify(values)}`); if (!pumpRunning) { setState(STATE_PH, null, true); setState(STATE_REDOX, null, true); setState(STATE_VALUES_VALID, false, true); log('Bayrol: Pumpe läuft nicht. pH und Redox wurden auf null gesetzt.', 'warn'); return; } if (values.ph === undefined && values.redox === undefined) { loggedIn = false; setState(STATE_CONNECTED, false, true); setState(STATE_VALUES_VALID, false, true); setState(STATE_PH, null, true); setState(STATE_REDOX, null, true); log('Bayrol: Keine pH-/Redox-Werte gefunden. Werte wurden auf null gesetzt. HTML-Auszug: ' + html.substring(0, 1500), 'warn'); return; } if (values.ph !== undefined) { setState(STATE_PH, Number(values.ph), true); } else { setState(STATE_PH, null, true); } if (values.redox !== undefined) { setState(STATE_REDOX, Number(values.redox), true); } else { setState(STATE_REDOX, null, true); } setState(STATE_VALUES_VALID, true, true); log('Bayrol: Werte aktualisiert: ' + JSON.stringify(values)); } catch (err) { loggedIn = false; setState(STATE_CONNECTED, false, true); setState(STATE_VALUES_VALID, false, true); setState(STATE_PH, null, true); setState(STATE_REDOX, null, true); log('Bayrol Fehler: ' + err.message, 'warn'); } finally { pollRunning = false; } } function isWithinPollWindow() { const now = new Date(); const hour = now.getHours(); const minute = now.getMinutes(); // 09:00 bis 21:00 inklusive if (hour < 9) return false; if (hour > 21) return false; if (hour === 21 && minute > 0) return false; return true; } function pollBayrolIfInWindow() { if (!isWithinPollWindow()) { log('Bayrol: Außerhalb des Abfragezeitraums 09:00–21:00, kein Abruf.'); return; } pollBayrol(); } // Objekte sauber definieren ensureObjects(); // Beim Skriptstart nur abrufen, wenn innerhalb des Zeitfensters setTimeout(pollBayrolIfInWindow, 3000); // alle 10 Minuten von 09:00 bis 20:50 schedule('*/10 9-20 * * *', pollBayrol); // zusätzlich exakt um 21:00 schedule('0 21 * * *', pollBayrol); -
Also ich habe mal ein lauffähiges javascript für den iobroker erstellt.
Was ich ausgeklammert habe ist die Temp, da ich keinen Sensor dran habe, aber PH + Redox wird zuverlässig ausgelesen.
User + PW eintragen und die cid rausfinden (im Browser einmal anmelden dann F12 und dann steht da in der Konsole die cid

ich lese nur zwischen 9 und 21 Uhr aus, alle 10 Min, da sonst meine Umwälzpumpe eh nicht läuft.
Um keine falschen Werte anzuzeigen setze ich sie auf null wenn die Pumpe nicht läuft, aber das ist noch nicht 100%.Also hier das Script:
const https = require('https'); const { URL } = require('url'); // ioBroker JavaScript-Adapter-Instanz const JS_INSTANCE = 'javascript.0'; // Zugangsdaten const USER = 'xxx'; const PASS = 'xxx'; // Bayrol Controller-ID const CID = 'xxxxx'; const BASE = 'https://www.bayrol-poolaccess.de/webview'; const LOGIN_URL = `${BASE}/m/login.php`; const PLANTS_M_URL = `${BASE}/m/plants.php`; const DATA_URL = `${BASE}/getdata.php?cid=${CID}`; // ioBroker States const STATE_PH = `${JS_INSTANCE}.bayrol.ph`; const STATE_REDOX = `${JS_INSTANCE}.bayrol.redox`; const STATE_LAST_UPDATE = `${JS_INSTANCE}.bayrol.lastUpdate`; const STATE_CONNECTED = `${JS_INSTANCE}.bayrol.connected`; const STATE_PUMP_RUNNING = `${JS_INSTANCE}.bayrol.pumpRunning`; const STATE_VALUES_VALID = `${JS_INSTANCE}.bayrol.valuesValid`; let cookies = {}; let loggedIn = false; let pollRunning = false; function ensureObjects() { setObject(STATE_PH, { type: 'state', common: { name: 'Bayrol pH', type: 'number', role: 'value.ph', unit: 'pH', read: true, write: false }, native: {} }); setObject(STATE_REDOX, { type: 'state', common: { name: 'Bayrol Redox', type: 'number', role: 'value', unit: 'mV', read: true, write: false }, native: {} }); setObject(STATE_LAST_UPDATE, { type: 'state', common: { name: 'Bayrol letzte Aktualisierung', type: 'string', role: 'date', read: true, write: false }, native: {} }); setObject(STATE_CONNECTED, { type: 'state', common: { name: 'Bayrol verbunden', type: 'boolean', role: 'indicator.connected', read: true, write: false }, native: {} }); setObject(STATE_PUMP_RUNNING, { type: 'state', common: { name: 'Bayrol Umwälzpumpe läuft', type: 'boolean', role: 'indicator', read: true, write: false }, native: {} }); setObject(STATE_VALUES_VALID, { type: 'state', common: { name: 'Bayrol Messwerte gültig', type: 'boolean', role: 'indicator', read: true, write: false }, native: {} }); } function saveCookies(setCookieHeaders) { if (!setCookieHeaders) return; if (!Array.isArray(setCookieHeaders)) { setCookieHeaders = [setCookieHeaders]; } setCookieHeaders.forEach(cookie => { const firstPart = cookie.split(';')[0]; const eq = firstPart.indexOf('='); if (eq > 0) { const name = firstPart.substring(0, eq).trim(); const value = firstPart.substring(eq + 1).trim(); cookies[name] = value; } }); } function getCookieHeader() { return Object.entries(cookies) .map(([name, value]) => `${name}=${value}`) .join('; '); } function getAttr(tag, attrName) { const regex = new RegExp(attrName + '\\s*=\\s*["\\\']([^"\\\']*)["\\\']', 'i'); const match = tag.match(regex); return match ? match[1] : ''; } function request(method, url, body, extraHeaders = {}) { return new Promise((resolve, reject) => { const u = new URL(url); const headers = { 'User-Agent': 'Mozilla/5.0', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'de-DE,de;q=0.9,en;q=0.8', ...extraHeaders }; const cookieHeader = getCookieHeader(); if (cookieHeader) { headers['Cookie'] = cookieHeader; } let payload = null; if (body !== undefined && body !== null) { payload = String(body); headers['Content-Length'] = Buffer.byteLength(payload); } const options = { protocol: u.protocol, hostname: u.hostname, port: u.port || 443, path: u.pathname + u.search, method, headers }; const req = https.request(options, res => { const chunks = []; saveCookies(res.headers['set-cookie']); res.on('data', chunk => chunks.push(chunk)); res.on('end', () => { resolve({ statusCode: res.statusCode, headers: res.headers, body: Buffer.concat(chunks).toString('utf8') }); }); }); req.on('error', reject); req.setTimeout(15000, () => { req.destroy(new Error('Timeout')); }); if (payload) { req.write(payload); } req.end(); }); } async function requestFollow(method, url, body, headers = {}) { let currentMethod = method; let currentUrl = url; let currentBody = body; let currentHeaders = headers; for (let i = 0; i < 5; i++) { const res = await request(currentMethod, currentUrl, currentBody, currentHeaders); if (![301, 302, 303, 307, 308].includes(res.statusCode) || !res.headers.location) { return res; } const oldUrl = currentUrl; const nextUrl = new URL(res.headers.location, currentUrl).toString(); if (res.statusCode === 307 || res.statusCode === 308) { currentUrl = nextUrl; } else { currentMethod = 'GET'; currentUrl = nextUrl; currentBody = null; currentHeaders = { 'Referer': oldUrl }; } } throw new Error('Zu viele Redirects'); } function parseLoginForm(html) { const form = {}; const inputTags = html.match(/<input\b[^>]*>/gi) || []; let usernameSet = false; let passwordSet = false; inputTags.forEach(tag => { const name = getAttr(tag, 'name'); const type = (getAttr(tag, 'type') || 'text').toLowerCase(); const value = getAttr(tag, 'value'); if (!name) return; form[name] = value || ''; if (!usernameSet && ( type === 'email' || type === 'text' || /user|mail|email|login|benutzer/i.test(name) )) { form[name] = USER; usernameSet = true; } if (!passwordSet && ( type === 'password' || /pass|pwd|kennwort/i.test(name) )) { form[name] = PASS; passwordSet = true; } }); if (!usernameSet) { form.username = USER; } if (!passwordSet) { form.password = PASS; } if (form.autologin !== undefined) { form.autologin = 'on'; } const formMatch = html.match(/<form\b[^>]*action\s*=\s*["']([^"']+)["']/i); const action = formMatch ? new URL(formMatch[1], LOGIN_URL).toString() : `${BASE}/m/login.php?r=reg`; return { action, form }; } function formEncode(obj) { return Object.entries(obj) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) .join('&'); } function cleanHtmlText(value) { return String(value || '') .replace(/<script[\s\S]*?<\/script>/gi, '') .replace(/<style[\s\S]*?<\/style>/gi, '') .replace(/<[^>]*>/g, '') .replace(/ /g, ' ') .replace(/ /g, ' ') .replace(/°/g, '°') .replace(/ä/g, 'ä') .replace(/ö/g, 'ö') .replace(/ü/g, 'ü') .replace(/Ä/g, 'Ä') .replace(/Ö/g, 'Ö') .replace(/Ü/g, 'Ü') .replace(/ß/g, 'ß') .replace(/&/g, '&') .replace(/\s+/g, ' ') .trim(); } function parseNumber(value) { const text = cleanHtmlText(value) .replace(',', '.') .replace(/[^\d.+-]/g, ''); const number = parseFloat(text); return Number.isFinite(number) ? number : null; } function labelToKey(label) { const l = cleanHtmlText(label) .replace(/\[[^\]]*\]/g, '') .replace(/\./g, '') .trim() .toLowerCase(); if (l === 'ph' || l.includes('ph')) { return 'ph'; } if (l.includes('redox') || l.includes('orp') || l.includes('mv')) { return 'redox'; } return null; } function hasCssClass(html, className) { const regex = new RegExp('class\\s*=\\s*["\\\'][^"\\\']*\\b' + className + '\\b[^"\\\']*["\\\']', 'i'); return regex.test(String(html || '')); } function parsePumpRunning(html) { const source = String(html || ''); if (hasCssClass(source, 'gstat_ok')) { return true; } if ( hasCssClass(source, 'gstat_nok') || hasCssClass(source, 'gstat_error') || hasCssClass(source, 'gstat_err') || hasCssClass(source, 'gstat_alarm') || hasCssClass(source, 'gstat_off') ) { return false; } const match = source.match(/class\s*=\s*["'][^"']*\bgstat_[^"']*["']/i); if (match) { log('Bayrol: Unbekannte Statusklasse gefunden: ' + match[0], 'warn'); } else { log('Bayrol: Keine gstat-Statusklasse gefunden', 'warn'); } return false; } function parseValues(html) { const source = String(html || ''); const values = {}; const boxRegex = /<div\b[^>]*class\s*=\s*["'][^"']*tab_box[^"']*["'][^>]*>([\s\S]*?)(?=<div\b[^>]*class\s*=\s*["'][^"']*tab_box|<div\b[^>]*class\s*=\s*["'][^"']*gapp_ase|<\/body>|$)/gi; let boxMatch; while ((boxMatch = boxRegex.exec(source)) !== null) { const boxHtml = boxMatch[1]; const spanMatch = boxHtml.match(/<span\b[^>]*>([\s\S]*?)<\/span>/i); const h1Match = boxHtml.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i); if (!spanMatch || !h1Match) { continue; } const label = cleanHtmlText(spanMatch[1]); const number = parseNumber(h1Match[1]); const key = labelToKey(label); if (key && number !== null) { values[key] = Number(number); } } const spanH1Regex = /<span\b[^>]*>([\s\S]*?)<\/span>[\s\S]{0,400}?<h1\b[^>]*>([\s\S]*?)<\/h1>/gi; let spanH1Match; while ((spanH1Match = spanH1Regex.exec(source)) !== null) { const label = cleanHtmlText(spanH1Match[1]); const number = parseNumber(spanH1Match[2]); const key = labelToKey(label); if (key && number !== null) { values[key] = Number(number); } } if (values.ph === undefined) { const phFallback = source.match(/pH\s*(?:\[[^\]]*\])?[\s\S]{0,500}?<h1[^>]*>\s*([0-9]+(?:[.,][0-9]+)?)\s*<\/h1>/i); if (phFallback) { values.ph = Number(parseFloat(phFallback[1].replace(',', '.'))); } } if (values.redox === undefined) { const redoxFallback = source.match(/Redox\s*(?:\[[^\]]*\])?[\s\S]{0,500}?<h1[^>]*>\s*([0-9]+(?:[.,][0-9]+)?)\s*<\/h1>/i); if (redoxFallback) { values.redox = Number(parseFloat(redoxFallback[1].replace(',', '.'))); } } if (values.redox !== undefined && values.redox < 100) { log('Bayrol: Redox-Wert unplausibel verworfen: ' + values.redox, 'warn'); delete values.redox; } return values; } async function login() { cookies = {}; log('Bayrol: Loginseite abrufen ...'); const loginPage = await requestFollow('GET', LOGIN_URL, null); const parsed = parseLoginForm(loginPage.body); log(`Bayrol: Login-Action: ${parsed.action}`); const loginBody = formEncode(parsed.form); const loginRes = await requestFollow('POST', parsed.action, loginBody, { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': LOGIN_URL }); const body = String(loginRes.body); if ( body.includes('Fehler') || body.includes('Zeit abgelaufen') || body.includes('Captcha') ) { throw new Error('Bayrol Login fehlgeschlagen'); } loggedIn = true; log('Bayrol: Login erfolgreich'); } async function pollBayrol() { if (pollRunning) { log('Bayrol: Abruf läuft noch, überspringe diesen Durchlauf', 'warn'); return; } pollRunning = true; try { if (!loggedIn) { await login(); } const res = await requestFollow('GET', DATA_URL, null, { 'Referer': PLANTS_M_URL }); const html = String(res.body); if (html.includes('Anmeldung') && html.includes('Passwort')) { loggedIn = false; throw new Error('Session abgelaufen, Login wird beim nächsten Lauf erneuert'); } const pumpRunning = parsePumpRunning(html); const values = parseValues(html); setState(STATE_CONNECTED, true, true); setState(STATE_PUMP_RUNNING, pumpRunning, true); setState(STATE_LAST_UPDATE, new Date().toISOString(), true); log(`Bayrol: Antwort Status ${res.statusCode}, Länge ${html.length}, Pumpe ${pumpRunning}, Werte ${JSON.stringify(values)}`); if (!pumpRunning) { setState(STATE_PH, null, true); setState(STATE_REDOX, null, true); setState(STATE_VALUES_VALID, false, true); log('Bayrol: Pumpe läuft nicht. pH und Redox wurden auf null gesetzt.', 'warn'); return; } if (values.ph === undefined && values.redox === undefined) { loggedIn = false; setState(STATE_CONNECTED, false, true); setState(STATE_VALUES_VALID, false, true); setState(STATE_PH, null, true); setState(STATE_REDOX, null, true); log('Bayrol: Keine pH-/Redox-Werte gefunden. Werte wurden auf null gesetzt. HTML-Auszug: ' + html.substring(0, 1500), 'warn'); return; } if (values.ph !== undefined) { setState(STATE_PH, Number(values.ph), true); } else { setState(STATE_PH, null, true); } if (values.redox !== undefined) { setState(STATE_REDOX, Number(values.redox), true); } else { setState(STATE_REDOX, null, true); } setState(STATE_VALUES_VALID, true, true); log('Bayrol: Werte aktualisiert: ' + JSON.stringify(values)); } catch (err) { loggedIn = false; setState(STATE_CONNECTED, false, true); setState(STATE_VALUES_VALID, false, true); setState(STATE_PH, null, true); setState(STATE_REDOX, null, true); log('Bayrol Fehler: ' + err.message, 'warn'); } finally { pollRunning = false; } } function isWithinPollWindow() { const now = new Date(); const hour = now.getHours(); const minute = now.getMinutes(); // 09:00 bis 21:00 inklusive if (hour < 9) return false; if (hour > 21) return false; if (hour === 21 && minute > 0) return false; return true; } function pollBayrolIfInWindow() { if (!isWithinPollWindow()) { log('Bayrol: Außerhalb des Abfragezeitraums 09:00–21:00, kein Abruf.'); return; } pollBayrol(); } // Objekte sauber definieren ensureObjects(); // Beim Skriptstart nur abrufen, wenn innerhalb des Zeitfensters setTimeout(pollBayrolIfInWindow, 3000); // alle 10 Minuten von 09:00 bis 20:50 schedule('*/10 9-20 * * *', pollBayrol); // zusätzlich exakt um 21:00 schedule('0 21 * * *', pollBayrol); -
nochmal eines?
was war das problem am letzen?
bzw was ist der vorteil von diesem? -
@OliverIO das andere hat bei mir nicht funktioniert, bzw. vielleicht hatte ich auch nicht den letzten Stand gefunden
@radi71
kein Problem.
Das skript war nicht für mich.
Allerdings spaltet sich nun die Entwicklungsmöglichkeit nun auf 2 Skripte auf.
Besser wäre gewesen das vorhandene Skript zu verbessern.
Es gab auch Hinweise darauf, das es wohl verschiedene Geräte gibt, evtl solltest du noch dazuschreiben für welches Gerät exakt dein Skript nun funktioniert
Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.
Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.
Mit deinem Input könnte dieser Beitrag noch besser werden 💗
Registrieren Anmelden