* Optimize Sun data calculation * Remove not required enum * Split config struct into different sub structs * Feature: Allow configuration of LWT QoS * Made resetreason methods static * Feature: Implement offset cache for "YieldDay" Thanks to @broth-itk for the idea! Fix: #1258 #1397 * Add Esp32-Stick-PoE-A * remove broken LilyGO_T_ETH_POE config, use device profile instead * Feature: High resolution Icon and PWA (Progressive Web App) functionality Fix: #1289 * webapp: Update dependencies * Initialize TaskScheduler * Migrate SunPosition to TaskScheduler * Migrate Datastore to TaskScheduler * Migrate MqttHandleInverterTotal to TaskSchedule * Migrate MqttHandleHass to TaskScheduler * Migrate MqttHandleDtu to TaskScheduler * Migrate MqttHandleInverter to TaskScheduler * Migrate LedSingle to TaskScheduler * Migrate NetworkSettings to TaskScheduler * Migrate InverterSettings to TaskScheduler * Migrate MessageOutput to TaskScheduler * Migrate Display_Graphic to TaskScheduler * Migrate WebApi to TaskScheduler * Split InverterSettings into multiple tasks * Calculate SunPosition only every 5 seconds * Split LedSingle into multiple tasks * Upgrade espMqttClient from 1.4.5 to 1.5.0 * Doc: Correct amount of MPP-Tracker * Added HMT-1600-4T and HMT-1800-4T to DevInfoParser Fix #1524 * Adjusted inverter names for HMS-1600/1800/2000-4T * Add channel count to description of detected inverter type (DevInfoParser) * Adjust device web api endpoint for dynamic led count * Feature: Added ability to change the brightness of the LEDs Based on the idea of @moritzlerch with several modifications like pwmTable and structure * webapp: Update dependencies * Update olikraus/U8g2 from 2.35.7 to 2.35.8 * Remove not required onWebsocketEvent * Remove code nesting * Introduce several const statements * Remove not required AsyncEventSource * Doc: Added byte specification to each command * Feature: Added basic Grid Profile parser which shows the used profile and version Other values are still outstanding. * Optimize AlarmLogParser to save memory * Add libfrozen to project to create constexpr maps * Feature: First version of GridProfile Parser which shows all values contained in the profile. * webapp: Update dependencies * Apply better variable names * Remove not required casts * Add additional compiler flags to prevent errors * Add const statement to several variables * Replace NULL by nullptr * Update bblanchon/ArduinoJson from 6.21.3 to 6.21.4 * Add const keyword to method parameters * Add const keyword to methods * Use references instead of pointers whenver possible * Adjust member variable names in MqttSettings * Adjust member variable names in NetworkSettings * webapp: Update timezone database to latest version * webapp: Beautify and unify form footers * Feature: Allow setting of an inverter limit of 0% and 0W Thanks to @madmartin in #1270 * Feature: Allow links in device profiles These links will be shown on the hardware settings page. * Doc: Added hint regarding HMS-xxxx-xT-NA inverters * Feature: Added DeviceProfile for CASmo-DTU Based on #1565 * Upgrade actions/upload-artifact from v3 to v4 * Upgrade actions/download-artifact from v3 to v4 * webapp: add app.js.gz * Gridprofileparser: Added latest known values Thanks to @stefan123t and @noone2k * webapp: Fix lint errors * Feature: Add DTU to Home Assistant Auto Discovery This is based on PR 1365 from @CFenner with several fixes and optimizations * Fix: Remove debug output as it floods the console * Fix: Gridprofileparser: Add additional error handling if profile is unknown * webapp: add app.js.gz * Fix: Offset cache for "YieldDay" did not work correctly * webapp: update dependencies * webapp: add app.js.gz * Fix: yarn.lock was outdated * Fix: yarn build error * Fix: Reset Yield day correction in combination with Zero Yield Day on Midnight lead to wrong values. * Fix: Allow negative values in GridProfileParser * Correct variable name * Fix #1579: Static IP in Ethernet mode did not work correctly * Feature: Added diagram to display This is based on the idea of @Henrik-Ingenieur and was discussed in #1504 * webapp: update dependencies * webapp: add app.js.gz --------- Co-authored-by: Thomas Basler <thomas@familie-basler.net> Co-authored-by: Pierre Kancir <pierre.kancir.emn@gmail.com>
223 lines
7.4 KiB
C++
223 lines
7.4 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2022 Thomas Basler and others
|
|
*/
|
|
#include "PowerMeter.h"
|
|
#include "Configuration.h"
|
|
#include "HttpPowerMeter.h"
|
|
#include "MqttSettings.h"
|
|
#include "NetworkSettings.h"
|
|
#include "SDM.h"
|
|
#include "MessageOutput.h"
|
|
#include <ctime>
|
|
#include <SoftwareSerial.h>
|
|
|
|
PowerMeterClass PowerMeter;
|
|
|
|
SDM sdm(Serial2, 9600, NOT_A_PIN, SERIAL_8N1, SDM_RX_PIN, SDM_TX_PIN);
|
|
|
|
SoftwareSerial inputSerial;
|
|
|
|
void PowerMeterClass::init(Scheduler& scheduler)
|
|
{
|
|
scheduler.addTask(_loopTask);
|
|
_loopTask.setCallback(std::bind(&PowerMeterClass::loop, this));
|
|
_loopTask.setIterations(TASK_FOREVER);
|
|
_loopTask.enable();
|
|
|
|
_lastPowerMeterCheck = 0;
|
|
_lastPowerMeterUpdate = 0;
|
|
|
|
for (auto const& s: _mqttSubscriptions) { MqttSettings.unsubscribe(s.first); }
|
|
_mqttSubscriptions.clear();
|
|
|
|
CONFIG_T& config = Configuration.get();
|
|
|
|
if (!config.PowerMeter.Enabled) {
|
|
return;
|
|
}
|
|
|
|
switch(config.PowerMeter.Source) {
|
|
case SOURCE_MQTT: {
|
|
auto subscribe = [this](char const* topic, float* target) {
|
|
if (strlen(topic) == 0) { return; }
|
|
MqttSettings.subscribe(topic, 0,
|
|
std::bind(&PowerMeterClass::onMqttMessage,
|
|
this, std::placeholders::_1, std::placeholders::_2,
|
|
std::placeholders::_3, std::placeholders::_4,
|
|
std::placeholders::_5, std::placeholders::_6)
|
|
);
|
|
_mqttSubscriptions.try_emplace(topic, target);
|
|
};
|
|
|
|
subscribe(config.PowerMeter.MqttTopicPowerMeter1, &_powerMeter1Power);
|
|
subscribe(config.PowerMeter.MqttTopicPowerMeter2, &_powerMeter2Power);
|
|
subscribe(config.PowerMeter.MqttTopicPowerMeter3, &_powerMeter3Power);
|
|
break;
|
|
}
|
|
|
|
case SOURCE_SDM1PH:
|
|
case SOURCE_SDM3PH:
|
|
sdm.begin();
|
|
break;
|
|
|
|
case SOURCE_HTTP:
|
|
HttpPowerMeter.init();
|
|
break;
|
|
|
|
case SOURCE_SML:
|
|
pinMode(SML_RX_PIN, INPUT);
|
|
inputSerial.begin(9600, SWSERIAL_8N1, SML_RX_PIN, -1, false, 128, 95);
|
|
inputSerial.enableRx(true);
|
|
inputSerial.enableTx(false);
|
|
inputSerial.flush();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PowerMeterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
|
{
|
|
for (auto const& subscription: _mqttSubscriptions) {
|
|
if (subscription.first != topic) { continue; }
|
|
|
|
std::string value(reinterpret_cast<const char*>(payload), len);
|
|
try {
|
|
*subscription.second = std::stof(value);
|
|
}
|
|
catch(std::invalid_argument const& e) {
|
|
MessageOutput.printf("PowerMeterClass: cannot parse payload of topic '%s' as float: %s\r\n",
|
|
topic, value.c_str());
|
|
return;
|
|
}
|
|
|
|
if (_verboseLogging) {
|
|
MessageOutput.printf("PowerMeterClass: Updated from '%s', TotalPower: %5.2f\r\n",
|
|
topic, getPowerTotal());
|
|
}
|
|
|
|
_lastPowerMeterUpdate = millis();
|
|
}
|
|
}
|
|
|
|
float PowerMeterClass::getPowerTotal(bool forceUpdate)
|
|
{
|
|
if (forceUpdate) {
|
|
CONFIG_T& config = Configuration.get();
|
|
if (config.PowerMeter.Enabled
|
|
&& (millis() - _lastPowerMeterUpdate) > (1000)) {
|
|
readPowerMeter();
|
|
}
|
|
}
|
|
return _powerMeter1Power + _powerMeter2Power + _powerMeter3Power;
|
|
}
|
|
|
|
uint32_t PowerMeterClass::getLastPowerMeterUpdate()
|
|
{
|
|
return _lastPowerMeterUpdate;
|
|
}
|
|
|
|
void PowerMeterClass::mqtt()
|
|
{
|
|
if (!MqttSettings.getConnected()) {
|
|
return;
|
|
} else {
|
|
String topic = "powermeter";
|
|
MqttSettings.publish(topic + "/power1", String(_powerMeter1Power));
|
|
MqttSettings.publish(topic + "/power2", String(_powerMeter2Power));
|
|
MqttSettings.publish(topic + "/power3", String(_powerMeter3Power));
|
|
MqttSettings.publish(topic + "/powertotal", String(getPowerTotal()));
|
|
MqttSettings.publish(topic + "/voltage1", String(_powerMeter1Voltage));
|
|
MqttSettings.publish(topic + "/voltage2", String(_powerMeter2Voltage));
|
|
MqttSettings.publish(topic + "/voltage3", String(_powerMeter3Voltage));
|
|
MqttSettings.publish(topic + "/import", String(_powerMeterImport));
|
|
MqttSettings.publish(topic + "/export", String(_powerMeterExport));
|
|
}
|
|
}
|
|
|
|
void PowerMeterClass::loop()
|
|
{
|
|
CONFIG_T const& config = Configuration.get();
|
|
_verboseLogging = config.PowerMeter.VerboseLogging;
|
|
|
|
if (!config.PowerMeter.Enabled) { return; }
|
|
|
|
if (config.PowerMeter.Source == SOURCE_SML) {
|
|
if (!smlReadLoop()) {
|
|
return;
|
|
} else {
|
|
_lastPowerMeterUpdate = millis();
|
|
}
|
|
}
|
|
|
|
if ((millis() - _lastPowerMeterCheck) < (config.PowerMeter.Interval * 1000)) {
|
|
return;
|
|
}
|
|
|
|
readPowerMeter();
|
|
|
|
MessageOutput.printf("PowerMeterClass: TotalPower: %5.2f\r\n", getPowerTotal());
|
|
|
|
mqtt();
|
|
|
|
_lastPowerMeterCheck = millis();
|
|
}
|
|
|
|
void PowerMeterClass::readPowerMeter()
|
|
{
|
|
CONFIG_T& config = Configuration.get();
|
|
|
|
uint8_t _address = config.PowerMeter.SdmAddress;
|
|
|
|
if (config.PowerMeter.Source == SOURCE_SDM1PH) {
|
|
_powerMeter1Power = static_cast<float>(sdm.readVal(SDM_PHASE_1_POWER, _address));
|
|
_powerMeter2Power = 0.0;
|
|
_powerMeter3Power = 0.0;
|
|
_powerMeter1Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_1_VOLTAGE, _address));
|
|
_powerMeter2Voltage = 0.0;
|
|
_powerMeter3Voltage = 0.0;
|
|
_powerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
|
|
_powerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
|
|
_lastPowerMeterUpdate = millis();
|
|
}
|
|
else if (config.PowerMeter.Source == SOURCE_SDM3PH) {
|
|
_powerMeter1Power = static_cast<float>(sdm.readVal(SDM_PHASE_1_POWER, _address));
|
|
_powerMeter2Power = static_cast<float>(sdm.readVal(SDM_PHASE_2_POWER, _address));
|
|
_powerMeter3Power = static_cast<float>(sdm.readVal(SDM_PHASE_3_POWER, _address));
|
|
_powerMeter1Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_1_VOLTAGE, _address));
|
|
_powerMeter2Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_2_VOLTAGE, _address));
|
|
_powerMeter3Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_3_VOLTAGE, _address));
|
|
_powerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
|
|
_powerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
|
|
_lastPowerMeterUpdate = millis();
|
|
}
|
|
else if (config.PowerMeter.Source == SOURCE_HTTP) {
|
|
if (HttpPowerMeter.updateValues()) {
|
|
_powerMeter1Power = HttpPowerMeter.getPower(1);
|
|
_powerMeter2Power = HttpPowerMeter.getPower(2);
|
|
_powerMeter3Power = HttpPowerMeter.getPower(3);
|
|
_lastPowerMeterUpdate = millis();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PowerMeterClass::smlReadLoop()
|
|
{
|
|
while (inputSerial.available()) {
|
|
double readVal = 0;
|
|
unsigned char smlCurrentChar = inputSerial.read();
|
|
sml_states_t smlCurrentState = smlState(smlCurrentChar);
|
|
if (smlCurrentState == SML_LISTEND) {
|
|
for (auto& handler: smlHandlerList) {
|
|
if (smlOBISCheck(handler.OBIS)) {
|
|
handler.Fn(readVal);
|
|
*handler.Arg = readVal;
|
|
}
|
|
}
|
|
} else if (smlCurrentState == SML_FINAL) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|