// v2.5.2 19.04.2024 var TibberConfig = { password: "XXXX-XXXX", statesPrefix: "0_userdata.0.TibberPulse-test", interval: 2000, Datainterval: 30000, host: "tibber-host", //negSignPattern: "77070100010800ff6301a", node_id: 1 } const debug = true const http = require('http'); async function getPulseData(password) { const auth = Buffer.from(`admin:${password}`).toString('base64'); const options = { hostname: TibberConfig.host, path: '/metrics.json?node_id=' + TibberConfig.node_id, method: 'GET', headers: { 'Authorization': `Basic ${auth}`, 'Host': 'tibber-host', 'lang': 'de-de', 'content-type': 'application/json', 'user-agent': 'okhttp/3.14.9' } }; function httpRequest(options) { return new Promise((resolve, reject) => { const req = http.request(options, res => { let data = ''; res.on('data', chunk => { data += chunk; }); res.on('end', () => { resolve(data); }); }); req.on('error', error => { reject(error); }); req.end(); }); } try { let response = await (httpRequest(options)); return response.replace("$type", "type"); // Gibt das Antwortobjekt zurück } catch (error) { console.error("Ein Fehler beim Abruf der metrics (getPulseData)."); throw error; } } function getDataAsHexString(password) { return new Promise((resolve, reject) => { const auth = Buffer.from(`admin:${password}`).toString('base64'); const options = { hostname: TibberConfig.host, path: '/data.json?node_id=' + TibberConfig.node_id, headers: { 'Authorization': `Basic ${auth}` } }; http.get(options, res => { const chunks = []; res.on('data', chunk => { chunks.push(chunk); }); res.on('end', () => { const buffer = Buffer.concat(chunks); const hexString = buffer.toString('hex'); resolve(hexString); }); }).on('error', error => { reject(error); }); }); } function isState2(strStatePath, strict = true) { let mSelector; if (strict) { mSelector = $(strStatePath); } else { mSelector = $(strStatePath + "*"); } if (mSelector.length > 0) { return true; } else { return false; } } function isValidUnixTimestampAndConvert(n) { // Typüberprüfung if (typeof n !== 'number') { return false; } // Bereichsüberprüfung (optional) const unixEpoch = 0; // UNIX-Epoch: 1. Januar 1970 const currentTime = Math.floor(Date.now() / 1000); if (n < unixEpoch || n > currentTime) { return false; } // Granularität (optional) if (Math.floor(n) !== n) { return false; } // Konvertiere zu deutschem Zeitformat const date = new Date(n * 1000); const germanTimeFormat = date.toLocaleString('de-DE'); return germanTimeFormat; } function generateAndSyncSub(id, JElements, sub = false, preset = "") { //log("Aufruf:" + JElements) if (!JElements || typeof JElements !== 'object') { log('Ungültige JElements übergeben!: ' + JElements); // return; } for (var JElement in JElements) { var AktVal; if (typeof JElements[JElement] === "object") { if (id === "") { generateAndSyncSub(JElement, JElements[JElement], true, preset); } else { generateAndSyncSub(id + "." + JElement, JElements[JElement], true, preset); } //generateAndSyncSub(id + "." + JElement, JElements[JElement], true, preset); } else { try { if (isState2(preset + "." + id + "." + JElement)) AktVal = getState(preset + "." + id + "." + JElement).val; else AktVal = null } catch (e) { log("Fehler: " + e); // } // Überprüfung für den Elementnamen "timestamp" if (JElement === "timestamp") { const TimeValue = isValidUnixTimestampAndConvert(JElements[JElement]) if (TimeValue) JElements[JElement] = TimeValue; } if (AktVal == null) { createState(preset + "." + id + "." + JElement, JElements[JElement], false); AktVal = JElements[JElement]; } if (AktVal != JElements[JElement]) { if (isState2(preset + "." + id + "." + JElement)) { setState(preset + "." + id + "." + JElement, JElements[JElement], true); } } } } } const obisCodesWithNames = [ { code: "0100100700ff", name: "Power" }, { code: "01000f0700ff", name: "Power", checkSign: true }, { code: "0100010800ff", name: "Import_total" }, { code: "0100010801ff", name: "Import_total_Tarif_1" }, { code: "0100010802ff", name: "Import_total_Tarif_2" }, { code: "0100020800ff", name: "Export_total" }, { code: "0100010800ff_in_k", name: "Import_total_(kWh)" }, { code: "0100020800ff_in_k", name: "Export_total_(kWh)" }, { code: "0100240700ff", name: "Power_L1" }, { code: "0100380700ff", name: "Power_L2" }, { code: "01004c0700ff", name: "Power_L3" }, { code: "0100200700ff", name: "Potential_L1" }, { code: "0100340700ff", name: "Potential_L2" }, { code: "0100480700ff", name: "Potential_L3" }, { code: "01001f0700ff", name: "Current_L1" }, { code: "0100330700ff", name: "Current_L2" }, { code: "0100470700ff", name: "Current_L3" }, { code: "01000e0700ff", name: "Net_frequency" }, { code: "0100510701ff", name: "Potential_Phase_deviation_L1/L2" }, { code: "0100510702ff", name: "Potential_Phase_deviation_L1/L3" }, { code: "0100510704ff", name: "Current/Potential_L1_Phase_deviation" }, { code: "010051070fff", name: "Current/Potential_L2_Phase_deviation" }, { code: "010051071aff", name: "Current/Potential_L3_Phase_deviation" } ]; function findObisCodeName(code, obisCodesWithNames) { const found = obisCodesWithNames.find(item => item.code === code); return found ? found.name : "Unbekannt"; } /** * Static lookup table */ const dlmsUnits = [ { code: 0x1, unit: "a", quantity: "time", unitName: "year", siDefinition: "52*7*24*60*60 s" }, { code: 0x2, unit: "mo", quantity: "time", unitName: "month", siDefinition: "31*24*60*60 s" }, { code: 0x3, unit: "wk", quantity: "time", unitName: "week", siDefinition: "7*24*60*60 s" }, { code: 0x4, unit: "d", quantity: "time", unitName: "day", siDefinition: "24*60*60 s" }, { code: 0x5, unit: "h", quantity: "time", unitName: "hour", siDefinition: "60*60 s" }, { code: 0x6, unit: "min.", quantity: "time", unitName: "min", siDefinition: "60 s" }, { code: 0x7, unit: "s", quantity: "time", unitName: "second", siDefinition: "s" }, { code: 0x8, unit: "°", quantity: "phase angle", unitName: "degree", siDefinition: "rad*180/π" }, { code: 0x9, unit: "°C", quantity: "temperature", unitName: "degree celsius", siDefinition: "K-273.15" }, { code: 0xA, unit: "currency", quantity: "local currency", unitName: "", siDefinition: "" }, { code: 0xB, unit: "m", quantity: "length", unitName: "metre", siDefinition: "m" }, { code: 0xC, unit: "m/s", quantity: "speed", unitName: "metre per second", siDefinition: "m/s" }, { code: 0xD, unit: "m³", quantity: "volume", unitName: "cubic metre", siDefinition: "m³" }, { code: 0xE, unit: "m³", quantity: "corrected volume", unitName: "cubic metre", siDefinition: "m³" }, { code: 0xF, unit: "m³/h", quantity: "volume flux", unitName: "cubic metre per hour", siDefinition: "m³/(60*60s)" }, { code: 0x10, unit: "m³/h", quantity: "corrected volume flux", unitName: "cubic metre per hour", siDefinition: "m³/(60*60s)" }, { code: 0x11, unit: "m³/d", quantity: "volume flux", unitName: "cubic metre per day", siDefinition: "m³/(24*60*60s)" }, { code: 0x12, unit: "m³/d", quantity: "corrected volume flux", unitName: "cubic metre per day", siDefinition: "m³/(24*60*60s)" }, { code: 0x13, unit: "l", quantity: "volume", unitName: "litre", siDefinition: "10-3 m³" }, { code: 0x14, unit: "kg", quantity: "mass", unitName: "kilogram", siDefinition: "" }, { code: 0x15, unit: "N", quantity: "force", unitName: "newton", siDefinition: "" }, { code: 0x16, unit: "Nm", quantity: "energy", unitName: "newtonmeter", siDefinition: "J = Nm = Ws" }, { code: 0x17, unit: "Pa", quantity: "pressure", unitName: "pascal", siDefinition: "N/m²" }, { code: 0x18, unit: "bar", quantity: "pressure", unitName: "bar", siDefinition: "10⁵ N/m²" }, { code: 0x19, unit: "J", quantity: "energy", unitName: "joule", siDefinition: "J = Nm = Ws" }, { code: 0x1A, unit: "J/h", quantity: "thermal power", unitName: "joule per hour", siDefinition: "J/(60*60s)" }, { code: 0x1B, unit: "W", quantity: "active power", unitName: "watt", siDefinition: "W = J/s" }, { code: 0x1C, unit: "VA", quantity: "apparent power", unitName: "volt-ampere", siDefinition: "" }, { code: 0x1D, unit: "var", quantity: "reactive power", unitName: "var", siDefinition: "" }, { code: 0x1E, unit: "Wh", quantity: "active energy", unitName: "watt-hour", siDefinition: "W*(60*60s)" }, { code: 0x1F, unit: "VAh", quantity: "apparent energy", unitName: "volt-ampere-hour", siDefinition: "VA*(60*60s)" }, { code: 0x20, unit: "varh", quantity: "reactive energy", unitName: "var-hour", siDefinition: "var*(60*60s)" }, { code: 0x21, unit: "A", quantity: "current", unitName: "ampere", siDefinition: "A" }, { code: 0x22, unit: "C", quantity: "electrical charge", unitName: "coulomb", siDefinition: "C = As" }, { code: 0x23, unit: "V", quantity: "voltage", unitName: "volt", siDefinition: "V" }, { code: 0x24, unit: "V/m", quantity: "electric field strength", unitName: "volt per metre", siDefinition: "" }, { code: 0x25, unit: "F", quantity: "capacitance", unitName: "farad", siDefinition: "C/V = As/V" }, { code: 0x26, unit: "Ω", quantity: "resistance", unitName: "ohm", siDefinition: "Ω = V/A" }, { code: 0x27, unit: "Ωm²/m", quantity: "resistivity", unitName: "Ωm", siDefinition: "" }, { code: 0x28, unit: "Wb", quantity: "magnetic flux", unitName: "weber", siDefinition: "Wb = Vs" }, { code: 0x29, unit: "T", quantity: "magnetic flux density", unitName: "tesla", siDefinition: "Wb/m2" }, { code: 0x2A, unit: "A/m", quantity: "magnetic field strength", unitName: "ampere per metre", siDefinition: "A/m" }, { code: 0x2B, unit: "H", quantity: "inductance", unitName: "henry", siDefinition: "H = Wb/A" }, { code: 0x2C, unit: "Hz", quantity: "frequency", unitName: "hertz", siDefinition: "1/s" }, { code: 0x2D, unit: "1/(Wh)", quantity: "R_W", unitName: "Active energy meter constant or pulse value", siDefinition: "" }, { code: 0x2E, unit: "1/(varh)", quantity: "R_B", unitName: "reactive energy meter constant or pulse value", siDefinition: "" }, { code: 0x2F, unit: "1/(VAh)", quantity: "R_S", unitName: "apparent energy meter constant or pulse value", siDefinition: "" }, { code: 0x30, unit: "V²h", quantity: "volt-squared hour", unitName: "volt-squaredhours", siDefinition: "V²(60*60s)" }, { code: 0x31, unit: "A²h", quantity: "ampere-squared hour", unitName: "ampere-squaredhours", siDefinition: "A²(60*60s)" }, { code: 0x32, unit: "kg/s", quantity: "mass flux", unitName: "kilogram per second", siDefinition: "kg/s" }, { code: 0x33, unit: "S, mho", quantity: "conductance siemens", unitName: "siemens", siDefinition: "1/Ω" }, { code: 0x34, unit: "K", quantity: "temperature", unitName: "kelvin", siDefinition: "" }, { code: 0x35, unit: "1/(V²h)", quantity: "", unitName: "Volt-squared hour meter constant or pulse value", siDefinition: "" }, { code: 0x36, unit: "1/(A²h)", quantity: "", unitName: "Ampere-squared hour meter constant or pulse value", siDefinition: "" }, { code: 0x37, unit: "1/m³", quantity: "R_V", unitName: "meter constant or pulse value (volume)", siDefinition: "" }, { code: 0x38, unit: "%", quantity: "percentage", unitName: "%", siDefinition: "" }, { code: 0x39, unit: "Ah", quantity: "ampere-hours", unitName: "ampere-hour", siDefinition: "" }, { code: 0x3C, unit: "Wh/m³", quantity: "energy per volume", unitName: "", siDefinition: "3,6*103 J/m³" }, { code: 0x3D, unit: "J/m³", quantity: "calorific value, wobbe", unitName: "", siDefinition: "" }, { code: 0x3E, unit: "Mol %", quantity: "molar fraction of", unitName: "mole percent", siDefinition: "Basic gas composition unit" }, { code: 0x3F, unit: "Wh/m³", quantity: "energy per volume", unitName: "", siDefinition: "3,6*103 J/m³" }, { code: 0x40, unit: "(reserved)", quantity: "", unitName: "", siDefinition: "" }, { code: 0x41, unit: "(other)", quantity: "", unitName: "", siDefinition: "" }, { code: 0x42, unit: "(unitless)", quantity: "no unit, unitless, count", unitName: "", siDefinition: "" }, { code: 0x0, unit: "", quantity: "", unitName: "", siDefinition: "stop condition for iterator" } ]; function findDlmsUnitByCode(decimalCode, dlmsUnits) { const found = dlmsUnits.find(item => item.code === decimalCode); return found ? found.unit : ""; } function parseSignedHex(hexStr) { let num = BigInt("0x" + hexStr); let bitLength = hexStr.length * 4; if (bitLength <= 4) { // Behandlung als 4-Bit-Zahl if (num > 0x7) { num = num - 0x1n; } } else if (bitLength <= 8) { // Behandlung als 8-Bit-Zahl if (num > 0x7F) { num = num - 0x100n; } } else if (bitLength <= 16) { // Behandlung als 16-Bit-Zahl if (num > 0x7FFF) { num = num - 0x10000n; } } else if (bitLength <= 24) { // Behandlung als 16-Bit-Zahl if (num > 0x7FFFFF) { num = num - 0x1000000n; } } else if (bitLength <= 32) { // Behandlung als 32-Bit-Zahl if (num > 0x7FFFFFFF) { num = num - 0x100000000n; } } else { // Behandlung als 64-Bit-Zahl if (num > 0x7FFFFFFFFFFFFFFFn) { num = num - 0x10000000000000000n; } } return Number(num.toString()); } function extractAndParseSMLMessages(transfer) { //const messages = transfer.matchAll(/7707(0100[0-9a-fA-F].{5}?ff)(?:.{6}|.{14}|.{20}|.{26})([0-9a-fA-F]{2})52([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{4,16})01(?=(7)|(0101)|(\n))/g); const messages = transfer.matchAll(/7707(0100[0-9a-fA-F].{5}?ff).{4,28}62([0-9a-fA-F]{2})52([0-9a-fA-F]{2})([0-9a-fA-F]{2})((?:[0-9a-fA-F]{2}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8}|[0-9a-fA-F]{10}|[0-9a-fA-F]{8}|[0-9a-fA-F]{16}))01(?=(77)|(0101)|(\n))/g); let output = []; for (const match of messages) { let result = {} if (debug) console.log('Gesamte Übereinstimmung:' + match[0]); //console.log('Gruppe 1:'+ match[1]); // Der Teil, der dem ersten Klammerausdruck entspricht //console.log('Gruppe 2:'+ match[2]); // Der Teil, der dem zweiten Klammerausdruck entspricht //console.log('Gruppe 3:'+ match[3]); // Der Teil, der dem dritten Klammerausdruck entspricht //console.log('Gruppe 4:'+ match[4]); // Der Teil, der dem dritten Klammerausdruck entspricht //console.log('Gruppe 5:'+ match[5]); // Der Teil, der dem vierten Klammerausdruck entspricht result.name = findObisCodeName(match[1], obisCodesWithNames) result.value = parseSignedHex(match[5]) const decimalCode = parseInt(match[2], 16); result.unit = findDlmsUnitByCode(decimalCode, dlmsUnits) if (match[3].toLowerCase() == "ff") { result.value = result.value / 10 } else if (match[3].toLowerCase() == "fe") { result.value = result.value / 100 } if ('negSignPattern' in TibberConfig && TibberConfig.negSignPattern.length > 2) { const obisCodeOb = obisCodesWithNames.find(item => item.code === match[1]) if (obisCodeOb) { if (obisCodeOb.checkSign) { if (transfer.includes(TibberConfig.negSignPattern)) { //log("Negativ!!!!") result.value = result.value * -1 } } } } /* if (result.value > 1000000000 || result.value < -1000000000) { log("Result.value < or > 1.000.000.000 Skiped!") log(JSON.stringify(result)) console.log('Gesamte Übereinstimmung:' + match[0]); console.log('RAW:' + transfer); continue } */ const valId = TibberConfig.statesPrefix + ".SML." + result.name if (isState2(valId)) { setStateAsync(valId, result.value, true) } else { let common = { name: result.name, type: 'mixed', role: 'state', unit: result.unit, read: true, write: true, }; let native = {} createState(valId, result.value, false, common, native); } if (debug) log(JSON.stringify(result)) let formattedMatch = match[0].replace(/(..)/g, '$1 ').trim(); output.push(getCurrentTimeFormatted() + " : " + formattedMatch + "\n"); } if (debug && output.length > 0) log(" Format für https://tasmota-sml-parser.dicp.net :\n" + output.join('')); } function swapEndianness(hexStr) { const result = []; for (let i = 0; i < hexStr.length; i += 2) { result.unshift(hexStr.substring(i, i + 2)); } return result.join(''); } if (typeof TestData == 'undefined') { var intervalID = setInterval(function () { getPulseData(TibberConfig.password).then(response => { if (debug) console.log("Bridge Data: " + response); // generateAndSyncSub("Data", JSON.parse(response), false, TibberConfig.statesPrefix) }).catch(error => { log('Fehler beim Abrufen der Bridge Daten:' + error, "warn"); }); }, TibberConfig.Datainterval); //jede x Sekunden var intervalID2 = setInterval(function () { // Daten abrufen und als HEX-String ausgeben getDataAsHexString(TibberConfig.password).then(hexString => { extractAndParseSMLMessages(hexString); if (debug) console.log("HEX: " + hexString); // Gibt die Daten als HEX-String aus if (isState2(TibberConfig.statesPrefix + ".SMLDataHEX")) { setState(TibberConfig.statesPrefix + ".SMLDataHEX", hexString, true) } else { createState(TibberConfig.statesPrefix + ".SMLDataHEX", hexString, false); } }).catch(error => { log('Fehler beim Abrufen der RAW Daten:' + error, "warn"); }); }, TibberConfig.interval); } else { const parsedMessages = extractAndParseSMLMessages(TestData); } function getCurrentTimeFormatted() { let now = new Date(); return now.getHours().toString().padStart(2, '0') + ":" + now.getMinutes().toString().padStart(2, '0') + ":" + now.getSeconds().toString().padStart(2, '0') + "." + now.getMilliseconds().toString().padStart(3, '0'); } // clear Timer if script stopped onStop(function (callback) { log("Timer beenden") // clearInterval(intervalID); clearInterval(intervalID2); }, 2000)