Shelly 3em pro - Entwicklung Flow/Json Daten (Stromwächter)

#include <PubSubClient.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>

// WiFi credentials
const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";

// MQTT server credentials
const char* mqtt_server = "your_MQTT_server";
const int mqtt_port = 1883;
const char* mqtt_user = "your_MQTT_user";
const char* mqtt_password = "your_MQTT_password";

// MQTT topics
const char* topic1 = "topic/1";
const char* topic2 = "topic/2";
const char* topic3 = "topic/3";

// Variables to store MQTT values
float value1 = 0.0;
float value2 = 0.0;
float value3 = 0.0;

WiFiClient espClient;
PubSubClient client(espClient);
AsyncWebServer server(80);

void setup() {
  Serial.begin(115200);

  // Connect to WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");

  // Configure MQTT
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(mqttCallback);

  // Connect to MQTT server
  while (!client.connected()) {
    Serial.println("Connecting to MQTT...");
    if (client.connect("ESPClient", mqtt_user, mqtt_password)) {
      Serial.println("Connected to MQTT");
    } else {
      Serial.print("Failed to connect, rc=");
      Serial.print(client.state());
      delay(5000);
    }
  }

  // Subscribe to MQTT topics
  client.subscribe(topic1);
  client.subscribe(topic2);
  client.subscribe(topic3);

  // Configure web server
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    StaticJsonDocument<200> jsonDoc;
    jsonDoc["value1"] = value1;
    jsonDoc["value2"] = value2;
    jsonDoc["value3"] = value3;

    String jsonString;
    serializeJson(jsonDoc, jsonString);

    request->send(200, "application/json", jsonString);
  });

  server.begin();
}

void loop() {
  if (!client.connected()) {
    // Reconnect to MQTT server
    while (!client.connected()) {
      Serial.println("Reconnecting to MQTT...");
      if (client.connect("ESPClient", mqtt_user, mqtt_password)) {
        Serial.println("Reconnected to MQTT");
        client.subscribe(topic1);
        client.subscribe(topic2);
        client.subscribe(topic3);
      } else {
        Serial.print("Failed to reconnect, rc=");
        Serial.print(client.state());
        delay(5000);
      }
    }
  }
  client.loop();
}

void mqttCallback(char* topic, byte* payload, unsigned int length) {
  String message;
  for (unsigned int i = 0; i < length; i++) {
    message += (char)payload[i];
  }

  if (String(topic) == topic1) {
    value1 = message.toFloat();
  } else if (String(topic) == topic2) {
    value2 = message.toFloat();
  } else if (String(topic) == topic3) {
    value3 = message.toFloat();
  }
}
1 „Gefällt mir“

Zeig mal das json das gesendet werden soll.

Hier ein Beispiel mit ArduinoJson

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// WiFi credentials
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

// Server URL
const char* serverUrl = "http://your-server-url.com/endpoint";

void setup() {
  // Initialize serial communication
  Serial.begin(115200);

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");
}

void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;

    // Initialize the JSON document
    StaticJsonDocument<1024> doc;

    // Populate the JSON document
    doc["ble"] = JsonObject();
    doc["cloud"]["connected"] = true;
    doc["eth"]["ip"] = nullptr;

    JsonObject input0 = doc.createNestedObject("input:0");
    input0["id"] = 0;
    input0["state"] = false;

    JsonObject input1 = doc.createNestedObject("input:1");
    input1["id"] = 1;
    input1["state"] = false;

    JsonObject input2 = doc.createNestedObject("input:2");
    input2["id"] = 2;
    input2["state"] = false;

    JsonObject input3 = doc.createNestedObject("input:3");
    input3["id"] = 3;
    input3["state"] = false;

    doc["mqtt"]["connected"] = true;

    JsonObject switch0 = doc.createNestedObject("switch:0");
    switch0["id"] = 0;
    switch0["source"] = "init";
    switch0["output"] = true;
    switch0["apower"] = -25.1;
    switch0["voltage"] = 240.8;
    switch0["freq"] = 50;
    switch0["current"] = 0.17;
    switch0["pf"] = 0.58;
    JsonObject aenergy0 = switch0.createNestedObject("aenergy");
    aenergy0["total"] = 254400.937;
    JsonArray by_minute0 = aenergy0.createNestedArray("by_minute");
    by_minute0.add(348.54);
    by_minute0.add(324.313);
    by_minute0.add(317.25);
    aenergy0["minute_ts"] = 1720027500;

    

    // Convert JSON document to string
    String jsonData;
    serializeJson(doc, jsonData);

    // Specify content-type header
    http.begin(serverUrl);
    http.addHeader("Content-Type", "application/json");

    // Send HTTP POST request
    int httpResponseCode = http.POST(jsonData);

    if (httpResponseCode > 0) {
      String response = http.getString();
      Serial.println(httpResponseCode);
      Serial.println(response);
    } else {
      Serial.print("Error on sending POST: ");
      Serial.println(httpResponseCode);
    }

    // Free resources
    http.end();
  } else {
    Serial.println("WiFi Disconnected");
  }

  
}

oder als String

#include <WiFi.h>
#include <HTTPClient.h>

// WiFi credentials
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

// Server URL
const char* serverUrl = "http://your-server-url.com/endpoint";

void setup() {
  // Initialize serial communication
  Serial.begin(115200);

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");
}

void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;

    // JSON data to be sent
    String jsonData = "{\"ble\":{},\"cloud\":{\"connected\":true},\"eth\":{\"ip\":null},\"input:0\":{\"id\":0,\"state\":false},\"input:1\":{\"id\":1,\"state\":false},\"input:2\":{\"id\":2,\"state\":false},\"input:3\":{\"id\":3,\"state\":false},\"mqtt\":{\"connected\":true},\"switch:0\":{\"id\":0,\"source\":\"init\",\"output\":true,\"apower\":-25.1,\"voltage\":240.8,\"freq\":50,\"current\":0.17,\"pf\":0.58,\"aenergy\":{\"total\":254400.937,\"by_minute\":[348.54,324.313,317.25],\"minute_ts\":1720027500},\"ret_aenergy\":{\"total\":254400.937,\"by_minute\":[348.54,324.313,317.25],\"minute_ts\":1720027500},\"temperature\":{\"tC\":43.1,\"tF\":109.6}},\"switch:1\":{\"id\":1,\"source\":\"SHC\",\"output\":true,\"apower\":-17.9,\"voltage\":240.9,\"freq\":50,\"current\":0.151,\"pf\":0.55,\"aenergy\":{\"total\":220036.408,\"by_minute\":[282.932,266.59,270.125],\"minute_ts\":1720027500},\"ret_aenergy\":{\"total\":220036.404,\"by_minute\":[282.932,266.59,270.125],\"minute_ts\":1720027500},\"temperature\":{\"tC\":43.1,\"tF\":109.6}},\"switch:2\":{\"id\":2,\"source\":\"init\",\"output\":true,\"apower\":-4.5,\"voltage\":240.9,\"freq\":50,\"current\":0.109,\"pf\":0.26,\"aenergy\":{\"total\":90423.149,\"by_minute\":[123.189,127.169,116.267],\"minute_ts\":1720027500},\"ret_aenergy\":{\"total\":88581.894,\"by_minute\":[123.189,127.169,116.267],\"minute_ts\":1720027500},\"temperature\":{\"tC\":43.1,\"tF\":109.6}},\"switch:3\":{\"id\":3,\"source\":\"MQTT\",\"output\":false,\"apower\":0,\"voltage\":240.8,\"freq\":50,\"current\":0,\"pf\":0,\"aenergy\":{\"total\":1077.815,\"by_minute\":[0,0,0],\"minute_ts\":1720027500},\"ret_aenergy\":{\"total\":0,\"by_minute\":[0,0,0],\"minute_ts\":1720027500},\"temperature\":{\"tC\":43.1,\"tF\":109.6}},\"sys\":{\"mac\":\"0CB815FB4FE4\",\"restart_required\":false,\"time\":\"19:25\",\"unixtime\":1720027557,\"uptime\":3820965,\"ram_size\":241624,\"ram_free\":96128,\"fs_size\":524288,\"fs_free\":192512,\"cfg_rev\":41,\"kvs_rev\":0,\"schedule_rev\":0,\"webhook_rev\":0,\"available_updates\":{\"stable\":{\"version\":\"1.3.3\"}},\"reset_reason\":3},\"ui\":{},\"wifi\":{\"sta_ip\":\"192.168.178.192\",\"status\":\"got ip\",\"ssid\":\"FRITZ!Box Fon WLAN 7390\",\"rssi\":-82},\"ws\":{\"connected\":true}}";

    // Specify content-type header
    http.begin(serverUrl);
    http.addHeader("Content-Type", "application/json");

    // Send HTTP POST request
    int httpResponseCode = http.POST(jsonData);

    if (httpResponseCode > 0) {
      String response = http.getString();
      Serial.println(httpResponseCode);
      Serial.println(response);
    } else {
      Serial.print("Error on sending POST: ");
      Serial.println(httpResponseCode);
    }

    // Free resources
    http.end();
  } else {
    Serial.println("WiFi Disconnected");
  }
}

1 „Gefällt mir“

Also ich würd gern das hier als JSON übertragen:
Das ist jetzt nicht 1:1 der JSON von @Poolcat aber ich bin grad nicht an „meinem“ PC und hab den orignalen JSON-Output nicht, aber generell geht es ziemlich genau um diesen JSON hier:

{
    "relays": [
        {
            "ison": false,
            "has_timer": false,
            "timer_started": 0,
            "timer_duration": 0,
            "timer_remaining": 0,
            "overpower": false,
            "is_valid": true,
            "source": "http"
        }
    ],
    "emeters": [
        {
            "power": 0,
            "pf": 0,
            "current": 0,
            "voltage": 0,
            "is_valid": true,
            "total": 0,
            "total_returned": 0
        },
        {
            "power": 0,
            "pf": 0,
            "current": 0,
            "voltage": 0,
            "is_valid": true,
            "total": 0,
            "total_returned": 0
        },
        {
            "power": 0,
            "pf": 0,
            "current": 0,
            "voltage": 0,
            "is_valid": true,
            "total": 0,
            "total_returned": 0
        }
    ],
    "total_power": 67.50,
    "fs_mounted": true
}

Danke für deine Code, @Micha
Das ist ja nicht nur ein Beispiel, sondern direkt der ganze Code, mit MQTT-Behandlung :smiley:
:+1:

Ich versteh aber nicht, wie ich das JSON aufbauen soll/muss.
Hier z.B. in deinem ersten Code:

grafik

Muss ich die Variable „jsonDoc“ einzeln zusammenbauen?
Bei mir mit „relays“ und „emeters“? Und wie komme ich dann „eine Ebene nach unten“, also zu den Einträgen wie „current“ und „voltage“?

Ich hatte gedacht (ok, gehofft), dass ich das JSON einfach als String übergeben kann und nur durch die Info des Übertragungstyps (application/json) vorgebe, dass der String als JSON behandelt werden soll.
Aber so einfach ist es dann wohl doch nicht…

Im dritten Beispiel von dir wird zwar ein String übertragen, aber da bin ich mir noch nicht sicher, wie der aufgebaut wird → ist da einfach jedes Leerzeichen/Zeilenumbruch durch einen Backslash ersetzt?

Wenn ich mir das so anschaue, ist das „aufbauen“ durch das JSON-Object wohl schlauer, oder?
Ich muss ja auch die Inhalte (Werte) innerhalb des JSON jedes Mal dynamisch ändern.

Was meinst du?

Inspiriert von deinem Code, hab ich mir das jetzt mal angeschaut.

Also, ganz verstanden hab ich es noch nicht, aber ich glaube, es macht Sinn alles damit aufzubauen, oder?
Und die verschiedenen Level werden dann durch Arrays abgebildet, oder?

Ich hab jedenfalls mal den Assistent für „deserialize“ ausprobiert und da sieht es für mich so aus.
Und langsam versteh ich auch, warum im Code „serialize“ steht…

1 „Gefällt mir“

Grundsätzlich kannst du es als String umsetzen dazu musst du nur eine variablen in den String einfügen und senden.
Denke das es für deine Anwendung durchaus reicht.
Die Lesbarkeit ist natürlich bei einem String etwas begrenzt aber wenn du es einmal gemacht hast ist es ja fertig.

2 „Gefällt mir“

Alles klar, ich werd es auf jeden Fall mal ausprobieren.
Erstes Ziel: eine vernünftig formatierte JSON-Ausgabe zu erzeugen.
Und dann überleg ich mal, welche Variante mir besser gefällt :slight_smile:

Viele Grüße

Okay, „fertig“ würd ich mal sagen :slight_smile:

Hab es jetzt (doch) mit dem JsonObject gelöst, weil das einfacher übersichtlicher ist (wenn man einmal verstanden hat, wie das funktioniert) und auch die „Datenübergabe“ ist dann schön übersichtlich und leicht änderbar.

Hier mal der entscheidende Code-Auszug:

void handleJson() {
  // Setze den Status auf true, wenn die /status Route aufgerufen wird
  statusReceived = true;
  
  // Überprüfen, ob der Status empfangen wurde
  if (statusReceived) {
    // Erstellen eines JSON-Dokuments
    StaticJsonDocument<768> doc;

    JsonObject relays_0 = doc["relays"].add<JsonObject>();
    relays_0["ison"] = false;
    relays_0["has_timer"] = false;
    relays_0["timer_started"] = 0;
    relays_0["timer_duration"] = 0;
    relays_0["timer_remaining"] = 0;
    relays_0["overpower"] = false;
    relays_0["is_valid"] = true;
    relays_0["source"] = "http";

    JsonArray emeters = doc["emeters"].to<JsonArray>();

    JsonObject emeters_0 = emeters.add<JsonObject>();
    emeters_0["power"] = power_1;
    emeters_0["pf"] = pf_1;
    emeters_0["current"] = current_1;
    emeters_0["voltage"] = voltage_1;
    emeters_0["is_valid"] = true;
    emeters_0["total"] = 0;
    emeters_0["total_returned"] = 0;

    JsonObject emeters_1 = emeters.add<JsonObject>();
    emeters_1["power"] = power_2;
    emeters_1["pf"] = pf_2;
    emeters_1["current"] = current_2;
    emeters_1["voltage"] = voltage_2;
    emeters_1["is_valid"] = true;
    emeters_1["total"] = 0;
    emeters_1["total_returned"] = 0;

    JsonObject emeters_2 = emeters.add<JsonObject>();
    emeters_2["power"] = power_3;
    emeters_2["pf"] = pf_3;
    emeters_2["current"] = current_3;
    emeters_2["voltage"] = voltage_3;
    emeters_2["is_valid"] = true;
    emeters_2["total"] = 0;
    emeters_2["total_returned"] = 0;
    doc["total_power"] = (power_1 + power_2 + power_3);
    doc["fs_mounted"] = true;

    // Serialisieren des JSON-Dokuments in einen String
    String json;
    serializeJson(doc, json);

    // Senden der JSON-Antwort
    server.send(200, "application/json", json);
    
    // Status zurücksetzen
    statusReceived = false;
  } else {
    // Falls der Status nicht empfangen wurde, wird eine Fehlermeldung gesendet
    server.send(403, "text/plain", "Unauthorized: Please access /status first");
  }
}  

Ganz am Anfang frag ich ab, ob es einen GET mit „/status“ gab.
Falls ja, wird der JSON zusammengebaut und dann anschließend zurückgegeben.

Die einzelnen Werte (Strom, Spannung, Leistung…) werden mir alle per MQTT übergeben und dann direkt eingefügt.

Läuft :slight_smile:
Und läuft bisher auch recht stabil.

Dankeschön noch einmal für die ganze Hilfe!

@Poolcat und @Micha
Ich würd den Code dann auch „öffentlich“ machen, oder spricht da was dagegen?

Viele Grüße

4 „Gefällt mir“

Ja sehr sehr cool! Ist aus meiner sicht alles public domain, kannst du also gern veröffentlichen. Macht denn der stromwächter jetzt mehr als die 800w?

Edit: Wichtig - bevor du es öffentlich stellst, bau bitte noch einen totmann-schalter ein - falls du die Werte ebenfalls leicht abänderst auf dem esp. Das hebelt ja die Sicherheitsabschaltung vom wächter aus und dann muss der esp wenn bspw länger als 30 sekunden keine neuen mqtt werte empfangen wurden, das system auf ungültig setzen.

Jein :slight_smile:
Generell ist die Idee vom Stromwächter (jetzt mal so ganz grob), dass man sich eine PV-Anlage aufbauen kann, die eigentlich „überdimensioniert“ ist, also z.B. 4 kWp, damit man im Winter bei tiefer Sonne auch noch etwas Ertrag bekommt.
In diesem Fall würde man aber (zumindest teilweise) in sonnigen Zeiten eine totale Überproduktion haben und z.B. mehr als die erlaubten 600/800 W ins Netz schieben.

Da kommt dann der Stromwächter ins Spiel, der die ins Hausnetz einspeiste Leistung dann soweit reduziert, dass man unter den Grenzwerten bleibt.
Angefangen hat es mit dem Gedanken „Nulleinspeisung“, ging dann aber (wohl) hoch Richtung 600/800 W bzw. komplett dynamisch bzw. selbst bestimmter Grenze.
Das ist (so wie ich das sehe) aber noch nicht vollständig implementiert bzw. hängt vom Wechselrichter ab.
Dazu kommt noch, dass wohl eine Funktion eingebaut ist, die berechnet (und sicherstellen soll), dass man nicht zu viel Leistung über eine Leitung schiebt.
Beispiel: In irgendeinen Raum macht jemand drei Haartrockner (ALF lässt grüßen…) und einen Heizstrahler an. Da würd es normalerweise die Sicherung raushauen, aber es könnte ja ein großer Teil von der PV-Anlage erzeugt worden sein. Dann fließt aber trotzdem viel (eventuell zu viel Strom) über die Leitung und die Sicherung im Sicherungskasten bekommt davon nichts mit. Das könnte zu Überhitzung der Stromleitungen führen, mit allen möglichen Konsequenzen.
Durch den Stromwächter wird überwacht, dass dies nicht geschehen kann (wie genau, weiß ich grad nicht, ist aber irgendwo auf deren Homepage beschrieben :slight_smile: ).

Gute Idee, aber ich glaub, das brauch ich nicht mehr, weil ich diese dynamische (zufällige Änderung) der Werte im node-red-Code und im ESP-Code schon rausgenommen hab.
Mir war das zu speziell auf meinen Fall zugeschnitten und betrifft die meisten Anwender wohl gar nicht und verwirrt wohl eher :smiley:
Ich übergeb daher direkt von MQTT-topic den Powerfaktor-Wert.

In MEINEM Fall hab ich diesen jetzt auf meinem Hauptsystem selbst erstellt und einem MQTT-topic zugeordnet, weil ich diesen Wert ja von meiner Stromzange nicht bekomme.
Aber das ist ja dann „mein Bier“ und der Code (den dann eventuell andere User verwenden können) bietet diese Funktionalität gar nicht.
Sollte also eigentlich passen, oder?

Viele Grüße

1 „Gefällt mir“

Öhm Nein. Man kann problemlos eine bspw bei mir damals 3kwp an einen 600w wr koppeln (oder einen grösseren wr limitieren). Es werden max 600w eingespeist und alles ist tutti. Der stromwächter hingegen hat den flair, dass das maximal leitungsmögliche (also bis 3.6kw) eingespeisst werden können; ohne das man vor den von dir geschilderten fällen angst haben muss. Er regelt dann selber runter sobald es nötig ist. Das ist für die interessant, die baulich nix ändern können aber trotzdem geprüft mehr als die aktuell erlaubten 0.8kw einspeisen wollen.

Das andere - wenn du die werte einfach durchreichst, passt es soweit. Nur wiegesagt achtung mit dem pf. Änderst du den bspw auf 0.1 werden deine leitungen überlastet werden. Ein fettes do not touch sollte dort hin. Jeder, der in der lage ist, die wurzel aus -1 zu ziehen, kann das ausrechnen :wink:

Stimmt, du hast Recht. Ich war mir gestern ehrlich gesagt nicht sicher, ob diese Funktion jetzt wirklich schon „durch“ ist, oder wie grad der Stand ist.

:+1: