Extended configuration to allow string names

* Current config will be migrated to new format
* Already extended web API to get/post new format
This commit is contained in:
Thomas Basler 2022-11-15 19:31:31 +01:00
parent 0c46ecf121
commit d28fadbdac
5 changed files with 64 additions and 18 deletions

View File

@ -4,7 +4,7 @@
#include <Arduino.h> #include <Arduino.h>
#define CONFIG_FILENAME "/config.json" #define CONFIG_FILENAME "/config.json"
#define CONFIG_VERSION 0x00011600 // 0.1.22 // make sure to clean all after change #define CONFIG_VERSION 0x00011700 // 0.1.23 // make sure to clean all after change
#define WIFI_MAX_SSID_STRLEN 31 #define WIFI_MAX_SSID_STRLEN 31
#define WIFI_MAX_PASSWORD_STRLEN 64 #define WIFI_MAX_PASSWORD_STRLEN 64
@ -25,12 +25,19 @@
#define INV_MAX_COUNT 10 #define INV_MAX_COUNT 10
#define INV_MAX_CHAN_COUNT 4 #define INV_MAX_CHAN_COUNT 4
#define CHAN_MAX_NAME_STRLEN 31
#define JSON_BUFFER_SIZE 6144 #define JSON_BUFFER_SIZE 6144
struct CHANNEL_CONFIG_T {
uint16_t MaxChannelPower;
char Name[CHAN_MAX_NAME_STRLEN];
};
struct INVERTER_CONFIG_T { struct INVERTER_CONFIG_T {
uint64_t Serial; uint64_t Serial;
char Name[INV_MAX_NAME_STRLEN + 1]; char Name[INV_MAX_NAME_STRLEN + 1];
uint16_t MaxChannelPower[INV_MAX_CHAN_COUNT]; CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
}; };
struct CONFIG_T { struct CONFIG_T {

View File

@ -84,9 +84,11 @@ bool ConfigurationClass::write()
inv["serial"] = config.Inverter[i].Serial; inv["serial"] = config.Inverter[i].Serial;
inv["name"] = config.Inverter[i].Name; inv["name"] = config.Inverter[i].Name;
JsonArray channels = inv.createNestedArray("channels"); JsonArray channel = inv.createNestedArray("channel");
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
channels.add(config.Inverter[i].MaxChannelPower[c]); JsonObject chanData = channel.createNestedObject();
chanData["name"] = config.Inverter[i].channel[c].Name;
chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower;
} }
} }
@ -202,9 +204,10 @@ bool ConfigurationClass::read()
config.Inverter[i].Serial = inv["serial"] | 0ULL; config.Inverter[i].Serial = inv["serial"] | 0ULL;
strlcpy(config.Inverter[i].Name, inv["name"] | "", sizeof(config.Inverter[i].Name)); strlcpy(config.Inverter[i].Name, inv["name"] | "", sizeof(config.Inverter[i].Name));
JsonArray channels = inv["channels"]; JsonArray channel = inv["channel"];
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
config.Inverter[i].MaxChannelPower[c] = channels[c]; config.Inverter[i].channel[c].MaxChannelPower = channel[c]["max_power"] | 0;
strlcpy(config.Inverter[i].channel[c].Name, channel[c]["name"] | "", sizeof(config.Inverter[i].channel[c].Name));
} }
} }
@ -214,8 +217,35 @@ bool ConfigurationClass::read()
void ConfigurationClass::migrate() void ConfigurationClass::migrate()
{ {
if (config.Cfg_Version < 0x00011700) {
File f = LittleFS.open(CONFIG_FILENAME, "r", false);
if (!f) {
Serial.println(F("Failed to open file, cancel migration"));
return;
}
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
// Deserialize the JSON document
DeserializationError error = deserializeJson(doc, f);
if (error) {
Serial.println(F("Failed to read file, cancel migration"));
return;
}
JsonArray inverters = doc["inverters"];
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
JsonObject inv = inverters[i].as<JsonObject>();
JsonArray channels = inv["channels"];
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
config.Inverter[i].channel[c].MaxChannelPower = channels[c];
strlcpy(config.Inverter[i].channel[c].Name, "", sizeof(config.Inverter[i].channel[c].Name));
}
}
}
config.Cfg_Version = CONFIG_VERSION; config.Cfg_Version = CONFIG_VERSION;
write(); write();
read();
} }
CONFIG_T& ConfigurationClass::get() CONFIG_T& ConfigurationClass::get()

View File

@ -62,8 +62,11 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
max_channels = inv->Statistics()->getChannelCount(); max_channels = inv->Statistics()->getChannelCount();
} }
JsonArray channel = obj.createNestedArray("channel");
for (uint8_t c = 0; c < max_channels; c++) { for (uint8_t c = 0; c < max_channels; c++) {
obj[F("max_power")][c] = config.Inverter[i].MaxChannelPower[c]; JsonObject chanData = channel.createNestedObject();
chanData["name"] = config.Inverter[i].channel[c].Name;
chanData["max_power"] = config.Inverter[i].channel[c].MaxChannelPower;
} }
} }
} }
@ -154,7 +157,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
if (inv != nullptr) { if (inv != nullptr) {
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
inv->Statistics()->setChannelMaxPower(c, inverter->MaxChannelPower[c]); inv->Statistics()->setChannelMaxPower(c, inverter->channel[c].MaxChannelPower);
} }
} }
@ -197,7 +200,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
return; return;
} }
if (!(root.containsKey("id") && root.containsKey("serial") && root.containsKey("name") && root.containsKey("max_power"))) { if (!(root.containsKey("id") && root.containsKey("serial") && root.containsKey("name") && root.containsKey("channel"))) {
retMsg[F("message")] = F("Values are missing!"); retMsg[F("message")] = F("Values are missing!");
response->setLength(); response->setLength();
request->send(response); request->send(response);
@ -225,8 +228,8 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
return; return;
} }
JsonArray maxPowerArray = root[F("max_power")].as<JsonArray>(); JsonArray channelArray = root[F("channel")].as<JsonArray>();
if (maxPowerArray.size() == 0 || maxPowerArray.size() > INV_MAX_CHAN_COUNT) { if (channelArray.size() == 0 || channelArray.size() > INV_MAX_CHAN_COUNT) {
retMsg[F("message")] = F("Invalid amount of max channel setting given!"); retMsg[F("message")] = F("Invalid amount of max channel setting given!");
response->setLength(); response->setLength();
request->send(response); request->send(response);
@ -243,8 +246,9 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
strncpy(inverter.Name, root[F("name")].as<String>().c_str(), INV_MAX_NAME_STRLEN); strncpy(inverter.Name, root[F("name")].as<String>().c_str(), INV_MAX_NAME_STRLEN);
uint8_t arrayCount = 0; uint8_t arrayCount = 0;
for (JsonVariant maxPower : maxPowerArray) { for (JsonVariant channel : channelArray) {
inverter.MaxChannelPower[arrayCount] = maxPower.as<uint16_t>(); inverter.channel[arrayCount].MaxChannelPower = channel[F("max_power")].as<uint16_t>();
strncpy(inverter.channel[arrayCount].Name, channel[F("name")] | "", sizeof(inverter.channel[arrayCount].Name));
arrayCount++; arrayCount++;
} }
@ -272,7 +276,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
if (inv != nullptr) { if (inv != nullptr) {
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
inv->Statistics()->setChannelMaxPower(c, inverter.MaxChannelPower[c]); inv->Statistics()->setChannelMaxPower(c, inverter.channel[c].MaxChannelPower);
} }
} }

View File

@ -116,7 +116,7 @@ void setup()
if (inv != nullptr) { if (inv != nullptr) {
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
inv->Statistics()->setChannelMaxPower(c, config.Inverter[i].MaxChannelPower[c]); inv->Statistics()->setChannelMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower);
} }
} }
Serial.println(F(" done")); Serial.println(F(" done"));

View File

@ -81,12 +81,12 @@
class="form-control" maxlength="31" /> class="form-control" maxlength="31" />
</div> </div>
<div v-for="(max, index) in selectedInverterData.max_power" :key="`${index}`"> <div v-for="(max, index) in selectedInverterData.channel" :key="`${index}`">
<label :for="`inverter-max_${index}`" class="col-form-label">Max power string {{ index +1 }}:</label> <label :for="`inverter-max_${index}`" class="col-form-label">Max power string {{ index +1 }}:</label>
<div class="d-flex mb-2"> <div class="d-flex mb-2">
<div class="input-group"> <div class="input-group">
<input type="number" class="form-control" :id="`inverter-max_${index}`" min="0" <input type="number" class="form-control" :id="`inverter-max_${index}`" min="0"
v-model="selectedInverterData.max_power[index]" v-model="selectedInverterData.channel[index].max_power"
:aria-describedby="`inverter-maxDescription_${index} inverter-customizer`" /> :aria-describedby="`inverter-maxDescription_${index} inverter-customizer`" />
<span class="input-group-text" :id="`inverter-maxDescription_${index}`">W<sup>*</sup></span> <span class="input-group-text" :id="`inverter-maxDescription_${index}`">W<sup>*</sup></span>
</div> </div>
@ -139,12 +139,17 @@ import * as bootstrap from 'bootstrap';
import BootstrapAlert from "@/components/BootstrapAlert.vue"; import BootstrapAlert from "@/components/BootstrapAlert.vue";
import { handleResponse, authHeader } from '@/utils/authentication'; import { handleResponse, authHeader } from '@/utils/authentication';
declare interface Channel {
name: string;
max_power: number;
}
declare interface Inverter { declare interface Inverter {
id: string; id: string;
serial: number; serial: number;
name: string; name: string;
type: string; type: string;
max_power: number[]; channel: Array<Channel>;
} }
declare interface AlertResponse { declare interface AlertResponse {