Skip to content

Commit

Permalink
DPL settings in web app: split metadata from config
Browse files Browse the repository at this point in the history
users are manipulating the DPL using HTTP POST requests. often they are
requesting the current settings using HTTP GET on the respective route,
then change a particular settings, and send all the data back using HTTP
POST. if they failed to remove the metadata node from the JSON,
OpenDTU-OnBattery would not be able to process the JSON due to its size.
the web app does not submit the metadata.

to avoid problems, the metadata is now split from the configuration
data.
  • Loading branch information
schlimmchen committed Mar 23, 2024
1 parent 8bfb5c6 commit 054a677
Showing 4 changed files with 56 additions and 36 deletions.
1 change: 1 addition & 0 deletions include/WebApi_powerlimiter.h
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ class WebApiPowerLimiterClass {

private:
void onStatus(AsyncWebServerRequest* request);
void onMetaData(AsyncWebServerRequest* request);
void onAdminGet(AsyncWebServerRequest* request);
void onAdminPost(AsyncWebServerRequest* request);

35 changes: 24 additions & 11 deletions src/WebApi_powerlimiter.cpp
Original file line number Diff line number Diff line change
@@ -22,18 +22,14 @@ void WebApiPowerLimiterClass::init(AsyncWebServer& server, Scheduler& scheduler)
_server->on("/api/powerlimiter/status", HTTP_GET, std::bind(&WebApiPowerLimiterClass::onStatus, this, _1));
_server->on("/api/powerlimiter/config", HTTP_GET, std::bind(&WebApiPowerLimiterClass::onAdminGet, this, _1));
_server->on("/api/powerlimiter/config", HTTP_POST, std::bind(&WebApiPowerLimiterClass::onAdminPost, this, _1));
_server->on("/api/powerlimiter/metadata", HTTP_GET, std::bind(&WebApiPowerLimiterClass::onMetaData, this, _1));
}

void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)
{
auto const& config = Configuration.get();

size_t invAmount = 0;
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
if (config.Inverter[i].Serial != 0) { ++invAmount; }
}

AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024 + 384 * invAmount);
AsyncJsonResponse* response = new AsyncJsonResponse(false, 512);
auto& root = response->getRoot();

root["enabled"] = config.PowerLimiter.Enabled;
@@ -60,12 +56,29 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)
root["full_solar_passthrough_start_voltage"] = static_cast<int>(config.PowerLimiter.FullSolarPassThroughStartVoltage * 100 + 0.5) / 100.0;
root["full_solar_passthrough_stop_voltage"] = static_cast<int>(config.PowerLimiter.FullSolarPassThroughStopVoltage * 100 + 0.5) / 100.0;

JsonObject metadata = root.createNestedObject("metadata");
metadata["power_meter_enabled"] = config.PowerMeter.Enabled;
metadata["battery_enabled"] = config.Battery.Enabled;
metadata["charge_controller_enabled"] = config.Vedirect.Enabled;
response->setLength();
request->send(response);
}

void WebApiPowerLimiterClass::onMetaData(AsyncWebServerRequest* request)
{
if (!WebApi.checkCredentials(request)) { return; }

auto const& config = Configuration.get();

size_t invAmount = 0;
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
if (config.Inverter[i].Serial != 0) { ++invAmount; }
}

AsyncJsonResponse* response = new AsyncJsonResponse(false, 256 + 256 * invAmount);
auto& root = response->getRoot();

root["power_meter_enabled"] = config.PowerMeter.Enabled;
root["battery_enabled"] = config.Battery.Enabled;
root["charge_controller_enabled"] = config.Vedirect.Enabled;

JsonObject inverters = metadata.createNestedObject("inverters");
JsonObject inverters = root.createNestedObject("inverters");
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
if (config.Inverter[i].Serial == 0) { continue; }

1 change: 0 additions & 1 deletion webapp/src/types/PowerLimiterConfig.ts
Original file line number Diff line number Diff line change
@@ -42,5 +42,4 @@ export interface PowerLimiterConfig {
full_solar_passthrough_soc: number;
full_solar_passthrough_start_voltage: number;
full_solar_passthrough_stop_voltage: number;
metadata: PowerLimiterMetaData;
}
55 changes: 31 additions & 24 deletions webapp/src/views/PowerLimiterAdminView.vue
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@
<div class="col-sm-8">
<select id="inverter_serial" class="form-select" v-model="powerLimiterConfigList.inverter_serial" required>
<option value="" disabled hidden selected>{{ $t('powerlimiteradmin.SelectInverter') }}</option>
<option v-for="(inv, serial) in powerLimiterConfigList.metadata.inverters" :key="serial" :value="serial">
<option v-for="(inv, serial) in powerLimiterMetaData.inverters" :key="serial" :value="serial">
{{ inv.name }} ({{ inv.type }})
</option>
</select>
@@ -74,7 +74,7 @@
</label>
<div class="col-sm-8">
<select id="inverter_channel" class="form-select" v-model="powerLimiterConfigList.inverter_channel_id">
<option v-for="channel in range(powerLimiterConfigList.metadata.inverters[powerLimiterConfigList.inverter_serial].channels)" :key="channel" :value="channel">
<option v-for="channel in range(powerLimiterMetaData.inverters[powerLimiterConfigList.inverter_serial].channels)" :key="channel" :value="channel">
{{ channel + 1 }}
</option>
</select>
@@ -194,7 +194,7 @@
<div class="alert alert-secondary" role="alert" v-html="$t('powerlimiteradmin.VoltageLoadCorrectionInfo')"></div>
</CardElement>

<FormFooter @reload="getPowerLimiterConfig"/>
<FormFooter @reload="getAllData"/>
</form>
</BasePage>
</template>
@@ -208,7 +208,7 @@ import CardElement from '@/components/CardElement.vue';
import FormFooter from '@/components/FormFooter.vue';
import InputElement from '@/components/InputElement.vue';
import { BIconInfoCircle } from 'bootstrap-icons-vue';
import type { PowerLimiterConfig } from "@/types/PowerLimiterConfig";
import type { PowerLimiterConfig, PowerLimiterMetaData } from "@/types/PowerLimiterConfig";
export default defineComponent({
components: {
@@ -223,24 +223,26 @@ export default defineComponent({
return {
dataLoading: true,
powerLimiterConfigList: {} as PowerLimiterConfig,
powerLimiterMetaData: {} as PowerLimiterMetaData,
alertMessage: "",
alertType: "info",
showAlert: false,
configAlert: false,
};
},
created() {
this.getPowerLimiterConfig();
this.getAllData();
},
watch: {
'powerLimiterConfigList.inverter_serial'(newVal) {
var cfg = this.powerLimiterConfigList;
var meta = this.powerLimiterMetaData;
if (newVal === "") { return; } // do not try to convert the placeholder value
if (cfg.metadata.inverters[newVal] !== undefined) { return; }
if (meta.inverters[newVal] !== undefined) { return; }
for (const [serial, inverter] of Object.entries(cfg.metadata.inverters)) {
for (const [serial, inverter] of Object.entries(meta.inverters)) {
// cfg.inverter_serial might be too large to parse as a 32 bit
// int, so we make sure to only try to parse two characters. if
// cfg.inverter_serial is indeed an old position based index,
@@ -261,30 +263,31 @@ export default defineComponent({
methods: {
getConfigHints() {
var cfg = this.powerLimiterConfigList;
var meta = this.powerLimiterMetaData;
var hints = [];
if (cfg.metadata.power_meter_enabled !== true) {
if (meta.power_meter_enabled !== true) {
hints.push({severity: "requirement", subject: "PowerMeterDisabled"});
this.configAlert = true;
}
if (typeof cfg.metadata.inverters === "undefined" || Object.keys(cfg.metadata.inverters).length == 0) {
if (typeof meta.inverters === "undefined" || Object.keys(meta.inverters).length == 0) {
hints.push({severity: "requirement", subject: "NoInverter"});
this.configAlert = true;
}
else {
var inv = cfg.metadata.inverters[cfg.inverter_serial];
var inv = meta.inverters[cfg.inverter_serial];
if (inv !== undefined && !(inv.poll_enable && inv.command_enable && inv.poll_enable_night && inv.command_enable_night)) {
hints.push({severity: "requirement", subject: "InverterCommunication"});
}
}
if (!cfg.is_inverter_solar_powered) {
if (!cfg.metadata.charge_controller_enabled) {
if (!meta.charge_controller_enabled) {
hints.push({severity: "optional", subject: "NoChargeController"});
}
if (!cfg.metadata.battery_enabled) {
if (!meta.battery_enabled) {
hints.push({severity: "optional", subject: "NoBatteryInterface"});
}
}
@@ -296,13 +299,15 @@ export default defineComponent({
},
canUseSolarPassthrough() {
var cfg = this.powerLimiterConfigList;
var canUse = this.isEnabled() && cfg.metadata.charge_controller_enabled && !cfg.is_inverter_solar_powered;
var meta = this.powerLimiterMetaData;
var canUse = this.isEnabled() && meta.charge_controller_enabled && !cfg.is_inverter_solar_powered;
if (!canUse) { cfg.solar_passthrough_enabled = false; }
return canUse;
},
canUseSoCThresholds() {
var cfg = this.powerLimiterConfigList;
return this.isEnabled() && cfg.metadata.battery_enabled && !cfg.is_inverter_solar_powered;
var meta = this.powerLimiterMetaData;
return this.isEnabled() && meta.battery_enabled && !cfg.is_inverter_solar_powered;
},
canUseVoltageThresholds() {
var cfg = this.powerLimiterConfigList;
@@ -316,6 +321,7 @@ export default defineComponent({
},
needsChannelSelection() {
var cfg = this.powerLimiterConfigList;
var meta = this.powerLimiterMetaData;
var reset = function() {
cfg.inverter_channel_id = 0;
@@ -326,7 +332,7 @@ export default defineComponent({
if (cfg.is_inverter_solar_powered) { return reset(); }
var inverter = cfg.metadata.inverters[cfg.inverter_serial];
var inverter = meta.inverters[cfg.inverter_serial];
if (inverter === undefined) { return reset(); }
if (cfg.inverter_channel_id >= inverter.channels) {
@@ -335,24 +341,25 @@ export default defineComponent({
return inverter.channels > 1;
},
getPowerLimiterConfig() {
getAllData() {
this.dataLoading = true;
fetch("/api/powerlimiter/config", { headers: authHeader() })
fetch("/api/powerlimiter/metadata", { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => {
this.powerLimiterConfigList = data;
this.dataLoading = false;
this.powerLimiterMetaData = data;
fetch("/api/powerlimiter/config", { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router))
.then((data) => {
this.powerLimiterConfigList = data;
this.dataLoading = false;
});
});
},
savePowerLimiterConfig(e: Event) {
e.preventDefault();
const formData = new FormData();
formData.append("data", JSON.stringify(this.powerLimiterConfigList, (key, value) => {
// do not submit metadata
if (key === "metadata") { return undefined; }
return value;
}));
formData.append("data", JSON.stringify(this.powerLimiterConfigList));
fetch("/api/powerlimiter/config", {
method: "POST",

0 comments on commit 054a677

Please sign in to comment.