mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 10:19:29 +02:00
Merge branch 'main' into feature/DIY-battery-RJXZS-bms
This commit is contained in:
commit
86b999e643
32 changed files with 733 additions and 479 deletions
2
.github/workflows/compile-all-batteries.yml
vendored
2
.github/workflows/compile-all-batteries.yml
vendored
|
@ -45,7 +45,7 @@ jobs:
|
|||
- RENAULT_KANGOO_BATTERY
|
||||
- RENAULT_ZOE_GEN1_BATTERY
|
||||
- RENAULT_ZOE_GEN2_BATTERY
|
||||
- TESLA_MODEL_3_BATTERY
|
||||
- TESLA_MODEL_3Y_BATTERY
|
||||
- VOLVO_SPA_BATTERY
|
||||
- TEST_FAKE_BATTERY
|
||||
- SERIAL_LINK_RECEIVER
|
||||
|
|
|
@ -47,7 +47,7 @@ jobs:
|
|||
- RENAULT_KANGOO_BATTERY
|
||||
- RENAULT_ZOE_GEN1_BATTERY
|
||||
- RENAULT_ZOE_GEN2_BATTERY
|
||||
- TESLA_MODEL_3_BATTERY
|
||||
- TESLA_MODEL_3Y_BATTERY
|
||||
- VOLVO_SPA_BATTERY
|
||||
- TEST_FAKE_BATTERY
|
||||
# These are the emulated inverter communication protocols for which the code will be compiled.
|
||||
|
|
2
.github/workflows/compile-all-inverters.yml
vendored
2
.github/workflows/compile-all-inverters.yml
vendored
|
@ -39,7 +39,7 @@ jobs:
|
|||
# - KIA_HYUNDAI_64_BATTERY
|
||||
- NISSAN_LEAF_BATTERY
|
||||
# - RENAULT_ZOE_BATTERY
|
||||
# - TESLA_MODEL_3_BATTERY
|
||||
# - TESLA_MODEL_3Y_BATTERY
|
||||
# These are the emulated inverter communication protocols for which the code will be compiled.
|
||||
inverter:
|
||||
- BYD_CAN
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
Preferences settings; // Store user settings
|
||||
// The current software version, shown on webserver
|
||||
const char* version_number = "7.2.dev";
|
||||
const char* version_number = "7.3.dev";
|
||||
|
||||
// Interval settings
|
||||
uint16_t intervalUpdateValues = INTERVAL_5_S; // Interval at which to update inverter values / Modbus registers
|
||||
|
@ -94,6 +94,8 @@ MyTimer connectivity_task_timer_10s(INTERVAL_10_S);
|
|||
|
||||
MyTimer loop_task_timer_10s(INTERVAL_10_S);
|
||||
|
||||
MyTimer check_pause_2s(INTERVAL_2_S);
|
||||
|
||||
// Contactor parameters
|
||||
#ifdef CONTACTOR_CONTROL
|
||||
enum State { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED };
|
||||
|
@ -287,6 +289,10 @@ void core_loop(void* task_time_us) {
|
|||
datalayer.system.status.time_cantx_us = 0;
|
||||
datalayer.system.status.core_task_10s_max_us = 0;
|
||||
}
|
||||
if (check_pause_2s.elapsed()) {
|
||||
emulator_pause_state_send_CAN_battery();
|
||||
}
|
||||
|
||||
#endif
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
}
|
||||
|
@ -575,6 +581,7 @@ void receive_can_native() { // This section checks if we have a complete CAN me
|
|||
|
||||
void send_can() {
|
||||
|
||||
if (can_send_CAN)
|
||||
send_can_battery();
|
||||
|
||||
#ifdef CAN_INVERTER_SELECTED
|
||||
|
@ -582,6 +589,7 @@ void send_can() {
|
|||
#endif // CAN_INVERTER_SELECTED
|
||||
|
||||
#ifdef CHARGER_SELECTED
|
||||
if (can_send_CAN)
|
||||
send_can_charger();
|
||||
#endif // CHARGER_SELECTED
|
||||
}
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
//#define RENAULT_ZOE_GEN1_BATTERY
|
||||
//#define RENAULT_ZOE_GEN2_BATTERY
|
||||
//#define SANTA_FE_PHEV_BATTERY
|
||||
//#define TESLA_MODEL_3_BATTERY
|
||||
//#define TESLA_MODEL_SX_BATTERY
|
||||
//#define TESLA_MODEL_3Y_BATTERY
|
||||
//#define VOLVO_SPA_BATTERY
|
||||
//#define TEST_FAKE_BATTERY
|
||||
//#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires DUAL_CAN setup)
|
||||
|
|
|
@ -66,8 +66,9 @@
|
|||
#include "SANTA-FE-PHEV-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef TESLA_MODEL_3_BATTERY
|
||||
#include "TESLA-MODEL-3-BATTERY.h"
|
||||
#if defined(TESLA_MODEL_SX_BATTERY) || defined(TESLA_MODEL_3Y_BATTERY)
|
||||
#define TESLA_BATTERY
|
||||
#include "TESLA-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef TEST_FAKE_BATTERY
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include "../include.h"
|
||||
#ifdef TESLA_MODEL_3_BATTERY
|
||||
#ifdef TESLA_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "TESLA-MODEL-3-BATTERY.h"
|
||||
#include "TESLA-BATTERY.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
/* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */
|
||||
|
@ -327,6 +327,8 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
battery_cell_deviation_mV = (battery_cell_max_v - battery_cell_min_v);
|
||||
|
||||
#ifdef TESLA_MODEL_3Y_BATTERY
|
||||
// Autodetect algoritm for chemistry on 3/Y packs.
|
||||
// NCM/A batteries have 96s, LFP has 102-106s
|
||||
// Drawback with this check is that it takes 3-5minutes before all cells have been counted!
|
||||
if (datalayer.battery.info.number_of_cells > 101) {
|
||||
|
@ -335,12 +337,13 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
//Once cell chemistry is determined, set maximum and minimum total pack voltage safety limits
|
||||
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_LFP;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_LFP;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_LFP;
|
||||
} else { // NCM/A chemistry
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_NCMA;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_NCMA;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_NCMA;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_NCMA;
|
||||
}
|
||||
#endif // TESLA_MODEL_3Y_BATTERY
|
||||
|
||||
//Check if SOC% is plausible
|
||||
if (datalayer.battery.status.voltage_dV >
|
||||
|
@ -906,6 +909,8 @@ void update_values_battery2() { //This function maps all the values fetched via
|
|||
|
||||
battery2_cell_deviation_mV = (battery2_cell_max_v - battery2_cell_min_v);
|
||||
|
||||
#ifdef TESLA_MODEL_3Y_BATTERY
|
||||
// Autodetect algoritm for chemistry on 3/Y packs.
|
||||
// NCM/A batteries have 96s, LFP has 102-106s
|
||||
// Drawback with this check is that it takes 3-5minutes before all cells have been counted!
|
||||
if (datalayer.battery2.info.number_of_cells > 101) {
|
||||
|
@ -914,13 +919,15 @@ void update_values_battery2() { //This function maps all the values fetched via
|
|||
|
||||
//Once cell chemistry is determined, set maximum and minimum total pack voltage safety limits
|
||||
if (datalayer.battery2.info.chemistry == battery_chemistry_enum::LFP) {
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_LFP;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_LFP;
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_LFP;
|
||||
} else { // NCM/A chemistry
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_NCMA;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_NCMA;
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_NCMA;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_NCMA;
|
||||
}
|
||||
|
||||
#endif // TESLA_MODEL_3Y_BATTERY
|
||||
|
||||
//Check if SOC% is plausible
|
||||
if (datalayer.battery2.status.voltage_dV >
|
||||
(datalayer.battery2.info.max_design_voltage_dV -
|
||||
|
@ -1239,28 +1246,39 @@ void printDebugIfActive(uint8_t symbol, const char* message) {
|
|||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Tesla Model 3 battery selected");
|
||||
Serial.println("Tesla Model S/3/X/Y battery selected");
|
||||
#endif
|
||||
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
|
||||
#ifdef TESLA_MODEL_SX_BATTERY // Always use NCM/A mode on S/X packs
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_SX_NCMA;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_SX_NCMA;
|
||||
#ifdef DOUBLE_BATTERY
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_SX_NCMA;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_SX_NCMA;
|
||||
#endif // DOUBLE_BATTERY
|
||||
#endif // TESLA_MODEL_SX_BATTERY
|
||||
|
||||
#ifdef TESLA_MODEL_3Y_BATTERY // Model 3/Y can be either LFP or NCM/A
|
||||
#ifdef LFP_CHEMISTRY
|
||||
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_LFP;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_LFP;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_LFP;
|
||||
#ifdef DOUBLE_BATTERY
|
||||
datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP;
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_LFP;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_LFP;
|
||||
#endif
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_LFP;
|
||||
#endif // DOUBLE_BATTERY
|
||||
#else // Startup in NCM/A mode
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_NCMA;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_NCMA;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_NCMA;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_NCMA;
|
||||
#ifdef DOUBLE_BATTERY
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_NCMA;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_NCMA;
|
||||
#endif
|
||||
#endif
|
||||
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_NCMA;
|
||||
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_NCMA;
|
||||
#endif // DOUBLE_BATTERY
|
||||
#endif // !LFP_CHEMISTRY
|
||||
#endif // TESLA_MODEL_3Y_BATTERY
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // TESLA_BATTERY
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef TESLA_MODEL_3_BATTERY_H
|
||||
#define TESLA_MODEL_3_BATTERY_H
|
||||
#ifndef TESLA_BATTERY_H
|
||||
#define TESLA_BATTERY_H
|
||||
#include "../include.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
|
@ -14,10 +14,13 @@
|
|||
#define RAMPDOWNPOWERALLOWED 15000 // What power we ramp down from towards top balancing
|
||||
#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery
|
||||
#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging
|
||||
#define MAX_PACK_VOLTAGE_NCMA 4030 // V+1, if pack voltage goes over this, charge stops
|
||||
#define MIN_PACK_VOLTAGE_NCMA 3100 // V+1, if pack voltage goes below this, discharge stops
|
||||
#define MAX_PACK_VOLTAGE_LFP 3880 // V+1, if pack voltage goes over this, charge stops
|
||||
#define MIN_PACK_VOLTAGE_LFP 2968 // V+1, if pack voltage goes below this, discharge stops
|
||||
|
||||
#define MAX_PACK_VOLTAGE_SX_NCMA 4600 // V+1, if pack voltage goes over this, charge stops
|
||||
#define MIN_PACK_VOLTAGE_SX_NCMA 3100 // V+1, if pack voltage goes over this, charge stops
|
||||
#define MAX_PACK_VOLTAGE_3Y_NCMA 4030 // V+1, if pack voltage goes over this, charge stops
|
||||
#define MIN_PACK_VOLTAGE_3Y_NCMA 3100 // V+1, if pack voltage goes below this, discharge stops
|
||||
#define MAX_PACK_VOLTAGE_3Y_LFP 3880 // V+1, if pack voltage goes over this, charge stops
|
||||
#define MIN_PACK_VOLTAGE_3Y_LFP 2968 // V+1, if pack voltage goes below this, discharge stops
|
||||
#define MAX_CELL_DEVIATION_MV 9999 // Handled inside the Tesla.cpp file, just for compilation
|
||||
|
||||
void printFaultCodesIfActive();
|
|
@ -48,6 +48,16 @@ SensorConfig sensorConfigs[] = {
|
|||
{"cell_max_voltage", "Battery Emulator Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"},
|
||||
{"cell_min_voltage", "Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"},
|
||||
{"battery_voltage", "Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"},
|
||||
{"total_capacity", "Battery Emulator Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"},
|
||||
{"remaining_capacity", "Battery Emulator Battery Remaining Capacity", "{{ value_json.remaining_capacity }}", "Wh",
|
||||
"energy"},
|
||||
{"max_discharge_power", "Battery Emulator Battery Max Discharge Power", "{{ value_json.max_discharge_power }}", "W",
|
||||
"power"},
|
||||
{"max_charge_power", "Battery Emulator Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W",
|
||||
"power"},
|
||||
{"bms_status", "Battery Emulator BMS Status", "{{ value_json.bms_status }}", "", ""},
|
||||
{"pause_status", "Battery Emulator Pause Status", "{{ value_json.pause_status }}", "", ""},
|
||||
|
||||
};
|
||||
|
||||
static String generateCommonInfoAutoConfigTopic(const char* object_id, const char* hostname) {
|
||||
|
@ -81,10 +91,13 @@ static void publish_common_info(void) {
|
|||
doc["unique_id"] = "battery-emulator_" + String(hostname) + "_" + String(config.object_id);
|
||||
doc["object_id"] = String(hostname) + "_" + String(config.object_id);
|
||||
doc["value_template"] = config.value_template;
|
||||
if (config.unit != nullptr && strlen(config.unit) > 0)
|
||||
doc["unit_of_measurement"] = config.unit;
|
||||
if (config.device_class != nullptr && strlen(config.device_class) > 0) {
|
||||
doc["device_class"] = config.device_class;
|
||||
doc["enabled_by_default"] = true;
|
||||
doc["state_class"] = "measurement";
|
||||
}
|
||||
doc["enabled_by_default"] = true;
|
||||
doc["expire_after"] = 240;
|
||||
doc["device"]["identifiers"][0] = "battery-emulator";
|
||||
doc["device"]["manufacturer"] = "DalaTech";
|
||||
|
@ -95,10 +108,16 @@ static void publish_common_info(void) {
|
|||
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
|
||||
serializeJson(doc, mqtt_msg);
|
||||
mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id, hostname).c_str(), mqtt_msg, true);
|
||||
}
|
||||
doc.clear();
|
||||
}
|
||||
|
||||
} else {
|
||||
#endif // HA_AUTODISCOVERY
|
||||
doc["bms_status"] = getBMSStatus(datalayer.battery.status.bms_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)
|
||||
if (datalayer.battery.status.bms_status == ACTIVE && can_send_CAN && millis() > BOOTUP_TIME) {
|
||||
doc["SOC"] = ((float)datalayer.battery.status.reported_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;
|
||||
|
@ -113,6 +132,12 @@ static void publish_common_info(void) {
|
|||
doc["cell_max_voltage"] = ((float)datalayer.battery.status.cell_max_voltage_mV) / 1000.0;
|
||||
doc["cell_min_voltage"] = ((float)datalayer.battery.status.cell_min_voltage_mV) / 1000.0;
|
||||
}
|
||||
doc["total_capacity"] = ((float)datalayer.battery.info.total_capacity_Wh);
|
||||
doc["remaining_capacity"] = ((float)datalayer.battery.status.remaining_capacity_Wh);
|
||||
doc["max_discharge_power"] = ((float)datalayer.battery.status.max_discharge_power_W);
|
||||
doc["max_charge_power"] = ((float)datalayer.battery.status.max_charge_power_W);
|
||||
}
|
||||
|
||||
serializeJson(doc, mqtt_msg);
|
||||
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
|
|
@ -9,9 +9,23 @@ static bool battery_empty_event_fired = false;
|
|||
|
||||
#define MAX_SOH_DEVIATION_PPTT 2500
|
||||
|
||||
//battery pause status begin
|
||||
bool emulator_pause_request_ON = false;
|
||||
bool emulator_pause_CAN_send_ON = false;
|
||||
bool can_send_CAN = true;
|
||||
|
||||
battery_pause_status emulator_pause_status = NORMAL;
|
||||
//battery pause status end
|
||||
|
||||
void update_machineryprotection() {
|
||||
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
|
||||
|
||||
// Pause function is on
|
||||
if (emulator_pause_request_ON) {
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
}
|
||||
|
||||
// Battery is overheated!
|
||||
if (datalayer.battery.status.temperature_max_dC > 500) {
|
||||
set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC);
|
||||
|
@ -138,6 +152,13 @@ void update_machineryprotection() {
|
|||
|
||||
#ifdef DOUBLE_BATTERY // Additional Double-Battery safeties are checked here
|
||||
// Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise an error
|
||||
|
||||
// Pause function is on
|
||||
if (emulator_pause_request_ON) {
|
||||
datalayer.battery2.status.max_discharge_power_W = 0;
|
||||
datalayer.battery2.status.max_charge_power_W = 0;
|
||||
}
|
||||
|
||||
if (!datalayer.battery2.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CAN2_RX_FAILURE, 0);
|
||||
} else {
|
||||
|
@ -171,3 +192,67 @@ void update_machineryprotection() {
|
|||
|
||||
#endif // DOUBLE_BATTERY
|
||||
}
|
||||
|
||||
//battery pause status begin
|
||||
void setBatteryPause(bool pause_battery, bool pause_CAN) {
|
||||
|
||||
emulator_pause_CAN_send_ON = pause_CAN;
|
||||
|
||||
if (pause_battery) {
|
||||
|
||||
set_event(EVENT_PAUSE_BEGIN, 1);
|
||||
emulator_pause_request_ON = true;
|
||||
emulator_pause_status = PAUSING;
|
||||
datalayer.battery.status.max_discharge_power_W = 0;
|
||||
datalayer.battery.status.max_charge_power_W = 0;
|
||||
#ifdef DOUBLE_BATTERY
|
||||
datalayer.battery2.status.max_discharge_power_W = 0;
|
||||
datalayer.battery2.status.max_charge_power_W = 0;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
clear_event(EVENT_PAUSE_BEGIN);
|
||||
set_event(EVENT_PAUSE_END, 0);
|
||||
emulator_pause_request_ON = false;
|
||||
emulator_pause_CAN_send_ON = false;
|
||||
emulator_pause_status = RESUMING;
|
||||
clear_event(EVENT_PAUSE_END);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief handle emulator pause status
|
||||
/// @return true if CAN messages should be sent to battery, false if not
|
||||
void emulator_pause_state_send_CAN_battery() {
|
||||
|
||||
if (emulator_pause_status == NORMAL)
|
||||
can_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
|
||||
if (emulator_pause_request_ON && emulator_pause_status == PAUSING && datalayer.battery.status.current_dA < 18 &&
|
||||
datalayer.battery.status.current_dA > -18) {
|
||||
emulator_pause_status = PAUSED;
|
||||
}
|
||||
|
||||
if (!emulator_pause_request_ON && emulator_pause_status == RESUMING) {
|
||||
emulator_pause_status = NORMAL;
|
||||
can_send_CAN = true;
|
||||
}
|
||||
|
||||
can_send_CAN = (!emulator_pause_CAN_send_ON || emulator_pause_status == NORMAL);
|
||||
}
|
||||
|
||||
std::string get_emulator_pause_status() {
|
||||
switch (emulator_pause_status) {
|
||||
case NORMAL:
|
||||
return "RUNNING";
|
||||
case PAUSING:
|
||||
return "PAUSING";
|
||||
case PAUSED:
|
||||
return "PAUSED";
|
||||
case RESUMING:
|
||||
return "RESUMING";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
//battery pause status
|
||||
|
|
|
@ -1,11 +1,26 @@
|
|||
#ifndef SAFETY_H
|
||||
#define SAFETY_H
|
||||
#include <Arduino.h>
|
||||
#include <string>
|
||||
|
||||
#define MAX_CAN_FAILURES 50
|
||||
|
||||
#define MAX_CHARGE_DISCHARGE_LIMIT_FAILURES 1
|
||||
|
||||
//battery pause status begin
|
||||
enum battery_pause_status { NORMAL = 0, PAUSING = 1, PAUSED = 2, RESUMING = 3 };
|
||||
extern bool emulator_pause_request_ON;
|
||||
extern bool emulator_pause_CAN_send_ON;
|
||||
extern battery_pause_status emulator_pause_status;
|
||||
extern bool can_send_CAN;
|
||||
//battery pause status end
|
||||
|
||||
void update_machineryprotection();
|
||||
|
||||
//battery pause status begin
|
||||
void setBatteryPause(bool pause_battery, bool pause_CAN);
|
||||
void emulator_pause_state_send_CAN_battery();
|
||||
std::string get_emulator_pause_status();
|
||||
//battery pause status end
|
||||
|
||||
#endif
|
||||
|
|
|
@ -198,6 +198,8 @@ void init_events(void) {
|
|||
events.entries[EVENT_RESET_EFUSE].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_PWR_GLITCH].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_RESET_CPU_LOCKUP].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_PAUSE_BEGIN].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_PAUSE_END].level = EVENT_LEVEL_INFO;
|
||||
|
||||
events.entries[EVENT_EEPROM_WRITE].log = false; // Don't log the logger...
|
||||
|
||||
|
@ -367,6 +369,10 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "Info: The board was reset due to a detected power glitch";
|
||||
case EVENT_RESET_CPU_LOCKUP:
|
||||
return "Warning: The board was reset due to CPU lockup. Inform developers!";
|
||||
case EVENT_PAUSE_BEGIN:
|
||||
return "Warning: The emulator is trying to pause the battery.";
|
||||
case EVENT_PAUSE_END:
|
||||
return "Info: The emulator is attempting to resume battery operation from pause.";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -93,6 +93,8 @@
|
|||
XX(EVENT_RESET_EFUSE) \
|
||||
XX(EVENT_RESET_PWR_GLITCH) \
|
||||
XX(EVENT_RESET_CPU_LOCKUP) \
|
||||
XX(EVENT_PAUSE_BEGIN) \
|
||||
XX(EVENT_PAUSE_END) \
|
||||
XX(EVENT_NOF_EVENTS)
|
||||
|
||||
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;
|
||||
|
|
21
Software/src/devboard/utils/types.cpp
Normal file
21
Software/src/devboard/utils/types.cpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include "types.h"
|
||||
|
||||
// Function to get string representation of bms_status_enum
|
||||
std::string getBMSStatus(bms_status_enum status) {
|
||||
switch (status) {
|
||||
case STANDBY:
|
||||
return "STANDBY";
|
||||
case INACTIVE:
|
||||
return "INACTIVE";
|
||||
case DARKSTART:
|
||||
return "DARKSTART";
|
||||
case ACTIVE:
|
||||
return "ACTIVE";
|
||||
case FAULT:
|
||||
return "FAULT";
|
||||
case UPDATING:
|
||||
return "UPDATING";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef _TYPES_H_
|
||||
#define _TYPES_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
enum bms_status_enum { STANDBY = 0, INACTIVE = 1, DARKSTART = 2, ACTIVE = 3, FAULT = 4, UPDATING = 5 };
|
||||
enum battery_chemistry_enum { NCA, NMC, LFP };
|
||||
enum led_color { GREEN, YELLOW, RED, BLUE, RGB };
|
||||
|
@ -46,4 +48,6 @@ typedef struct {
|
|||
} data;
|
||||
} CAN_frame;
|
||||
|
||||
std::string getBMSStatus(bms_status_enum status);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#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>
|
||||
|
@ -12,6 +10,13 @@ const char EVENTS_HTML_END[] = R"=====(
|
|||
<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>
|
||||
)=====";
|
||||
|
||||
static std::vector<EventData> order_events;
|
||||
|
||||
// Function to compare events by timestamp
|
||||
static bool compareEventsByTimestamp(const EventData& a, const EventData& b) {
|
||||
return a.event_pointer->timestamp > b.event_pointer->timestamp;
|
||||
}
|
||||
|
||||
String events_processor(const String& var) {
|
||||
if (var == "X") {
|
||||
String content = "";
|
||||
|
@ -22,16 +27,29 @@ String events_processor(const String& var) {
|
|||
|
||||
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(), compareEventsByTimestamp);
|
||||
|
||||
// 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>");
|
||||
|
@ -41,11 +59,13 @@ String events_processor(const String& var) {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/* Script for displaying event log before it gets minified
|
||||
|
|
|
@ -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
|
||||
|
@ -11,5 +14,10 @@
|
|||
* @return String
|
||||
*/
|
||||
String events_processor(const String& var);
|
||||
// Define a struct to hold event data
|
||||
struct EventData {
|
||||
EVENTS_ENUM_TYPE event_handle;
|
||||
const EVENTS_STRUCT_TYPE* event_pointer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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"
|
||||
|
@ -34,6 +35,7 @@ unsigned const long MAX_WIFI_RETRY_INTERVAL = 90000; // Maximum wifi ret
|
|||
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
|
||||
|
@ -53,6 +55,13 @@ 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();
|
||||
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) {
|
||||
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
|
||||
|
@ -159,6 +168,19 @@ 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 editing SOCMin
|
||||
server.on("/updateSocMin", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
|
||||
|
@ -414,10 +436,8 @@ void wifi_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;
|
||||
onOTAEnd(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -444,6 +464,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 = "";
|
||||
|
@ -576,9 +614,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
|
||||
|
@ -587,6 +628,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>";
|
||||
|
||||
|
@ -693,6 +737,10 @@ String processor(const String& var) {
|
|||
} else {
|
||||
content += "<span style='color: red;'>✕</span></h4>";
|
||||
}
|
||||
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>";
|
||||
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
|
@ -769,6 +817,10 @@ String processor(const String& var) {
|
|||
} else {
|
||||
content += "<span style='color: red;'>✕</span></h4>";
|
||||
}
|
||||
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>";
|
||||
|
@ -830,6 +882,11 @@ 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 Battery</button>";
|
||||
else
|
||||
content += "<button onclick='PauseBattery(true)'>Pause Battery</button>";
|
||||
|
||||
content += "<button onclick='OTA()'>Perform OTA update</button>";
|
||||
content += " ";
|
||||
content += "<button onclick='Settings()'>Change Settings</button>";
|
||||
|
@ -863,6 +920,12 @@ String processor(const String& var) {
|
|||
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 += "</script>";
|
||||
|
||||
|
@ -877,8 +940,10 @@ 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
|
||||
|
@ -900,8 +965,13 @@ 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) {
|
||||
// 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
|
||||
|
@ -909,12 +979,9 @@ 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
|
||||
|
|
|
@ -103,6 +103,7 @@ void init_ElegantOTA();
|
|||
* @return String
|
||||
*/
|
||||
String processor(const String& var);
|
||||
String get_firmware_info_processor(const String& var);
|
||||
|
||||
/**
|
||||
* @brief Executes on OTA start
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
/* TODO: Map error bits in 0x158 */
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis60s = 0;
|
||||
static unsigned long previousMillis100ms = 0;
|
||||
|
||||
//Actual content messages
|
||||
CAN_frame SMA_358 = {.FD = false,
|
||||
|
@ -51,7 +51,7 @@ CAN_frame SMA_598 = {.FD = false,
|
|||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x598,
|
||||
.data = {0x00, 0xD3, 0x00, 0x01, 0x5C, 0x98, 0xB6, 0xEE}};
|
||||
.data = {0x00, 0x01, 0x0F, 0x2C, 0x5C, 0x98, 0xB6, 0xEE}};
|
||||
CAN_frame SMA_5D8 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
|
@ -221,12 +221,14 @@ void receive_can_inverter(CAN_frame rx_frame) {
|
|||
break;
|
||||
case 0x420: //Message originating from SMA inverter - Timestamp
|
||||
//Frame0-3 Timestamp
|
||||
/*
|
||||
transmit_can(&SMA_158, can_config.inverter);
|
||||
transmit_can(&SMA_358, can_config.inverter);
|
||||
transmit_can(&SMA_3D8, can_config.inverter);
|
||||
transmit_can(&SMA_458, can_config.inverter);
|
||||
transmit_can(&SMA_518, can_config.inverter);
|
||||
transmit_can(&SMA_4D8, can_config.inverter);
|
||||
*/
|
||||
break;
|
||||
case 0x5E0: //Message originating from SMA inverter - String
|
||||
break;
|
||||
|
@ -254,9 +256,10 @@ void receive_can_inverter(CAN_frame rx_frame) {
|
|||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send CAN Message every 60s
|
||||
if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
|
||||
previousMillis60s = currentMillis;
|
||||
// Send CAN Message every 100ms if we're enabled
|
||||
if (datalayer.system.status.inverter_allows_contactor_closing) {
|
||||
if (currentMillis - previousMillis100ms >= 100) {
|
||||
previousMillis100ms = currentMillis;
|
||||
transmit_can(&SMA_158, can_config.inverter);
|
||||
transmit_can(&SMA_358, can_config.inverter);
|
||||
transmit_can(&SMA_3D8, can_config.inverter);
|
||||
|
@ -264,5 +267,6 @@ void send_can_inverter() {
|
|||
transmit_can(&SMA_518, can_config.inverter);
|
||||
transmit_can(&SMA_4D8, can_config.inverter);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
/* TODO: Map error bits in 0x158 */
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis60s = 0;
|
||||
static unsigned long previousMillis100ms = 0;
|
||||
|
||||
//Actual content messages
|
||||
CAN_frame SMA_558 = {.FD = false,
|
||||
|
@ -218,12 +218,14 @@ void receive_can_inverter(CAN_frame rx_frame) {
|
|||
break;
|
||||
case 0x420: //Message originating from SMA inverter - Timestamp
|
||||
//Frame0-3 Timestamp
|
||||
/*
|
||||
transmit_can(&SMA_158, can_config.inverter);
|
||||
transmit_can(&SMA_358, can_config.inverter);
|
||||
transmit_can(&SMA_3D8, can_config.inverter);
|
||||
transmit_can(&SMA_458, can_config.inverter);
|
||||
transmit_can(&SMA_518, can_config.inverter);
|
||||
transmit_can(&SMA_4D8, can_config.inverter);
|
||||
*/
|
||||
break;
|
||||
case 0x5E0: //Message originating from SMA inverter - String
|
||||
break;
|
||||
|
@ -251,9 +253,10 @@ void receive_can_inverter(CAN_frame rx_frame) {
|
|||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send CAN Message every 60s
|
||||
if (currentMillis - previousMillis60s >= INTERVAL_60_S) {
|
||||
previousMillis60s = currentMillis;
|
||||
// Send CAN Message every 100ms if Enable line is HIGH
|
||||
if (datalayer.system.status.inverter_allows_contactor_closing) {
|
||||
if (currentMillis - previousMillis100ms >= 100) {
|
||||
previousMillis100ms = currentMillis;
|
||||
|
||||
transmit_can(&SMA_158, can_config.inverter);
|
||||
transmit_can(&SMA_358, can_config.inverter);
|
||||
|
@ -262,5 +265,6 @@ void send_can_inverter() {
|
|||
transmit_can(&SMA_518, can_config.inverter);
|
||||
transmit_can(&SMA_4D8, can_config.inverter);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -37,3 +37,4 @@ node_modules
|
|||
.vscode
|
||||
/build
|
||||
/portal
|
||||
.pio
|
||||
|
|
212
Software/src/lib/ayushsharma82-ElegantOTA/CurrentPlainHTML.txt
Normal file
212
Software/src/lib/ayushsharma82-ElegantOTA/CurrentPlainHTML.txt
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,19 +1,24 @@
|
|||
<p><br/></p>
|
||||
<p align="center"><img src="/docs/feature.png?sanitize=true&raw=true" width="700"></p>
|
||||
<p align="center">
|
||||
<a href="https://elegantota.pro?ref=ghfeature" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/ayushsharma82/ElegantOTA/master/docs/feature.png" width="1200"></p>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
<p align="center">
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/github/last-commit/ayushsharma82/ElegantOTA.svg?style=for-the-badge" />
|
||||
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/ayushsharma82/ElegantOTA/ci.yml?branch=master&style=for-the-badge" />
|
||||
|
||||
<img src="https://img.shields.io/github/license/ayushsharma82/ElegantOTA.svg?style=for-the-badge" />
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>Over-the-Air update library for wireless microcontrollers</p>
|
||||
|
||||
<p align="center">OTA update library for wireless microcontrollers</p>
|
||||
<p align="center">
|
||||
<p>
|
||||
ElegantOTA provides a beautiful user interface to upload over-the-air firmware/filesystem updates to your hardware with precise status and progress. ElegantOTA is designed to make the process of OTA updates slick and simple!
|
||||
</p>
|
||||
|
||||
|
@ -21,6 +26,7 @@ ElegantOTA provides a beautiful user interface to upload over-the-air firmware/f
|
|||
<br/>
|
||||
|
||||
## Features
|
||||
|
||||
- 🔥 Quick & Simple OTA procedure
|
||||
- 🏀 Get useful insight on progress and status of your OTA update
|
||||
- 🎷 No need to learn HTML/CSS/JS
|
||||
|
@ -29,7 +35,9 @@ ElegantOTA provides a beautiful user interface to upload over-the-air firmware/f
|
|||
<br/>
|
||||
|
||||
## Supported MCUs
|
||||
|
||||
ElegantOTA works on the following microcontrollers/boards:
|
||||
|
||||
- ESP8266
|
||||
- ESP32
|
||||
- RP2040 ( Pico W )
|
||||
|
@ -51,12 +59,12 @@ ElegantOTA works on the following microcontrollers/boards:
|
|||
*Preview might appear as blurry due to image optimization.*
|
||||
<br>
|
||||
|
||||
<img src="/docs/demo.gif?raw=true" width="600">
|
||||
<img src="https://raw.githubusercontent.com/ayushsharma82/ElegantOTA/master/docs/demo.gif" width="600">
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
## Want More? Upgrade to Pro
|
||||
## Looking for more? Upgrade to Pro.
|
||||
|
||||
ElegantOTA Pro comes with the following extended functionality:
|
||||
- New Drag & Drop Zone
|
||||
|
@ -74,7 +82,7 @@ ElegantOTA Pro comes with the following extended functionality:
|
|||
<br/>
|
||||
|
||||
<a href="https://elegantota.pro" target="_blank">
|
||||
<img src="/docs/pro-preview.jpg" alt="ElegantOTA Pro" width="600">
|
||||
<img src="https://raw.githubusercontent.com/ayushsharma82/ElegantOTA/master/docs/pro-preview.jpg" alt="ElegantOTA Pro" width="600">
|
||||
</a>
|
||||
|
||||
<br>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
Modifying HTML for the ElegantOTA Library
|
||||
|
||||
This guide provides the necessary steps to update and modify the HTML used in the ElegantOTA library.
|
||||
Follow these steps carefully to ensure that your changes are properly integrated.
|
||||
|
||||
Steps to Modify the HTML:
|
||||
|
||||
1 - Edit the CurrentPlainHTML.txt:
|
||||
|
||||
Locate the file CurrentPlainHTML.txt in your project directory.
|
||||
Modify the HTML content as needed. This file contains the plain HTML code that will be served through the OTA interface.
|
||||
|
||||
Convert the HTML to GZIP and Decimal Format:
|
||||
|
||||
Copy the content of the updated CurrentPlainHTML.txt.
|
||||
Navigate to the CyberChef tool for encoding and compression.
|
||||
Apply the following recipe:
|
||||
Gzip with the "Dynamic Huffman Coding" option enabled.
|
||||
Convert to Decimal with a comma separator.
|
||||
Use this link for the process: https://gchq.github.io/CyberChef/#recipe=Gzip('Dynamic%20Huffman%20Coding','','',false)To_Decimal('Comma',false)
|
||||
|
||||
2 - Update the ELEGANT_HTML Array:
|
||||
|
||||
Copy the resulting decimal output from CyberChef.
|
||||
Replace the existing content of the ELEGANT_HTML array in elop.cpp with the new decimal data from CyberChef.
|
||||
|
||||
3 - Adjust the ELEGANT_HTML Array Size:
|
||||
|
||||
After updating the ELEGANT_HTML array in both elop.h and elop.cpp, update the array size to match the length of the new output from CyberChef.
|
||||
Ensure that the array size reflects the new length of the compressed HTML to avoid errors during compilation.
|
|
@ -15,7 +15,18 @@
|
|||
"maintainer": true
|
||||
}
|
||||
],
|
||||
"version": "3.1.1",
|
||||
"dependencies": [
|
||||
{
|
||||
"owner": "mathieucarbou",
|
||||
"name": "ESPAsyncWebServer",
|
||||
"version": "^3.1.1",
|
||||
"platforms": ["espressif8266", "espressif32"]
|
||||
}
|
||||
],
|
||||
"version": "3.1.5",
|
||||
"frameworks": "arduino",
|
||||
"platforms": ["espressif8266", "espressif32", "raspberrypi"]
|
||||
"platforms": ["espressif8266", "espressif32", "raspberrypi"],
|
||||
"build": {
|
||||
"libCompatMode": "strict"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name=ElegantOTA
|
||||
version=3.1.1
|
||||
version=3.1.5
|
||||
author=Ayush Sharma
|
||||
category=Communication
|
||||
maintainer=Ayush Sharma <asrocks5@gmail.com>
|
||||
|
|
|
@ -6,12 +6,13 @@
|
|||
#
|
||||
# extra_scripts = platformio_upload.py
|
||||
# upload_protocol = custom
|
||||
# upload_url = <your upload URL>
|
||||
# custom_upload_url = <your upload URL>
|
||||
#
|
||||
# An example of an upload URL:
|
||||
# upload_url = http://192.168.1.123/update
|
||||
# also possible: upload_url = http://domainname/update
|
||||
# custom_upload_url = http://192.168.1.123/update
|
||||
# also possible: custom_upload_url = http://domainname/update
|
||||
|
||||
import sys
|
||||
import requests
|
||||
import hashlib
|
||||
from urllib.parse import urlparse
|
||||
|
@ -54,7 +55,10 @@ def on_upload(source, target, env):
|
|||
'Connection': 'keep-alive'
|
||||
}
|
||||
|
||||
try:
|
||||
checkAuthResponse = requests.get(f"{upload_url_compatibility}/update")
|
||||
except Exception as e:
|
||||
return 'Error checking auth: ' + repr(e)
|
||||
|
||||
if checkAuthResponse.status_code == 401:
|
||||
try:
|
||||
|
@ -66,24 +70,27 @@ def on_upload(source, target, env):
|
|||
print("No authentication values specified.")
|
||||
print('Please, add some Options in your .ini file like: \n\ncustom_username=username\ncustom_password=password\n')
|
||||
if username is None or password is None:
|
||||
print("Authentication required, but no credentials provided.")
|
||||
return
|
||||
return "Authentication required, but no credentials provided."
|
||||
print("Serverconfiguration: authentication needed.")
|
||||
auth = HTTPDigestAuth(username, password)
|
||||
try:
|
||||
doUpdateAuth = requests.get(start_url, headers=start_headers, auth=auth)
|
||||
except Exception as e:
|
||||
return 'Error while authenticating: ' + repr(e)
|
||||
|
||||
if doUpdateAuth.status_code != 200:
|
||||
print("authentication faild " + str(doUpdateAuth.status_code))
|
||||
return
|
||||
print("Authentication successfull")
|
||||
return "Authentication failed " + str(doUpdateAuth.status_code)
|
||||
print("Authentication successful")
|
||||
else:
|
||||
auth = None
|
||||
print("Serverconfiguration: autentication not needed.")
|
||||
print("Serverconfiguration: authentication not needed.")
|
||||
try:
|
||||
doUpdate = requests.get(start_url, headers=start_headers)
|
||||
except Exception as e:
|
||||
return 'Error while starting upload: ' + repr(e)
|
||||
|
||||
if doUpdate.status_code != 200:
|
||||
print("start-request faild " + str(doUpdate.status_code))
|
||||
return
|
||||
return "Start request failed " + str(doUpdate.status_code)
|
||||
|
||||
firmware.seek(0)
|
||||
encoder = MultipartEncoder(fields={
|
||||
|
@ -114,14 +121,16 @@ def on_upload(source, target, env):
|
|||
'Origin': f'{upload_url}'
|
||||
}
|
||||
|
||||
|
||||
try:
|
||||
response = requests.post(f"{upload_url}/ota/upload", data=monitor, headers=post_headers, auth=auth)
|
||||
except Exception as e:
|
||||
return 'Error while uploading: ' + repr(e)
|
||||
|
||||
bar.close()
|
||||
time.sleep(0.1)
|
||||
|
||||
if response.status_code != 200:
|
||||
message = "\nUpload faild.\nServer response: " + response.text
|
||||
message = "\nUpload failed.\nServer response: " + response.text
|
||||
tqdm.write(message)
|
||||
else:
|
||||
message = "\nUpload successful.\nServer response: " + response.text
|
||||
|
|
|
@ -16,16 +16,20 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
|
||||
#if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
|
||||
_server->on("/update", HTTP_GET, [&](AsyncWebServerRequest *request){
|
||||
if(_authenticate && !request->authenticate(_username, _password)){
|
||||
if(_authenticate && !request->authenticate(_username.c_str(), _password.c_str())){
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
#if defined(ASYNCWEBSERVER_VERSION) && ASYNCWEBSERVER_VERSION_MAJOR > 2 // This means we are using recommended fork of AsyncWebServer
|
||||
AsyncWebServerResponse *response = request->beginResponse(200, "text/html", ELEGANT_HTML, sizeof(ELEGANT_HTML));
|
||||
#else
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", ELEGANT_HTML, sizeof(ELEGANT_HTML));
|
||||
#endif
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
request->send(response);
|
||||
});
|
||||
#else
|
||||
_server->on("/update", HTTP_GET, [&](){
|
||||
if (_authenticate && !_server->authenticate(_username, _password)) {
|
||||
if (_authenticate && !_server->authenticate(_username.c_str(), _password.c_str())) {
|
||||
return _server->requestAuthentication();
|
||||
}
|
||||
_server->sendHeader("Content-Encoding", "gzip");
|
||||
|
@ -35,10 +39,19 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
|
||||
#if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
|
||||
_server->on("/ota/start", HTTP_GET, [&](AsyncWebServerRequest *request) {
|
||||
if (_authenticate && !request->authenticate(_username, _password)) {
|
||||
if (_authenticate && !request->authenticate(_username.c_str(), _password.c_str())) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
|
||||
// Pre-OTA update callback
|
||||
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
|
||||
OTA_Mode mode = OTA_MODE_FIRMWARE;
|
||||
// Get mode from arg
|
||||
|
@ -69,7 +82,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
#endif
|
||||
|
||||
// Pre-OTA update callback
|
||||
if (preUpdateCallback != NULL) preUpdateCallback();
|
||||
//if (preUpdateCallback != NULL) preUpdateCallback();
|
||||
|
||||
// Start update process
|
||||
#if defined(ESP8266)
|
||||
|
@ -84,7 +97,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
StreamString str;
|
||||
Update.printError(str);
|
||||
_update_error_str = str.c_str();
|
||||
_update_error_str += "\n";
|
||||
_update_error_str.concat("\n");
|
||||
ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
|
@ -94,7 +107,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
StreamString str;
|
||||
Update.printError(str);
|
||||
_update_error_str = str.c_str();
|
||||
_update_error_str += "\n";
|
||||
_update_error_str.concat("\n");
|
||||
ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
|
||||
}
|
||||
#endif
|
||||
|
@ -103,7 +116,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
});
|
||||
#else
|
||||
_server->on("/ota/start", HTTP_GET, [&]() {
|
||||
if (_authenticate && !_server->authenticate(_username, _password)) {
|
||||
if (_authenticate && !_server->authenticate(_username.c_str(), _password.c_str())) {
|
||||
return _server->requestAuthentication();
|
||||
}
|
||||
|
||||
|
@ -152,7 +165,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
StreamString str;
|
||||
Update.printError(str);
|
||||
_update_error_str = str.c_str();
|
||||
_update_error_str += "\n";
|
||||
_update_error_str.concat("\n");
|
||||
ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
|
@ -162,7 +175,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
StreamString str;
|
||||
Update.printError(str);
|
||||
_update_error_str = str.c_str();
|
||||
_update_error_str += "\n";
|
||||
_update_error_str.concat("\n");
|
||||
ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
|
||||
}
|
||||
#elif defined(TARGET_RP2040)
|
||||
|
@ -191,7 +204,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
|
||||
#if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
|
||||
_server->on("/ota/upload", HTTP_POST, [&](AsyncWebServerRequest *request) {
|
||||
if(_authenticate && !request->authenticate(_username, _password)){
|
||||
if(_authenticate && !request->authenticate(_username.c_str(), _password.c_str())){
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
// Post-OTA update callback
|
||||
|
@ -210,7 +223,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
}, [&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
//Upload handler chunks in data
|
||||
if(_authenticate){
|
||||
if(!request->authenticate(_username, _password)){
|
||||
if(!request->authenticate(_username.c_str(), _password.c_str())){
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
}
|
||||
|
@ -236,7 +249,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
StreamString str;
|
||||
Update.printError(str);
|
||||
_update_error_str = str.c_str();
|
||||
_update_error_str += "\n";
|
||||
_update_error_str.concat("\n");
|
||||
ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
|
||||
}
|
||||
}else{
|
||||
|
@ -245,7 +258,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
});
|
||||
#else
|
||||
_server->on("/ota/upload", HTTP_POST, [&](){
|
||||
if (_authenticate && !_server->authenticate(_username, _password)) {
|
||||
if (_authenticate && !_server->authenticate(_username.c_str(), _password.c_str())) {
|
||||
return _server->requestAuthentication();
|
||||
}
|
||||
// Post-OTA update callback
|
||||
|
@ -264,7 +277,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
HTTPUpload& upload = _server->upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
// Check authentication
|
||||
if (_authenticate && !_server->authenticate(_username, _password)) {
|
||||
if (_authenticate && !_server->authenticate(_username.c_str(), _password.c_str())) {
|
||||
ELEGANTOTA_DEBUG_MSG("Authentication Failed on UPLOAD_FILE_START\n");
|
||||
return;
|
||||
}
|
||||
|
@ -289,7 +302,7 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
StreamString str;
|
||||
Update.printError(str);
|
||||
_update_error_str = str.c_str();
|
||||
_update_error_str += "\n";
|
||||
_update_error_str.concat("\n");
|
||||
ELEGANTOTA_DEBUG_MSG(_update_error_str.c_str());
|
||||
}
|
||||
|
||||
|
@ -304,11 +317,9 @@ void ElegantOTAClass::begin(ELEGANTOTA_WEBSERVER *server, const char * username,
|
|||
}
|
||||
|
||||
void ElegantOTAClass::setAuth(const char * username, const char * password){
|
||||
if (strlen(username) > 0 && strlen(password) > 0) {
|
||||
strlcpy(_username, username, sizeof(_username));
|
||||
strlcpy(_password, password, sizeof(_password));
|
||||
_authenticate = true;
|
||||
}
|
||||
_username = username;
|
||||
_password = password;
|
||||
_authenticate = _username.length() && _password.length();
|
||||
}
|
||||
|
||||
void ElegantOTAClass::clearAuth(){
|
||||
|
|
|
@ -119,8 +119,8 @@ class ElegantOTAClass{
|
|||
ELEGANTOTA_WEBSERVER *_server;
|
||||
|
||||
bool _authenticate;
|
||||
char _username[64];
|
||||
char _password[64];
|
||||
String _username;
|
||||
String _password;
|
||||
|
||||
bool _auto_reboot = true;
|
||||
bool _reboot = false;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,6 +3,6 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
|
||||
extern const uint8_t ELEGANT_HTML[9590];
|
||||
extern const uint8_t ELEGANT_HTML[40500];
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue