Remove F macro from onBattery extensions
This commit is contained in:
parent
bfcce16bc9
commit
d5155a07be
@ -216,7 +216,7 @@ bool Controller::init(bool verboseLogging)
|
|||||||
pin.battery_rx, pin.battery_rxen, pin.battery_tx, pin.battery_txen);
|
pin.battery_rx, pin.battery_rxen, pin.battery_tx, pin.battery_txen);
|
||||||
|
|
||||||
if (pin.battery_rx < 0 || pin.battery_tx < 0) {
|
if (pin.battery_rx < 0 || pin.battery_tx < 0) {
|
||||||
MessageOutput.println(F("[JK BMS] Invalid RX/TX pin config"));
|
MessageOutput.println("[JK BMS] Invalid RX/TX pin config");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ bool Controller::init(bool verboseLogging)
|
|||||||
_txEnablePin = pin.battery_txen;
|
_txEnablePin = pin.battery_txen;
|
||||||
|
|
||||||
if (_rxEnablePin < 0 || _txEnablePin < 0) {
|
if (_rxEnablePin < 0 || _txEnablePin < 0) {
|
||||||
MessageOutput.println(F("[JK BMS] Invalid transceiver pin config"));
|
MessageOutput.println("[JK BMS] Invalid transceiver pin config");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -109,29 +109,29 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char*
|
|||||||
statTopic.concat(subTopic);
|
statTopic.concat(subTopic);
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
DynamicJsonDocument root(1024);
|
||||||
root[F("name")] = caption;
|
root["name"] = caption;
|
||||||
root[F("stat_t")] = statTopic;
|
root["stat_t"] = statTopic;
|
||||||
root[F("uniq_id")] = serial + "_" + sensorId;
|
root["uniq_id"] = serial + "_" + sensorId;
|
||||||
|
|
||||||
if (icon != NULL) {
|
if (icon != NULL) {
|
||||||
root[F("icon")] = icon;
|
root["icon"] = icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unitOfMeasurement != NULL) {
|
if (unitOfMeasurement != NULL) {
|
||||||
root[F("unit_of_meas")] = unitOfMeasurement;
|
root["unit_of_meas"] = unitOfMeasurement;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject deviceObj = root.createNestedObject("dev");
|
JsonObject deviceObj = root.createNestedObject("dev");
|
||||||
createDeviceInfo(deviceObj);
|
createDeviceInfo(deviceObj);
|
||||||
|
|
||||||
if (Configuration.get().Mqtt.Hass.Expire) {
|
if (Configuration.get().Mqtt.Hass.Expire) {
|
||||||
root[F("exp_aft")] = Configuration.get().Mqtt.PublishInterval * 3;
|
root["exp_aft"] = Configuration.get().Mqtt.PublishInterval * 3;
|
||||||
}
|
}
|
||||||
if (deviceClass != NULL) {
|
if (deviceClass != NULL) {
|
||||||
root[F("dev_cla")] = deviceClass;
|
root["dev_cla"] = deviceClass;
|
||||||
}
|
}
|
||||||
if (stateClass != NULL) {
|
if (stateClass != NULL) {
|
||||||
root[F("stat_cla")] = stateClass;
|
root["stat_cla"] = stateClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
char buffer[512];
|
char buffer[512];
|
||||||
@ -160,14 +160,14 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const
|
|||||||
statTopic.concat(subTopic);
|
statTopic.concat(subTopic);
|
||||||
|
|
||||||
DynamicJsonDocument root(1024);
|
DynamicJsonDocument root(1024);
|
||||||
root[F("name")] = caption;
|
root["name"] = caption;
|
||||||
root[F("uniq_id")] = serial + "_" + sensorId;
|
root["uniq_id"] = serial + "_" + sensorId;
|
||||||
root[F("stat_t")] = statTopic;
|
root["stat_t"] = statTopic;
|
||||||
root[F("pl_on")] = payload_on;
|
root["pl_on"] = payload_on;
|
||||||
root[F("pl_off")] = payload_off;
|
root["pl_off"] = payload_off;
|
||||||
|
|
||||||
if (icon != NULL) {
|
if (icon != NULL) {
|
||||||
root[F("icon")] = icon;
|
root["icon"] = icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject deviceObj = root.createNestedObject("dev");
|
JsonObject deviceObj = root.createNestedObject("dev");
|
||||||
@ -182,12 +182,12 @@ void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object)
|
|||||||
{
|
{
|
||||||
auto spMpptData = VictronMppt.getData();
|
auto spMpptData = VictronMppt.getData();
|
||||||
String serial = spMpptData->SER;
|
String serial = spMpptData->SER;
|
||||||
object[F("name")] = "Victron(" + serial + ")";
|
object["name"] = "Victron(" + serial + ")";
|
||||||
object[F("ids")] = serial;
|
object["ids"] = serial;
|
||||||
object[F("cu")] = String(F("http://")) + NetworkSettings.localIP().toString();
|
object["cu"] = String("http://") + NetworkSettings.localIP().toString();
|
||||||
object[F("mf")] = F("OpenDTU");
|
object["mf"] = "OpenDTU";
|
||||||
object[F("mdl")] = spMpptData->getPidAsString();
|
object["mdl"] = spMpptData->getPidAsString();
|
||||||
object[F("sw")] = AUTO_GIT_HASH;
|
object["sw"] = AUTO_GIT_HASH;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleVedirectHassClass::publish(const String& subtopic, const String& payload)
|
void MqttHandleVedirectHassClass::publish(const String& subtopic, const String& payload)
|
||||||
|
|||||||
@ -586,7 +586,7 @@ float PowerLimiterClass::getLoadCorrectedVoltage()
|
|||||||
{
|
{
|
||||||
if (!_inverter) {
|
if (!_inverter) {
|
||||||
// there should be no need to call this method if no target inverter is known
|
// there should be no need to call this method if no target inverter is known
|
||||||
MessageOutput.println(F("DPL getLoadCorrectedVoltage: no inverter (programmer error)"));
|
MessageOutput.println("DPL getLoadCorrectedVoltage: no inverter (programmer error)");
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,14 +12,14 @@ bool PylontechCanReceiver::init(bool verboseLogging)
|
|||||||
{
|
{
|
||||||
_verboseLogging = verboseLogging;
|
_verboseLogging = verboseLogging;
|
||||||
|
|
||||||
MessageOutput.println(F("[Pylontech] Initialize interface..."));
|
MessageOutput.println("[Pylontech] Initialize interface...");
|
||||||
|
|
||||||
const PinMapping_t& pin = PinMapping.get();
|
const PinMapping_t& pin = PinMapping.get();
|
||||||
MessageOutput.printf("[Pylontech] Interface rx = %d, tx = %d\r\n",
|
MessageOutput.printf("[Pylontech] Interface rx = %d, tx = %d\r\n",
|
||||||
pin.battery_rx, pin.battery_tx);
|
pin.battery_rx, pin.battery_tx);
|
||||||
|
|
||||||
if (pin.battery_rx < 0 || pin.battery_tx < 0) {
|
if (pin.battery_rx < 0 || pin.battery_tx < 0) {
|
||||||
MessageOutput.println(F("[Pylontech] Invalid pin config"));
|
MessageOutput.println("[Pylontech] Invalid pin config");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,18 +35,18 @@ bool PylontechCanReceiver::init(bool verboseLogging)
|
|||||||
esp_err_t twaiLastResult = twai_driver_install(&g_config, &t_config, &f_config);
|
esp_err_t twaiLastResult = twai_driver_install(&g_config, &t_config, &f_config);
|
||||||
switch (twaiLastResult) {
|
switch (twaiLastResult) {
|
||||||
case ESP_OK:
|
case ESP_OK:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver installed"));
|
MessageOutput.println("[Pylontech] Twai driver installed");
|
||||||
break;
|
break;
|
||||||
case ESP_ERR_INVALID_ARG:
|
case ESP_ERR_INVALID_ARG:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver install - invalid arg"));
|
MessageOutput.println("[Pylontech] Twai driver install - invalid arg");
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
case ESP_ERR_NO_MEM:
|
case ESP_ERR_NO_MEM:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver install - no memory"));
|
MessageOutput.println("[Pylontech] Twai driver install - no memory");
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
case ESP_ERR_INVALID_STATE:
|
case ESP_ERR_INVALID_STATE:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver install - invalid state"));
|
MessageOutput.println("[Pylontech] Twai driver install - invalid state");
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -55,10 +55,10 @@ bool PylontechCanReceiver::init(bool verboseLogging)
|
|||||||
twaiLastResult = twai_start();
|
twaiLastResult = twai_start();
|
||||||
switch (twaiLastResult) {
|
switch (twaiLastResult) {
|
||||||
case ESP_OK:
|
case ESP_OK:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver started"));
|
MessageOutput.println("[Pylontech] Twai driver started");
|
||||||
break;
|
break;
|
||||||
case ESP_ERR_INVALID_STATE:
|
case ESP_ERR_INVALID_STATE:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver start - invalid state"));
|
MessageOutput.println("[Pylontech] Twai driver start - invalid state");
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -72,10 +72,10 @@ void PylontechCanReceiver::deinit()
|
|||||||
esp_err_t twaiLastResult = twai_stop();
|
esp_err_t twaiLastResult = twai_stop();
|
||||||
switch (twaiLastResult) {
|
switch (twaiLastResult) {
|
||||||
case ESP_OK:
|
case ESP_OK:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver stopped"));
|
MessageOutput.println("[Pylontech] Twai driver stopped");
|
||||||
break;
|
break;
|
||||||
case ESP_ERR_INVALID_STATE:
|
case ESP_ERR_INVALID_STATE:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver stop - invalid state"));
|
MessageOutput.println("[Pylontech] Twai driver stop - invalid state");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,10 +83,10 @@ void PylontechCanReceiver::deinit()
|
|||||||
twaiLastResult = twai_driver_uninstall();
|
twaiLastResult = twai_driver_uninstall();
|
||||||
switch (twaiLastResult) {
|
switch (twaiLastResult) {
|
||||||
case ESP_OK:
|
case ESP_OK:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver uninstalled"));
|
MessageOutput.println("[Pylontech] Twai driver uninstalled");
|
||||||
break;
|
break;
|
||||||
case ESP_ERR_INVALID_STATE:
|
case ESP_ERR_INVALID_STATE:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver uninstall - invalid state"));
|
MessageOutput.println("[Pylontech] Twai driver uninstall - invalid state");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,10 +103,10 @@ void PylontechCanReceiver::loop()
|
|||||||
if (twaiLastResult != ESP_OK) {
|
if (twaiLastResult != ESP_OK) {
|
||||||
switch (twaiLastResult) {
|
switch (twaiLastResult) {
|
||||||
case ESP_ERR_INVALID_ARG:
|
case ESP_ERR_INVALID_ARG:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver get status - invalid arg"));
|
MessageOutput.println("[Pylontech] Twai driver get status - invalid arg");
|
||||||
break;
|
break;
|
||||||
case ESP_ERR_INVALID_STATE:
|
case ESP_ERR_INVALID_STATE:
|
||||||
MessageOutput.println(F("[Pylontech] Twai driver get status - invalid state"));
|
MessageOutput.println("[Pylontech] Twai driver get status - invalid state");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -118,7 +118,7 @@ void PylontechCanReceiver::loop()
|
|||||||
// Wait for message to be received, function is blocking
|
// Wait for message to be received, function is blocking
|
||||||
twai_message_t rx_message;
|
twai_message_t rx_message;
|
||||||
if (twai_receive(&rx_message, pdMS_TO_TICKS(100)) != ESP_OK) {
|
if (twai_receive(&rx_message, pdMS_TO_TICKS(100)) != ESP_OK) {
|
||||||
MessageOutput.println(F("[Pylontech] Failed to receive message"));
|
MessageOutput.println("[Pylontech] Failed to receive message");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ void VictronMpptClass::updateSettings()
|
|||||||
MessageOutput.printf("[VictronMppt] rx = %d, tx = %d\r\n", rx, tx);
|
MessageOutput.printf("[VictronMppt] rx = %d, tx = %d\r\n", rx, tx);
|
||||||
|
|
||||||
if (rx < 0) {
|
if (rx < 0) {
|
||||||
MessageOutput.println(F("[VictronMppt] invalid pin config"));
|
MessageOutput.println("[VictronMppt] invalid pin config");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,14 +7,14 @@
|
|||||||
|
|
||||||
bool VictronSmartShunt::init(bool verboseLogging)
|
bool VictronSmartShunt::init(bool verboseLogging)
|
||||||
{
|
{
|
||||||
MessageOutput.println(F("[VictronSmartShunt] Initialize interface..."));
|
MessageOutput.println("[VictronSmartShunt] Initialize interface...");
|
||||||
|
|
||||||
const PinMapping_t& pin = PinMapping.get();
|
const PinMapping_t& pin = PinMapping.get();
|
||||||
MessageOutput.printf("[VictronSmartShunt] Interface rx = %d, tx = %d\r\n",
|
MessageOutput.printf("[VictronSmartShunt] Interface rx = %d, tx = %d\r\n",
|
||||||
pin.battery_rx, pin.battery_tx);
|
pin.battery_rx, pin.battery_tx);
|
||||||
|
|
||||||
if (pin.battery_rx < 0) {
|
if (pin.battery_rx < 0) {
|
||||||
MessageOutput.println(F("[VictronSmartShunt] Invalid pin config"));
|
MessageOutput.println("[VictronSmartShunt] Invalid pin config");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,12 +38,12 @@ void WebApiBatteryClass::onStatus(AsyncWebServerRequest* request)
|
|||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
root[F("enabled")] = config.Battery.Enabled;
|
root["enabled"] = config.Battery.Enabled;
|
||||||
root[F("verbose_logging")] = config.Battery.VerboseLogging;
|
root["verbose_logging"] = config.Battery.VerboseLogging;
|
||||||
root[F("provider")] = config.Battery.Provider;
|
root["provider"] = config.Battery.Provider;
|
||||||
root[F("jkbms_interface")] = config.Battery.JkBmsInterface;
|
root["jkbms_interface"] = config.Battery.JkBmsInterface;
|
||||||
root[F("jkbms_polling_interval")] = config.Battery.JkBmsPollingInterval;
|
root["jkbms_polling_interval"] = config.Battery.JkBmsPollingInterval;
|
||||||
root[F("mqtt_topic")] = config.Battery.MqttTopic;
|
root["mqtt_topic"] = config.Battery.MqttTopic;
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
@ -62,11 +62,11 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonObject retMsg = response->getRoot();
|
JsonObject retMsg = response->getRoot();
|
||||||
retMsg[F("type")] = F("warning");
|
retMsg["type"] = "warning";
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
if (!request->hasParam("data", true)) {
|
||||||
retMsg[F("message")] = F("No values found!");
|
retMsg["message"] = "No values found!";
|
||||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -75,8 +75,8 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
String json = request->getParam("data", true)->value();
|
String json = request->getParam("data", true)->value();
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
if (json.length() > 1024) {
|
||||||
retMsg[F("message")] = F("Data too large!");
|
retMsg["message"] = "Data too large!";
|
||||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -86,33 +86,33 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
DeserializationError error = deserializeJson(root, json);
|
DeserializationError error = deserializeJson(root, json);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
retMsg[F("message")] = F("Failed to parse data!");
|
retMsg["message"] = "Failed to parse data!";
|
||||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
retMsg["code"] = WebApiError::GenericParseError;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!root.containsKey(F("enabled")) || !root.containsKey(F("provider"))) {
|
if (!root.containsKey("enabled") || !root.containsKey("provider")) {
|
||||||
retMsg[F("message")] = F("Values are missing!");
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
config.Battery.Enabled = root[F("enabled")].as<bool>();
|
config.Battery.Enabled = root["enabled"].as<bool>();
|
||||||
config.Battery.VerboseLogging = root[F("verbose_logging")].as<bool>();
|
config.Battery.VerboseLogging = root["verbose_logging"].as<bool>();
|
||||||
config.Battery.Provider = root[F("provider")].as<uint8_t>();
|
config.Battery.Provider = root["provider"].as<uint8_t>();
|
||||||
config.Battery.JkBmsInterface = root[F("jkbms_interface")].as<uint8_t>();
|
config.Battery.JkBmsInterface = root["jkbms_interface"].as<uint8_t>();
|
||||||
config.Battery.JkBmsPollingInterval = root[F("jkbms_polling_interval")].as<uint8_t>();
|
config.Battery.JkBmsPollingInterval = root["jkbms_polling_interval"].as<uint8_t>();
|
||||||
strlcpy(config.Battery.MqttTopic, root[F("mqtt_topic")].as<String>().c_str(), sizeof(config.Battery.MqttTopic));
|
strlcpy(config.Battery.MqttTopic, root["mqtt_topic"].as<String>().c_str(), sizeof(config.Battery.MqttTopic));
|
||||||
Configuration.write();
|
Configuration.write();
|
||||||
|
|
||||||
retMsg[F("type")] = F("success");
|
retMsg["type"] = "success";
|
||||||
retMsg[F("message")] = F("Settings saved!");
|
retMsg["message"] = "Settings saved!";
|
||||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
retMsg["code"] = WebApiError::GenericSuccess;
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
|
|||||||
@ -37,27 +37,27 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)
|
|||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
root[F("enabled")] = config.PowerLimiter.Enabled;
|
root["enabled"] = config.PowerLimiter.Enabled;
|
||||||
root[F("verbose_logging")] = config.PowerLimiter.VerboseLogging;
|
root["verbose_logging"] = config.PowerLimiter.VerboseLogging;
|
||||||
root[F("solar_passthrough_enabled")] = config.PowerLimiter.SolarPassThroughEnabled;
|
root["solar_passthrough_enabled"] = config.PowerLimiter.SolarPassThroughEnabled;
|
||||||
root[F("solar_passthrough_losses")] = config.PowerLimiter.SolarPassThroughLosses;
|
root["solar_passthrough_losses"] = config.PowerLimiter.SolarPassThroughLosses;
|
||||||
root[F("battery_drain_strategy")] = config.PowerLimiter.BatteryDrainStategy;
|
root["battery_drain_strategy"] = config.PowerLimiter.BatteryDrainStategy;
|
||||||
root[F("is_inverter_behind_powermeter")] = config.PowerLimiter.IsInverterBehindPowerMeter;
|
root["is_inverter_behind_powermeter"] = config.PowerLimiter.IsInverterBehindPowerMeter;
|
||||||
root[F("inverter_id")] = config.PowerLimiter.InverterId;
|
root["inverter_id"] = config.PowerLimiter.InverterId;
|
||||||
root[F("inverter_channel_id")] = config.PowerLimiter.InverterChannelId;
|
root["inverter_channel_id"] = config.PowerLimiter.InverterChannelId;
|
||||||
root[F("target_power_consumption")] = config.PowerLimiter.TargetPowerConsumption;
|
root["target_power_consumption"] = config.PowerLimiter.TargetPowerConsumption;
|
||||||
root[F("target_power_consumption_hysteresis")] = config.PowerLimiter.TargetPowerConsumptionHysteresis;
|
root["target_power_consumption_hysteresis"] = config.PowerLimiter.TargetPowerConsumptionHysteresis;
|
||||||
root[F("lower_power_limit")] = config.PowerLimiter.LowerPowerLimit;
|
root["lower_power_limit"] = config.PowerLimiter.LowerPowerLimit;
|
||||||
root[F("upper_power_limit")] = config.PowerLimiter.UpperPowerLimit;
|
root["upper_power_limit"] = config.PowerLimiter.UpperPowerLimit;
|
||||||
root[F("battery_soc_start_threshold")] = config.PowerLimiter.BatterySocStartThreshold;
|
root["battery_soc_start_threshold"] = config.PowerLimiter.BatterySocStartThreshold;
|
||||||
root[F("battery_soc_stop_threshold")] = config.PowerLimiter.BatterySocStopThreshold;
|
root["battery_soc_stop_threshold"] = config.PowerLimiter.BatterySocStopThreshold;
|
||||||
root[F("voltage_start_threshold")] = static_cast<int>(config.PowerLimiter.VoltageStartThreshold * 100 +0.5) / 100.0;
|
root["voltage_start_threshold"] = static_cast<int>(config.PowerLimiter.VoltageStartThreshold * 100 +0.5) / 100.0;
|
||||||
root[F("voltage_stop_threshold")] = static_cast<int>(config.PowerLimiter.VoltageStopThreshold * 100 +0.5) / 100.0;;
|
root["voltage_stop_threshold"] = static_cast<int>(config.PowerLimiter.VoltageStopThreshold * 100 +0.5) / 100.0;;
|
||||||
root[F("voltage_load_correction_factor")] = config.PowerLimiter.VoltageLoadCorrectionFactor;
|
root["voltage_load_correction_factor"] = config.PowerLimiter.VoltageLoadCorrectionFactor;
|
||||||
root[F("inverter_restart_hour")] = config.PowerLimiter.RestartHour;
|
root["inverter_restart_hour"] = config.PowerLimiter.RestartHour;
|
||||||
root[F("full_solar_passthrough_soc")] = config.PowerLimiter.FullSolarPassThroughSoc;
|
root["full_solar_passthrough_soc"] = config.PowerLimiter.FullSolarPassThroughSoc;
|
||||||
root[F("full_solar_passthrough_start_voltage")] = static_cast<int>(config.PowerLimiter.FullSolarPassThroughStartVoltage * 100 + 0.5) / 100.0;
|
root["full_solar_passthrough_start_voltage"] = static_cast<int>(config.PowerLimiter.FullSolarPassThroughStartVoltage * 100 + 0.5) / 100.0;
|
||||||
root[F("full_solar_passthrough_stop_voltage")] = static_cast<int>(config.PowerLimiter.FullSolarPassThroughStopVoltage * 100 + 0.5) / 100.0;
|
root["full_solar_passthrough_stop_voltage"] = static_cast<int>(config.PowerLimiter.FullSolarPassThroughStopVoltage * 100 + 0.5) / 100.0;
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
@ -80,10 +80,10 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonObject retMsg = response->getRoot();
|
JsonObject retMsg = response->getRoot();
|
||||||
retMsg[F("type")] = F("warning");
|
retMsg["type"] = "warning";
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
if (!request->hasParam("data", true)) {
|
||||||
retMsg[F("message")] = F("No values found!");
|
retMsg["message"] = "No values found!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -92,7 +92,7 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
String json = request->getParam("data", true)->value();
|
String json = request->getParam("data", true)->value();
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
if (json.length() > 1024) {
|
||||||
retMsg[F("message")] = F("Data too large!");
|
retMsg["message"] = "Data too large!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -102,7 +102,7 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
DeserializationError error = deserializeJson(root, json);
|
DeserializationError error = deserializeJson(root, json);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
retMsg[F("message")] = F("Failed to parse data!");
|
retMsg["message"] = "Failed to parse data!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -115,8 +115,8 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
&& root.containsKey("target_power_consumption")
|
&& root.containsKey("target_power_consumption")
|
||||||
&& root.containsKey("target_power_consumption_hysteresis")
|
&& root.containsKey("target_power_consumption_hysteresis")
|
||||||
)) {
|
)) {
|
||||||
retMsg[F("message")] = F("Values are missing!");
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -124,30 +124,30 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
config.PowerLimiter.Enabled = root[F("enabled")].as<bool>();
|
config.PowerLimiter.Enabled = root["enabled"].as<bool>();
|
||||||
PowerLimiter.setMode(PowerLimiterClass::Mode::Normal); // User input sets PL to normal operation
|
PowerLimiter.setMode(PowerLimiterClass::Mode::Normal); // User input sets PL to normal operation
|
||||||
config.PowerLimiter.VerboseLogging = root[F("verbose_logging")].as<bool>();
|
config.PowerLimiter.VerboseLogging = root["verbose_logging"].as<bool>();
|
||||||
config.PowerLimiter.SolarPassThroughEnabled = root[F("solar_passthrough_enabled")].as<bool>();
|
config.PowerLimiter.SolarPassThroughEnabled = root["solar_passthrough_enabled"].as<bool>();
|
||||||
config.PowerLimiter.SolarPassThroughLosses = root[F("solar_passthrough_losses")].as<uint8_t>();
|
config.PowerLimiter.SolarPassThroughLosses = root["solar_passthrough_losses"].as<uint8_t>();
|
||||||
config.PowerLimiter.BatteryDrainStategy= root[F("battery_drain_strategy")].as<uint8_t>();
|
config.PowerLimiter.BatteryDrainStategy= root["battery_drain_strategy"].as<uint8_t>();
|
||||||
config.PowerLimiter.IsInverterBehindPowerMeter = root[F("is_inverter_behind_powermeter")].as<bool>();
|
config.PowerLimiter.IsInverterBehindPowerMeter = root["is_inverter_behind_powermeter"].as<bool>();
|
||||||
config.PowerLimiter.InverterId = root[F("inverter_id")].as<uint8_t>();
|
config.PowerLimiter.InverterId = root["inverter_id"].as<uint8_t>();
|
||||||
config.PowerLimiter.InverterChannelId = root[F("inverter_channel_id")].as<uint8_t>();
|
config.PowerLimiter.InverterChannelId = root["inverter_channel_id"].as<uint8_t>();
|
||||||
config.PowerLimiter.TargetPowerConsumption = root[F("target_power_consumption")].as<int32_t>();
|
config.PowerLimiter.TargetPowerConsumption = root["target_power_consumption"].as<int32_t>();
|
||||||
config.PowerLimiter.TargetPowerConsumptionHysteresis = root[F("target_power_consumption_hysteresis")].as<int32_t>();
|
config.PowerLimiter.TargetPowerConsumptionHysteresis = root["target_power_consumption_hysteresis"].as<int32_t>();
|
||||||
config.PowerLimiter.LowerPowerLimit = root[F("lower_power_limit")].as<int32_t>();
|
config.PowerLimiter.LowerPowerLimit = root["lower_power_limit"].as<int32_t>();
|
||||||
config.PowerLimiter.UpperPowerLimit = root[F("upper_power_limit")].as<int32_t>();
|
config.PowerLimiter.UpperPowerLimit = root["upper_power_limit"].as<int32_t>();
|
||||||
config.PowerLimiter.BatterySocStartThreshold = root[F("battery_soc_start_threshold")].as<uint32_t>();
|
config.PowerLimiter.BatterySocStartThreshold = root["battery_soc_start_threshold"].as<uint32_t>();
|
||||||
config.PowerLimiter.BatterySocStopThreshold = root[F("battery_soc_stop_threshold")].as<uint32_t>();
|
config.PowerLimiter.BatterySocStopThreshold = root["battery_soc_stop_threshold"].as<uint32_t>();
|
||||||
config.PowerLimiter.VoltageStartThreshold = root[F("voltage_start_threshold")].as<float>();
|
config.PowerLimiter.VoltageStartThreshold = root["voltage_start_threshold"].as<float>();
|
||||||
config.PowerLimiter.VoltageStartThreshold = static_cast<int>(config.PowerLimiter.VoltageStartThreshold * 100) / 100.0;
|
config.PowerLimiter.VoltageStartThreshold = static_cast<int>(config.PowerLimiter.VoltageStartThreshold * 100) / 100.0;
|
||||||
config.PowerLimiter.VoltageStopThreshold = root[F("voltage_stop_threshold")].as<float>();
|
config.PowerLimiter.VoltageStopThreshold = root["voltage_stop_threshold"].as<float>();
|
||||||
config.PowerLimiter.VoltageStopThreshold = static_cast<int>(config.PowerLimiter.VoltageStopThreshold * 100) / 100.0;
|
config.PowerLimiter.VoltageStopThreshold = static_cast<int>(config.PowerLimiter.VoltageStopThreshold * 100) / 100.0;
|
||||||
config.PowerLimiter.VoltageLoadCorrectionFactor = root[F("voltage_load_correction_factor")].as<float>();
|
config.PowerLimiter.VoltageLoadCorrectionFactor = root["voltage_load_correction_factor"].as<float>();
|
||||||
config.PowerLimiter.RestartHour = root[F("inverter_restart_hour")].as<int8_t>();
|
config.PowerLimiter.RestartHour = root["inverter_restart_hour"].as<int8_t>();
|
||||||
config.PowerLimiter.FullSolarPassThroughSoc = root[F("full_solar_passthrough_soc")].as<uint32_t>();
|
config.PowerLimiter.FullSolarPassThroughSoc = root["full_solar_passthrough_soc"].as<uint32_t>();
|
||||||
config.PowerLimiter.FullSolarPassThroughStartVoltage = static_cast<int>(root[F("full_solar_passthrough_start_voltage")].as<float>() * 100) / 100.0;
|
config.PowerLimiter.FullSolarPassThroughStartVoltage = static_cast<int>(root["full_solar_passthrough_start_voltage"].as<float>() * 100) / 100.0;
|
||||||
config.PowerLimiter.FullSolarPassThroughStopVoltage = static_cast<int>(root[F("full_solar_passthrough_stop_voltage")].as<float>() * 100) / 100.0;
|
config.PowerLimiter.FullSolarPassThroughStopVoltage = static_cast<int>(root["full_solar_passthrough_stop_voltage"].as<float>() * 100) / 100.0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -155,8 +155,8 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
PowerLimiter.calcNextInverterRestart();
|
PowerLimiter.calcNextInverterRestart();
|
||||||
|
|
||||||
retMsg[F("type")] = F("success");
|
retMsg["type"] = "success";
|
||||||
retMsg[F("message")] = F("Settings saved!");
|
retMsg["message"] = "Settings saved!";
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
|
|||||||
@ -38,32 +38,32 @@ void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
|
|||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
root[F("enabled")] = config.PowerMeter.Enabled;
|
root["enabled"] = config.PowerMeter.Enabled;
|
||||||
root[F("verbose_logging")] = config.PowerMeter.VerboseLogging;
|
root["verbose_logging"] = config.PowerMeter.VerboseLogging;
|
||||||
root[F("source")] = config.PowerMeter.Source;
|
root["source"] = config.PowerMeter.Source;
|
||||||
root[F("interval")] = config.PowerMeter.Interval;
|
root["interval"] = config.PowerMeter.Interval;
|
||||||
root[F("mqtt_topic_powermeter_1")] = config.PowerMeter.MqttTopicPowerMeter1;
|
root["mqtt_topic_powermeter_1"] = config.PowerMeter.MqttTopicPowerMeter1;
|
||||||
root[F("mqtt_topic_powermeter_2")] = config.PowerMeter.MqttTopicPowerMeter2;
|
root["mqtt_topic_powermeter_2"] = config.PowerMeter.MqttTopicPowerMeter2;
|
||||||
root[F("mqtt_topic_powermeter_3")] = config.PowerMeter.MqttTopicPowerMeter3;
|
root["mqtt_topic_powermeter_3"] = config.PowerMeter.MqttTopicPowerMeter3;
|
||||||
root[F("sdmbaudrate")] = config.PowerMeter.SdmBaudrate;
|
root["sdmbaudrate"] = config.PowerMeter.SdmBaudrate;
|
||||||
root[F("sdmaddress")] = config.PowerMeter.SdmAddress;
|
root["sdmaddress"] = config.PowerMeter.SdmAddress;
|
||||||
root[F("http_individual_requests")] = config.PowerMeter.HttpIndividualRequests;
|
root["http_individual_requests"] = config.PowerMeter.HttpIndividualRequests;
|
||||||
|
|
||||||
JsonArray httpPhases = root.createNestedArray(F("http_phases"));
|
JsonArray httpPhases = root.createNestedArray("http_phases");
|
||||||
|
|
||||||
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
||||||
JsonObject phaseObject = httpPhases.createNestedObject();
|
JsonObject phaseObject = httpPhases.createNestedObject();
|
||||||
|
|
||||||
phaseObject[F("index")] = i + 1;
|
phaseObject["index"] = i + 1;
|
||||||
phaseObject[F("enabled")] = config.PowerMeter.Http_Phase[i].Enabled;
|
phaseObject["enabled"] = config.PowerMeter.Http_Phase[i].Enabled;
|
||||||
phaseObject[F("url")] = String(config.PowerMeter.Http_Phase[i].Url);
|
phaseObject["url"] = String(config.PowerMeter.Http_Phase[i].Url);
|
||||||
phaseObject[F("auth_type")]= config.PowerMeter.Http_Phase[i].AuthType;
|
phaseObject["auth_type"]= config.PowerMeter.Http_Phase[i].AuthType;
|
||||||
phaseObject[F("username")] = String(config.PowerMeter.Http_Phase[i].Username);
|
phaseObject["username"] = String(config.PowerMeter.Http_Phase[i].Username);
|
||||||
phaseObject[F("password")] = String(config.PowerMeter.Http_Phase[i].Password);
|
phaseObject["password"] = String(config.PowerMeter.Http_Phase[i].Password);
|
||||||
phaseObject[F("header_key")] = String(config.PowerMeter.Http_Phase[i].HeaderKey);
|
phaseObject["header_key"] = String(config.PowerMeter.Http_Phase[i].HeaderKey);
|
||||||
phaseObject[F("header_value")] = String(config.PowerMeter.Http_Phase[i].HeaderValue);
|
phaseObject["header_value"] = String(config.PowerMeter.Http_Phase[i].HeaderValue);
|
||||||
phaseObject[F("json_path")] = String(config.PowerMeter.Http_Phase[i].JsonPath);
|
phaseObject["json_path"] = String(config.PowerMeter.Http_Phase[i].JsonPath);
|
||||||
phaseObject[F("timeout")] = config.PowerMeter.Http_Phase[i].Timeout;
|
phaseObject["timeout"] = config.PowerMeter.Http_Phase[i].Timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
@ -87,10 +87,10 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonObject retMsg = response->getRoot();
|
JsonObject retMsg = response->getRoot();
|
||||||
retMsg[F("type")] = F("warning");
|
retMsg["type"] = "warning";
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
if (!request->hasParam("data", true)) {
|
||||||
retMsg[F("message")] = F("No values found!");
|
retMsg["message"] = "No values found!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -99,7 +99,7 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
String json = request->getParam("data", true)->value();
|
String json = request->getParam("data", true)->value();
|
||||||
|
|
||||||
if (json.length() > 4096) {
|
if (json.length() > 4096) {
|
||||||
retMsg[F("message")] = F("Data too large!");
|
retMsg["message"] = "Data too large!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -109,49 +109,49 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
DeserializationError error = deserializeJson(root, json);
|
DeserializationError error = deserializeJson(root, json);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
retMsg[F("message")] = F("Failed to parse data!");
|
retMsg["message"] = "Failed to parse data!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(root.containsKey("enabled") && root.containsKey("source"))) {
|
if (!(root.containsKey("enabled") && root.containsKey("source"))) {
|
||||||
retMsg[F("message")] = F("Values are missing!");
|
retMsg["message"] = "Values are missing!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root[F("source")].as<uint8_t>() == PowerMeter.SOURCE_HTTP) {
|
if (root["source"].as<uint8_t>() == PowerMeter.SOURCE_HTTP) {
|
||||||
JsonArray http_phases = root[F("http_phases")];
|
JsonArray http_phases = root["http_phases"];
|
||||||
for (uint8_t i = 0; i < http_phases.size(); i++) {
|
for (uint8_t i = 0; i < http_phases.size(); i++) {
|
||||||
JsonObject phase = http_phases[i].as<JsonObject>();
|
JsonObject phase = http_phases[i].as<JsonObject>();
|
||||||
|
|
||||||
if (i > 0 && !phase[F("enabled")].as<bool>()) {
|
if (i > 0 && !phase["enabled"].as<bool>()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == 0 || phase[F("http_individual_requests")].as<bool>()) {
|
if (i == 0 || phase["http_individual_requests"].as<bool>()) {
|
||||||
if (!phase.containsKey("url")
|
if (!phase.containsKey("url")
|
||||||
|| (!phase[F("url")].as<String>().startsWith("http://")
|
|| (!phase["url"].as<String>().startsWith("http://")
|
||||||
&& !phase[F("url")].as<String>().startsWith("https://"))) {
|
&& !phase["url"].as<String>().startsWith("https://"))) {
|
||||||
retMsg[F("message")] = F("URL must either start with http:// or https://!");
|
retMsg["message"] = "URL must either start with http:// or https://!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((phase[F("auth_type")].as<Auth>() != Auth::none)
|
if ((phase["auth_type"].as<Auth>() != Auth::none)
|
||||||
&& ( phase[F("username")].as<String>().length() == 0 || phase[F("password")].as<String>().length() == 0)) {
|
&& ( phase["username"].as<String>().length() == 0 || phase["password"].as<String>().length() == 0)) {
|
||||||
retMsg[F("message")] = F("Username or password must not be empty!");
|
retMsg["message"] = "Username or password must not be empty!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!phase.containsKey("timeout")
|
if (!phase.containsKey("timeout")
|
||||||
|| phase[F("timeout")].as<uint16_t>() <= 0) {
|
|| phase["timeout"].as<uint16_t>() <= 0) {
|
||||||
retMsg[F("message")] = F("Timeout must be greater than 0 ms!");
|
retMsg["message"] = "Timeout must be greater than 0 ms!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -159,8 +159,8 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!phase.containsKey("json_path")
|
if (!phase.containsKey("json_path")
|
||||||
|| phase[F("json_path")].as<String>().length() == 0) {
|
|| phase["json_path"].as<String>().length() == 0) {
|
||||||
retMsg[F("message")] = F("Json path must not be empty!");
|
retMsg["message"] = "Json path must not be empty!";
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -169,36 +169,36 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
config.PowerMeter.Enabled = root[F("enabled")].as<bool>();
|
config.PowerMeter.Enabled = root["enabled"].as<bool>();
|
||||||
config.PowerMeter.VerboseLogging = root[F("verbose_logging")].as<bool>();
|
config.PowerMeter.VerboseLogging = root["verbose_logging"].as<bool>();
|
||||||
config.PowerMeter.Source = root[F("source")].as<uint8_t>();
|
config.PowerMeter.Source = root["source"].as<uint8_t>();
|
||||||
config.PowerMeter.Interval = root[F("interval")].as<uint32_t>();
|
config.PowerMeter.Interval = root["interval"].as<uint32_t>();
|
||||||
strlcpy(config.PowerMeter.MqttTopicPowerMeter1, root[F("mqtt_topic_powermeter_1")].as<String>().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter1));
|
strlcpy(config.PowerMeter.MqttTopicPowerMeter1, root["mqtt_topic_powermeter_1"].as<String>().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter1));
|
||||||
strlcpy(config.PowerMeter.MqttTopicPowerMeter2, root[F("mqtt_topic_powermeter_2")].as<String>().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter2));
|
strlcpy(config.PowerMeter.MqttTopicPowerMeter2, root["mqtt_topic_powermeter_2"].as<String>().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter2));
|
||||||
strlcpy(config.PowerMeter.MqttTopicPowerMeter3, root[F("mqtt_topic_powermeter_3")].as<String>().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter3));
|
strlcpy(config.PowerMeter.MqttTopicPowerMeter3, root["mqtt_topic_powermeter_3"].as<String>().c_str(), sizeof(config.PowerMeter.MqttTopicPowerMeter3));
|
||||||
config.PowerMeter.SdmBaudrate = root[F("sdmbaudrate")].as<uint32_t>();
|
config.PowerMeter.SdmBaudrate = root["sdmbaudrate"].as<uint32_t>();
|
||||||
config.PowerMeter.SdmAddress = root[F("sdmaddress")].as<uint8_t>();
|
config.PowerMeter.SdmAddress = root["sdmaddress"].as<uint8_t>();
|
||||||
config.PowerMeter.HttpIndividualRequests = root[F("http_individual_requests")].as<bool>();
|
config.PowerMeter.HttpIndividualRequests = root["http_individual_requests"].as<bool>();
|
||||||
|
|
||||||
JsonArray http_phases = root[F("http_phases")];
|
JsonArray http_phases = root["http_phases"];
|
||||||
for (uint8_t i = 0; i < http_phases.size(); i++) {
|
for (uint8_t i = 0; i < http_phases.size(); i++) {
|
||||||
JsonObject phase = http_phases[i].as<JsonObject>();
|
JsonObject phase = http_phases[i].as<JsonObject>();
|
||||||
|
|
||||||
config.PowerMeter.Http_Phase[i].Enabled = (i == 0 ? true : phase[F("enabled")].as<bool>());
|
config.PowerMeter.Http_Phase[i].Enabled = (i == 0 ? true : phase["enabled"].as<bool>());
|
||||||
strlcpy(config.PowerMeter.Http_Phase[i].Url, phase[F("url")].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Url));
|
strlcpy(config.PowerMeter.Http_Phase[i].Url, phase["url"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Url));
|
||||||
config.PowerMeter.Http_Phase[i].AuthType = phase[F("auth_type")].as<Auth>();
|
config.PowerMeter.Http_Phase[i].AuthType = phase["auth_type"].as<Auth>();
|
||||||
strlcpy(config.PowerMeter.Http_Phase[i].Username, phase[F("username")].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Username));
|
strlcpy(config.PowerMeter.Http_Phase[i].Username, phase["username"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Username));
|
||||||
strlcpy(config.PowerMeter.Http_Phase[i].Password, phase[F("password")].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Password));
|
strlcpy(config.PowerMeter.Http_Phase[i].Password, phase["password"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].Password));
|
||||||
strlcpy(config.PowerMeter.Http_Phase[i].HeaderKey, phase[F("header_key")].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderKey));
|
strlcpy(config.PowerMeter.Http_Phase[i].HeaderKey, phase["header_key"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderKey));
|
||||||
strlcpy(config.PowerMeter.Http_Phase[i].HeaderValue, phase[F("header_value")].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderValue));
|
strlcpy(config.PowerMeter.Http_Phase[i].HeaderValue, phase["header_value"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].HeaderValue));
|
||||||
config.PowerMeter.Http_Phase[i].Timeout = phase[F("timeout")].as<uint16_t>();
|
config.PowerMeter.Http_Phase[i].Timeout = phase["timeout"].as<uint16_t>();
|
||||||
strlcpy(config.PowerMeter.Http_Phase[i].JsonPath, phase[F("json_path")].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].JsonPath));
|
strlcpy(config.PowerMeter.Http_Phase[i].JsonPath, phase["json_path"].as<String>().c_str(), sizeof(config.PowerMeter.Http_Phase[i].JsonPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
Configuration.write();
|
Configuration.write();
|
||||||
|
|
||||||
retMsg[F("type")] = F("success");
|
retMsg["type"] = "success";
|
||||||
retMsg[F("message")] = F("Settings saved!");
|
retMsg["message"] = "Settings saved!";
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
@ -217,10 +217,10 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
AsyncJsonResponse* asyncJsonResponse = new AsyncJsonResponse();
|
AsyncJsonResponse* asyncJsonResponse = new AsyncJsonResponse();
|
||||||
JsonObject retMsg = asyncJsonResponse->getRoot();
|
JsonObject retMsg = asyncJsonResponse->getRoot();
|
||||||
retMsg[F("type")] = F("warning");
|
retMsg["type"] = "warning";
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
if (!request->hasParam("data", true)) {
|
||||||
retMsg[F("message")] = F("No values found!");
|
retMsg["message"] = "No values found!";
|
||||||
asyncJsonResponse->setLength();
|
asyncJsonResponse->setLength();
|
||||||
request->send(asyncJsonResponse);
|
request->send(asyncJsonResponse);
|
||||||
return;
|
return;
|
||||||
@ -229,7 +229,7 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
String json = request->getParam("data", true)->value();
|
String json = request->getParam("data", true)->value();
|
||||||
|
|
||||||
if (json.length() > 2048) {
|
if (json.length() > 2048) {
|
||||||
retMsg[F("message")] = F("Data too large!");
|
retMsg["message"] = "Data too large!";
|
||||||
asyncJsonResponse->setLength();
|
asyncJsonResponse->setLength();
|
||||||
request->send(asyncJsonResponse);
|
request->send(asyncJsonResponse);
|
||||||
return;
|
return;
|
||||||
@ -239,7 +239,7 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
DeserializationError error = deserializeJson(root, json);
|
DeserializationError error = deserializeJson(root, json);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
retMsg[F("message")] = F("Failed to parse data!");
|
retMsg["message"] = "Failed to parse data!";
|
||||||
asyncJsonResponse->setLength();
|
asyncJsonResponse->setLength();
|
||||||
request->send(asyncJsonResponse);
|
request->send(asyncJsonResponse);
|
||||||
return;
|
return;
|
||||||
@ -248,7 +248,7 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
if (!root.containsKey("url") || !root.containsKey("auth_type") || !root.containsKey("username") || !root.containsKey("password")
|
if (!root.containsKey("url") || !root.containsKey("auth_type") || !root.containsKey("username") || !root.containsKey("password")
|
||||||
|| !root.containsKey("header_key") || !root.containsKey("header_value")
|
|| !root.containsKey("header_key") || !root.containsKey("header_value")
|
||||||
|| !root.containsKey("timeout") || !root.containsKey("json_path")) {
|
|| !root.containsKey("timeout") || !root.containsKey("json_path")) {
|
||||||
retMsg[F("message")] = F("Missing fields!");
|
retMsg["message"] = "Missing fields!";
|
||||||
asyncJsonResponse->setLength();
|
asyncJsonResponse->setLength();
|
||||||
request->send(asyncJsonResponse);
|
request->send(asyncJsonResponse);
|
||||||
return;
|
return;
|
||||||
@ -258,15 +258,15 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
errorMessage[256];
|
errorMessage[256];
|
||||||
char response[200];
|
char response[200];
|
||||||
|
|
||||||
if (HttpPowerMeter.httpRequest(root[F("url")].as<String>().c_str(),
|
if (HttpPowerMeter.httpRequest(root["url"].as<String>().c_str(),
|
||||||
root[F("auth_type")].as<Auth>(), root[F("username")].as<String>().c_str(), root[F("password")].as<String>().c_str(),
|
root["auth_type"].as<Auth>(), root["username"].as<String>().c_str(), root["password"].as<String>().c_str(),
|
||||||
root[F("header_key")].as<String>().c_str(), root[F("header_value")].as<String>().c_str(), root[F("timeout")].as<uint16_t>(),
|
root["header_key"].as<String>().c_str(), root["header_value"].as<String>().c_str(), root["timeout"].as<uint16_t>(),
|
||||||
powerMeterResponse, sizeof(powerMeterResponse), errorMessage, sizeof(errorMessage))) {
|
powerMeterResponse, sizeof(powerMeterResponse), errorMessage, sizeof(errorMessage))) {
|
||||||
float power;
|
float power;
|
||||||
|
|
||||||
if (HttpPowerMeter.getFloatValueByJsonPath(powerMeterResponse,
|
if (HttpPowerMeter.getFloatValueByJsonPath(powerMeterResponse,
|
||||||
root[F("json_path")].as<String>().c_str(), power)) {
|
root["json_path"].as<String>().c_str(), power)) {
|
||||||
retMsg[F("type")] = F("success");
|
retMsg["type"] = "success";
|
||||||
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", power);
|
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", power);
|
||||||
} else {
|
} else {
|
||||||
snprintf_P(response, sizeof(response), "Error: Could not find value for JSON path!");
|
snprintf_P(response, sizeof(response), "Error: Could not find value for JSON path!");
|
||||||
@ -275,7 +275,7 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
snprintf_P(response, sizeof(response), errorMessage);
|
snprintf_P(response, sizeof(response), errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
retMsg[F("message")] = F(response);
|
retMsg["message"] = response;
|
||||||
asyncJsonResponse->setLength();
|
asyncJsonResponse->setLength();
|
||||||
request->send(asyncJsonResponse);
|
request->send(asyncJsonResponse);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,9 +36,9 @@ void WebApiVedirectClass::onVedirectStatus(AsyncWebServerRequest* request)
|
|||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
root[F("vedirect_enabled")] = config.Vedirect.Enabled;
|
root["vedirect_enabled"] = config.Vedirect.Enabled;
|
||||||
root[F("verbose_logging")] = config.Vedirect.VerboseLogging;
|
root["verbose_logging"] = config.Vedirect.VerboseLogging;
|
||||||
root[F("vedirect_updatesonly")] = config.Vedirect.UpdatesOnly;
|
root["vedirect_updatesonly"] = config.Vedirect.UpdatesOnly;
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
@ -54,9 +54,9 @@ void WebApiVedirectClass::onVedirectAdminGet(AsyncWebServerRequest* request)
|
|||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
root[F("vedirect_enabled")] = config.Vedirect.Enabled;
|
root["vedirect_enabled"] = config.Vedirect.Enabled;
|
||||||
root[F("verbose_logging")] = config.Vedirect.VerboseLogging;
|
root["verbose_logging"] = config.Vedirect.VerboseLogging;
|
||||||
root[F("vedirect_updatesonly")] = config.Vedirect.UpdatesOnly;
|
root["vedirect_updatesonly"] = config.Vedirect.UpdatesOnly;
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
@ -70,11 +70,11 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonObject retMsg = response->getRoot();
|
JsonObject retMsg = response->getRoot();
|
||||||
retMsg[F("type")] = F("warning");
|
retMsg["type"] = "warning";
|
||||||
|
|
||||||
if (!request->hasParam("data", true)) {
|
if (!request->hasParam("data", true)) {
|
||||||
retMsg[F("message")] = F("No values found!");
|
retMsg["message"] = "No values found!";
|
||||||
retMsg[F("code")] = WebApiError::GenericNoValueFound;
|
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -83,8 +83,8 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request)
|
|||||||
String json = request->getParam("data", true)->value();
|
String json = request->getParam("data", true)->value();
|
||||||
|
|
||||||
if (json.length() > 1024) {
|
if (json.length() > 1024) {
|
||||||
retMsg[F("message")] = F("Data too large!");
|
retMsg["message"] = "Data too large!";
|
||||||
retMsg[F("code")] = WebApiError::GenericDataTooLarge;
|
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -94,8 +94,8 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request)
|
|||||||
DeserializationError error = deserializeJson(root, json);
|
DeserializationError error = deserializeJson(root, json);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
retMsg[F("message")] = F("Failed to parse data!");
|
retMsg["message"] = "Failed to parse data!";
|
||||||
retMsg[F("code")] = WebApiError::GenericParseError;
|
retMsg["code"] = WebApiError::GenericParseError;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -104,24 +104,24 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request)
|
|||||||
if (!root.containsKey("vedirect_enabled") ||
|
if (!root.containsKey("vedirect_enabled") ||
|
||||||
!root.containsKey("verbose_logging") ||
|
!root.containsKey("verbose_logging") ||
|
||||||
!root.containsKey("vedirect_updatesonly") ) {
|
!root.containsKey("vedirect_updatesonly") ) {
|
||||||
retMsg[F("message")] = F("Values are missing!");
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg[F("code")] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
config.Vedirect.Enabled = root[F("vedirect_enabled")].as<bool>();
|
config.Vedirect.Enabled = root["vedirect_enabled"].as<bool>();
|
||||||
config.Vedirect.VerboseLogging = root[F("verbose_logging")].as<bool>();
|
config.Vedirect.VerboseLogging = root["verbose_logging"].as<bool>();
|
||||||
config.Vedirect.UpdatesOnly = root[F("vedirect_updatesonly")].as<bool>();
|
config.Vedirect.UpdatesOnly = root["vedirect_updatesonly"].as<bool>();
|
||||||
Configuration.write();
|
Configuration.write();
|
||||||
|
|
||||||
VictronMppt.updateSettings();
|
VictronMppt.updateSettings();
|
||||||
|
|
||||||
retMsg[F("type")] = F("success");
|
retMsg["type"] = "success";
|
||||||
retMsg[F("message")] = F("Settings saved!");
|
retMsg["message"] = "Settings saved!";
|
||||||
retMsg[F("code")] = WebApiError::GenericSuccess;
|
retMsg["code"] = WebApiError::GenericSuccess;
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
|
|||||||
@ -78,26 +78,26 @@ void WebApiWsHuaweiLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
const RectifierParameters_t * rp = HuaweiCan.get();
|
const RectifierParameters_t * rp = HuaweiCan.get();
|
||||||
|
|
||||||
root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000;
|
root["data_age"] = (millis() - HuaweiCan.getLastUpdate()) / 1000;
|
||||||
root[F("input_voltage")]["v"] = rp->input_voltage;
|
root["input_voltage"]["v"] = rp->input_voltage;
|
||||||
root[F("input_voltage")]["u"] = "V";
|
root["input_voltage"]["u"] = "V";
|
||||||
root[F("input_current")]["v"] = rp->input_current;
|
root["input_current"]["v"] = rp->input_current;
|
||||||
root[F("input_current")]["u"] = "A";
|
root["input_current"]["u"] = "A";
|
||||||
root[F("input_power")]["v"] = rp->input_power;
|
root["input_power"]["v"] = rp->input_power;
|
||||||
root[F("input_power")]["u"] = "W";
|
root["input_power"]["u"] = "W";
|
||||||
root[F("output_voltage")]["v"] = rp->output_voltage;
|
root["output_voltage"]["v"] = rp->output_voltage;
|
||||||
root[F("output_voltage")]["u"] = "V";
|
root["output_voltage"]["u"] = "V";
|
||||||
root[F("output_current")]["v"] = rp->output_current;
|
root["output_current"]["v"] = rp->output_current;
|
||||||
root[F("output_current")]["u"] = "A";
|
root["output_current"]["u"] = "A";
|
||||||
root[F("max_output_current")]["v"] = rp->max_output_current;
|
root["max_output_current"]["v"] = rp->max_output_current;
|
||||||
root[F("max_output_current")]["u"] = "A";
|
root["max_output_current"]["u"] = "A";
|
||||||
root[F("output_power")]["v"] = rp->output_power;
|
root["output_power"]["v"] = rp->output_power;
|
||||||
root[F("output_power")]["u"] = "W";
|
root["output_power"]["u"] = "W";
|
||||||
root[F("input_temp")]["v"] = rp->input_temp;
|
root["input_temp"]["v"] = rp->input_temp;
|
||||||
root[F("input_temp")]["u"] = "°C";
|
root["input_temp"]["u"] = "°C";
|
||||||
root[F("output_temp")]["v"] = rp->output_temp;
|
root["output_temp"]["v"] = rp->output_temp;
|
||||||
root[F("output_temp")]["u"] = "°C";
|
root["output_temp"]["u"] = "°C";
|
||||||
root[F("efficiency")]["v"] = rp->efficiency * 100;
|
root["efficiency"]["v"] = rp->efficiency * 100;
|
||||||
root[F("efficiency")]["u"] = "%";
|
root["efficiency"]["u"] = "%";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -184,7 +184,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
}
|
}
|
||||||
|
|
||||||
JsonObject vedirectObj = root.createNestedObject("vedirect");
|
JsonObject vedirectObj = root.createNestedObject("vedirect");
|
||||||
vedirectObj[F("enabled")] = Configuration.get().Vedirect.Enabled;
|
vedirectObj["enabled"] = Configuration.get().Vedirect.Enabled;
|
||||||
JsonObject totalVeObj = vedirectObj.createNestedObject("total");
|
JsonObject totalVeObj = vedirectObj.createNestedObject("total");
|
||||||
|
|
||||||
addTotalField(totalVeObj, "Power", VictronMppt.getPanelPowerWatts(), "W", 1);
|
addTotalField(totalVeObj, "Power", VictronMppt.getPanelPowerWatts(), "W", 1);
|
||||||
@ -192,16 +192,16 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
addTotalField(totalVeObj, "YieldTotal", VictronMppt.getYieldTotal(), "kWh", 2);
|
addTotalField(totalVeObj, "YieldTotal", VictronMppt.getYieldTotal(), "kWh", 2);
|
||||||
|
|
||||||
JsonObject huaweiObj = root.createNestedObject("huawei");
|
JsonObject huaweiObj = root.createNestedObject("huawei");
|
||||||
huaweiObj[F("enabled")] = Configuration.get().Huawei.Enabled;
|
huaweiObj["enabled"] = Configuration.get().Huawei.Enabled;
|
||||||
const RectifierParameters_t * rp = HuaweiCan.get();
|
const RectifierParameters_t * rp = HuaweiCan.get();
|
||||||
addTotalField(huaweiObj, "Power", rp->output_power, "W", 2);
|
addTotalField(huaweiObj, "Power", rp->output_power, "W", 2);
|
||||||
|
|
||||||
JsonObject batteryObj = root.createNestedObject("battery");
|
JsonObject batteryObj = root.createNestedObject("battery");
|
||||||
batteryObj[F("enabled")] = Configuration.get().Battery.Enabled;
|
batteryObj["enabled"] = Configuration.get().Battery.Enabled;
|
||||||
addTotalField(batteryObj, "soc", Battery.getStats()->getSoC(), "%", 0);
|
addTotalField(batteryObj, "soc", Battery.getStats()->getSoC(), "%", 0);
|
||||||
|
|
||||||
JsonObject powerMeterObj = root.createNestedObject("power_meter");
|
JsonObject powerMeterObj = root.createNestedObject("power_meter");
|
||||||
powerMeterObj[F("enabled")] = Configuration.get().PowerMeter.Enabled;
|
powerMeterObj["enabled"] = Configuration.get().PowerMeter.Enabled;
|
||||||
addTotalField(powerMeterObj, "Power", PowerMeter.getPowerTotal(false), "W", 1);
|
addTotalField(powerMeterObj, "Power", PowerMeter.getPowerTotal(false), "W", 1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user