mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-06 03:50:13 +02:00
Merge branch 'main' into feature/advanced-battery-webpage
This commit is contained in:
commit
742f51c658
92 changed files with 5121 additions and 1402 deletions
|
@ -19,6 +19,10 @@ To update the software over the air:
|
|||
- In your webbrowser, go to the url consisting of the IP address, followed by `/update`, for instance `http://192.168.0.224/update`.
|
||||
- In the webbrowser, follow the steps to select the `.bin` file and to upload the file to the board.
|
||||
|
||||
Security Concerns
|
||||
(https://randomnerdtutorials.com/esp32-esp8266-web-server-http-authentication/)
|
||||
Authentication implemented here is meant to be used in your local network to protect from anyone just typing the ESP IP address and accessing the web server (like unauthorized family member or friend).
|
||||
|
||||
## Future work
|
||||
This section lists a number of features that can be implemented as part of the webserver in the future.
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
#include "events_html.h"
|
||||
#include <Arduino.h>
|
||||
#include "../utils/events.h"
|
||||
|
||||
const char EVENTS_HTML_START[] = R"=====(
|
||||
<style>body{background-color:#000;color:#fff}.event-log{display:flex;flex-direction:column}.event{display:flex;flex-wrap:wrap;border:1px solid #fff;padding:10px}.event>div{flex:1;min-width:100px;max-width:90%;word-break:break-word}</style><div style="background-color:#303e47;padding:10px;margin-bottom:10px;border-radius:25px"><div class="event-log"><div class="event" style="background-color:#1e2c33;font-weight:700"><div>Event Type</div><div>Severity</div><div>Last Event</div><div>Count</div><div>Data</div><div>Message</div></div>
|
||||
)=====";
|
||||
const char EVENTS_HTML_END[] = R"=====(
|
||||
</div></div>
|
||||
<button onclick='home()'>Back to main page</button>
|
||||
<style>.event:nth-child(even){background-color:#455a64}.event:nth-child(odd){background-color:#394b52}</style>
|
||||
<script>function showEvent(){document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago");n&&(n.innerText=new Date(new Date().getTime()-1e3*parseInt(n.innerText,10)).toLocaleString())})}function home(){window.location.href="/"}window.onload=function(){showEvent()}</script>
|
||||
<button onclick="askClear()">Clear all events</button>
|
||||
<button onclick="home()">Back to main page</button>
|
||||
<style>.event:nth-child(even){background-color:#455a64}.event:nth-child(odd){background-color:#394b52}</style><script>function showEvent(){document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago");n&&(n.innerText=new Date(Date.now()-(4294967296*+n.innerText.split(";")[0]+ +n.innerText.split(";")[1])).toLocaleString())})}function askClear(){window.confirm("Are you sure you want to clear all events?")&&(window.location.href="/clearevents")}function home(){window.location.href="/"}window.onload=function(){showEvent()}</script>
|
||||
)=====";
|
||||
|
||||
static std::vector<EventData> order_events;
|
||||
|
||||
String events_processor(const String& var) {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
|
@ -20,65 +20,76 @@ String events_processor(const String& var) {
|
|||
content.concat(FPSTR(EVENTS_HTML_START));
|
||||
const EVENTS_STRUCT_TYPE* event_pointer;
|
||||
|
||||
unsigned long timestamp_now = get_current_event_time_secs();
|
||||
|
||||
//clear the vector
|
||||
order_events.clear();
|
||||
// Collect all events
|
||||
for (int i = 0; i < EVENT_NOF_EVENTS; i++) {
|
||||
event_pointer = get_event_pointer((EVENTS_ENUM_TYPE)i);
|
||||
EVENTS_ENUM_TYPE event_handle = static_cast<EVENTS_ENUM_TYPE>(i);
|
||||
if (event_pointer->occurences > 0) {
|
||||
order_events.push_back({static_cast<EVENTS_ENUM_TYPE>(i), event_pointer});
|
||||
}
|
||||
}
|
||||
// Sort events by timestamp
|
||||
std::sort(order_events.begin(), order_events.end(), compareEventsByTimestampDesc);
|
||||
unsigned long timestamp_now = millis();
|
||||
|
||||
// Generate HTML and debug output
|
||||
for (const auto& event : order_events) {
|
||||
EVENTS_ENUM_TYPE event_handle = event.event_handle;
|
||||
event_pointer = event.event_pointer;
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Event: " + String(get_event_enum_string(event_handle)) +
|
||||
" count: " + String(event_pointer->occurences) + " seconds: " + String(event_pointer->timestamp) +
|
||||
" data: " + String(event_pointer->data) +
|
||||
" level: " + String(get_event_level_string(event_handle)));
|
||||
#endif
|
||||
if (event_pointer->occurences > 0) {
|
||||
content.concat("<div class='event'>");
|
||||
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>");
|
||||
content.concat("<div>" + String(get_event_level_string(event_handle)) + "</div>");
|
||||
content.concat("<div class='sec-ago'>" + String(timestamp_now - event_pointer->timestamp) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->occurences) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->data) + "</div>");
|
||||
content.concat("<div>" + String(get_event_message_string(event_handle)) + "</div>");
|
||||
content.concat("</div>"); // End of event row
|
||||
}
|
||||
content.concat("<div class='event'>");
|
||||
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>");
|
||||
content.concat("<div>" + String(get_event_level_string(event_handle)) + "</div>");
|
||||
content.concat("<div class='sec-ago'>" + String(millisrolloverCount) + ";" +
|
||||
String(timestamp_now - event_pointer->timestamp) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->occurences) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->data) + "</div>");
|
||||
content.concat("<div>" + String(get_event_message_string(event_handle)) + "</div>");
|
||||
content.concat("</div>"); // End of event row
|
||||
}
|
||||
|
||||
//clear the vector
|
||||
order_events.clear();
|
||||
content.concat(FPSTR(EVENTS_HTML_END));
|
||||
return content;
|
||||
return String();
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
/* Script for displaying event log before it gets minified
|
||||
<button onclick="askClear()">Clear all events</button>
|
||||
<button onclick="home()">Back to main page</button>
|
||||
<style>
|
||||
.event:nth-child(even) {
|
||||
background-color: #455a64;
|
||||
}
|
||||
.event:nth-child(odd) {
|
||||
background-color: #394b52;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function showEvent() {
|
||||
var eventLogElement = document.querySelector('.event-log');
|
||||
// Get the current time on the client side
|
||||
var currentTime = new Date().getTime() / 1000; // Convert milliseconds to seconds
|
||||
// Loop through the events and update the "Last Event" column
|
||||
var events = document.querySelectorAll('.event');
|
||||
events.forEach(function(event) {
|
||||
var secondsAgoElement = event.querySelector('.sec-ago');
|
||||
var timestampElement = event.querySelector('.timestamp');
|
||||
if (secondsAgoElement && timestampElement) {
|
||||
var secondsAgo = parseInt(secondsAgoElement.innerText, 10);
|
||||
var uptimeTimestamp = parseFloat(timestampElement.innerText); // Parse as float to handle seconds with decimal parts
|
||||
// Calculate the actual system time based on the client-side current time
|
||||
var actualTime = new Date((currentTime - uptimeTimestamp + secondsAgo) * 1000);
|
||||
// Format the date and time
|
||||
var formattedTime = actualTime.toLocaleString();
|
||||
// Update the "Last Event" column with the formatted time
|
||||
secondsAgoElement.innerText = formattedTime;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Call the showEvent function when the page is loaded
|
||||
window.onload = function() {
|
||||
showEvent();
|
||||
};
|
||||
|
||||
function home() {
|
||||
window.location.href = '/';
|
||||
}
|
||||
function showEvent() {
|
||||
document.querySelectorAll(".event").forEach(function (e) {
|
||||
var n = e.querySelector(".sec-ago");
|
||||
n && (n.innerText = new Date(Date.now() - (+n.innerText.split(";")[0] * 4294967296 + +n.innerText.split(";")[1])).toLocaleString());
|
||||
});
|
||||
}
|
||||
function askClear() {
|
||||
if (window.confirm('Are you sure you want to clear all events?')) {
|
||||
window.location.href = '/clearevents';
|
||||
}
|
||||
}
|
||||
function home() {
|
||||
window.location.href = "/";
|
||||
}
|
||||
window.onload = function () {
|
||||
showEvent();
|
||||
};
|
||||
</script>
|
||||
*/
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
#define EVENTS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "../utils/events.h"
|
||||
|
||||
/**
|
||||
* @brief Replaces placeholder with content section in web page
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "webserver.h"
|
||||
#include <Preferences.h>
|
||||
#include "../../datalayer/datalayer.h"
|
||||
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
|
||||
#include "../utils/events.h"
|
||||
#include "../utils/led_handler.h"
|
||||
#include "../utils/timer.h"
|
||||
|
@ -17,48 +18,37 @@ unsigned long ota_progress_millis = 0;
|
|||
#include "index_html.cpp"
|
||||
#include "settings_html.h"
|
||||
|
||||
enum WifiState {
|
||||
INIT, //before connecting first time
|
||||
RECONNECTING, //we've connected before, but lost connection
|
||||
CONNECTED //we are connected
|
||||
};
|
||||
|
||||
WifiState wifi_state = INIT;
|
||||
|
||||
MyTimer ota_timeout_timer = MyTimer(15000);
|
||||
bool ota_active = false;
|
||||
|
||||
unsigned const long WIFI_MONITOR_INTERVAL_TIME = 15000;
|
||||
unsigned const long INIT_WIFI_CONNECT_TIMEOUT = 8000; // Timeout for initial WiFi connect in milliseconds
|
||||
unsigned const long DEFAULT_WIFI_RECONNECT_INTERVAL = 1000; // Default WiFi reconnect interval in ms
|
||||
unsigned const long MAX_WIFI_RETRY_INTERVAL = 90000; // Maximum wifi retry interval in ms
|
||||
unsigned long last_wifi_monitor_time = millis(); //init millis so wifi monitor doesn't run immediately
|
||||
unsigned long wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
|
||||
unsigned long last_wifi_attempt_time = millis(); //init millis so wifi monitor doesn't run immediately
|
||||
const char get_firmware_info_html[] = R"rawliteral(%X%)rawliteral";
|
||||
|
||||
void init_webserver() {
|
||||
// Configure WiFi
|
||||
#ifdef WIFIAP
|
||||
if (AccessPointEnabled) {
|
||||
WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection
|
||||
init_WiFi_AP();
|
||||
} else {
|
||||
WiFi.mode(WIFI_STA); // Only Router connection
|
||||
}
|
||||
#else
|
||||
WiFi.mode(WIFI_STA); // Only Router connection
|
||||
#endif // WIFIAP
|
||||
init_WiFi_STA(ssid.c_str(), password.c_str(), wifi_channel);
|
||||
|
||||
String content = index_html;
|
||||
|
||||
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();
|
||||
request->send_P(200, "application/json", get_firmware_info_html, get_firmware_info_processor);
|
||||
});
|
||||
|
||||
// Route for root / web page
|
||||
server.on("/", HTTP_GET,
|
||||
[](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, processor); });
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
|
||||
return request->requestAuthentication();
|
||||
request->send_P(200, "text/html", index_html, processor);
|
||||
});
|
||||
|
||||
// Route for going to settings web page
|
||||
server.on("/settings", HTTP_GET,
|
||||
[](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, settings_processor); });
|
||||
server.on("/settings", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
|
||||
return request->requestAuthentication();
|
||||
request->send_P(200, "text/html", index_html, settings_processor);
|
||||
});
|
||||
|
||||
// Route for going to advanced battery info web page
|
||||
server.on("/advanced", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
|
@ -67,15 +57,35 @@ void init_webserver() {
|
|||
|
||||
// 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();
|
||||
request->send_P(200, "text/html", index_html, cellmonitor_processor);
|
||||
});
|
||||
|
||||
// Route for going to event log web page
|
||||
server.on("/events", HTTP_GET,
|
||||
[](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, events_processor); });
|
||||
server.on("/events", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
|
||||
return request->requestAuthentication();
|
||||
request->send_P(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();
|
||||
reset_all_events();
|
||||
// Send back a response that includes an instant redirect to /events
|
||||
String response = "<html><body>";
|
||||
response += "<script>window.location.href = '/events';</script>"; // Instant redirect
|
||||
response += "</body></html>";
|
||||
request->send(200, "text/html", response);
|
||||
});
|
||||
|
||||
// Route for editing SSID
|
||||
server.on("/updateSSID", 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();
|
||||
if (value.length() <= 63) { // Check if SSID is within the allowable length
|
||||
|
@ -91,6 +101,8 @@ 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();
|
||||
if (request->hasParam("value")) {
|
||||
String value = request->getParam("value")->value();
|
||||
if (value.length() > 8) { // Check if password is within the allowable length
|
||||
|
@ -107,6 +119,8 @@ 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();
|
||||
if (request->hasParam("value")) {
|
||||
String value = request->getParam("value")->value();
|
||||
datalayer.battery.info.total_capacity_Wh = value.toInt();
|
||||
|
@ -119,6 +133,8 @@ void init_webserver() {
|
|||
|
||||
// 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();
|
||||
|
@ -131,6 +147,8 @@ void init_webserver() {
|
|||
|
||||
// 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();
|
||||
datalayer.battery.settings.max_percentage = static_cast<uint16_t>(value.toFloat() * 100);
|
||||
|
@ -141,8 +159,40 @@ void init_webserver() {
|
|||
}
|
||||
});
|
||||
|
||||
// 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");
|
||||
}
|
||||
});
|
||||
|
||||
// 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") {
|
||||
setBatteryPause(true, true, true);
|
||||
} else {
|
||||
setBatteryPause(false, false, false);
|
||||
}
|
||||
request->send(200, "text/plain", "Updated successfully");
|
||||
} else {
|
||||
request->send(400, "text/plain", "Bad Request");
|
||||
}
|
||||
});
|
||||
|
||||
// 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();
|
||||
datalayer.battery.settings.min_percentage = static_cast<uint16_t>(value.toFloat() * 100);
|
||||
|
@ -155,6 +205,8 @@ void init_webserver() {
|
|||
|
||||
// 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();
|
||||
datalayer.battery.info.max_charge_amp_dA = static_cast<uint16_t>(value.toFloat() * 10);
|
||||
|
@ -167,6 +219,8 @@ void init_webserver() {
|
|||
|
||||
// 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();
|
||||
datalayer.battery.info.max_discharge_amp_dA = static_cast<uint16_t>(value.toFloat() * 10);
|
||||
|
@ -180,6 +234,8 @@ void init_webserver() {
|
|||
#ifdef TEST_FAKE_BATTERY
|
||||
// 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");
|
||||
}
|
||||
|
@ -196,6 +252,8 @@ void init_webserver() {
|
|||
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_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");
|
||||
}
|
||||
|
@ -218,6 +276,8 @@ void init_webserver() {
|
|||
|
||||
// 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");
|
||||
}
|
||||
|
@ -240,6 +300,8 @@ void init_webserver() {
|
|||
|
||||
// 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();
|
||||
charger_setpoint_HV_IDC_END = value.toFloat();
|
||||
|
@ -251,6 +313,8 @@ void init_webserver() {
|
|||
|
||||
// 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();
|
||||
charger_HV_enabled = (bool)value.toInt();
|
||||
|
@ -262,6 +326,8 @@ void init_webserver() {
|
|||
|
||||
// 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();
|
||||
charger_aux12V_enabled = (bool)value.toInt();
|
||||
|
@ -273,13 +339,21 @@ void init_webserver() {
|
|||
#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
|
||||
|
||||
// Send a GET request to <ESP_IP>/update
|
||||
server.on("/debug", HTTP_GET,
|
||||
[](AsyncWebServerRequest* request) { request->send(200, "text/plain", "Debug: all OK."); });
|
||||
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.");
|
||||
});
|
||||
|
||||
// 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();
|
||||
request->send(200, "text/plain", "Rebooting server...");
|
||||
//TODO: Should we handle contactors gracefully? Ifdef CONTACTOR_CONTROL then what?
|
||||
|
||||
//Equipment STOP without persisting the equipment state before restart
|
||||
// Max Charge/Discharge = 0; CAN = stop; contactors = open
|
||||
setBatteryPause(true, true, true, false);
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
});
|
||||
|
@ -289,29 +363,8 @@ void init_webserver() {
|
|||
|
||||
// Start server
|
||||
server.begin();
|
||||
|
||||
#ifdef MQTT
|
||||
// Init MQTT
|
||||
init_mqtt();
|
||||
#endif // MQTT
|
||||
}
|
||||
|
||||
#ifdef WIFIAP
|
||||
void init_WiFi_AP() {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Creating Access Point: " + String(ssidAP));
|
||||
Serial.println("With password: " + String(passwordAP));
|
||||
#endif // DEBUG_VIA_USB
|
||||
WiFi.softAP(ssidAP, passwordAP);
|
||||
IPAddress IP = WiFi.softAPIP();
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Access Point created.");
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(IP);
|
||||
#endif // DEBUG_VIA_USB
|
||||
}
|
||||
#endif // WIFIAP
|
||||
|
||||
String getConnectResultString(wl_status_t status) {
|
||||
switch (status) {
|
||||
case WL_CONNECTED:
|
||||
|
@ -335,62 +388,11 @@ String getConnectResultString(wl_status_t status) {
|
|||
}
|
||||
}
|
||||
|
||||
void wifi_monitor() {
|
||||
unsigned long currentMillis = millis();
|
||||
if (currentMillis - last_wifi_monitor_time > WIFI_MONITOR_INTERVAL_TIME) {
|
||||
last_wifi_monitor_time = currentMillis;
|
||||
wl_status_t status = WiFi.status();
|
||||
if (status != WL_CONNECTED && status != WL_IDLE_STATUS) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println(getConnectResultString(status));
|
||||
#endif // DEBUG_VIA_USB
|
||||
if (wifi_state == INIT) { //we haven't been connected yet, try the init logic
|
||||
init_WiFi_STA(ssid.c_str(), password.c_str(), wifi_channel);
|
||||
} else { //we were connected before, try the reconnect logic
|
||||
if (currentMillis - last_wifi_attempt_time > wifi_reconnect_interval) {
|
||||
last_wifi_attempt_time = currentMillis;
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("WiFi not connected, trying to reconnect...");
|
||||
#endif // DEBUG_VIA_USB
|
||||
wifi_state = RECONNECTING;
|
||||
WiFi.reconnect();
|
||||
wifi_reconnect_interval = min(wifi_reconnect_interval * 2, MAX_WIFI_RETRY_INTERVAL);
|
||||
}
|
||||
}
|
||||
} else if (status == WL_CONNECTED && wifi_state != CONNECTED) {
|
||||
wifi_state = CONNECTED;
|
||||
wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
|
||||
// Print local IP address and start web server
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("Connected to WiFi network: " + String(ssid.c_str()));
|
||||
Serial.print(" IP address: " + WiFi.localIP().toString());
|
||||
Serial.print(" Signal Strength: " + String(WiFi.RSSI()) + " dBm");
|
||||
Serial.println(" Channel: " + String(WiFi.channel()));
|
||||
Serial.println(" Hostname: " + String(WiFi.getHostname()));
|
||||
#endif // DEBUG_VIA_USB
|
||||
}
|
||||
}
|
||||
|
||||
void ota_monitor() {
|
||||
if (ota_active && ota_timeout_timer.elapsed()) {
|
||||
// OTA timeout, try to restore can and clear the update event
|
||||
ESP32Can.CANInit();
|
||||
clear_event(EVENT_OTA_UPDATE);
|
||||
set_event(EVENT_OTA_UPDATE_TIMEOUT, 0);
|
||||
ota_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void init_WiFi_STA(const char* ssid, const char* password, const uint8_t wifi_channel) {
|
||||
// Connect to Wi-Fi network with SSID and password
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.print("Connecting to ");
|
||||
Serial.println(ssid);
|
||||
#endif // DEBUG_VIA_USB
|
||||
WiFi.begin(ssid, password, wifi_channel);
|
||||
WiFi.setAutoReconnect(true); // Enable auto reconnect
|
||||
wl_status_t result = static_cast<wl_status_t>(WiFi.waitForConnectResult(INIT_WIFI_CONNECT_TIMEOUT));
|
||||
if (result) {
|
||||
//TODO: Add event or serial print?
|
||||
onOTAEnd(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -403,6 +405,24 @@ void init_ElegantOTA() {
|
|||
ElegantOTA.onEnd(onOTAEnd);
|
||||
}
|
||||
|
||||
String get_firmware_info_processor(const String& var) {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
static JsonDocument doc;
|
||||
#ifdef HW_LILYGO
|
||||
doc["hardware"] = "LilyGo T-CAN485";
|
||||
#endif // HW_LILYGO
|
||||
#ifdef HW_STARK
|
||||
doc["hardware"] = "Stark CMR Module";
|
||||
#endif // HW_STARK
|
||||
|
||||
doc["firmware"] = String(version_number);
|
||||
serializeJson(doc, content);
|
||||
return content;
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
String processor(const String& var) {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
|
@ -477,9 +497,9 @@ String processor(const String& var) {
|
|||
#ifdef BYD_MODBUS
|
||||
content += "BYD 11kWh HVM battery over Modbus RTU";
|
||||
#endif // BYD_MODBUS
|
||||
#ifdef LUNA2000_MODBUS
|
||||
content += "Luna2000 battery over Modbus RTU";
|
||||
#endif // LUNA2000_MODBUS
|
||||
#ifdef FOXESS_CAN
|
||||
content += "FoxESS compatible HV2600/ECS4100 battery";
|
||||
#endif // FOXESS_CAN
|
||||
#ifdef PYLON_CAN
|
||||
content += "Pylontech battery over CAN bus";
|
||||
#endif // PYLON_CAN
|
||||
|
@ -528,6 +548,12 @@ String processor(const String& var) {
|
|||
#ifdef NISSAN_LEAF_BATTERY
|
||||
content += "Nissan LEAF";
|
||||
#endif // NISSAN_LEAF_BATTERY
|
||||
#ifdef PYLON_BATTERY
|
||||
content += "Pylon compatible battery";
|
||||
#endif // PYLON_BATTERY
|
||||
#ifdef RJXZS_BMS
|
||||
content += "RJXZS BMS, DIY battery";
|
||||
#endif // RJXZS_BMS
|
||||
#ifdef RENAULT_KANGOO_BATTERY
|
||||
content += "Renault Kangoo";
|
||||
#endif // RENAULT_KANGOO_BATTERY
|
||||
|
@ -543,9 +569,12 @@ String processor(const String& var) {
|
|||
#ifdef SERIAL_LINK_RECEIVER
|
||||
content += "Serial link to another LilyGo board";
|
||||
#endif // SERIAL_LINK_RECEIVER
|
||||
#ifdef TESLA_MODEL_3_BATTERY
|
||||
content += "Tesla Model S/3/X/Y";
|
||||
#endif // TESLA_MODEL_3_BATTERY
|
||||
#ifdef TESLA_MODEL_SX_BATTERY
|
||||
content += "Tesla Model S/X";
|
||||
#endif // TESLA_MODEL_SX_BATTERY
|
||||
#ifdef TESLA_MODEL_3Y_BATTERY
|
||||
content += "Tesla Model 3/Y";
|
||||
#endif // TESLA_MODEL_3Y_BATTERY
|
||||
#ifdef VOLVO_SPA_BATTERY
|
||||
content += "Volvo / Polestar 78kWh battery";
|
||||
#endif // VOLVO_SPA_BATTERY
|
||||
|
@ -554,6 +583,9 @@ String processor(const String& var) {
|
|||
#endif // TEST_FAKE_BATTERY
|
||||
#ifdef DOUBLE_BATTERY
|
||||
content += " (Double battery)";
|
||||
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
|
||||
content += " (LFP)";
|
||||
}
|
||||
#endif // DOUBLE_BATTERY
|
||||
content += "</h4>";
|
||||
|
||||
|
@ -616,6 +648,8 @@ String processor(const String& var) {
|
|||
float powerFloat = static_cast<float>(datalayer.battery.status.active_power_W); // Convert to float
|
||||
float tempMaxFloat = static_cast<float>(datalayer.battery.status.temperature_max_dC) / 10.0; // Convert to float
|
||||
float tempMinFloat = static_cast<float>(datalayer.battery.status.temperature_min_dC) / 10.0; // Convert to float
|
||||
uint16_t cell_delta_mv =
|
||||
datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV;
|
||||
|
||||
content += "<h4 style='color: white;'>Real SOC: " + String(socRealFloat, 2) + "</h4>";
|
||||
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) + "</h4>";
|
||||
|
@ -625,10 +659,22 @@ String processor(const String& var) {
|
|||
content += formatPowerValue("Power", powerFloat, "", 1);
|
||||
content += formatPowerValue("Total capacity", datalayer.battery.info.total_capacity_Wh, "h", 0);
|
||||
content += formatPowerValue("Remaining capacity", datalayer.battery.status.remaining_capacity_Wh, "h", 1);
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
|
||||
|
||||
if (emulator_pause_status == NORMAL) {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
|
||||
} else {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red");
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1, "red");
|
||||
}
|
||||
|
||||
content += "<h4>Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";
|
||||
content += "<h4>Cell min: " + String(datalayer.battery.status.cell_min_voltage_mV) + " mV</h4>";
|
||||
if (cell_delta_mv > datalayer.battery.info.max_cell_voltage_deviation_mV) {
|
||||
content += "<h4 style='color: red;'>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
|
||||
} else {
|
||||
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
|
||||
}
|
||||
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
|
||||
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
|
@ -660,6 +706,20 @@ String processor(const String& var) {
|
|||
} else {
|
||||
content += "<span style='color: red;'>✕</span></h4>";
|
||||
}
|
||||
if (emulator_pause_status == NORMAL)
|
||||
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
else
|
||||
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
|
||||
#ifdef CONTACTOR_CONTROL
|
||||
content += "<h4>Contactors controlled by Battery-Emulator: ";
|
||||
if (datalayer.system.status.contactor_control_closed) {
|
||||
content += "<span style='color: green;'>ON</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>OFF</span>";
|
||||
}
|
||||
content += "</h4>";
|
||||
#endif
|
||||
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
|
@ -692,6 +752,7 @@ String processor(const String& var) {
|
|||
powerFloat = static_cast<float>(datalayer.battery2.status.active_power_W); // Convert to float
|
||||
tempMaxFloat = static_cast<float>(datalayer.battery2.status.temperature_max_dC) / 10.0; // Convert to float
|
||||
tempMinFloat = static_cast<float>(datalayer.battery2.status.temperature_min_dC) / 10.0; // Convert to float
|
||||
cell_delta_mv = datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV;
|
||||
|
||||
content += "<h4 style='color: white;'>Real SOC: " + String(socRealFloat, 2) + "</h4>";
|
||||
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) + "</h4>";
|
||||
|
@ -705,6 +766,11 @@ String processor(const String& var) {
|
|||
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1);
|
||||
content += "<h4>Cell max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV</h4>";
|
||||
content += "<h4>Cell min: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV</h4>";
|
||||
if (cell_delta_mv > datalayer.battery2.info.max_cell_voltage_deviation_mV) {
|
||||
content += "<h4 style='color: red;'>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
|
||||
} else {
|
||||
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
|
||||
}
|
||||
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
|
||||
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
|
@ -737,6 +803,21 @@ String processor(const String& var) {
|
|||
content += "<span style='color: red;'>✕</span></h4>";
|
||||
}
|
||||
|
||||
#ifdef CONTACTOR_CONTROL
|
||||
content += "<h4>Contactors controlled by Battery-Emulator: ";
|
||||
if (datalayer.system.status.contactor_control_closed) {
|
||||
content += "<span style='color: green;'>ON</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>OFF</span>";
|
||||
}
|
||||
content += "</h4>";
|
||||
#endif
|
||||
|
||||
if (emulator_pause_status == NORMAL)
|
||||
content += "<h4>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
else
|
||||
content += "<h4 style='color: red;'>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
|
||||
content += "</div>";
|
||||
content += "</div>";
|
||||
#endif // DOUBLE_BATTERY
|
||||
|
@ -797,6 +878,39 @@ String processor(const String& var) {
|
|||
content += "</div>";
|
||||
#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
|
||||
|
||||
if (emulator_pause_request_ON)
|
||||
content += "<button onclick='PauseBattery(false)'>Resume charge/discharge</button>";
|
||||
else
|
||||
content +=
|
||||
"<button onclick=\"if(confirm('Are you sure you want to pause charging and discharging? This will set the "
|
||||
"maximum charge and discharge values to zero, preventing any further power flow.')) { PauseBattery(true); "
|
||||
"}\">Pause charge/discharge</button>";
|
||||
content += " ";
|
||||
content += "<button onclick='OTA()'>Perform OTA update</button>";
|
||||
content += " ";
|
||||
content += "<button onclick='Settings()'>Change Settings</button>";
|
||||
content += " ";
|
||||
content += "<button onclick='Cellmon()'>Cellmonitor</button>";
|
||||
content += " ";
|
||||
content += "<button onclick='Events()'>Events</button>";
|
||||
content += " ";
|
||||
content += "<button onclick='askReboot()'>Reboot Emulator</button>";
|
||||
if (WEBSERVER_AUTH_REQUIRED)
|
||||
content += "<button onclick='logout()'>Logout</button>";
|
||||
if (!datalayer.system.settings.equipment_stop_active)
|
||||
content +=
|
||||
"<br/><br/><button style=\"background:red;color:white;cursor:pointer;\""
|
||||
" onclick=\""
|
||||
"if(confirm('This action will open contactors on the battery and stop all CAN communications. Are you "
|
||||
"sure?')) { estop(true); }\""
|
||||
">Open Contactors</button><br/>";
|
||||
else
|
||||
content +=
|
||||
"<br/><br/><button style=\"background:green;color:white;cursor:pointer;\""
|
||||
"20px;font-size:16px;font-weight:bold;cursor:pointer;border-radius:5px; margin:10px;"
|
||||
" onclick=\""
|
||||
"if(confirm('This action will restore the battery state. Are you sure?')) { estop(false); }\""
|
||||
">Close Contactors</button><br/>";
|
||||
content += "<script>";
|
||||
content += "function OTA() { window.location.href = '/update'; }";
|
||||
content += "function Cellmon() { window.location.href = '/cellmonitor'; }";
|
||||
|
@ -812,6 +926,26 @@ String processor(const String& var) {
|
|||
content += " xhr.open('GET', '/reboot', true);";
|
||||
content += " xhr.send();";
|
||||
content += "}";
|
||||
if (WEBSERVER_AUTH_REQUIRED) {
|
||||
content += "function logout() {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.open('GET', '/logout', true);";
|
||||
content += " xhr.send();";
|
||||
content += " setTimeout(function(){ window.open(\"/\",\"_self\"); }, 1000);";
|
||||
content += "}";
|
||||
}
|
||||
content += "function PauseBattery(pause){";
|
||||
content +=
|
||||
"var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=function() { "
|
||||
"window.location.reload();};xhr.open('GET','/pause?p='+pause,true);xhr.send();";
|
||||
content += "}";
|
||||
content += "function estop(stop){";
|
||||
content +=
|
||||
"var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=function() { "
|
||||
"window.location.reload();};xhr.open('GET','/equipmentStop?stop='+stop,true);xhr.send();";
|
||||
content += "}";
|
||||
content += "</script>";
|
||||
|
||||
//Script for refreshing page
|
||||
|
@ -825,13 +959,16 @@ String processor(const String& var) {
|
|||
}
|
||||
|
||||
void onOTAStart() {
|
||||
//try to Pause the battery
|
||||
setBatteryPause(true, true);
|
||||
|
||||
// Log when OTA has started
|
||||
ESP32Can.CANStop();
|
||||
set_event(EVENT_OTA_UPDATE, 0);
|
||||
|
||||
// If already set, make a new attempt
|
||||
clear_event(EVENT_OTA_UPDATE_TIMEOUT);
|
||||
ota_active = true;
|
||||
|
||||
ota_timeout_timer.reset();
|
||||
}
|
||||
|
||||
|
@ -848,8 +985,16 @@ void onOTAProgress(size_t current, size_t final) {
|
|||
}
|
||||
|
||||
void onOTAEnd(bool success) {
|
||||
|
||||
ota_active = false;
|
||||
clear_event(EVENT_OTA_UPDATE);
|
||||
|
||||
// Log when OTA has finished
|
||||
if (success) {
|
||||
//Equipment STOP without persisting the equipment state before restart
|
||||
// Max Charge/Discharge = 0; CAN = stop; contactors = open
|
||||
setBatteryPause(true, true, true, false);
|
||||
// a reboot will be done by the OTA library. no need to do anything here
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("OTA update finished successfully!");
|
||||
#endif // DEBUG_VIA_USB
|
||||
|
@ -857,17 +1002,14 @@ void onOTAEnd(bool success) {
|
|||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("There was an error during OTA update!");
|
||||
#endif // DEBUG_VIA_USB
|
||||
|
||||
// If we fail without a timeout, try to restore CAN
|
||||
ESP32Can.CANInit();
|
||||
//try to Resume the battery pause and CAN communication
|
||||
setBatteryPause(false, false);
|
||||
}
|
||||
ota_active = false;
|
||||
clear_event(EVENT_OTA_UPDATE);
|
||||
}
|
||||
|
||||
template <typename T> // This function makes power values appear as W when under 1000, and kW when over
|
||||
String formatPowerValue(String label, T value, String unit, int precision) {
|
||||
String result = "<h4 style='color: white;'>" + label + ": ";
|
||||
String formatPowerValue(String label, T value, String unit, int precision, String color) {
|
||||
String result = "<h4 style='color: " + color + ";'>" + label + ": ";
|
||||
|
||||
if (std::is_same<T, float>::value || std::is_same<T, uint16_t>::value || std::is_same<T, uint32_t>::value) {
|
||||
float convertedValue = static_cast<float>(value);
|
||||
|
|
|
@ -6,24 +6,17 @@
|
|||
#include "../../include.h"
|
||||
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h"
|
||||
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h"
|
||||
#ifdef MQTT
|
||||
#include "../../lib/knolleary-pubsubclient/PubSubClient.h"
|
||||
#endif
|
||||
#include "../../lib/me-no-dev-AsyncTCP/src/AsyncTCP.h"
|
||||
#include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
|
||||
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#ifdef MQTT
|
||||
#include "../mqtt/mqtt.h"
|
||||
#endif
|
||||
|
||||
extern const char* version_number; // The current software version, shown on webserver
|
||||
|
||||
#include <string>
|
||||
extern std::string ssid;
|
||||
extern std::string password;
|
||||
extern const uint8_t wifi_channel;
|
||||
extern const char* http_username;
|
||||
extern const char* http_password;
|
||||
|
||||
extern const char* ssidAP;
|
||||
extern const char* passwordAP;
|
||||
|
||||
// Common charger parameters
|
||||
extern float charger_stat_HVcur;
|
||||
|
@ -45,36 +38,6 @@ extern uint16_t OBC_Charge_Power;
|
|||
*/
|
||||
void init_webserver();
|
||||
|
||||
/**
|
||||
* @brief Monitoring loop for WiFi. Will attempt to reconnect to access point if the connection goes down.
|
||||
*
|
||||
* @param[in] void
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void wifi_monitor();
|
||||
|
||||
#ifdef WIFIAP
|
||||
/**
|
||||
* @brief Initialization function that creates a WiFi Access Point.
|
||||
*
|
||||
* @param[in] void
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void init_WiFi_AP();
|
||||
#endif // WIFIAP
|
||||
|
||||
/**
|
||||
* @brief Initialization function that connects to an existing network.
|
||||
*
|
||||
* @param[in] ssid WiFi network name
|
||||
* @param[in] password WiFi network password
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void init_WiFi_STA(const char* ssid, const char* password, const uint8_t channel);
|
||||
|
||||
// /**
|
||||
// * @brief Function to handle WiFi reconnection.
|
||||
// *
|
||||
|
@ -101,6 +64,7 @@ void init_ElegantOTA();
|
|||
* @return String
|
||||
*/
|
||||
String processor(const String& var);
|
||||
String get_firmware_info_processor(const String& var);
|
||||
|
||||
/**
|
||||
* @brief Executes on OTA start
|
||||
|
@ -138,8 +102,10 @@ void onOTAEnd(bool success);
|
|||
* @return string: values
|
||||
*/
|
||||
template <typename T>
|
||||
String formatPowerValue(String label, T value, String unit, int precision);
|
||||
String formatPowerValue(String label, T value, String unit, int precision, String color = "white");
|
||||
|
||||
extern void storeSettings();
|
||||
|
||||
void ota_monitor();
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue