Wifi settings for common image

This commit is contained in:
Jaakko Haakana 2025-07-11 15:10:16 +03:00
parent 0b73436d3d
commit 5d86058852
9 changed files with 254 additions and 384 deletions

View file

@ -21,14 +21,26 @@ volatile CAN_Configuration can_config = {
.shunt = CAN_NATIVE // (OPTIONAL) Which CAN is your shunt connected to?
};
#ifdef COMMON_IMAGE
std::string ssid;
std::string password;
std::string passwordAP;
#else
std::string ssid = WIFI_SSID; // Set in USER_SECRETS.h
std::string password = WIFI_PASSWORD; // Set in USER_SECRETS.h
const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters, also used for device name on web interface
const char* passwordAP = AP_PASSWORD; // Set in USER_SECRETS.h
std::string passwordAP = AP_PASSWORD; // Set in USER_SECRETS.h
#endif
const uint8_t wifi_channel = 0; // Set to 0 for automatic channel selection
const char* http_username = HTTP_USERNAME; // Set in USER_SECRETS.h
const char* http_password = HTTP_PASSWORD; // Set in USER_SECRETS.h
#ifdef COMMON_IMAGE
std::string http_username;
std::string http_password;
#else
std::string http_username = HTTP_USERNAME; // Set in USER_SECRETS.h
std::string http_password = HTTP_PASSWORD; // Set in USER_SECRETS.h
#endif
// Set your Static IP address. Only used incase WIFICONFIG is set in USER_SETTINGS.h
IPAddress local_IP(192, 168, 10, 150);
IPAddress gateway(192, 168, 10, 1);

View file

@ -51,6 +51,8 @@ ACAN2517FDSettings* settings2517;
// Initialization functions
bool native_can_initialized = false;
bool init_CAN() {
auto nativeIt = can_receivers.find(CAN_NATIVE);
@ -82,6 +84,7 @@ bool init_CAN() {
CAN_cfg.rx_queue = xQueueCreate(rx_queue_size, sizeof(CAN_frame_t));
// Init CAN Module
ESP32Can.CANInit();
native_can_initialized = true;
}
auto addonIt = can_receivers.find(CAN_ADDON_MCP2515);
@ -260,7 +263,9 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
// Receive functions
void receive_can() {
if (native_can_initialized) {
receive_frame_can_native(); // Receive CAN messages from native CAN port
}
if (can2515) {
receive_frame_can_addon(); // Receive CAN messages on add-on MCP2515 chip

View file

@ -111,7 +111,9 @@ void init_stored_settings() {
remote_bms_reset = settings.getBool("REMBMSRESET", false);
use_canfd_as_can = settings.getBool("CANFDASCAN", false);
wifiap_enabled = settings.getBool("WIFIAPENABLED", false);
// WIFI AP is enabled by default unless disabled in the settings
wifiap_enabled = settings.getBool("WIFIAPENABLED", true);
passwordAP = settings.getString("APPASSWORD", "123456789").c_str();
mqtt_enabled = settings.getBool("MQTTENABLED", false);
ha_autodiscovery_enabled = settings.getBool("HADISC", false);

View file

@ -48,6 +48,11 @@ class BatteryEmulatorSettingsStore {
~BatteryEmulatorSettingsStore() { settings.end(); }
void clearAll() {
settings.clear();
settingsUpdated = true;
}
uint32_t getUInt(const char* name, uint32_t defaultValue) { return settings.getUInt(name, defaultValue); }
void saveUInt(const char* name, uint32_t value) {
@ -56,7 +61,9 @@ class BatteryEmulatorSettingsStore {
settingsUpdated = settingsUpdated || value != oldValue;
}
bool getBool(const char* name) { return settings.getBool(name, false); }
bool settingExists(const char* name) { return settings.isKey(name); }
bool getBool(const char* name, bool defaultValue = false) { return settings.getBool(name, defaultValue); }
void saveBool(const char* name, bool value) {
auto oldValue = settings.getBool(name, false);

View file

@ -217,7 +217,7 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
}
if (var == "WIFIAPENABLED") {
return settings.getBool("WIFIAPENABLED") ? "checked" : "";
return settings.getBool("WIFIAPENABLED", wifiap_enabled) ? "checked" : "";
}
if (var == "MQTTENABLED") {
@ -435,6 +435,26 @@ const char* getCANInterfaceName(CAN_Interface interface) {
#define SETTINGS_HTML_SCRIPTS \
R"rawliteral(
<script>
function askFactoryReset() {
if (confirm('Are you sure you want to reset the device to factory settings? This will erase all settings and data.')) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
if (this.status == 200) {
alert('Factory reset successful. The device will now restart.');
reboot();
} else {
alert('Factory reset failed. Please try again.');
}
};
xhr.onerror = function() {
alert('An error occurred while trying to reset the device.');
};
xhr.open('POST', '/factoryReset', true);
xhr.send();
}
}
function editComplete(){if(this.status==200){window.location.reload();}}
function editError(){alert('Invalid input');}
@ -725,6 +745,7 @@ const char* getCANInterfaceName(CAN_Interface interface) {
</div>
<button onclick="askFactoryReset()">Factory reset</button>
</div>

View file

@ -16,6 +16,10 @@
#include "../utils/timer.h"
#include "esp_task_wdt.h"
#include <string>
extern std::string http_username;
extern std::string http_password;
void transmit_can_frame(CAN_frame* tx_frame, int interface);
#ifdef WEBSERVER
@ -26,6 +30,14 @@ const bool webserver_enabled_default = false;
bool webserver_enabled = webserver_enabled_default; // Global flag to enable or disable the webserver
#ifndef COMMON_IMAGE
const bool webserver_auth_default = WEBSERVER_AUTH_REQUIRED;
#else
const bool webserver_auth_default = false;
#endif
bool webserver_auth = webserver_auth_default;
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
@ -167,30 +179,31 @@ void canReplayTask(void* param) {
vTaskDelete(NULL);
}
void def_route_with_auth(const char* uri, AsyncWebServer& serv, WebRequestMethodComposite method,
std::function<void(AsyncWebServerRequest*)> handler) {
serv.on(uri, method, [handler](AsyncWebServerRequest* request) {
if (webserver_auth && !request->authenticate(http_username.c_str(), http_password.c_str())) {
return request->requestAuthentication();
}
handler(request);
});
}
void init_webserver() {
server.on("/logout", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(401); });
// Route for firmware info from ota update page
server.on("/GetFirmwareInfo", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
def_route_with_auth("/GetFirmwareInfo", server, HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "application/json", get_firmware_info_html, get_firmware_info_processor);
});
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
request->send(200, "text/html", index_html, processor);
});
def_route_with_auth("/", server, HTTP_GET,
[](AsyncWebServerRequest* request) { request->send(200, "text/html", index_html, processor); });
// Route for going to settings web page
server.on("/settings", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
def_route_with_auth("/settings", server, HTTP_GET, [](AsyncWebServerRequest* request) {
// Using make_shared to ensure lifetime for the settings object during send() lambda execution
auto settings = std::make_shared<BatteryEmulatorSettingsStore>(true);
@ -199,30 +212,21 @@ void init_webserver() {
});
// Route for going to advanced battery info web page
server.on("/advanced", HTTP_GET, [](AsyncWebServerRequest* request) {
def_route_with_auth("/advanced", server, HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html", index_html, advanced_battery_processor);
});
// Route for going to CAN logging web page
server.on("/canlog", HTTP_GET, [](AsyncWebServerRequest* request) {
AsyncWebServerResponse* response = request->beginResponse(200, "text/html", can_logger_processor());
request->send(response);
def_route_with_auth("/canlog", server, HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(request->beginResponse(200, "text/html", can_logger_processor()));
});
// Route for going to CAN replay web page
server.on("/canreplay", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
AsyncWebServerResponse* response = request->beginResponse(200, "text/html", can_replay_processor());
request->send(response);
def_route_with_auth("/canreplay", server, HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(request->beginResponse(200, "text/html", can_replay_processor()));
});
server.on("/startReplay", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
def_route_with_auth("/startReplay", server, HTTP_GET, [](AsyncWebServerRequest* request) {
// Prevent multiple replay tasks from being created
if (isReplayRunning) {
request->send(400, "text/plain", "Replay already running!");
@ -238,18 +242,14 @@ void init_webserver() {
});
// Route for stopping the CAN replay
server.on("/stopReplay", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
def_route_with_auth("/stopReplay", server, HTTP_GET, [](AsyncWebServerRequest* request) {
datalayer.system.info.loop_playback = false;
request->send(200, "text/plain", "CAN replay stopped!");
});
// Route to handle setting the CAN interface for CAN replay
server.on("/setCANInterface", HTTP_GET, [](AsyncWebServerRequest* request) {
def_route_with_auth("/setCANInterface", server, HTTP_GET, [](AsyncWebServerRequest* request) {
if (request->hasParam("interface")) {
String canInterface = request->getParam("interface")->value();
@ -377,23 +377,17 @@ void init_webserver() {
#endif
// Route for going to cellmonitor web page
server.on("/cellmonitor", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
def_route_with_auth("/cellmonitor", server, HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html", index_html, cellmonitor_processor);
});
// Route for going to event log web page
server.on("/events", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
def_route_with_auth("/events", server, HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html", index_html, events_processor);
});
// Route for clearing all events
server.on("/clearevents", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
def_route_with_auth("/clearevents", server, HTTP_GET, [](AsyncWebServerRequest* request) {
reset_all_events();
// Send back a response that includes an instant redirect to /events
String response = "<html><body>";
@ -402,6 +396,14 @@ void init_webserver() {
request->send(200, "text/html", response);
});
def_route_with_auth("/factoryReset", server, HTTP_POST, [](AsyncWebServerRequest* request) {
// Reset all settings to factory defaults
BatteryEmulatorSettingsStore settings;
settings.clearAll();
request->send(200, "text/html", "OK");
});
#ifdef COMMON_IMAGE
struct BoolSetting {
const char* name;
@ -421,7 +423,7 @@ void init_webserver() {
std::vector<BoolSetting> boolSettings;
for (auto& name : boolSettingNames) {
boolSettings.push_back({name, settings.getBool(name), false});
boolSettings.push_back({name, settings.getBool(name, name == std::string("WIFIAPENABLED")), false});
}
int numParams = request->params();
@ -495,10 +497,7 @@ void init_webserver() {
#endif
// Route for editing SSID
server.on("/updateSSID", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
def_route_with_auth("/updateSSID", server, HTTP_GET, [](AsyncWebServerRequest* request) {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
if (value.length() <= 63) { // Check if SSID is within the allowable length
@ -513,9 +512,7 @@ void init_webserver() {
}
});
// Route for editing Password
server.on("/updatePassword", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
def_route_with_auth("/updatePassword", server, HTTP_GET, [](AsyncWebServerRequest* request) {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
if (value.length() > 8) { // Check if password is within the allowable length
@ -530,118 +527,84 @@ void init_webserver() {
}
});
// Route for editing Wh
server.on("/updateBatterySize", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
auto update_string = [](const char* route, std::function<void(String)> setter,
std::function<bool(String)> validator = nullptr) {
def_route_with_auth(route, server, HTTP_GET, [&](AsyncWebServerRequest* request) {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.info.total_capacity_Wh = value.toInt();
store_settings();
if (validator && !validator(value)) {
request->send(400, "text/plain", "Invalid value");
return;
}
setter(value);
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
};
auto update_string_setting = [=](const char* route, std::function<void(String)> setter,
std::function<bool(String)> validator = nullptr) {
update_string(
route,
[setter](String value) {
setter(value);
store_settings();
},
validator);
};
auto update_int_setting = [=](const char* route, std::function<void(int)> setter) {
update_string_setting(route, [setter](String value) { setter(value.toInt()); });
};
// Route for editing Wh
update_int_setting("/updateBatterySize", [](int value) { datalayer.battery.info.total_capacity_Wh = value; });
// Route for editing USE_SCALED_SOC
server.on("/updateUseScaledSOC", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.soc_scaling_active = value.toInt();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
update_int_setting("/updateUseScaledSOC", [](int value) { datalayer.battery.settings.soc_scaling_active = value; });
// Route for editing SOCMax
server.on("/updateSocMax", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
update_string_setting("/updateSocMax", [](String value) {
datalayer.battery.settings.max_percentage = static_cast<uint16_t>(value.toFloat() * 100);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for pause/resume Battery emulator
server.on("/pause", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("p")) {
String valueStr = request->getParam("p")->value();
setBatteryPause(valueStr == "true" || valueStr == "1", false);
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
update_string("/pause", [](String value) { setBatteryPause(value == "true" || value == "1", false); });
// Route for equipment stop/resume
server.on("/equipmentStop", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("stop")) {
String valueStr = request->getParam("stop")->value();
if (valueStr == "true" || valueStr == "1") {
update_string("/equipmentStop", [](String value) {
if (value == "true" || value == "1") {
setBatteryPause(true, false, true); //Pause battery, do not pause CAN, equipment stop on (store to flash)
} else {
setBatteryPause(false, false, false);
}
request->send(200, "text/plain", "Updated successfully");
});
update_string("/equipmentStop", [](String value) {
if (value == "true" || value == "1") {
setBatteryPause(true, false, true); //Pause battery, do not pause CAN, equipment stop on (store to flash)
} else {
request->send(400, "text/plain", "Bad Request");
setBatteryPause(false, false, false);
}
});
// Route for editing SOCMin
server.on("/updateSocMin", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
update_string_setting("/updateSocMin", [](String value) {
datalayer.battery.settings.min_percentage = static_cast<uint16_t>(value.toFloat() * 100);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing MaxChargeA
server.on("/updateMaxChargeA", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
update_string_setting("/updateMaxChargeA", [](String value) {
datalayer.battery.settings.max_user_set_charge_dA = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing MaxDischargeA
server.on("/updateMaxDischargeA", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
update_string_setting("/updateMaxDischargeA", [](String value) {
datalayer.battery.settings.max_user_set_discharge_dA = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
for (const auto& cmd : battery_commands) {
@ -649,7 +612,7 @@ void init_webserver() {
server.on(
route.c_str(), HTTP_PUT,
[cmd](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
if (webserver_auth && !request->authenticate(http_username.c_str(), http_password.c_str())) {
return request->requestAuthentication();
}
},
@ -671,247 +634,88 @@ void init_webserver() {
}
// Route for editing BATTERY_USE_VOLTAGE_LIMITS
server.on("/updateUseVoltageLimit", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.user_set_voltage_limits_active = value.toInt();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
update_int_setting("/updateUseVoltageLimit",
[](int value) { datalayer.battery.settings.user_set_voltage_limits_active = value; });
// Route for editing MaxChargeVoltage
server.on("/updateMaxChargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
update_string_setting("/updateMaxChargeVoltage", [](String value) {
datalayer.battery.settings.max_user_set_charge_voltage_dV = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing MaxDischargeVoltage
server.on("/updateMaxDischargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
update_string_setting("/updateMaxDischargeVoltage", [](String value) {
datalayer.battery.settings.max_user_set_discharge_voltage_dV = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing FakeBatteryVoltage
server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (!request->hasParam("value")) {
request->send(400, "text/plain", "Bad Request");
}
String value = request->getParam("value")->value();
float val = value.toFloat();
battery->set_fake_voltage(val);
request->send(200, "text/plain", "Updated successfully");
});
update_string_setting("/updateFakeBatteryVoltage", [](String value) { battery->set_fake_voltage(value.toFloat()); });
// Route for editing balancing enabled
server.on("/TeslaBalAct", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.user_requests_balancing = value.toInt();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
update_int_setting("/TeslaBalAct", [](int value) { datalayer.battery.settings.user_requests_balancing = value; });
// Route for editing balancing max time
server.on("/BalTime", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
update_string_setting("/BalTime", [](String value) {
datalayer.battery.settings.balancing_time_ms = static_cast<uint32_t>(value.toFloat() * 60000);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing balancing max power
server.on("/BalFloatPower", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
update_string_setting("/BalFloatPower", [](String value) {
datalayer.battery.settings.balancing_float_power_W = static_cast<uint16_t>(value.toFloat());
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing balancing max pack voltage
server.on("/BalMaxPackV", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
update_string_setting("/BalMaxPackV", [](String value) {
datalayer.battery.settings.balancing_max_pack_voltage_dV = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing balancing max cell voltage
server.on("/BalMaxCellV", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
update_string_setting("/BalMaxCellV", [](String value) {
datalayer.battery.settings.balancing_max_cell_voltage_mV = static_cast<uint16_t>(value.toFloat());
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing balancing max cell voltage deviation
server.on("/BalMaxDevCellV", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
update_string_setting("/BalMaxDevCellV", [](String value) {
datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV = static_cast<uint16_t>(value.toFloat());
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
if (charger) {
// Route for editing ChargerTargetV
server.on("/updateChargeSetpointV", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (!request->hasParam("value")) {
request->send(400, "text/plain", "Bad Request");
}
String value = request->getParam("value")->value();
update_string_setting(
"/updateChargeSetpointV", [](String value) { datalayer.charger.charger_setpoint_HV_VDC = value.toFloat(); },
[](String value) {
float val = value.toFloat();
if (!(val <= CHARGER_MAX_HV && val >= CHARGER_MIN_HV)) {
request->send(400, "text/plain", "Bad Request");
}
if (!(val * datalayer.charger.charger_setpoint_HV_IDC <= CHARGER_MAX_POWER)) {
request->send(400, "text/plain", "Bad Request");
}
datalayer.charger.charger_setpoint_HV_VDC = val;
request->send(200, "text/plain", "Updated successfully");
return (val <= CHARGER_MAX_HV && val >= CHARGER_MIN_HV) &&
(val * datalayer.charger.charger_setpoint_HV_IDC <= CHARGER_MAX_POWER);
});
// Route for editing ChargerTargetA
server.on("/updateChargeSetpointA", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (!request->hasParam("value")) {
request->send(400, "text/plain", "Bad Request");
}
String value = request->getParam("value")->value();
update_string_setting(
"/updateChargeSetpointA", [](String value) { datalayer.charger.charger_setpoint_HV_IDC = value.toFloat(); },
[](String value) {
float val = value.toFloat();
if (!(val <= datalayer.battery.settings.max_user_set_charge_dA && val <= CHARGER_MAX_A)) {
request->send(400, "text/plain", "Bad Request");
}
if (!(val * datalayer.charger.charger_setpoint_HV_VDC <= CHARGER_MAX_POWER)) {
request->send(400, "text/plain", "Bad Request");
}
datalayer.charger.charger_setpoint_HV_IDC = value.toFloat();
request->send(200, "text/plain", "Updated successfully");
return (val <= CHARGER_MAX_A) && (val <= datalayer.battery.settings.max_user_set_charge_dA) &&
(val * datalayer.charger.charger_setpoint_HV_VDC <= CHARGER_MAX_POWER);
});
// Route for editing ChargerEndA
server.on("/updateChargeEndA", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.charger.charger_setpoint_HV_IDC_END = value.toFloat();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
update_string_setting("/updateChargeEndA",
[](String value) { datalayer.charger.charger_setpoint_HV_IDC_END = value.toFloat(); });
// Route for enabling/disabling HV charger
server.on("/updateChargerHvEnabled", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.charger.charger_HV_enabled = (bool)value.toInt();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
update_int_setting("/updateChargerHvEnabled",
[](int value) { datalayer.charger.charger_HV_enabled = (bool)value; });
// Route for enabling/disabling aux12v charger
server.on("/updateChargerAux12vEnabled", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.charger.charger_aux12V_enabled = (bool)value.toInt();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
update_int_setting("/updateChargerAux12vEnabled",
[](int value) { datalayer.charger.charger_aux12V_enabled = (bool)value; });
}
// Send a GET request to <ESP_IP>/update
server.on("/debug", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
request->send(200, "text/plain", "Debug: all OK.");
});
def_route_with_auth("/debug", server, HTTP_GET,
[](AsyncWebServerRequest* request) { request->send(200, "text/plain", "Debug: all OK."); });
// Route to handle reboot command
server.on("/reboot", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
def_route_with_auth("/reboot", server, HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Rebooting server...");
//Equipment STOP without persisting the equipment state before restart
@ -995,7 +799,7 @@ String processor(const String& var) {
content += "</style>";
// Compact header
content += "<h2>" + String(ssidAP) + "</h2>";
content += "<h2>" + String(ssidAP.c_str()) + "</h2>";
// Start content block
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px; border-radius: 50px'>";
@ -1551,7 +1355,7 @@ String processor(const String& var) {
content += "<button onclick='Cellmon()'>Cellmonitor</button> ";
content += "<button onclick='Events()'>Events</button> ";
content += "<button onclick='askReboot()'>Reboot Emulator</button>";
if (WEBSERVER_AUTH_REQUIRED)
if (webserver_auth)
content += "<button onclick='logout()'>Logout</button>";
if (!datalayer.system.settings.equipment_stop_active)
content +=
@ -1577,7 +1381,7 @@ String processor(const String& var) {
content += "function CANreplay() { window.location.href = '/canreplay'; }";
content += "function Log() { window.location.href = '/log'; }";
content += "function Events() { window.location.href = '/events'; }";
if (WEBSERVER_AUTH_REQUIRED) {
if (webserver_auth) {
content += "function logout() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/logout', true);";
@ -1589,7 +1393,7 @@ String processor(const String& var) {
content +=
"var xhr=new "
"XMLHttpRequest();xhr.onload=function() { "
"window.location.reload();};xhr.open('GET','/pause?p='+pause,true);xhr.send();";
"window.location.reload();};xhr.open('GET','/pause?value='+pause,true);xhr.send();";
content += "}";
content += "function estop(stop){";
content +=

View file

@ -13,12 +13,6 @@ extern bool webserver_enabled;
extern const char* version_number; // The current software version, shown on webserver
#include <string>
extern const char* http_username;
extern const char* http_password;
extern const char* ssidAP;
// Common charger parameters
extern float charger_stat_HVcur;
extern float charger_stat_HVvol;

View file

@ -11,11 +11,15 @@ const bool wifi_enabled_default = false;
bool wifi_enabled = wifi_enabled_default;
#ifdef COMMON_IMAGE
const bool wifiap_enabled_default = true;
#else
#ifdef WIFIAP
const bool wifiap_enabled_default = true;
#else
const bool wifiap_enabled_default = false;
#endif
#endif
bool wifiap_enabled = wifiap_enabled_default;
@ -26,6 +30,8 @@ const bool mdns_enabled_default = false;
#endif
bool mdns_enabled = mdns_enabled_default;
std::string ssidAP;
// Configuration Parameters
static const uint16_t WIFI_CHECK_INTERVAL = 2000; // 1 seconds normal check interval when last connected
static const uint16_t STEP_WIFI_CHECK_INTERVAL = 2000; // 3 seconds wait step increase in checks for normal reconnects
@ -51,12 +57,14 @@ static uint16_t current_check_interval = WIFI_CHECK_INTERVAL;
static bool connected_once = false;
void init_WiFi() {
DEBUG_PRINTF("init_Wifi enabled=%d, apå=%d, ssid=%s, password=%s\n", wifi_enabled, wifiap_enabled, ssid.c_str(),
password.c_str());
if (!wifiap_enabled) {
if (wifiap_enabled) {
WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection
init_WiFi_AP();
} else {
WiFi.mode(WIFI_AP); // Only AP mode
} else if (wifi_enabled) {
WiFi.mode(WIFI_STA); // Only Router connection
}
// Set WiFi to auto reconnect
@ -67,17 +75,26 @@ void init_WiFi() {
WiFi.config(local_IP, gateway, subnet);
#endif
DEBUG_PRINTF("init_Wifi set event handlers\n");
// Initialize Wi-Fi event handlers
WiFi.onEvent(onWifiConnect, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED);
WiFi.onEvent(onWifiDisconnect, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
WiFi.onEvent(onWifiGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);
// Start Wi-Fi connection
DEBUG_PRINTF("start Wifi\n");
connectToWiFi();
DEBUG_PRINTF("init_Wifi complete\n");
}
// Task to monitor Wi-Fi status and handle reconnections
void wifi_monitor() {
if (ssid.empty() || password.empty()) {
return;
}
unsigned long currentMillis = millis();
// Check if it's time to monitor the Wi-Fi status
@ -124,6 +141,11 @@ void wifi_monitor() {
#ifdef DEBUG_LOG
logging.println("No previous OK connection, force a full connection attempt...");
#endif
wifiap_enabled = true;
WiFi.mode(WIFI_AP_STA);
init_WiFi_AP();
FullReconnectToWiFi();
}
}
@ -145,11 +167,18 @@ void FullReconnectToWiFi() {
// Function to handle Wi-Fi connection
void connectToWiFi() {
if (ssid.empty() || password.empty()) {
return;
}
if (WiFi.status() != WL_CONNECTED) {
lastReconnectAttempt = millis(); // Reset the reconnect attempt timer
#ifdef DEBUG_LOG
logging.println("Connecting to Wi-Fi...");
#endif
DEBUG_PRINTF("Connecting to Wi-Fi SSID: %s, password: %s, Channel: %d\n", ssid.c_str(), password.c_str(),
wifi_channel);
WiFi.begin(ssid.c_str(), password.c_str(), wifi_channel);
} else {
#ifdef DEBUG_LOG
@ -163,12 +192,8 @@ void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info) {
clear_event(EVENT_WIFI_DISCONNECT);
set_event(EVENT_WIFI_CONNECT, 0);
connected_once = true;
#ifdef DEBUG_LOG
logging.print("Wi-Fi connected. RSSI: ");
logging.print(-WiFi.RSSI());
logging.print(" dBm, IP address: ");
logging.println(WiFi.localIP().toString());
#endif
DEBUG_PRINTF("Wi-Fi connected. RSSI: %d dBm, IP address: %s, SSID: %s\n", -WiFi.RSSI(),
WiFi.localIP().toString().c_str(), WiFi.SSID().c_str());
hasConnectedBefore = true; // Mark as successfully connected at least once
reconnectAttempts = 0; // Reset the attempt counter
current_full_reconnect_interval = INIT_WIFI_FULL_RECONNECT_INTERVAL; // Reset the full reconnect interval
@ -189,8 +214,10 @@ void onWifiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
// Event handler for Wi-Fi disconnection
void onWifiDisconnect(WiFiEvent_t event, WiFiEventInfo_t info) {
if (connected_once)
if (connected_once) {
set_event(EVENT_WIFI_DISCONNECT, 0);
}
#ifdef DEBUG_LOG
logging.println("Wi-Fi disconnected.");
#endif
@ -218,15 +245,13 @@ void init_mDNS() {
}
void init_WiFi_AP() {
#ifdef DEBUG_LOG
logging.println("Creating Access Point: " + String(ssidAP));
logging.println("With password: " + String(passwordAP));
#endif
WiFi.softAP(ssidAP, passwordAP);
ssidAP = std::string("BatteryEmulator") + WiFi.macAddress().c_str();
DEBUG_PRINTF("Creating Access Point: %s\n", ssidAP.c_str());
DEBUG_PRINTF("With password: %s\n", passwordAP.c_str());
WiFi.softAP(ssidAP.c_str(), passwordAP.c_str());
IPAddress IP = WiFi.softAPIP();
#ifdef DEBUG_LOG
logging.println("Access Point created.");
logging.print("IP address: ");
logging.println(IP.toString());
#endif
DEBUG_PRINTF("Access Point created.\nIP address: %s\n", IP.toString().c_str());
}

View file

@ -8,8 +8,8 @@
extern std::string ssid;
extern std::string password;
extern const uint8_t wifi_channel;
extern const char* ssidAP;
extern const char* passwordAP;
extern std::string ssidAP;
extern std::string passwordAP;
void init_WiFi();
void wifi_monitor();