NEWS
Shelly 3PM Pro Emulator
-
Das GitHub B2500 Projekt hat eine Möglichkeit IOB Datenpunkte anzubinden. In diese Datenpunkte schreibst du deine Werte vom Zähler.
-
@mikerow sagte in Shelly 3PM Pro Emulator:
Das GitHub B2500 Projekt hat eine Möglichkeit IOB Datenpunkte anzubinden. In diese Datenpunkte schreibst du deine Werte vom Zähler.
Puh, weiß jemand was aktuell das einfachste ist den akku zu steuern? so wie ich das gelesen habe entweder mit ne shelly, oder shelly emulieren, oder Modbus und selbst steuern.
Hat jemand da schon was im Einsatz und kann berichten, was am besten wäre?Gruß und Besten Dank.
-
@ple Das absolut einfachste ist natürlich der Marstek Zähler CT001 .. 003. Dann der Shelly.
@miwi: was spricht denn gegen Uni-Meter? Das ist doch alles virtuell. Du musst nur einen Container in Docker installieren.
Oder ist dein aktueller, per Mod-Bus ausgelesener Zähler, nicht kompatibel mit Uni-Meter? Wobei uni-Meter ja auch iob dp auslesen kann. Ist doch absolut genau das, was du suchst. -
Interessante Lösung dieses Uni-Meter. Habe es mal parallel zum ioBroker im Docker erstellt und versucht das Uni-Meter für input Tasmota (habe ein BitShake IR SmartReader) zu konfigurieren.
Startet schon mal ohne Fehlermeldung. Allerdings bin ich mir nicht sicher, ob die Tasmotasettings auch Werte liefern.
So ein IR SmartReader ist auf jeden Fall billiger als ein Shelly, den ggf. notwendigen Elektriker für den Einbau kann man sich sparen und Spaß macht so eine Bastelei auch
-
Kämpfe zwar noch damit den docker container vernünftig einzurichten. Aber Werte liefert der Shelly Emulator
{"id":0,"a_current":0.54,"a_voltage":230,"a_act_power":124.67,"a_aprt_power":124.67,"a_pf":1,"a_freq":50,"b_current":0.54,"b_voltage":230,"b_act_power":124.67,"b_aprt_power":124.67,"b_pf":1,"b_freq":50,"c_current":0.54,"c_voltage":230,"c_act_power":124.67,"c_aprt_power":124.67,"c_pf":1,"c_freq":50,"total_current":1.62,"total_act_power":374.01,"total_aprt_power":374.01}
Nutzt hier irgendwer Uni-Meter????
Mod-Edit
Bitte Codetags </> benutzen! -
ich habe zwar den uni-meter im docker am laufen, aber die Marstek APP 1.6.43 findet diesen nicht.
uni-meter { output = "uni-meter.output-devices.shelly-pro3em" input = "uni-meter.input-devices.shelly-3em" http-server { port:4711 } output-devices { shelly-pro3em { mac = "DC4F22764880" hostname = "shellypro3em-DC4F22764880" port = 4711 udp-port = 1010 udp-interface ="0.0.0.0" min-sample-period = 5000ms } } input-devices { shelly-3em { url = "http://10.xx.0.xxx" } } }
-
Komme auch nicht wirklich weiter, habe ein JS-Skript für den IOBroker mit Hilfe von einer KI gebaut,
Der Marstek Venus E kann sich mit dem Simulierten Shelly 3emPro verbinden, dort werden auch Realistische Werte angezeigt, wenn über 100Watt.
Hatte vorher alle drei Phasen ausprobiert, habe ich allerdings nicht hinbekommen.Bin wieder bei meinem Problem, was ich auch bei anderen Versuchen hatte:
Wenn der Marstek sieht, er soll entladen, macht er das nicht zum reelen Wert, also z.B. 500 Watt, sondern fährt auf die Begrenzung in der App (800 Watt).
Laut Anzeige 797 Watt, und speist so 300 Watt in das Netz ein, was ich natürlich nicht so erwünsche.
(5 Tage investiert und das Ding macht immer noch was es will)
Hat dort jemand eine Lösung dafür?
(Firmware v152) -
Nun kommen alle drei Phasen in der App an.
Weis aber noch nicht wie sich der Venus-E verhält, aktuelle sind die Speicher voll, und haben PV-Überschuss. -
LoL, habe nun mal den anderen Marstek Venus e auf Eigenverbrauch gesetzt, dort regelt der Mustek nun selbstständig
Muss nur noch beobachten wie, aber denke darauf habe ich mit echten Werten eh keinen Einfluss,
aber der JS scheint auszureichen.// =================================================== // Shelly EM3 Pro Emulation für Marstek Venus E (MIT ECHTEN SHELLY-WERTEN!) // by Gismoh // =================================================== // MARSTEK KONFIGURATION - HIER ANPASSEN! var MARSTEK_CONFIG = { useAbsoluteValues: false, // Für CT001 Protokoll-Kompatibilität invertSignForTest: false, // ✅ RICHTIGE VORZEICHEN (wie echter Shelly!) - TESTWEISE AUF TRUE SETZEN! clampNegativeToZero: false, // Alternative: negative Werte auf 0 separateInputOutput: true, // Separate Input/Output Behandlung debugMode: false, // ❌ Debug-Modus DEAKTIVIERT (reduziert Spam) logInterval: 30000, // Nur alle 30 Sekunden loggen logOnlyChanges: true, // Nur bei Wert-Änderungen loggen minChangeThreshold: 50 // Mindestens 50W Änderung für Log }; // Konfiguration - Anpassen an deine Shelly EM3 Instanz var SHELLY_EM3_INSTANCE = 'shelly.0.SHEM-3#XXXXXXXXXX'; // Deine Shelly EM3 Instanz ID var EMULATION_PORT = 1010; // Port für die Emulation var UPDATE_INTERVAL = 5000; // Update-Intervall in ms // Module laden var http = require('http'); var url = require('url'); var dgram = require('dgram'); var os = require('os'); // Globale Variablen für die Messwerte var currentPowerTotal = 0; var currentPowerL1 = 0; var currentPowerL2 = 0; var currentPowerL3 = 0; // ✅ ECHTE MESSWERTE FÜR ALLE PHASEN (AUS SHELLY DATEN!) var currentCurrentL1 = 0; var currentCurrentL2 = 0; var currentCurrentL3 = 0; var currentVoltageL1 = 230.0; var currentVoltageL2 = 230.0; var currentVoltageL3 = 230.0; var currentPowerFactorL1 = 1.0; var currentPowerFactorL2 = 1.0; var currentPowerFactorL3 = 1.0; var energyTotal = 0; var energyL1 = 0; var energyL2 = 0; var energyL3 = 0; var energyReturnedL1 = 0; var energyReturnedL2 = 0; var energyReturnedL3 = 0; // Performance-Variablen var lastLogTime = 0; var requestCounter = 0; var lastLoggedPower = 0; // ✅ Für Change-Detection var lastDetailedLog = 0; // ✅ Für seltene Detail-Logs // Server-Instanzen var serverInstance = null; var udpSocket = null; function calculateMarstekPower(originalPower) { var result = { originalPower: originalPower, marstekPower: originalPower, // Für CT-Anzeige (immer Echtwert) limitedPower: originalPower, // Wird unten korrigiert powerInput: 0, powerOutput: 0, direction: originalPower >= 0 ? 'Bezug' : 'Einspeisung', explanation: '' }; // ✅ VORZEICHEN UMKEHREN FÜR TEST if (MARSTEK_CONFIG.invertSignForTest) { result.marstekPower = -originalPower; result.direction = result.marstekPower >= 0 ? 'Bezug (umgekehrt)' : 'Einspeisung (umgekehrt)'; result.explanation = 'Vorzeichen umgekehrt für Venus E Test'; } // Begrenzung für Akkuverhalten (auf umgekehrten Wert anwenden) var powerForLimit = result.marstekPower; if (powerForLimit < 0) { // Einspeisung → Akku darf max. mit 1000 W laden result.limitedPower = Math.max(powerForLimit, -1000); result.explanation += ' - Ladebegrenzung auf max 1000W'; } else if (powerForLimit > 0) { // Netzbezug → Akku darf max. mit 1000 W entladen result.limitedPower = Math.min(powerForLimit, 1000); result.explanation += ' - Entladebegrenzung auf max 1000W'; } else { result.limitedPower = 0; result.explanation += ' - Kein Netzfluss – keine Aktion'; } if (MARSTEK_CONFIG.separateInputOutput) { if (result.marstekPower >= 0) { result.powerInput = result.marstekPower; result.powerOutput = 0; } else { result.powerInput = 0; result.powerOutput = Math.abs(result.marstekPower); } } // ✅ NUR LOGGEN BEI GROßEN ÄNDERUNGEN var powerChange = Math.abs(result.marstekPower - lastLoggedPower); if (MARSTEK_CONFIG.debugMode && powerChange > MARSTEK_CONFIG.minChangeThreshold) { console.log('POWER CHANGE: ' + lastLoggedPower + 'W → ' + result.marstekPower + 'W (' + result.direction + ')'); lastLoggedPower = result.marstekPower; } return result; } // ✅ KORRIGIERTE Funktion zum Lesen der ECHTEN Shelly EM3 Pro Werte function readShellyValues() { try { // ✅ HAUPTQUELLE: Tibber Pulse (direkt am Stromzähler) var tibberPower = getState('tibberlink.0.LocalPulse.0.Power').val; // ✅ ECHTE SHELLY EM3 PRO WERTE AUSLESEN var shellyL1Power = getState(SHELLY_EM3_INSTANCE + '.Emeter0.Power').val || 0; var shellyL2Power = getState(SHELLY_EM3_INSTANCE + '.Emeter1.Power').val || 0; var shellyL3Power = getState(SHELLY_EM3_INSTANCE + '.Emeter2.Power').val || 0; // ✅ ECHTE CURRENT-WERTE (Ampere) var shellyL1Current = getState(SHELLY_EM3_INSTANCE + '.Emeter0.Current').val || 0; var shellyL2Current = getState(SHELLY_EM3_INSTANCE + '.Emeter1.Current').val || 0; var shellyL3Current = getState(SHELLY_EM3_INSTANCE + '.Emeter2.Current').val || 0; // ✅ ECHTE POWER FACTOR WERTE var shellyL1PF = getState(SHELLY_EM3_INSTANCE + '.Emeter0.PowerFactor').val || 1.0; var shellyL2PF = getState(SHELLY_EM3_INSTANCE + '.Emeter1.PowerFactor').val || 1.0; var shellyL3PF = getState(SHELLY_EM3_INSTANCE + '.Emeter2.PowerFactor').val || 1.0; // ✅ ECHTE ENERGIE-WERTE (Wh) var shellyL1Energy = getState(SHELLY_EM3_INSTANCE + '.Emeter0.Total').val || 0; var shellyL2Energy = getState(SHELLY_EM3_INSTANCE + '.Emeter1.Total').val || 0; var shellyL3Energy = getState(SHELLY_EM3_INSTANCE + '.Emeter2.Total').val || 0; // ✅ ECHTE RÜCKSPEISUNG-WERTE (Wh) var shellyL1Returned = getState(SHELLY_EM3_INSTANCE + '.Emeter0.Total_Returned').val || 0; var shellyL2Returned = getState(SHELLY_EM3_INSTANCE + '.Emeter1.Total_Returned').val || 0; var shellyL3Returned = getState(SHELLY_EM3_INSTANCE + '.Emeter2.Total_Returned').val || 0; var shellyTotal = shellyL1Power + shellyL2Power + shellyL3Power; // ✅ PLAUSIBILITÄTSPRÜFUNG für Shelly-Werte if (Math.abs(shellyL1Power) > 5000 || Math.abs(shellyL2Power) > 5000 || Math.abs(shellyL3Power) > 5000) { console.log('⚠️ Unplausible Shelly-Werte detected: L1=' + shellyL1Power + ', L2=' + shellyL2Power + ', L3=' + shellyL3Power); return; // Keine Update bei unrealistischen Werten } // ✅ SICHERHEITSPRÜFUNG: Plausible Werte verwenden if (tibberPower !== null && tibberPower !== undefined && Math.abs(tibberPower) < 15000) { currentPowerTotal = tibberPower; if (MARSTEK_CONFIG.debugMode) { console.log('Verwende Tibber Pulse: ' + tibberPower + 'W'); } } else { currentPowerTotal = shellyTotal; console.log('FALLBACK zu Shelly: ' + shellyTotal + 'W (Tibber: ' + tibberPower + ')'); } // ✅ ALLE ECHTEN WERTE ÜBERNEHMEN currentPowerL1 = shellyL1Power; currentPowerL2 = shellyL2Power; currentPowerL3 = shellyL3Power; currentCurrentL1 = shellyL1Current; currentCurrentL2 = shellyL2Current; currentCurrentL3 = shellyL3Current; currentPowerFactorL1 = shellyL1PF; currentPowerFactorL2 = shellyL2PF; currentPowerFactorL3 = shellyL3PF; // ✅ VOLTAGE BERECHNUNG (falls verfügbar, sonst aus Power/Current) if (shellyL1Current > 0.1) { currentVoltageL1 = Math.abs(shellyL1Power / shellyL1Current / shellyL1PF); } else { currentVoltageL1 = 230.0; // Fallback } if (shellyL2Current > 0.1) { currentVoltageL2 = Math.abs(shellyL2Power / shellyL2Current / shellyL2PF); } else { currentVoltageL2 = 230.0; // Fallback } if (shellyL3Current > 0.1) { currentVoltageL3 = Math.abs(shellyL3Power / shellyL3Current / shellyL3PF); } else { currentVoltageL3 = 230.0; // Fallback } // ✅ ENERGIE-WERTE energyL1 = shellyL1Energy; energyL2 = shellyL2Energy; energyL3 = shellyL3Energy; energyTotal = energyL1 + energyL2 + energyL3; energyReturnedL1 = shellyL1Returned; energyReturnedL2 = shellyL2Returned; energyReturnedL3 = shellyL3Returned; if (MARSTEK_CONFIG.debugMode) { console.log('📊 ECHTE SHELLY WERTE:'); console.log(' Power: L1=' + currentPowerL1.toFixed(1) + 'W, L2=' + currentPowerL2.toFixed(1) + 'W, L3=' + currentPowerL3.toFixed(1) + 'W, Total=' + currentPowerTotal.toFixed(1) + 'W'); console.log(' Current: L1=' + currentCurrentL1.toFixed(2) + 'A, L2=' + currentCurrentL2.toFixed(2) + 'A, L3=' + currentCurrentL3.toFixed(2) + 'A'); console.log(' Voltage: L1=' + currentVoltageL1.toFixed(1) + 'V, L2=' + currentVoltageL2.toFixed(1) + 'V, L3=' + currentVoltageL3.toFixed(1) + 'V'); console.log(' PowerFactor: L1=' + currentPowerFactorL1.toFixed(2) + ', L2=' + currentPowerFactorL2.toFixed(2) + ', L3=' + currentPowerFactorL3.toFixed(2)); } } catch (error) { console.error('❌ Fehler beim Lesen der Shelly Werte:'); console.error(error); } } // Lokale IP-Adresse ermitteln function getLocalIP() { try { var interfaces = os.networkInterfaces(); for (var interfaceName in interfaces) { var addresses = interfaces[interfaceName]; for (var i = 0; i < addresses.length; i++) { var address = addresses[i]; if (address.family === 'IPv4' && !address.internal) { return address.address; } } } } catch (e) { console.log('Fehler beim Ermitteln der IP-Adresse'); } return '192.168.1.100'; } // ✅ MARSTEK VENUS E FORMAT - EXAKT 4 FELDER AUF ROOT-EBENE! function createMarstekVenusEResponse() { var powerCalc = calculateMarstekPower(currentPowerTotal); // ✅ GENAU DAS WAS MARSTEK ERWARTET - NUR 4 FELDER! var response = { "a_act_power": currentPowerL1, "b_act_power": currentPowerL2, "c_act_power": currentPowerL3, "total_act_power": powerCalc.marstekPower }; // ✅ SICHERSTELLEN DASS NUR 4 FELDER GESENDET WERDEN if (Object.keys(response).length !== 4) { console.log('⚠️ WARNUNG: Response hat ' + Object.keys(response).length + ' Felder statt 4!'); } return response; } // ✅ ECHTE Shelly.GetStatus Response (VERSCHACHTELTE Struktur) function createShellyGetStatusResponse() { var demand = currentPowerTotal; var correctedPower = demand; var powerCalc = calculateMarstekPower(correctedPower); // ✅ NUR LOGGEN BEI DEBUG MODE UND GROßEN ÄNDERUNGEN if (MARSTEK_CONFIG.debugMode && Math.abs(correctedPower - lastLoggedPower) > MARSTEK_CONFIG.minChangeThreshold) { console.log('📊 Shelly.GetStatus ECHTE WERTE: A=' + currentPowerL1.toFixed(1) + 'W/' + currentCurrentL1.toFixed(2) + 'A, B=' + currentPowerL2.toFixed(1) + 'W/' + currentCurrentL2.toFixed(2) + 'A, C=' + currentPowerL3.toFixed(1) + 'W/' + currentCurrentL3.toFixed(2) + 'A'); } // ✅ ECHTE SHELLY.GETSTATUS STRUKTUR - MIT 100% ECHTEN 3-PHASEN WERTEN! return { "ble": {}, "cloud": {"connected": true}, "em:0": { "id": 0, "a_current": currentCurrentL1, // ✅ ECHTER Current-Wert Phase A! "a_voltage": currentVoltageL1, // ✅ ECHTER Voltage-Wert Phase A! "a_act_power": currentPowerL1, // ✅ ECHTER Power-Wert Phase A! "a_aprt_power": Math.abs(currentPowerL1), "a_pf": currentPowerFactorL1, // ✅ ECHTER PowerFactor Phase A! "a_freq": 50.0, "b_current": currentCurrentL2, // ✅ ECHTER Current-Wert Phase B! "b_voltage": currentVoltageL2, // ✅ ECHTER Voltage-Wert Phase B! "b_act_power": currentPowerL2, // ✅ ECHTER Power-Wert Phase B! "b_aprt_power": Math.abs(currentPowerL2), "b_pf": currentPowerFactorL2, // ✅ ECHTER PowerFactor Phase B! "b_freq": 50.0, "c_current": currentCurrentL3, // ✅ ECHTER Current-Wert Phase C! "c_voltage": currentVoltageL3, // ✅ ECHTER Voltage-Wert Phase C! "c_act_power": currentPowerL3, // ✅ ECHTER Power-Wert Phase C! "c_aprt_power": Math.abs(currentPowerL3), "c_pf": currentPowerFactorL3, // ✅ ECHTER PowerFactor Phase C! "c_freq": 50.0, "n_current": null, // ✅ Wie echter Shelly (oft null) "total_current": currentCurrentL1 + currentCurrentL2 + currentCurrentL3, // ✅ Summe der echten Ströme "total_act_power": powerCalc.marstekPower, "total_aprt_power": Math.abs(powerCalc.marstekPower), "user_calibrated_phase": [] }, "emdata:0": { "id": 0, "a_total_act_energy": energyL1, // ✅ ECHTE Energie Phase A "a_total_act_ret_energy": energyReturnedL1, // ✅ ECHTE Rückspeisung Phase A "b_total_act_energy": energyL2, // ✅ ECHTE Energie Phase B "b_total_act_ret_energy": energyReturnedL2, // ✅ ECHTE Rückspeisung Phase B "c_total_act_energy": energyL3, // ✅ ECHTE Energie Phase C "c_total_act_ret_energy": energyReturnedL3, // ✅ ECHTE Rückspeisung Phase C "total_act": energyTotal, // ✅ ECHTE Gesamt-Energie "total_act_ret": energyReturnedL1 + energyReturnedL2 + energyReturnedL3 // ✅ ECHTE Gesamt-Rückspeisung }, "eth": {"ip": getLocalIP()}, "modbus": {}, "mqtt": {"connected": false}, "sys": { "mac": "XXXXXXXXXXXX", "restart_required": false, "time": new Date().toTimeString().split(' ')[0], "unixtime": Math.floor(Date.now() / 1000), "uptime": 12345, "ram_size": 255736, "ram_free": 76476, "fs_size": 524288, "fs_free": 188416, "cfg_rev": 12, "kvs_rev": 1 } }; } // HTTP Server erstellen var server = http.createServer(function(req, res) { var parsedUrl = url.parse(req.url, true); // CORS Headers setzen res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; } // ✅ NUR WICHTIGE HTTP REQUESTS LOGGEN var isImportantRequest = parsedUrl.pathname.includes('/rpc/EM.GetStatus') || parsedUrl.pathname.includes('/rpc/Shelly.GetStatus') || parsedUrl.pathname.includes('/settings'); if (isImportantRequest) { console.log('🌐 HTTP: ' + parsedUrl.pathname + ' von ' + req.connection.remoteAddress); } // ✅ KRITISCH: VERSCHIEDENE ENDPOINTS = VERSCHIEDENE STRUKTUREN! // Venus E erwartet: /rpc/EM.GetStatus → MARSTEK FORMAT (4 Felder) if (parsedUrl.pathname === '/rpc/EM.GetStatus') { res.writeHead(200, { 'Content-Type': 'application/json' }); var response = createMarstekVenusEResponse(); console.log('🎯 VENUS E EM.GetStatus! Sende MARSTEK FORMAT: total_act_power=' + response.total_act_power + 'W (ROOT-LEVEL)'); res.end(JSON.stringify(response)); return; } // Legacy Support: /rpc/Shelly.GetStatus → VERSCHACHTELTE Struktur if (parsedUrl.pathname === '/rpc/Shelly.GetStatus' || parsedUrl.pathname === '/status') { res.writeHead(200, { 'Content-Type': 'application/json' }); var response = createShellyGetStatusResponse(); // ✅ NUR BEI GROßEN ÄNDERUNGEN LOGGEN var powerChange = Math.abs(response['em:0'].total_act_power - lastLoggedPower); if (powerChange > MARSTEK_CONFIG.minChangeThreshold) { console.log('📊 Legacy Shelly.GetStatus! em:0.total_act_power: ' + response['em:0'].total_act_power + 'W'); } res.end(JSON.stringify(response)); return; } // Shelly Info API emulieren if (parsedUrl.pathname === '/rpc/Shelly.GetInfo' || parsedUrl.pathname === '/settings') { res.writeHead(200, { 'Content-Type': 'application/json' }); var infoResponse = { "id": 0, "src": "shellypro3em-XXXXXXXXXXXX", "result": { "name": "Shelly Pro 3EM Emulation (ECHTE SHELLY-DATEN)", "id": "shellypro3em-XXXXXXXXXXXX", "mac": "XXXXXXXXXXXX", "slot": 1, "model": "SPRO-3EM", "gen": 2, "fw_id": "20230913-XXXXXX/v1.14.0-XXXXXXXX", "ver": "1.14.0", "app": "Pro3EM", "auth_en": false, "auth_domain": null } }; res.end(JSON.stringify(infoResponse)); return; } // Zusätzliche Shelly-Endpunkte if (parsedUrl.pathname === '/shelly') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ "type": "SPRO-3EM", "mac": "XXXXXXXXXXXX", "auth": false, "fw": "1.14.0", "discoverable": true, "longid": 1, "num_outputs": 1, "num_meters": 3 })); return; } // RPC über HTTP (fallback) - AUCH MARSTEK FORMAT if (parsedUrl.pathname === '/rpc') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(createMarstekVenusEResponse())); return; } // Device Description if (parsedUrl.pathname === '/description.xml') { res.writeHead(200, { 'Content-Type': 'text/xml' }); var xml = '<?xml version="1.0"?>\n' + '<root xmlns="urn:schemas-upnp-org:device-1-0">\n' + '<device>\n' + '<deviceType>urn:shelly:device:pro3em:1</deviceType>\n' + '<friendlyName>Shelly Pro 3EM Emulation (ECHTE SHELLY-DATEN)</friendlyName>\n' + '<manufacturer>Allterco</manufacturer>\n' + '<modelName>Shelly Pro 3EM</modelName>\n' + '<UDN>uuid:shelly-pro3em-emulation</UDN>\n' + '</device>\n' + '</root>'; res.end(xml); return; } // Fallback für unbekannte Anfragen // ✅ NUR UNBEKANNTE REQUESTS LOGGEN (reduziert Spam) res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found: ' + parsedUrl.pathname); }); // ✅ KORRIGIERTE RPC Request Handler für UDP function handleRPCRequest(msg, rinfo, socket) { var message = msg.toString(); try { // RPC-Anfrage parsen if (message.includes('EM.GetStatus') || message.includes('Shelly.GetStatus')) { var correctedPower = currentPowerTotal; var powerCalc = calculateMarstekPower(correctedPower); requestCounter++; var now = Date.now(); // Reduziertes Logging if (now - lastLogTime > MARSTEK_CONFIG.logInterval || requestCounter % 20 === 0) { console.log('📡 UDP REQUEST #' + requestCounter + ' von ' + rinfo.address + ' - Method: ' + (message.includes('EM.GetStatus') ? 'EM.GetStatus' : 'Shelly.GetStatus')); lastLogTime = now; } // Parse die Anfrage um die ID zu bekommen var requestData; try { requestData = JSON.parse(message); } catch (e) { requestData = { id: 1, method: 'EM.GetStatus', params: { id: 0 } }; } // ✅ VERSCHIEDENE RPC RESPONSES je nach Method! var rpcResponse; if (message.includes('EM.GetStatus')) { // ✅ MARSTEK VENUS E UDP REQUEST → NUR 4 FELDER AUF ROOT-EBENE! rpcResponse = createMarstekVenusEResponse(); console.log('🎯 UDP EM.GetStatus! Sende MARSTEK FORMAT: total_act_power=' + rpcResponse.total_act_power + 'W (ROOT-LEVEL)'); } else { // Legacy UDP Request → VERSCHACHTELTE Struktur rpcResponse = { "id": requestData.id || 1, "src": "shellypro3em-XXXXXXXXXXXX", "result": createShellyGetStatusResponse() }; console.log('📊 UDP Shelly.GetStatus! Sende em:0.total_act_power: ' + powerCalc.marstekPower + 'W (VERSCHACHTELT)'); } var responseStr = JSON.stringify(rpcResponse); socket.send(responseStr, rinfo.port, rinfo.address, function(err) { if (err && MARSTEK_CONFIG.debugMode) { console.log('Fehler beim Senden: ' + err); } }); } // Fallback: JSON RPC versuchen zu parsen else { try { var rpcRequest = JSON.parse(message); if (rpcRequest.method) { console.log('🔧 RPC Method: ' + rpcRequest.method); var rpcResponse = createMarstekVenusEResponse(); var responseStr = JSON.stringify(rpcResponse); socket.send(responseStr, rinfo.port, rinfo.address); console.log('📤 JSON RPC Response gesendet'); } } catch (e) { if (MARSTEK_CONFIG.debugMode) { console.log('❓ Unbekannte UDP-Nachricht: ' + message.substring(0, 100)); } } } } catch (error) { console.log('❌ Fehler bei UDP-Verarbeitung: ' + error); } } // UDP Socket für Discovery erstellen function createUDPSocket() { udpSocket = dgram.createSocket('udp4'); udpSocket.on('message', function(msg, rinfo) { var message = msg.toString(); // Discovery Response if (message.includes('M-SEARCH') || message.includes('shelly') || message.includes('SSDP')) { var localIP = getLocalIP(); var response = 'HTTP/1.1 200 OK\r\n' + 'ST: urn:shelly:device\r\n' + 'USN: uuid:shelly-pro3em-emulation\r\n' + 'LOCATION: http://' + localIP + ':' + EMULATION_PORT + '/settings\r\n' + 'SERVER: Shelly/1.14.0\r\n' + 'CACHE-CONTROL: max-age=1800\r\n' + '\r\n'; udpSocket.send(response, rinfo.port, rinfo.address, function(err) { // Kein Success-Logging für Discovery }); } // Falls doch RPC über Port 1900 kommt else { handleRPCRequest(msg, rinfo, udpSocket); } }); udpSocket.on('error', function(err) { console.log('UDP Discovery Fehler: ' + err); }); } // Emulation starten function startEmulation() { if (serverInstance) { console.log('Emulation läuft bereits'); return; } try { // HTTP Server starten serverInstance = server.listen(EMULATION_PORT, function() { console.log('=== ECHTE SHELLY PRO 3EM EMULATION GESTARTET ==='); console.log('Port: ' + EMULATION_PORT); console.log('Erreichbar unter: http://localhost:' + EMULATION_PORT); console.log(''); console.log('✅ ECHTE API STRUKTUR:'); console.log(' /rpc/EM.GetStatus → DIREKTE Struktur (für Venus E)'); console.log(' /rpc/Shelly.GetStatus → VERSCHACHTELTE Struktur (Legacy)'); console.log(''); console.log('🎯 Venus E sollte /rpc/EM.GetStatus verwenden!'); console.log('📊 Verwendet echte Shelly EM3 Pro Messwerte!'); console.log('========================================================'); }); // UDP Discovery starten (Port 1900) createUDPSocket(); udpSocket.bind(1900, function() { console.log('UDP Discovery läuft auf Port 1900'); }); // UDP Socket für Port 1010 (RPC) var rpcSocket = dgram.createSocket('udp4'); rpcSocket.bind(1010, function() { console.log('UDP RPC Socket läuft auf Port 1010'); }); rpcSocket.on('message', function(msg, rinfo) { handleRPCRequest(msg, rinfo, rpcSocket); }); rpcSocket.on('error', function(err) { console.log('UDP RPC Port 1010 Fehler: ' + err); }); // Regelmäßige Updates der Messwerte setInterval(readShellyValues, UPDATE_INTERVAL); // Initiale Werte laden readShellyValues(); } catch (error) { console.error('Fehler beim Starten der Emulation:'); console.error(error); } } function stopEmulation() { if (serverInstance) { serverInstance.close(function() { console.log('Shelly EM3 Pro Emulation gestoppt'); serverInstance = null; }); } if (udpSocket) { udpSocket.close(function() { console.log('UDP Discovery gestoppt'); }); } } // ZUSÄTZLICHE Überwachungsfunktion für Marstek-Verhalten function monitorMarstekBehavior() { console.log('\n=== MARSTEK STATUS UPDATE ==='); console.log('⚡ Aktueller Verbrauch: ' + currentPowerTotal + 'W'); console.log('📊 Phase Details: L1=' + currentPowerL1.toFixed(1) + 'W, L2=' + currentPowerL2.toFixed(1) + 'W, L3=' + currentPowerL3.toFixed(1) + 'W'); console.log('🔌 Current Details: L1=' + currentCurrentL1.toFixed(2) + 'A, L2=' + currentCurrentL2.toFixed(2) + 'A, L3=' + currentCurrentL3.toFixed(2) + 'A'); console.log('⚡ Voltage Details: L1=' + currentVoltageL1.toFixed(1) + 'V, L2=' + currentVoltageL2.toFixed(1) + 'V, L3=' + currentVoltageL3.toFixed(1) + 'V'); console.log('📡 UDP Requests: ' + requestCounter + ' (seit Start)'); console.log('🔧 API-Struktur: EM.GetStatus (DIREKT) + Shelly.GetStatus (VERSCHACHTELT)'); if (currentPowerTotal !== 0) { var calc = calculateMarstekPower(currentPowerTotal); console.log('🎯 Venus E erhält: total_act_power=' + calc.marstekPower + 'W (' + calc.direction + ')'); } // Reset Counter für bessere Übersicht if (requestCounter > 1000) { requestCounter = 0; console.log('🔄 Request Counter zurückgesetzt'); } console.log('=============================\n'); } // Monitoring alle 2 Minuten setInterval(monitorMarstekBehavior, 120000); // Emulation starten startEmulation(); // Cleanup bei Script-Stop onStop(function() { console.log('Script wird gestoppt...'); stopEmulation(); }); // Test-Funktion für Venus E Kompatibilität function testVenusECompatibility() { console.log('\n=== VENUS E KOMPATIBILITÄTS-TEST ==='); console.log('🎯 Teste EM.GetStatus (Venus E Endpoint):'); var emResponse = createMarstekVenusEResponse(); console.log('- total_act_power: ' + (emResponse.total_act_power !== undefined ? '✅ ' + emResponse.total_act_power + 'W' : '❌ FEHLT')); console.log('- id: ' + (emResponse.id !== undefined ? '✅ ' + emResponse.id : '❌ FEHLT')); console.log('- a_act_power: ' + (emResponse.a_act_power !== undefined ? '✅ ' + emResponse.a_act_power + 'W' : '❌ FEHLT')); console.log('- Struktur: ' + (emResponse.result === undefined ? '✅ ROOT-LEVEL (korrekt für Venus E)' : '❌ VERSCHACHTELT')); console.log('- Felder-Anzahl: ' + (Object.keys(emResponse).length === 4 ? '✅ 4 Felder (korrekt)' : '❌ ' + Object.keys(emResponse).length + ' Felder')); console.log('- Felder: ' + JSON.stringify(Object.keys(emResponse))); console.log('\n📊 Teste Shelly.GetStatus (Legacy Endpoint):'); var shellyResponse = createShellyGetStatusResponse(); console.log('- em:0.total_act_power: ' + (shellyResponse['em:0'] && shellyResponse['em:0'].total_act_power !== undefined ? '✅ ' + shellyResponse['em:0'].total_act_power + 'W' : '❌ FEHLT')); console.log('- Struktur: ' + (shellyResponse['em:0'] !== undefined ? '✅ VERSCHACHTELT (korrekt für Legacy)' : '❌ NICHT VERSCHACHTELT')); console.log('\n📊 ECHTE SHELLY-DATEN INTEGRATION:'); console.log('- Power Werte: ' + (currentPowerL1 !== 0 || currentPowerL2 !== 0 || currentPowerL3 !== 0 ? '✅ Echte Werte geladen' : '❌ Keine echten Werte')); console.log('- Current Werte: ' + (currentCurrentL1 !== 0 || currentCurrentL2 !== 0 || currentCurrentL3 !== 0 ? '✅ Echte Ampere-Werte' : '❌ Standard-Werte')); console.log('- PowerFactor: ' + (currentPowerFactorL1 !== 1.0 || currentPowerFactorL2 !== 1.0 || currentPowerFactorL3 !== 1.0 ? '✅ Echte PF-Werte' : '❌ Standard-Werte')); console.log('- Energie-Werte: ' + (energyL1 > 0 || energyL2 > 0 || energyL3 > 0 ? '✅ Echte Energie-Daten' : '❌ Keine Energie-Daten')); console.log('\n💡 LOGGING KONFIGURATION:'); console.log('- Debug Mode: ' + (MARSTEK_CONFIG.debugMode ? '🔍 AN (ausführlich)' : '🔇 AUS (reduziert)')); console.log('- Change Threshold: ' + MARSTEK_CONFIG.minChangeThreshold + 'W (nur bei größeren Änderungen loggen)'); console.log('- Log Interval: ' + (MARSTEK_CONFIG.logInterval / 1000) + 's (reduziert Spam)'); console.log('====================================\n'); } // Test bei Start ausführen setTimeout(testVenusECompatibility, 3000); // DEBUGGING: Zeige alle HTTP Requests im Detail function enableDetailedLogging() { MARSTEK_CONFIG.debugMode = true; MARSTEK_CONFIG.minChangeThreshold = 0; // Jede Änderung loggen MARSTEK_CONFIG.logInterval = 5000; // Alle 5 Sekunden console.log('🔍 DETAILED LOGGING AKTIVIERT'); console.log('- Jede Wert-Änderung wird geloggt'); console.log('- Alle HTTP/UDP Requests werden geloggt'); console.log('- Echte Shelly-Werte werden im Detail angezeigt'); console.log('- Aufruf: disableDetailedLogging() zum Deaktivieren'); } function disableDetailedLogging() { MARSTEK_CONFIG.debugMode = false; MARSTEK_CONFIG.minChangeThreshold = 50; // Nur große Änderungen MARSTEK_CONFIG.logInterval = 30000; // Alle 30 Sekunden console.log('🔇 DETAILED LOGGING DEAKTIVIERT'); console.log('- Nur noch wichtige Änderungen werden geloggt'); console.log('- Reduzierte Log-Ausgabe für bessere Übersicht'); } // Spezielle Test-Funktion für echte Shelly-Werte function testRealShellyValues() { console.log('\n=== ECHTE SHELLY-WERTE TEST ==='); console.log('📊 Teste Shelly EM3 Instance: ' + SHELLY_EM3_INSTANCE); try { // Test alle Shelly-Werte var shellyL1Power = getState(SHELLY_EM3_INSTANCE + '.Emeter0.Power').val; var shellyL1Current = getState(SHELLY_EM3_INSTANCE + '.Emeter0.Current').val; var shellyL1PF = getState(SHELLY_EM3_INSTANCE + '.Emeter0.PowerFactor').val; var shellyL1Energy = getState(SHELLY_EM3_INSTANCE + '.Emeter0.Total').val; var shellyL1Returned = getState(SHELLY_EM3_INSTANCE + '.Emeter0.Total_Returned').val; console.log('Phase A (L1):'); console.log(' Power: ' + (shellyL1Power !== null ? shellyL1Power + 'W ✅' : 'FEHLT ❌')); console.log(' Current: ' + (shellyL1Current !== null ? shellyL1Current + 'A ✅' : 'FEHLT ❌')); console.log(' PowerFactor: ' + (shellyL1PF !== null ? shellyL1PF + ' ✅' : 'FEHLT ❌')); console.log(' Energy: ' + (shellyL1Energy !== null ? shellyL1Energy + 'Wh ✅' : 'FEHLT ❌')); console.log(' Returned: ' + (shellyL1Returned !== null ? shellyL1Returned + 'Wh ✅' : 'FEHLT ❌')); // Tibber Test var tibberPower = getState('tibberlink.0.LocalPulse.0.Power').val; console.log('\nTibber Pulse:'); console.log(' Power: ' + (tibberPower !== null ? tibberPower + 'W ✅' : 'FEHLT ❌')); } catch (error) { console.log('❌ Fehler beim Testen der Shelly-Werte: ' + error); } console.log('====================================\n'); } // Test der echten Werte bei Start setTimeout(testRealShellyValues, 5000); // Aufruf: enableDetailedLogging() zum Aktivieren des Detailed Logging // Aufruf: testRealShellyValues() zum Testen der Shelly-Verbindung
-
nach einem Update des Marstek Venus E auf FW 153 war plötzlich alles ok.
Der Emulierte Shelly wird gefunden und arbeitet richtig.