mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 02:09:30 +02:00
Merge branch 'main' into feature/foxess-inverter
This commit is contained in:
commit
1d786463a8
12 changed files with 128 additions and 70 deletions
|
@ -50,7 +50,7 @@
|
||||||
|
|
||||||
Preferences settings; // Store user settings
|
Preferences settings; // Store user settings
|
||||||
// The current software version, shown on webserver
|
// The current software version, shown on webserver
|
||||||
const char* version_number = "7.4.dev";
|
const char* version_number = "7.4.0";
|
||||||
|
|
||||||
// Interval settings
|
// Interval settings
|
||||||
uint16_t intervalUpdateValues = INTERVAL_5_S; // Interval at which to update inverter values / Modbus registers
|
uint16_t intervalUpdateValues = INTERVAL_5_S; // Interval at which to update inverter values / Modbus registers
|
||||||
|
@ -547,8 +547,11 @@ void init_battery() {
|
||||||
#ifdef CAN_FD
|
#ifdef CAN_FD
|
||||||
// Functions
|
// Functions
|
||||||
#ifdef DEBUG_CANFD_DATA
|
#ifdef DEBUG_CANFD_DATA
|
||||||
void print_canfd_frame(CANFDMessage rx_frame) {
|
enum frameDirection { MSG_RX, MSG_TX };
|
||||||
|
void print_canfd_frame(CANFDMessage rx_frame, frameDirection msgDir); // Needs to be declared before it is defined
|
||||||
|
void print_canfd_frame(CANFDMessage rx_frame, frameDirection msgDir) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
(msgDir == 0) ? Serial.print("RX ") : Serial.print("TX ");
|
||||||
Serial.print(rx_frame.id, HEX);
|
Serial.print(rx_frame.id, HEX);
|
||||||
Serial.print(" ");
|
Serial.print(" ");
|
||||||
for (i = 0; i < rx_frame.len; i++) {
|
for (i = 0; i < rx_frame.len; i++) {
|
||||||
|
@ -564,7 +567,7 @@ void receive_canfd() { // This section checks if we have a complete CAN-FD mess
|
||||||
if (canfd.available()) {
|
if (canfd.available()) {
|
||||||
canfd.receive(frame);
|
canfd.receive(frame);
|
||||||
#ifdef DEBUG_CANFD_DATA
|
#ifdef DEBUG_CANFD_DATA
|
||||||
print_canfd_frame(frame);
|
print_canfd_frame(frame, frameDirection(MSG_RX));
|
||||||
#endif
|
#endif
|
||||||
CAN_frame rx_frame;
|
CAN_frame rx_frame;
|
||||||
rx_frame.ID = frame.id;
|
rx_frame.ID = frame.id;
|
||||||
|
@ -599,8 +602,10 @@ void receive_can_native() { // This section checks if we have a complete CAN me
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_can() {
|
void send_can() {
|
||||||
|
if (!allowed_to_send_CAN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (can_send_CAN)
|
|
||||||
send_can_battery();
|
send_can_battery();
|
||||||
|
|
||||||
#ifdef CAN_INVERTER_SELECTED
|
#ifdef CAN_INVERTER_SELECTED
|
||||||
|
@ -608,7 +613,6 @@ void send_can() {
|
||||||
#endif // CAN_INVERTER_SELECTED
|
#endif // CAN_INVERTER_SELECTED
|
||||||
|
|
||||||
#ifdef CHARGER_SELECTED
|
#ifdef CHARGER_SELECTED
|
||||||
if (can_send_CAN)
|
|
||||||
send_can_charger();
|
send_can_charger();
|
||||||
#endif // CHARGER_SELECTED
|
#endif // CHARGER_SELECTED
|
||||||
}
|
}
|
||||||
|
@ -932,7 +936,12 @@ void check_reset_reason() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void transmit_can(CAN_frame* tx_frame, int interface) {
|
void transmit_can(CAN_frame* tx_frame, int interface) {
|
||||||
|
if (!allowed_to_send_CAN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (interface) {
|
switch (interface) {
|
||||||
case CAN_NATIVE:
|
case CAN_NATIVE:
|
||||||
CAN_frame_t frame;
|
CAN_frame_t frame;
|
||||||
|
@ -974,6 +983,10 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
|
||||||
send_ok = canfd.tryToSend(MCP2518Frame);
|
send_ok = canfd.tryToSend(MCP2518Frame);
|
||||||
if (!send_ok) {
|
if (!send_ok) {
|
||||||
set_event(EVENT_CANFD_BUFFER_FULL, interface);
|
set_event(EVENT_CANFD_BUFFER_FULL, interface);
|
||||||
|
} else {
|
||||||
|
#ifdef DEBUG_CANFD_DATA
|
||||||
|
print_canfd_frame(MCP2518Frame, frameDirection(MSG_TX));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#else // Interface not compiled, and settings try to use it
|
#else // Interface not compiled, and settings try to use it
|
||||||
set_event(EVENT_INTERFACE_MISSING, interface);
|
set_event(EVENT_INTERFACE_MISSING, interface);
|
||||||
|
|
|
@ -89,12 +89,12 @@ void update_values_battery() { //This function maps all the values fetched via
|
||||||
for (int i = 0; i < 88; ++i) {
|
for (int i = 0; i < 88; ++i) {
|
||||||
datalayer.battery.status.cell_voltages_mV[i] = (uint16_t)(cell_voltages[i] * 1000);
|
datalayer.battery.status.cell_voltages_mV[i] = (uint16_t)(cell_voltages[i] * 1000);
|
||||||
}
|
}
|
||||||
|
datalayer.battery.info.number_of_cells = 88;
|
||||||
if (max_volt_cel > 2200) { // Only update cellvoltage when we have a value
|
if (max_volt_cel > 2.2) { // Only update cellvoltage when we have a value
|
||||||
datalayer.battery.status.cell_max_voltage_mV = (uint16_t)(max_volt_cel * 1000);
|
datalayer.battery.status.cell_max_voltage_mV = (uint16_t)(max_volt_cel * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (min_volt_cel > 2200) { // Only update cellvoltage when we have a value
|
if (min_volt_cel > 2.2) { // Only update cellvoltage when we have a value
|
||||||
datalayer.battery.status.cell_min_voltage_mV = (uint16_t)(min_volt_cel * 1000);
|
datalayer.battery.status.cell_min_voltage_mV = (uint16_t)(min_volt_cel * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.h"
|
||||||
#include "PYLON-BATTERY.h"
|
#include "PYLON-BATTERY.h"
|
||||||
|
|
||||||
|
/* Change the following to suit your battery */
|
||||||
|
#define MAX_PACK_VOLTAGE 5000 //5000 = 500.0V
|
||||||
|
#define MIN_PACK_VOLTAGE 1500
|
||||||
|
|
||||||
/* Do not change code below unless you are sure what you are doing */
|
/* Do not change code below unless you are sure what you are doing */
|
||||||
static unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
|
static unsigned long previousMillis1000 = 0; // will store last time a 1s CAN Message was sent
|
||||||
|
|
||||||
|
@ -179,8 +183,8 @@ void setup_battery(void) { // Performs one time setup at startup
|
||||||
Serial.println("Pylon battery selected");
|
Serial.println("Pylon battery selected");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
datalayer.battery.info.max_design_voltage_dV = 4040; // 404.0V, charging over this is not possible
|
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE;
|
||||||
datalayer.battery.info.min_design_voltage_dV = 3100; // 310.0V, under this, discharging further is disabled
|
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -66,8 +66,6 @@ SensorConfig sensorConfigs[] = {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::vector<EventData> order_events;
|
|
||||||
|
|
||||||
static String generateCommonInfoAutoConfigTopic(const char* object_id, const char* hostname) {
|
static String generateCommonInfoAutoConfigTopic(const char* object_id, const char* hostname) {
|
||||||
return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/" + String(object_id) + "/config";
|
return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/" + String(object_id) + "/config";
|
||||||
}
|
}
|
||||||
|
@ -83,6 +81,8 @@ static String generateEventsAutoConfigTopic(const char* object_id, const char* h
|
||||||
|
|
||||||
#endif // HA_AUTODISCOVERY
|
#endif // HA_AUTODISCOVERY
|
||||||
|
|
||||||
|
static std::vector<EventData> order_events;
|
||||||
|
|
||||||
static void publish_common_info(void) {
|
static void publish_common_info(void) {
|
||||||
static JsonDocument doc;
|
static JsonDocument doc;
|
||||||
#ifdef HA_AUTODISCOVERY
|
#ifdef HA_AUTODISCOVERY
|
||||||
|
@ -125,7 +125,7 @@ static void publish_common_info(void) {
|
||||||
doc["pause_status"] = get_emulator_pause_status();
|
doc["pause_status"] = get_emulator_pause_status();
|
||||||
|
|
||||||
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
|
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
|
||||||
if (datalayer.battery.status.bms_status == ACTIVE && can_send_CAN && millis() > BOOTUP_TIME) {
|
if (datalayer.battery.status.bms_status == ACTIVE && allowed_to_send_CAN && millis() > BOOTUP_TIME) {
|
||||||
doc["SOC"] = ((float)datalayer.battery.status.reported_soc) / 100.0;
|
doc["SOC"] = ((float)datalayer.battery.status.reported_soc) / 100.0;
|
||||||
doc["SOC_real"] = ((float)datalayer.battery.status.real_soc) / 100.0;
|
doc["SOC_real"] = ((float)datalayer.battery.status.real_soc) / 100.0;
|
||||||
doc["state_of_health"] = ((float)datalayer.battery.status.soh_pptt) / 100.0;
|
doc["state_of_health"] = ((float)datalayer.battery.status.soh_pptt) / 100.0;
|
||||||
|
|
|
@ -12,7 +12,7 @@ static bool battery_empty_event_fired = false;
|
||||||
//battery pause status begin
|
//battery pause status begin
|
||||||
bool emulator_pause_request_ON = false;
|
bool emulator_pause_request_ON = false;
|
||||||
bool emulator_pause_CAN_send_ON = false;
|
bool emulator_pause_CAN_send_ON = false;
|
||||||
bool can_send_CAN = true;
|
bool allowed_to_send_CAN = true;
|
||||||
|
|
||||||
battery_pause_status emulator_pause_status = NORMAL;
|
battery_pause_status emulator_pause_status = NORMAL;
|
||||||
//battery pause status end
|
//battery pause status end
|
||||||
|
@ -88,6 +88,7 @@ void update_machineryprotection() {
|
||||||
clear_event(EVENT_SOH_LOW);
|
clear_event(EVENT_SOH_LOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef PYLON_BATTERY
|
||||||
// Check if SOC% is plausible
|
// Check if SOC% is plausible
|
||||||
if (datalayer.battery.status.voltage_dV >
|
if (datalayer.battery.status.voltage_dV >
|
||||||
(datalayer.battery.info.max_design_voltage_dV -
|
(datalayer.battery.info.max_design_voltage_dV -
|
||||||
|
@ -98,6 +99,7 @@ void update_machineryprotection() {
|
||||||
clear_event(EVENT_SOC_PLAUSIBILITY_ERROR);
|
clear_event(EVENT_SOC_PLAUSIBILITY_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Check diff between highest and lowest cell
|
// Check diff between highest and lowest cell
|
||||||
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
|
||||||
|
@ -218,14 +220,18 @@ void setBatteryPause(bool pause_battery, bool pause_CAN) {
|
||||||
emulator_pause_status = RESUMING;
|
emulator_pause_status = RESUMING;
|
||||||
clear_event(EVENT_PAUSE_END);
|
clear_event(EVENT_PAUSE_END);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//immediate check if we can send CAN messages
|
||||||
|
emulator_pause_state_send_CAN_battery();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief handle emulator pause status
|
/// @brief handle emulator pause status
|
||||||
/// @return true if CAN messages should be sent to battery, false if not
|
/// @return true if CAN messages should be sent to battery, false if not
|
||||||
void emulator_pause_state_send_CAN_battery() {
|
void emulator_pause_state_send_CAN_battery() {
|
||||||
|
|
||||||
if (emulator_pause_status == NORMAL)
|
if (emulator_pause_status == NORMAL) {
|
||||||
can_send_CAN = true;
|
allowed_to_send_CAN = true;
|
||||||
|
}
|
||||||
|
|
||||||
// in some inverters this values are not accurate, so we need to check if we are consider 1.8 amps as the limit
|
// in some inverters this values are not accurate, so we need to check if we are consider 1.8 amps as the limit
|
||||||
if (emulator_pause_request_ON && emulator_pause_status == PAUSING && datalayer.battery.status.current_dA < 18 &&
|
if (emulator_pause_request_ON && emulator_pause_status == PAUSING && datalayer.battery.status.current_dA < 18 &&
|
||||||
|
@ -235,10 +241,10 @@ void emulator_pause_state_send_CAN_battery() {
|
||||||
|
|
||||||
if (!emulator_pause_request_ON && emulator_pause_status == RESUMING) {
|
if (!emulator_pause_request_ON && emulator_pause_status == RESUMING) {
|
||||||
emulator_pause_status = NORMAL;
|
emulator_pause_status = NORMAL;
|
||||||
can_send_CAN = true;
|
allowed_to_send_CAN = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
can_send_CAN = (!emulator_pause_CAN_send_ON || emulator_pause_status == NORMAL);
|
allowed_to_send_CAN = (!emulator_pause_CAN_send_ON || emulator_pause_status == NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_emulator_pause_status() {
|
std::string get_emulator_pause_status() {
|
||||||
|
|
|
@ -12,7 +12,7 @@ enum battery_pause_status { NORMAL = 0, PAUSING = 1, PAUSED = 2, RESUMING = 3 };
|
||||||
extern bool emulator_pause_request_ON;
|
extern bool emulator_pause_request_ON;
|
||||||
extern bool emulator_pause_CAN_send_ON;
|
extern bool emulator_pause_CAN_send_ON;
|
||||||
extern battery_pause_status emulator_pause_status;
|
extern battery_pause_status emulator_pause_status;
|
||||||
extern bool can_send_CAN;
|
extern bool allowed_to_send_CAN;
|
||||||
//battery pause status end
|
//battery pause status end
|
||||||
|
|
||||||
void update_machineryprotection();
|
void update_machineryprotection();
|
||||||
|
|
|
@ -237,6 +237,21 @@ void clear_event(EVENTS_ENUM_TYPE event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset_all_events() {
|
||||||
|
events.nof_logged_events = 0;
|
||||||
|
for (uint16_t i = 0; i < EVENT_NOF_EVENTS; i++) {
|
||||||
|
events.entries[i].data = 0;
|
||||||
|
events.entries[i].state = EVENT_STATE_INACTIVE;
|
||||||
|
events.entries[i].timestamp = 0;
|
||||||
|
events.entries[i].millisrolloverCount = 0;
|
||||||
|
events.entries[i].occurences = 0;
|
||||||
|
events.entries[i].log = true;
|
||||||
|
events.entries[i].MQTTpublished = false; // Not published by default
|
||||||
|
}
|
||||||
|
events.level = EVENT_LEVEL_INFO;
|
||||||
|
update_bms_status();
|
||||||
|
}
|
||||||
|
|
||||||
void set_event_MQTTpublished(EVENTS_ENUM_TYPE event) {
|
void set_event_MQTTpublished(EVENTS_ENUM_TYPE event) {
|
||||||
events.entries[event].MQTTpublished = true;
|
events.entries[event].MQTTpublished = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,6 +153,7 @@ void init_events(void);
|
||||||
void set_event_latched(EVENTS_ENUM_TYPE event, uint8_t data);
|
void set_event_latched(EVENTS_ENUM_TYPE event, uint8_t data);
|
||||||
void set_event(EVENTS_ENUM_TYPE event, uint8_t data);
|
void set_event(EVENTS_ENUM_TYPE event, uint8_t data);
|
||||||
void clear_event(EVENTS_ENUM_TYPE event);
|
void clear_event(EVENTS_ENUM_TYPE event);
|
||||||
|
void reset_all_events();
|
||||||
void set_event_MQTTpublished(EVENTS_ENUM_TYPE event);
|
void set_event_MQTTpublished(EVENTS_ENUM_TYPE event);
|
||||||
|
|
||||||
const EVENTS_STRUCT_TYPE* get_event_pointer(EVENTS_ENUM_TYPE event);
|
const EVENTS_STRUCT_TYPE* get_event_pointer(EVENTS_ENUM_TYPE event);
|
||||||
|
|
|
@ -5,9 +5,9 @@ const char EVENTS_HTML_START[] = R"=====(
|
||||||
)=====";
|
)=====";
|
||||||
const char EVENTS_HTML_END[] = R"=====(
|
const char EVENTS_HTML_END[] = R"=====(
|
||||||
</div></div>
|
</div></div>
|
||||||
<button onclick='home()'>Back to main page</button>
|
<button onclick="askClear()">Clear all events</button>
|
||||||
<style>.event:nth-child(even){background-color:#455a64}.event:nth-child(odd){background-color:#394b52}</style>
|
<button onclick="home()">Back to main page</button>
|
||||||
<script>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 home(){window.location.href="/"}window.onload=function(){showEvent()}</script>
|
<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;
|
static std::vector<EventData> order_events;
|
||||||
|
@ -63,36 +63,33 @@ String events_processor(const String& var) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Script for displaying event log before it gets minified
|
/* Script for displaying event log before it gets minified
|
||||||
<script>
|
<button onclick="askClear()">Clear all events</button>
|
||||||
function showEvent() {
|
<button onclick="home()">Back to main page</button>
|
||||||
var eventLogElement = document.querySelector('.event-log');
|
<style>
|
||||||
// Get the current time on the client side
|
.event:nth-child(even) {
|
||||||
var currentTime = new Date().getTime() / 1000; // Convert milliseconds to seconds
|
background-color: #455a64;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
.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() - (+n.innerText.split(";")[0] * 4294967296 + +n.innerText.split(";")[1])).toLocaleString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function askClear() {
|
||||||
// Call the showEvent function when the page is loaded
|
if (window.confirm('Are you sure you want to clear all events?')) {
|
||||||
window.onload = function() {
|
window.location.href = '/clearevents';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function home() {
|
||||||
|
window.location.href = "/";
|
||||||
|
}
|
||||||
|
window.onload = function () {
|
||||||
showEvent();
|
showEvent();
|
||||||
};
|
};
|
||||||
|
|
||||||
function home() {
|
|
||||||
window.location.href = '/';
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -63,6 +63,18 @@ void init_webserver() {
|
||||||
request->send_P(200, "text/html", index_html, events_processor);
|
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
|
// Route for editing SSID
|
||||||
server.on("/updateSSID", HTTP_GET, [](AsyncWebServerRequest* request) {
|
server.on("/updateSSID", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
|
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
|
||||||
|
@ -502,6 +514,9 @@ String processor(const String& var) {
|
||||||
#ifdef NISSAN_LEAF_BATTERY
|
#ifdef NISSAN_LEAF_BATTERY
|
||||||
content += "Nissan LEAF";
|
content += "Nissan LEAF";
|
||||||
#endif // NISSAN_LEAF_BATTERY
|
#endif // NISSAN_LEAF_BATTERY
|
||||||
|
#ifdef PYLON_BATTERY
|
||||||
|
content += "Pylon compatible battery";
|
||||||
|
#endif // PYLON_BATTERY
|
||||||
#ifdef RJXZS_BMS
|
#ifdef RJXZS_BMS
|
||||||
content += "RJXZS BMS, DIY battery";
|
content += "RJXZS BMS, DIY battery";
|
||||||
#endif // RJXZS_BMS
|
#endif // RJXZS_BMS
|
||||||
|
@ -608,8 +623,15 @@ String processor(const String& var) {
|
||||||
content += formatPowerValue("Power", powerFloat, "", 1);
|
content += formatPowerValue("Power", powerFloat, "", 1);
|
||||||
content += formatPowerValue("Total capacity", datalayer.battery.info.total_capacity_Wh, "h", 0);
|
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("Remaining capacity", datalayer.battery.status.remaining_capacity_Wh, "h", 1);
|
||||||
|
|
||||||
|
if (emulator_pause_status == NORMAL) {
|
||||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 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);
|
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 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>";
|
content += "<h4>Cell min: " + String(datalayer.battery.status.cell_min_voltage_mV) + " mV</h4>";
|
||||||
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
|
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
|
||||||
|
@ -644,9 +666,9 @@ String processor(const String& var) {
|
||||||
content += "<span style='color: red;'>✕</span></h4>";
|
content += "<span style='color: red;'>✕</span></h4>";
|
||||||
}
|
}
|
||||||
if (emulator_pause_status == NORMAL)
|
if (emulator_pause_status == NORMAL)
|
||||||
content += "<h4>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||||
else
|
else
|
||||||
content += "<h4 style='color: red;'>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||||
|
|
||||||
#ifdef CONTACTOR_CONTROL
|
#ifdef CONTACTOR_CONTROL
|
||||||
content += "<h4>Contactors controlled by Battery-Emulator: ";
|
content += "<h4>Contactors controlled by Battery-Emulator: ";
|
||||||
|
@ -810,10 +832,10 @@ String processor(const String& var) {
|
||||||
#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
|
#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
|
||||||
|
|
||||||
if (emulator_pause_request_ON)
|
if (emulator_pause_request_ON)
|
||||||
content += "<button onclick='PauseBattery(false)'>Resume Battery</button>";
|
content += "<button onclick='PauseBattery(false)'>Resume charge/discharge</button>";
|
||||||
else
|
else
|
||||||
content += "<button onclick='PauseBattery(true)'>Pause Battery</button>";
|
content += "<button onclick='PauseBattery(true)'>Pause charge/discharge</button>";
|
||||||
|
content += " ";
|
||||||
content += "<button onclick='OTA()'>Perform OTA update</button>";
|
content += "<button onclick='OTA()'>Perform OTA update</button>";
|
||||||
content += " ";
|
content += " ";
|
||||||
content += "<button onclick='Settings()'>Change Settings</button>";
|
content += "<button onclick='Settings()'>Change Settings</button>";
|
||||||
|
@ -876,6 +898,10 @@ void onOTAStart() {
|
||||||
// If already set, make a new attempt
|
// If already set, make a new attempt
|
||||||
clear_event(EVENT_OTA_UPDATE_TIMEOUT);
|
clear_event(EVENT_OTA_UPDATE_TIMEOUT);
|
||||||
ota_active = true;
|
ota_active = true;
|
||||||
|
|
||||||
|
//completely force stop the CAN communication
|
||||||
|
ESP32Can.CANStop();
|
||||||
|
|
||||||
ota_timeout_timer.reset();
|
ota_timeout_timer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -908,12 +934,14 @@ void onOTAEnd(bool success) {
|
||||||
#endif // DEBUG_VIA_USB
|
#endif // DEBUG_VIA_USB
|
||||||
//try to Resume the battery pause and CAN communication
|
//try to Resume the battery pause and CAN communication
|
||||||
setBatteryPause(false, false);
|
setBatteryPause(false, false);
|
||||||
|
//resume CAN communication
|
||||||
|
ESP32Can.CANInit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> // This function makes power values appear as W when under 1000, and kW when over
|
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 formatPowerValue(String label, T value, String unit, int precision, String color) {
|
||||||
String result = "<h4 style='color: white;'>" + label + ": ";
|
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) {
|
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);
|
float convertedValue = static_cast<float>(value);
|
||||||
|
|
|
@ -102,7 +102,7 @@ void onOTAEnd(bool success);
|
||||||
* @return string: values
|
* @return string: values
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
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();
|
extern void storeSettings();
|
||||||
|
|
||||||
|
|
|
@ -46,12 +46,6 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
||||||
// Pre-OTA update callback
|
// Pre-OTA update callback
|
||||||
if (preUpdateCallback != NULL) preUpdateCallback();
|
if (preUpdateCallback != NULL) preUpdateCallback();
|
||||||
|
|
||||||
// Sleep for 3 seconds to allow asynchronous preUpdateCallback tasks to complete
|
|
||||||
unsigned long sleepStart = millis();
|
|
||||||
while (millis() - sleepStart < 3000) { // Sleep for 3 second
|
|
||||||
delay(1); // Yield to other tasks
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get header x-ota-mode value, if present
|
// Get header x-ota-mode value, if present
|
||||||
OTA_Mode mode = OTA_MODE_FIRMWARE;
|
OTA_Mode mode = OTA_MODE_FIRMWARE;
|
||||||
// Get mode from arg
|
// Get mode from arg
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue