mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 10:49:42 +02:00
Merge branch 'main' into feature/ioniq-28kWh
This commit is contained in:
commit
2d56ab8916
62 changed files with 1370 additions and 1069 deletions
|
@ -116,6 +116,8 @@ const char* name_for_battery_type(BatteryType type) {
|
|||
return RenaultZoeGen1Battery::Name;
|
||||
case BatteryType::RenaultZoe2:
|
||||
return RenaultZoeGen2Battery::Name;
|
||||
case BatteryType::SamsungSdiLv:
|
||||
return SamsungSdiLVBattery::Name;
|
||||
case BatteryType::SantaFePhev:
|
||||
return SantaFePhevBattery::Name;
|
||||
case BatteryType::SimpBms:
|
||||
|
@ -215,6 +217,8 @@ Battery* create_battery(BatteryType type) {
|
|||
return new RenaultZoeGen1Battery();
|
||||
case BatteryType::RenaultZoe2:
|
||||
return new RenaultZoeGen2Battery();
|
||||
case BatteryType::SamsungSdiLv:
|
||||
return new SamsungSdiLVBattery();
|
||||
case BatteryType::SantaFePhev:
|
||||
return new SantaFePhevBattery();
|
||||
case BatteryType::SimpBms:
|
||||
|
|
|
@ -44,6 +44,7 @@ void setup_can_shunt();
|
|||
#include "RENAULT-ZOE-GEN1-BATTERY.h"
|
||||
#include "RENAULT-ZOE-GEN2-BATTERY.h"
|
||||
#include "RJXZS-BMS.h"
|
||||
#include "SAMSUNG-SDI-LV-BATTERY.h"
|
||||
#include "SANTA-FE-PHEV-BATTERY.h"
|
||||
#include "SIMPBMS-BATTERY.h"
|
||||
#include "SONO-BATTERY.h"
|
||||
|
|
|
@ -43,7 +43,8 @@ enum class BatteryType {
|
|||
VolvoSpa = 35,
|
||||
VolvoSpaHybrid = 36,
|
||||
MgHsPhev = 37,
|
||||
HyundaiIoniq28 = 38,
|
||||
SamsungSdiLv = 38,
|
||||
HyundaiIoniq28 = 39,
|
||||
Highest
|
||||
};
|
||||
|
||||
|
|
|
@ -123,6 +123,7 @@ void EcmpBattery::update_values() {
|
|||
datalayer_extended.stellantisECMP.pid_time_spent_over_55c = pid_time_spent_over_55c;
|
||||
datalayer_extended.stellantisECMP.pid_contactor_closing_counter = pid_contactor_closing_counter;
|
||||
datalayer_extended.stellantisECMP.pid_date_of_manufacture = pid_date_of_manufacture;
|
||||
datalayer_extended.stellantisECMP.pid_SOH_cell_1 = pid_SOH_cell_1;
|
||||
|
||||
if (battery_InterlockOpen) {
|
||||
set_event(EVENT_HVIL_FAILURE, 0);
|
||||
|
@ -135,6 +136,12 @@ void EcmpBattery::update_values() {
|
|||
} else {
|
||||
clear_event(EVENT_12V_LOW);
|
||||
}
|
||||
|
||||
if (pid_reason_open == 7) { //Invalid status
|
||||
set_event(EVENT_CONTACTOR_OPEN, 0);
|
||||
} else {
|
||||
clear_event(EVENT_CONTACTOR_OPEN);
|
||||
}
|
||||
}
|
||||
|
||||
void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||
|
@ -578,8 +585,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
(rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
|
||||
break;
|
||||
case PID_ENERGY_CAPACITY:
|
||||
pid_energy_capacity = ((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) |
|
||||
(rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
|
||||
pid_energy_capacity = (rx_frame.data.u8[4] << 16) | (rx_frame.data.u8[5] << 8) | (rx_frame.data.u8[6]);
|
||||
break;
|
||||
case PID_HIGH_CELL_NUM:
|
||||
pid_highest_cell_voltage_num = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||
|
@ -720,6 +726,33 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
|
||||
switch (incoming_poll) //Multiframe responses
|
||||
{
|
||||
case PID_ALL_CELL_SOH:
|
||||
switch (rx_frame.data.u8[0]) {
|
||||
case 0x10:
|
||||
pid_SOH_cell_1 = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
|
||||
break;
|
||||
case 0x21:
|
||||
break;
|
||||
case 0x22:
|
||||
break;
|
||||
case 0x23:
|
||||
break;
|
||||
case 0x24:
|
||||
break;
|
||||
case 0x25:
|
||||
break;
|
||||
case 0x26:
|
||||
break;
|
||||
case 0x27:
|
||||
break;
|
||||
case 0x28:
|
||||
break;
|
||||
case 0x29:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PID_ALL_CELL_VOLTAGES:
|
||||
switch (rx_frame.data.u8[0]) {
|
||||
case 0x10:
|
||||
|
@ -1291,6 +1324,11 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) {
|
|||
case PID_DATE_OF_MANUFACTURE:
|
||||
ECMP_POLL.data.u8[2] = (uint8_t)((PID_DATE_OF_MANUFACTURE & 0xFF00) >> 8);
|
||||
ECMP_POLL.data.u8[3] = (uint8_t)(PID_DATE_OF_MANUFACTURE & 0x00FF);
|
||||
poll_state = PID_ALL_CELL_SOH;
|
||||
break;
|
||||
case PID_ALL_CELL_SOH:
|
||||
ECMP_POLL.data.u8[2] = (uint8_t)((PID_ALL_CELL_SOH & 0xFF00) >> 8);
|
||||
ECMP_POLL.data.u8[3] = (uint8_t)(PID_ALL_CELL_SOH & 0x00FF);
|
||||
poll_state = PID_WELD_CHECK; // Loop back to beginning
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -136,6 +136,7 @@ class EcmpBattery : public CanBattery {
|
|||
uint32_t pid_time_spent_over_55c = NOT_SAMPLED_YET;
|
||||
uint32_t pid_contactor_closing_counter = NOT_SAMPLED_YET;
|
||||
uint32_t pid_date_of_manufacture = NOT_SAMPLED_YET;
|
||||
uint16_t pid_SOH_cell_1 = NOT_SAMPLED_YET;
|
||||
|
||||
unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was sent
|
||||
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was sent
|
||||
|
@ -197,7 +198,7 @@ class EcmpBattery : public CanBattery {
|
|||
static const uint16_t PID_SW_VERSION_NUM = 0xF195; //Not supported on all batteris
|
||||
static const uint16_t PID_FACTORY_MODE_CONTROL = 0xD900;
|
||||
static const uint16_t PID_BATTERY_SERIAL = 0xD901;
|
||||
static const uint16_t PID_ALL_CELL_SOH = 0xD4B5; //Very long message reply, too much data for this integration
|
||||
static const uint16_t PID_ALL_CELL_SOH = 0xD4B5;
|
||||
static const uint16_t PID_AUX_FUSE_STATE = 0xD86C;
|
||||
static const uint16_t PID_BATTERY_STATE = 0xD811;
|
||||
static const uint16_t PID_PRECHARGE_SHORT_CIRCUIT = 0xD4D8;
|
||||
|
|
|
@ -376,6 +376,11 @@ class EcmpHtmlRenderer : public BatteryHtmlRenderer {
|
|||
? "N/A"
|
||||
: String(datalayer_extended.stellantisECMP.pid_contactor_closing_counter)) +
|
||||
" cycles</h4>";
|
||||
content += "<h4>State of Health Cell-1: " +
|
||||
(datalayer_extended.stellantisECMP.pid_SOH_cell_1 == 255
|
||||
? "N/A"
|
||||
: String(datalayer_extended.stellantisECMP.pid_SOH_cell_1)) +
|
||||
"</h4>";
|
||||
|
||||
return content;
|
||||
}
|
||||
|
|
|
@ -48,66 +48,19 @@ void KiaHyundai64Battery::
|
|||
datalayer_battery_extended->batteryManagementMode = batteryManagementMode;
|
||||
datalayer_battery_extended->BMS_ign = BMS_ign;
|
||||
datalayer_battery_extended->batteryRelay = batteryRelay;
|
||||
|
||||
//Perform logging if configured to do so
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println(); //sepatator
|
||||
logging.println("Values from battery: ");
|
||||
logging.print("SOC BMS: ");
|
||||
logging.print((uint16_t)SOC_BMS / 10.0, 1);
|
||||
logging.print("% | SOC Display: ");
|
||||
logging.print((uint16_t)SOC_Display / 10.0, 1);
|
||||
logging.print("% | SOH ");
|
||||
logging.print((uint16_t)batterySOH / 10.0, 1);
|
||||
logging.println("%");
|
||||
logging.print((int16_t)batteryAmps / 10.0, 1);
|
||||
logging.print(" Amps | ");
|
||||
logging.print((uint16_t)batteryVoltage / 10.0, 1);
|
||||
logging.print(" Volts | ");
|
||||
logging.print((int16_t)datalayer_battery->status.active_power_W);
|
||||
logging.println(" Watts");
|
||||
logging.print("Allowed Charge ");
|
||||
logging.print((uint16_t)allowedChargePower * 10);
|
||||
logging.print(" W | Allowed Discharge ");
|
||||
logging.print((uint16_t)allowedDischargePower * 10);
|
||||
logging.println(" W");
|
||||
logging.print("MaxCellVolt ");
|
||||
logging.print(CellVoltMax_mV);
|
||||
logging.print(" mV No ");
|
||||
logging.print(CellVmaxNo);
|
||||
logging.print(" | MinCellVolt ");
|
||||
logging.print(CellVoltMin_mV);
|
||||
logging.print(" mV No ");
|
||||
logging.println(CellVminNo);
|
||||
logging.print("TempHi ");
|
||||
logging.print((int16_t)temperatureMax);
|
||||
logging.print("°C TempLo ");
|
||||
logging.print((int16_t)temperatureMin);
|
||||
logging.print("°C WaterInlet ");
|
||||
logging.print((int8_t)temperature_water_inlet);
|
||||
logging.print("°C PowerRelay ");
|
||||
logging.print((int8_t)powerRelayTemperature * 2);
|
||||
logging.println("°C");
|
||||
logging.print("Aux12volt: ");
|
||||
logging.print((int16_t)leadAcidBatteryVoltage / 10.0, 1);
|
||||
logging.println("V | ");
|
||||
logging.print("BmsManagementMode ");
|
||||
logging.print((uint8_t)batteryManagementMode, BIN);
|
||||
if (bitRead((uint8_t)BMS_ign, 2) == 1) {
|
||||
logging.print(" | BmsIgnition ON");
|
||||
} else {
|
||||
logging.print(" | BmsIgnition OFF");
|
||||
}
|
||||
|
||||
if (bitRead((uint8_t)batteryRelay, 0) == 1) {
|
||||
logging.print(" | PowerRelay ON");
|
||||
} else {
|
||||
logging.print(" | PowerRelay OFF");
|
||||
}
|
||||
logging.print(" | Inverter ");
|
||||
logging.print(inverterVoltage);
|
||||
logging.println(" Volts");
|
||||
#endif
|
||||
datalayer_battery_extended->inverterVoltage = inverterVoltage;
|
||||
memcpy(datalayer_battery_extended->ecu_serial_number, ecu_serial_number, sizeof(ecu_serial_number));
|
||||
memcpy(datalayer_battery_extended->ecu_version_number, ecu_version_number, sizeof(ecu_version_number));
|
||||
datalayer_battery_extended->cumulative_charge_current_ah = cumulative_charge_current_ah;
|
||||
datalayer_battery_extended->cumulative_discharge_current_ah = cumulative_discharge_current_ah;
|
||||
datalayer_battery_extended->cumulative_energy_charged_kWh = cumulative_energy_charged_kWh;
|
||||
datalayer_battery_extended->cumulative_energy_discharged_kWh = cumulative_energy_discharged_kWh;
|
||||
datalayer_battery_extended->powered_on_total_time = powered_on_total_time;
|
||||
datalayer_battery_extended->isolation_resistance_kOhm = isolation_resistance_kOhm;
|
||||
datalayer_battery_extended->number_of_standard_charging_sessions = number_of_standard_charging_sessions;
|
||||
datalayer_battery_extended->number_of_fastcharging_sessions = number_of_fastcharging_sessions;
|
||||
datalayer_battery_extended->accumulated_normal_charging_energy_kWh = accumulated_normal_charging_energy_kWh;
|
||||
datalayer_battery_extended->accumulated_fastcharging_energy_kWh = accumulated_fastcharging_energy_kWh;
|
||||
}
|
||||
|
||||
void KiaHyundai64Battery::update_number_of_cells() {
|
||||
|
@ -129,6 +82,7 @@ void KiaHyundai64Battery::update_number_of_cells() {
|
|||
void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||
switch (rx_frame.ID) {
|
||||
case 0x4DE:
|
||||
datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
startedUp = true;
|
||||
break;
|
||||
case 0x542: //BMS SOC
|
||||
|
@ -168,65 +122,118 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
case 0x5D8:
|
||||
startedUp = true;
|
||||
|
||||
//PID data is polled after last message sent from battery every other time:
|
||||
//PID data is polled after last message sent from battery every other time this 0x5D8 message arrives:
|
||||
if (holdPidCounter == true) {
|
||||
holdPidCounter = false;
|
||||
} else {
|
||||
holdPidCounter = true;
|
||||
if (poll_data_pid >= 6) { //polling one of six PIDs at 100ms*2, resolution = 1200ms
|
||||
poll_data_pid = 0;
|
||||
}
|
||||
|
||||
poll_data_pid++;
|
||||
if (poll_data_pid == 1) {
|
||||
transmit_can_frame(&KIA64_7E4_id1);
|
||||
KIA64_7E4_poll.data.u8[2] = (uint8_t)((POLL_GROUP_1 & 0xFF00) >> 8);
|
||||
KIA64_7E4_poll.data.u8[3] = (uint8_t)(POLL_GROUP_1 & 0x00FF);
|
||||
} else if (poll_data_pid == 2) {
|
||||
transmit_can_frame(&KIA64_7E4_id2);
|
||||
KIA64_7E4_poll.data.u8[2] = (uint8_t)((POLL_GROUP_2 & 0xFF00) >> 8);
|
||||
KIA64_7E4_poll.data.u8[3] = (uint8_t)(POLL_GROUP_2 & 0x00FF);
|
||||
} else if (poll_data_pid == 3) {
|
||||
transmit_can_frame(&KIA64_7E4_id3);
|
||||
KIA64_7E4_poll.data.u8[2] = (uint8_t)((POLL_GROUP_3 & 0xFF00) >> 8);
|
||||
KIA64_7E4_poll.data.u8[3] = (uint8_t)(POLL_GROUP_3 & 0x00FF);
|
||||
} else if (poll_data_pid == 4) {
|
||||
transmit_can_frame(&KIA64_7E4_id4);
|
||||
KIA64_7E4_poll.data.u8[2] = (uint8_t)((POLL_GROUP_4 & 0xFF00) >> 8);
|
||||
KIA64_7E4_poll.data.u8[3] = (uint8_t)(POLL_GROUP_4 & 0x00FF);
|
||||
} else if (poll_data_pid == 5) {
|
||||
transmit_can_frame(&KIA64_7E4_id5);
|
||||
KIA64_7E4_poll.data.u8[2] = (uint8_t)((POLL_GROUP_5 & 0xFF00) >> 8);
|
||||
KIA64_7E4_poll.data.u8[3] = (uint8_t)(POLL_GROUP_5 & 0x00FF);
|
||||
} else if (poll_data_pid == 6) {
|
||||
transmit_can_frame(&KIA64_7E4_id6);
|
||||
KIA64_7E4_poll.data.u8[2] = (uint8_t)((POLL_GROUP_6 & 0xFF00) >> 8);
|
||||
KIA64_7E4_poll.data.u8[3] = (uint8_t)(POLL_GROUP_6 & 0x00FF);
|
||||
} else if (poll_data_pid == 7) {
|
||||
KIA64_7E4_poll.data.u8[2] = (uint8_t)((POLL_GROUP_11 & 0xFF00) >> 8);
|
||||
KIA64_7E4_poll.data.u8[3] = (uint8_t)(POLL_GROUP_11 & 0x00FF);
|
||||
} else if (poll_data_pid == 8) {
|
||||
KIA64_7E4_poll.data.u8[2] = (uint8_t)((POLL_ECU_SERIAL & 0xFF00) >> 8);
|
||||
KIA64_7E4_poll.data.u8[3] = (uint8_t)(POLL_ECU_SERIAL & 0x00FF);
|
||||
} else if (poll_data_pid == 9) {
|
||||
KIA64_7E4_poll.data.u8[2] = (uint8_t)((POLL_ECU_VERSION & 0xFF00) >> 8);
|
||||
KIA64_7E4_poll.data.u8[3] = (uint8_t)(POLL_ECU_VERSION & 0x00FF);
|
||||
poll_data_pid = 0;
|
||||
}
|
||||
transmit_can_frame(&KIA64_7E4_poll);
|
||||
}
|
||||
break;
|
||||
case 0x7EC: //Data From polled PID group, BigEndian
|
||||
switch (rx_frame.data.u8[0]) {
|
||||
case 0x10: //"PID Header"
|
||||
if (rx_frame.data.u8[4] == poll_data_pid) {
|
||||
transmit_can_frame(&KIA64_7E4_ack); //Send ack to BMS if the same frame is sent as polled
|
||||
|
||||
if (rx_frame.data.u8[0] < 0x10) { //One line response
|
||||
pid_reply = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
|
||||
}
|
||||
|
||||
if (rx_frame.data.u8[0] == 0x10) { //Multiframe response, send ACK
|
||||
transmit_can_frame(&KIA64_7E4_ack);
|
||||
pid_reply = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
||||
}
|
||||
|
||||
switch (rx_frame.data.u8[0]) { //Multiframe responses
|
||||
case 0x10: //Header frame sometimes has data
|
||||
if (pid_reply == POLL_ECU_SERIAL) {
|
||||
ecu_serial_number[0] = rx_frame.data.u8[5];
|
||||
ecu_serial_number[1] = rx_frame.data.u8[6];
|
||||
ecu_serial_number[2] = rx_frame.data.u8[7];
|
||||
} else if (pid_reply == POLL_ECU_VERSION) {
|
||||
ecu_version_number[0] = rx_frame.data.u8[5];
|
||||
ecu_version_number[1] = rx_frame.data.u8[6];
|
||||
ecu_version_number[2] = rx_frame.data.u8[7];
|
||||
}
|
||||
break;
|
||||
case 0x21: //First frame in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
if (pid_reply == POLL_GROUP_1) {
|
||||
batteryRelay = rx_frame.data.u8[7];
|
||||
} else if (poll_data_pid == 2) {
|
||||
} else if (pid_reply == POLL_GROUP_2) {
|
||||
cellvoltages_mv[0] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[1] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[2] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[3] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[4] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[5] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 3) {
|
||||
} else if (pid_reply == POLL_GROUP_3) {
|
||||
cellvoltages_mv[32] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[33] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[34] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[35] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[36] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[37] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 4) {
|
||||
} else if (pid_reply == POLL_GROUP_4) {
|
||||
cellvoltages_mv[64] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[65] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[66] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[67] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[68] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[69] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (pid_reply == POLL_GROUP_11) {
|
||||
number_of_standard_charging_sessions = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
} else if (pid_reply == POLL_ECU_SERIAL) {
|
||||
ecu_serial_number[3] = rx_frame.data.u8[1];
|
||||
ecu_serial_number[4] = rx_frame.data.u8[2];
|
||||
ecu_serial_number[5] = rx_frame.data.u8[3];
|
||||
ecu_serial_number[6] = rx_frame.data.u8[4];
|
||||
ecu_serial_number[7] = rx_frame.data.u8[5];
|
||||
ecu_serial_number[8] = rx_frame.data.u8[6];
|
||||
ecu_serial_number[9] = rx_frame.data.u8[7];
|
||||
} else if (pid_reply == POLL_ECU_VERSION) {
|
||||
ecu_version_number[3] = rx_frame.data.u8[1];
|
||||
ecu_version_number[4] = rx_frame.data.u8[2];
|
||||
ecu_version_number[5] = rx_frame.data.u8[3];
|
||||
ecu_version_number[6] = rx_frame.data.u8[4];
|
||||
ecu_version_number[7] = rx_frame.data.u8[5];
|
||||
ecu_version_number[8] = rx_frame.data.u8[6];
|
||||
ecu_version_number[9] = rx_frame.data.u8[7];
|
||||
}
|
||||
break;
|
||||
case 0x22: //Second datarow in PID group
|
||||
if (poll_data_pid == 2) {
|
||||
if (pid_reply == POLL_GROUP_1) {
|
||||
//battery_max_temperature = rx_frame.data.u8[5];
|
||||
//battery_min_temperature = rx_frame.data.u8[6];
|
||||
//module_1_temperature = rx_frame.data.u8[7];
|
||||
} else if (pid_reply == POLL_GROUP_2) {
|
||||
cellvoltages_mv[6] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[7] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[8] = (rx_frame.data.u8[3] * 20);
|
||||
|
@ -234,7 +241,7 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
cellvoltages_mv[10] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[11] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[12] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 3) {
|
||||
} else if (pid_reply == POLL_GROUP_3) {
|
||||
cellvoltages_mv[38] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[39] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[40] = (rx_frame.data.u8[3] * 20);
|
||||
|
@ -242,7 +249,7 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
cellvoltages_mv[42] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[43] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[44] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 4) {
|
||||
} else if (pid_reply == POLL_GROUP_4) {
|
||||
cellvoltages_mv[70] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[71] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[72] = (rx_frame.data.u8[3] * 20);
|
||||
|
@ -250,15 +257,35 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
cellvoltages_mv[74] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[75] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[76] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 6) {
|
||||
} else if (pid_reply == POLL_GROUP_6) {
|
||||
batteryManagementMode = rx_frame.data.u8[5];
|
||||
} else if (pid_reply == POLL_GROUP_11) {
|
||||
number_of_fastcharging_sessions = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
||||
accumulated_normal_charging_energy_kWh = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
|
||||
} else if (pid_reply == POLL_ECU_SERIAL) {
|
||||
ecu_serial_number[10] = rx_frame.data.u8[1];
|
||||
ecu_serial_number[11] = rx_frame.data.u8[2];
|
||||
ecu_serial_number[12] = rx_frame.data.u8[3];
|
||||
ecu_serial_number[13] = rx_frame.data.u8[4];
|
||||
ecu_serial_number[14] = rx_frame.data.u8[5];
|
||||
ecu_serial_number[15] = rx_frame.data.u8[6];
|
||||
} else if (pid_reply == POLL_ECU_VERSION) {
|
||||
ecu_version_number[10] = rx_frame.data.u8[1];
|
||||
ecu_version_number[11] = rx_frame.data.u8[2];
|
||||
ecu_version_number[12] = rx_frame.data.u8[3];
|
||||
ecu_version_number[13] = rx_frame.data.u8[4];
|
||||
ecu_version_number[14] = rx_frame.data.u8[5];
|
||||
ecu_version_number[15] = rx_frame.data.u8[6];
|
||||
}
|
||||
break;
|
||||
case 0x23: //Third datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
if (pid_reply == POLL_GROUP_1) {
|
||||
//module_2_temperature = rx_frame.data.u8[1];
|
||||
//module_3_temperature = rx_frame.data.u8[2];
|
||||
//module_4_temperature = rx_frame.data.u8[3];
|
||||
temperature_water_inlet = rx_frame.data.u8[6];
|
||||
CellVoltMax_mV = (rx_frame.data.u8[7] * 20); //(volts *50) *20 =mV
|
||||
} else if (poll_data_pid == 2) {
|
||||
} else if (pid_reply == POLL_GROUP_2) {
|
||||
cellvoltages_mv[13] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[14] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[15] = (rx_frame.data.u8[3] * 20);
|
||||
|
@ -266,7 +293,7 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
cellvoltages_mv[17] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[18] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[19] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 3) {
|
||||
} else if (pid_reply == POLL_GROUP_3) {
|
||||
cellvoltages_mv[45] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[46] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[47] = (rx_frame.data.u8[3] * 20);
|
||||
|
@ -274,7 +301,7 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
cellvoltages_mv[49] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[50] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[51] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 4) {
|
||||
} else if (pid_reply == POLL_GROUP_4) {
|
||||
cellvoltages_mv[77] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[78] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[79] = (rx_frame.data.u8[3] * 20);
|
||||
|
@ -282,16 +309,18 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
cellvoltages_mv[81] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[82] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[83] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 5) {
|
||||
} else if (pid_reply == POLL_GROUP_5) {
|
||||
heatertemp = rx_frame.data.u8[7];
|
||||
} else if (pid_reply == POLL_GROUP_11) {
|
||||
accumulated_fastcharging_energy_kWh = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
}
|
||||
break;
|
||||
case 0x24: //Fourth datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
if (pid_reply == POLL_GROUP_1) {
|
||||
CellVmaxNo = rx_frame.data.u8[1];
|
||||
CellVminNo = rx_frame.data.u8[3];
|
||||
CellVoltMin_mV = (rx_frame.data.u8[2] * 20); //(volts *50) *20 =mV
|
||||
} else if (poll_data_pid == 2) {
|
||||
} else if (pid_reply == POLL_GROUP_2) {
|
||||
cellvoltages_mv[20] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[21] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[22] = (rx_frame.data.u8[3] * 20);
|
||||
|
@ -299,7 +328,7 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
cellvoltages_mv[24] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[25] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[26] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 3) {
|
||||
} else if (pid_reply == POLL_GROUP_3) {
|
||||
cellvoltages_mv[52] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[53] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[54] = (rx_frame.data.u8[3] * 20);
|
||||
|
@ -307,7 +336,7 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
cellvoltages_mv[56] = (rx_frame.data.u8[5] * 20);
|
||||
cellvoltages_mv[57] = (rx_frame.data.u8[6] * 20);
|
||||
cellvoltages_mv[58] = (rx_frame.data.u8[7] * 20);
|
||||
} else if (poll_data_pid == 4) {
|
||||
} else if (pid_reply == POLL_GROUP_4) {
|
||||
cellvoltages_mv[84] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[85] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[86] = (rx_frame.data.u8[3] * 20);
|
||||
|
@ -317,25 +346,30 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
if (rx_frame.data.u8[7] > 4) { // Data only valid on 98S
|
||||
cellvoltages_mv[90] = (rx_frame.data.u8[7] * 20); // Perform extra checks
|
||||
}
|
||||
} else if (poll_data_pid == 5) {
|
||||
} else if (pid_reply == POLL_GROUP_5) {
|
||||
batterySOH = ((rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]);
|
||||
}
|
||||
break;
|
||||
case 0x25: //Fifth datarow in PID group
|
||||
if (poll_data_pid == 2) {
|
||||
if (pid_reply == POLL_GROUP_1) {
|
||||
cumulative_charge_current_ah =
|
||||
((rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
cumulative_discharge_current_ah =
|
||||
((rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
|
||||
} else if (pid_reply == POLL_GROUP_2) {
|
||||
cellvoltages_mv[27] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[28] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[29] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[30] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[31] = (rx_frame.data.u8[5] * 20);
|
||||
} else if (poll_data_pid == 3) {
|
||||
} else if (pid_reply == POLL_GROUP_3) {
|
||||
cellvoltages_mv[59] = (rx_frame.data.u8[1] * 20);
|
||||
cellvoltages_mv[60] = (rx_frame.data.u8[2] * 20);
|
||||
cellvoltages_mv[61] = (rx_frame.data.u8[3] * 20);
|
||||
cellvoltages_mv[62] = (rx_frame.data.u8[4] * 20);
|
||||
cellvoltages_mv[63] = (rx_frame.data.u8[5] * 20);
|
||||
} else if (poll_data_pid == 4) { // Data only valid on 98S
|
||||
if (rx_frame.data.u8[1] > 4) { // Perform extra checks
|
||||
} else if (pid_reply == POLL_GROUP_4) { // Data only valid on 98S
|
||||
if (rx_frame.data.u8[1] > 4) { // Perform extra checks
|
||||
cellvoltages_mv[91] = (rx_frame.data.u8[1] * 20);
|
||||
}
|
||||
if (rx_frame.data.u8[2] > 4) { // Perform extra checks
|
||||
|
@ -350,8 +384,8 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
if (rx_frame.data.u8[5] > 4) { // Perform extra checks
|
||||
cellvoltages_mv[95] = (rx_frame.data.u8[5] * 20);
|
||||
}
|
||||
} else if (poll_data_pid == 5) { // Data only valid on 98S
|
||||
if (rx_frame.data.u8[4] > 4) { // Perform extra checks
|
||||
} else if (pid_reply == POLL_GROUP_5) { // Data only valid on 98S
|
||||
if (rx_frame.data.u8[4] > 4) { // Perform extra checks
|
||||
cellvoltages_mv[96] = (rx_frame.data.u8[4] * 20);
|
||||
}
|
||||
if (rx_frame.data.u8[5] > 4) { // Perform extra checks
|
||||
|
@ -360,7 +394,11 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
}
|
||||
break;
|
||||
case 0x26: //Sixth datarow in PID group
|
||||
if (poll_data_pid == 5) {
|
||||
if (pid_reply == POLL_GROUP_1) {
|
||||
cumulative_energy_charged_kWh =
|
||||
((rx_frame.data.u8[2] << 16) | (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
|
||||
cumulative_energy_discharged_HIGH_BYTE = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
|
||||
} else if (pid_reply == POLL_GROUP_5) {
|
||||
//We have read all cells, check that content is valid:
|
||||
for (uint8_t i = 85; i < 97; ++i) {
|
||||
if (cellvoltages_mv[i] < 300) { // Zero the value if it's below 300
|
||||
|
@ -374,17 +412,24 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
}
|
||||
break;
|
||||
case 0x27: //Seventh datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
if (pid_reply == POLL_GROUP_1) {
|
||||
cumulative_energy_discharged_kWh = ((cumulative_energy_discharged_HIGH_BYTE << 8) | rx_frame.data.u8[1]);
|
||||
powered_on_total_time = ((rx_frame.data.u8[2] << 24) | (rx_frame.data.u8[3] << 16) |
|
||||
(rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
BMS_ign = rx_frame.data.u8[6];
|
||||
inverterVoltageFrameHigh = rx_frame.data.u8[7];
|
||||
}
|
||||
break;
|
||||
case 0x28: //Eighth datarow in PID group
|
||||
if (poll_data_pid == 1) {
|
||||
if (pid_reply == POLL_GROUP_1) {
|
||||
inverterVoltage = (inverterVoltageFrameHigh << 8) + rx_frame.data.u8[1];
|
||||
isolation_resistance_kOhm = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -401,7 +446,8 @@ void KiaHyundai64Battery::transmit_can(unsigned long currentMillis) {
|
|||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
if (contactor_closing_allowed == nullptr || *contactor_closing_allowed) {
|
||||
if ((contactor_closing_allowed == nullptr || *contactor_closing_allowed) &&
|
||||
datalayer.system.status.inverter_allows_contactor_closing) {
|
||||
transmit_can_frame(&KIA64_553);
|
||||
transmit_can_frame(&KIA64_57F);
|
||||
transmit_can_frame(&KIA64_2A1);
|
||||
|
@ -412,7 +458,8 @@ void KiaHyundai64Battery::transmit_can(unsigned long currentMillis) {
|
|||
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
|
||||
previousMillis10 = currentMillis;
|
||||
|
||||
if (contactor_closing_allowed == nullptr || *contactor_closing_allowed) {
|
||||
if ((contactor_closing_allowed == nullptr || *contactor_closing_allowed) &&
|
||||
datalayer.system.status.inverter_allows_contactor_closing) {
|
||||
|
||||
switch (counter_200) {
|
||||
case 0:
|
||||
|
|
|
@ -79,7 +79,8 @@ class KiaHyundai64Battery : public CanBattery {
|
|||
int16_t batteryAmps = 0;
|
||||
int16_t temperatureMax = 0;
|
||||
int16_t temperatureMin = 0;
|
||||
int16_t poll_data_pid = 0;
|
||||
uint8_t poll_data_pid = 0;
|
||||
uint16_t pid_reply = 0;
|
||||
bool holdPidCounter = false;
|
||||
uint8_t CellVmaxNo = 0;
|
||||
uint8_t CellVminNo = 0;
|
||||
|
@ -92,6 +93,19 @@ class KiaHyundai64Battery : public CanBattery {
|
|||
int8_t heatertemp = 0;
|
||||
int8_t powerRelayTemperature = 0;
|
||||
bool startedUp = false;
|
||||
uint8_t ecu_serial_number[16] = {0};
|
||||
uint8_t ecu_version_number[16] = {0};
|
||||
uint32_t cumulative_charge_current_ah = 0;
|
||||
uint32_t cumulative_discharge_current_ah = 0;
|
||||
uint32_t cumulative_energy_charged_kWh = 0;
|
||||
uint16_t cumulative_energy_discharged_HIGH_BYTE = 0;
|
||||
uint32_t cumulative_energy_discharged_kWh = 0;
|
||||
uint32_t powered_on_total_time = 0;
|
||||
uint16_t isolation_resistance_kOhm = 0;
|
||||
uint16_t number_of_standard_charging_sessions = 0;
|
||||
uint16_t number_of_fastcharging_sessions = 0;
|
||||
uint16_t accumulated_normal_charging_energy_kWh = 0;
|
||||
uint16_t accumulated_fastcharging_energy_kWh = 0;
|
||||
|
||||
CAN_frame KIA_HYUNDAI_200 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
|
@ -126,42 +140,26 @@ class KiaHyundai64Battery : public CanBattery {
|
|||
.DLC = 8,
|
||||
.ID = 0x2A1,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame KIA64_7E4_id1 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x7E4,
|
||||
.data = {0x03, 0x22, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 01
|
||||
CAN_frame KIA64_7E4_id2 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x7E4,
|
||||
.data = {0x03, 0x22, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 02
|
||||
CAN_frame KIA64_7E4_id3 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x7E4,
|
||||
.data = {0x03, 0x22, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 03
|
||||
CAN_frame KIA64_7E4_id4 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x7E4,
|
||||
.data = {0x03, 0x22, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 04
|
||||
CAN_frame KIA64_7E4_id5 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x7E4,
|
||||
.data = {0x03, 0x22, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 05
|
||||
CAN_frame KIA64_7E4_id6 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x7E4,
|
||||
.data = {0x03, 0x22, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00}}; //Poll PID 03 22 01 06
|
||||
CAN_frame KIA64_7E4_poll = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x7E4,
|
||||
.data = {0x03, 0x22, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame KIA64_7E4_ack = {
|
||||
.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x7E4,
|
||||
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Ack frame, correct PID is returned
|
||||
.data = {0x30, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Ack frame, correct PID is returned
|
||||
static const int POLL_GROUP_1 = 0x0101;
|
||||
static const int POLL_GROUP_2 = 0x0102;
|
||||
static const int POLL_GROUP_3 = 0x0103;
|
||||
static const int POLL_GROUP_4 = 0x0104;
|
||||
static const int POLL_GROUP_5 = 0x0105;
|
||||
static const int POLL_GROUP_6 = 0x0106;
|
||||
static const int POLL_GROUP_11 = 0x0111;
|
||||
static const int POLL_ECU_SERIAL = 0xF18C;
|
||||
static const int POLL_ECU_VERSION = 0xF191;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -11,16 +11,45 @@ class KiaHyundai64HtmlRenderer : public BatteryHtmlRenderer {
|
|||
|
||||
String get_status_html() {
|
||||
String content;
|
||||
|
||||
auto print_hyundai = [&content](DATALAYER_INFO_KIAHYUNDAI64& data) {
|
||||
content += "<h4>Cells: " + String(data.total_cell_count) + "S</h4>";
|
||||
content += "<h4>12V voltage: " + String(data.battery_12V / 10.0, 1) + "</h4>";
|
||||
content += "<h4>Waterleakage: " + String(data.waterleakageSensor) + "</h4>";
|
||||
content += "<h4>Temperature, water inlet: " + String(data.temperature_water_inlet) + "</h4>";
|
||||
content += "<h4>Temperature, power relay: " + String(data.powerRelayTemperature) + "</h4>";
|
||||
char readableSerialNumber[17]; // One extra space for null terminator
|
||||
memcpy(readableSerialNumber, data.ecu_serial_number, sizeof(data.ecu_serial_number));
|
||||
readableSerialNumber[16] = '\0'; // Null terminate the string
|
||||
char readableVersionNumber[17]; // One extra space for null terminator
|
||||
memcpy(readableVersionNumber, data.ecu_version_number, sizeof(data.ecu_version_number));
|
||||
readableVersionNumber[16] = '\0'; // Null terminate the string
|
||||
|
||||
content += "<h4>BMS serial number: " + String(readableSerialNumber) + "</h4>";
|
||||
content += "<h4>BMS software version: " + String(readableVersionNumber) + "</h4>";
|
||||
content += "<h4>Cells: " + String(data.total_cell_count) + " S</h4>";
|
||||
content += "<h4>12V voltage: " + String(data.battery_12V / 10.0, 1) + " V</h4>";
|
||||
content += "<h4>Waterleakage: ";
|
||||
if (data.waterleakageSensor == 0) {
|
||||
content += " LEAK DETECTED</h4>";
|
||||
} else if (data.waterleakageSensor == 164) {
|
||||
content += " No leakage</h4>";
|
||||
} else {
|
||||
content += String(data.waterleakageSensor) + "</h4>";
|
||||
}
|
||||
content += "<h4>Temperature, water inlet: " + String(data.temperature_water_inlet) + " °C</h4>";
|
||||
content += "<h4>Temperature, power relay: " + String(data.powerRelayTemperature) + " °C</h4>";
|
||||
content += "<h4>Batterymanagement mode: " + String(data.batteryManagementMode) + "</h4>";
|
||||
content += "<h4>BMS ignition: " + String(data.BMS_ign) + "</h4>";
|
||||
content += "<h4>Battery relay: " + String(data.batteryRelay) + "</h4>";
|
||||
content += "<h4>Inverter voltage: " + String(data.inverterVoltage) + " V</h4>";
|
||||
content += "<h4>Isolation resistance: " + String(data.isolation_resistance_kOhm) + " kOhm</h4>";
|
||||
content += "<h4>Power on total time: " + String(data.powered_on_total_time) + " s</h4>";
|
||||
content += "<h4>Fastcharging sessions: " + String(data.number_of_fastcharging_sessions) + " x</h4>";
|
||||
content += "<h4>Slowcharging sessions: " + String(data.number_of_standard_charging_sessions) + " x</h4>";
|
||||
content +=
|
||||
"<h4>Normal charged energy amount: " + String(data.accumulated_normal_charging_energy_kWh) + " kWh</h4>";
|
||||
content += "<h4>Fastcharged energy amount: " + String(data.accumulated_fastcharging_energy_kWh) + " kWh</h4>";
|
||||
content += "<h4>Total amount charged energy: " + String(data.cumulative_energy_charged_kWh / 10.0) + " kWh</h4>";
|
||||
content +=
|
||||
"<h4>Total amount discharged energy: " + String(data.cumulative_energy_discharged_kWh / 10.0) + " kWh</h4>";
|
||||
content += "<h4>Cumulative charge current: " + String(data.cumulative_charge_current_ah / 10.0) + " Ah</h4>";
|
||||
content +=
|
||||
"<h4>Cumulative discharge current: " + String(data.cumulative_discharge_current_ah / 10.0) + " Ah</h4>";
|
||||
};
|
||||
|
||||
print_hyundai(*kia_datalayer);
|
||||
|
|
102
Software/src/battery/SAMSUNG-SDI-LV-BATTERY.cpp
Normal file
102
Software/src/battery/SAMSUNG-SDI-LV-BATTERY.cpp
Normal file
|
@ -0,0 +1,102 @@
|
|||
#include "SAMSUNG-SDI-LV-BATTERY.h"
|
||||
#include "../communication/can/comm_can.h"
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
|
||||
/*
|
||||
- Baud rate: 500kbps
|
||||
- Format: CAN2.0A 11 bit identifier
|
||||
- Data Length: 8byte
|
||||
- CAN data is transmitted with encoding in little endian – low byte first – unless stated otherwise.
|
||||
- Broadcasting period: 500ms
|
||||
|
||||
TODO: Implement the error bit handling for easier visualization if the battery stops operating
|
||||
- alarms_frame0
|
||||
- alarms_frame1
|
||||
- protection_frame2
|
||||
- protection_frame3
|
||||
|
||||
*/
|
||||
|
||||
void SamsungSdiLVBattery::update_values() {
|
||||
|
||||
datalayer.battery.status.real_soc = system_SOC * 100;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.soh_pptt = system_SOH * 100;
|
||||
|
||||
datalayer.battery.status.voltage_dV = system_voltage / 10;
|
||||
|
||||
datalayer.battery.status.current_dA = system_current * 10;
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = system_voltage * charge_current_limit;
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = system_voltage * discharge_current_limit;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = (int16_t)(minimum_cell_temperature * 10);
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = (int16_t)(maximum_cell_temperature * 10);
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = maximum_cell_voltage;
|
||||
|
||||
datalayer.battery.status.cell_min_voltage_mV = minimum_cell_voltage;
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = battery_charge_voltage;
|
||||
|
||||
datalayer.battery.info.min_design_voltage_dV = battery_discharge_voltage;
|
||||
}
|
||||
|
||||
void SamsungSdiLVBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
||||
switch (rx_frame.ID) {
|
||||
case 0x500: //Voltage, current, SOC, SOH
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
system_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
|
||||
system_current = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
system_SOC = rx_frame.data.u8[4];
|
||||
system_SOH = rx_frame.data.u8[5];
|
||||
break;
|
||||
case 0x501:
|
||||
alarms_frame0 = rx_frame.data.u8[0];
|
||||
alarms_frame1 = rx_frame.data.u8[1];
|
||||
protection_frame2 = rx_frame.data.u8[2];
|
||||
protection_frame3 = rx_frame.data.u8[3];
|
||||
break;
|
||||
case 0x502:
|
||||
battery_charge_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
|
||||
charge_current_limit = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
discharge_current_limit = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
battery_discharge_voltage = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
|
||||
break;
|
||||
case 0x503:
|
||||
maximum_cell_voltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
minimum_cell_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
break;
|
||||
case 0x504:
|
||||
maximum_cell_temperature = rx_frame.data.u8[5];
|
||||
minimum_cell_temperature = rx_frame.data.u8[6];
|
||||
break;
|
||||
case 0x505:
|
||||
system_permanent_failure_status_dry_contact = rx_frame.data.u8[2];
|
||||
system_permanent_failure_status_fuse_open = rx_frame.data.u8[3];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SamsungSdiLVBattery::transmit_can(unsigned long currentMillis) {
|
||||
//No periodic sending required
|
||||
}
|
||||
|
||||
void SamsungSdiLVBattery::setup(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, Name, 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
||||
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
|
||||
datalayer.battery.info.total_capacity_Wh = 5000;
|
||||
}
|
46
Software/src/battery/SAMSUNG-SDI-LV-BATTERY.h
Normal file
46
Software/src/battery/SAMSUNG-SDI-LV-BATTERY.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#ifndef SAMSUNG_SDI_LV_BATTERY_H
|
||||
#define SAMSUNG_SDI_LV_BATTERY_H
|
||||
#include <Arduino.h>
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "CanBattery.h"
|
||||
|
||||
#ifdef SAMSUNG_SDI_LV_BATTERY
|
||||
#define SELECTED_BATTERY_CLASS SamsungSdiLVBattery
|
||||
#endif
|
||||
|
||||
class SamsungSdiLVBattery : public CanBattery {
|
||||
public:
|
||||
virtual void setup(void);
|
||||
virtual void handle_incoming_can_frame(CAN_frame rx_frame);
|
||||
virtual void update_values();
|
||||
virtual void transmit_can(unsigned long currentMillis);
|
||||
static constexpr const char* Name = "Samsung SDI LV Battery";
|
||||
|
||||
private:
|
||||
static const int MAX_PACK_VOLTAGE_DV = 600; //5000 = 500.0V
|
||||
static const int MIN_PACK_VOLTAGE_DV = 300;
|
||||
static const int MAX_CELL_DEVIATION_MV = 250;
|
||||
static const int MAX_CELL_VOLTAGE_MV = 4200;
|
||||
static const int MIN_CELL_VOLTAGE_MV = 3000;
|
||||
|
||||
uint16_t system_voltage = 50000;
|
||||
int16_t system_current = 0;
|
||||
uint8_t system_SOC = 50;
|
||||
uint8_t system_SOH = 99;
|
||||
uint16_t battery_charge_voltage = 9999;
|
||||
uint16_t charge_current_limit = 0;
|
||||
uint16_t discharge_current_limit = 0;
|
||||
uint16_t battery_discharge_voltage = 0;
|
||||
uint8_t alarms_frame0 = 0;
|
||||
uint8_t alarms_frame1 = 0;
|
||||
uint8_t protection_frame2 = 0;
|
||||
uint8_t protection_frame3 = 0;
|
||||
uint16_t maximum_cell_voltage = 3700;
|
||||
uint16_t minimum_cell_voltage = 3700;
|
||||
int8_t maximum_cell_temperature = 0;
|
||||
int8_t minimum_cell_temperature = 0;
|
||||
uint8_t system_permanent_failure_status_dry_contact = 0;
|
||||
uint8_t system_permanent_failure_status_fuse_open = 0;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -75,7 +75,6 @@ unsigned long currentTime = 0;
|
|||
unsigned long lastPowerRemovalTime = 0;
|
||||
unsigned long bmsPowerOnTime = 0;
|
||||
const unsigned long powerRemovalInterval = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
||||
const unsigned long powerRemovalDuration = 30000; // 30 seconds in milliseconds
|
||||
const unsigned long bmsWarmupDuration = 3000;
|
||||
|
||||
void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFF) {
|
||||
|
@ -306,8 +305,9 @@ void handle_BMSpower() {
|
|||
}
|
||||
}
|
||||
|
||||
// If power has been removed for 30 seconds, restore the power
|
||||
if (datalayer.system.status.BMS_reset_in_progress && currentTime - lastPowerRemovalTime >= powerRemovalDuration) {
|
||||
// If power has been removed for user configured interval (1-59 seconds), restore the power
|
||||
if (datalayer.system.status.BMS_reset_in_progress &&
|
||||
currentTime - lastPowerRemovalTime >= datalayer.battery.settings.user_set_bms_reset_duration_ms) {
|
||||
// Reapply power to the BMS
|
||||
digitalWrite(bms_power_pin, HIGH);
|
||||
bmsPowerOnTime = currentTime;
|
||||
|
|
|
@ -79,6 +79,10 @@ void init_stored_settings() {
|
|||
if (temp < 16) {
|
||||
datalayer.battery.settings.sofar_user_specified_battery_id = temp;
|
||||
}
|
||||
temp = settings.getUInt("BMSRESETDUR", false);
|
||||
if (temp != 0) {
|
||||
datalayer.battery.settings.user_set_bms_reset_duration_ms = temp;
|
||||
}
|
||||
|
||||
#ifdef COMMON_IMAGE
|
||||
user_selected_battery_type = (BatteryType)settings.getUInt("BATTTYPE", (int)BatteryType::None);
|
||||
|
@ -187,6 +191,9 @@ void store_settings() {
|
|||
if (!settings.putUInt("SOFAR_ID", datalayer.battery.settings.sofar_user_specified_battery_id)) {
|
||||
set_event(EVENT_PERSISTENT_SAVE_INFO, 12);
|
||||
}
|
||||
if (!settings.putUInt("BMSRESETDUR", datalayer.battery.settings.sofar_user_specified_battery_id)) {
|
||||
set_event(EVENT_PERSISTENT_SAVE_INFO, 13);
|
||||
}
|
||||
|
||||
settings.end(); // Close preferences handle
|
||||
}
|
||||
|
|
|
@ -139,6 +139,9 @@ typedef struct {
|
|||
/** The user specified maximum allowed discharge voltage, in deciVolt. 3000 = 300.0 V */
|
||||
uint16_t max_user_set_discharge_voltage_dV = BATTERY_MAX_DISCHARGE_VOLTAGE;
|
||||
|
||||
/** The user specified BMS reset period. Keeps track on how many milliseconds should we keep power off during daily BMS reset */
|
||||
uint16_t user_set_bms_reset_duration_ms = 30000;
|
||||
|
||||
/** Parameters for keeping track of the limiting factor in the system */
|
||||
bool user_settings_limit_discharge = false;
|
||||
bool user_settings_limit_charge = false;
|
||||
|
|
|
@ -312,6 +312,7 @@ typedef struct {
|
|||
uint32_t pid_time_spent_over_55c = 0;
|
||||
uint32_t pid_contactor_closing_counter = 0;
|
||||
uint32_t pid_date_of_manufacture = 0;
|
||||
uint16_t pid_SOH_cell_1 = 0;
|
||||
} DATALAYER_INFO_ECMP;
|
||||
|
||||
typedef struct {
|
||||
|
@ -350,6 +351,19 @@ typedef struct {
|
|||
uint8_t batteryManagementMode = 0;
|
||||
uint8_t BMS_ign = 0;
|
||||
uint8_t batteryRelay = 0;
|
||||
uint16_t inverterVoltage = 0;
|
||||
uint8_t ecu_serial_number[16] = {0};
|
||||
uint8_t ecu_version_number[16] = {0};
|
||||
uint32_t cumulative_charge_current_ah = 0;
|
||||
uint32_t cumulative_discharge_current_ah = 0;
|
||||
uint32_t cumulative_energy_charged_kWh = 0;
|
||||
uint32_t cumulative_energy_discharged_kWh = 0;
|
||||
uint32_t powered_on_total_time = 0;
|
||||
uint16_t isolation_resistance_kOhm = 0;
|
||||
uint16_t number_of_standard_charging_sessions = 0;
|
||||
uint16_t number_of_fastcharging_sessions = 0;
|
||||
uint16_t accumulated_normal_charging_energy_kWh = 0;
|
||||
uint16_t accumulated_fastcharging_energy_kWh = 0;
|
||||
} DATALAYER_INFO_KIAHYUNDAI64;
|
||||
|
||||
typedef struct {
|
||||
|
@ -750,7 +764,7 @@ typedef struct {
|
|||
uint16_t BECMsupplyVoltage = 0;
|
||||
|
||||
uint16_t BECMBatteryVoltage = 0;
|
||||
uint16_t BECMBatteryCurrent = 0;
|
||||
int16_t BECMBatteryCurrent = 0;
|
||||
uint16_t BECMUDynMaxLim = 0;
|
||||
uint16_t BECMUDynMinLim = 0;
|
||||
|
||||
|
|
|
@ -59,16 +59,31 @@ class Esp32Hal {
|
|||
return alloc_pins(name, pins[Is]...);
|
||||
}
|
||||
|
||||
// Base case: no more pins
|
||||
inline bool alloc_pins_ignore_unused_impl(const char* name) {
|
||||
return alloc_pins(name); // Call with 0 pins
|
||||
}
|
||||
|
||||
// Recursive case: process one pin at a time
|
||||
template <typename... Rest>
|
||||
bool alloc_pins_ignore_unused_impl(const char* name, gpio_num_t first, Rest... rest) {
|
||||
if (first == GPIO_NUM_NC) {
|
||||
return alloc_pins_ignore_unused_impl(name, rest...);
|
||||
} else {
|
||||
return call_alloc_pins_filtered(name, first, rest...);
|
||||
}
|
||||
}
|
||||
|
||||
// This helper just forwards pins after filtering is done
|
||||
template <typename... Pins>
|
||||
bool call_alloc_pins_filtered(const char* name, Pins... pins) {
|
||||
return alloc_pins(name, pins...);
|
||||
}
|
||||
|
||||
// Entry point
|
||||
template <typename... Pins>
|
||||
bool alloc_pins_ignore_unused(const char* name, Pins... pins) {
|
||||
std::vector<gpio_num_t> valid_pins;
|
||||
for (gpio_num_t pin : std::vector<gpio_num_t>{static_cast<gpio_num_t>(pins)...}) {
|
||||
if (pin != GPIO_NUM_NC) {
|
||||
valid_pins.push_back(pin);
|
||||
}
|
||||
}
|
||||
|
||||
return alloc_pins_from_vector(name, valid_pins, std::make_index_sequence<sizeof...(pins)>{});
|
||||
return alloc_pins_ignore_unused_impl(name, static_cast<gpio_num_t>(pins)...);
|
||||
}
|
||||
|
||||
virtual bool always_enable_bms_power() { return false; }
|
||||
|
|
|
@ -130,7 +130,8 @@ void update_machineryprotection() {
|
|||
|
||||
// Battery is fully charged. Dont allow any more power into it
|
||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
||||
if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00%
|
||||
if (datalayer.battery.status.reported_soc == 10000 ||
|
||||
datalayer.battery.status.real_soc == 10000) //Either Scaled OR Real SOC% value is 100.00%
|
||||
{
|
||||
if (!battery_full_event_fired) {
|
||||
set_event(EVENT_BATTERY_FULL, 0);
|
||||
|
@ -145,7 +146,8 @@ void update_machineryprotection() {
|
|||
// Battery is empty. Do not allow further discharge.
|
||||
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
|
||||
if (datalayer.battery.status.bms_status == ACTIVE) {
|
||||
if (datalayer.battery.status.reported_soc == 0) { //Scaled SOC% value is 0.00%
|
||||
if (datalayer.battery.status.reported_soc == 0 ||
|
||||
datalayer.battery.status.real_soc == 0) { //Either Scaled OR Real SOC% value is 0.00%, time to stop
|
||||
if (!battery_empty_event_fired) {
|
||||
set_event(EVENT_BATTERY_EMPTY, 0);
|
||||
battery_empty_event_fired = true;
|
||||
|
|
|
@ -40,6 +40,7 @@ void init_events(void) {
|
|||
events.entries[EVENT_CAN_CHARGER_MISSING].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CONTACTOR_WELDED].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CONTACTOR_OPEN].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CPU_OVERHEATING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CPU_OVERHEATED].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
|
||||
|
@ -191,6 +192,8 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "Inverter not sending messages via CAN for the last 60 seconds. Check wiring!";
|
||||
case EVENT_CONTACTOR_WELDED:
|
||||
return "Contactors sticking/welded. Inspect battery with caution!";
|
||||
case EVENT_CONTACTOR_OPEN:
|
||||
return "Battery decided to open contactors. Inspect battery!";
|
||||
case EVENT_CPU_OVERHEATING:
|
||||
return "Battery-Emulator CPU overheating! Increase airflow/cooling to increase hardware lifespan!";
|
||||
case EVENT_CPU_OVERHEATED:
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
XX(EVENT_CAN_NATIVE_TX_FAILURE) \
|
||||
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_CONTACTOR_WELDED) \
|
||||
XX(EVENT_CONTACTOR_OPEN) \
|
||||
XX(EVENT_CPU_OVERHEATING) \
|
||||
XX(EVENT_CPU_OVERHEATED) \
|
||||
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define __LOGGING_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "../../../USER_SETTINGS.h"
|
||||
#include "Print.h"
|
||||
#include "types.h"
|
||||
|
||||
|
|
|
@ -4,8 +4,45 @@
|
|||
#include "index_html.h"
|
||||
|
||||
#if defined(DEBUG_VIA_WEB) || defined(LOG_TO_SD)
|
||||
char* strnchr(const char* s, int c, size_t n) {
|
||||
// Like strchr, but only searches the first 'n' bytes of the string.
|
||||
|
||||
if (s == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Iterate through the string up to 'n' characters or until a null terminator is found.
|
||||
for (size_t i = 0; i < n && s[i] != '\0'; ++i) {
|
||||
if (s[i] == c) {
|
||||
// Character found, return a pointer to it.
|
||||
return (char*)&s[i];
|
||||
}
|
||||
}
|
||||
|
||||
// If the character to be found is the null terminator, and we haven't exceeded
|
||||
// 'n' bytes, check if the null terminator is at the current position.
|
||||
if (c == '\0') {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (s[i] == '\0') {
|
||||
return (char*)&s[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Character not found within the first 'n' bytes.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
String debug_logger_processor(void) {
|
||||
String content = String(index_html_header);
|
||||
String content = String();
|
||||
// Reserve enough space for the content to avoid reallocations.
|
||||
if (!content.reserve(1000 + sizeof(datalayer.system.info.logged_can_messages))) {
|
||||
if (content.reserve(15)) {
|
||||
content += "Out of memory.";
|
||||
}
|
||||
return content;
|
||||
}
|
||||
content += index_html_header;
|
||||
// Page format
|
||||
content += "<style>";
|
||||
content += "body { background-color: black; color: white; font-family: Arial, sans-serif; }";
|
||||
|
@ -28,7 +65,34 @@ String debug_logger_processor(void) {
|
|||
|
||||
// Start a new block for the debug log messages
|
||||
content += "<PRE style='text-align: left'>";
|
||||
content += String(datalayer.system.info.logged_can_messages);
|
||||
size_t offset = datalayer.system.info.logged_can_messages_offset;
|
||||
// If we're mid-buffer, print the older part first.
|
||||
if (offset > 0 && offset < (sizeof(datalayer.system.info.logged_can_messages) - 1)) {
|
||||
// Find the next newline after the current offset. The offset will always be
|
||||
// before the penultimate character, so (offset + 1) will be the final '\0'
|
||||
// or earlier.
|
||||
|
||||
char* next_newline = strnchr(&datalayer.system.info.logged_can_messages[offset + 1], '\n',
|
||||
sizeof(datalayer.system.info.logged_can_messages) - offset - 1);
|
||||
|
||||
if (next_newline != NULL) {
|
||||
// We found a newline, so append from the character after that. We check
|
||||
// the string length to ensure we don't add any intermediate '\0'
|
||||
// characters.
|
||||
content.concat(next_newline + 1,
|
||||
strnlen(next_newline + 1, sizeof(datalayer.system.info.logged_can_messages) - offset - 2));
|
||||
} else {
|
||||
// No newline found, so append from the next character after the offset to
|
||||
// the end of the buffer. We check the string length to ensure we don't
|
||||
// add any intermediate '\0' characters.
|
||||
content.concat(&datalayer.system.info.logged_can_messages[offset + 1],
|
||||
strnlen(&datalayer.system.info.logged_can_messages[offset + 1],
|
||||
sizeof(datalayer.system.info.logged_can_messages) - offset - 1));
|
||||
}
|
||||
}
|
||||
// Append the first part of the buffer up to the current write offset (which
|
||||
// points to the first \0).
|
||||
content.concat(datalayer.system.info.logged_can_messages, offset);
|
||||
content += "</PRE>";
|
||||
|
||||
// Add JavaScript for navigation
|
||||
|
|
|
@ -402,6 +402,10 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
|
|||
return String(datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV / 1.0, 0);
|
||||
}
|
||||
|
||||
if (var == "BMS_RESET_DURATION") {
|
||||
return String(datalayer.battery.settings.user_set_bms_reset_duration_ms / 1000.0, 0);
|
||||
}
|
||||
|
||||
if (var == "CHARGER_CLASS") {
|
||||
if (!charger) {
|
||||
return "hidden";
|
||||
|
@ -544,6 +548,9 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
|||
xhr=new
|
||||
XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/updateMaxDischargeVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 and 1000.0');}}}
|
||||
|
||||
function editBMSresetDuration(){var value=prompt('Amount of seconds BMS power should be off during periodic daily resets. Requires "Periodic BMS reset" to be enabled. Enter value in seconds (1-59):');if(value!==null){if(value>=1&&value<=59){var
|
||||
xhr=new XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/updateBMSresetDuration?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 1 and 59');}}}
|
||||
|
||||
function editTeslaBalAct(){var value=prompt('Enable or disable forced LFP balancing. Makes the battery charge to 101percent. This should be performed once every month, to keep LFP batteries balanced. Ensure battery is fully charged before enabling, and also that you have enough sun or grid power to feed power into the battery while balancing is active. Enter 1 for enabled, 0 for disabled');if(value!==null){if(value==0||value==1){var xhr=new
|
||||
XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/TeslaBalAct?value='+value,true);xhr.send();}}else{alert('Invalid value. Please enter 1 or 0');}}
|
||||
|
||||
|
@ -792,6 +799,8 @@ const char* getCANInterfaceName(CAN_Interface interface) {
|
|||
|
||||
<h4 class='%VOLTAGE_LIMITS_ACTIVE_CLASS%'>Target discharge voltage: %DISCHARGE_VOLTAGE% V </span> <button onclick='editMaxDischargeVoltage()'>Edit</button></h4>
|
||||
|
||||
<h4 style='color: white;'>Periodic BMS reset off time: %BMS_RESET_DURATION% s </span><button onclick='editBMSresetDuration()'>Edit</button></h4>
|
||||
|
||||
</div>
|
||||
|
||||
<div style='background-color: #2E37AD; padding: 10px; margin-bottom: 10px;border-radius: 50px' class="%FAKE_VOLTAGE_CLASS%">
|
||||
|
|
|
@ -594,14 +594,6 @@ void init_webserver() {
|
|||
}
|
||||
});
|
||||
|
||||
update_string("/equipmentStop", [](String value) {
|
||||
if (value == "true" || value == "1") {
|
||||
setBatteryPause(true, false, true); //Pause battery, do not pause CAN, equipment stop on (store to flash)
|
||||
} else {
|
||||
setBatteryPause(false, false, false);
|
||||
}
|
||||
});
|
||||
|
||||
// Route for editing SOCMin
|
||||
update_string_setting("/updateSocMin", [](String value) {
|
||||
datalayer.battery.settings.min_percentage = static_cast<uint16_t>(value.toFloat() * 100);
|
||||
|
@ -657,6 +649,11 @@ void init_webserver() {
|
|||
datalayer.battery.settings.max_user_set_discharge_voltage_dV = static_cast<uint16_t>(value.toFloat() * 10);
|
||||
});
|
||||
|
||||
// Route for editing BMSresetDuration
|
||||
update_string_setting("/updateBMSresetDuration", [](String value) {
|
||||
datalayer.battery.settings.user_set_bms_reset_duration_ms = static_cast<uint16_t>(value.toFloat() * 1000);
|
||||
});
|
||||
|
||||
// Route for editing FakeBatteryVoltage
|
||||
update_string_setting("/updateFakeBatteryVoltage", [](String value) { battery->set_fake_voltage(value.toFloat()); });
|
||||
|
||||
|
@ -1410,7 +1407,7 @@ String processor(const String& var) {
|
|||
content +=
|
||||
"var xhr=new "
|
||||
"XMLHttpRequest();xhr.onload=function() { "
|
||||
"window.location.reload();};xhr.open('GET','/equipmentStop?stop='+stop,true);xhr.send();";
|
||||
"window.location.reload();};xhr.open('GET','/equipmentStop?value='+stop,true);xhr.send();";
|
||||
content += "}";
|
||||
content += "</script>";
|
||||
|
||||
|
|
|
@ -113,16 +113,20 @@ void wifi_monitor() {
|
|||
if ((hasConnectedBefore && (currentMillis - lastWiFiCheck > current_check_interval)) ||
|
||||
(!hasConnectedBefore && (currentMillis - lastWiFiCheck > INIT_WIFI_FULL_RECONNECT_INTERVAL))) {
|
||||
|
||||
DEBUG_PRINTF("Time to monitor Wi-Fi status: %d, %d, %d, %d, %d\n", hasConnectedBefore, currentMillis, lastWiFiCheck,
|
||||
current_check_interval, INIT_WIFI_FULL_RECONNECT_INTERVAL);
|
||||
|
||||
lastWiFiCheck = currentMillis;
|
||||
|
||||
wl_status_t status = WiFi.status();
|
||||
if (status != WL_CONNECTED) {
|
||||
// WL_IDLE_STATUS can mean we're connected but haven't yet gotten the IP.
|
||||
if (status != WL_CONNECTED && status != WL_IDLE_STATUS) {
|
||||
// Increase the current check interval if it's not at the maximum
|
||||
if (current_check_interval + STEP_WIFI_CHECK_INTERVAL <= MAX_STEP_WIFI_CHECK_INTERVAL)
|
||||
if (current_check_interval + STEP_WIFI_CHECK_INTERVAL <= MAX_STEP_WIFI_CHECK_INTERVAL) {
|
||||
current_check_interval += STEP_WIFI_CHECK_INTERVAL;
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Wi-Fi not connected, attempting to reconnect...");
|
||||
#endif
|
||||
}
|
||||
DEBUG_PRINTF("Wi-Fi not connected (status=%d), attempting to reconnect\n", status);
|
||||
|
||||
// Try WiFi.reconnect() if it was successfully connected at least once
|
||||
if (hasConnectedBefore) {
|
||||
lastReconnectAttempt = currentMillis; // Reset reconnection attempt timer
|
||||
|
@ -203,7 +207,7 @@ void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info) {
|
|||
clear_event(EVENT_WIFI_DISCONNECT);
|
||||
set_event(EVENT_WIFI_CONNECT, 0);
|
||||
connected_once = true;
|
||||
DEBUG_PRINTF("Wi-Fi connected. RSSI: %d dBm, IP address: %s, SSID: %s\n", -WiFi.RSSI(),
|
||||
DEBUG_PRINTF("Wi-Fi connected. status: %d, RSSI: %d dBm, IP address: %s, SSID: %s\n", WiFi.status(), -WiFi.RSSI(),
|
||||
WiFi.localIP().toString().c_str(), WiFi.SSID().c_str());
|
||||
hasConnectedBefore = true; // Mark as successfully connected at least once
|
||||
reconnectAttempts = 0; // Reset the attempt counter
|
||||
|
|
|
@ -154,6 +154,12 @@ void KostalInverterProtocol::update_values() {
|
|||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 18); // Last current
|
||||
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 22); // Should be Avg current(1s)
|
||||
|
||||
// Close contactors after 7 battery info frames requested
|
||||
if (f2_startup_count > 7) {
|
||||
datalayer.system.status.inverter_allows_contactor_closing = true;
|
||||
dbg_message("inverter_allows_contactor_closing -> true");
|
||||
}
|
||||
|
||||
// On startup, byte 56 seems to be always 0x00 couple of frames,.
|
||||
if (f2_startup_count < 9) {
|
||||
CYCLIC_DATA[56] = 0x00;
|
||||
|
@ -226,7 +232,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th
|
|||
if (check_kostal_frame_crc(rx_index)) {
|
||||
incoming_message_counter = RS485_HEALTHY;
|
||||
|
||||
if (RS485_RXFRAME[1] == 'c') {
|
||||
if (RS485_RXFRAME[1] == 'c' && info_sent) {
|
||||
if (RS485_RXFRAME[6] == 0x47) {
|
||||
// Set time function - Do nothing.
|
||||
send_kostal(ACK_FRAME, 8); // ACK
|
||||
|
@ -234,12 +240,11 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th
|
|||
if (RS485_RXFRAME[6] == 0x5E) {
|
||||
// Set State function
|
||||
if (RS485_RXFRAME[7] == 0x00) {
|
||||
// Allow contactor closing
|
||||
datalayer.system.status.inverter_allows_contactor_closing = true;
|
||||
dbg_message("inverter_allows_contactor_closing -> true");
|
||||
send_kostal(ACK_FRAME, 8); // ACK
|
||||
} else if (RS485_RXFRAME[7] == 0x04) {
|
||||
// INVALID STATE, no ACK sent
|
||||
send_kostal(ACK_FRAME, 8); // ACK
|
||||
} else if (RS485_RXFRAME[7] == 0xFF) {
|
||||
// no ACK sent
|
||||
} else {
|
||||
// Battery deep sleep?
|
||||
send_kostal(ACK_FRAME, 8); // ACK
|
||||
|
@ -250,7 +255,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th
|
|||
//Reverse polarity, do nothing
|
||||
} else {
|
||||
int code = RS485_RXFRAME[6] + RS485_RXFRAME[7] * 0x100;
|
||||
if (code == 0x44a) {
|
||||
if (code == 0x44a && info_sent) {
|
||||
//Send cyclic data
|
||||
// TODO: Probably not a good idea to use the battery object here like this.
|
||||
if (battery) {
|
||||
|
@ -274,13 +279,12 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th
|
|||
tmpframe[38] = calculate_kostal_crc(tmpframe, 38);
|
||||
null_stuffer(tmpframe, 40);
|
||||
send_kostal(tmpframe, 40);
|
||||
datalayer.system.status.inverter_allows_contactor_closing = true;
|
||||
dbg_message("inverter_allows_contactor_closing (battery_info) -> true");
|
||||
info_sent = true;
|
||||
if (!startupMillis) {
|
||||
startupMillis = currentMillis;
|
||||
}
|
||||
}
|
||||
if (code == 0x353) {
|
||||
if (code == 0x353 && info_sent) {
|
||||
//Send battery error/status
|
||||
byte tmpframe[9]; //copy values to prevent data manipulation during rewrite/crc calculation
|
||||
memcpy(tmpframe, STATUS_FRAME, 9);
|
||||
|
|
|
@ -40,8 +40,7 @@ class KostalInverterProtocol : public Rs485InverterProtocol {
|
|||
uint8_t incoming_message_counter = RS485_HEALTHY;
|
||||
int8_t f2_startup_count = 0;
|
||||
|
||||
boolean B1_delay = false;
|
||||
unsigned long B1_last_millis = 0;
|
||||
boolean info_sent = false;
|
||||
unsigned long currentMillis;
|
||||
unsigned long startupMillis = 0;
|
||||
unsigned long contactorMillis = 0;
|
||||
|
|
|
@ -61,12 +61,35 @@ void SmaBydHInverter::
|
|||
SMA_4D8.data.u8[6] = STOP_STATE;
|
||||
}
|
||||
|
||||
//Lifetime charged energy amount
|
||||
SMA_458.data.u8[0] = (datalayer.battery.status.total_charged_battery_Wh & 0xFF000000) >> 24;
|
||||
SMA_458.data.u8[1] = (datalayer.battery.status.total_charged_battery_Wh & 0x00FF0000) >> 16;
|
||||
SMA_458.data.u8[2] = (datalayer.battery.status.total_charged_battery_Wh & 0x0000FF00) >> 8;
|
||||
SMA_458.data.u8[3] = (datalayer.battery.status.total_charged_battery_Wh & 0x000000FF);
|
||||
//Lifetime discharged energy amount
|
||||
SMA_458.data.u8[4] = (datalayer.battery.status.total_discharged_battery_Wh & 0xFF000000) >> 24;
|
||||
SMA_458.data.u8[5] = (datalayer.battery.status.total_discharged_battery_Wh & 0x00FF0000) >> 16;
|
||||
SMA_458.data.u8[6] = (datalayer.battery.status.total_discharged_battery_Wh & 0x0000FF00) >> 8;
|
||||
SMA_458.data.u8[7] = (datalayer.battery.status.total_discharged_battery_Wh & 0x000000FF);
|
||||
|
||||
//Error bits
|
||||
if (datalayer.system.status.battery_allows_contactor_closing) {
|
||||
SMA_158.data.u8[2] = 0xAA;
|
||||
} else {
|
||||
SMA_158.data.u8[2] = 0x6A;
|
||||
}
|
||||
//Highest battery temperature
|
||||
SMA_518.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
SMA_518.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
//Lowest battery temperature
|
||||
SMA_518.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
SMA_518.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
//Sum of all cellvoltages
|
||||
SMA_518.data.u8[4] = (datalayer.battery.status.voltage_dV >> 8);
|
||||
SMA_518.data.u8[5] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||
//Cell min/max voltage (mV / 25)
|
||||
SMA_518.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV / 25);
|
||||
SMA_518.data.u8[7] = (datalayer.battery.status.cell_max_voltage_mV / 25);
|
||||
|
||||
control_contactor_led();
|
||||
|
||||
|
|
|
@ -60,6 +60,30 @@ void SmaBydHvsInverter::
|
|||
SMA_4D8.data.u8[6] = STOP_STATE;
|
||||
}
|
||||
|
||||
//Highest battery temperature
|
||||
SMA_518.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
SMA_518.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
//Lowest battery temperature
|
||||
SMA_518.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
SMA_518.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
//Sum of all cellvoltages
|
||||
SMA_518.data.u8[4] = (datalayer.battery.status.voltage_dV >> 8);
|
||||
SMA_518.data.u8[5] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||
//Cell min/max voltage (mV / 25)
|
||||
SMA_518.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV / 25);
|
||||
SMA_518.data.u8[7] = (datalayer.battery.status.cell_max_voltage_mV / 25);
|
||||
|
||||
//Lifetime charged energy amount
|
||||
SMA_458.data.u8[0] = (datalayer.battery.status.total_charged_battery_Wh & 0xFF000000) >> 24;
|
||||
SMA_458.data.u8[1] = (datalayer.battery.status.total_charged_battery_Wh & 0x00FF0000) >> 16;
|
||||
SMA_458.data.u8[2] = (datalayer.battery.status.total_charged_battery_Wh & 0x0000FF00) >> 8;
|
||||
SMA_458.data.u8[3] = (datalayer.battery.status.total_charged_battery_Wh & 0x000000FF);
|
||||
//Lifetime discharged energy amount
|
||||
SMA_458.data.u8[4] = (datalayer.battery.status.total_discharged_battery_Wh & 0xFF000000) >> 24;
|
||||
SMA_458.data.u8[5] = (datalayer.battery.status.total_discharged_battery_Wh & 0x00FF0000) >> 16;
|
||||
SMA_458.data.u8[6] = (datalayer.battery.status.total_discharged_battery_Wh & 0x0000FF00) >> 8;
|
||||
SMA_458.data.u8[7] = (datalayer.battery.status.total_discharged_battery_Wh & 0x000000FF);
|
||||
|
||||
//Error bits
|
||||
if (datalayer.system.status.battery_allows_contactor_closing) {
|
||||
SMA_158.data.u8[2] = 0xAA;
|
||||
|
@ -247,7 +271,7 @@ void SmaBydHvsInverter::transmit_can(unsigned long currentMillis) {
|
|||
// Increment message index and wrap around if needed
|
||||
batch_send_index++;
|
||||
|
||||
if (transmit_can_init == false) {
|
||||
if (transmit_can_init == false) { //We completed sending the batches
|
||||
batch_send_index = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ class SmaBydHvsInverter : public SmaInverterBase {
|
|||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x518,
|
||||
.data = {0x01, 0x4A, 0x01, 0x25, 0xFF, 0xFF, 0xFF, 0xFF}};
|
||||
.data = {0x01, 0x4A, 0x01, 0x25, 0x10, 0x10, 0xFF, 0xFF}};
|
||||
|
||||
// Pairing/Battery setup information
|
||||
|
||||
|
@ -79,7 +79,7 @@ class SmaBydHvsInverter : public SmaInverterBase {
|
|||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x598,
|
||||
.data = {0x00, 0x01, 0x0F, 0x2C, 0x5C, 0x98, 0xB6, 0xEE}};
|
||||
.data = {0x00, 0x01, 0x0F, 0x2C, 0x5C, 0x98, 0xB6, 0xEE}}; //B0-4 Serial, rest unknown
|
||||
CAN_frame SMA_5D8 = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
|
|
|
@ -64,6 +64,30 @@ void SmaTripowerInverter::
|
|||
SMA_4D8.data.u8[6] = READY_STATE;
|
||||
}
|
||||
|
||||
//Highest battery temperature
|
||||
SMA_518.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
SMA_518.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
//Lowest battery temperature
|
||||
SMA_518.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
SMA_518.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
//Sum of all cellvoltages
|
||||
SMA_518.data.u8[4] = (datalayer.battery.status.voltage_dV >> 8);
|
||||
SMA_518.data.u8[5] = (datalayer.battery.status.voltage_dV & 0x00FF);
|
||||
//Cell min/max voltage (mV / 25)
|
||||
SMA_518.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV / 25);
|
||||
SMA_518.data.u8[7] = (datalayer.battery.status.cell_max_voltage_mV / 25);
|
||||
|
||||
//Lifetime charged energy amount
|
||||
SMA_458.data.u8[0] = (datalayer.battery.status.total_charged_battery_Wh & 0xFF000000) >> 24;
|
||||
SMA_458.data.u8[1] = (datalayer.battery.status.total_charged_battery_Wh & 0x00FF0000) >> 16;
|
||||
SMA_458.data.u8[2] = (datalayer.battery.status.total_charged_battery_Wh & 0x0000FF00) >> 8;
|
||||
SMA_458.data.u8[3] = (datalayer.battery.status.total_charged_battery_Wh & 0x000000FF);
|
||||
//Lifetime discharged energy amount
|
||||
SMA_458.data.u8[4] = (datalayer.battery.status.total_discharged_battery_Wh & 0xFF000000) >> 24;
|
||||
SMA_458.data.u8[5] = (datalayer.battery.status.total_discharged_battery_Wh & 0x00FF0000) >> 16;
|
||||
SMA_458.data.u8[6] = (datalayer.battery.status.total_discharged_battery_Wh & 0x0000FF00) >> 8;
|
||||
SMA_458.data.u8[7] = (datalayer.battery.status.total_discharged_battery_Wh & 0x000000FF);
|
||||
|
||||
control_contactor_led();
|
||||
|
||||
// Check if Enable line is working. If we go too long without any input, raise an event
|
||||
|
|
|
@ -38,7 +38,7 @@ It is also deployed in these registries:
|
|||
|
||||
- Arduino Library Registry: [https://github.com/arduino/library-registry](https://github.com/arduino/library-registry)
|
||||
|
||||
- ESP Component Registry [https://components.espressif.com/components/esp32async/espasyncbebserver/](https://components.espressif.com/components/esp32async/espasyncbebserver/)
|
||||
- ESP Component Registry [https://components.espressif.com/components/esp32async/espasyncwebserver](https://components.espressif.com/components/esp32async/espasyncwebserver)
|
||||
|
||||
- PlatformIO Registry: [https://registry.platformio.org/libraries/esp32async/ESPAsyncWebServer](https://registry.platformio.org/libraries/esp32async/ESPAsyncWebServer)
|
||||
|
||||
|
@ -72,6 +72,19 @@ lib_deps =
|
|||
ESP32Async/ESPAsyncWebServer
|
||||
```
|
||||
|
||||
### LibreTiny (BK7231N/T, RTL8710B, etc.)
|
||||
|
||||
Version 1.9.1 or newer is required.
|
||||
|
||||
```ini
|
||||
[env:stable]
|
||||
platform = libretiny @ ^1.9.1
|
||||
lib_ldf_mode = chain
|
||||
lib_deps =
|
||||
ESP32Async/AsyncTCP
|
||||
ESP32Async/ESPAsyncWebServer
|
||||
```
|
||||
|
||||
### Unofficial dependencies
|
||||
|
||||
**AsyncTCPSock**
|
||||
|
@ -100,7 +113,7 @@ platform = https://github.com/maxgerhardt/platform-raspberrypi.git
|
|||
board = rpipicow
|
||||
board_build.core = earlephilhower
|
||||
lib_deps =
|
||||
ayushsharma82/RPAsyncTCP@^1.3.1
|
||||
ayushsharma82/RPAsyncTCP@^1.3.2
|
||||
ESP32Async/ESPAsyncWebServer
|
||||
lib_ignore =
|
||||
lwIP_ESPHost
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ESPAsyncWebServer",
|
||||
"version": "3.7.2",
|
||||
"version": "3.7.10",
|
||||
"description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.",
|
||||
"keywords": "http,async,websocket,webserver",
|
||||
"homepage": "https://github.com/ESP32Async/ESPAsyncWebServer",
|
||||
|
@ -18,14 +18,18 @@
|
|||
"platforms": [
|
||||
"espressif32",
|
||||
"espressif8266",
|
||||
"raspberrypi"
|
||||
"raspberrypi",
|
||||
"libretiny"
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"owner": "ESP32Async",
|
||||
"name": "AsyncTCP",
|
||||
"version": "^3.3.6",
|
||||
"platforms": "espressif32"
|
||||
"version": "^3.4.5",
|
||||
"platforms": [
|
||||
"espressif32",
|
||||
"libretiny"
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "ESP32Async",
|
||||
|
@ -40,7 +44,7 @@
|
|||
{
|
||||
"owner": "ayushsharma82",
|
||||
"name": "RPAsyncTCP",
|
||||
"version": "^1.3.1",
|
||||
"version": "^1.3.2",
|
||||
"platforms": "raspberrypi"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name=ESP Async WebServer
|
||||
includes=ESPAsyncWebServer.h
|
||||
version=3.7.2
|
||||
version=3.7.10
|
||||
author=ESP32Async
|
||||
maintainer=ESP32Async
|
||||
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
|
||||
|
|
|
@ -193,7 +193,7 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A
|
|||
|
||||
AsyncEventSourceClient::~AsyncEventSourceClient() {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||
#endif
|
||||
_messageQueue.clear();
|
||||
close();
|
||||
|
@ -211,7 +211,7 @@ bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) {
|
|||
|
||||
#ifdef ESP32
|
||||
// length() is not thread-safe, thus acquiring the lock before this call..
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||
#endif
|
||||
|
||||
_messageQueue.emplace_back(message, len);
|
||||
|
@ -241,7 +241,7 @@ bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
|
|||
|
||||
#ifdef ESP32
|
||||
// length() is not thread-safe, thus acquiring the lock before this call..
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||
#endif
|
||||
|
||||
_messageQueue.emplace_back(std::move(msg));
|
||||
|
@ -261,7 +261,7 @@ bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
|
|||
void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) {
|
||||
#ifdef ESP32
|
||||
// Same here, acquiring the lock early
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||
#endif
|
||||
|
||||
// adjust in-flight len
|
||||
|
@ -290,7 +290,7 @@ void AsyncEventSourceClient::_onPoll() {
|
|||
if (_messageQueue.size()) {
|
||||
#ifdef ESP32
|
||||
// Same here, acquiring the lock early
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||
#endif
|
||||
_runQueue();
|
||||
}
|
||||
|
@ -367,7 +367,7 @@ void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
|
|||
return;
|
||||
}
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
_clients.emplace_back(client);
|
||||
if (_connectcb) {
|
||||
|
@ -382,7 +382,7 @@ void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
|
|||
_disconnectcb(client);
|
||||
}
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
|
||||
if (i->get() == client) {
|
||||
|
@ -398,10 +398,15 @@ void AsyncEventSource::close() {
|
|||
// iterator should remain valid even when AsyncEventSource::_handleDisconnect()
|
||||
// is called very early
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
for (const auto &c : _clients) {
|
||||
if (c->connected()) {
|
||||
/**
|
||||
* @brief: Fix self-deadlock by using recursive_mutex instead.
|
||||
* Due to c->close() shall call the callback function _onDisconnect()
|
||||
* The calling flow _onDisconnect() --> _handleDisconnect() --> deadlock
|
||||
*/
|
||||
c->close();
|
||||
}
|
||||
}
|
||||
|
@ -412,7 +417,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
|
|||
size_t aql = 0;
|
||||
uint32_t nConnectedClients = 0;
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
if (!_clients.size()) {
|
||||
return 0;
|
||||
|
@ -430,7 +435,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
|
|||
AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
|
||||
AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect));
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
size_t hits = 0;
|
||||
size_t miss = 0;
|
||||
|
@ -446,7 +451,7 @@ AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const c
|
|||
|
||||
size_t AsyncEventSource::count() const {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
size_t n_clients{0};
|
||||
for (const auto &i : _clients) {
|
||||
|
|
|
@ -6,8 +6,13 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#if defined(ESP32) || defined(LIBRETINY)
|
||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||
#ifdef LIBRETINY
|
||||
#ifdef round
|
||||
#undef round
|
||||
#endif
|
||||
#endif
|
||||
#include <mutex>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||
|
@ -129,7 +134,7 @@ private:
|
|||
size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
|
||||
std::list<AsyncEventSourceMessage> _messageQueue;
|
||||
#ifdef ESP32
|
||||
mutable std::mutex _lockmq;
|
||||
mutable std::recursive_mutex _lockmq;
|
||||
#endif
|
||||
bool _queueMessage(const char *message, size_t len);
|
||||
bool _queueMessage(AsyncEvent_SharedData_t &&msg);
|
||||
|
@ -230,7 +235,7 @@ private:
|
|||
#ifdef ESP32
|
||||
// Same as for individual messages, protect mutations of _clients list
|
||||
// since simultaneous access from different tasks is possible
|
||||
mutable std::mutex _client_queue_lock;
|
||||
mutable std::recursive_mutex _client_queue_lock;
|
||||
#endif
|
||||
ArEventHandlerFunction _connectcb = nullptr;
|
||||
ArEventHandlerFunction _disconnectcb = nullptr;
|
||||
|
|
|
@ -113,53 +113,77 @@ bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) cons
|
|||
|
||||
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||
if (_onRequest) {
|
||||
// GET request:
|
||||
if (request->method() == HTTP_GET) {
|
||||
JsonVariant json;
|
||||
_onRequest(request, json);
|
||||
return;
|
||||
} else if (request->_tempObject != NULL) {
|
||||
}
|
||||
|
||||
// POST / PUT / ... requests:
|
||||
// check if JSON body is too large, if it is, don't deserialize
|
||||
if (request->contentLength() > _maxContentLength) {
|
||||
#ifdef ESP32
|
||||
log_e("Content length exceeds maximum allowed");
|
||||
#endif
|
||||
request->send(413);
|
||||
return;
|
||||
}
|
||||
|
||||
if (request->_tempObject == NULL) {
|
||||
// there is no body
|
||||
request->send(400);
|
||||
return;
|
||||
}
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
|
||||
if (json.success()) {
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonVariant json = jsonBuffer.parse((const char *)request->_tempObject);
|
||||
if (json.success()) {
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#else
|
||||
JsonDocument jsonBuffer;
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
JsonDocument jsonBuffer;
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#endif
|
||||
|
||||
_onRequest(request, json);
|
||||
return;
|
||||
}
|
||||
_onRequest(request, json);
|
||||
} else {
|
||||
// error parsing the body
|
||||
request->send(400);
|
||||
}
|
||||
request->send(_contentLength > _maxContentLength ? 413 : 400);
|
||||
} else {
|
||||
request->send(500);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
if (_onRequest) {
|
||||
_contentLength = total;
|
||||
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
|
||||
request->_tempObject = malloc(total);
|
||||
// ignore callback if size is larger than maxContentLength
|
||||
if (total > _maxContentLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
// this check allows request->_tempObject to be initialized from a middleware
|
||||
if (request->_tempObject == NULL) {
|
||||
request->_tempObject = calloc(total + 1, sizeof(uint8_t)); // null-terminated string
|
||||
if (request->_tempObject == NULL) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
request->abort();
|
||||
return;
|
||||
request->abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request->_tempObject != NULL) {
|
||||
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
|
||||
uint8_t *buffer = (uint8_t *)request->_tempObject;
|
||||
memcpy(buffer + index, data, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,7 +79,6 @@ protected:
|
|||
String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
ArJsonRequestHandlerFunction _onRequest;
|
||||
size_t _contentLength;
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
size_t maxJsonBufferSize;
|
||||
#endif
|
||||
|
|
|
@ -3,30 +3,32 @@
|
|||
|
||||
#include "ESPAsyncWebServer.h"
|
||||
|
||||
AsyncWebHeader::AsyncWebHeader(const String &data) {
|
||||
const AsyncWebHeader AsyncWebHeader::parse(const char *data) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers
|
||||
// In HTTP/1.X, a header is a case-insensitive name followed by a colon, then optional whitespace which will be ignored, and finally by its value
|
||||
if (!data) {
|
||||
return;
|
||||
return AsyncWebHeader(); // nullptr
|
||||
}
|
||||
int index = data.indexOf(':');
|
||||
if (index < 0) {
|
||||
return;
|
||||
if (data[0] == '\0') {
|
||||
return AsyncWebHeader(); // empty string
|
||||
}
|
||||
_name = data.substring(0, index);
|
||||
_value = data.substring(index + 2);
|
||||
}
|
||||
|
||||
String AsyncWebHeader::toString() const {
|
||||
String str;
|
||||
if (str.reserve(_name.length() + _value.length() + 2)) {
|
||||
str.concat(_name);
|
||||
str.concat((char)0x3a);
|
||||
str.concat((char)0x20);
|
||||
str.concat(_value);
|
||||
str.concat(asyncsrv::T_rn);
|
||||
} else {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
}
|
||||
return str;
|
||||
if (strchr(data, '\n') || strchr(data, '\r')) {
|
||||
return AsyncWebHeader(); // Invalid header format
|
||||
}
|
||||
char *colon = strchr(data, ':');
|
||||
if (!colon) {
|
||||
return AsyncWebHeader(); // separator not found
|
||||
}
|
||||
if (colon == data) {
|
||||
return AsyncWebHeader(); // Header name cannot be empty
|
||||
}
|
||||
char *startOfValue = colon + 1; // Skip the colon
|
||||
// skip one optional whitespace after the colon
|
||||
if (*startOfValue == ' ') {
|
||||
startOfValue++;
|
||||
}
|
||||
String name;
|
||||
name.reserve(colon - data);
|
||||
name.concat(data, colon - data);
|
||||
return AsyncWebHeader(name, String(startOfValue));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
#include "ESPAsyncWebServer.h"
|
||||
|
||||
/**
|
||||
* @brief Sends a file from the filesystem to the client, with optional gzip compression and ETag-based caching.
|
||||
*
|
||||
* This method serves files over HTTP from the provided filesystem. If a compressed version of the file
|
||||
* (with a `.gz` extension) exists and uncompressed version does not exist, it serves the compressed file.
|
||||
* It also handles ETag caching using the CRC32 value from the gzip trailer, responding with `304 Not Modified`
|
||||
* if the client's `If-None-Match` header matches the generated ETag.
|
||||
*
|
||||
* @param fs Reference to the filesystem (SPIFFS, LittleFS, etc.).
|
||||
* @param path Path to the file to be served.
|
||||
* @param contentType Optional MIME type of the file to be sent.
|
||||
* If contentType is "" it will be obtained from the file extension
|
||||
* @param download If true, forces the file to be sent as a download.
|
||||
* @param callback Optional template processor for dynamic content generation.
|
||||
* Templates will not be processed in compressed files.
|
||||
*
|
||||
* @note If neither the file nor its compressed version exists, responds with `404 Not Found`.
|
||||
*/
|
||||
void AsyncWebServerRequest::send(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) {
|
||||
// Check uncompressed file first
|
||||
if (fs.exists(path)) {
|
||||
send(beginResponse(fs, path, contentType, download, callback));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle compressed version
|
||||
const String gzPath = path + asyncsrv::T__gz;
|
||||
File gzFile = fs.open(gzPath, "r");
|
||||
|
||||
// Compressed file not found or invalid
|
||||
if (!gzFile.seek(gzFile.size() - 8)) {
|
||||
send(404);
|
||||
gzFile.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// ETag validation
|
||||
if (this->hasHeader(asyncsrv::T_INM)) {
|
||||
// Generate server ETag from CRC in gzip trailer
|
||||
uint8_t crcInTrailer[4];
|
||||
gzFile.read(crcInTrailer, 4);
|
||||
char serverETag[9];
|
||||
_getEtag(crcInTrailer, serverETag);
|
||||
|
||||
// Compare with client's ETag
|
||||
const AsyncWebHeader *inmHeader = this->getHeader(asyncsrv::T_INM);
|
||||
if (inmHeader && inmHeader->value() == serverETag) {
|
||||
gzFile.close();
|
||||
this->send(304); // Not Modified
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Send compressed file response
|
||||
gzFile.close();
|
||||
send(beginResponse(fs, path, contentType, download, callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generates an ETag string from a 4-byte trailer
|
||||
*
|
||||
* This function converts a 4-byte array into a hexadecimal ETag string enclosed in quotes.
|
||||
*
|
||||
* @param trailer[4] Input array of 4 bytes to convert to hexadecimal
|
||||
* @param serverETag Output buffer to store the ETag
|
||||
* Must be pre-allocated with minimum 9 bytes (8 hex + 1 null terminator)
|
||||
*/
|
||||
void AsyncWebServerRequest::_getEtag(uint8_t trailer[4], char *serverETag) {
|
||||
static constexpr char hexChars[] = "0123456789ABCDEF";
|
||||
|
||||
uint32_t data;
|
||||
memcpy(&data, trailer, 4);
|
||||
|
||||
serverETag[0] = hexChars[(data >> 4) & 0x0F];
|
||||
serverETag[1] = hexChars[data & 0x0F];
|
||||
serverETag[2] = hexChars[(data >> 12) & 0x0F];
|
||||
serverETag[3] = hexChars[(data >> 8) & 0x0F];
|
||||
serverETag[4] = hexChars[(data >> 20) & 0x0F];
|
||||
serverETag[5] = hexChars[(data >> 16) & 0x0F];
|
||||
serverETag[6] = hexChars[(data >> 28)];
|
||||
serverETag[7] = hexChars[(data >> 24) & 0x0F];
|
||||
serverETag[8] = '\0';
|
||||
}
|
|
@ -12,7 +12,7 @@ extern "C" {
|
|||
/** Minor version number (x.X.x) */
|
||||
#define ASYNCWEBSERVER_VERSION_MINOR 7
|
||||
/** Patch version number (x.x.X) */
|
||||
#define ASYNCWEBSERVER_VERSION_PATCH 2
|
||||
#define ASYNCWEBSERVER_VERSION_PATCH 10
|
||||
|
||||
/**
|
||||
* Macro to convert version number into an integer
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <rom/ets_sys.h>
|
||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(ESP8266)
|
||||
#include <Hash.h>
|
||||
#elif defined(LIBRETINY)
|
||||
#include <mbedtls/sha1.h>
|
||||
#endif
|
||||
|
||||
using namespace asyncsrv;
|
||||
|
@ -333,7 +335,7 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async
|
|||
AsyncWebSocketClient::~AsyncWebSocketClient() {
|
||||
{
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
_messageQueue.clear();
|
||||
_controlQueue.clear();
|
||||
|
@ -351,7 +353,7 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
|
|||
_lastMessageTime = millis();
|
||||
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::unique_lock<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
|
||||
if (!_controlQueue.empty()) {
|
||||
|
@ -362,6 +364,14 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
|
|||
_controlQueue.pop_front();
|
||||
_status = WS_DISCONNECTED;
|
||||
if (_client) {
|
||||
#ifdef ESP32
|
||||
/*
|
||||
Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock.
|
||||
Due to _client->close(true) shall call the callback function _onDisconnect()
|
||||
The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
|
||||
*/
|
||||
lock.unlock();
|
||||
#endif
|
||||
_client->close(true);
|
||||
}
|
||||
return;
|
||||
|
@ -385,7 +395,7 @@ void AsyncWebSocketClient::_onPoll() {
|
|||
}
|
||||
|
||||
#ifdef ESP32
|
||||
std::unique_lock<std::mutex> lock(_lock);
|
||||
std::unique_lock<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
if (_client && _client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) {
|
||||
_runQueue();
|
||||
|
@ -415,21 +425,21 @@ void AsyncWebSocketClient::_runQueue() {
|
|||
|
||||
bool AsyncWebSocketClient::queueIsFull() const {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
return (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED);
|
||||
}
|
||||
|
||||
size_t AsyncWebSocketClient::queueLen() const {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
return _messageQueue.size();
|
||||
}
|
||||
|
||||
bool AsyncWebSocketClient::canSend() const {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
return _messageQueue.size() < WS_MAX_QUEUED_MESSAGES;
|
||||
}
|
||||
|
@ -440,7 +450,7 @@ bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, si
|
|||
}
|
||||
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
|
||||
_controlQueue.emplace_back(opcode, data, len, mask);
|
||||
|
@ -458,7 +468,7 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
|
|||
}
|
||||
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::unique_lock<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
|
||||
if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) {
|
||||
|
@ -466,6 +476,14 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
|
|||
_status = WS_DISCONNECTED;
|
||||
|
||||
if (_client) {
|
||||
#ifdef ESP32
|
||||
/*
|
||||
Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock.
|
||||
Due to _client->close(true) shall call the callback function _onDisconnect()
|
||||
The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
|
||||
*/
|
||||
lock.unlock();
|
||||
#endif
|
||||
_client->close(true);
|
||||
}
|
||||
|
||||
|
@ -551,6 +569,7 @@ void AsyncWebSocketClient::_onTimeout(uint32_t time) {
|
|||
void AsyncWebSocketClient::_onDisconnect() {
|
||||
// Serial.println("onDis");
|
||||
_client = nullptr;
|
||||
_server->_handleDisconnect(this);
|
||||
}
|
||||
|
||||
void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
|
||||
|
@ -857,6 +876,16 @@ AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request)
|
|||
return &_clients.back();
|
||||
}
|
||||
|
||||
void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient *client) {
|
||||
const auto client_id = client->id();
|
||||
const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [client_id](const AsyncWebSocketClient &c) {
|
||||
return c.id() == client_id;
|
||||
});
|
||||
if (iter != std::end(_clients)) {
|
||||
_clients.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
bool AsyncWebSocket::availableForWriteAll() {
|
||||
return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
|
||||
return c.queueIsFull();
|
||||
|
@ -1300,11 +1329,20 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String &key, AsyncWebSocket
|
|||
}
|
||||
k.concat(key);
|
||||
k.concat(WS_STR_UUID);
|
||||
#ifdef LIBRETINY
|
||||
mbedtls_sha1_context ctx;
|
||||
mbedtls_sha1_init(&ctx);
|
||||
mbedtls_sha1_starts(&ctx);
|
||||
mbedtls_sha1_update(&ctx, (const uint8_t *)k.c_str(), k.length());
|
||||
mbedtls_sha1_finish(&ctx, hash);
|
||||
mbedtls_sha1_free(&ctx);
|
||||
#else
|
||||
SHA1Builder sha1;
|
||||
sha1.begin();
|
||||
sha1.add((const uint8_t *)k.c_str(), k.length());
|
||||
sha1.calculate();
|
||||
sha1.getBytes(hash);
|
||||
#endif
|
||||
#endif
|
||||
base64_encodestate _state;
|
||||
base64_init_encodestate(&_state);
|
||||
|
|
|
@ -5,8 +5,14 @@
|
|||
#define ASYNCWEBSOCKET_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#ifdef ESP32
|
||||
|
||||
#if defined(ESP32) || defined(LIBRETINY)
|
||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||
#ifdef LIBRETINY
|
||||
#ifdef round
|
||||
#undef round
|
||||
#endif
|
||||
#endif
|
||||
#include <mutex>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 32
|
||||
|
@ -152,7 +158,7 @@ private:
|
|||
uint32_t _clientId;
|
||||
AwsClientStatus _status;
|
||||
#ifdef ESP32
|
||||
mutable std::mutex _lock;
|
||||
mutable std::recursive_mutex _lock;
|
||||
#endif
|
||||
std::deque<AsyncWebSocketControl> _controlQueue;
|
||||
std::deque<AsyncWebSocketMessage> _messageQueue;
|
||||
|
@ -291,7 +297,7 @@ private:
|
|||
String _url;
|
||||
std::list<AsyncWebSocketClient> _clients;
|
||||
uint32_t _cNextId;
|
||||
AwsEventHandler _eventHandler{nullptr};
|
||||
AwsEventHandler _eventHandler;
|
||||
AwsHandshakeHandler _handshakeHandler;
|
||||
bool _enabled;
|
||||
#ifdef ESP32
|
||||
|
@ -305,8 +311,8 @@ public:
|
|||
PARTIALLY_ENQUEUED = 2,
|
||||
} SendStatus;
|
||||
|
||||
explicit AsyncWebSocket(const char *url) : _url(url), _cNextId(1), _enabled(true) {}
|
||||
AsyncWebSocket(const String &url) : _url(url), _cNextId(1), _enabled(true) {}
|
||||
explicit AsyncWebSocket(const char *url, AwsEventHandler handler = nullptr) : _url(url), _cNextId(1), _eventHandler(handler), _enabled(true) {}
|
||||
AsyncWebSocket(const String &url, AwsEventHandler handler = nullptr) : _url(url), _cNextId(1), _eventHandler(handler), _enabled(true) {}
|
||||
~AsyncWebSocket(){};
|
||||
const char *url() const {
|
||||
return _url.c_str();
|
||||
|
@ -385,6 +391,7 @@ public:
|
|||
return _cNextId++;
|
||||
}
|
||||
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
|
||||
void _handleDisconnect(AsyncWebSocketClient *client);
|
||||
void _handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
|
||||
bool canHandle(AsyncWebServerRequest *request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
|
@ -413,4 +420,86 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class AsyncWebSocketMessageHandler {
|
||||
public:
|
||||
AwsEventHandler eventHandler() const {
|
||||
return _handler;
|
||||
}
|
||||
|
||||
void onConnect(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client)> onConnect) {
|
||||
_onConnect = onConnect;
|
||||
}
|
||||
|
||||
void onDisconnect(std::function<void(AsyncWebSocket *server, uint32_t clientId)> onDisconnect) {
|
||||
_onDisconnect = onDisconnect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error callback
|
||||
* @param reason null-terminated string
|
||||
* @param len length of the string
|
||||
*/
|
||||
void onError(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len)> onError) {
|
||||
_onError = onError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete message callback
|
||||
* @param data pointer to the data (binary or null-terminated string). This handler expects the user to know which data type he uses.
|
||||
*/
|
||||
void onMessage(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len)> onMessage) {
|
||||
_onMessage = onMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fragmented message callback
|
||||
* @param data pointer to the data (binary or null-terminated string), will be null-terminated. This handler expects the user to know which data type he uses.
|
||||
*/
|
||||
// clang-format off
|
||||
void onFragment(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsFrameInfo *frameInfo, const uint8_t *data, size_t len)> onFragment) {
|
||||
_onFragment = onFragment;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
// clang-format off
|
||||
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client)> _onConnect;
|
||||
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len)> _onError;
|
||||
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len)> _onMessage;
|
||||
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsFrameInfo *frameInfo, const uint8_t *data, size_t len)> _onFragment;
|
||||
std::function<void(AsyncWebSocket *server, uint32_t clientId)> _onDisconnect;
|
||||
// clang-format on
|
||||
|
||||
// this handler is meant to only support 1-frame messages (== unfragmented messages)
|
||||
AwsEventHandler _handler = [this](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
|
||||
if (type == WS_EVT_CONNECT) {
|
||||
if (_onConnect) {
|
||||
_onConnect(server, client);
|
||||
}
|
||||
} else if (type == WS_EVT_DISCONNECT) {
|
||||
if (_onDisconnect) {
|
||||
_onDisconnect(server, client->id());
|
||||
}
|
||||
} else if (type == WS_EVT_ERROR) {
|
||||
if (_onError) {
|
||||
_onError(server, client, *((uint16_t *)arg), (const char *)data, len);
|
||||
}
|
||||
} else if (type == WS_EVT_DATA) {
|
||||
AwsFrameInfo *info = (AwsFrameInfo *)arg;
|
||||
if (info->opcode == WS_TEXT) {
|
||||
data[len] = 0;
|
||||
}
|
||||
if (info->final && info->index == 0 && info->len == len) {
|
||||
if (_onMessage) {
|
||||
_onMessage(server, client, data, len);
|
||||
}
|
||||
} else {
|
||||
if (_onFragment) {
|
||||
_onFragment(server, client, info, data, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
#endif /* ASYNCWEBSOCKET_H_ */
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
#ifndef _ESPAsyncWebServer_H_
|
||||
#define _ESPAsyncWebServer_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
#include <lwip/tcpbase.h>
|
||||
|
||||
#include "FS.h"
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
|
@ -14,16 +15,13 @@
|
|||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ESP32
|
||||
#if defined(ESP32) || defined(LIBRETINY)
|
||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||
#include <WiFi.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
#include <RPAsyncTCP.h>
|
||||
#include <HTTP_Method.h>
|
||||
#include <WiFi.h>
|
||||
#include <http_parser.h>
|
||||
#else
|
||||
#error Platform not supported
|
||||
|
@ -131,12 +129,20 @@ private:
|
|||
String _value;
|
||||
|
||||
public:
|
||||
AsyncWebHeader() {}
|
||||
AsyncWebHeader(const AsyncWebHeader &) = default;
|
||||
AsyncWebHeader(AsyncWebHeader &&) = default;
|
||||
AsyncWebHeader(const char *name, const char *value) : _name(name), _value(value) {}
|
||||
AsyncWebHeader(const String &name, const String &value) : _name(name), _value(value) {}
|
||||
AsyncWebHeader(const String &data);
|
||||
|
||||
#ifndef ESP8266
|
||||
[[deprecated("Use AsyncWebHeader::parse(data) instead")]]
|
||||
#endif
|
||||
AsyncWebHeader(const String &data)
|
||||
: AsyncWebHeader(parse(data)){};
|
||||
|
||||
AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
|
||||
AsyncWebHeader &operator=(AsyncWebHeader &&other) = default;
|
||||
|
||||
const String &name() const {
|
||||
return _name;
|
||||
|
@ -144,7 +150,18 @@ public:
|
|||
const String &value() const {
|
||||
return _value;
|
||||
}
|
||||
|
||||
String toString() const;
|
||||
|
||||
// returns true if the header is valid
|
||||
operator bool() const {
|
||||
return _name.length();
|
||||
}
|
||||
|
||||
static const AsyncWebHeader parse(const String &data) {
|
||||
return parse(data.c_str());
|
||||
}
|
||||
static const AsyncWebHeader parse(const char *data);
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -180,6 +197,7 @@ class AsyncWebServerRequest {
|
|||
using FS = fs::FS;
|
||||
friend class AsyncWebServer;
|
||||
friend class AsyncCallbackWebHandler;
|
||||
friend class AsyncFileResponse;
|
||||
|
||||
private:
|
||||
AsyncClient *_client;
|
||||
|
@ -251,6 +269,8 @@ private:
|
|||
void _send();
|
||||
void _runMiddlewareChain();
|
||||
|
||||
static void _getEtag(uint8_t trailer[4], char *serverETag);
|
||||
|
||||
public:
|
||||
File _tempFile;
|
||||
void *_tempObject;
|
||||
|
@ -363,13 +383,7 @@ public:
|
|||
send(beginResponse(code, contentType, content, len, callback));
|
||||
}
|
||||
|
||||
void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
|
||||
if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) {
|
||||
send(beginResponse(fs, path, contentType, download, callback));
|
||||
} else {
|
||||
send(404);
|
||||
}
|
||||
}
|
||||
void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||
void send(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
|
||||
send(fs, path, contentType.c_str(), download, callback);
|
||||
}
|
||||
|
@ -462,7 +476,9 @@ public:
|
|||
}
|
||||
|
||||
AsyncWebServerResponse *beginChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
AsyncWebServerResponse *beginChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
AsyncWebServerResponse *beginChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
|
||||
return beginChunkedResponse(contentType.c_str(), callback, templateCallback);
|
||||
}
|
||||
|
||||
AsyncResponseStream *beginResponseStream(const char *contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE);
|
||||
AsyncResponseStream *beginResponseStream(const String &contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) {
|
||||
|
@ -531,6 +547,9 @@ public:
|
|||
* @return const AsyncWebParameter*
|
||||
*/
|
||||
const AsyncWebParameter *getParam(size_t num) const;
|
||||
const AsyncWebParameter *getParam(int num) const {
|
||||
return num < 0 ? nullptr : getParam((size_t)num);
|
||||
}
|
||||
|
||||
size_t args() const {
|
||||
return params();
|
||||
|
@ -545,9 +564,15 @@ public:
|
|||
#ifdef ESP8266
|
||||
const String &arg(const __FlashStringHelper *data) const; // get request argument value by F(name)
|
||||
#endif
|
||||
const String &arg(size_t i) const; // get request argument value by number
|
||||
const String &arg(size_t i) const; // get request argument value by number
|
||||
const String &arg(int i) const {
|
||||
return i < 0 ? emptyString : arg((size_t)i);
|
||||
};
|
||||
const String &argName(size_t i) const; // get request argument name by number
|
||||
bool hasArg(const char *name) const; // check if argument exists
|
||||
const String &argName(int i) const {
|
||||
return i < 0 ? emptyString : argName((size_t)i);
|
||||
};
|
||||
bool hasArg(const char *name) const; // check if argument exists
|
||||
bool hasArg(const String &name) const {
|
||||
return hasArg(name.c_str());
|
||||
};
|
||||
|
@ -556,6 +581,9 @@ public:
|
|||
#endif
|
||||
|
||||
const String &ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
|
||||
const String &ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(int i) const {
|
||||
return i < 0 ? emptyString : pathArg((size_t)i);
|
||||
}
|
||||
|
||||
// get request header value by name
|
||||
const String &header(const char *name) const;
|
||||
|
@ -567,8 +595,14 @@ public:
|
|||
const String &header(const __FlashStringHelper *data) const; // get request header value by F(name)
|
||||
#endif
|
||||
|
||||
const String &header(size_t i) const; // get request header value by number
|
||||
const String &header(size_t i) const; // get request header value by number
|
||||
const String &header(int i) const {
|
||||
return i < 0 ? emptyString : header((size_t)i);
|
||||
};
|
||||
const String &headerName(size_t i) const; // get request header name by number
|
||||
const String &headerName(int i) const {
|
||||
return i < 0 ? emptyString : headerName((size_t)i);
|
||||
};
|
||||
|
||||
size_t headers() const; // get header count
|
||||
|
||||
|
@ -590,6 +624,9 @@ public:
|
|||
#endif
|
||||
|
||||
const AsyncWebHeader *getHeader(size_t num) const;
|
||||
const AsyncWebHeader *getHeader(int num) const {
|
||||
return num < 0 ? nullptr : getHeader((size_t)num);
|
||||
};
|
||||
|
||||
const std::list<AsyncWebHeader> &getHeaders() const {
|
||||
return _headers;
|
||||
|
@ -1011,6 +1048,10 @@ public:
|
|||
setContentType(type.c_str());
|
||||
}
|
||||
void setContentType(const char *type);
|
||||
bool addHeader(AsyncWebHeader &&header, bool replaceExisting = true);
|
||||
bool addHeader(const AsyncWebHeader &header, bool replaceExisting = true) {
|
||||
return header && addHeader(header.name(), header.value(), replaceExisting);
|
||||
}
|
||||
bool addHeader(const char *name, const char *value, bool replaceExisting = true);
|
||||
bool addHeader(const String &name, const String &value, bool replaceExisting = true) {
|
||||
return addHeader(name.c_str(), value.c_str(), replaceExisting);
|
||||
|
@ -1069,6 +1110,15 @@ public:
|
|||
void begin();
|
||||
void end();
|
||||
|
||||
tcp_state state() const {
|
||||
#ifdef ESP8266
|
||||
// ESPAsyncTCP and RPAsyncTCP methods are not corrected declared with const for immutable ones.
|
||||
return static_cast<tcp_state>(const_cast<AsyncWebServer *>(this)->_server.status());
|
||||
#else
|
||||
return static_cast<tcp_state>(_server.status());
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void onSslFileRequest(AcSSlFileHandler cb, void *arg);
|
||||
void beginSecure(const char *cert, const char *private_key_file, const char *password);
|
||||
|
|
|
@ -172,7 +172,11 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNex
|
|||
return;
|
||||
}
|
||||
_out->print(F("* Connection from "));
|
||||
#ifndef LIBRETINY
|
||||
_out->print(request->client()->remoteIP().toString());
|
||||
#else
|
||||
_out->print(request->client()->remoteIP());
|
||||
#endif
|
||||
_out->print(':');
|
||||
_out->println(request->client()->remotePort());
|
||||
_out->print('>');
|
||||
|
|
|
@ -19,7 +19,6 @@ class AsyncStaticWebHandler : public AsyncWebHandler {
|
|||
private:
|
||||
bool _getFile(AsyncWebServerRequest *request) const;
|
||||
bool _searchFile(AsyncWebServerRequest *request, const String &path);
|
||||
uint8_t _countBits(const uint8_t value) const;
|
||||
|
||||
protected:
|
||||
FS _fs;
|
||||
|
|
|
@ -187,15 +187,6 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest *request, const St
|
|||
return found;
|
||||
}
|
||||
|
||||
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
|
||||
uint8_t w = value;
|
||||
uint8_t n;
|
||||
for (n = 0; w != 0; n++) {
|
||||
w &= w - 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||
// Get the filename from request->_tempObject and free it
|
||||
String filename((char *)request->_tempObject);
|
||||
|
@ -218,11 +209,14 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
|||
char buf[len];
|
||||
char *ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10);
|
||||
etag = ret ? String(ret) : String(request->_tempFile.size());
|
||||
#elif defined(LIBRETINY)
|
||||
long val = lw ^ request->_tempFile.size();
|
||||
etag = String(val);
|
||||
#else
|
||||
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
|
||||
#endif
|
||||
} else {
|
||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
|
||||
etag = String(request->_tempFile.size());
|
||||
#else
|
||||
etag = request->_tempFile.size();
|
||||
|
|
|
@ -22,10 +22,10 @@ enum {
|
|||
};
|
||||
|
||||
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
|
||||
: _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(),
|
||||
_contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false),
|
||||
_expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(),
|
||||
_itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
|
||||
: _client(c), _server(s), _handler(NULL), _response(NULL), _onDisconnectfn(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY),
|
||||
_url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false),
|
||||
_isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0),
|
||||
_itemSize(0), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
|
||||
c->onError(
|
||||
[](void *r, AsyncClient *c, int8_t error) {
|
||||
(void)c;
|
||||
|
@ -341,10 +341,10 @@ bool AsyncWebServerRequest::_parseReqHead() {
|
|||
}
|
||||
|
||||
bool AsyncWebServerRequest::_parseReqHeader() {
|
||||
int index = _temp.indexOf(':');
|
||||
if (index) {
|
||||
String name(_temp.substring(0, index));
|
||||
String value(_temp.substring(index + 2));
|
||||
AsyncWebHeader header = AsyncWebHeader::parse(_temp);
|
||||
if (header) {
|
||||
const String &name = header.name();
|
||||
const String &value = header.value();
|
||||
if (name.equalsIgnoreCase(T_Host)) {
|
||||
_host = value;
|
||||
} else if (name.equalsIgnoreCase(T_Content_Type)) {
|
||||
|
@ -392,9 +392,9 @@ bool AsyncWebServerRequest::_parseReqHeader() {
|
|||
_reqconntype = RCT_EVENT;
|
||||
}
|
||||
}
|
||||
_headers.emplace_back(name, value);
|
||||
_headers.emplace_back(std::move(header));
|
||||
}
|
||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
|
||||
// Ancient PRI core does not have String::clear() method 8-()
|
||||
_temp = emptyString;
|
||||
#else
|
||||
|
@ -419,7 +419,7 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
|
|||
_params.emplace_back(name, urlDecode(value), true);
|
||||
}
|
||||
|
||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
|
||||
// Ancient PRI core does not have String::clear() method 8-()
|
||||
_temp = emptyString;
|
||||
#else
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#undef max
|
||||
#endif
|
||||
#include "literals.h"
|
||||
#include <StreamString.h>
|
||||
#include <cbuf.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
@ -157,7 +157,7 @@ public:
|
|||
|
||||
class AsyncResponseStream : public AsyncAbstractResponse, public Print {
|
||||
private:
|
||||
StreamString _content;
|
||||
std::unique_ptr<cbuf> _content;
|
||||
|
||||
public:
|
||||
AsyncResponseStream(const char *contentType, size_t bufferSize);
|
||||
|
@ -168,6 +168,12 @@ public:
|
|||
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
|
||||
size_t write(const uint8_t *data, size_t len);
|
||||
size_t write(uint8_t data);
|
||||
/**
|
||||
* @brief Returns the number of bytes available in the stream.
|
||||
*/
|
||||
size_t available() const {
|
||||
return _content->available();
|
||||
}
|
||||
using Print::write;
|
||||
};
|
||||
|
||||
|
|
|
@ -134,6 +134,30 @@ bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AsyncWebServerResponse::addHeader(AsyncWebHeader &&header, bool replaceExisting) {
|
||||
if (!header) {
|
||||
return false; // invalid header
|
||||
}
|
||||
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
|
||||
if (i->name().equalsIgnoreCase(header.name())) {
|
||||
// header already set
|
||||
if (replaceExisting) {
|
||||
// remove, break and add the new one
|
||||
_headers.erase(i);
|
||||
break;
|
||||
} else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
|
||||
// do not update
|
||||
return false;
|
||||
} else {
|
||||
break; // accept multiple headers with the same name
|
||||
}
|
||||
}
|
||||
}
|
||||
// header was not found found, or existing one was removed
|
||||
_headers.emplace_back(std::move(header));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
|
||||
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
|
||||
if (i->name().equalsIgnoreCase(name)) {
|
||||
|
@ -595,6 +619,16 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size
|
|||
* File Response
|
||||
* */
|
||||
|
||||
/**
|
||||
* @brief Sets the content type based on the file path extension
|
||||
*
|
||||
* This method determines the appropriate MIME content type for a file based on its
|
||||
* file extension. It supports both external content type functions (if available)
|
||||
* and an internal mapping of common file extensions to their corresponding MIME types.
|
||||
*
|
||||
* @param path The file path string from which to extract the extension
|
||||
* @note The method modifies the internal _contentType member variable
|
||||
*/
|
||||
void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
|
||||
#if HAVE_EXTERN_GET_Content_Type_FUNCTION
|
||||
#ifndef ESP8266
|
||||
|
@ -604,41 +638,47 @@ void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
|
|||
#endif
|
||||
_contentType = getContentType(path);
|
||||
#else
|
||||
if (path.endsWith(T__html)) {
|
||||
const char *cpath = path.c_str();
|
||||
const char *dot = strrchr(cpath, '.');
|
||||
|
||||
if (!dot) {
|
||||
_contentType = T_text_plain;
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(dot, T__html) == 0 || strcmp(dot, T__htm) == 0) {
|
||||
_contentType = T_text_html;
|
||||
} else if (path.endsWith(T__htm)) {
|
||||
_contentType = T_text_html;
|
||||
} else if (path.endsWith(T__css)) {
|
||||
} else if (strcmp(dot, T__css) == 0) {
|
||||
_contentType = T_text_css;
|
||||
} else if (path.endsWith(T__json)) {
|
||||
_contentType = T_application_json;
|
||||
} else if (path.endsWith(T__js)) {
|
||||
} else if (strcmp(dot, T__js) == 0) {
|
||||
_contentType = T_application_javascript;
|
||||
} else if (path.endsWith(T__png)) {
|
||||
} else if (strcmp(dot, T__json) == 0) {
|
||||
_contentType = T_application_json;
|
||||
} else if (strcmp(dot, T__png) == 0) {
|
||||
_contentType = T_image_png;
|
||||
} else if (path.endsWith(T__gif)) {
|
||||
_contentType = T_image_gif;
|
||||
} else if (path.endsWith(T__jpg)) {
|
||||
_contentType = T_image_jpeg;
|
||||
} else if (path.endsWith(T__ico)) {
|
||||
} else if (strcmp(dot, T__ico) == 0) {
|
||||
_contentType = T_image_x_icon;
|
||||
} else if (path.endsWith(T__svg)) {
|
||||
} else if (strcmp(dot, T__svg) == 0) {
|
||||
_contentType = T_image_svg_xml;
|
||||
} else if (path.endsWith(T__eot)) {
|
||||
_contentType = T_font_eot;
|
||||
} else if (path.endsWith(T__woff)) {
|
||||
_contentType = T_font_woff;
|
||||
} else if (path.endsWith(T__woff2)) {
|
||||
} else if (strcmp(dot, T__jpg) == 0) {
|
||||
_contentType = T_image_jpeg;
|
||||
} else if (strcmp(dot, T__gif) == 0) {
|
||||
_contentType = T_image_gif;
|
||||
} else if (strcmp(dot, T__woff2) == 0) {
|
||||
_contentType = T_font_woff2;
|
||||
} else if (path.endsWith(T__ttf)) {
|
||||
} else if (strcmp(dot, T__woff) == 0) {
|
||||
_contentType = T_font_woff;
|
||||
} else if (strcmp(dot, T__ttf) == 0) {
|
||||
_contentType = T_font_ttf;
|
||||
} else if (path.endsWith(T__xml)) {
|
||||
} else if (strcmp(dot, T__eot) == 0) {
|
||||
_contentType = T_font_eot;
|
||||
} else if (strcmp(dot, T__xml) == 0) {
|
||||
_contentType = T_text_xml;
|
||||
} else if (path.endsWith(T__pdf)) {
|
||||
} else if (strcmp(dot, T__pdf) == 0) {
|
||||
_contentType = T_application_pdf;
|
||||
} else if (path.endsWith(T__zip)) {
|
||||
} else if (strcmp(dot, T__zip) == 0) {
|
||||
_contentType = T_application_zip;
|
||||
} else if (path.endsWith(T__gz)) {
|
||||
} else if (strcmp(dot, T__gz) == 0) {
|
||||
_contentType = T_application_x_gzip;
|
||||
} else {
|
||||
_contentType = T_text_plain;
|
||||
|
@ -646,40 +686,73 @@ void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
|
|||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructor for AsyncFileResponse that handles file serving with compression support
|
||||
*
|
||||
* This constructor creates an AsyncFileResponse object that can serve files from a filesystem,
|
||||
* with automatic fallback to gzip-compressed versions if the original file is not found.
|
||||
* It also handles ETag generation for caching and supports both inline and download modes.
|
||||
*
|
||||
* @param fs Reference to the filesystem object used to open files
|
||||
* @param path Path to the file to be served (without compression extension)
|
||||
* @param contentType MIME type of the file content (empty string for auto-detection)
|
||||
* @param download If true, file will be served as download attachment; if false, as inline content
|
||||
* @param callback Template processor callback for dynamic content processing
|
||||
*/
|
||||
AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
|
||||
: AsyncAbstractResponse(callback) {
|
||||
_code = 200;
|
||||
_path = path;
|
||||
// Try to open the uncompressed version first
|
||||
_content = fs.open(path, fs::FileOpenMode::read);
|
||||
if (_content.available()) {
|
||||
_path = path;
|
||||
_contentLength = _content.size();
|
||||
} else {
|
||||
// Try to open the compressed version (.gz)
|
||||
_path = path + asyncsrv::T__gz;
|
||||
_content = fs.open(_path, fs::FileOpenMode::read);
|
||||
_contentLength = _content.size();
|
||||
|
||||
if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) {
|
||||
_path = _path + T__gz;
|
||||
addHeader(T_Content_Encoding, T_gzip, false);
|
||||
_callback = nullptr; // Unable to process zipped templates
|
||||
_sendContentLength = true;
|
||||
_chunked = false;
|
||||
if (_content.seek(_contentLength - 8)) {
|
||||
addHeader(T_Content_Encoding, T_gzip, false);
|
||||
_callback = nullptr; // Unable to process zipped templates
|
||||
_sendContentLength = true;
|
||||
_chunked = false;
|
||||
|
||||
// Add ETag and cache headers
|
||||
uint8_t crcInTrailer[4];
|
||||
_content.read(crcInTrailer, sizeof(crcInTrailer));
|
||||
char serverETag[9];
|
||||
AsyncWebServerRequest::_getEtag(crcInTrailer, serverETag);
|
||||
addHeader(T_ETag, serverETag, true);
|
||||
addHeader(T_Cache_Control, T_no_cache, true);
|
||||
|
||||
_content.seek(0);
|
||||
} else {
|
||||
// File is corrupted or invalid
|
||||
_code = 404;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_content = fs.open(_path, fs::FileOpenMode::read);
|
||||
_contentLength = _content.size();
|
||||
|
||||
if (strlen(contentType) == 0) {
|
||||
if (*contentType != '\0') {
|
||||
_setContentTypeFromPath(path);
|
||||
} else {
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
int filenameStart = path.lastIndexOf('/') + 1;
|
||||
char buf[26 + path.length() - filenameStart];
|
||||
char *filename = (char *)path.c_str() + filenameStart;
|
||||
|
||||
if (download) {
|
||||
// set filename and force download
|
||||
// Extract filename from path and set as download attachment
|
||||
int filenameStart = path.lastIndexOf('/') + 1;
|
||||
char buf[26 + path.length() - filenameStart];
|
||||
char *filename = (char *)path.c_str() + filenameStart;
|
||||
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
|
||||
addHeader(T_Content_Disposition, buf, false);
|
||||
} else {
|
||||
// set filename and force rendering
|
||||
snprintf_P(buf, sizeof(buf), PSTR("inline"));
|
||||
// Serve file inline (display in browser)
|
||||
addHeader(T_Content_Disposition, PSTR("inline"), false);
|
||||
}
|
||||
addHeader(T_Content_Disposition, buf, false);
|
||||
|
||||
_code = 200;
|
||||
}
|
||||
|
||||
AsyncFileResponse::AsyncFileResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
|
||||
|
@ -820,7 +893,9 @@ AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferS
|
|||
_code = 200;
|
||||
_contentLength = 0;
|
||||
_contentType = contentType;
|
||||
if (!_content.reserve(bufferSize)) {
|
||||
// internal buffer will be null on allocation failure
|
||||
_content = std::unique_ptr<cbuf>(new cbuf(bufferSize));
|
||||
if (bufferSize && _content->size() < bufferSize) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
|
@ -828,14 +903,26 @@ AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferS
|
|||
}
|
||||
|
||||
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) {
|
||||
return _content.readBytes((char *)buf, maxLen);
|
||||
return _content->read((char *)buf, maxLen);
|
||||
}
|
||||
|
||||
size_t AsyncResponseStream::write(const uint8_t *data, size_t len) {
|
||||
if (_started()) {
|
||||
return 0;
|
||||
}
|
||||
size_t written = _content.write(data, len);
|
||||
if (len > _content->room()) {
|
||||
size_t needed = len - _content->room();
|
||||
_content->resizeAdd(needed);
|
||||
// log a warning if allocation failed, but do not return: keep writing the bytes we can
|
||||
// with _content->write: if len is more than the available size in the buffer, only
|
||||
// the available size will be written
|
||||
if (len > _content->room()) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
size_t written = _content->write((const char *)data, len);
|
||||
_contentLength += written;
|
||||
return written;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,18 @@
|
|||
#include "ESPAsyncWebServer.h"
|
||||
#include "WebHandlerImpl.h"
|
||||
|
||||
#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
|
||||
#include <WiFi.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#else
|
||||
#error Platform not supported
|
||||
#endif
|
||||
|
||||
using namespace asyncsrv;
|
||||
|
||||
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
|
||||
return WiFi.localIP() == request->client()->localIP();
|
||||
#else
|
||||
return false;
|
||||
|
@ -15,7 +23,7 @@ bool ON_STA_FILTER(AsyncWebServerRequest *request) {
|
|||
}
|
||||
|
||||
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
|
||||
return WiFi.localIP() != request->client()->localIP();
|
||||
#else
|
||||
return false;
|
||||
|
|
|
@ -213,6 +213,12 @@
|
|||
# pragma GCC system_header
|
||||
# endif
|
||||
#endif
|
||||
#ifdef true
|
||||
# undef true
|
||||
#endif
|
||||
#ifdef false
|
||||
# undef false
|
||||
#endif
|
||||
#define ARDUINOJSON_CONCAT_(A, B) A##B
|
||||
#define ARDUINOJSON_CONCAT2(A, B) ARDUINOJSON_CONCAT_(A, B)
|
||||
#define ARDUINOJSON_CONCAT3(A, B, C) \
|
||||
|
@ -239,11 +245,11 @@
|
|||
#define ARDUINOJSON_BIN2ALPHA_1111() P
|
||||
#define ARDUINOJSON_BIN2ALPHA_(A, B, C, D) ARDUINOJSON_BIN2ALPHA_##A##B##C##D()
|
||||
#define ARDUINOJSON_BIN2ALPHA(A, B, C, D) ARDUINOJSON_BIN2ALPHA_(A, B, C, D)
|
||||
#define ARDUINOJSON_VERSION "7.4.1"
|
||||
#define ARDUINOJSON_VERSION "7.4.2"
|
||||
#define ARDUINOJSON_VERSION_MAJOR 7
|
||||
#define ARDUINOJSON_VERSION_MINOR 4
|
||||
#define ARDUINOJSON_VERSION_REVISION 1
|
||||
#define ARDUINOJSON_VERSION_MACRO V741
|
||||
#define ARDUINOJSON_VERSION_REVISION 2
|
||||
#define ARDUINOJSON_VERSION_MACRO V742
|
||||
#ifndef ARDUINOJSON_VERSION_NAMESPACE
|
||||
# define ARDUINOJSON_VERSION_NAMESPACE \
|
||||
ARDUINOJSON_CONCAT5( \
|
||||
|
|
|
@ -300,7 +300,7 @@ class AsyncServer : public AsyncSocketBase
|
|||
|
||||
void setNoDelay(bool nodelay) { _noDelay = nodelay; }
|
||||
bool getNoDelay() { return _noDelay; }
|
||||
uint8_t status();
|
||||
uint8_t status() const;
|
||||
|
||||
protected:
|
||||
uint16_t _port;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue