shelly-EM / openDTU power smoocher: act_power+epex -> js script -> websocket

proof of concept:

  • iinterne shelly-Daten auslesen und in eigener Struktur speichern
  • irgendeine beliebige Umrechnung oder Datentransformation durchführen
  • http-websocket aufmachen und neue Struktur für andere Geräte bereitstellen
  • mit anderem Gerät diese Struktur einlesen und verarbeiten

Ich hab erstmal nur einen shelly Daten-smoother gemacht: die ewig umherspringenden Leistungswerte waren nicht gut zu gebrauchen für die Nulleinspeisung mit openDTU oB.

Jeder Aufruf des websocket von aussen startet die Funktion. Dann werden die shelly-Daten lokal zwischengespeichert, der “smoother” (mitteln über die letzten 4 Messwerte) läuft durch, und die modifizierte Struktur wird als JSON rausgeschickt. Auf meinem shelly ist der websocket script/2/dtu, neben script/1/spotelly epex-Verarbeitung.

Den websocket kann man sich anschauen am Rechner

und an den Diagnosedaten in console vom shelly sieht man, daß diese einfache Mittelung gr nicht so übel arbeitet

Zur Abfrage muss man die openDTU umstellen:

ps-doc.zip (890 Bytes) und diese paar Zeilen haben mich 3 Tage gekostet. Es schwirren nämlich viele Anleitungen herum, deren Methoden alle nicht mehr funktionieren

So. Nach gefühlten 48 Sunden am Rechner an einem einzigen Tag :rofl: bin ich bei Version 0.2 angekommen. js is ja neu für mich. Aktueller Stand: smoother optimiert auf meine Systeme. Paar Schnitzer rausoperiert. Ein epex Signal ausm spotelly auswerten (hab nur ein Relais). Funktionen:

  • spotelly schaltet output → (bei mir Boiler + Lader ein) + WR aus
  • f<49,9 → volle Leistung
  • f>50,1 → WR aus
  • U<210 → volle Leistung
  • U>250 → WR aus
  • ansonsten über die letzten 3 Werte der Leistungsmessung gemittelte Werte
// shelly script power smooch for openDTU oB. extension for spotelly as script1
var a,b,sp;                                           // 2 pointers, send power
var ip[];                                                // array for smoothing
var s0[];                                     // array for switch 0 = min price
var s1[];                                     // array for switch 1 = max price
var ev[];                                                 // array for response
const ip_sz=3;                                                 // size of array
const lp=33.0;                                           // lowest pwr inverter
if ((a===undefined))                                               // init once
{a=0;
 b=0;
 sp=0.0;
 ip=[];
 ev=[];
 for (b=0;b<ip_sz;b++){ip.push(lp)}};             // preset array of size ip_sz
function ps(req,res){                             // THIS IS WEBSOCKET ENDPOINT
 Shelly.call("EM1.GetStatus",{id:0},function(result){  // get shelly power ch 0
  ev=result;                                     // store result in local array
  b++;                                     // b is pointer into smoothing array
  if(b>=ip_sz||b<0){b=0};                  // b is pointer into smoothing array
  ip[b]=ev.act_power;                               // store act_power in array
//s1=(Shelly.getComponentStatus("switch",1)); // get max price switch(spotelly)
//if(s1){if (s1.output===true){ip[b]=1000}}; // if max price true: inverter max
  s0=(Shelly.getComponentStatus("switch",0)); // get min price switch(spotelly)
  if(s0){if (s0.output===true){ip[b]=-1000}};// if min price true: inverter off
  if(ev.freq<49.9){ip[b]=1000};                     // freq < min: inverter max
  if(ev.freq>50.1){ip[b]=-1000};                    // freq > max: inverter off
  if(ev.voltage<210){ip[b]=1000};          // local voltage < min: inverter max
  if(ev.voltage>250){ip[b]=-1000};         // local voltage > max: inverter off
  for (a=0;a<(ip_sz-1);a++){sp+=ip[a]};         // add all power array elements
 sp=(sp/(ip_sz));                            // resize send power to openDTU oB
 });
 ev.act_power=sp;                                      // prepare outgoing data
 ev.id=2;
 res.code=200;
 res.body=JSON.stringify(ev);
 res.headers=[["Content-Type","application/json"],["X-Device-Id",Shelly.getDeviceInfo().id]];
 res.send()};

HTTPServer.registerEndpoint("dtu",ps)

läuft stabil, Überfrequenz hatte ich schon kurz und Relais schaltet WR aus. Läuft so erstmal mit. drawback: DTU kriegt stabil nur alle 3s neuen Wert. Mein code ist noch zu langsam. drawback 2: shelly liefert freq nur mit 1 Nachkommastelle.

Einen Tag gekämpft mit bool und output und buttons. Dem pro EM hab ichs geschafft das Betriebssystem zu crashen, neu BS-Version drauf und Reset und läuft wieder. Leider kann spotelly bislang nur auf das Relais unten zugreifen, der boolean button für max. Leistung ausm WR funktioniert aber schon, wenn ich den anklicke.

Noch paar Kleinigkeiten angepasst, Version 0.22 läuft stabil:

// shelly script power smooch for openDTU oB. extension for spotelly as script1
var a,b,sp;                                           // 2 pointers, send power
var ip[];                                                // array for smoothing
var s0[];                                     // array for switch 0 = min price
var s1[];                                     // array for bool 200 = max price
var ev[];                                                 // array for response
const ip_sz=3;                                                 // size of array
const lp=33.0;                                           // lowest pwr inverter
if ((a===undefined))                                               // init once
{a=0;
 b=0;
 sp=0.0;
 ip=[];
 ev=[];
 for (b=0;b<ip_sz;b++){ip.push(lp)}};             // preset array of size ip_sz
function ps(req,res){                             // THIS IS WEBSOCKET ENDPOINT
 Shelly.call("EM1.GetStatus",{id:0},function(result){  // get shelly power ch 0
  ev=result;                                     // store result in local array
  b++;                                     // b is pointer into smoothing array
  if(b>=ip_sz||b<0){b=0};                  // b is pointer into smoothing array
  ip[b]=ev.act_power;                               // store act_power in array
  s1=(Shelly.getComponentStatus("boolean",200)); // max price switch (spotelly)
  if(s1){if (s1.value===true){ip[b]=1000}};  // if max price true: inverter max
  s0=(Shelly.getComponentStatus("switch",0)); // get min price switch(spotelly)
  if(s0){if (s0.output===true){ip[b]=-1000}};// if min price true: inverter off
  if(ev.freq<49.9){ip[b]=1000};                     // freq < min: inverter max
  if(ev.freq>50.1){ip[b]=-1000};                    // freq > max: inverter off
  if(ev.voltage<210){ip[b]=50};        // local voltage < min: inverter ramp up
  if(ev.voltage>250){ip[b]=-50};     // local voltage > max: inverter ramp down
  for (a=0;a<(ip_sz-1);a++){sp+=ip[a]};         // add all power array elements
 sp=(sp/(ip_sz));                            // resize send power to openDTU oB
 });
 ev.act_power=sp;                                      // prepare outgoing data
 ev.id=2;
 res.code=200;
 res.body=JSON.stringify(ev);
 res.headers=[["Content-Type","application/json"],["X-Device-Id",Shelly.getDeviceInfo().id]];
 res.send()};

HTTPServer.registerEndpoint("dtu",ps)

Hab mich den halben tag rumgeärgert, weil eine shelly-Messung (nicht etwa der vom shelly angezeigte Wert!) ungefähr 5-6 sekunden brauchte bis in den Inverter. Ursache ist, daß ich im script eine out-of-sync Anweisung verwendet hatte. Steht natürlich nirgends, daß man sich damit ein Ei legt. Rumprobiert und eine andere schnellere inline-Funktion gefunden. Paar Zeilen code weniger, schachteltiefe von 3 auf 2, neuer Wert kommt jetzt in 1-2 sekunden am Inverter an.

Ursache und Lösung:

// old, slow
 Shelly.call("EM1.GetStatus",{id:0},function(result){// get shelly power ch 0
  ev=result;                                   // store result in local array
// calculations
 });
// new, fast
 ev=Shelly.getComponentStatus("EM1", 0)
// calculations

Version 0.23 stable läuft mit.


Der pro EM50 hat ein eingebautes Relais, lustigerweise heißt der “switch 0”. An der Seite hat der eine Reihe winziger Löcher, da kann man ein “switch add-on” anstecken, noch ein Relais. Man kann aber auch einfach eine blaue LED in Reihe mit einem 1k Widerstand reinstecken in 2 der Löcher, nämlich “+3,3V” und “out 0” (Vorsicht: mach das im stomlosen Zustand, dann gut isolieren, da kann Netzspannung anliegen) und im shelly “Addo-on switch” aktivieren. Das ist aber nicht etwa switch 1, sondern switch 100 … was haben die geraucht?

Hallo,

hast du dir schon mal meinen Beitrag hier im Forum angesehen? Vielleicht kannst du es ja nutzen.

Gruß Robert

das alles muss ich in den shelly laden? Passt doch niemals. Zeig mir die Stelle, an der du die eingebauten Messfehler rausrechnest vielleicht kann ich ein js draus machen

hmm, nee ich lasse das nicht auf den Shellys laufen, läuft aber auf jedem System, Linux, Windows, MacOS. Jeder Raspi geht, bei mir läuft es in einer Virtuellen Maschine im Synology NAS. Messfehler, naja ich denke mit denen muss man leben, aber die Shellys sind schon ziemlich genau, vor allem auch im niedrigen Watt Bereich.

Wie genau brauchst du die Daten? Jeder Zähler im Zählerschrank ist nicht unbedingt genauer.

Wenn ich die Daten vom EVU Zähler mit den Werten der Shellys vergleiche ist der gemessene Unterschied im unteren einstelligen kwh Bereich jetzt nach ca. 9 Monaten.

Gruß Robert

Grosses umprogrammieren: Messwerterfassung jetzt in einstellbarer Zeitschleife, bei mir alle 0,5s neuer Wert. Die Wertübermittlung shelly bis WR ist noch mal einiges schneller geworden.

Version 0.4 läuft mit:

//                           shelly meter script power smooch for openDTU oB.
//  smoothes power signal, modifies via signals from epex script, line status
//     openDTU oB setup: http+json, interval 1s, http://< addr >/script/2/dtu
var a=0;                                                           // pointer
var b=0;                                                           // pointer
var sp=0.0;                                                     // send power
var s0=Shelly.getComponentStatus("switch",0);//array for switch 0 = min price
//var s1=Shelly.getComponentStatus("switch",1);//array for switch 1 = max pri
var s1=Shelly.getComponentStatus("switch",100);//array for switch 100 = max p
//var s1=Shelly.getComponentStatus("boolean",200);//array for switch 200 = ma
var ev=Shelly.getComponentStatus("EM1",0);              // array for response
var ip=[];                                             // array for act_power
var lp=33.0;                                              // act_power preset
var up=600.0;                           // max. act_power increase into array
var dp=-160.0;                          // max. act_power decrease into array
const ip_sz=5;                                               // size of array
const tick_sz=500;                                         // timer tick (ms)
var TickHandle;
for (b=0;b<ip_sz;b++){ip.push(lp)};             // preset array of size ip_sz
function Tick(){
 ev=Shelly.getComponentStatus("EM1",0);              // get shelly power ch 0
 b++;                                    // b is pointer into smoothing array
 if(b>=ip_sz||b<0){b=0;}                                // ip_sz ticks passed
 ip[b]=ev.act_power;                              // store act_power in array
// s1=Shelly.getComponentStatus("switch",1);              // max price switch
 s1=Shelly.getComponentStatus("switch",100);              // max price switch
// s1=Shelly.getComponentStatus("boolean",200);           // max price switch
 if(s1){if (s1.output===true){ip[b]=100}}; // if max price true: inverter max
// if(s1){if (s1.value===true){ip[b]=100}};// if max price true: inverter max
 s0=Shelly.getComponentStatus("switch",0);  // get min price switch(spotelly)
 if(s0){if (s0.output===true){ip[b]=-100}};// if min price true: inverter off
 if(ev.freq<49.9){ip[b]=150};  // freq < min: inverter max to smoothing array
 if(ev.freq>50.1){ip[b]=-150}; // freq > max: inverter off to smoothing array
 if(ev.voltage<212){ip[b]+=33};        // local voltage low: inverter ramp up
 if(ev.voltage>248){ip[b]-=33};     // local voltage high: inverter ramp down
 if(ip[b]>up){ip[b]=up};                                  // rise/fall limits
 if(ip[b]<dp){ip[b]=dp};                                  // rise/fall limits
 sp=0;                                                    // clear send power
 for (a=0;a<(ip_sz);a++){sp+=ip[a]};          // add all power array elements
 sp=((sp/(ip_sz)));                                      // resize send power
 if(ev.freq<49.9){sp=2000};           // freq < min: inverter max immediately
 if(ev.freq>50.1){sp[=-2000};         // freq > max: inverter off immediately
//console.log(ip,"=",sp);                    // diag: input power, send power
Shelly.call("Number.Set",{"id":200,"value":sp})};
function startTimer(){TickHandle=Timer.set(tick_sz,true,Tick)};
startTimer();
function ps(req,res){                           // THIS IS WEBSOCKET ENDPOINT
 ev.act_power=sp;                                    // prepare outgoing data
 ev.id=2;
 res.code=200;
 res.body=JSON.stringify(ev);
 res.headers=[["Content-Type","application/json"],["X-Device-Id",Shelly.getDeviceInfo().id]];
 res.send()};
HTTPServer.registerEndpoint("dtu",ps)