Hallo,
ich versuche mich auch an einer Webseite.
Verzweifel aber.
Kann mir jemand behilflich sein. Möchte den % Wert gerne als Sensor.
Hallo,
ich versuche mich auch an einer Webseite.
Verzweifel aber.
Kann mir jemand behilflich sein. Möchte den % Wert gerne als Sensor.
Hallo Malti,
ich mache das mit Node-RED,
http://IP-Adresse deines Speichers/stat
wenn du Unterstützung brauchst bitte melden…
by HarryP: Zusammenführung Doppelpost (bei Änderungen oder hinzufügen von Inhalten bitte die „Bearbeitungsfunktion“ anstatt „Antworten“ zu nutzen)
Hallo Community,
wir haben für diese Geschichte einen kleinen Controller basierend auf ESPHome entwickelt, der nicht nur den SoC als Sensor ausliest und in Home Assistant bekannt macht sondern auch …
… Sensoren für:
… zur Verfügung stellt. Die Gründe das Ganze in einen ESP32 auszulagern sind:
@Andreas59: hast du herausbekommen wie man eine Battery flex steuert? Würde dem Ding gerne sage “lade jetzt mit xxx Watt”. Vielen Dank für deine Hilfe
Guten Morgen Albrecht,
ich habe für meine PV-Anlage keinen Energy Manager von Solarwatt, daher weis ich nicht was jener für Daten zum BatteryFlex ausgiebt.
Ich lese die Daten des Speichers und der Packs mit NodeRED aus. Den Speicher erreiche ich über seine IP-Adresse/stat und die Packs IP-Adresse/pack?p=0 bzw. =1 für das Zweite Pack und so weiter. Die Daten liegen in JSON-Format vor.
Nun zu deiner eigentlichen Frage, ich glaube nicht das man in die direckte Steuerung / Regelung eingereifen kann.Nach meinen Erfahrungen mit Solarwatt sind sie mit Informationen sehr sparsam.
Wie man mir bei einem persönichen Gespräch bei Solarwatt erklärt hat ist die Steuerung sehr komplex so das der Endkunde da nicht eingreifen soll auch wegen der Garantie. Ob man was beim Energy Manager einstellen kann weis ich nicht, da ich keinen habe. Hast du einen?
Mann hat mir nicht mal sagen können was die Werte des SysStatus aussagen.
Das ist die Oberfläche vom Speicher über siene IP-Adresse.
Das andere kommt mit IP-Adresse/…
@Andreas59 : vielen Dank für die prompte Antwort. Dieser Punkt …
>>>>
… habe für meine PV-Anlage keinen Energy Manager von Solarwatt …
>>>>
… ist ja sehr interessant! Wie wird ohne einen Energy Manager, dann das Laden des Akkus gesteuert ODER(!) ist die Battery flex autonom und steuert den Ladevorgang über den AC-Sensor …
… selbst? Deine Aussage legt letzteres nahe … damit wäre der Energy Manager nicht das was er vorgibt zu sein, sondern vermutlich lediglich ein leider sehr, sehr teurer, Proxy ![]()
>>>>
Wie man mir bei einem persönichen Gespräch bei Solarwatt erklärt hat ist die Steuerung sehr komplex so das der Endkunde da nicht eingreifen soll auch wegen der Garantie.
>>>>
Diese Story bekommen wir auch immer zu hören … um fair zu sein: den Teil mit der Garantie kann ich nachvollziehen. Der Eindruck, der bei uns nach einem solchen Gespräch aber immer hängen bleibt ist der, dass die Leute von Solarwatt schlicht sehr wenig Ahnung haben, leider auch nicht von ihrem eigenen System. Wie das Ganze zusammenspielt bzw. zusammenspielen soll, konnten sie bisher nicht wirklich erklären ![]()
>>>>
Hast du einen?
>>>>
Ja, bei uns gibt es einen Energy Manager flex …
… der …
… und keiner kann genau erklären, was das Ding eigentlich macht. Was er nicht macht/kann ist:
… wenn bei dir kein Energy Manager am Laufen ist und du sagst, dass die Batterie trotzdem lädt, heißt das für uns: wir können den Energy Manager eigentlich abschalten, da er keine Funktion erfüllt.
>>>>
Mann hat mir nicht mal sagen können was die Werte des SysStatus aussagen.
>>>>
Passt voll ins Bild: uns wurde erklärt, dass sie keine Software machen würden sonder sich “nur” um die “Funktion” kümmern … was immer das auch heißen mag ![]()
Könntest du bitte bestättigen, dass bei dir kein Energy Manager von Solarwatt läuft und das Ganze trotzdem funktioniert. Dann ziehen wir unserem Energy Manager flex entgültig den Stecker …
um die ganze sache besser zu verstehen, ich bin gelernter Mess- Steuer- und Regelungstechniker kenne mich alse ein wenig mit Strom und Spannung aus.
2023 die erste PV-Anlage mit 10,9 kWp mit Süd West Ausrichtung errichten lassen, ohne Energy Manager, da mir die im Handbuch aufgeführten Komponenten die er managen kann zu übersichtlich (mager) war. Geschirrspühler und Waschmaschine kann er sowie so nicht. Zu diesem Zeitpunkt habe ich mich dann mit HA beschäftigt um den Kaco-Wechselrichter im lokalen LAN zu loggen. Geht mit HA und der SunSpec Integration über Mod-Bus. Im Dez. 2023 noch ein BatterieFlex AC1 (2 Packs 4800 Wh) installieren lassen, weil die Hausgeräte (die elek. Heizleistung lässt sich nicht steuern) es nicht interessiert wie viel PV vom Dach kommt. Mit Speicher kann mann das gut ausgleichen.
Der Batteriespeicher ist mit dem Solarwatt AC-Sensor über CAN-Bus verbunden und hat intern sein Batteriemamagement.
Die Netzleistung die vom AC-Sensor ermittelt wird ist entscheidend ob die Batterie geladen wird, bei PV-Überschuss oder ob entladen wird bei Netzbezug. Der Wert pendelt zwischen -10 und +10W solange was im Speicher ist bzw. genügend vom Dach kommt.
Im Okt. 2042 ein zweite PV-Anlage mit 4,5 kWp mit Nor Ost Ausrichtung angeschafft weil im Sommerhalbjahr Vormittags nur wenig PV-Leistung gegeben war.
Wenn ich jetzt bezüglich meiner Fragen bei Solarwatt ober bei meinem Installateur angerufen habe war die erste Frage immer haben sie keinen Energy Manager ich kann ihre Anlage nicht sehen. Mein Installateur meinte den braucht mann nur damit mann über die App mobil die Anlage sehen kann. Ich mache alles mit HA.
entweder mit Grafana oder
HA
das sind Werte aus dem Speicher mittels NodeRed,
das sind die Sensoren der Packs, aber keine Ahnung was sie bedeuten,
Es gibt z.b. ca. 10verschieden Batterie SysStatus Meldungen, als Dezimalwert oder in der Web-Oberfläche als Hex-Wert aber es kann oder will mir keiner sagen was sie bedeuten.
Zeigt dein Energy Manager Status Meldungen der Batterie an?
Hallo @Andreas59,
danke für deine Antwort. Das …
>>>>
Die Netzleistung die vom AC-Sensor ermittelt wird ist entscheidend ob die Batterie geladen wird, bei PV-Überschuss oder ob entladen wird bei Netzbezug.
>>>>
… und …
>>>>
Mein Installateur meinte den braucht mann nur damit mann über die App mobil die Anlage sehen kann.
>>>>
… war die “befürchtete”, wenn auch erwartete Antwort
. Damit ist dieses sehr teure Ding nur ein überflüssiger Verbraucher. Fühlen uns nun noch mehr von Solarwatt “verschauckelt”
…
Auch wir machen, nach nur drei Monaten mit der Solarwatt App, alles über Home Assistant hier z.B. Teile unseres Solar Dashboards …
… und haben die App de-installiert.
Kurze Erläuterung: das Dashboard sieht im Moment nicht so beeindruckend aus, da es Nacht ist und unsere PV-Anlage nichts produziert ![]()
>>>>
Zeigt dein Energy Manager Status Meldungen der Batterie an?
>>>>
Nein, das UI des Energy Managers …
… dient ausschließlich der Konfiguration, was direkt nach Installation des Managers die Alarmglocken läuten lies, da wir erwartet hatten, dass da …
… angezeigt werden, bzw. “Regeln” wie “wenn PV Überschuss, dann E-Bike laden” eingegeben werden können (machen wir in der Zwischenzeit über evcc
). Hatten extra mehrfach während des Verkaufsgesprächs nachgefragt: funktioniert das Ganze, wenn das Internet weg ist? Können wir unsere Anlage steuern? Klare Aussage: “aber natürlich” … tja … Satz mit X ![]()
Damit wird dem Ding der Stecker gezogen, am Internet hängt es sowieso schon länger nicht mehr, da Solarwatt, ohne vorherige Ankündigung, irgendwelche Software updates eingespielt hat, die unser Gesamtsystem disfunktional machten.
Nochmals Danke für deine Antworten. Dank des weiter oben erwähnten ESPHome basierten Controllers erhalten wir nun Werte wie SoC usw. direkt von der Batterie und können damit …
… vielleicht nimmt Solarwatt das Ding ja zurück … es ja noch nicht sonderlich alt ![]()
Gehe ich Richtig in der Annahme das du außer dem AC-Sensor der den Speicher mit Daten versorgt noch einen weiteren Sensor für Strom/Leistung (Shelly o.ä.) nach bem Zähler des Energieversorges hast, der den Energy Manager mit Daten füttert?
Hallo Andreas59,
>>>>
Gehe ich Richtig in der Annahme das du außer dem AC-Sensor der den Speicher mit Daten versorgt noch einen weiteren Sensor für Strom/Leistung (Shelly o.ä.) nach bem Zähler des Energieversorges hast, der den Energy Manager mit Daten füttert?
>>>>
Bingo
… es gibt …
… und es wird noch einen Shelly …
… damit hätte wir alle Sensoren zusammen um das, was ursprünglich der Energy Manager von Solarwatt geliefert hat selbst zu ersetzen
und da ja, nach deiner Aussage, die Battery flex selbständig über Laden bzw. Entladen entscheiden, kann der Energy Manager abgeschaltet werden. Es ist zwar sehr, sehr ärgerlich wenn ich an die über 2200€ denke, die wir für diese Box bezahlt haben … “C’est la vie”
. Angesichts dieser neuen und der vielen anderer negativen Erfahrungen, die wir mit Solarwatt gemacht haben, möchten wir niemals mehr etwas mit ihnen zu tun haben ![]()
Möchte aber nicht weiter auf unsere Probleme mit Solarwatt eingehen sondern lieber unseren ESPHome basierten Battery flex Controller vorstellen, der eine eigene Battery flex Komponente sowie asynchrone Kommunikation besitzt ![]()
Hallo Albrecht, ich habe mir heute mal deinen Code auf Github an geschaut, das sieht ja so ähnlich aus wie ich es mit NodeRed mache, nur ohne ESP23. Das Intervall für den HTTP-request auf die Daten /stat habe ich auf 1s und bei den Daten /pack?… habe ich auf 5s, habe bis jetzt keine Probleme damit. Bei 3 Paks sind das in Summe 164 Entitäten.
Hallo Community,
war längere Zeit mit etwas anderem beschäftigt … aber zurück zum ESPHome basierten Battery Flex Controller
. Wie schon im ersten Beitrag geschrieben ist das Ziel dieses Controllers die Möglichkeit zu schaffen Dinge wie:
in einem standardisierten Format direkt, also ohne zu Hilfenahme von REST Calls an den Energy Managers von Solarwatt, auszulesen und zwar so, dass Home Assistant selbst minimal belastet wird. Deshalb ein eigener Controller basierend auf ESPHome.
Um das Ganze ans Laufen zu bringen wurde eine ESPHome Custom Component definiert, die sich im YAML File wie folgt manifestiert:
external_components:
- source: ./components
Durch diesen Abschnitt weist man ESPHome an, lokale Komponenten aus dem Verzeichnis ./components einzubinden. Der große Vorteil:
Um die Verarbeitung von Nachrichten sauber vom internen Scheduling von ESPHome zu entkoppeln, setzt der Controller auf AsyncTCP. Der Einstieg erfolgt über:
platformio_options:
lib_deps:
- me-no-dev/AsyncTCP
Damit läuft die Kommunikation asynchron:
Bottom line: AsyncTCP sorgt dafür, dass der Controller Nachrichten „nebenbei“ abarbeitet, ohne den Takt von ESPHome durcheinanderzubringen. ![]()
Damit die Sensoren der Custom Component überhaupt in Home Assistant auftauchen, reicht der reine C++‑Code leider nicht. Es braucht zusätzlich ein __init__.py (macht das Ganze zu einem gültigen ESPHome‑Modul), das in diesem Fall wie folgt aussieht:
# __init__.py
# BatteryFlex custom component package
from . import sensor
CONFIG_SCHEMA = sensor.CONFIG_SCHEMA
to_code = sensor.to_code
… sowie ein sensor.py. Letzteres definiert das YAML‑Schema und verknüpft die Einträge direkt mit der C++‑Welt.
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_NAME
from esphome.components import sensor
battery_flex_ns = cg.esphome_ns.namespace('battery_flex')
BatteryFlexSensor = battery_flex_ns.class_('BatteryFlexSensor', cg.Component)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(BatteryFlexSensor),
cv.Required("grid_power_raw"): cv.use_id(sensor.Sensor),
cv.Required("battery_power_raw"): cv.use_id(sensor.Sensor),
cv.Required("battery_soc"): cv.use_id(sensor.Sensor),
cv.Required("battery_soh"): cv.use_id(sensor.Sensor),
cv.Required("battery_capacity"): cv.use_id(sensor.Sensor),
cv.Required("battery_operation"): cv.use_id(sensor.Sensor),
})
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
grid = await cg.get_variable(config["grid_power_raw"])
cg.add(var.set_grid_power_raw(grid))
bat = await cg.get_variable(config["battery_power_raw"])
cg.add(var.set_battery_power_raw(bat))
soc = await cg.get_variable(config["battery_soc"])
cg.add(var.set_battery_soc(soc))
soh = await cg.get_variable(config["battery_soh"])
cg.add(var.set_battery_soh(soh))
cap = await cg.get_variable(config["battery_capacity"])
cg.add(var.set_battery_capacity(cap))
operation = await cg.get_variable(config["battery_operation"])
cg.add(var.set_battery_operation(operation))
Der eigentlich C++ Code ist dann straight forward
. Es wird eine BatteryFlexSensor Klasse definiert …
#pragma once
#include <array>
#include <string>
#include <AsyncTCP.h>
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "Buffer.hpp"
namespace esphome
{
namespace battery_flex
{
class BatteryFlexSensor : public PollingComponent //, public sensor::Sensor
{
public:
inline static const std::string_view IP_BATTERY_FLEX = "solar-batterie.fritz.box";
inline static const std::string_view HTTP_COMMAND_GET = "GET";
inline static const std::string_view HTTP_STATUS_TARGET = "/stat";
inline static const std::string_view HTTP_PACK_TARGET = "/pack?p=";
inline static const std::string_view HTTP_PROTOCOL = "HTTP/1.1";
inline static const std::string_view HTTP_EOL = "\r\n";
inline static const std::string_view HTTP_HEADER_TERMINATOR = Buffer::HTTP_HEADER_TERMINATOR;
inline static const std::string_view HTTP_HOST = "Host:";
inline static const std::string_view HTTP_CONNECTION = "Connection: close";
inline static const std::string_view HTTP_ACCEPT_ANY = "Accept: */*";
inline static constexpr int HTTP_PORT = 80;
inline static constexpr int HTTP_OK = 200;
inline static constexpr int PERIOD_MS = 1000; // polling interval
inline static constexpr int PERIOD_PACK = 5;
inline static constexpr size_t NUM_PACKS = 6;
inline static constexpr float PACK_CAPACITY = 2400; // capacity per pack in Wh
inline static constexpr float HOURS_IN_SEC = 3600;
BatteryFlexSensor();
void setup() override;
void update() override;
void set_grid_power_raw(sensor::Sensor* p_sensor);
void set_battery_power_raw(sensor::Sensor* p_sensor);
void set_battery_soc(sensor::Sensor* p_sensor);
void set_battery_soh(sensor::Sensor* p_sensor);
void set_battery_capacity(sensor::Sensor* p_sensor);
void set_battery_operation(sensor::Sensor* p_sensor);
private:
struct PackData
{
std::string m_serialNumber;
float m_soh;
};
void setupClientStatus();
void setupClientBatteryPacks();
void scheduleClientStatus();
void scheduleClientBatteryPacks();
// members to deal with status information
std::string m_httpCommandStatus;
Buffer m_bufferStatus;
AsyncClient m_clientStatus;
// members to deal with detailed information
int m_timerPacks;
size_t m_indexPack;
std::array<std::string, NUM_PACKS> m_httpCommandPacks;
std::array<PackData, NUM_PACKS> m_dataPacks;
Buffer m_bufferPack;
AsyncClient m_clientPacks;
// pointers to YAML-sensors
sensor::Sensor* m_grid_power_raw_sensor;
sensor::Sensor* m_battery_power_raw_sensor;
sensor::Sensor* m_battery_soc_sensor;
sensor::Sensor* m_battery_soh_sensor;
sensor::Sensor* m_battery_capacity_sensor;
sensor::Sensor* m_battery_operation_sensor;
// calculated values
float m_soh;
};
} // namespace battery_flex
} // namespace esphome
… die dann die eigentliche Arbeit bzw. die Kommunikation zur Battery Flex übernimmt:
#include <string>
#include "esphome/core/hal.h"
#include "battery_flex.hpp"
namespace esphome
{
namespace battery_flex
{
template<typename TypeValue>
static void
publishSensor
(const TypeValue p_value,
const char* p_name,
sensor::Sensor* p_sensor)
{
if (nullptr != p_name)
{
if (nullptr != p_sensor)
{
p_sensor->publish_state(p_value);
}
else
{
ESP_LOGW("battery_flex", "readValue: sensor '%s' not bound", p_name);
}
}
else
{
ESP_LOGW("battery_flex", "readValue: got invalid name");
}
}
template<typename TypeValue>
static void
publishSensor
(const JsonDocument& p_doc,
const char* p_name,
sensor::Sensor* p_sensor)
{
TypeValue actValue = p_doc[p_name].as<TypeValue>();
publishSensor<TypeValue>(actValue, p_name, p_sensor);
}
BatteryFlexSensor::BatteryFlexSensor
() :
PollingComponent(PERIOD_MS),
m_indexPack(0),
m_grid_power_raw_sensor(nullptr),
m_battery_power_raw_sensor(nullptr),
m_battery_soc_sensor(nullptr),
m_battery_soh_sensor(nullptr),
m_battery_capacity_sensor(nullptr),
m_battery_operation_sensor(nullptr),
m_soh(100.0)
{
// nothing to be done here
}
void
BatteryFlexSensor::setup
()
{
setupClientStatus();
setupClientBatteryPacks();
}
void
BatteryFlexSensor::update
()
{
scheduleClientStatus();
scheduleClientBatteryPacks();
}
void
BatteryFlexSensor::set_grid_power_raw
(sensor::Sensor* p_sensor)
{
m_grid_power_raw_sensor = p_sensor;
}
void
BatteryFlexSensor::set_battery_power_raw
(sensor::Sensor* p_sensor)
{
m_battery_power_raw_sensor = p_sensor;
}
void
BatteryFlexSensor::set_battery_soc
(sensor::Sensor* p_sensor)
{
m_battery_soc_sensor = p_sensor;
}
void
BatteryFlexSensor::set_battery_soh
(sensor::Sensor* p_sensor)
{
m_battery_soh_sensor = p_sensor;
}
void
BatteryFlexSensor::set_battery_capacity
(sensor::Sensor* p_sensor)
{
m_battery_capacity_sensor = p_sensor;
}
void
BatteryFlexSensor::set_battery_operation
(sensor::Sensor* p_sensor)
{
m_battery_operation_sensor = p_sensor;
}
void
BatteryFlexSensor::setupClientStatus
()
{
// setup command to access status of battery
m_httpCommandStatus = std::string(HTTP_COMMAND_GET) + " " + std::string(HTTP_STATUS_TARGET) + " " + std::string(HTTP_PROTOCOL) + std::string(HTTP_EOL)
+ std::string(HTTP_HOST) + " " + std::string(IP_BATTERY_FLEX) + std::string(HTTP_EOL)
+ std::string(HTTP_CONNECTION) + std::string(HTTP_HEADER_TERMINATOR);
// setup callback for connect
m_clientStatus.onConnect
([this]
(void* p_arg, AsyncClient* p_client)
{
p_client->write(m_httpCommandStatus.c_str());
},
nullptr);
// setup callback for receiving data
m_clientStatus.onData
([this]
(void* p_arg, AsyncClient* p_client, void* p_data, size_t p_length)
{
// add received data to buffer
Buffer::Status actStatus = m_bufferStatus.append(p_data, p_length);
// check for overflow
if (Buffer::Status::SUCCESS != actStatus)
{
ESP_LOGW("battery_flex", "m_clientStatus.onData: Buffer overflow, closing Client");
// ensure, that client is available
if (nullptr != p_client)
{
p_client -> close();
}
else
{
ESP_LOGW("battery_flex", "m_clientStatus.onData: unable to close Client");
}
}
},
nullptr);
m_clientStatus.onDisconnect
([this]
(void* p_arg, AsyncClient* p_client)
{
// get access to payload
const char* actPayload = m_bufferStatus.getPayload();
// check if payload is available
if (nullptr != actPayload)
{
// read values from JSON Object
JsonDocument actDoc;
DeserializationError actError = deserializeJson(actDoc, actPayload);
if (!actError)
{
publishSensor<int>(actDoc, "SoC", m_battery_soc_sensor);
publishSensor<float>(actDoc, "PGrid", m_grid_power_raw_sensor);
publishSensor<float>(actDoc, "PBat", m_battery_power_raw_sensor);
}
else
{
ESP_LOGW("battery_flex", "m_clientStatus.onDisconnect: unable to deserialize Json: %s", actError.c_str());
}
}
else
{
ESP_LOGW("battery_flex", "m_clientStatus.onDisconnect: unable to detect payload");
}
// finally clear buffer
m_bufferStatus.clear();
},
nullptr);
}
void
BatteryFlexSensor::setupClientBatteryPacks
()
{
// setup commands to get information of individual battery pack
for (int actIndex = 0; actIndex < NUM_PACKS; actIndex++)
{
// setup command to GET data related to pack
char actBuffer[16];
itoa(actIndex, actBuffer, 10);
m_httpCommandPacks[actIndex] = std::string(HTTP_COMMAND_GET) + " " + std::string(HTTP_PACK_TARGET) + actBuffer + " " + std::string(HTTP_PROTOCOL) + std::string(HTTP_EOL)
+ std::string(HTTP_HOST) + " " + std::string(IP_BATTERY_FLEX) + std::string(HTTP_EOL)
+ std::string(HTTP_CONNECTION) + std::string(HTTP_EOL)
+ std::string(HTTP_ACCEPT_ANY) + std::string(HTTP_HEADER_TERMINATOR);
// setup State of Health related to pack to invalid
m_dataPacks[actIndex].m_soh = -1.0;
}
// setup callback for connect
m_clientPacks.onConnect
([this]
(void* p_arg, AsyncClient* p_client)
{
p_client->write(m_httpCommandPacks[m_indexPack].c_str());
},
nullptr);
// setup callback for receiving data
m_clientPacks.onData
([this]
(void* p_arg, AsyncClient* p_client, void* p_data, size_t p_length)
{
// add received data to buffer
Buffer::Status actStatus = m_bufferPack.append(p_data, p_length);
// check for overflow
if (Buffer::Status::SUCCESS != actStatus)
{
ESP_LOGW("battery_flex", "m_clientPacks.onData: Buffer overflow, closing Client");
// ensure, that client is available
if (nullptr != p_client)
{
p_client -> close();
}
else
{
ESP_LOGW("battery_flex", "m_clientPacks.onData: unable to close Client");
}
}
},
nullptr);
m_clientPacks.onDisconnect
([this]
(void* p_arg, AsyncClient* p_client)
{
// get access to payload
const char* actPayload = m_bufferPack.getPayload();
// check if payload is available
if (nullptr != actPayload)
{
// read values from JSON Object
JsonDocument actDoc;
DeserializationError actError = deserializeJson(actDoc, actPayload);
if (!actError)
{
m_dataPacks[m_indexPack].m_serialNumber = actDoc["PK"]["SN"].as<std::string>();
m_dataPacks[m_indexPack].m_soh = actDoc["PK"]["SOH"].as<float>();
// calculate overall SoH of battery
m_soh = std::min(m_soh, m_dataPacks[m_indexPack].m_soh);
// publish SoH
publishSensor<float>(m_soh, "SoH", m_battery_soh_sensor);
// calculate remaining capacity of battery
float actCapacity = NUM_PACKS * PACK_CAPACITY * m_soh / 100;
// publish remaining capacity
publishSensor<float>(actCapacity,"Capacity", m_battery_capacity_sensor);
// calculate opererating hours of battery
float actOperatingHours = actDoc["PK"]["OP"].as<float>() / HOURS_IN_SEC;
// publish operation hours
publishSensor<float>(actOperatingHours, "Operation", m_battery_operation_sensor);
}
else
{
ESP_LOGW("battery_flex", "m_clientPacks.onDisconnect: unable to deserialize Json: %s", actError.c_str());
}
}
else
{
ESP_LOGW("battery_flex", "m_clientPacks.onDisconnect: unable to detect payload");
}
// finally clear buffer
m_bufferPack.clear();
// move to next battery pack
m_indexPack++;
m_indexPack %= NUM_PACKS;
},
nullptr);
}
void
BatteryFlexSensor::scheduleClientStatus
()
{
// check if connection is closed
if (false == m_clientStatus.connected())
{
const std::string actAddress(IP_BATTERY_FLEX);
// re-open connection
m_clientStatus.connect(actAddress.c_str(), HTTP_PORT);
}
else
{
ESP_LOGW("battery_flex", "Client Status still connected – skipping new request");
}
}
void
BatteryFlexSensor::scheduleClientBatteryPacks
()
{
// check if ready to access battery packs
if (0 == m_timerPacks)
{
// check if connection is closed
if (false == m_clientPacks.connected())
{
const std::string actAddress(IP_BATTERY_FLEX);
// re-open connection
m_clientPacks.connect(actAddress.c_str(), HTTP_PORT);
}
else
{
ESP_LOGW("battery_flex", "Client Packs still connected – skipping new request");
}
}
m_timerPacks++;
m_timerPacks %= PERIOD_PACK;
}
} // namespace battery_flex
} // namespace esphome
… sehr schön ist hierbei das Aufsetzen der asynchronen Kommunikation zu sehen …
m_clientXYZ.onConnect ...
m_clientXYZ.onData ...
m_clientXYZ.onDisconnect ...
… in denen jeweils Callbacks für das entsprechende Verhalten der Kommunikation registriert werden.
Man kann sagen, dass dieses kleine Projekt richtig Spass gemacht hat
. Wenn es jemand nachbauen möchte: alle Sourcen liegen auf Github … viel Spaß ![]()
Hallo Community,
vergaß gestern zu erwähnen, dass das Ganze noch einen Buffer …
#pragma once
#include <string>
namespace esphome
{
namespace battery_flex
{
class Buffer
{
public:
inline static const std::string_view HTTP_HEADER_TERMINATOR = "\r\n\r\n";
inline static constexpr size_t CAPACITY_BUFFER = 2048;
enum class Status : int { SUCCESS = 0, OVERFLOW = -1 };
Buffer();
size_t size() const;
void clear();
const char* getData() const;
Status append(void* p_data, size_t p_length);
const char* getPayload() const;
private:
// disabled operations
Buffer(const Buffer&) = delete;
Buffer(Buffer&&) = delete;
Buffer& operator=(const Buffer&) = delete;
Buffer& operator=(Buffer&&) = delete;
const std::string m_http_header_terminator;
size_t m_lengthBuffer;
char m_buffer[CAPACITY_BUFFER];
};
} // namespace battery_flex
} // namespace esphome
… besitzt, der es erlaubt:
… und am Ende dafür sorgt, dass die Payload mit \0 beendet wird.
#include <algorithm>
#include <cstring>
#include "Buffer.hpp"
namespace esphome
{
namespace battery_flex
{
Buffer::Buffer
() :
m_http_header_terminator(HTTP_HEADER_TERMINATOR),
m_lengthBuffer(0)
{
// ensure, that buffer is cleared
memset(m_buffer, 0, sizeof(m_buffer));
}
size_t
Buffer::size
() const
{
return m_lengthBuffer;
}
void
Buffer::clear
()
{
m_lengthBuffer = 0;
m_buffer[0] = '\0';
}
const char*
Buffer::getData
() const
{
return (m_buffer);
}
Buffer::Status
Buffer::append
(void* p_data,
size_t p_length)
{
Status result = Status::SUCCESS;
size_t actLength = std::min(sizeof(m_buffer) - 1 - m_lengthBuffer, p_length);
// check if there's still space available
if (0 < actLength)
{
memcpy(m_buffer + m_lengthBuffer, p_data, actLength);
m_lengthBuffer += actLength;
// ensure, that buffer is terminated with '\0'
m_buffer[m_lengthBuffer] = '\0';
}
// check for buffer overflow
if (p_length > actLength)
{
result = Status::OVERFLOW;
}
return result;
}
const char*
Buffer::getPayload
() const
{
// setup default result
const char* result = nullptr;
// check if buffer holds any data
if (0 < m_lengthBuffer)
{
result = strstr(m_buffer, m_http_header_terminator.c_str());
if (nullptr != result)
{
// skip terminator
result = &result[m_http_header_terminator.length()];
}
}
return result;
}
} // namespace battery_flex
} // namespace esphome
Damit wäre die Beschreibung des Controllers komplett. Viel Spass beim Nachbauen ![]()
@Andreas59: hallo war krank, hatte einiges an Ärger mit einer KI … in der Zwischenzeit hat Solarwatt bestätigt, dass unser sehr teurer Energy Manager Flex wirklich “NICHTS” macht! Wie gesagt, wir nutzen die App von Solarwatt nicht … von daher müssen in unserem Fall keinen Daten in die Solarwatt Cloud und das wäre wohl das einzige was er in unserem Fall machen würde: “Datenschaufeln”. Werden also das Ding nun abschalten und hoffen, dass irgendwer bei dem bekannten Online Händler ihn kauft …
Ist schon ein sehr teurer Spaß für “nichts machen” ![]()
ja, ich war schon immer sehr interessiert seit der Smart Home Entwicklung wie die Geräte aller Hersteller mit einander kommunizieren und schon da ist mir aufgefallen das es oft nur Insellösungen von verschiedenen Herstellern gibt.
Hatte dann den Einstieg mit Homematic IP, war aber auch nur für ausgewählte Geräte geeignet. Als dann die PV-Anlage kam bin ich auf Home Assistant gewechselt weil es keine Herstellerbeschränkung gibt. So das ich die Möglichkeiten eines Energy Manager Flex laut Handbuch für mich als Alternative halt ausgeschlossen habe. Meine Erfahrung bis heute ist das das Teil nur da für da ist das di App geht und das die Installfirma / Solarwatt unkompliziert auf das System zugreifen kann.
Denn bei jeglicher Kommunikation mit der Installfirma / Solarwatt kam die Frage ich sehe Ihre Anlage nicht, ja ich habe keinen Energy Manager, und dann geht die eierei los….
Schönes Rest WE
![]()
Hallo Community,
habe gerade eine 1.0 Version des weiter oben beschriebenen Battery Flex Controllers released. Die großen Änderungen sind:
All diese Änderungen führten dazu, dass nun auch sein WEB Interface …
… sauber funktioniert inklusive eines, alle 5s blinkenden, Heartbeats ![]()
Für uns ist damit der Solarwatt Energy Manager Flex …
… mit seinem nicht dokumentierten und instabilen REST-API, Geschichte ![]()