diff --git a/include/WebApi_ws_live.h b/include/WebApi_ws_live.h index 05f8ab8f9..4a29fff5b 100644 --- a/include/WebApi_ws_live.h +++ b/include/WebApi_ws_live.h @@ -17,6 +17,9 @@ class WebApiWsLiveClass { static void generateInverterChannelJsonResponse(JsonObject& root, std::shared_ptr inv); static void generateCommonJsonResponse(JsonVariant& root); + void generateOnBatteryJsonResponse(JsonVariant& root, bool all); + void sendOnBatteryStats(); + static void addField(JsonObject& root, std::shared_ptr inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, String topic = ""); static void addTotalField(JsonObject& root, const String& name, const float value, const String& unit, const uint8_t digits); @@ -25,6 +28,12 @@ class WebApiWsLiveClass { AsyncWebSocket _ws; + uint32_t _lastPublishOnBatteryFull = 0; + uint32_t _lastPublishVictron = 0; + uint32_t _lastPublishHuawei = 0; + uint32_t _lastPublishBattery = 0; + uint32_t _lastPublishPowerMeter = 0; + uint32_t _lastPublishStats[INV_MAX_COUNT] = { 0 }; std::mutex _mutex; diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index e51361d7c..d2ed35d9d 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -54,6 +54,66 @@ void WebApiWsLiveClass::wsCleanupTaskCb() } } +void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool all) +{ + auto constexpr halfOfAllMillis = std::numeric_limits::max() / 2; + + if (all || (millis() - _lastPublishVictron) > VictronMppt.getDataAgeMillis()) { + JsonObject vedirectObj = root.createNestedObject("vedirect"); + vedirectObj["enabled"] = Configuration.get().Vedirect.Enabled; + JsonObject totalVeObj = vedirectObj.createNestedObject("total"); + + addTotalField(totalVeObj, "Power", VictronMppt.getPanelPowerWatts(), "W", 1); + addTotalField(totalVeObj, "YieldDay", VictronMppt.getYieldDay() * 1000, "Wh", 0); + addTotalField(totalVeObj, "YieldTotal", VictronMppt.getYieldTotal(), "kWh", 2); + + if (!all) { _lastPublishVictron = millis(); } + } + + if (all || (HuaweiCan.getLastUpdate() - _lastPublishHuawei) < halfOfAllMillis ) { + JsonObject huaweiObj = root.createNestedObject("huawei"); + huaweiObj["enabled"] = Configuration.get().Huawei.Enabled; + const RectifierParameters_t * rp = HuaweiCan.get(); + addTotalField(huaweiObj, "Power", rp->output_power, "W", 2); + + if (!all) { _lastPublishHuawei = millis(); } + } + + auto spStats = Battery.getStats(); + if (all || spStats->updateAvailable(_lastPublishBattery)) { + JsonObject batteryObj = root.createNestedObject("battery"); + batteryObj["enabled"] = Configuration.get().Battery.Enabled; + addTotalField(batteryObj, "soc", spStats->getSoC(), "%", 0); + + if (!all) { _lastPublishBattery = millis(); } + } + + if (all || (PowerMeter.getLastPowerMeterUpdate() - _lastPublishPowerMeter) < halfOfAllMillis) { + JsonObject powerMeterObj = root.createNestedObject("power_meter"); + powerMeterObj["enabled"] = Configuration.get().PowerMeter.Enabled; + addTotalField(powerMeterObj, "Power", PowerMeter.getPowerTotal(false), "W", 1); + + if (!all) { _lastPublishPowerMeter = millis(); } + } +} + +void WebApiWsLiveClass::sendOnBatteryStats() +{ + DynamicJsonDocument root(512); + if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { return; } + + JsonVariant var = root; + + bool all = (millis() - _lastPublishOnBatteryFull) > 10 * 1000; + if (all) { _lastPublishOnBatteryFull = millis(); } + generateOnBatteryJsonResponse(var, all); + + String buffer; + serializeJson(root, buffer); + + _ws.textAll(buffer); +} + void WebApiWsLiveClass::sendDataTaskCb() { // do nothing if no WS client is connected @@ -61,6 +121,8 @@ void WebApiWsLiveClass::sendDataTaskCb() return; } + sendOnBatteryStats(); + // Loop all inverters for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); @@ -115,27 +177,6 @@ void WebApiWsLiveClass::generateCommonJsonResponse(JsonVariant& root) hintObj["time_sync"] = !getLocalTime(&timeinfo, 5); hintObj["radio_problem"] = (Hoymiles.getRadioNrf()->isInitialized() && (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant())) || (Hoymiles.getRadioCmt()->isInitialized() && (!Hoymiles.getRadioCmt()->isConnected())); hintObj["default_password"] = strcmp(Configuration.get().Security.Password, ACCESS_POINT_PASSWORD) == 0; - - JsonObject vedirectObj = root.createNestedObject("vedirect"); - vedirectObj["enabled"] = Configuration.get().Vedirect.Enabled; - JsonObject totalVeObj = vedirectObj.createNestedObject("total"); - - addTotalField(totalVeObj, "Power", VictronMppt.getPanelPowerWatts(), "W", 1); - addTotalField(totalVeObj, "YieldDay", VictronMppt.getYieldDay() * 1000, "Wh", 0); - addTotalField(totalVeObj, "YieldTotal", VictronMppt.getYieldTotal(), "kWh", 2); - - JsonObject huaweiObj = root.createNestedObject("huawei"); - huaweiObj["enabled"] = Configuration.get().Huawei.Enabled; - const RectifierParameters_t * rp = HuaweiCan.get(); - addTotalField(huaweiObj, "Power", rp->output_power, "W", 2); - - JsonObject batteryObj = root.createNestedObject("battery"); - batteryObj["enabled"] = Configuration.get().Battery.Enabled; - addTotalField(batteryObj, "soc", Battery.getStats()->getSoC(), "%", 0); - - JsonObject powerMeterObj = root.createNestedObject("power_meter"); - powerMeterObj["enabled"] = Configuration.get().PowerMeter.Enabled; - addTotalField(powerMeterObj, "Power", PowerMeter.getPowerTotal(false), "W", 1); } void WebApiWsLiveClass::generateInverterCommonJsonResponse(JsonObject& root, std::shared_ptr inv) @@ -279,6 +320,8 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request) generateCommonJsonResponse(root); + generateOnBatteryJsonResponse(root, true); + response->setLength(); request->send(response); diff --git a/webapp/src/views/HomeView.vue b/webapp/src/views/HomeView.vue index b8f4b682c..9371d0fae 100644 --- a/webapp/src/views/HomeView.vue +++ b/webapp/src/views/HomeView.vue @@ -461,12 +461,16 @@ export default defineComponent({ console.log(event); if (event.data != "{}") { const newData = JSON.parse(event.data); + + if (typeof newData.vedirect !== 'undefined') { Object.assign(this.liveData.vedirect, newData.vedirect); } + if (typeof newData.huawei !== 'undefined') { Object.assign(this.liveData.huawei, newData.huawei); } + if (typeof newData.battery !== 'undefined') { Object.assign(this.liveData.battery, newData.battery); } + if (typeof newData.power_meter !== 'undefined') { Object.assign(this.liveData.power_meter, newData.power_meter); } + + if (typeof newData.total === 'undefined') { return; } + Object.assign(this.liveData.total, newData.total); Object.assign(this.liveData.hints, newData.hints); - Object.assign(this.liveData.vedirect, newData.vedirect); - Object.assign(this.liveData.huawei, newData.huawei); - Object.assign(this.liveData.battery, newData.battery); - Object.assign(this.liveData.power_meter, newData.power_meter); const foundIdx = this.liveData.inverters.findIndex((element) => element.serial == newData.inverters[0].serial); if (foundIdx == -1) {