Merge branch 'main' into feature/bmw-sbox

This commit is contained in:
rha 2024-12-21 22:24:20 +02:00
commit b12480b6af
62 changed files with 4241 additions and 2627 deletions

View file

@ -23,6 +23,6 @@
- Inverter communication protocol: ``
- Hardware used for Battery-Emulator: `HW_LILYGO, HW_STARK, Custom`
- CONTACTOR_CONTROL: `yes/no`
- DUAL_CAN: `yes/no`
- CAN_ADDON: `yes/no`
- WEBSERVER: `yes/no`
- MQTT: `yes/no`

View file

@ -9,12 +9,28 @@ on:
- pull_request
# This is the list of jobs that will be run concurrently.
# Since we use a build matrix, the actual number of jobs
# started depends on how many configurations the matrix
# will produce.
jobs:
# This is the name of the job
# This pre-job is run to skip workflows in case a workflow is already run, i.e. because the workflow is triggered by both push and pull_request
skip-duplicate-actions:
runs-on: ubuntu-latest
# Map a step output to a job output
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true'
do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
# Since we use a build matrix, the actual number of jobs
# started depends on how many configurations the matrix
# will produce.
build-batteries:
needs: skip-duplicate-actions
if: needs.skip-duplicate-actions.outputs.should_skip != 'true'
# Here we tell GitHub that the jobs must be determined
# dynamically depending on a matrix configuration.
@ -40,8 +56,8 @@ jobs:
- CHADEMO_BATTERY
- IMIEV_CZERO_ION_BATTERY
- JAGUAR_IPACE_BATTERY
- KIA_HYUNDAI_64_BATTERY
- KIA_E_GMP_BATTERY
- KIA_HYUNDAI_64_BATTERY
- KIA_HYUNDAI_HYBRID_BATTERY
- MEB_BATTERY
- MG_5_BATTERY
@ -55,6 +71,7 @@ jobs:
- RENAULT_ZOE_GEN2_BATTERY
- SANTA_FE_PHEV_BATTERY
- TESLA_MODEL_3Y_BATTERY
- TESLA_MODEL_SX_BATTERY
- VOLVO_SPA_BATTERY
- TEST_FAKE_BATTERY
- SERIAL_LINK_RECEIVER

View file

@ -0,0 +1,111 @@
# This is the name of the workflow, visible on GitHub UI.
name: Compile All Combinations
# Here we tell GitHub when to run the workflow.
on:
# This allows you to run this workflow manually from the
# GitHub Actions tab.
workflow_dispatch:
# The workflow is run upon creating, editing,
# pre-releasing, releasing and publishing a release
release:
types: [created, edited, prereleased, released, published]
# This is the list of jobs that will be run concurrently.
jobs:
# This pre-job is run to skip workflows in case a workflow is already run, i.e. because the workflow is triggered by both push and pull_request
skip-duplicate-actions:
runs-on: ubuntu-latest
# Map a step output to a job output
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true'
do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
# Since we use a build matrix, the actual number of jobs
# started depends on how many configurations the matrix
# will produce.
# This is the name of the job.
build-matrix:
needs: skip-duplicate-actions
if: needs.skip-duplicate-actions.outputs.should_skip != 'true'
# Here we tell GitHub that the jobs must be determined
# dynamically depending on a matrix configuration.
strategy:
# The matrix will produce one job for each combination of parameters.
matrix:
# This is the development board hardware for which the code will be compiled.
# FBQN stands for "fully qualified board name", and is used by Arduino to define the hardware to compile for.
fqbn:
- esp32:esp32:esp32
# further ESP32 chips
#- esp32:esp32:esp32c3
#- esp32:esp32:esp32c2
#- esp32:esp32:esp32c6
#- esp32:esp32:esp32h2
#- esp32:esp32:esp32s3
# These are the batteries for which the code will be compiled.
battery:
- BMW_I3_BATTERY
- BMW_IX_BATTERY
- BYD_ATTO_3_BATTERY
- CELLPOWER_BMS
- CHADEMO_BATTERY
- IMIEV_CZERO_ION_BATTERY
- JAGUAR_IPACE_BATTERY
- KIA_E_GMP_BATTERY
- KIA_HYUNDAI_64_BATTERY
- KIA_HYUNDAI_HYBRID_BATTERY
- MEB_BATTERY
- MG_5_BATTERY
# These are the emulated inverter communication protocols for which the code will be compiled.
inverter:
- AFORE_CAN
- BYD_CAN
- BYD_KOSTAL_RS485
- BYD_MODBUS
- BYD_SMA
- FOXESS_CAN
- PYLON_CAN
- PYLON_LV_CAN
- SCHNEIDER_CAN
- SMA_CAN
- SMA_LV_CAN
- SMA_TRIPOWER_CAN
- SOFAR_CAN
- SOLAX_CAN
- SERIAL_LINK_TRANSMITTER
# This is the platform GitHub will use to run our workflow.
runs-on: ubuntu-latest
# This is the list of steps this job will run.
steps:
# First we clone the repo using the `checkout` action.
- name: Checkout
uses: actions/checkout@v4
# We use the `arduino/setup-arduino-cli` action to install and
# configure the Arduino CLI on the system.
- name: Setup Arduino CLI
uses: arduino/setup-arduino-cli@v2
# We then install the platform.
- name: Install platform
run: |
arduino-cli core update-index
arduino-cli core install esp32:esp32
# Finally, we compile the sketch, using the FQBN that was set
# in the build matrix, and using build flags to define the
# battery and inverter set in the build matrix.
- name: Compile Sketch
run: arduino-cli compile --fqbn ${{ matrix.fqbn }} --build-property "build.extra_flags=-Wall -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}}" ./Software

View file

@ -12,12 +12,30 @@ on:
types: [created, edited, prereleased, released, published]
# This is the list of jobs that will be run concurrently.
# Since we use a build matrix, the actual number of jobs
# started depends on how many configurations the matrix
# will produce.
jobs:
# This pre-job is run to skip workflows in case a workflow is already run, i.e. because the workflow is triggered by both push and pull_request
skip-duplicate-actions:
runs-on: ubuntu-latest
# Map a step output to a job output
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true'
do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
# Since we use a build matrix, the actual number of jobs
# started depends on how many configurations the matrix
# will produce.
# This is the name of the job.
build-matrix:
needs: skip-duplicate-actions
if: needs.skip-duplicate-actions.outputs.should_skip != 'true'
# Here we tell GitHub that the jobs must be determined
# dynamically depending on a matrix configuration.
@ -36,30 +54,32 @@ jobs:
#- esp32:esp32:esp32s3
# These are the batteries for which the code will be compiled.
battery:
- BMW_I3_BATTERY
- BYD_ATTO_3_BATTERY
- CHADEMO_BATTERY
- IMIEV_CZERO_ION_BATTERY
- KIA_HYUNDAI_64_BATTERY
- KIA_HYUNDAI_HYBRID_BATTERY
- NISSAN_LEAF_BATTERY
- PYLON_BATTERY
- RJXZS_BMS
- RANGE_ROVER_PHEV_BATTERY
- RENAULT_KANGOO_BATTERY
- RENAULT_TWIZY_BATTERY
- RENAULT_ZOE_GEN1_BATTERY
- RENAULT_ZOE_GEN2_BATTERY
- SANTA_FE_PHEV_BATTERY
- TESLA_MODEL_3Y_BATTERY
- TESLA_MODEL_SX_BATTERY
- VOLVO_SPA_BATTERY
- TEST_FAKE_BATTERY
# These are the emulated inverter communication protocols for which the code will be compiled.
inverter:
- AFORE_CAN
- BYD_CAN
- BYD_KOSTAL_RS485
- BYD_SMA
- BYD_MODBUS
- BYD_SMA
- FOXESS_CAN
- PYLON_CAN
- PYLON_LV_CAN
- SCHNEIDER_CAN
- SMA_CAN
- SMA_LV_CAN
- SMA_TRIPOWER_CAN
- SOFAR_CAN
- SOLAX_CAN

View file

@ -9,12 +9,30 @@ on:
- pull_request
# This is the list of jobs that will be run concurrently.
# Since we use a build matrix, the actual number of jobs
# started depends on how many configurations the matrix
# will produce.
jobs:
# This pre-job is run to skip workflows in case a workflow is already run, i.e. because the workflow is triggered by both push and pull_request
skip-duplicate-actions:
runs-on: ubuntu-latest
# Map a step output to a job output
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
# All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true'
do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
# Since we use a build matrix, the actual number of jobs
# started depends on how many configurations the matrix
# will produce.
# This is the name of the job.
build-inverters:
needs: skip-duplicate-actions
if: needs.skip-duplicate-actions.outputs.should_skip != 'true'
# Here we tell GitHub that the jobs must be determined
# dynamically depending on a matrix configuration.
@ -33,30 +51,25 @@ jobs:
#- esp32:esp32:esp32s3
# These are the batteries for which the code will be compiled.
battery:
# - BMW_I3_BATTERY
# - CHADEMO_BATTERY
# - IMIEV_CZERO_ION_BATTERY
# - KIA_HYUNDAI_64_BATTERY
- NISSAN_LEAF_BATTERY
# - RENAULT_ZOE_BATTERY
# - TESLA_MODEL_3Y_BATTERY
# These are the emulated inverter communication protocols for which the code will be compiled.
inverter:
- AFORE_CAN
- BYD_CAN
- BYD_KOSTAL_RS485
- BYD_SMA
- BYD_MODBUS
- BYD_SMA
- FOXESS_CAN
- PYLON_LV_CAN
- PYLON_CAN
- PYLON_LV_CAN
- SCHNEIDER_CAN
- SMA_CAN
- SMA_LV_CAN
- SMA_TRIPOWER_CAN
- SOFAR_CAN
- SOLAX_CAN
- SERIAL_LINK_TRANSMITTER
- NISSANLEAF_CHARGER # Last element is not an inverter, but good to also test if charger compiles
- NISSANLEAF_CHARGER # Last element is not an inverter, but good to also test if the charger compiles
# This is the platform GitHub will use to run our workflow.
runs-on: ubuntu-latest

View file

@ -36,7 +36,7 @@ Here's how to wire up the communication between the components.
Here's how to connect the high voltage lines
![HighVoltageWiring](https://github.com/dalathegreat/Battery-Emulator/assets/26695010/f70e6262-d630-4148-9a39-dad32e79b3d6)
For more examples showing wiring, see each battery types own Wiki page. For instance the [Nissan LEAF page](https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki/Nissan-LEAF-battery#wiring-diagram)
For more examples showing wiring, see each battery types own Wiki page. For instance the [Nissan LEAF page](https://github.com/dalathegreat/Battery-Emulator/wiki/Battery:-Nissan-LEAF---e%E2%80%90NV200)
## How to compile the software 💻
1. Download the Arduino IDE: https://www.arduino.cc/en/software
@ -83,7 +83,7 @@ This code uses the following excellent libraries:
- [eModbus/eModbus](https://github.com/eModbus/eModbus) MIT-License
- [knolleary/pubsubclient](https://github.com/knolleary/pubsubclient) MIT-License
- [mackelec/SerialDataLink](https://github.com/mackelec/SerialDataLink)
- [me-no-dev/AsyncTCP](https://github.com/me-no-dev/AsyncTCP) LGPL-3.0 license
- [mathieucarbou/AsyncTCP](https://github.com/mathieucarbou/AsyncTCP) LGPL-3.0 license
- [me-no-dev/ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
- [miwagner/ESP32-Arduino-CAN](https://github.com/miwagner/ESP32-Arduino-CAN/) MIT-License
- [pierremolinaro/acan2515](https://github.com/pierremolinaro/acan2515) MIT-License

View file

@ -11,6 +11,12 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "src/charger/CHARGERS.h"
#include "src/communication/can/comm_can.h"
#include "src/communication/contactorcontrol/comm_contactorcontrol.h"
#include "src/communication/equipmentstopbutton/comm_equipmentstopbutton.h"
#include "src/communication/nvm/comm_nvm.h"
#include "src/communication/rs485/comm_rs485.h"
#include "src/communication/seriallink/comm_seriallink.h"
#include "src/datalayer/datalayer.h"
#include "src/devboard/utils/events.h"
#include "src/devboard/utils/led_handler.h"
@ -41,17 +47,6 @@
#endif // MQTT
#endif // WIFI
#ifndef CONTACTOR_CONTROL
#ifdef PWM_CONTACTOR_CONTROL
#error CONTACTOR_CONTROL needs to be enabled for PWM_CONTACTOR_CONTROL
#endif
#endif
#ifdef EQUIPMENT_STOP_BUTTON
#include "src/devboard/utils/debounce_button.h"
#endif
Preferences settings; // Store user settings
// The current software version, shown on webserver
const char* version_number = "8.0.dev";
@ -60,33 +55,6 @@ uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update in
unsigned long previousMillis10ms = 0;
unsigned long previousMillisUpdateVal = 0;
// CAN parameters
CAN_device_t CAN_cfg; // CAN Config
const int rx_queue_size = 10; // Receive Queue size
volatile bool send_ok = 0;
#ifdef DUAL_CAN
#include "src/lib/pierremolinaro-acan2515/ACAN2515.h"
static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h
ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT);
static ACAN2515_Buffer16 gBuffer;
#endif //DUAL_CAN
#ifdef CAN_FD
#include "src/lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
ACAN2517FD canfd(MCP2517_CS, SPI, MCP2517_INT);
#endif //CAN_FD
// ModbusRTU parameters
#ifdef MODBUS_INVERTER_SELECTED
#define MB_RTU_NUM_VALUES 13100
uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout
ModbusServerRTU MBserver(Serial2, 2000);
#endif
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
#define SERIAL_LINK_BAUDRATE 112500
#endif
// Common charger parameters
volatile float charger_setpoint_HV_VDC = 0.0f;
volatile float charger_setpoint_HV_IDC = 0.0f;
@ -113,62 +81,6 @@ MyTimer loop_task_timer_10s(INTERVAL_10_S);
MyTimer check_pause_2s(INTERVAL_2_S);
// Contactor parameters
#ifdef CONTACTOR_CONTROL
enum State { DISCONNECTED, PRECHARGE, NEGATIVE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED };
State contactorStatus = DISCONNECTED;
#define ON 1
#define OFF 0
#ifdef NC_CONTACTORS //Normally closed contactors use inverted logic
#undef ON
#define ON 0
#undef OFF
#define OFF 1
#endif
#define MAX_ALLOWED_FAULT_TICKS 1000
/* NOTE: modify the precharge time constant below to account for the resistance and capacitance of the target system.
* t=3RC at minimum, t=5RC ideally
*/
#define PRECHARGE_TIME_MS 160
#define NEGATIVE_CONTACTOR_TIME_MS 1000
#define POSITIVE_CONTACTOR_TIME_MS 2000
#define PWM_Freq 20000 // 20 kHz frequency, beyond audible range
#define PWM_Res 10 // 10 Bit resolution 0 to 1023, maps 'nicely' to 0% 100%
#define PWM_HOLD_DUTY 250
#define PWM_OFF_DUTY 0
#define PWM_ON_DUTY 1023
#define POSITIVE_PWM_Ch 0
#define NEGATIVE_PWM_Ch 1
unsigned long prechargeStartTime = 0;
unsigned long negativeStartTime = 0;
unsigned long timeSpentInFaultedMode = 0;
#endif
void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFFFFFFFF) {
#ifdef PWM_CONTACTOR_CONTROL
if (pwm_freq != 0xFFFFFFFFFF) {
ledcWrite(pin, pwm_freq);
return;
}
#endif
if (direction == 1) {
digitalWrite(pin, HIGH);
} else { // 0
digitalWrite(pin, LOW);
}
}
#ifdef EQUIPMENT_STOP_BUTTON
const unsigned long equipment_button_long_press_duration =
15000; // 15 seconds for long press in case of MOMENTARY_SWITCH
const unsigned long equipment_button_debounce_duration = 200; // 250ms for debouncing the button
unsigned long timeSincePress = 0; // Variable to store the time since the last press
DebouncedButton equipment_stop_button; // Debounced button object
#endif
TaskHandle_t main_loop_task;
TaskHandle_t connectivity_loop_task;
@ -277,24 +189,24 @@ void core_loop(void* task_time_us) {
// Input, Runs as fast as possible
receive_can_native(); // Receive CAN messages from native CAN port
#ifdef CAN_FD
receive_canfd(); // Receive CAN-FD messages.
#endif
#ifdef DUAL_CAN
receive_can_addonMCP2515(); // Receive CAN messages on add-on MCP2515 chip
#endif
#ifdef CANFD_ADDON
receive_canfd_addon(); // Receive CAN-FD messages.
#endif // CANFD_ADDON
#ifdef CAN_ADDON
receive_can_addon(); // Receive CAN messages on add-on MCP2515 chip
#endif // CAN_ADDON
#ifdef RS485_INVERTER_SELECTED
receive_RS485(); // Process serial2 RS485 interface
#endif
#endif // RS485_INVERTER_SELECTED
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
runSerialDataLink();
#endif
run_serialDataLink();
#endif // SERIAL_LINK_RECEIVER || SERIAL_LINK_TRANSMITTER
END_TIME_MEASUREMENT_MAX(comm, datalayer.system.status.time_comm_us);
#ifdef WEBSERVER
START_TIME_MEASUREMENT(ota);
ElegantOTA.loop();
END_TIME_MEASUREMENT_MAX(ota, datalayer.system.status.time_ota_us);
#endif
#endif // WEBSERVER
START_TIME_MEASUREMENT(time_10ms);
// Process
@ -312,11 +224,11 @@ void core_loop(void* task_time_us) {
#ifdef DOUBLE_BATTERY
update_values_battery2();
check_interconnect_available();
#endif
#endif // DOUBLE_BATTERY
update_calculated_values();
#ifndef SERIAL_LINK_RECEIVER
update_machineryprotection(); // Check safeties (Not on serial link reciever board)
#endif
#endif // SERIAL_LINK_RECEIVER
update_values_inverter(); // Update values heading towards inverter
if (DUMMY_EVENT_ENABLED) {
set_event(EVENT_DUMMY_ERROR, (uint8_t)millis());
@ -331,7 +243,6 @@ void core_loop(void* task_time_us) {
END_TIME_MEASUREMENT_MAX(cantx, datalayer.system.status.time_cantx_us);
END_TIME_MEASUREMENT_MAX(all, datalayer.system.status.core_task_10s_max_us);
#ifdef FUNCTION_TIME_MEASUREMENT
if (datalayer.system.status.core_task_10s_max_us > datalayer.system.status.core_task_max_us) {
// Update worst case total time
datalayer.system.status.core_task_max_us = datalayer.system.status.core_task_10s_max_us;
@ -353,8 +264,7 @@ void core_loop(void* task_time_us) {
datalayer.system.status.time_cantx_us = 0;
datalayer.system.status.core_task_10s_max_us = 0;
}
#endif
#endif // FUNCTION_TIME_MEASUREMENT
if (check_pause_2s.elapsed()) {
emulator_pause_state_send_CAN_battery();
}
@ -370,389 +280,9 @@ void init_serial() {
while (!Serial) {}
#ifdef DEBUG_VIA_USB
Serial.println("__ OK __");
#endif
#endif // DEBUG_VIA_USB
}
void init_stored_settings() {
static uint32_t temp = 0;
settings.begin("batterySettings", false);
// Always get the equipment stop status
datalayer.system.settings.equipment_stop_active = settings.getBool("EQUIPMENT_STOP", false);
if (datalayer.system.settings.equipment_stop_active) {
set_event(EVENT_EQUIPMENT_STOP, 1);
}
#ifndef LOAD_SAVED_SETTINGS_ON_BOOT
settings.clear(); // If this clear function is executed, no settings will be read from storage
//always save the equipment stop status
settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active);
#endif
#ifdef WIFI
char tempSSIDstring[63]; // Allocate buffer with sufficient size
size_t lengthSSID = settings.getString("SSID", tempSSIDstring, sizeof(tempSSIDstring));
if (lengthSSID > 0) { // Successfully read the string from memory. Set it to SSID!
ssid = tempSSIDstring;
} else { // Reading from settings failed. Do nothing with SSID. Raise event?
}
char tempPasswordString[63]; // Allocate buffer with sufficient size
size_t lengthPassword = settings.getString("PASSWORD", tempPasswordString, sizeof(tempPasswordString));
if (lengthPassword > 7) { // Successfully read the string from memory. Set it to password!
password = tempPasswordString;
} else { // Reading from settings failed. Do nothing with SSID. Raise event?
}
#endif
temp = settings.getUInt("BATTERY_WH_MAX", false);
if (temp != 0) {
datalayer.battery.info.total_capacity_Wh = temp;
}
temp = settings.getUInt("MAXPERCENTAGE", false);
if (temp != 0) {
datalayer.battery.settings.max_percentage = temp * 10; // Multiply by 10 for backwards compatibility
}
temp = settings.getUInt("MINPERCENTAGE", false);
if (temp != 0) {
datalayer.battery.settings.min_percentage = temp * 10; // Multiply by 10 for backwards compatibility
}
temp = settings.getUInt("MAXCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_charge_dA = temp;
}
temp = settings.getUInt("MAXDISCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_discharge_dA = temp;
temp = settings.getBool("USE_SCALED_SOC", false);
datalayer.battery.settings.soc_scaling_active = temp; //This bool needs to be checked inside the temp!= block
} // No way to know if it wasnt reset otherwise
settings.end();
}
void init_CAN() {
// CAN pins
#ifdef CAN_SE_PIN
pinMode(CAN_SE_PIN, OUTPUT);
digitalWrite(CAN_SE_PIN, LOW);
#endif
CAN_cfg.speed = CAN_SPEED_500KBPS;
#ifdef NATIVECAN_250KBPS // Some component is requesting lower CAN speed
CAN_cfg.speed = CAN_SPEED_250KBPS;
#endif
CAN_cfg.tx_pin_id = CAN_TX_PIN;
CAN_cfg.rx_pin_id = CAN_RX_PIN;
CAN_cfg.rx_queue = xQueueCreate(rx_queue_size, sizeof(CAN_frame_t));
// Init CAN Module
ESP32Can.CANInit();
#ifdef DUAL_CAN
#ifdef DEBUG_VIA_USB
Serial.println("Dual CAN Bus (ESP32+MCP2515) selected");
#endif
gBuffer.initWithSize(25);
SPI.begin(MCP2515_SCK, MCP2515_MISO, MCP2515_MOSI);
ACAN2515Settings settings(QUARTZ_FREQUENCY, 500UL * 1000UL); // CAN bit rate 500 kb/s
settings.mRequestedMode = ACAN2515Settings::NormalMode;
const uint16_t errorCodeMCP = can.begin(settings, [] { can.isr(); });
if (errorCodeMCP == 0) {
#ifdef DEBUG_VIA_USB
Serial.println("Can ok");
#endif
} else {
#ifdef DEBUG_VIA_USB
Serial.print("Error Can: 0x");
Serial.println(errorCodeMCP, HEX);
#endif
set_event(EVENT_CANMCP_INIT_FAILURE, (uint8_t)errorCodeMCP);
}
#endif
#ifdef CAN_FD
#ifdef DEBUG_VIA_USB
Serial.println("CAN FD add-on (ESP32+MCP2517) selected");
#endif
SPI.begin(MCP2517_SCK, MCP2517_SDO, MCP2517_SDI);
ACAN2517FDSettings settings(CAN_FD_CRYSTAL_FREQUENCY_MHZ, 500 * 1000,
DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
settings.mRequestedMode = ACAN2517FDSettings::Normal20B; // ListenOnly / Normal20B / NormalFD
#else
settings.mRequestedMode = ACAN2517FDSettings::NormalFD; // ListenOnly / Normal20B / NormalFD
#endif
const uint32_t errorCode = canfd.begin(settings, [] { canfd.isr(); });
canfd.poll();
if (errorCode == 0) {
#ifdef DEBUG_VIA_USB
Serial.print("Bit Rate prescaler: ");
Serial.println(settings.mBitRatePrescaler);
Serial.print("Arbitration Phase segment 1: ");
Serial.println(settings.mArbitrationPhaseSegment1);
Serial.print("Arbitration Phase segment 2: ");
Serial.println(settings.mArbitrationPhaseSegment2);
Serial.print("Arbitration SJW:");
Serial.println(settings.mArbitrationSJW);
Serial.print("Actual Arbitration Bit Rate: ");
Serial.print(settings.actualArbitrationBitRate());
Serial.println(" bit/s");
Serial.print("Exact Arbitration Bit Rate ? ");
Serial.println(settings.exactArbitrationBitRate() ? "yes" : "no");
Serial.print("Arbitration Sample point: ");
Serial.print(settings.arbitrationSamplePointFromBitStart());
Serial.println("%");
#endif
} else {
#ifdef DEBUG_VIA_USB
Serial.print("CAN-FD Configuration error 0x");
Serial.println(errorCode, HEX);
#endif
set_event(EVENT_CANFD_INIT_FAILURE, (uint8_t)errorCode);
}
#endif
}
void init_contactors() {
// Init contactor pins
#ifdef CONTACTOR_CONTROL
#ifdef PWM_CONTACTOR_CONTROL
ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res,
POSITIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution
ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res,
NEGATIVE_PWM_Ch); // Setup PWM Channel Frequency and Resolution
ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_OFF_DUTY); // Set Positive PWM to 0%
ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_OFF_DUTY); // Set Negative PWM to 0%
#else //Normal CONTACTOR_CONTROL
pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT);
set(POSITIVE_CONTACTOR_PIN, OFF);
pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT);
set(NEGATIVE_CONTACTOR_PIN, OFF);
#endif
pinMode(PRECHARGE_PIN, OUTPUT);
set(PRECHARGE_PIN, OFF);
#endif //CONTACTOR_CONTROL
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
pinMode(SECOND_POSITIVE_CONTACTOR_PIN, OUTPUT);
set(SECOND_POSITIVE_CONTACTOR_PIN, OFF);
pinMode(SECOND_NEGATIVE_CONTACTOR_PIN, OUTPUT);
set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF);
#endif //CONTACTOR_CONTROL_DOUBLE_BATTERY
// Init BMS contactor
#ifdef HW_STARK // TODO: Rewrite this so LilyGo can also handle this BMS contactor
pinMode(BMS_POWER, OUTPUT);
digitalWrite(BMS_POWER, HIGH);
#endif //HW_STARK
}
void init_rs485() {
// Set up Modbus RTU Server
#ifdef RS485_EN_PIN
pinMode(RS485_EN_PIN, OUTPUT);
digitalWrite(RS485_EN_PIN, HIGH);
#endif
#ifdef RS485_SE_PIN
pinMode(RS485_SE_PIN, OUTPUT);
digitalWrite(RS485_SE_PIN, HIGH);
#endif
#ifdef PIN_5V_EN
pinMode(PIN_5V_EN, OUTPUT);
digitalWrite(PIN_5V_EN, HIGH);
#endif
#ifdef RS485_INVERTER_SELECTED
Serial2.begin(57600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
#endif
#ifdef MODBUS_INVERTER_SELECTED
#ifdef BYD_MODBUS
// Init Static data to the RTU Modbus
handle_static_data_modbus_byd();
#endif
// Init Serial2 connected to the RTU Modbus
RTUutils::prepareHardwareSerial(Serial2);
Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
// Register served function code worker for server
MBserver.registerWorker(MBTCP_ID, READ_HOLD_REGISTER, &FC03);
MBserver.registerWorker(MBTCP_ID, WRITE_HOLD_REGISTER, &FC06);
MBserver.registerWorker(MBTCP_ID, WRITE_MULT_REGISTERS, &FC16);
MBserver.registerWorker(MBTCP_ID, R_W_MULT_REGISTERS, &FC23);
// Start ModbusRTU background task
MBserver.begin(Serial2, MODBUS_CORE);
#endif
}
#ifdef EQUIPMENT_STOP_BUTTON
void monitor_equipment_stop_button() {
ButtonState changed_state = debounceButton(equipment_stop_button, timeSincePress);
if (equipment_stop_behavior == LATCHING_SWITCH) {
if (changed_state == PRESSED) {
// Changed to ON initiating equipment stop.
setBatteryPause(true, false, true);
} else if (changed_state == RELEASED) {
// Changed to OFF ending equipment stop.
setBatteryPause(false, false, false);
}
} else if (equipment_stop_behavior == MOMENTARY_SWITCH) {
if (changed_state == RELEASED) { // button is released
if (timeSincePress < equipment_button_long_press_duration) {
// Short press detected, trigger equipment stop
setBatteryPause(true, false, true);
} else {
// Long press detected, reset equipment stop state
setBatteryPause(false, false, false);
}
}
}
}
void init_equipment_stop_button() {
//using external pullup resistors NC
pinMode(EQUIPMENT_STOP_PIN, INPUT);
// Initialize the debounced button with NC switch type and equipment_button_debounce_duration debounce time
initDebouncedButton(equipment_stop_button, EQUIPMENT_STOP_PIN, NC, equipment_button_debounce_duration);
}
#endif
enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1
void print_can_frame(CAN_frame frame, frameDirection msgDir);
void print_can_frame(CAN_frame frame, frameDirection msgDir) {
#ifdef DEBUG_CAN_DATA // If enabled in user settings, print out the CAN messages via USB
uint8_t i = 0;
Serial.print("(");
Serial.print(millis() / 1000.0);
(msgDir == MSG_RX) ? Serial.print(") RX0 ") : Serial.print(") TX1 ");
Serial.print(frame.ID, HEX);
Serial.print(" [");
Serial.print(frame.DLC);
Serial.print("] ");
for (i = 0; i < frame.DLC; i++) {
Serial.print(frame.data.u8[i] < 16 ? "0" : "");
Serial.print(frame.data.u8[i], HEX);
if (i < frame.DLC - 1)
Serial.print(" ");
}
Serial.println("");
#endif //#DEBUG_CAN_DATA
if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording
char* message_string = datalayer.system.info.logged_can_messages;
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
if (offset + 128 > sizeof(datalayer.system.info.logged_can_messages)) {
// Not enough space, reset and start from the beginning
offset = 0;
}
unsigned long currentTime = millis();
// Add timestamp
offset += snprintf(message_string + offset, message_string_size - offset, "(%lu.%03lu) ", currentTime / 1000,
currentTime % 1000);
// Add direction. The 0 and 1 after RX and TX ensures that SavvyCAN puts TX and RX in a different bus.
offset +=
snprintf(message_string + offset, message_string_size - offset, "%s ", (msgDir == MSG_RX) ? "RX0" : "TX1");
// Add ID and DLC
offset += snprintf(message_string + offset, message_string_size - offset, "%X [%u] ", frame.ID, frame.DLC);
// Add data bytes
for (uint8_t i = 0; i < frame.DLC; i++) {
if (i < frame.DLC - 1) {
offset += snprintf(message_string + offset, message_string_size - offset, "%02X ", frame.data.u8[i]);
} else {
offset += snprintf(message_string + offset, message_string_size - offset, "%02X", frame.data.u8[i]);
}
}
// Add linebreak
offset += snprintf(message_string + offset, message_string_size - offset, "\n");
datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer
}
}
#ifdef CAN_FD
// Functions
void receive_canfd() { // This section checks if we have a complete CAN-FD message incoming
CANFDMessage frame;
int count = 0;
while (canfd.available() && count++ < 16) {
canfd.receive(frame);
CAN_frame rx_frame;
rx_frame.ID = frame.id;
rx_frame.ext_ID = frame.ext;
rx_frame.DLC = frame.len;
memcpy(rx_frame.data.u8, frame.data, MIN(rx_frame.DLC, 64));
//message incoming, pass it on to the handler
receive_can(&rx_frame, CAN_ADDON_FD_MCP2518);
receive_can(&rx_frame, CANFD_NATIVE);
}
}
#endif
void receive_can_native() { // This section checks if we have a complete CAN message incoming on native CAN port
CAN_frame_t rx_frame_native;
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame_native, 0) == pdTRUE) {
CAN_frame rx_frame;
rx_frame.ID = rx_frame_native.MsgID;
if (rx_frame_native.FIR.B.FF == CAN_frame_std) {
rx_frame.ext_ID = false;
} else { //CAN_frame_ext == 1
rx_frame.ext_ID = true;
}
rx_frame.DLC = rx_frame_native.FIR.B.DLC;
for (uint8_t i = 0; i < rx_frame.DLC && i < 8; i++) {
rx_frame.data.u8[i] = rx_frame_native.data.u8[i];
}
//message incoming, pass it on to the handler
receive_can(&rx_frame, CAN_NATIVE);
}
}
void send_can() {
if (!allowed_to_send_CAN) {
return;
}
send_can_battery();
#ifdef CAN_INVERTER_SELECTED
send_can_inverter();
#endif // CAN_INVERTER_SELECTED
#ifdef CHARGER_SELECTED
send_can_charger();
#endif // CHARGER_SELECTED
}
#ifdef DUAL_CAN
void receive_can_addonMCP2515() { // This section checks if we have a complete CAN message incoming on add-on CAN port
CAN_frame rx_frame; // Struct with our CAN format
CANMessage MCP2515Frame; // Struct with ACAN2515 library format, needed to use the MCP2515 library
if (can.available()) {
can.receive(MCP2515Frame);
rx_frame.ID = MCP2515Frame.id;
rx_frame.ext_ID = MCP2515Frame.ext ? CAN_frame_ext : CAN_frame_std;
rx_frame.DLC = MCP2515Frame.len;
for (uint8_t i = 0; i < MCP2515Frame.len && i < 8; i++) {
rx_frame.data.u8[i] = MCP2515Frame.data[i];
}
//message incoming, pass it on to the handler
receive_can(&rx_frame, CAN_ADDON_MCP2515);
}
}
#endif // DUAL_CAN
#ifdef DOUBLE_BATTERY
void check_interconnect_available() {
if (datalayer.battery.status.voltage_dV == 0 || datalayer.battery2.status.voltage_dV == 0) {
@ -773,113 +303,7 @@ void check_interconnect_available() {
set_event(EVENT_VOLTAGE_DIFFERENCE, (uint8_t)(voltage_diff / 10));
}
}
#endif //DOUBLE_BATTERY
void handle_contactors() {
#ifdef BYD_SMA
datalayer.system.status.inverter_allows_contactor_closing = digitalRead(INVERTER_CONTACTOR_ENABLE_PIN);
#endif
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
handle_contactors_battery2();
#endif
#ifdef CONTACTOR_CONTROL
// First check if we have any active errors, incase we do, turn off the battery
if (datalayer.battery.status.bms_status == FAULT) {
timeSpentInFaultedMode++;
} else {
timeSpentInFaultedMode = 0;
}
//handle contactor control SHUTDOWN_REQUESTED
if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS) {
contactorStatus = SHUTDOWN_REQUESTED;
}
if (contactorStatus == SHUTDOWN_REQUESTED) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set_event(EVENT_ERROR_OPEN_CONTACTOR, 0);
datalayer.system.status.contactors_engaged = false;
return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured)
}
// After that, check if we are OK to start turning on the battery
if (contactorStatus == DISCONNECTED) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
if (datalayer.system.status.battery_allows_contactor_closing &&
datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.settings.equipment_stop_active) {
contactorStatus = PRECHARGE;
}
}
// In case the inverter requests contactors to open, set the state accordingly
if (contactorStatus == COMPLETED) {
//Incase inverter (or estop) requests contactors to open, make state machine jump to Disconnected state (recoverable)
if (!datalayer.system.status.inverter_allows_contactor_closing || datalayer.system.settings.equipment_stop_active) {
contactorStatus = DISCONNECTED;
}
// Skip running the state machine below if it has already completed
return;
}
unsigned long currentTime = millis();
// Handle actual state machine. This first turns on Precharge, then Negative, then Positive, and finally turns OFF precharge
switch (contactorStatus) {
case PRECHARGE:
set(PRECHARGE_PIN, ON);
prechargeStartTime = currentTime;
contactorStatus = NEGATIVE;
break;
case NEGATIVE:
if (currentTime - prechargeStartTime >= PRECHARGE_TIME_MS) {
set(NEGATIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY);
negativeStartTime = currentTime;
contactorStatus = POSITIVE;
}
break;
case POSITIVE:
if (currentTime - negativeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) {
set(POSITIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY);
contactorStatus = PRECHARGE_OFF;
}
break;
case PRECHARGE_OFF:
if (currentTime - negativeStartTime >= POSITIVE_CONTACTOR_TIME_MS) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY);
set(POSITIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY);
contactorStatus = COMPLETED;
datalayer.system.status.contactors_engaged = true;
}
break;
default:
break;
}
#endif // CONTACTOR_CONTROL
}
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
void handle_contactors_battery2() {
if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allows_contactor_closing) {
set(SECOND_NEGATIVE_CONTACTOR_PIN, ON);
set(SECOND_POSITIVE_CONTACTOR_PIN, ON);
datalayer.system.status.contactors_battery2_engaged = true;
} else { // Closing contactors on secondary battery not allowed
set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF);
set(SECOND_POSITIVE_CONTACTOR_PIN, OFF);
datalayer.system.status.contactors_battery2_engaged = false;
}
}
#endif //CONTACTOR_CONTROL_DOUBLE_BATTERY
#endif // DOUBLE_BATTERY
void update_calculated_values() {
/* Calculate allowed charge/discharge currents*/
@ -969,7 +393,7 @@ void update_calculated_values() {
} else {
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
}
#endif
#endif // DOUBLE_BATTERY
} else { // soc_scaling_active == false. No SOC window wanted. Set scaled to same as real.
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
@ -998,65 +422,19 @@ void update_calculated_values() {
datalayer.battery.status.reported_soc = datalayer.battery2.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
}
#endif //DOUBLE_BATTERY
#endif // DOUBLE_BATTERY
}
void update_values_inverter() {
#ifdef CAN_INVERTER_SELECTED
update_values_can_inverter();
#endif
#endif // CAN_INVERTER_SELECTED
#ifdef MODBUS_INVERTER_SELECTED
update_modbus_registers_inverter();
#endif
#endif // CAN_INVERTER_SELECTED
#ifdef RS485_INVERTER_SELECTED
update_RS485_registers_inverter();
#endif
}
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
void runSerialDataLink() {
static unsigned long updateTime = 0;
unsigned long currentMillis = millis();
if ((currentMillis - updateTime) > 1) { //Every 2ms
updateTime = currentMillis;
#ifdef SERIAL_LINK_RECEIVER
manageSerialLinkReceiver();
#endif
#ifdef SERIAL_LINK_TRANSMITTER
manageSerialLinkTransmitter();
#endif
}
}
#endif
void init_serialDataLink() {
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
Serial2.begin(SERIAL_LINK_BAUDRATE, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
#endif
}
void store_settings_equipment_stop() {
settings.begin("batterySettings", false);
settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active);
settings.end();
}
void storeSettings() {
settings.begin("batterySettings", false);
#ifdef WIFI
settings.putString("SSID", String(ssid.c_str()));
settings.putString("PASSWORD", String(password.c_str()));
#endif
settings.putUInt("BATTERY_WH_MAX", datalayer.battery.info.total_capacity_Wh);
settings.putUInt("MAXPERCENTAGE",
datalayer.battery.settings.max_percentage / 10); // Divide by 10 for backwards compatibility
settings.putUInt("MINPERCENTAGE",
datalayer.battery.settings.min_percentage / 10); // Divide by 10 for backwards compatibility
settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA);
settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA);
settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active);
settings.end();
#endif // CAN_INVERTER_SELECTED
}
/** Reset reason numbering and description
@ -1135,92 +513,3 @@ void check_reset_reason() {
break;
}
}
void transmit_can(CAN_frame* tx_frame, int interface) {
if (!allowed_to_send_CAN) {
return;
}
print_can_frame(*tx_frame, frameDirection(MSG_TX));
switch (interface) {
case CAN_NATIVE:
CAN_frame_t frame;
frame.MsgID = tx_frame->ID;
frame.FIR.B.FF = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
frame.FIR.B.DLC = tx_frame->DLC;
frame.FIR.B.RTR = CAN_no_RTR;
for (uint8_t i = 0; i < tx_frame->DLC; i++) {
frame.data.u8[i] = tx_frame->data.u8[i];
}
ESP32Can.CANWriteFrame(&frame);
break;
case CAN_ADDON_MCP2515: {
#ifdef DUAL_CAN
//Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2
CANMessage MCP2515Frame;
MCP2515Frame.id = tx_frame->ID;
MCP2515Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
MCP2515Frame.len = tx_frame->DLC;
MCP2515Frame.rtr = false;
for (uint8_t i = 0; i < MCP2515Frame.len; i++) {
MCP2515Frame.data[i] = tx_frame->data.u8[i];
}
can.tryToSend(MCP2515Frame);
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
#endif //DUAL_CAN
} break;
case CANFD_NATIVE:
case CAN_ADDON_FD_MCP2518: {
#ifdef CAN_FD
CANFDMessage MCP2518Frame;
if (tx_frame->FD) {
MCP2518Frame.type = CANFDMessage::CANFD_WITH_BIT_RATE_SWITCH;
} else { //Classic CAN message
MCP2518Frame.type = CANFDMessage::CAN_DATA;
}
MCP2518Frame.id = tx_frame->ID;
MCP2518Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
MCP2518Frame.len = tx_frame->DLC;
for (uint8_t i = 0; i < MCP2518Frame.len; i++) {
MCP2518Frame.data[i] = tx_frame->data.u8[i];
}
send_ok = canfd.tryToSend(MCP2518Frame);
if (!send_ok) {
set_event(EVENT_CANFD_BUFFER_FULL, interface);
}
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
#endif //CAN_FD
} break;
default:
// Invalid interface sent with function call. TODO: Raise event that coders messed up
break;
}
}
void receive_can(CAN_frame* rx_frame, int interface) {
print_can_frame(*rx_frame, frameDirection(MSG_RX));
if (interface == can_config.battery) {
receive_can_battery(*rx_frame);
#ifdef CHADEMO_BATTERY
ISA_handleFrame(rx_frame);
#endif
}
if (interface == can_config.inverter) {
#ifdef CAN_INVERTER_SELECTED
receive_can_inverter(*rx_frame);
#endif
}
if (interface == can_config.battery_double) {
#ifdef DOUBLE_BATTERY
receive_can_battery2(*rx_frame);
#endif
}
if (interface == can_config.charger) {
#ifdef CHARGER_SELECTED
receive_can_charger(*rx_frame);
#endif
}
}

View file

@ -9,7 +9,7 @@
CAN_NATIVE = Native CAN port on the LilyGo & Stark hardware
CANFD_NATIVE = Native CANFD port on the Stark CMR hardware
CAN_ADDON_MCP2515 = Add-on CAN MCP2515 connected to GPIO pins
CAN_ADDON_FD_MCP2518 = Add-on CAN-FD MCP2518 connected to GPIO pins
CANFD_ADDON_MCP2518 = Add-on CAN-FD MCP2518 connected to GPIO pins
*/
volatile CAN_Configuration can_config = {

View file

@ -16,8 +16,8 @@
//#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below
//#define IMIEV_CZERO_ION_BATTERY
//#define JAGUAR_IPACE_BATTERY
//#define KIA_HYUNDAI_64_BATTERY
//#define KIA_E_GMP_BATTERY
//#define KIA_HYUNDAI_64_BATTERY
//#define KIA_HYUNDAI_HYBRID_BATTERY
//#define MEB_BATTERY
//#define MG_5_BATTERY
@ -30,18 +30,18 @@
//#define RENAULT_ZOE_GEN1_BATTERY
//#define RENAULT_ZOE_GEN2_BATTERY
//#define SANTA_FE_PHEV_BATTERY
//#define TESLA_MODEL_SX_BATTERY
//#define TESLA_MODEL_3Y_BATTERY
//#define TESLA_MODEL_SX_BATTERY
//#define VOLVO_SPA_BATTERY
//#define TEST_FAKE_BATTERY
//#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires DUAL_CAN setup)
//#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires CAN_ADDON setup)
/* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */
//#define AFORE_CAN //Enable this line to emulate an "Afore battery" over CAN bus
//#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus
//#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus
//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
//#define BYD_KOSTAL_RS485 //Enable this line to emulate a "BYD 11kWh HVM battery" over Kostal RS485
//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
//#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus
//#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus
//#define PYLON_LV_CAN //Enable this line to emulate a "48V Pylontech battery" over CAN bus
//#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus
@ -58,6 +58,7 @@
//#define HW_3LB
/* Contactor settings. If you have a battery that does not activate contactors via CAN, configure this section */
#define PRECHARGE_TIME_MS 500 //Precharge time in milliseconds. Modify to suit your inverter (See wiki for more info)
//#define CONTACTOR_CONTROL //Enable this line to have the emulator handle automatic precharge/contactor+/contactor- closing sequence (See wiki for pins)
//#define CONTACTOR_CONTROL_DOUBLE_BATTERY //Enable this line to have the emulator hardware control secondary set of contactors for double battery setups (See wiki for pins)
//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM for CONTACTOR_CONTROL, which lowers power consumption and heat generation. CONTACTOR_CONTROL must be enabled.
@ -70,14 +71,14 @@
//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production)
//#define DEBUG_CAN_DATA //Enable this line to print incoming/outgoing CAN & CAN-FD messages to USB serial (WARNING, raises CPU load, do not use for production)
//#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting
//#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery)
#define CRYSTAL_FREQUENCY_MHZ 8 //DUAL_CAN option, what is your MCP2515 add-on boards crystal frequency?
//#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board
#ifdef CAN_FD // CAN_FD additional options if enabled
#define CAN_FD_CRYSTAL_FREQUENCY_MHZ \
//#define CAN_ADDON //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery)
#define CRYSTAL_FREQUENCY_MHZ 8 //CAN_ADDON option, what is your MCP2515 add-on boards crystal frequency?
//#define CANFD_ADDON //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board
#ifdef CANFD_ADDON // CANFD_ADDON additional options if enabled
#define CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ \
ACAN2517FDSettings:: \
OSC_40MHz //CAN_FD option, what is your MCP2518 add-on boards crystal frequency? (Default OSC_40MHz)
#endif
OSC_40MHz //CANFD_ADDON option, what is your MCP2518 add-on boards crystal frequency? (Default OSC_40MHz)
#endif // CANFD_ADDON
//#define USE_CANFD_INTERFACE_AS_CLASSIC_CAN // Enable this line if you intend to use the CANFD as normal CAN
//#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter)
//#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery)
@ -127,14 +128,20 @@
#define BATTERY_MAXTEMPERATURE 500
// -250 = -25.0 °C , Min temperature (Will produce a battery frozen event if below)
#define BATTERY_MINTEMPERATURE -250
// 300 = 30.0A , BYD CAN specific setting, Max charge in Amp (Some inverters needs to be limited)
// 300 = 30.0A , Max charge in Amp (Some inverters needs to be limited)
#define BATTERY_MAX_CHARGE_AMP 300
// 300 = 30.0A , BYD CAN specific setting, Max discharge in Amp (Some inverters needs to be limited)
// 300 = 30.0A , Max discharge in Amp (Some inverters needs to be limited)
#define BATTERY_MAX_DISCHARGE_AMP 300
// Enable this to manually set voltage limits on how much battery can be discharged/charged. Normally not used.
#define BATTERY_USE_VOLTAGE_LIMITS false
// 5000 = 500.0V , Target charge voltage (Value can be tuned on the fly via webserver). Not used unless BATTERY_USE_VOLTAGE_LIMITS = true
#define BATTERY_MAX_CHARGE_VOLTAGE 5000
// 3000 = 300.0V, Target discharge voltage (Value can be tuned on the fly via webserver). Not used unless BATTERY_USE_VOLTAGE_LIMITS = true
#define BATTERY_MAX_DISCHARGE_VOLTAGE 3000
/* Do not change any code below this line unless you are sure what you are doing */
/* Only change battery specific settings in "USER_SETTINGS.h" */
typedef enum { CAN_NATIVE = 0, CANFD_NATIVE = 1, CAN_ADDON_MCP2515 = 2, CAN_ADDON_FD_MCP2518 = 3 } CAN_Interface;
typedef enum { CAN_NATIVE = 0, CANFD_NATIVE = 1, CAN_ADDON_MCP2515 = 2, CANFD_ADDON_MCP2518 = 3 } CAN_Interface;
typedef struct {
CAN_Interface battery;
CAN_Interface inverter;

View file

@ -118,7 +118,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV);
SOC_method = ESTIMATED;
#else // Pack is not crashed, we can use periodically transmitted SOC
datalayer.battery.status.real_soc = battery_highprecision_SOC * 100;
datalayer.battery.status.real_soc = battery_highprecision_SOC * 10;
SOC_method = MEASURED;
#endif
@ -419,6 +419,9 @@ void setup_battery(void) { // Performs one time setup at startup
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.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
//Due to the Datalayer having 370.0V as startup value, which is 10V lower than the Atto 3 min voltage 380.0V
//We now init the value to 380.1V to avoid false positive events.
datalayer.battery.status.voltage_dV = MIN_PACK_VOLTAGE_DV + 1;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = 126;
datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP;

View file

@ -38,11 +38,13 @@ CAN_frame LEAF_1D4 = {.FD = false,
.ID = 0x1D4,
.data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}};
// Active polling messages
uint8_t PIDgroups[] = {0x01, 0x02, 0x04, 0x83, 0x84, 0x90};
uint8_t PIDindex = 0;
CAN_frame LEAF_GROUP_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {2, 0x21, 1, 0, 0, 0, 0, 0}};
.data = {0x02, 0x21, PIDgroups[0], 0, 0, 0, 0, 0}};
CAN_frame LEAF_NEXT_LINE_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
@ -107,7 +109,6 @@ static bool battery_Batt_Heater_Mail_Send_Request = false; //Stores info when a
// Nissan LEAF battery data from polled CAN messages
static uint8_t battery_request_idx = 0;
static uint8_t group_7bb = 0;
static uint8_t group = 1;
static bool stop_battery_query = true;
static uint8_t hold_off_with_polling_10seconds = 10;
static uint16_t battery_cell_voltages[97]; //array with all the cellvoltages
@ -124,7 +125,9 @@ static uint16_t battery_temp_raw_max = 0;
static uint16_t battery_temp_raw_min = 0;
static int16_t battery_temp_polled_max = 0;
static int16_t battery_temp_polled_min = 0;
static uint8_t BatterySerialNumber[15] = {0}; // Stores raw HEX values for ASCII chars
static uint8_t BatteryPartNumber[7] = {0}; // Stores raw HEX values for ASCII chars
static uint8_t BMSIDcode[8] = {0};
#ifdef DOUBLE_BATTERY
static uint8_t LEAF_battery2_Type = ZE0_BATTERY;
static bool battery2_can_alive = false;
@ -326,6 +329,9 @@ void update_values_battery() { /* This function maps all the values fetched via
}
// Update webserver datalayer
memcpy(datalayer_extended.nissanleaf.BatterySerialNumber, BatterySerialNumber, sizeof(BatterySerialNumber));
memcpy(datalayer_extended.nissanleaf.BatteryPartNumber, BatteryPartNumber, sizeof(BatteryPartNumber));
memcpy(datalayer_extended.nissanleaf.BMSIDcode, BMSIDcode, sizeof(BMSIDcode));
datalayer_extended.nissanleaf.LEAF_gen = LEAF_battery_Type;
datalayer_extended.nissanleaf.GIDS = battery_GIDS;
datalayer_extended.nissanleaf.ChargePowerLimit = battery_Charge_Power_Limit;
@ -598,20 +604,16 @@ void receive_can_battery2(CAN_frame rx_frame) {
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
break;
case 0x7BB:
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
battery2_group_7bb = rx_frame.data.u8[3];
if (battery2_group_7bb != 1 && battery2_group_7bb != 2 &&
battery2_group_7bb != 4) { //We are only interested in groups 1,2 and 4
break;
}
}
if (stop_battery_query) { //Leafspy/Service request is active, stop our own polling
break;
}
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
battery2_group_7bb = rx_frame.data.u8[3];
transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery_double);
}
if (battery2_group_7bb == 1) //High precision SOC, Current, voltages etc.
{
@ -845,7 +847,7 @@ void receive_can_battery(CAN_frame rx_frame) {
break;
case 0x7BB:
// This section checks if we are doing a SOH reset towards BMS
// This section checks if we are doing a SOH reset towards BMS. If we do, all 7BB handling is halted
if (stateMachineClearSOH < 255) {
//Intercept the messages based on state machine
if (rx_frame.data.u8[0] == 0x06) { // Incoming challenge data!
@ -860,18 +862,16 @@ void receive_can_battery(CAN_frame rx_frame) {
break;
}
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
group_7bb = rx_frame.data.u8[3];
if (group_7bb != 1 && group_7bb != 2 && group_7bb != 4) { //We are only interested in groups 1,2 and 4
break;
}
}
if (stop_battery_query) { //Leafspy is active, stop our own polling
break;
}
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
group_7bb = rx_frame.data.u8[3];
transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery); //Request the next frame for the group
}
if (group_7bb == 1) //High precision SOC, Current, voltages etc.
{
@ -991,6 +991,66 @@ void receive_can_battery(CAN_frame rx_frame) {
}
}
if (group_7bb == 0x83) //BatteryPartNumber
{
if (rx_frame.data.u8[0] == 0x10) { //First frame (101A6183334E4B32)
BatteryPartNumber[0] = rx_frame.data.u8[4];
BatteryPartNumber[1] = rx_frame.data.u8[5];
BatteryPartNumber[2] = rx_frame.data.u8[6];
BatteryPartNumber[3] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x21) { //Second frame (2141524205170000)
BatteryPartNumber[4] = rx_frame.data.u8[1];
BatteryPartNumber[5] = rx_frame.data.u8[2];
BatteryPartNumber[6] = rx_frame.data.u8[3];
}
if (rx_frame.data.u8[0] == 0x22) { //Third frame (2200000002101311)
}
if (rx_frame.data.u8[0] == 0x23) { //Fourth frame (23000000000080FF)
}
}
if (group_7bb == 0x84) { //BatterySerialNumber
if (rx_frame.data.u8[0] == 0x10) { //First frame (10 16 61 84 32 33 30 55)
BatterySerialNumber[0] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x21) { //Second frame (21 4B 31 31 39 32 45 30)
BatterySerialNumber[1] = rx_frame.data.u8[1];
BatterySerialNumber[2] = rx_frame.data.u8[2];
BatterySerialNumber[3] = rx_frame.data.u8[3];
BatterySerialNumber[4] = rx_frame.data.u8[4];
BatterySerialNumber[5] = rx_frame.data.u8[5];
BatterySerialNumber[6] = rx_frame.data.u8[6];
BatterySerialNumber[7] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x22) { //Third frame (22 30 31 34 38 32 20 A0)
BatterySerialNumber[8] = rx_frame.data.u8[1];
BatterySerialNumber[9] = rx_frame.data.u8[2];
BatterySerialNumber[10] = rx_frame.data.u8[3];
BatterySerialNumber[11] = rx_frame.data.u8[4];
BatterySerialNumber[12] = rx_frame.data.u8[5];
BatterySerialNumber[13] = rx_frame.data.u8[6];
BatterySerialNumber[14] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x23) { //Fourth frame (23 00 00 00 00 00 00 00)
}
}
if (group_7bb == 0x90) { //BMSIDcode
if (rx_frame.data.u8[0] == 0x10) { //First frame (100A619044434131)
BMSIDcode[0] = rx_frame.data.u8[4];
BMSIDcode[1] = rx_frame.data.u8[5];
BMSIDcode[2] = rx_frame.data.u8[6];
BMSIDcode[3] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x21) { //Second frame (2130303535FFFFFF)
BMSIDcode[4] = rx_frame.data.u8[1];
BMSIDcode[5] = rx_frame.data.u8[2];
BMSIDcode[6] = rx_frame.data.u8[3];
BMSIDcode[7] = rx_frame.data.u8[4];
}
}
break;
default:
break;
@ -1190,9 +1250,11 @@ void send_can_battery() {
//Every 10s, ask diagnostic data from the battery. Don't ask if someone is already polling on the bus (Leafspy?)
if (!stop_battery_query) {
group = (group == 1) ? 2 : (group == 2) ? 4 : 1;
// Cycle between group 1, 2, and 4 using ternary operation
LEAF_GROUP_REQUEST.data.u8[2] = group;
// Move to the next group
PIDindex = (PIDindex + 1) % 6; // 6 = amount of elements in the PIDgroups[]
LEAF_GROUP_REQUEST.data.u8[2] = PIDgroups[PIDindex];
transmit_can(&LEAF_GROUP_REQUEST, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&LEAF_GROUP_REQUEST, can_config.battery_double);

View file

@ -1,6 +1,5 @@
#include "../include.h"
#ifdef RENAULT_ZOE_GEN1_BATTERY
#include <algorithm> // For std::min and std::max
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "RENAULT-ZOE-GEN1-BATTERY.h"
@ -9,20 +8,24 @@
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe/src/vehicle_renaultzoe.cpp
The Zoe BMS apparently does not send total pack voltage, so we use the polled 96x cellvoltages summed up as total voltage
Still TODO:
- Find max discharge and max charge values (for now hardcoded to 5kW)
- Fix the missing cell96 issue (Only cells 1-95 is shown)
- Find current sensor value (OVMS code reads this from inverter, which we dont have)
- Figure out why SOH% is not read (low prio)
- Fix the missing cell96 issue (Only cells 1-95 is shown on cellmonitor page)
- Automatically detect if we are on 22 or 41kWh battery (Nice to have, requires log file from 22kWh battery)
/*
/* Do not change code below unless you are sure what you are doing */
static uint16_t LB_SOC = 50;
static uint16_t LB_Display_SOC = 50;
static uint16_t LB_SOH = 99;
static int16_t LB_Average_Temperature = 0;
static uint32_t LB_Charge_Power_W = 0;
static int32_t LB_Current = 0;
static uint32_t LB_Charging_Power_W = 0;
static uint32_t LB_Regen_allowed_W = 0;
static uint32_t LB_Discharge_allowed_W = 0;
static int16_t LB_Current = 0;
static int16_t LB_Cell_minimum_temperature = 0;
static int16_t LB_Cell_maximum_temperature = 0;
static uint16_t LB_kWh_Remaining = 0;
static uint16_t LB_Battery_Voltage = 3700;
static uint8_t LB_Heartbeat = 0;
static uint8_t frame0 = 0;
static uint8_t current_poll = 0;
static uint8_t requested_poll = 0;
@ -77,32 +80,17 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.soh_pptt = (LB_SOH * 100); // Increase range from 99% -> 99.00%
datalayer.battery.status.real_soc = SOC_polled;
//datalayer.battery.status.real_soc = LB_Display_SOC; //Alternative would be to use Dash SOC%
datalayer.battery.status.current_dA = LB_Current; //TODO: Take from CAN
datalayer.battery.status.current_dA = LB_Current * 10; //Convert A to dA
//Calculate the remaining Wh amount from SOC% and max Wh value.
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.max_discharge_power_W = 5000; //TODO: Take from CAN
datalayer.battery.status.max_discharge_power_W = LB_Discharge_allowed_W;
datalayer.battery.status.max_charge_power_W = 5000; //TODO: Take from CAN
// TODO: Remove this hacky wacky scaling down charge power when we find value from CAN
if (datalayer.battery.status.real_soc > 9500) {
datalayer.battery.status.max_charge_power_W = 3000;
}
if (datalayer.battery.status.real_soc > 9600) {
datalayer.battery.status.max_charge_power_W = 2000;
}
if (datalayer.battery.status.real_soc > 9700) {
datalayer.battery.status.max_charge_power_W = 1000;
}
if (datalayer.battery.status.real_soc > 9800) {
datalayer.battery.status.max_charge_power_W = 500;
}
if (datalayer.battery.status.real_soc > 9900) {
datalayer.battery.status.max_charge_power_W = 50;
}
datalayer.battery.status.max_charge_power_W = LB_Charging_Power_W;
int16_t temperatures[] = {cell_1_temperature_polled, cell_2_temperature_polled, cell_3_temperature_polled,
cell_4_temperature_polled, cell_5_temperature_polled, cell_6_temperature_polled,
@ -145,28 +133,61 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_min_voltage_mV = min_cell_mv_value;
datalayer.battery.status.cell_max_voltage_mV = max_cell_mv_value;
datalayer.battery.status.voltage_dV = static_cast<uint32_t>((calculated_total_pack_voltage_mV / 100)); // mV to dV
#ifdef DEBUG_VIA_USB
#endif
}
void receive_can_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) {
case 0x427:
LB_Charge_Power_W = rx_frame.data.u8[5] * 300;
case 0x155: //10ms - Charging power, current and SOC
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_Charging_Power_W = rx_frame.data.u8[0] * 300;
LB_Current = (((((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) * 0.25) - 500);
LB_Display_SOC = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case 0x427: // NOTE: Not present on 41kWh battery!
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_kWh_Remaining = (((((rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])) >> 6) & 0x3ff) * 0.1);
break;
case 0x42E: //HV SOC & Battery Temp & Charging Power
case 0x42E: //NOTE: Not present on 41kWh battery!
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_Battery_Voltage = (((((rx_frame.data.u8[3] << 8) | (rx_frame.data.u8[4])) >> 5) & 0x3ff) * 0.5); //0.5V/bit
LB_Average_Temperature = (((((rx_frame.data.u8[5] << 8) | (rx_frame.data.u8[6])) >> 5) & 0x7F) - 40);
break;
case 0x424: //100ms - Charge limits, Temperatures, SOH
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_Regen_allowed_W = rx_frame.data.u8[2] * 500;
LB_Discharge_allowed_W = rx_frame.data.u8[3] * 500;
LB_Cell_minimum_temperature = (rx_frame.data.u8[4] - 40);
LB_SOH = rx_frame.data.u8[5];
LB_Heartbeat = rx_frame.data.u8[6]; // Alternates between 0x55 and 0xAA every 500ms (Same as on Nissan LEAF)
LB_Cell_maximum_temperature = (rx_frame.data.u8[7] - 40);
break;
case 0x425: //100ms Unknown content
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery
break;
case 0x445: //100ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x4AE: //3000ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x4AF: //100ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x654: //SOC
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_SOC = rx_frame.data.u8[3];
break;
case 0x658: //SOH
LB_SOH = (rx_frame.data.u8[4] & 0x7F);
case 0x658: //SOH - NOTE: Not present on 41kWh battery! (Is this message on 21kWh?)
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//LB_SOH = (rx_frame.data.u8[4] & 0x7F);
break;
case 0x659: //3000ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x7BB: //Reply from active polling
frame0 = rx_frame.data.u8[0];

View file

@ -18,6 +18,8 @@ static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
static uint8_t poll_data_pid = 0;
static uint8_t counter_200 = 0;
static uint8_t checksum_200 = 0;
static uint16_t SOC_Display = 0;
static uint16_t batterySOH = 100;
@ -32,11 +34,27 @@ static int16_t leadAcidBatteryVoltage = 120;
static int8_t temperatureMax = 0;
static int8_t temperatureMin = 0;
static int16_t batteryAmps = 0;
static uint8_t counter_200 = 0;
static uint8_t checksum_200 = 0;
static uint8_t StatusBattery = 0;
static uint16_t cellvoltages_mv[96];
#ifdef DOUBLE_BATTERY
static uint16_t battery2_SOC_Display = 0;
static uint16_t battery2_SOH = 100;
static uint16_t battery2_CellVoltMax_mV = 3700;
static uint16_t battery2_CellVoltMin_mV = 3700;
static uint8_t battery2_CellVmaxNo = 0;
static uint8_t battery2_CellVminNo = 0;
static uint16_t battery2_allowedDischargePower = 0;
static uint16_t battery2_allowedChargePower = 0;
static uint16_t battery2_batteryVoltage = 0;
static int16_t battery2_leadAcidBatteryVoltage = 120;
static int8_t battery2_temperatureMax = 0;
static int8_t battery2_temperatureMin = 0;
static int16_t battery2_batteryAmps = 0;
static uint8_t battery2_StatusBattery = 0;
static uint16_t battery2_cellvoltages_mv[96];
#endif //DOUBLE_BATTERY
CAN_frame SANTAFE_200 = {.FD = false,
.ext_ID = false,
.DLC = 8,
@ -96,10 +114,6 @@ void update_values_battery() { //This function maps all the values fetched via
if (leadAcidBatteryVoltage < 110) {
set_event(EVENT_12V_LOW, leadAcidBatteryVoltage);
}
#ifdef DEBUG_VIA_USB
#endif
}
void receive_can_battery(CAN_frame rx_frame) {
@ -338,10 +352,13 @@ void send_can_battery() {
SANTAFE_200.data.u8[7] = checksum_200;
transmit_can(&SANTAFE_200, can_config.battery);
transmit_can(&SANTAFE_2A1, can_config.battery);
transmit_can(&SANTAFE_2F0, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&SANTAFE_200, can_config.battery_double);
transmit_can(&SANTAFE_2A1, can_config.battery_double);
transmit_can(&SANTAFE_2F0, can_config.battery_double);
#endif //DOUBLE_BATTERY
counter_200++;
if (counter_200 > 0xF) {
@ -354,36 +371,279 @@ void send_can_battery() {
previousMillis100 = currentMillis;
transmit_can(&SANTAFE_523, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&SANTAFE_523, can_config.battery_double);
#endif //DOUBLE_BATTERY
}
// Send 500ms CAN Message
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
previousMillis500 = currentMillis;
//PID data is polled after last message sent from battery:
if (poll_data_pid >= 5) { //polling one of 5 PIDs at 100ms, resolution = 500ms
poll_data_pid = 0;
}
poll_data_pid++;
if (poll_data_pid == 1) {
SANTAFE_7E4_poll.data.u8[3] = 0x01;
// PID data is polled after last message sent from battery:
poll_data_pid = (poll_data_pid % 5) + 1;
SANTAFE_7E4_poll.data.u8[3] = (uint8_t)poll_data_pid;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 2) {
SANTAFE_7E4_poll.data.u8[3] = 0x02;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 3) {
SANTAFE_7E4_poll.data.u8[3] = 0x03;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 4) {
SANTAFE_7E4_poll.data.u8[3] = 0x04;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 5) {
SANTAFE_7E4_poll.data.u8[3] = 0x05;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
}
#ifdef DOUBLE_BATTERY
transmit_can(&SANTAFE_7E4_poll, can_config.battery_double);
#endif //DOUBLE_BATTERY
}
}
#ifdef DOUBLE_BATTERY
void update_values_battery2() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery2.status.real_soc = (battery2_SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
datalayer.battery2.status.soh_pptt = (battery2_SOH * 100); //Increase decimals from 100% -> 100.00%
datalayer.battery2.status.voltage_dV = battery2_batteryVoltage;
datalayer.battery2.status.current_dA = -battery2_batteryAmps;
datalayer.battery2.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery2.status.real_soc) / 10000) * datalayer.battery2.info.total_capacity_Wh);
datalayer.battery2.status.max_discharge_power_W = battery2_allowedDischargePower * 10;
datalayer.battery2.status.max_charge_power_W = battery2_allowedChargePower * 10;
//Power in watts, Negative = charging batt
datalayer.battery2.status.active_power_W =
((datalayer.battery2.status.voltage_dV * datalayer.battery2.status.current_dA) / 100);
datalayer.battery2.status.cell_max_voltage_mV = battery2_CellVoltMax_mV;
datalayer.battery2.status.cell_min_voltage_mV = battery2_CellVoltMin_mV;
datalayer.battery2.status.temperature_min_dC = battery2_temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery2.status.temperature_max_dC = battery2_temperatureMax * 10; //Increase decimals, 18C -> 18.0C
if (battery2_leadAcidBatteryVoltage < 110) {
set_event(EVENT_12V_LOW, battery2_leadAcidBatteryVoltage);
}
}
void receive_can_battery2(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x1FF:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_StatusBattery = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x4D5:
break;
case 0x4DD:
break;
case 0x4DE:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4E0:
break;
case 0x542:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_SOC_Display = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]) / 2;
break;
case 0x588:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_batteryVoltage = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]);
break;
case 0x597:
break;
case 0x5A6:
break;
case 0x5A7:
break;
case 0x5AD:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_batteryAmps = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2];
break;
case 0x5AE:
break;
case 0x5F1:
break;
case 0x620:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_leadAcidBatteryVoltage = rx_frame.data.u8[1];
battery2_temperatureMin = rx_frame.data.u8[6]; //Lowest temp in battery
battery2_temperatureMax = rx_frame.data.u8[7]; //Highest temp in battery
break;
case 0x670:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_allowedChargePower = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery2_allowedDischargePower = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
break;
case 0x671:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
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(&SANTAFE_7E4_ack,
can_config.battery_double); //Send ack to BMS if the same frame is sent as polled
}
break;
case 0x21: //First frame in PID group
if (poll_data_pid == 1) {
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[0] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[1] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[2] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[3] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[4] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[5] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[32] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[33] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[34] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[35] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[36] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[37] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[64] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[65] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[66] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[67] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[68] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[69] = (rx_frame.data.u8[7] * 20);
}
break;
case 0x22: //Second datarow in PID group
if (poll_data_pid == 2) {
battery2_cellvoltages_mv[6] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[7] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[8] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[9] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[10] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[11] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[12] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[38] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[39] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[40] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[41] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[42] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[43] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[44] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[70] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[71] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[72] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[73] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[74] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[75] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[76] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 6) {
}
break;
case 0x23: //Third datarow in PID group
if (poll_data_pid == 1) {
battery2_CellVoltMax_mV = (rx_frame.data.u8[7] * 20); //(volts *50) *20 =mV
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[13] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[14] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[15] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[16] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[17] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[18] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[19] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[45] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[46] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[47] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[48] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[49] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[50] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[51] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[77] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[78] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[79] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[80] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[81] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[82] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[83] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
if (rx_frame.data.u8[6] > 0) {
battery2_SOH = rx_frame.data.u8[6];
}
if (battery2_SOH > 100) {
battery2_SOH = 100;
}
}
break;
case 0x24: //Fourth datarow in PID group
if (poll_data_pid == 1) {
battery2_CellVmaxNo = rx_frame.data.u8[1];
battery2_CellVminNo = rx_frame.data.u8[3];
CellVoltMin_mV = (rx_frame.data.u8[2] * 20); //(volts *50) *20 =mV
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[20] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[21] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[22] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[23] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[24] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[25] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[26] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[52] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[53] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[54] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[55] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[56] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[57] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[58] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[84] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[85] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[86] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[87] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[88] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[89] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[90] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
}
break;
case 0x25: //Fifth datarow in PID group
if (poll_data_pid == 2) {
battery2_cellvoltages_mv[27] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[28] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[29] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[30] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[31] = (rx_frame.data.u8[5] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[59] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[60] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[61] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[62] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[63] = (rx_frame.data.u8[5] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[91] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[92] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[93] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[94] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[95] = (rx_frame.data.u8[5] * 20);
//Map all cell voltages to the global array, we have sampled them all!
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages_mv, 96 * sizeof(uint16_t));
} else if (poll_data_pid == 5) {
}
break;
case 0x26: //Sixth datarow in PID group
break;
case 0x27: //Seventh datarow in PID group
break;
case 0x28: //Eighth datarow in PID group
break;
}
break;
default:
break;
}
}
#endif //DOUBLE_BATTERY
uint8_t CalculateCRC8(CAN_frame rx_frame) {
int crc = 0;
@ -409,6 +669,16 @@ void setup_battery(void) { // Performs one time setup at startup
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.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
datalayer.battery2.info.max_cell_voltage_mV = datalayer.battery.info.max_cell_voltage_mV;
datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
#endif //DOUBLE_BATTERY
}
#endif

View file

@ -0,0 +1,324 @@
#include "comm_can.h"
#include "../../include.h"
// Parameters
CAN_device_t CAN_cfg; // CAN Config
const int rx_queue_size = 10; // Receive Queue size
volatile bool send_ok = 0;
#ifdef CAN_ADDON
static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h
ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT);
static ACAN2515_Buffer16 gBuffer;
#endif //CAN_ADDON
#ifdef CANFD_ADDON
ACAN2517FD canfd(MCP2517_CS, SPI, MCP2517_INT);
#endif //CANFD_ADDON
// Initialization functions
void init_CAN() {
// CAN pins
#ifdef CAN_SE_PIN
pinMode(CAN_SE_PIN, OUTPUT);
digitalWrite(CAN_SE_PIN, LOW);
#endif // CAN_SE_PIN
CAN_cfg.speed = CAN_SPEED_500KBPS;
#ifdef NATIVECAN_250KBPS // Some component is requesting lower CAN speed
CAN_cfg.speed = CAN_SPEED_250KBPS;
#endif // NATIVECAN_250KBPS
CAN_cfg.tx_pin_id = CAN_TX_PIN;
CAN_cfg.rx_pin_id = CAN_RX_PIN;
CAN_cfg.rx_queue = xQueueCreate(rx_queue_size, sizeof(CAN_frame_t));
// Init CAN Module
ESP32Can.CANInit();
#ifdef CAN_ADDON
#ifdef DEBUG_VIA_USB
Serial.println("Dual CAN Bus (ESP32+MCP2515) selected");
#endif // DEBUG_VIA_USB
gBuffer.initWithSize(25);
SPI.begin(MCP2515_SCK, MCP2515_MISO, MCP2515_MOSI);
ACAN2515Settings settings(QUARTZ_FREQUENCY, 500UL * 1000UL); // CAN bit rate 500 kb/s
settings.mRequestedMode = ACAN2515Settings::NormalMode;
const uint16_t errorCodeMCP = can.begin(settings, [] { can.isr(); });
if (errorCodeMCP == 0) {
#ifdef DEBUG_VIA_USB
Serial.println("Can ok");
#endif // DEBUG_VIA_USB
} else {
#ifdef DEBUG_VIA_USB
Serial.print("Error Can: 0x");
Serial.println(errorCodeMCP, HEX);
#endif // DEBUG_VIA_USB
set_event(EVENT_CANMCP_INIT_FAILURE, (uint8_t)errorCodeMCP);
}
#endif // CAN_ADDON
#ifdef CANFD_ADDON
#ifdef DEBUG_VIA_USB
Serial.println("CAN FD add-on (ESP32+MCP2517) selected");
#endif // DEBUG_VIA_USB
SPI.begin(MCP2517_SCK, MCP2517_SDO, MCP2517_SDI);
ACAN2517FDSettings settings(CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ, 500 * 1000,
DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
settings.mRequestedMode = ACAN2517FDSettings::Normal20B; // ListenOnly / Normal20B / NormalFD
#else // not USE_CANFD_INTERFACE_AS_CLASSIC_CAN
settings.mRequestedMode = ACAN2517FDSettings::NormalFD; // ListenOnly / Normal20B / NormalFD
#endif // USE_CANFD_INTERFACE_AS_CLASSIC_CAN
const uint32_t errorCode = canfd.begin(settings, [] { canfd.isr(); });
canfd.poll();
if (errorCode == 0) {
#ifdef DEBUG_VIA_USB
Serial.print("Bit Rate prescaler: ");
Serial.println(settings.mBitRatePrescaler);
Serial.print("Arbitration Phase segment 1: ");
Serial.println(settings.mArbitrationPhaseSegment1);
Serial.print("Arbitration Phase segment 2: ");
Serial.println(settings.mArbitrationPhaseSegment2);
Serial.print("Arbitration SJW:");
Serial.println(settings.mArbitrationSJW);
Serial.print("Actual Arbitration Bit Rate: ");
Serial.print(settings.actualArbitrationBitRate());
Serial.println(" bit/s");
Serial.print("Exact Arbitration Bit Rate ? ");
Serial.println(settings.exactArbitrationBitRate() ? "yes" : "no");
Serial.print("Arbitration Sample point: ");
Serial.print(settings.arbitrationSamplePointFromBitStart());
Serial.println("%");
#endif // DEBUG_VIA_USB
} else {
#ifdef DEBUG_VIA_USB
Serial.print("CAN-FD Configuration error 0x");
Serial.println(errorCode, HEX);
#endif // DEBUG_VIA_USB
set_event(EVENT_CANFD_INIT_FAILURE, (uint8_t)errorCode);
}
#endif // CANFD_ADDON
}
// Transmit functions
void transmit_can(CAN_frame* tx_frame, int interface) {
if (!allowed_to_send_CAN) {
return;
}
print_can_frame(*tx_frame, frameDirection(MSG_TX));
switch (interface) {
case CAN_NATIVE:
CAN_frame_t frame;
frame.MsgID = tx_frame->ID;
frame.FIR.B.FF = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
frame.FIR.B.DLC = tx_frame->DLC;
frame.FIR.B.RTR = CAN_no_RTR;
for (uint8_t i = 0; i < tx_frame->DLC; i++) {
frame.data.u8[i] = tx_frame->data.u8[i];
}
ESP32Can.CANWriteFrame(&frame);
break;
case CAN_ADDON_MCP2515: {
#ifdef CAN_ADDON
//Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2
CANMessage MCP2515Frame;
MCP2515Frame.id = tx_frame->ID;
MCP2515Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
MCP2515Frame.len = tx_frame->DLC;
MCP2515Frame.rtr = false;
for (uint8_t i = 0; i < MCP2515Frame.len; i++) {
MCP2515Frame.data[i] = tx_frame->data.u8[i];
}
can.tryToSend(MCP2515Frame);
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
#endif //CAN_ADDON
} break;
case CANFD_NATIVE:
case CANFD_ADDON_MCP2518: {
#ifdef CANFD_ADDON
CANFDMessage MCP2518Frame;
if (tx_frame->FD) {
MCP2518Frame.type = CANFDMessage::CANFD_WITH_BIT_RATE_SWITCH;
} else { //Classic CAN message
MCP2518Frame.type = CANFDMessage::CAN_DATA;
}
MCP2518Frame.id = tx_frame->ID;
MCP2518Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
MCP2518Frame.len = tx_frame->DLC;
for (uint8_t i = 0; i < MCP2518Frame.len; i++) {
MCP2518Frame.data[i] = tx_frame->data.u8[i];
}
send_ok = canfd.tryToSend(MCP2518Frame);
if (!send_ok) {
set_event(EVENT_CANFD_BUFFER_FULL, interface);
}
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
#endif //CANFD_ADDON
} break;
default:
// Invalid interface sent with function call. TODO: Raise event that coders messed up
break;
}
}
void send_can() {
if (!allowed_to_send_CAN) {
return;
}
send_can_battery();
#ifdef CAN_INVERTER_SELECTED
send_can_inverter();
#endif // CAN_INVERTER_SELECTED
#ifdef CHARGER_SELECTED
send_can_charger();
#endif // CHARGER_SELECTED
}
// Receive functions
void receive_can(CAN_frame* rx_frame, int interface) {
print_can_frame(*rx_frame, frameDirection(MSG_RX));
if (interface == can_config.battery) {
receive_can_battery(*rx_frame);
#ifdef CHADEMO_BATTERY
ISA_handleFrame(rx_frame);
#endif
}
if (interface == can_config.inverter) {
#ifdef CAN_INVERTER_SELECTED
receive_can_inverter(*rx_frame);
#endif
}
if (interface == can_config.battery_double) {
#ifdef DOUBLE_BATTERY
receive_can_battery2(*rx_frame);
#endif
}
if (interface == can_config.charger) {
#ifdef CHARGER_SELECTED
receive_can_charger(*rx_frame);
#endif
}
}
void receive_can_native() { // This section checks if we have a complete CAN message incoming on native CAN port
CAN_frame_t rx_frame_native;
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame_native, 0) == pdTRUE) {
CAN_frame rx_frame;
rx_frame.ID = rx_frame_native.MsgID;
if (rx_frame_native.FIR.B.FF == CAN_frame_std) {
rx_frame.ext_ID = false;
} else { //CAN_frame_ext == 1
rx_frame.ext_ID = true;
}
rx_frame.DLC = rx_frame_native.FIR.B.DLC;
for (uint8_t i = 0; i < rx_frame.DLC && i < 8; i++) {
rx_frame.data.u8[i] = rx_frame_native.data.u8[i];
}
//message incoming, pass it on to the handler
receive_can(&rx_frame, CAN_NATIVE);
}
}
#ifdef CAN_ADDON
void receive_can_addon() { // This section checks if we have a complete CAN message incoming on add-on CAN port
CAN_frame rx_frame; // Struct with our CAN format
CANMessage MCP2515Frame; // Struct with ACAN2515 library format, needed to use the MCP2515 library
if (can.available()) {
can.receive(MCP2515Frame);
rx_frame.ID = MCP2515Frame.id;
rx_frame.ext_ID = MCP2515Frame.ext ? CAN_frame_ext : CAN_frame_std;
rx_frame.DLC = MCP2515Frame.len;
for (uint8_t i = 0; i < MCP2515Frame.len && i < 8; i++) {
rx_frame.data.u8[i] = MCP2515Frame.data[i];
}
//message incoming, pass it on to the handler
receive_can(&rx_frame, CAN_ADDON_MCP2515);
}
}
#endif // CAN_ADDON
#ifdef CANFD_ADDON
// Functions
void receive_canfd_addon() { // This section checks if we have a complete CAN-FD message incoming
CANFDMessage frame;
int count = 0;
while (canfd.available() && count++ < 16) {
canfd.receive(frame);
CAN_frame rx_frame;
rx_frame.ID = frame.id;
rx_frame.ext_ID = frame.ext;
rx_frame.DLC = frame.len;
memcpy(rx_frame.data.u8, frame.data, MIN(rx_frame.DLC, 64));
//message incoming, pass it on to the handler
receive_can(&rx_frame, CANFD_ADDON_MCP2518);
receive_can(&rx_frame, CANFD_NATIVE);
}
}
#endif // CANFD_ADDON
// Support functions
void print_can_frame(CAN_frame frame, frameDirection msgDir) {
#ifdef DEBUG_CAN_DATA // If enabled in user settings, print out the CAN messages via USB
uint8_t i = 0;
Serial.print("(");
Serial.print(millis() / 1000.0);
(msgDir == MSG_RX) ? Serial.print(") RX0 ") : Serial.print(") TX1 ");
Serial.print(frame.ID, HEX);
Serial.print(" [");
Serial.print(frame.DLC);
Serial.print("] ");
for (i = 0; i < frame.DLC; i++) {
Serial.print(frame.data.u8[i] < 16 ? "0" : "");
Serial.print(frame.data.u8[i], HEX);
if (i < frame.DLC - 1)
Serial.print(" ");
}
Serial.println("");
#endif // DEBUG_CAN_DATA
if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording
char* message_string = datalayer.system.info.logged_can_messages;
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
if (offset + 128 > sizeof(datalayer.system.info.logged_can_messages)) {
// Not enough space, reset and start from the beginning
offset = 0;
}
unsigned long currentTime = millis();
// Add timestamp
offset += snprintf(message_string + offset, message_string_size - offset, "(%lu.%03lu) ", currentTime / 1000,
currentTime % 1000);
// Add direction. The 0 and 1 after RX and TX ensures that SavvyCAN puts TX and RX in a different bus.
offset +=
snprintf(message_string + offset, message_string_size - offset, "%s ", (msgDir == MSG_RX) ? "RX0" : "TX1");
// Add ID and DLC
offset += snprintf(message_string + offset, message_string_size - offset, "%X [%u] ", frame.ID, frame.DLC);
// Add data bytes
for (uint8_t i = 0; i < frame.DLC; i++) {
if (i < frame.DLC - 1) {
offset += snprintf(message_string + offset, message_string_size - offset, "%02X ", frame.data.u8[i]);
} else {
offset += snprintf(message_string + offset, message_string_size - offset, "%02X", frame.data.u8[i]);
}
}
// Add linebreak
offset += snprintf(message_string + offset, message_string_size - offset, "\n");
datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer
}
}

View file

@ -0,0 +1,93 @@
#ifndef _COMM_CAN_H_
#define _COMM_CAN_H_
#include "../../include.h"
#include "../../datalayer/datalayer.h"
#include "../../devboard/utils/events.h"
#include "../../devboard/utils/value_mapping.h"
#include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#ifdef CAN_ADDON
#include "../../lib/pierremolinaro-acan2515/ACAN2515.h"
#endif //CAN_ADDON
#ifdef CANFD_ADDON
#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
#endif //CANFD_ADDON
enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1
/**
* @brief Initialization function for CAN.
*
* @param[in] void
*
* @return void
*/
void init_CAN();
/**
* @brief Transmit one CAN frame
*
* @param[in] CAN_frame* tx_frame
* @param[in] int interface
*
* @return void
*/
void transmit_can();
/**
* @brief Send CAN messages to all components
*
* @param[in] void
*
* @return void
*/
void send_can();
/**
* @brief Receive CAN messages from all interfaces
*
* @param[in] void
*
* @return void
*/
void receive_can();
/**
* @brief Receive CAN messages from CAN tranceiver natively installed on Lilygo hardware
*
* @param[in] void
*
* @return void
*/
void receive_can_native();
/**
* @brief Receive CAN messages from CAN addon chip
*
* @param[in] void
*
* @return void
*/
void receive_can_addon();
/**
* @brief Receive CAN messages from CANFD addon chip
*
* @param[in] void
*
* @return void
*/
void receive_canfd_addon();
/**
* @brief print CAN frames via USB
*
* @param[in] void
*
* @return void
*/
void print_can_frame(CAN_frame frame, frameDirection msgDir);
#endif

View file

@ -0,0 +1,204 @@
#include "comm_contactorcontrol.h"
#include "../../include.h"
// Parameters
#ifndef CONTACTOR_CONTROL
#ifdef PWM_CONTACTOR_CONTROL
#error CONTACTOR_CONTROL needs to be enabled for PWM_CONTACTOR_CONTROL
#endif
#endif
#ifdef CONTACTOR_CONTROL
enum State { DISCONNECTED, START_PRECHARGE, PRECHARGE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED };
State contactorStatus = DISCONNECTED;
#define ON 1
#define OFF 0
#ifdef NC_CONTACTORS //Normally closed contactors use inverted logic
#undef ON
#define ON 0
#undef OFF
#define OFF 1
#endif //NC_CONTACTORS
#define MAX_ALLOWED_FAULT_TICKS 1000
#define NEGATIVE_CONTACTOR_TIME_MS \
500 // Time after negative contactor is turned on, to start precharge (not actual precharge time!)
#define PRECHARGE_COMPLETED_TIME_MS \
1000 // After successful precharge, resistor is turned off after this delay (and contactors are economized if PWM enabled)
#define PWM_Freq 20000 // 20 kHz frequency, beyond audible range
#define PWM_Res 10 // 10 Bit resolution 0 to 1023, maps 'nicely' to 0% 100%
#define PWM_HOLD_DUTY 250
#define PWM_OFF_DUTY 0
#define PWM_ON_DUTY 1023
#define PWM_Positive_Channel 0
#define PWM_Negative_Channel 1
unsigned long prechargeStartTime = 0;
unsigned long negativeStartTime = 0;
unsigned long prechargeCompletedTime = 0;
unsigned long timeSpentInFaultedMode = 0;
#endif
void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFF) {
#ifdef PWM_CONTACTOR_CONTROL
if (pwm_freq != 0xFFFF) {
ledcWrite(pin, pwm_freq);
return;
}
#endif
if (direction == 1) {
digitalWrite(pin, HIGH);
} else { // 0
digitalWrite(pin, LOW);
}
}
// Initialization functions
void init_contactors() {
// Init contactor pins
#ifdef CONTACTOR_CONTROL
#ifdef PWM_CONTACTOR_CONTROL
// Setup PWM Channel Frequency and Resolution
ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Positive_Channel);
ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Negative_Channel);
// Set all pins OFF (0% PWM)
ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_OFF_DUTY);
ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_OFF_DUTY);
#else //Normal CONTACTOR_CONTROL
pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT);
set(POSITIVE_CONTACTOR_PIN, OFF);
pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT);
set(NEGATIVE_CONTACTOR_PIN, OFF);
#endif // Precharge never has PWM regardless of setting
pinMode(PRECHARGE_PIN, OUTPUT);
set(PRECHARGE_PIN, OFF);
#endif // CONTACTOR_CONTROL
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
pinMode(SECOND_POSITIVE_CONTACTOR_PIN, OUTPUT);
set(SECOND_POSITIVE_CONTACTOR_PIN, OFF);
pinMode(SECOND_NEGATIVE_CONTACTOR_PIN, OUTPUT);
set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF);
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
// Init BMS contactor
#ifdef HW_STARK // TODO: Rewrite this so LilyGo can also handle this BMS contactor
pinMode(BMS_POWER, OUTPUT);
digitalWrite(BMS_POWER, HIGH);
#endif // HW_STARK
}
// Main functions
void handle_contactors() {
#ifdef BYD_SMA
datalayer.system.status.inverter_allows_contactor_closing = digitalRead(INVERTER_CONTACTOR_ENABLE_PIN);
#endif // BYD_SMA
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
handle_contactors_battery2();
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
#ifdef CONTACTOR_CONTROL
// First check if we have any active errors, incase we do, turn off the battery
if (datalayer.battery.status.bms_status == FAULT) {
timeSpentInFaultedMode++;
} else {
timeSpentInFaultedMode = 0;
}
//handle contactor control SHUTDOWN_REQUESTED
if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS) {
contactorStatus = SHUTDOWN_REQUESTED;
}
if (contactorStatus == SHUTDOWN_REQUESTED) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set_event(EVENT_ERROR_OPEN_CONTACTOR, 0);
datalayer.system.status.contactors_engaged = false;
return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured)
}
// After that, check if we are OK to start turning on the battery
if (contactorStatus == DISCONNECTED) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
if (datalayer.system.status.battery_allows_contactor_closing &&
datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.settings.equipment_stop_active) {
contactorStatus = START_PRECHARGE;
}
}
// In case the inverter requests contactors to open, set the state accordingly
if (contactorStatus == COMPLETED) {
//Incase inverter (or estop) requests contactors to open, make state machine jump to Disconnected state (recoverable)
if (!datalayer.system.status.inverter_allows_contactor_closing || datalayer.system.settings.equipment_stop_active) {
contactorStatus = DISCONNECTED;
}
// Skip running the state machine below if it has already completed
return;
}
unsigned long currentTime = millis();
if (currentTime < INTERVAL_10_S) {
// Skip running the state machine before system has started up.
// Gives the system some time to detect any faults from battery before blindly just engaging the contactors
return;
}
// Handle actual state machine. This first turns on Negative, then Precharge, then Positive, and finally turns OFF precharge
switch (contactorStatus) {
case START_PRECHARGE:
set(NEGATIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY);
prechargeStartTime = currentTime;
contactorStatus = PRECHARGE;
break;
case PRECHARGE:
if (currentTime - prechargeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) {
set(PRECHARGE_PIN, ON);
negativeStartTime = currentTime;
contactorStatus = POSITIVE;
}
break;
case POSITIVE:
if (currentTime - negativeStartTime >= PRECHARGE_TIME_MS) {
set(POSITIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY);
prechargeCompletedTime = currentTime;
contactorStatus = PRECHARGE_OFF;
}
break;
case PRECHARGE_OFF:
if (currentTime - prechargeCompletedTime >= PRECHARGE_COMPLETED_TIME_MS) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY);
set(POSITIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY);
contactorStatus = COMPLETED;
datalayer.system.status.contactors_engaged = true;
}
break;
default:
break;
}
#endif // CONTACTOR_CONTROL
}
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
void handle_contactors_battery2() {
if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allows_contactor_closing) {
set(SECOND_NEGATIVE_CONTACTOR_PIN, ON);
set(SECOND_POSITIVE_CONTACTOR_PIN, ON);
datalayer.system.status.contactors_battery2_engaged = true;
} else { // Closing contactors on secondary battery not allowed
set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF);
set(SECOND_POSITIVE_CONTACTOR_PIN, OFF);
datalayer.system.status.contactors_battery2_engaged = false;
}
}
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY

View file

@ -0,0 +1,36 @@
#ifndef _COMM_CONTACTORCONTROL_H_
#define _COMM_CONTACTORCONTROL_H_
#include "../../include.h"
#include "../../datalayer/datalayer.h"
#include "../../devboard/utils/events.h"
/**
* @brief Contactor initialization
*
* @param[in] void
*
* @return void
*/
void init_contactors();
/**
* @brief Handle contactors
*
* @param[in] void
*
* @return void
*/
void handle_contactors();
/**
* @brief Handle contactors of battery 2
*
* @param[in] void
*
* @return void
*/
void handle_contactors_battery2();
#endif

View file

@ -0,0 +1,51 @@
#include "comm_equipmentstopbutton.h"
#include "../../include.h"
// Parameters
#ifdef EQUIPMENT_STOP_BUTTON
const unsigned long equipment_button_long_press_duration =
15000; // 15 seconds for long press in case of MOMENTARY_SWITCH
const unsigned long equipment_button_debounce_duration = 200; // 200ms for debouncing the button
unsigned long timeSincePress = 0; // Variable to store the time since the last press
DebouncedButton equipment_stop_button; // Debounced button object
#endif // EQUIPMENT_STOP_BUTTON
// Initialization functions
#ifdef EQUIPMENT_STOP_BUTTON
void init_equipment_stop_button() {
//using external pullup resistors NC
pinMode(EQUIPMENT_STOP_PIN, INPUT);
// Initialize the debounced button with NC switch type and equipment_button_debounce_duration debounce time
initDebouncedButton(equipment_stop_button, EQUIPMENT_STOP_PIN, NC, equipment_button_debounce_duration);
}
#endif // EQUIPMENT_STOP_BUTTON
// Main functions
#ifdef EQUIPMENT_STOP_BUTTON
void monitor_equipment_stop_button() {
ButtonState changed_state = debounceButton(equipment_stop_button, timeSincePress);
if (equipment_stop_behavior == LATCHING_SWITCH) {
if (changed_state == PRESSED) {
// Changed to ON initiating equipment stop.
setBatteryPause(true, false, true);
} else if (changed_state == RELEASED) {
// Changed to OFF ending equipment stop.
setBatteryPause(false, false, false);
}
} else if (equipment_stop_behavior == MOMENTARY_SWITCH) {
if (changed_state == RELEASED) { // button is released
if (timeSincePress < equipment_button_long_press_duration) {
// Short press detected, trigger equipment stop
setBatteryPause(true, false, true);
} else {
// Long press detected, reset equipment stop state
setBatteryPause(false, false, false);
}
}
}
}
#endif // EQUIPMENT_STOP_BUTTON

View file

@ -0,0 +1,28 @@
#ifndef _COMM_EQUIPMENTSTOPBUTTON_H_
#define _COMM_EQUIPMENTSTOPBUTTON_H_
#include "../../include.h"
#ifdef EQUIPMENT_STOP_BUTTON
#include "../../devboard/utils/debounce_button.h"
#endif
/**
* @brief Initialization of equipment stop button
*
* @param[in] void
*
* @return void
*/
void init_equipment_stop_button();
/**
* @brief Monitor equipment stop button
*
* @param[in] void
*
* @return void
*/
void monitor_equipment_stop_button();
#endif

View file

@ -0,0 +1,125 @@
#include "comm_nvm.h"
#include "../../include.h"
// Parameters
Preferences settings; // Store user settings
// Initialization functions
void init_stored_settings() {
static uint32_t temp = 0;
// ATTENTION ! The maximum length for settings keys is 15 characters
settings.begin("batterySettings", false);
// Always get the equipment stop status
datalayer.system.settings.equipment_stop_active = settings.getBool("EQUIPMENT_STOP", false);
if (datalayer.system.settings.equipment_stop_active) {
set_event(EVENT_EQUIPMENT_STOP, 1);
}
#ifndef LOAD_SAVED_SETTINGS_ON_BOOT
settings.clear(); // If this clear function is executed, no settings will be read from storage
//always save the equipment stop status
settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active);
#endif // LOAD_SAVED_SETTINGS_ON_BOOT
#ifdef WIFI
char tempSSIDstring[63]; // Allocate buffer with sufficient size
size_t lengthSSID = settings.getString("SSID", tempSSIDstring, sizeof(tempSSIDstring));
if (lengthSSID > 0) { // Successfully read the string from memory. Set it to SSID!
ssid = tempSSIDstring;
} else { // Reading from settings failed. Do nothing with SSID. Raise event?
}
char tempPasswordString[63]; // Allocate buffer with sufficient size
size_t lengthPassword = settings.getString("PASSWORD", tempPasswordString, sizeof(tempPasswordString));
if (lengthPassword > 7) { // Successfully read the string from memory. Set it to password!
password = tempPasswordString;
} else { // Reading from settings failed. Do nothing with SSID. Raise event?
}
#endif // WIFI
temp = settings.getUInt("BATTERY_WH_MAX", false);
if (temp != 0) {
datalayer.battery.info.total_capacity_Wh = temp;
}
temp = settings.getUInt("MAXPERCENTAGE", false);
if (temp != 0) {
datalayer.battery.settings.max_percentage = temp * 10; // Multiply by 10 for backwards compatibility
}
temp = settings.getUInt("MINPERCENTAGE", false);
if (temp != 0) {
datalayer.battery.settings.min_percentage = temp * 10; // Multiply by 10 for backwards compatibility
}
temp = settings.getUInt("MAXCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_charge_dA = temp;
}
temp = settings.getUInt("MAXDISCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_discharge_dA = temp;
}
datalayer.battery.settings.soc_scaling_active = settings.getBool("USE_SCALED_SOC", false);
temp = settings.getUInt("TARGETCHVOLT", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_charge_voltage_dV = temp;
}
temp = settings.getUInt("TARGETDISCHVOLT", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_discharge_voltage_dV = temp;
}
datalayer.battery.settings.user_set_voltage_limits_active = settings.getBool("USEVOLTLIMITS", false);
settings.end();
}
void store_settings_equipment_stop() {
settings.begin("batterySettings", false);
settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active);
settings.end();
}
void store_settings() {
// ATTENTION ! The maximum length for settings keys is 15 characters
if (!settings.begin("batterySettings", false)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 0);
return;
}
#ifdef WIFI
if (!settings.putString("SSID", String(ssid.c_str()))) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 1);
}
if (!settings.putString("PASSWORD", String(password.c_str()))) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 2);
}
#endif
if (!settings.putUInt("BATTERY_WH_MAX", datalayer.battery.info.total_capacity_Wh)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 3);
}
if (!settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 4);
}
if (!settings.putUInt("MAXPERCENTAGE", datalayer.battery.settings.max_percentage / 10)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 5);
}
if (!settings.putUInt("MINPERCENTAGE", datalayer.battery.settings.min_percentage / 10)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 6);
}
if (!settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 7);
}
if (!settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 8);
}
if (!settings.putBool("USEVOLTLIMITS", datalayer.battery.settings.user_set_voltage_limits_active)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 9);
}
if (!settings.putUInt("TARGETCHVOLT", datalayer.battery.settings.max_user_set_charge_voltage_dV)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 10);
}
if (!settings.putUInt("TARGETDISCHVOLT", datalayer.battery.settings.max_user_set_discharge_voltage_dV)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 11);
}
settings.end(); // Close preferences handle
}

View file

@ -0,0 +1,37 @@
#ifndef _COMM_NVM_H_
#define _COMM_NVM_H_
#include "../../include.h"
#include "../../datalayer/datalayer.h"
#include "../../devboard/utils/events.h"
#include "../../devboard/wifi/wifi.h"
/**
* @brief Initialization of setting storage
*
* @param[in] void
*
* @return void
*/
void init_stored_settings();
/**
* @brief Store settings of equipment stop button
*
* @param[in] void
*
* @return void
*/
void store_settings_equipment_stop();
/**
* @brief Store settings
*
* @param[in] void
*
* @return void
*/
void store_settings();
#endif

View file

@ -0,0 +1,51 @@
#include "comm_rs485.h"
#include "../../include.h"
// Parameters
#ifdef MODBUS_INVERTER_SELECTED
#define MB_RTU_NUM_VALUES 13100
uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout
ModbusServerRTU MBserver(Serial2, 2000);
#endif
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
#define SERIAL_LINK_BAUDRATE 112500
#endif
// Initialization functions
void init_rs485() {
// Set up Modbus RTU Server
#ifdef RS485_EN_PIN
pinMode(RS485_EN_PIN, OUTPUT);
digitalWrite(RS485_EN_PIN, HIGH);
#endif // RS485_EN_PIN
#ifdef RS485_SE_PIN
pinMode(RS485_SE_PIN, OUTPUT);
digitalWrite(RS485_SE_PIN, HIGH);
#endif // RS485_SE_PIN
#ifdef PIN_5V_EN
pinMode(PIN_5V_EN, OUTPUT);
digitalWrite(PIN_5V_EN, HIGH);
#endif // PIN_5V_EN
#ifdef RS485_INVERTER_SELECTED
Serial2.begin(57600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
#endif // RS485_INVERTER_SELECTED
#ifdef MODBUS_INVERTER_SELECTED
#ifdef BYD_MODBUS
// Init Static data to the RTU Modbus
handle_static_data_modbus_byd();
#endif // BYD_MODBUS
// Init Serial2 connected to the RTU Modbus
RTUutils::prepareHardwareSerial(Serial2);
Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
// Register served function code worker for server
MBserver.registerWorker(MBTCP_ID, READ_HOLD_REGISTER, &FC03);
MBserver.registerWorker(MBTCP_ID, WRITE_HOLD_REGISTER, &FC06);
MBserver.registerWorker(MBTCP_ID, WRITE_MULT_REGISTERS, &FC16);
MBserver.registerWorker(MBTCP_ID, R_W_MULT_REGISTERS, &FC23);
// Start ModbusRTU background task
MBserver.begin(Serial2, MODBUS_CORE);
#endif // MODBUS_INVERTER_SELECTED
}

View file

@ -0,0 +1,19 @@
#ifndef _COMM_RS485_H_
#define _COMM_RS485_H_
#include "../../include.h"
#include "../../lib/eModbus-eModbus/Logging.h"
#include "../../lib/eModbus-eModbus/ModbusServerRTU.h"
#include "../../lib/eModbus-eModbus/scripts/mbServerFCs.h"
/**
* @brief Initialization of RS485
*
* @param[in] void
*
* @return void
*/
void init_rs485();
#endif

View file

@ -0,0 +1,35 @@
#include "comm_seriallink.h"
#include "../../include.h"
// Parameters
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
#define SERIAL_LINK_BAUDRATE 112500
#endif
// Initialization functions
void init_serialDataLink() {
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
Serial2.begin(SERIAL_LINK_BAUDRATE, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
#endif // SERIAL_LINK_RECEIVER || SERIAL_LINK_TRANSMITTER
}
// Main functions
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
void run_serialDataLink() {
static unsigned long updateTime = 0;
unsigned long currentMillis = millis();
if ((currentMillis - updateTime) > 1) { //Every 2ms
updateTime = currentMillis;
#ifdef SERIAL_LINK_RECEIVER
manageSerialLinkReceiver();
#endif
#ifdef SERIAL_LINK_TRANSMITTER
manageSerialLinkTransmitter();
#endif
}
}
#endif // SERIAL_LINK_RECEIVER || SERIAL_LINK_TRANSMITTER

View file

@ -0,0 +1,17 @@
#ifndef _COMM_SERIALLINK_H_
#define _COMM_SERIALLINK_H_
#include "../../include.h"
/**
* @brief Initialization of serial data link
*
* @param[in] void
*
* @return void
*/
void init_serialDataLink();
void run_serialDataLink();
#endif

View file

@ -107,10 +107,20 @@ typedef struct {
* you want the inverter to be able to use. At this real SOC, the inverter
* will "see" 100% */
uint16_t max_percentage = BATTERY_MAXPERCENTAGE;
/** The user specified maximum allowed charge rate, in deciAmpere. 300 = 30.0 A */
uint16_t max_user_set_charge_dA = BATTERY_MAX_CHARGE_AMP;
/** The user specified maximum allowed discharge rate, in deciAmpere. 300 = 30.0 A */
uint16_t max_user_set_discharge_dA = BATTERY_MAX_DISCHARGE_AMP;
/** User specified discharge/charge voltages in use. Set to true to use user specified values */
/** Some inverters like to see a specific target voltage for charge/discharge. Use these values to override automatic voltage limits*/
bool user_set_voltage_limits_active = BATTERY_USE_VOLTAGE_LIMITS;
/** The user specified maximum allowed charge voltage, in deciVolt. 4000 = 400.0 V */
uint16_t max_user_set_charge_voltage_dV = BATTERY_MAX_CHARGE_VOLTAGE;
/** 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;
} DATALAYER_BATTERY_SETTINGS_TYPE;
typedef struct {

View file

@ -215,6 +215,11 @@ typedef struct {
} DATALAYER_INFO_TESLA;
typedef struct {
/** uint8_t */
/** Battery info, stores raw HEX values for ASCII chars */
uint8_t BatterySerialNumber[15] = {0};
uint8_t BatteryPartNumber[7] = {0};
uint8_t BMSIDcode[8] = {0};
/** uint8_t */
/** Enum, ZE0 = 0, AZE0 = 1, ZE1 = 2 */
uint8_t LEAF_gen = 0;

View file

@ -26,14 +26,14 @@
// CAN2 defines below
// DUAL_CAN defines
// CAN_ADDON defines
#define MCP2515_SCK 12 // SCK input of MCP2515
#define MCP2515_MOSI 5 // SDI input of MCP2515
#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors
#define MCP2515_CS 18 // CS input of MCP2515
#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors
// CAN_FD defines
// CANFD_ADDON defines
#define MCP2517_SCK 17 // SCK input of MCP2517
#define MCP2517_SDI 23 // SDI input of MCP2517
#define MCP2517_SDO 39 // SDO output of MCP2517
@ -80,17 +80,17 @@
#endif
#ifdef CHADEMO_BATTERY
#ifdef DUAL_CAN
#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
#ifdef CAN_ADDON
#error CHADEMO and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#endif
#ifdef EQUIPMENT_STOP_BUTTON
#ifdef DUAL_CAN
#error EQUIPMENT_STOP_BUTTON and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
#ifdef CAN_ADDON
#error EQUIPMENT_STOP_BUTTON and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CAN_FD
#error EQUIPMENT_STOP_BUTTON and CAN_FD cannot coexist due to overlapping GPIO pin usage
#ifdef CANFD_ADDON
#error EQUIPMENT_STOP_BUTTON and CANFD_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CHADEMO_BATTERY
#error EQUIPMENT_STOP_BUTTON and CHADEMO_BATTERY cannot coexist due to overlapping GPIO pin usage

View file

@ -26,14 +26,14 @@
// CAN2 defines below
// DUAL_CAN defines
// CAN_ADDON defines
#define MCP2515_SCK 12 // SCK input of MCP2515
#define MCP2515_MOSI 5 // SDI input of MCP2515
#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors
#define MCP2515_CS 18 // CS input of MCP2515
#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors
// CAN_FD defines
// CANFD_ADDON defines
#define MCP2517_SCK 12 // SCK input of MCP2517
#define MCP2517_SDI 5 // SDI input of MCP2517
#define MCP2517_SDO 34 // SDO output of MCP2517
@ -76,17 +76,17 @@
#endif
#ifdef CHADEMO_BATTERY
#ifdef DUAL_CAN
#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
#ifdef CAN_ADDON
#error CHADEMO and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#endif
#ifdef EQUIPMENT_STOP_BUTTON
#ifdef DUAL_CAN
#error EQUIPMENT_STOP_BUTTON and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
#ifdef CAN_ADDON
#error EQUIPMENT_STOP_BUTTON and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CAN_FD
#error EQUIPMENT_STOP_BUTTON and CAN_FD cannot coexist due to overlapping GPIO pin usage
#ifdef CANFD_ADDON
#error EQUIPMENT_STOP_BUTTON and CANFD_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CHADEMO_BATTERY
#error EQUIPMENT_STOP_BUTTON and CHADEMO_BATTERY cannot coexist due to overlapping GPIO pin usage

View file

@ -38,7 +38,7 @@ GPIOs on extra header
#define CAN_RX_PIN GPIO_NUM_26
// #define CAN_SE_PIN 23 // (No function, GPIO 23 used instead as MCP_SCK)
// CAN_FD defines
// CANFD_ADDON defines
#define MCP2517_SCK 17 // SCK input of MCP2517
#define MCP2517_SDI 5 // SDI input of MCP2517
#define MCP2517_SDO 34 // SDO output of MCP2517

View file

@ -111,7 +111,8 @@ void update_machineryprotection() {
#endif //NISSAN_LEAF_BATTERY
// Check diff between highest and lowest cell
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
cell_deviation_mV =
std::abs(datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
if (cell_deviation_mV > datalayer.battery.info.max_cell_voltage_deviation_mV) {
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
} else {

View file

@ -191,6 +191,7 @@ void init_events(void) {
events.entries[EVENT_DUMMY_DEBUG].level = EVENT_LEVEL_DEBUG;
events.entries[EVENT_DUMMY_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_DUMMY_ERROR].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_PERSISTENT_SAVE_INFO].level = EVENT_LEVEL_INFO;
events.entries[EVENT_SERIAL_RX_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_SERIAL_RX_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_SERIAL_TX_FAILURE].level = EVENT_LEVEL_ERROR;
@ -368,6 +369,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "The dummy warning event was set!"; // Don't change this event message!
case EVENT_DUMMY_ERROR:
return "The dummy error event was set!"; // Don't change this event message!
case EVENT_PERSISTENT_SAVE_INFO:
return "Info: Failed to save user settings. Namespace full?";
case EVENT_SERIAL_RX_WARNING:
return "Error in serial function: No data received for some time, see data for minutes";
case EVENT_SERIAL_RX_FAILURE:

View file

@ -6,7 +6,7 @@
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
#define EE_MAGIC_HEADER_VALUE 0x0017 // 0x0000 to 0xFFFF
#define EE_MAGIC_HEADER_VALUE 0x0018 // 0x0000 to 0xFFFF
#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,
@ -79,6 +79,7 @@
XX(EVENT_DUMMY_DEBUG) \
XX(EVENT_DUMMY_WARNING) \
XX(EVENT_DUMMY_ERROR) \
XX(EVENT_PERSISTENT_SAVE_INFO) \
XX(EVENT_SERIAL_RX_WARNING) \
XX(EVENT_SERIAL_RX_FAILURE) \
XX(EVENT_SERIAL_TX_FAILURE) \

View file

@ -399,6 +399,20 @@ String advanced_battery_processor(const String& var) {
#ifdef NISSAN_LEAF_BATTERY
static const char* LEAFgen[] = {"ZE0", "AZE0", "ZE1"};
content += "<h4>LEAF generation: " + String(LEAFgen[datalayer_extended.nissanleaf.LEAF_gen]) + "</h4>";
char readableSerialNumber[16]; // One extra space for null terminator
memcpy(readableSerialNumber, datalayer_extended.nissanleaf.BatterySerialNumber,
sizeof(datalayer_extended.nissanleaf.BatterySerialNumber));
readableSerialNumber[15] = '\0'; // Null terminate the string
content += "<h4>Serial number: " + String(readableSerialNumber) + "</h4>";
char readablePartNumber[8]; // One extra space for null terminator
memcpy(readablePartNumber, datalayer_extended.nissanleaf.BatteryPartNumber,
sizeof(datalayer_extended.nissanleaf.BatteryPartNumber));
readablePartNumber[7] = '\0'; // Null terminate the string
content += "<h4>Part number: " + String(readablePartNumber) + "</h4>";
char readableBMSID[9]; // One extra space for null terminator
memcpy(readableBMSID, datalayer_extended.nissanleaf.BMSIDcode, sizeof(datalayer_extended.nissanleaf.BMSIDcode));
readableBMSID[8] = '\0'; // Null terminate the string
content += "<h4>BMS ID: " + String(readableBMSID) + "</h4>";
content += "<h4>GIDS: " + String(datalayer_extended.nissanleaf.GIDS) + "</h4>";
content += "<h4>Regen kW: " + String(datalayer_extended.nissanleaf.ChargePowerLimit) + "</h4>";
content += "<h4>Charge kW: " + String(datalayer_extended.nissanleaf.MaxPowerForCharger) + "</h4>";

View file

@ -72,6 +72,21 @@ String settings_processor(const String& var) {
content += "<h4 style='color: white;'>Max discharge speed: " +
String(datalayer.battery.settings.max_user_set_discharge_dA / 10.0, 1) +
" A </span> <button onclick='editMaxDischargeA()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Manual charge voltage limits: <span id='BATTERY_USE_VOLTAGE_LIMITS'>" +
String(datalayer.battery.settings.user_set_voltage_limits_active
? "<span>&#10003;</span>"
: "<span style='color: red;'>&#10005;</span>") +
"</span> <button onclick='editUseVoltageLimit()'>Edit</button></h4>";
content +=
"<h4 style='color: " +
String(datalayer.battery.settings.user_set_voltage_limits_active ? "white" : "darkgrey") +
";'>Target charge voltage: " + String(datalayer.battery.settings.max_user_set_charge_voltage_dV / 10.0, 1) +
" V </span> <button onclick='editMaxChargeVoltage()'>Edit</button></h4>";
content += "<h4 style='color: " +
String(datalayer.battery.settings.user_set_voltage_limits_active ? "white" : "darkgrey") +
";'>Target discharge voltage: " +
String(datalayer.battery.settings.max_user_set_discharge_voltage_dV / 10.0, 1) +
" V </span> <button onclick='editMaxDischargeVoltage()'>Edit</button></h4>";
// Close the block
content += "</div>";
@ -135,7 +150,9 @@ String settings_processor(const String& var) {
"updateBatterySize?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 1 "
"and 120000.');}}}";
content +=
"function editUseScaledSOC(){var value=prompt('Should SOC% be scaled? (0 = No, 1 = "
"function editUseScaledSOC(){var value=prompt('Extends battery life by rescaling the SOC within the configured "
"minimum "
"and maximum percentage. Should SOC scaling be applied? (0 = No, 1 = "
"Yes):');if(value!==null){if(value==0||value==1){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateUseScaledSOC?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
@ -166,6 +183,33 @@ String settings_processor(const String& var) {
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateMaxDischargeA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
"and 1000.0');}}}";
content +=
"function editUseVoltageLimit(){var value=prompt('Enable this option to manually restrict charge/discharge to "
"a specific voltage set below."
"If disabled the emulator automatically determines this based on battery limits. Restrict manually? (0 = No, 1 "
"= Yes)"
":');if(value!==null){if(value==0||value==1){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateUseVoltageLimit?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between "
"0 "
"and 1.');}}}";
content +=
"function editMaxChargeVoltage(){var value=prompt('Some inverters needs to be artificially limited. Enter new "
"voltage setpoint batttery should charge to (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var "
"xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateMaxChargeVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value "
"between 0 "
"and 1000.0');}}}";
content +=
"function editMaxDischargeVoltage(){var value=prompt('Some inverters needs to be artificially limited. Enter "
"new "
"voltage setpoint batttery should discharge to (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var "
"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');}}}";
#ifdef TEST_FAKE_BATTERY
content +=
@ -232,7 +276,7 @@ const char* getCANInterfaceName(CAN_Interface interface) {
#endif
case CAN_ADDON_MCP2515:
return "Add-on CAN via GPIO MCP2515";
case CAN_ADDON_FD_MCP2518:
case CANFD_ADDON_MCP2518:
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
return "Add-on CAN-FD via GPIO MCP2518 (Classic CAN)";
#else

View file

@ -131,7 +131,7 @@ void init_webserver() {
String value = request->getParam("value")->value();
if (value.length() <= 63) { // Check if SSID is within the allowable length
ssid = value.c_str();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "SSID must be 63 characters or less");
@ -148,7 +148,7 @@ void init_webserver() {
String value = request->getParam("value")->value();
if (value.length() > 8) { // Check if password is within the allowable length
password = value.c_str();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Password must be atleast 8 characters");
@ -165,7 +165,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.info.total_capacity_Wh = value.toInt();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -179,7 +179,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.soc_scaling_active = value.toInt();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -193,7 +193,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_percentage = static_cast<uint16_t>(value.toFloat() * 100);
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -237,7 +237,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.min_percentage = static_cast<uint16_t>(value.toFloat() * 100);
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -251,7 +251,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_user_set_charge_dA = static_cast<uint16_t>(value.toFloat() * 10);
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -265,7 +265,49 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_user_set_discharge_dA = static_cast<uint16_t>(value.toFloat() * 10);
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing BATTERY_USE_VOLTAGE_LIMITS
server.on("/updateUseVoltageLimit", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.user_set_voltage_limits_active = value.toInt();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing MaxChargeVoltage
server.on("/updateMaxChargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_user_set_charge_voltage_dV = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing MaxDischargeVoltage
server.on("/updateMaxDischargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_user_set_discharge_voltage_dV = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -691,7 +733,7 @@ String processor(const String& var) {
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
#ifdef CONTACTOR_CONTROL
content += "<h4>Contactors controlled by Battery-Emulator: ";
content += "<h4>Contactors controlled by emulator, state: ";
if (datalayer.system.status.contactors_engaged) {
content += "<span style='color: green;'>ON</span>";
} else {
@ -699,13 +741,21 @@ String processor(const String& var) {
}
content += "</h4>";
content += "<h4>Pre Charge: ";
if (digitalRead(PRECHARGE_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
content += "<h4>Precharge: (";
content += PRECHARGE_TIME_MS;
content += " ms) Cont. Neg.: ";
#ifdef PWM_CONTACTOR_CONTROL
if (datalayer.system.status.contactors_engaged) {
content += "<span style='color: green;'>Economized</span>";
content += " Cont. Pos.: ";
content += "<span style='color: green;'>Economized</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
content += " Cont. Pos.: ";
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Cont. Neg.: ";
#else // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
@ -718,6 +768,7 @@ String processor(const String& var) {
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
#endif //no PWM_CONTACTOR_CONTROL
content += "</h4>";
#endif
@ -822,7 +873,7 @@ String processor(const String& var) {
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
#ifdef CONTACTOR_CONTROL
content += "<h4>Contactors controlled by Battery-Emulator: ";
content += "<h4>Contactors controlled by emulator, state: ";
if (datalayer.system.status.contactors_battery2_engaged) {
content += "<span style='color: green;'>ON</span>";
} else {
@ -830,13 +881,19 @@ String processor(const String& var) {
}
content += "</h4>";
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
content += "<h4>Pre Charge: ";
if (digitalRead(SECOND_PRECHARGE_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
content += "<h4>Cont. Neg.: ";
#ifdef PWM_CONTACTOR_CONTROL
if (datalayer.system.status.contactors_battery2_engaged) {
content += "<span style='color: green;'>Economized</span>";
content += " Cont. Pos.: ";
content += "<span style='color: green;'>Economized</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
content += " Cont. Pos.: ";
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Cont. Neg.: ";
#else // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
if (digitalRead(SECOND_NEGATIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
@ -849,6 +906,7 @@ String processor(const String& var) {
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
#endif //no PWM_CONTACTOR_CONTROL
content += "</h4>";
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
#endif // CONTACTOR_CONTROL

View file

@ -6,7 +6,7 @@
#include "../../include.h"
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h"
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h"
#include "../../lib/me-no-dev-AsyncTCP/src/AsyncTCP.h"
#include "../../lib/mathieucarbou-AsyncTCP/src/AsyncTCP.h"
#include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
@ -104,7 +104,7 @@ void onOTAEnd(bool success);
template <typename T>
String formatPowerValue(String label, T value, String unit, int precision, String color = "white");
extern void storeSettings();
extern void store_settings();
void ota_monitor();

View file

@ -22,15 +22,15 @@
#error You must select a HW to run on!
#endif
#if defined(DUAL_CAN) && defined(CAN_FD)
#if defined(CAN_ADDON) && defined(CANFD_ADDON)
// Check that user did not try to use dual can and fd-can on same hardware pins
#error CAN-FD AND DUAL-CAN CANNOT BE USED SIMULTANEOUSLY
#error CAN_ADDON AND CANFD_ADDON CANNOT BE USED SIMULTANEOUSLY
#endif
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
#if !defined(CAN_FD)
#if !defined(CANFD_ADDON)
// Check that user did not try to use classic CAN over FD, without FD component
#error PLEASE ENABLE CAN_FD TO USE CLASSIC CAN OVER CANFD INTERFACE
#error PLEASE ENABLE CANFD_ADDON TO USE CLASSIC CAN OVER CANFD INTERFACE
#endif
#endif

View file

@ -8,6 +8,8 @@ static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Me
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
#define VOLTAGE_OFFSET_DV 20
CAN_frame BYD_250 = {.FD = false,
.ext_ID = false,
.DLC = 8,
@ -98,12 +100,22 @@ void update_values_can_inverter() { //This function maps all the values fetched
}
//Map values to CAN messages
//Maxvoltage (eg 400.0V = 4000 , 16bits long)
BYD_110.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
BYD_110.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
//Minvoltage (eg 300.0V = 3000 , 16bits long)
BYD_110.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
BYD_110.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
if (datalayer.battery.settings.user_set_voltage_limits_active) { //If user is requesting a specific voltage
//Target charge voltage (eg 400.0V = 4000 , 16bits long)
BYD_110.data.u8[0] = (datalayer.battery.settings.max_user_set_charge_voltage_dV >> 8);
BYD_110.data.u8[1] = (datalayer.battery.settings.max_user_set_charge_voltage_dV & 0x00FF);
//Target discharge voltage (eg 300.0V = 3000 , 16bits long)
BYD_110.data.u8[2] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV >> 8);
BYD_110.data.u8[3] = (datalayer.battery.settings.max_user_set_discharge_voltage_dV & 0x00FF);
} else { //Use the voltage based on battery reported design voltage +- offset to avoid triggering events
//Target charge voltage (eg 400.0V = 4000 , 16bits long)
BYD_110.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) >> 8);
BYD_110.data.u8[1] = ((datalayer.battery.info.max_design_voltage_dV - VOLTAGE_OFFSET_DV) & 0x00FF);
//Target discharge voltage (eg 300.0V = 3000 , 16bits long)
BYD_110.data.u8[2] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) >> 8);
BYD_110.data.u8[3] = ((datalayer.battery.info.min_design_voltage_dV + VOLTAGE_OFFSET_DV) & 0x00FF);
}
//Maximum discharge power allowed (Unit: A+1)
BYD_110.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
BYD_110.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);

View file

@ -64,6 +64,31 @@ CAN_frame SOLAX_1879 = {.FD = false,
.DLC = 8,
.ID = 0x1879,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187E = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187E,
.data = {0x0, 0x2D, 0x0, 0x0, 0x0, 0x5F, 0x0, 0x0}};
CAN_frame SOLAX_187D = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187D,
.data = {0x8B, 0x01, 0x0, 0x0, 0x8B, 0x1, 0x0, 0x0}};
CAN_frame SOLAX_187C = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187C,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187B = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187B,
.data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_187A = {.FD = false, //Needed for Ultra
.ext_ID = true,
.DLC = 8,
.ID = 0x187A,
.data = {0x01, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
CAN_frame SOLAX_1881 = {.FD = false,
.ext_ID = true,
.DLC = 8,
@ -256,5 +281,12 @@ void setup_inverter(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.inverter_protocol, "SolaX Triple Power LFP over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
// Sending these messages once towards the inverter makes SOC% work on the Ultra variant
transmit_can(&SOLAX_187E, can_config.inverter);
transmit_can(&SOLAX_187D, can_config.inverter);
transmit_can(&SOLAX_187C, can_config.inverter);
transmit_can(&SOLAX_187B, can_config.inverter);
transmit_can(&SOLAX_187A, can_config.inverter);
}
#endif

View file

@ -64,7 +64,7 @@ _____ _ _ ___ _____ _
#include "Update.h"
#include "StreamString.h"
#if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
#include "../../me-no-dev-AsyncTCP/src/AsyncTCP.h"
#include "../../mathieucarbou-AsyncTCP/src/AsyncTCP.h"
#include "../../me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#define ELEGANTOTA_WEBSERVER AsyncWebServer
#else

View file

@ -0,0 +1,129 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
https://sidweb.nl/cms3/en/contact.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View file

@ -0,0 +1,62 @@
# AsyncTCP
[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/)
[![Continuous Integration](https://github.com/mathieucarbou/AsyncTCP/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/AsyncTCP/actions/workflows/ci.yml)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/AsyncTCP.svg)](https://registry.platformio.org/libraries/mathieucarbou/AsyncTCP)
A fork of the [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) library by [@me-no-dev](https://github.com/me-no-dev).
### Async TCP Library for ESP32 Arduino
This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs.
This library is the base for [ESPAsyncWebServer](https://github.com/mathieucarbou/ESPAsyncWebServer)
## AsyncClient and AsyncServer
The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use.
## Changes in this fork
- Based on [ESPHome fork](https://github.com/esphome/AsyncTCP)
- `library.properties` for Arduino IDE users
- Add `CONFIG_ASYNC_TCP_MAX_ACK_TIME`
- Add `CONFIG_ASYNC_TCP_PRIORITY`
- Add `CONFIG_ASYNC_TCP_QUEUE_SIZE`
- Add `setKeepAlive()`
- Arduino 3 / ESP-IDF 5 compatibility
- Better CI
- Better example
- Customizable macros
- Fix for "Required to lock TCPIP core functionality". Ref: https://github.com/mathieucarbou/AsyncTCP/issues/27 and https://github.com/espressif/arduino-esp32/issues/10526
- Fix for "ack timeout 4" client disconnects.
- Fix from https://github.com/me-no-dev/AsyncTCP/pull/173 (partially applied)
- Fix from https://github.com/me-no-dev/AsyncTCP/pull/184
- IPv6
- LIBRETINY support
- LibreTuya
- Reduce logging of non critical messages
- Use IPADDR6_INIT() macro to set connecting IPv6 address
- xTaskCreateUniversal function
## Coordinates
```
mathieucarbou/AsyncTCP @ ^3.3.1
```
## Important recommendations
Most of the crashes are caused by improper configuration of the library for the project.
Here are some recommendations to avoid them.
I personally use the following configuration in my projects:
```c++
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 // (keep default)
-D CONFIG_ASYNC_TCP_PRIORITY=10 // (keep default)
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 // (keep default)
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1 // force async_tcp task to be on same core as the app (default is core 0)
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096 // reduce the stack size (default is 16K)
```

View file

@ -0,0 +1,38 @@
{
"name": "AsyncTCP",
"version": "3.3.1",
"description": "Asynchronous TCP Library for ESP32",
"keywords": "async,tcp",
"repository": {
"type": "git",
"url": "https://github.com/mathieucarbou/AsyncTCP.git"
},
"authors": [
{
"name": "Hristo Gochkov"
},
{
"name": "Mathieu Carbou",
"maintainer": true
}
],
"license": "LGPL-3.0",
"frameworks": "arduino",
"platforms": [
"espressif32",
"libretiny"
],
"build": {
"libCompatMode": 2
},
"export": {
"include": [
"examples",
"src",
"library.json",
"library.properties",
"LICENSE",
"README.md"
]
}
}

View file

@ -0,0 +1,10 @@
name=Async TCP
includes=AsyncTCP.h
version=3.3.1
author=Me-No-Dev
maintainer=Mathieu Carbou <mathieu.carbou@gmail.com>
sentence=Async TCP Library for ESP32
paragraph=Async TCP Library for ESP32
category=Other
url=https://github.com/mathieucarbou/AsyncTCP.git
architectures=*

View file

@ -0,0 +1,43 @@
[platformio]
default_envs = arduino-2, arduino-3, arduino-310
lib_dir = .
src_dir = examples/Client
[env]
framework = arduino
build_flags =
-Wall -Wextra
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000
-D CONFIG_ASYNC_TCP_PRIORITY=10
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=64
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096
-D CONFIG_ARDUHAL_LOG_COLORS
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
upload_protocol = esptool
monitor_speed = 115200
monitor_filters = esp32_exception_decoder, log2file
board = esp32dev
[env:arduino-2]
platform = espressif32@6.9.0
[env:arduino-3]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
[env:arduino-310]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc3/platform-espressif32.zip
; CI
[env:ci-arduino-2]
platform = espressif32@6.9.0
board = ${sysenv.PIO_BOARD}
[env:ci-arduino-3]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
board = ${sysenv.PIO_BOARD}
[env:ci-arduino-310]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc3/platform-espressif32.zip
board = ${sysenv.PIO_BOARD}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,347 @@
/*
Asynchronous TCP library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCTCP_H_
#define ASYNCTCP_H_
#define ASYNCTCP_VERSION "3.3.1"
#define ASYNCTCP_VERSION_MAJOR 3
#define ASYNCTCP_VERSION_MINOR 3
#define ASYNCTCP_VERSION_REVISION 1
#define ASYNCTCP_FORK_mathieucarbou
#include "../../../devboard/hal/hal.h"
#include "../../../system_settings.h"
#include "IPAddress.h"
#if ESP_IDF_VERSION_MAJOR < 5
#include "IPv6Address.h"
#endif
#include "lwip/ip6_addr.h"
#include "lwip/ip_addr.h"
#include <functional>
#ifndef LIBRETINY
#include "sdkconfig.h"
extern "C" {
#include "freertos/semphr.h"
#include "lwip/pbuf.h"
}
#else
extern "C" {
#include <lwip/pbuf.h>
#include <semphr.h>
}
#define CONFIG_ASYNC_TCP_RUNNING_CORE WIFI_CORE
#endif
// If core is not defined, then we are running in Arduino or PIO
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
#define CONFIG_ASYNC_TCP_RUNNING_CORE WIFI_CORE
#endif
// guard AsyncTCP task with watchdog
#ifndef CONFIG_ASYNC_TCP_USE_WDT
#define CONFIG_ASYNC_TCP_USE_WDT 0
#endif
#ifndef CONFIG_ASYNC_TCP_STACK_SIZE
#define CONFIG_ASYNC_TCP_STACK_SIZE 8192 * 2
#endif
#ifndef CONFIG_ASYNC_TCP_PRIORITY
#define CONFIG_ASYNC_TCP_PRIORITY 10
#endif
#ifndef CONFIG_ASYNC_TCP_QUEUE_SIZE
#define CONFIG_ASYNC_TCP_QUEUE_SIZE 64
#endif
#ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME
#define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000
#endif
class AsyncClient;
#define ASYNC_WRITE_FLAG_COPY 0x01 // will allocate new buffer to hold the data while sending (else will hold reference to the data given)
#define ASYNC_WRITE_FLAG_MORE 0x02 // will not send PSH flag, meaning that there should be more data to be sent before the application should react.
typedef std::function<void(void*, AsyncClient*)> AcConnectHandler;
typedef std::function<void(void*, AsyncClient*, size_t len, uint32_t time)> AcAckHandler;
typedef std::function<void(void*, AsyncClient*, int8_t error)> AcErrorHandler;
typedef std::function<void(void*, AsyncClient*, void* data, size_t len)> AcDataHandler;
typedef std::function<void(void*, AsyncClient*, struct pbuf* pb)> AcPacketHandler;
typedef std::function<void(void*, AsyncClient*, uint32_t time)> AcTimeoutHandler;
struct tcp_pcb;
struct ip_addr;
class AsyncClient {
public:
AsyncClient(tcp_pcb* pcb = 0);
~AsyncClient();
AsyncClient& operator=(const AsyncClient& other);
AsyncClient& operator+=(const AsyncClient& other);
bool operator==(const AsyncClient& other);
bool operator!=(const AsyncClient& other) {
return !(*this == other);
}
bool connect(const IPAddress& ip, uint16_t port);
#if ESP_IDF_VERSION_MAJOR < 5
bool connect(const IPv6Address& ip, uint16_t port);
#endif
bool connect(const char* host, uint16_t port);
/**
* @brief close connection
*
* @param now - ignored
*/
void close(bool now = false);
// same as close()
void stop() { close(false); };
int8_t abort();
bool free();
// ack is not pending
bool canSend();
// TCP buffer space available
size_t space();
/**
* @brief add data to be send (but do not send yet)
* @note add() would call lwip's tcp_write()
By default apiflags=ASYNC_WRITE_FLAG_COPY
You could try to use apiflags with this flag unset to pass data by reference and avoid copy to socket buffer,
but looks like it does not work for Arduino's lwip in ESP32/IDF at least
it is enforced in https://github.com/espressif/esp-lwip/blob/0606eed9d8b98a797514fdf6eabb4daf1c8c8cd9/src/core/tcp_out.c#L422C5-L422C30
if LWIP_NETIF_TX_SINGLE_PBUF is set, and it is set indeed in IDF
https://github.com/espressif/esp-idf/blob/a0f798cfc4bbd624aab52b2c194d219e242d80c1/components/lwip/port/include/lwipopts.h#L744
*
* @param data
* @param size
* @param apiflags
* @return size_t amount of data that has been copied
*/
size_t add(const char* data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY);
/**
* @brief send data previously add()'ed
*
* @return true on success
* @return false on error
*/
bool send();
/**
* @brief add and enqueue data for sending
* @note it is same as add() + send()
* @note only make sense when canSend() == true
*
* @param data
* @param size
* @param apiflags
* @return size_t
*/
size_t write(const char* data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY);
/**
* @brief add and enque data for sending
* @note treats data as null-terminated string
*
* @param data
* @return size_t
*/
size_t write(const char* data) { return data == NULL ? 0 : write(data, strlen(data)); };
uint8_t state();
bool connecting();
bool connected();
bool disconnecting();
bool disconnected();
// disconnected or disconnecting
bool freeable();
uint16_t getMss();
uint32_t getRxTimeout();
// no RX data timeout for the connection in seconds
void setRxTimeout(uint32_t timeout);
uint32_t getAckTimeout();
// no ACK timeout for the last sent packet in milliseconds
void setAckTimeout(uint32_t timeout);
void setNoDelay(bool nodelay);
bool getNoDelay();
void setKeepAlive(uint32_t ms, uint8_t cnt);
uint32_t getRemoteAddress();
uint16_t getRemotePort();
uint32_t getLocalAddress();
uint16_t getLocalPort();
#if LWIP_IPV6
ip6_addr_t getRemoteAddress6();
ip6_addr_t getLocalAddress6();
#if ESP_IDF_VERSION_MAJOR < 5
IPv6Address remoteIP6();
IPv6Address localIP6();
#else
IPAddress remoteIP6();
IPAddress localIP6();
#endif
#endif
// compatibility
IPAddress remoteIP();
uint16_t remotePort();
IPAddress localIP();
uint16_t localPort();
// set callback - on successful connect
void onConnect(AcConnectHandler cb, void* arg = 0);
// set callback - disconnected
void onDisconnect(AcConnectHandler cb, void* arg = 0);
// set callback - ack received
void onAck(AcAckHandler cb, void* arg = 0);
// set callback - unsuccessful connect or error
void onError(AcErrorHandler cb, void* arg = 0);
// set callback - data received (called if onPacket is not used)
void onData(AcDataHandler cb, void* arg = 0);
// set callback - data received
void onPacket(AcPacketHandler cb, void* arg = 0);
// set callback - ack timeout
void onTimeout(AcTimeoutHandler cb, void* arg = 0);
// set callback - every 125ms when connected
void onPoll(AcConnectHandler cb, void* arg = 0);
// ack pbuf from onPacket
void ackPacket(struct pbuf* pb);
// ack data that you have not acked using the method below
size_t ack(size_t len);
// will not ack the current packet. Call from onData
void ackLater() { _ack_pcb = false; }
static const char* errorToString(int8_t error);
const char* stateToString();
// internal callbacks - Do NOT call any of the functions below in user code!
static int8_t _s_poll(void* arg, struct tcp_pcb* tpcb);
static int8_t _s_recv(void* arg, struct tcp_pcb* tpcb, struct pbuf* pb, int8_t err);
static int8_t _s_fin(void* arg, struct tcp_pcb* tpcb, int8_t err);
static int8_t _s_lwip_fin(void* arg, struct tcp_pcb* tpcb, int8_t err);
static void _s_error(void* arg, int8_t err);
static int8_t _s_sent(void* arg, struct tcp_pcb* tpcb, uint16_t len);
static int8_t _s_connected(void* arg, struct tcp_pcb* tpcb, int8_t err);
static void _s_dns_found(const char* name, struct ip_addr* ipaddr, void* arg);
int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err);
tcp_pcb* pcb() { return _pcb; }
protected:
bool _connect(ip_addr_t addr, uint16_t port);
tcp_pcb* _pcb;
int8_t _closed_slot;
AcConnectHandler _connect_cb;
void* _connect_cb_arg;
AcConnectHandler _discard_cb;
void* _discard_cb_arg;
AcAckHandler _sent_cb;
void* _sent_cb_arg;
AcErrorHandler _error_cb;
void* _error_cb_arg;
AcDataHandler _recv_cb;
void* _recv_cb_arg;
AcPacketHandler _pb_cb;
void* _pb_cb_arg;
AcTimeoutHandler _timeout_cb;
void* _timeout_cb_arg;
AcConnectHandler _poll_cb;
void* _poll_cb_arg;
bool _ack_pcb;
uint32_t _tx_last_packet;
uint32_t _rx_ack_len;
uint32_t _rx_last_packet;
uint32_t _rx_timeout;
uint32_t _rx_last_ack;
uint32_t _ack_timeout;
uint16_t _connect_port;
int8_t _close();
void _free_closed_slot();
bool _allocate_closed_slot();
int8_t _connected(tcp_pcb* pcb, int8_t err);
void _error(int8_t err);
int8_t _poll(tcp_pcb* pcb);
int8_t _sent(tcp_pcb* pcb, uint16_t len);
int8_t _fin(tcp_pcb* pcb, int8_t err);
int8_t _lwip_fin(tcp_pcb* pcb, int8_t err);
void _dns_found(struct ip_addr* ipaddr);
public:
AsyncClient* prev;
AsyncClient* next;
};
class AsyncServer {
public:
AsyncServer(IPAddress addr, uint16_t port);
#if ESP_IDF_VERSION_MAJOR < 5
AsyncServer(IPv6Address addr, uint16_t port);
#endif
AsyncServer(uint16_t port);
~AsyncServer();
void onClient(AcConnectHandler cb, void* arg);
void begin();
void end();
void setNoDelay(bool nodelay);
bool getNoDelay();
uint8_t status();
// Do not use any of the functions below!
static int8_t _s_accept(void* arg, tcp_pcb* newpcb, int8_t err);
static int8_t _s_accepted(void* arg, AsyncClient* client);
protected:
uint16_t _port;
bool _bind4 = false;
bool _bind6 = false;
IPAddress _addr;
#if ESP_IDF_VERSION_MAJOR < 5
IPv6Address _addr6;
#endif
bool _noDelay;
tcp_pcb* _pcb;
AcConnectHandler _connect_cb;
void* _connect_cb_arg;
int8_t _accept(tcp_pcb* newpcb, int8_t err);
int8_t _accepted(AsyncClient* client);
};
#endif /* ASYNCTCP_H_ */

View file

@ -1,2 +0,0 @@
.DS_Store

View file

@ -1,34 +0,0 @@
sudo: false
language: python
os:
- linux
git:
depth: false
stages:
- build
jobs:
include:
- name: "Arduino Build"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh
- name: "PlatformIO Build"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh 1 1
notifications:
email:
on_success: change
on_failure: change
webhooks:
urls:
- https://webhooks.gitter.im/e/60e65d0c78ea0a920347
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: false # default: false

View file

@ -1,30 +0,0 @@
menu "AsyncTCP Configuration"
choice ASYNC_TCP_RUNNING_CORE
bool "Core on which AsyncTCP's thread is running"
default ASYNC_TCP_RUN_CORE1
help
Select on which core AsyncTCP is running
config ASYNC_TCP_RUN_CORE0
bool "CORE 0"
config ASYNC_TCP_RUN_CORE1
bool "CORE 1"
config ASYNC_TCP_RUN_NO_AFFINITY
bool "BOTH"
endchoice
config ASYNC_TCP_RUNNING_CORE
int
default 0 if ASYNC_TCP_RUN_CORE0
default 1 if ASYNC_TCP_RUN_CORE1
default -1 if ASYNC_TCP_RUN_NO_AFFINITY
config ASYNC_TCP_USE_WDT
bool "Enable WDT for the AsyncTCP task"
default "y"
help
Enable WDT for the AsyncTCP task, so it will trigger if a handler is locking the thread.
endmenu

View file

@ -1,15 +0,0 @@
This is commit ca8ac5f from https://github.com/me-no-dev/AsyncTCP
# AsyncTCP
[![Build Status](https://travis-ci.org/me-no-dev/AsyncTCP.svg?branch=master)](https://travis-ci.org/me-no-dev/AsyncTCP) ![](https://github.com/me-no-dev/AsyncTCP/workflows/Async%20TCP%20CI/badge.svg) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/2f7e4d1df8b446d192cbfec6dc174d2d)](https://www.codacy.com/manual/me-no-dev/AsyncTCP?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=me-no-dev/AsyncTCP&amp;utm_campaign=Badge_Grade)
### Async TCP Library for ESP32 Arduino
[![Join the chat at https://gitter.im/me-no-dev/ESPAsyncWebServer](https://badges.gitter.im/me-no-dev/ESPAsyncWebServer.svg)](https://gitter.im/me-no-dev/ESPAsyncWebServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs.
This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
## AsyncClient and AsyncServer
The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use.

View file

@ -1,22 +0,0 @@
{
"name":"AsyncTCP",
"description":"Asynchronous TCP Library for ESP32",
"keywords":"async,tcp",
"authors":
{
"name": "Hristo Gochkov",
"maintainer": true
},
"repository":
{
"type": "git",
"url": "https://github.com/me-no-dev/AsyncTCP.git"
},
"version": "1.1.1",
"license": "LGPL-3.0",
"frameworks": "arduino",
"platforms": "espressif32",
"build": {
"libCompatMode": 2
}
}

View file

@ -1,9 +0,0 @@
name=AsyncTCP
version=1.1.1
author=Me-No-Dev
maintainer=Me-No-Dev
sentence=Async TCP Library for ESP32
paragraph=Async TCP Library for ESP32
category=Other
url=https://github.com/me-no-dev/AsyncTCP
architectures=*

File diff suppressed because it is too large Load diff

View file

@ -1,220 +0,0 @@
/*
Asynchronous TCP library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCTCP_H_
#define ASYNCTCP_H_
#include "IPAddress.h"
#include "sdkconfig.h"
#include <functional>
extern "C" {
#include "freertos/semphr.h"
#include "lwip/pbuf.h"
}
#include "../../../system_settings.h"
#include "../../../devboard/hal/hal.h"
//If core is not defined, then we are running in Arduino or PIO
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
#define CONFIG_ASYNC_TCP_RUNNING_CORE WIFI_CORE //any available core
#define CONFIG_ASYNC_TCP_USE_WDT 0 //if enabled, adds between 33us and 200us per event
#endif
class AsyncClient;
#define ASYNC_MAX_ACK_TIME 5000
#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given)
#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react.
typedef std::function<void(void*, AsyncClient*)> AcConnectHandler;
typedef std::function<void(void*, AsyncClient*, size_t len, uint32_t time)> AcAckHandler;
typedef std::function<void(void*, AsyncClient*, int8_t error)> AcErrorHandler;
typedef std::function<void(void*, AsyncClient*, void *data, size_t len)> AcDataHandler;
typedef std::function<void(void*, AsyncClient*, struct pbuf *pb)> AcPacketHandler;
typedef std::function<void(void*, AsyncClient*, uint32_t time)> AcTimeoutHandler;
struct tcp_pcb;
struct ip_addr;
class AsyncClient {
public:
AsyncClient(tcp_pcb* pcb = 0);
~AsyncClient();
AsyncClient & operator=(const AsyncClient &other);
AsyncClient & operator+=(const AsyncClient &other);
bool operator==(const AsyncClient &other);
bool operator!=(const AsyncClient &other) {
return !(*this == other);
}
bool connect(IPAddress ip, uint16_t port);
bool connect(const char* host, uint16_t port);
void close(bool now = false);
void stop();
int8_t abort();
bool free();
bool canSend();//ack is not pending
size_t space();//space available in the TCP window
size_t add(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY);//add for sending
bool send();//send all data added with the method above
//write equals add()+send()
size_t write(const char* data);
size_t write(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY); //only when canSend() == true
uint8_t state();
bool connecting();
bool connected();
bool disconnecting();
bool disconnected();
bool freeable();//disconnected or disconnecting
uint16_t getMss();
uint32_t getRxTimeout();
void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds
uint32_t getAckTimeout();
void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds
void setNoDelay(bool nodelay);
bool getNoDelay();
uint32_t getRemoteAddress();
uint16_t getRemotePort();
uint32_t getLocalAddress();
uint16_t getLocalPort();
//compatibility
IPAddress remoteIP();
uint16_t remotePort();
IPAddress localIP();
uint16_t localPort();
void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect
void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected
void onAck(AcAckHandler cb, void* arg = 0); //ack received
void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error
void onData(AcDataHandler cb, void* arg = 0); //data received (called if onPacket is not used)
void onPacket(AcPacketHandler cb, void* arg = 0); //data received
void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout
void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected
void ackPacket(struct pbuf * pb);//ack pbuf from onPacket
size_t ack(size_t len); //ack data that you have not acked using the method below
void ackLater(){ _ack_pcb = false; } //will not ack the current packet. Call from onData
const char * errorToString(int8_t error);
const char * stateToString();
//Do not use any of the functions below!
static int8_t _s_poll(void *arg, struct tcp_pcb *tpcb);
static int8_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, int8_t err);
static int8_t _s_fin(void *arg, struct tcp_pcb *tpcb, int8_t err);
static int8_t _s_lwip_fin(void *arg, struct tcp_pcb *tpcb, int8_t err);
static void _s_error(void *arg, int8_t err);
static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len);
static int8_t _s_connected(void* arg, void* tpcb, int8_t err);
static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg);
int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err);
tcp_pcb * pcb(){ return _pcb; }
protected:
tcp_pcb* _pcb;
int8_t _closed_slot;
AcConnectHandler _connect_cb;
void* _connect_cb_arg;
AcConnectHandler _discard_cb;
void* _discard_cb_arg;
AcAckHandler _sent_cb;
void* _sent_cb_arg;
AcErrorHandler _error_cb;
void* _error_cb_arg;
AcDataHandler _recv_cb;
void* _recv_cb_arg;
AcPacketHandler _pb_cb;
void* _pb_cb_arg;
AcTimeoutHandler _timeout_cb;
void* _timeout_cb_arg;
AcConnectHandler _poll_cb;
void* _poll_cb_arg;
bool _pcb_busy;
uint32_t _pcb_sent_at;
bool _ack_pcb;
uint32_t _rx_ack_len;
uint32_t _rx_last_packet;
uint32_t _rx_since_timeout;
uint32_t _ack_timeout;
uint16_t _connect_port;
int8_t _close();
void _free_closed_slot();
void _allocate_closed_slot();
int8_t _connected(void* pcb, int8_t err);
void _error(int8_t err);
int8_t _poll(tcp_pcb* pcb);
int8_t _sent(tcp_pcb* pcb, uint16_t len);
int8_t _fin(tcp_pcb* pcb, int8_t err);
int8_t _lwip_fin(tcp_pcb* pcb, int8_t err);
void _dns_found(struct ip_addr *ipaddr);
public:
AsyncClient* prev;
AsyncClient* next;
};
class AsyncServer {
public:
AsyncServer(IPAddress addr, uint16_t port);
AsyncServer(uint16_t port);
~AsyncServer();
void onClient(AcConnectHandler cb, void* arg);
void begin();
void end();
void setNoDelay(bool nodelay);
bool getNoDelay();
uint8_t status();
//Do not use any of the functions below!
static int8_t _s_accept(void *arg, tcp_pcb* newpcb, int8_t err);
static int8_t _s_accepted(void *arg, AsyncClient* client);
protected:
uint16_t _port;
IPAddress _addr;
bool _noDelay;
tcp_pcb* _pcb;
AcConnectHandler _connect_cb;
void* _connect_cb_arg;
int8_t _accept(tcp_pcb* newpcb, int8_t err);
int8_t _accepted(AsyncClient* client);
};
#endif /* ASYNCTCP_H_ */

View file

@ -22,7 +22,7 @@
#include <Arduino.h>
#ifdef ESP32
#include "../../me-no-dev-AsyncTCP/src/AsyncTCP.h"
#include "../../mathieucarbou-AsyncTCP/src/AsyncTCP.h"
#define SSE_MAX_QUEUED_MESSAGES 32
#else
#include <ESPAsyncTCP.h>

View file

@ -23,7 +23,7 @@
#include <Arduino.h>
#ifdef ESP32
#include "../../me-no-dev-AsyncTCP/src/AsyncTCP.h"
#include "../../mathieucarbou-AsyncTCP/src/AsyncTCP.h"
#define WS_MAX_QUEUED_MESSAGES 32
#else
#include <ESPAsyncTCP.h>

View file

@ -30,10 +30,10 @@
#ifdef ESP32
#include <WiFi.h>
#include "../../me-no-dev-AsyncTCP/src/AsyncTCP.h"
#include "../../mathieucarbou-AsyncTCP/src/AsyncTCP.h"
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include "../../me-no-dev-AsyncTCP/src/AsyncTCP.h"
#include "../../mathieucarbou-AsyncTCP/src/AsyncTCP.h"
#else
#error Platform not supported
#endif