Merge branch 'main' into feature/CMFA-EV

This commit is contained in:
Daniel Öster 2025-03-29 19:59:41 +02:00
commit a37b621e99
31 changed files with 1364 additions and 4751 deletions

View file

@ -1,122 +0,0 @@
# 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
- BMW_PHEV_BATTERY
- BYD_ATTO_3_BATTERY
- CELLPOWER_BMS
- CHADEMO_BATTERY
- CMFA_EV_BATTERY
- FOXESS_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
- FOXESS_CAN
- GROWATT_HV_CAN
- GROWATT_LV_CAN
- PYLON_CAN
- PYLON_LV_CAN
- SCHNEIDER_CAN
- SMA_BYD_H_CAN
- SMA_BYD_HVS_CAN
- SMA_LV_CAN
- SMA_TRIPOWER_CAN
- SOFAR_CAN
- SOLAX_CAN
# These are the supported hardware platforms for which the code will be compiled.
hardware:
- HW_LILYGO
# 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
# Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
- name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h
# 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 -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software

View file

@ -1,121 +0,0 @@
# 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:
- NISSAN_LEAF_BATTERY
- ORION_BMS
- 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
- STELLANTIS_ECMP_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_MODBUS
- FOXESS_CAN
- GROWATT_LV_CAN
- PYLON_CAN
- PYLON_LV_CAN
- SCHNEIDER_CAN
- SMA_BYD_H_CAN
- SMA_BYD_HVS_CAN
- SMA_LV_CAN
- SMA_TRIPOWER_CAN
- SOFAR_CAN
- SOLAX_CAN
# These are the supported hardware platforms for which the code will be compiled.
hardware:
- HW_LILYGO
# 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
# Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
- name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h
# 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 -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software

View file

@ -0,0 +1,291 @@
# 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: [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-batteries-A-to-J: # we split this matrix into multiple parts, to prevent en error that is triggered when the matrix expansion exeeds 255
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
- BMW_PHEV_BATTERY
- BYD_ATTO_3_BATTERY
- CELLPOWER_BMS
- CHADEMO_BATTERY
- FOXESS_BATTERY
- IMIEV_CZERO_ION_BATTERY
- JAGUAR_IPACE_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
- FERROAMP_CAN
- FOXESS_CAN
- GROWATT_HV_CAN
- GROWATT_LV_CAN
- PYLON_CAN
- PYLON_LV_CAN
- SCHNEIDER_CAN
- SMA_BYD_H_CAN
- SMA_BYD_HVS_CAN
- SMA_LV_CAN
- SMA_TRIPOWER_CAN
- SOFAR_CAN
- SOLAX_CAN
- SUNGROW_CAN
# These are the supported hardware platforms for which the code will be compiled.
hardware:
- HW_LILYGO
# 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
# Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
- name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h
# 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 -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software
# This is the name of the job.
build-matrix-batteries-K-to-P:
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:
- KIA_E_GMP_BATTERY
- KIA_HYUNDAI_64_BATTERY
- KIA_HYUNDAI_HYBRID_BATTERY
- MEB_BATTERY
- MG_5_BATTERY
- NISSAN_LEAF_BATTERY
- ORION_BMS
- PYLON_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
- FERROAMP_CAN
- FOXESS_CAN
- GROWATT_HV_CAN
- GROWATT_LV_CAN
- PYLON_CAN
- PYLON_LV_CAN
- SCHNEIDER_CAN
- SMA_BYD_H_CAN
- SMA_BYD_HVS_CAN
- SMA_LV_CAN
- SMA_TRIPOWER_CAN
- SOFAR_CAN
- SOLAX_CAN
- SUNGROW_CAN
# These are the supported hardware platforms for which the code will be compiled.
hardware:
- HW_LILYGO
# 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
# Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
- name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h
# 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 -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software
# This is the name of the job.
build-matrix-batteries-R-to-Z:
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:
- RANGE_ROVER_PHEV_BATTERY
- RENAULT_KANGOO_BATTERY
- RENAULT_TWIZY_BATTERY
- RENAULT_ZOE_GEN1_BATTERY
- RENAULT_ZOE_GEN2_BATTERY
- RJXZS_BMS
- SANTA_FE_PHEV_BATTERY
- STELLANTIS_ECMP_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_MODBUS
- FERROAMP_CAN
- FOXESS_CAN
- GROWATT_HV_CAN
- GROWATT_LV_CAN
- PYLON_CAN
- PYLON_LV_CAN
- SCHNEIDER_CAN
- SMA_BYD_H_CAN
- SMA_BYD_HVS_CAN
- SMA_LV_CAN
- SMA_TRIPOWER_CAN
- SOFAR_CAN
- SOLAX_CAN
- SUNGROW_CAN
# These are the supported hardware platforms for which the code will be compiled.
hardware:
- HW_LILYGO
# 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
# Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
- name: Copy USER_SECRETS.TEMPLATE.h to USER_SECRETS.h
run: cp ./Software/USER_SECRETS.TEMPLATE.h ./Software/USER_SECRETS.h
# 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 -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software

View file

@ -49,29 +49,18 @@
volatile unsigned long long bmsResetTimeOffset = 0;
// The current software version, shown on webserver
const char* version_number = "8.9.dev";
const char* version_number = "8.10.dev";
// Interval settings
uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers
// Interval timers
unsigned long previousMillis10ms = 0;
unsigned long previousMillisUpdateVal = 0;
unsigned long lastMillisOverflowCheck = 0;
// Task time measurement for debugging and for setting CPU load events
int64_t core_task_time_us;
MyTimer core_task_timer_10s(INTERVAL_10_S);
int64_t core_task_time_us;
int64_t connectivity_task_time_us;
MyTimer connectivity_task_timer_10s(INTERVAL_10_S);
int64_t logging_task_time_us;
MyTimer logging_task_timer_10s(INTERVAL_10_S);
MyTimer loop_task_timer_10s(INTERVAL_10_S);
MyTimer check_pause_2s(INTERVAL_2_S);
int64_t mqtt_task_time_us;
MyTimer mqtt_task_timer_10s(INTERVAL_10_S);
TaskHandle_t main_loop_task;
TaskHandle_t connectivity_loop_task;
@ -80,8 +69,6 @@ TaskHandle_t mqtt_loop_task;
Logging logging;
#define WDT_TIMEOUT_SECONDS 5 // If code hangs for longer than this, it will be rebooted by the watchdog
// Initialization
void setup() {
init_serial();
@ -135,14 +122,11 @@ void setup() {
// Initialize Task Watchdog for subscribed tasks
esp_task_wdt_config_t wdt_config = {
.timeout_ms = WDT_TIMEOUT_SECONDS * 1000, // Convert seconds to milliseconds
.timeout_ms = INTERVAL_5_S, // If task hangs for longer than this, reboot
.idle_core_mask = (1 << CORE_FUNCTION_CORE) | (1 << WIFI_CORE), // Watch both cores
.trigger_panic = true // Enable panic reset on timeout
};
// Initialize Task Watchdog
esp_task_wdt_init(&wdt_config);
// Start tasks
xTaskCreatePinnedToCore((TaskFunction_t)&core_loop, "core_loop", 4096, &core_task_time_us, TASK_CORE_PRIO,
&main_loop_task, CORE_FUNCTION_CORE);
@ -198,11 +182,6 @@ void connectivity_loop(void* task_time_us) {
#endif
END_TIME_MEASUREMENT_MAX(wifi, datalayer.system.status.wifi_task_10s_max_us);
#ifdef FUNCTION_TIME_MEASUREMENT
if (connectivity_task_timer_10s.elapsed()) {
datalayer.system.status.wifi_task_10s_max_us = 0;
}
#endif
esp_task_wdt_reset(); // Reset watchdog
delay(1);
}
@ -219,12 +198,6 @@ void mqtt_loop(void* task_time_us) {
START_TIME_MEASUREMENT(mqtt);
mqtt_loop();
END_TIME_MEASUREMENT_MAX(mqtt, datalayer.system.status.mqtt_task_10s_max_us);
#ifdef FUNCTION_TIME_MEASUREMENT
if (mqtt_task_timer_10s.elapsed()) {
datalayer.system.status.mqtt_task_10s_max_us = 0;
}
#endif
esp_task_wdt_reset(); // Reset watchdog
delay(1);
}
@ -256,21 +229,22 @@ void core_loop(void* task_time_us) {
END_TIME_MEASUREMENT_MAX(ota, datalayer.system.status.time_ota_us);
#endif // WEBSERVER
START_TIME_MEASUREMENT(time_10ms);
// Process
if (millis() - previousMillis10ms >= INTERVAL_10_MS) {
previousMillis10ms = millis();
START_TIME_MEASUREMENT(time_10ms);
led_exe();
handle_contactors(); // Take care of startup precharge/contactor closing
#ifdef PRECHARGE_CONTROL
handle_precharge_control();
#endif // PRECHARGE_CONTROL
}
END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us);
}
START_TIME_MEASUREMENT(time_values);
if (millis() - previousMillisUpdateVal >= intervalUpdateValues) {
if (millis() - previousMillisUpdateVal >= INTERVAL_1_S) {
previousMillisUpdateVal = millis(); // Order matters on the update_loop!
START_TIME_MEASUREMENT(time_values);
update_pause_state(); // Check if we are OK to send CAN or need to pause
update_values_battery(); // Fetch battery values
#ifdef DOUBLE_BATTERY
update_values_battery2();
@ -279,8 +253,8 @@ void core_loop(void* task_time_us) {
update_calculated_values();
update_machineryprotection(); // Check safeties
update_values_inverter(); // Update values heading towards inverter
}
END_TIME_MEASUREMENT_MAX(time_values, datalayer.system.status.time_values_us);
}
START_TIME_MEASUREMENT(cantx);
// Output
@ -313,13 +287,12 @@ void core_loop(void* task_time_us) {
datalayer.system.status.time_values_us = 0;
datalayer.system.status.time_cantx_us = 0;
datalayer.system.status.core_task_10s_max_us = 0;
datalayer.system.status.wifi_task_10s_max_us = 0;
datalayer.system.status.mqtt_task_10s_max_us = 0;
}
#endif // FUNCTION_TIME_MEASUREMENT
if (check_pause_2s.elapsed()) {
emulator_pause_state_transmit_can_battery();
}
#ifdef DEBUG_LOG
logging.log_bms_status(datalayer.battery.status.real_bms_status, 1);
logging.log_bms_status(datalayer.battery.status.real_bms_status);
#endif
esp_task_wdt_reset(); // Reset watchdog to prevent reset
vTaskDelayUntil(&xLastWakeTime, xFrequency);
@ -462,6 +435,10 @@ void update_calculated_values() {
} else {
datalayer.battery.status.reported_remaining_capacity_Wh = 0;
}
datalayer.battery.info.reported_total_capacity_Wh =
(datalayer.battery.info.total_capacity_Wh *
(datalayer.battery.settings.max_percentage - datalayer.battery.settings.min_percentage)) /
10000;
} else {
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
@ -532,76 +509,55 @@ void update_values_inverter() {
#endif // CAN_INVERTER_SELECTED
}
/** Reset reason numbering and description
*
typedef enum {
ESP_RST_UNKNOWN, //!< 0 Reset reason can not be determined
ESP_RST_POWERON, //!< 1 OK Reset due to power-on event
ESP_RST_EXT, //!< 2 Reset by external pin (not applicable for ESP32)
ESP_RST_SW, //!< 3 OK Software reset via esp_restart
ESP_RST_PANIC, //!< 4 Software reset due to exception/panic
ESP_RST_INT_WDT, //!< 5 Reset (software or hardware) due to interrupt watchdog
ESP_RST_TASK_WDT, //!< 6 Reset due to task watchdog
ESP_RST_WDT, //!< 7 Reset due to other watchdogs
ESP_RST_DEEPSLEEP, //!< 8 Reset after exiting deep sleep mode
ESP_RST_BROWNOUT, //!< 9 Brownout reset (software or hardware)
ESP_RST_SDIO, //!< 10 Reset over SDIO
ESP_RST_USB, //!< 11 Reset by USB peripheral
ESP_RST_JTAG, //!< 12 Reset by JTAG
ESP_RST_EFUSE, //!< 13 Reset due to efuse error
ESP_RST_PWR_GLITCH, //!< 14 Reset due to power glitch detected
ESP_RST_CPU_LOCKUP, //!< 15 Reset due to CPU lock up
} esp_reset_reason_t;
*/
void check_reset_reason() {
esp_reset_reason_t reason = esp_reset_reason();
switch (reason) {
case ESP_RST_UNKNOWN:
case ESP_RST_UNKNOWN: //Reset reason can not be determined
set_event(EVENT_RESET_UNKNOWN, reason);
break;
case ESP_RST_POWERON:
case ESP_RST_POWERON: //OK Reset due to power-on event
set_event(EVENT_RESET_POWERON, reason);
break;
case ESP_RST_EXT:
case ESP_RST_EXT: //Reset by external pin (not applicable for ESP32)
set_event(EVENT_RESET_EXT, reason);
break;
case ESP_RST_SW:
case ESP_RST_SW: //OK Software reset via esp_restart
set_event(EVENT_RESET_SW, reason);
break;
case ESP_RST_PANIC:
case ESP_RST_PANIC: //Software reset due to exception/panic
set_event(EVENT_RESET_PANIC, reason);
break;
case ESP_RST_INT_WDT:
case ESP_RST_INT_WDT: //Reset (software or hardware) due to interrupt watchdog
set_event(EVENT_RESET_INT_WDT, reason);
break;
case ESP_RST_TASK_WDT:
case ESP_RST_TASK_WDT: //Reset due to task watchdog
set_event(EVENT_RESET_TASK_WDT, reason);
break;
case ESP_RST_WDT:
case ESP_RST_WDT: //Reset due to other watchdogs
set_event(EVENT_RESET_WDT, reason);
break;
case ESP_RST_DEEPSLEEP:
case ESP_RST_DEEPSLEEP: //Reset after exiting deep sleep mode
set_event(EVENT_RESET_DEEPSLEEP, reason);
break;
case ESP_RST_BROWNOUT:
case ESP_RST_BROWNOUT: //Brownout reset (software or hardware)
set_event(EVENT_RESET_BROWNOUT, reason);
break;
case ESP_RST_SDIO:
case ESP_RST_SDIO: //Reset over SDIO
set_event(EVENT_RESET_SDIO, reason);
break;
case ESP_RST_USB:
case ESP_RST_USB: //Reset by USB peripheral
set_event(EVENT_RESET_USB, reason);
break;
case ESP_RST_JTAG:
case ESP_RST_JTAG: //Reset by JTAG
set_event(EVENT_RESET_JTAG, reason);
break;
case ESP_RST_EFUSE:
case ESP_RST_EFUSE: //Reset due to efuse error
set_event(EVENT_RESET_EFUSE, reason);
break;
case ESP_RST_PWR_GLITCH:
case ESP_RST_PWR_GLITCH: //Reset due to power glitch detected
set_event(EVENT_RESET_PWR_GLITCH, reason);
break;
case ESP_RST_CPU_LOCKUP:
case ESP_RST_CPU_LOCKUP: //Reset due to CPU lock up
set_event(EVENT_RESET_CPU_LOCKUP, reason);
break;
default:

View file

@ -44,11 +44,12 @@
//#define VOLVO_SPA_BATTERY
//#define VOLVO_SPA_HYBRID_BATTERY
//#define TEST_FAKE_BATTERY
//#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires CAN_ADDON setup)
//#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires separate CAN setup)
/* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */
/* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/Battery-Emulator/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_CAN_DEYE //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus, with Deye specific fixes
//#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 FERROAMP_CAN //Enable this line to emulate a "Pylon 4x96V Force H2" over CAN Bus

View file

@ -32,6 +32,8 @@ static uint16_t inverterVoltage = 0;
static uint16_t soc_calculated = 0;
static uint16_t SOC_BMS = 0;
static uint16_t SOC_Display = 0;
static uint16_t SOC_estimated_lowest = 0;
static uint16_t SOC_estimated_highest = 0;
static uint16_t batterySOH = 1000;
static uint16_t CellVoltMax_mV = 3700;
static uint16_t CellVoltMin_mV = 3700;
@ -61,6 +63,58 @@ static uint8_t ticks_200ms_counter = 0;
static uint8_t EGMP_1CF_counter = 0;
static uint8_t EGMP_3XF_counter = 0;
// Define the data points for %SOC depending on cell voltage
const uint8_t numPoints = 100;
const uint16_t SOC[] = {10000, 9900, 9800, 9700, 9600, 9500, 9400, 9300, 9200, 9100, 9000, 8900, 8800, 8700, 8600,
8500, 8400, 8300, 8200, 8100, 8000, 7900, 7800, 7700, 7600, 7500, 7400, 7300, 7200, 7100,
7000, 6900, 6800, 6700, 6600, 6500, 6400, 6300, 6200, 6100, 6000, 5900, 5800, 5700, 5600,
5500, 5400, 5300, 5200, 5100, 5000, 4900, 4800, 4700, 4600, 4500, 4400, 4300, 4200, 4100,
4000, 3900, 3800, 3700, 3600, 3500, 3400, 3300, 3200, 3100, 3000, 2900, 2800, 2700, 2600,
2500, 2400, 2300, 2200, 2100, 2000, 1900, 1800, 1700, 1600, 1500, 1400, 1300, 1200, 1100,
1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 0};
const uint16_t voltage[] = {4200, 4171, 4143, 4117, 4093, 4070, 4050, 4031, 4013, 3998, 3985, 3973, 3964, 3957, 3952,
3950, 3941, 3933, 3924, 3916, 3907, 3899, 3890, 3881, 3873, 3864, 3856, 3847, 3839, 3830,
3821, 3813, 3804, 3796, 3787, 3779, 3770, 3761, 3753, 3744, 3736, 3727, 3719, 3710, 3701,
3693, 3684, 3676, 3667, 3659, 3650, 3641, 3633, 3624, 3616, 3607, 3599, 3590, 3581, 3573,
3564, 3556, 3547, 3539, 3530, 3521, 3513, 3504, 3496, 3487, 3479, 3470, 3461, 3453, 3444,
3436, 3427, 3419, 3410, 3401, 3393, 3384, 3376, 3367, 3359, 3350, 3333, 3315, 3297, 3278,
3258, 3237, 3215, 3192, 3166, 3139, 3108, 3074, 3033, 2979, 2850};
uint16_t estimateSOC(uint16_t cellVoltage) { // Linear interpolation function
if (cellVoltage >= voltage[0]) {
return SOC[0];
}
if (cellVoltage <= voltage[numPoints - 1]) {
return SOC[numPoints - 1];
}
for (int i = 1; i < numPoints; ++i) {
if (cellVoltage >= voltage[i]) {
// Fix: Cast to float or double to ensure proper floating-point division
float t = (float)(cellVoltage - voltage[i]) / (float)(voltage[i - 1] - voltage[i]);
// Calculate interpolated SOC value
uint16_t socDiff = SOC[i - 1] - SOC[i];
uint16_t interpolatedValue = SOC[i] + (uint16_t)(t * socDiff);
return interpolatedValue;
}
}
return 0; // Default return for safety, should never reach here
}
uint16_t selectSOC(uint16_t SOC_low, uint16_t SOC_high) {
if (SOC_low == 0 || SOC_high == 0) {
return 0; // If either value is 0, return 0
}
if (SOC_low == 10000 || SOC_high == 10000) {
return 10000; // If either value is 100, return 100
}
return (SOC_low < SOC_high) ? SOC_low : SOC_high; // Otherwise, return the lowest value
}
/* These messages are needed for contactor closing */
unsigned long startMillis;
uint8_t messageIndex = 0;
@ -632,7 +686,13 @@ static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
#ifdef ESTIMATE_SOC_FROM_CELLVOLTAGE
SOC_estimated_lowest = estimateSOC(CellVoltMin_mV);
SOC_estimated_highest = estimateSOC(CellVoltMax_mV);
datalayer.battery.status.real_soc = selectSOC(SOC_estimated_lowest, SOC_estimated_highest);
#else
datalayer.battery.status.real_soc = (SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
#endif
datalayer.battery.status.soh_pptt = (batterySOH * 10); //Increase decimals from 100.0% -> 100.00%

View file

@ -6,6 +6,8 @@
extern ACAN2517FD canfd;
#define ESTIMATE_SOC_FROM_CELLVOLTAGE
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 8064 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 4320

View file

@ -185,6 +185,8 @@ static uint8_t seconds = 0;
static uint32_t first_can_msg = 0;
static uint32_t last_can_msg_timestamp = 0;
static bool hv_requested = false;
static int32_t kwh_charge = 0;
static int32_t kwh_discharge = 0;
#define TIME_YEAR 2024
#define TIME_MONTH 8
@ -540,14 +542,27 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.real_soc = battery_SOC * 5; //*0.05*100
datalayer.battery.status.soh_pptt;
datalayer.battery.status.voltage_dV = BMS_voltage * 2.5; // *0.25*10
datalayer.battery.status.current_dA = (BMS_current - 16300); // 0.1 * 10
if (nof_cells_determined) {
datalayer.battery.info.total_capacity_Wh =
((float)datalayer.battery.info.number_of_cells) * 3.6458 * ((float)BMS_capacity_ah) * 0.2 * 1.13;
((float)datalayer.battery.info.number_of_cells) * 3.67 * ((float)BMS_capacity_ah) * 0.2 * 1.02564;
// The factor 1.02564 = 1/0.975 is to correct for bottom 2.5% which is reported by the remaining_capacity_Wh,
// but which is not actually usable, but if we do not include it, the remaining_capacity_Wh can be larger than
// the total_capacity_Wh.
// 0.935 and 0.9025 are the different conversions for different battery sizes to go from design capacity to
// total_capacity_Wh calculated above.
int Wh_max = 61832 * 0.935; // 108 cells
if (datalayer.battery.info.number_of_cells <= 84)
Wh_max = 48091 * 0.9025;
else if (datalayer.battery.info.number_of_cells <= 96)
Wh_max = 82442 * 0.9025;
if (BMS_capacity_ah > 0)
datalayer.battery.status.soh_pptt = 10000 * datalayer.battery.info.total_capacity_Wh / (Wh_max * 1.02564);
}
datalayer.battery.status.remaining_capacity_Wh = usable_energy_amount_Wh * 5;
@ -589,6 +604,11 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.meb.BMS_mode = BMS_mode;
datalayer_extended.meb.battery_diagnostic = battery_diagnostic;
datalayer_extended.meb.status_HV_line = status_HV_line;
datalayer_extended.meb.BMS_fault_performance = BMS_fault_performance;
datalayer_extended.meb.BMS_fault_emergency_shutdown_crash = BMS_fault_emergency_shutdown_crash;
datalayer_extended.meb.BMS_error_shutdown_request = BMS_error_shutdown_request;
datalayer_extended.meb.BMS_error_shutdown = BMS_error_shutdown;
datalayer_extended.meb.warning_support = warning_support;
datalayer_extended.meb.BMS_status_voltage_free = BMS_status_voltage_free;
datalayer_extended.meb.BMS_OBD_MIL = BMS_OBD_MIL;
@ -1151,6 +1171,18 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
case PID_MIN_DISCHARGE_VOLTAGE:
battery_min_discharge_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case PID_ENERGY_COUNTERS:
// int32_t ah_discharge = ((rx_frame.data.u8[5] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[7] << 8) |rx_frame.data.u8[8]);
// int32_t ah_charge = ((rx_frame.data.u8[9] << 24) | (rx_frame.data.u8[10] << 16) | (rx_frame.data.u8[11] << 8) |rx_frame.data.u8[12]);
kwh_charge = ((rx_frame.data.u8[13] << 24) | (rx_frame.data.u8[14] << 16) | (rx_frame.data.u8[15] << 8) |
rx_frame.data.u8[16]);
kwh_discharge = ((rx_frame.data.u8[17] << 24) | (rx_frame.data.u8[18] << 16) | (rx_frame.data.u8[19] << 8) |
rx_frame.data.u8[20]);
// logging.printf("ah_dis:%.3f ah_ch:%.3f kwh_dis:%.3f kwh_ch:%.3f\n", ah_discharge*0.00182044545, ah_charge*0.00182044545,
// kwh_discharge*0.00011650853, kwh_charge*0.00011650853);
datalayer.battery.status.total_discharged_battery_Wh = kwh_discharge * 0.11650853;
datalayer.battery.status.total_charged_battery_Wh = kwh_charge * 0.11650853;
break;
case PID_ALLOWED_CHARGE_POWER:
battery_allowed_charge_power = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
@ -1836,6 +1868,11 @@ void transmit_can_battery() {
case PID_MIN_DISCHARGE_VOLTAGE:
MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_MIN_DISCHARGE_VOLTAGE >> 8);
MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_MIN_DISCHARGE_VOLTAGE;
poll_pid = PID_ENERGY_COUNTERS;
break;
case PID_ENERGY_COUNTERS:
MEB_POLLING_FRAME.data.u8[2] = (uint8_t)(PID_ENERGY_COUNTERS >> 8);
MEB_POLLING_FRAME.data.u8[3] = (uint8_t)PID_ENERGY_COUNTERS;
poll_pid = PID_ALLOWED_CHARGE_POWER;
break;
case PID_ALLOWED_CHARGE_POWER:

View file

@ -21,6 +21,7 @@
#define PID_MIN_TEMP 0x1E0F
#define PID_MAX_CHARGE_VOLTAGE 0x5171
#define PID_MIN_DISCHARGE_VOLTAGE 0x5170
#define PID_ENERGY_COUNTERS 0x1E32
#define PID_ALLOWED_CHARGE_POWER 0x1E1B
#define PID_ALLOWED_DISCHARGE_POWER 0x1E1C
#define PID_CELLVOLTAGE_CELL_1 0x1E40

View file

@ -170,12 +170,153 @@ void transmit_can_battery() {
transmit_can_frame(&PYLON_8200, can_config.battery); // Control device quit sleep status
transmit_can_frame(&PYLON_8210, can_config.battery); // Charge command
#ifdef DOUBLE_BATTERY
transmit_can_frame(&PYLON_3010, can_config.battery_double); // Heartbeat
transmit_can_frame(&PYLON_4200, can_config.battery_double); // Ensemble OR System equipment info, depends on frame0
transmit_can_frame(&PYLON_8200, can_config.battery_double); // Control device quit sleep status
transmit_can_frame(&PYLON_8210, can_config.battery_double); // Charge command
#endif //DOUBLE_BATTERY
if (ensemble_info_ack) {
PYLON_4200.data.u8[0] = 0x00; //Request system equipment info
}
}
}
#ifdef DOUBLE_BATTERY
static int16_t battery2_celltemperature_max_dC = 0;
static int16_t battery2_celltemperature_min_dC = 0;
static int16_t battery2_current_dA = 0;
static uint16_t battery2_voltage_dV = 0;
static uint16_t battery2_cellvoltage_max_mV = 3700;
static uint16_t battery2_cellvoltage_min_mV = 3700;
static uint16_t battery2_charge_cutoff_voltage = 0;
static uint16_t battery2_discharge_cutoff_voltage = 0;
static int16_t battery2_max_charge_current = 0;
static int16_t battery2_max_discharge_current = 0;
static uint8_t battery2_ensemble_info_ack = 0;
static uint8_t battery2_module_quantity = 0;
static uint8_t battery2_modules_in_series = 0;
static uint8_t battery2_cell_quantity_in_module = 0;
static uint8_t battery2_voltage_level = 0;
static uint8_t battery2_ah_number = 0;
static uint8_t battery2_SOC = 0;
static uint8_t battery2_SOH = 0;
static uint8_t battery2_charge_forbidden = 0;
static uint8_t battery2_discharge_forbidden = 0;
void update_values_battery2() {
datalayer.battery2.status.real_soc = (battery2_SOC * 100); //increase SOC range from 0-100 -> 100.00
datalayer.battery2.status.soh_pptt = (battery2_SOH * 100); //Increase decimals from 100% -> 100.00%
datalayer.battery2.status.voltage_dV = battery2_voltage_dV; //value is *10 (3700 = 370.0)
datalayer.battery2.status.current_dA = battery2_current_dA; //value is *10 (150 = 15.0) , invert the sign
datalayer.battery2.status.max_charge_power_W = (battery2_max_charge_current * (battery2_voltage_dV / 10));
datalayer.battery2.status.max_discharge_power_W = (-battery2_max_discharge_current * (battery2_voltage_dV / 10));
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.cell_max_voltage_mV = battery2_cellvoltage_max_mV;
datalayer.battery2.status.cell_voltages_mV[0] = battery2_cellvoltage_max_mV;
datalayer.battery2.status.cell_min_voltage_mV = battery2_cellvoltage_min_mV;
datalayer.battery2.status.cell_voltages_mV[1] = battery2_cellvoltage_min_mV;
datalayer.battery2.status.temperature_min_dC = battery2_celltemperature_min_dC;
datalayer.battery2.status.temperature_max_dC = battery2_celltemperature_max_dC;
datalayer.battery2.info.max_design_voltage_dV = battery2_charge_cutoff_voltage;
datalayer.battery2.info.min_design_voltage_dV = battery2_discharge_cutoff_voltage;
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) {
case 0x7310:
case 0x7311:
battery2_ensemble_info_ack = true;
// This message contains software/hardware version info. No interest to us
break;
case 0x7320:
case 0x7321:
battery2_ensemble_info_ack = true;
battery2_module_quantity = rx_frame.data.u8[0];
battery2_modules_in_series = rx_frame.data.u8[2];
battery2_cell_quantity_in_module = rx_frame.data.u8[3];
battery2_voltage_level = rx_frame.data.u8[4];
battery2_ah_number = rx_frame.data.u8[6];
break;
case 0x4210:
case 0x4211:
battery2_voltage_dV = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery2_current_dA = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 30000;
battery2_SOC = rx_frame.data.u8[6];
battery2_SOH = rx_frame.data.u8[7];
break;
case 0x4220:
case 0x4221:
battery2_charge_cutoff_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery2_discharge_cutoff_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
battery2_max_charge_current = (((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) * 0.1) - 3000;
battery2_max_discharge_current = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) * 0.1) - 3000;
break;
case 0x4230:
case 0x4231:
battery2_cellvoltage_max_mV = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery2_cellvoltage_min_mV = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
break;
case 0x4240:
case 0x4241:
battery2_celltemperature_max_dC = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) - 1000;
battery2_celltemperature_min_dC = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 1000;
break;
case 0x4250:
case 0x4251:
//Byte0 Basic Status
//Byte1-2 Cycle Period
//Byte3 Error
//Byte4-5 Alarm
//Byte6-7 Protection
break;
case 0x4260:
case 0x4261:
//Byte0-1 Module Max Voltage
//Byte2-3 Module Min Voltage
//Byte4-5 Module Max. Voltage Number
//Byte6-7 Module Min. Voltage Number
break;
case 0x4270:
case 0x4271:
//Byte0-1 Module Max. Temperature
//Byte2-3 Module Min. Temperature
//Byte4-5 Module Max. Temperature Number
//Byte6-7 Module Min. Temperature Number
break;
case 0x4280:
case 0x4281:
battery2_charge_forbidden = rx_frame.data.u8[0];
battery2_discharge_forbidden = rx_frame.data.u8[1];
break;
case 0x4290:
case 0x4291:
break;
default:
break;
}
}
#endif //DOUBLE_BATTERY
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
@ -184,6 +325,15 @@ 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;
#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

@ -9,4 +9,7 @@
#define MAX_CELL_VOLTAGE_MV 4200 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 3400 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
#endif

View file

@ -824,8 +824,8 @@ void update_values_battery() { //This function maps all the values fetched via
} else {
clear_event(EVENT_INTERNAL_OPEN_FAULT);
}
//Voltage missing, pyrofuse most likely blown
if (datalayer.battery.status.voltage_dV == 10) {
//Voltage between 0.5-5.0V, pyrofuse most likely blown
if (datalayer.battery.status.voltage_dV >= 5 && datalayer.battery.status.voltage_dV <= 50) {
set_event(EVENT_BATTERY_FUSE, 0);
} else {
clear_event(EVENT_BATTERY_FUSE);
@ -907,8 +907,8 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.tesla.battery_full_charge_complete = battery_full_charge_complete;
datalayer_extended.tesla.battery_fully_charged = battery_fully_charged;
//0x3D2
datalayer_extended.tesla.battery_total_discharge = battery_total_discharge;
datalayer_extended.tesla.battery_total_charge = battery_total_charge;
datalayer.battery.status.total_discharged_battery_Wh = battery_total_discharge;
datalayer.battery.status.total_charged_battery_Wh = battery_total_charge;
//0x392
datalayer_extended.tesla.battery_moduleType = battery_moduleType;
datalayer_extended.tesla.battery_packMass = battery_packMass;
@ -1311,11 +1311,11 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
case 0x3D2: //TotalChargeDischarge:
battery_total_discharge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) |
(rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) *
0.001; //0|32@1+ (0.001,0) [0|4294970] "kWh"
(rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
//0|32@1+ (0.001,0) [0|4294970] "kWh"
battery_total_charge = ((rx_frame.data.u8[7] << 24) | (rx_frame.data.u8[6] << 16) | (rx_frame.data.u8[5] << 8) |
rx_frame.data.u8[4]) *
0.001; //32|32@1+ (0.001,0) [0|4294970] "kWh"
rx_frame.data.u8[4]);
//32|32@1+ (0.001,0) [0|4294970] "kWh"
break;
case 0x332: //min/max hist values //BattBrickMinMax:
mux = (rx_frame.data.u8[0] & 0x03); //BattBrickMultiplexer M : 0|2@1+ (1,0) [0|0] ""
@ -1633,7 +1633,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
battery_OverDchgCurrentFault = ((rx_frame.data.u8[0] & 0x10) >> 4);
battery_OverChargeCurrentFault = ((rx_frame.data.u8[0] & 0x20) >> 5);
battery_OverCurrentFault = ((rx_frame.data.u8[0] & 0x40) >> 6);
battery_OverTemperatureFault = ((rx_frame.data.u8[1] & 0x80) >> 7);
battery_OverTemperatureFault = ((rx_frame.data.u8[0] & 0x80) >> 7);
battery_OverVoltageFault = (rx_frame.data.u8[1] & 0x01);
battery_UnderVoltageFault = ((rx_frame.data.u8[1] & 0x02) >> 1);
battery_PrimaryBmbMiaFault = ((rx_frame.data.u8[1] & 0x04) >> 2);

View file

@ -7,6 +7,7 @@ typedef struct {
/** uint32_t */
/** Total energy capacity in Watt-hours */
uint32_t total_capacity_Wh = BATTERY_WH_MAX;
uint32_t reported_total_capacity_Wh = BATTERY_WH_MAX;
/** uint16_t */
/** The maximum intended packvoltage, in deciVolt. 4900 = 490.0 V */
@ -45,6 +46,9 @@ typedef struct {
*/
uint32_t reported_remaining_capacity_Wh;
int32_t total_charged_battery_Wh = 0;
int32_t total_discharged_battery_Wh = 0;
/** Maximum allowed battery discharge power in Watts. Set by battery */
uint32_t max_discharge_power_W = 0;
/** Maximum allowed battery charge power in Watts. Set by battery */

View file

@ -321,8 +321,6 @@ typedef struct {
uint16_t battery_expected_energy_remaining_m1 = 0;
bool battery_full_charge_complete = false;
bool battery_fully_charged = false;
uint16_t battery_total_discharge = 0;
uint16_t battery_total_charge = 0;
uint16_t battery_BrickVoltageMax = 0;
uint16_t battery_BrickVoltageMin = 0;
uint8_t battery_BrickVoltageMaxNum = 0;
@ -585,6 +583,14 @@ typedef struct {
uint8_t status_HV_line = 0;
/** uint8_t */
/** 0 = OK, 1 = Not OK, 0x06 = init, 0x07 = fault */
bool BMS_fault_performance = false; //Error: Battery performance is limited (e.g. due to sensor or fan failure)
bool BMS_fault_emergency_shutdown_crash =
false; //Error: Safety-critical error (crash detection) Battery contactors are already opened / will be opened immediately Signal is read directly by the EMS and initiates an AKS of the PWR and an active discharge of the DC link
bool BMS_error_shutdown_request =
false; // Fault: Fault condition, requires battery contactors to be opened internal battery error; Advance notification of an impending opening of the battery contactors by the BMS
bool BMS_error_shutdown =
false; // Fault: Fault condition, requires battery contactors to be opened Internal battery error, battery contactors opened without notice by the BMS
uint8_t warning_support = 0;
/** uint32_t */
/** Isolation resistance in kOhm */

View file

@ -81,6 +81,10 @@ SensorConfig sensorConfigTemplate[] = {
{"remaining_capacity_real", "Battery Remaining Capacity (real)", "", "Wh", "energy"},
{"max_discharge_power", "Battery Max Discharge Power", "", "W", "power"},
{"max_charge_power", "Battery Max Charge Power", "", "W", "power"},
#if defined(MEB_BATTERY) || defined(TESLA_BATTERY)
{"charged_energy", "Battery Charged Energy", "", "Wh", "energy"},
{"discharged_energy", "Battery Discharged Energy", "", "Wh", "energy"},
#endif
{"bms_status", "BMS Status", "", "", ""},
{"pause_status", "Pause Status", "", "", ""}};
@ -179,6 +183,13 @@ void set_battery_attributes(JsonDocument& doc, const DATALAYER_BATTERY_TYPE& bat
doc["remaining_capacity" + suffix] = ((float)battery.status.reported_remaining_capacity_Wh);
doc["max_discharge_power" + suffix] = ((float)battery.status.max_discharge_power_W);
doc["max_charge_power" + suffix] = ((float)battery.status.max_charge_power_W);
#if defined(MEB_BATTERY) || defined(TESLA_BATTERY)
if (datalayer.battery.status.total_charged_battery_Wh != 0 &&
datalayer.battery.status.total_discharged_battery_Wh != 0) {
doc["charged_energy" + suffix] = ((float)datalayer.battery.status.total_charged_battery_Wh);
doc["discharged_energy" + suffix] = ((float)datalayer.battery.status.total_discharged_battery_Wh);
}
#endif
}
static std::vector<EventData> order_events;
@ -438,14 +449,17 @@ static void subscribe() {
esp_mqtt_client_subscribe(client, (topic_name + "/command/+").c_str(), 1);
}
void mqtt_message_received(char* topic, int topic_len, char* data, int data_len) {
void mqtt_message_received(char* topic_raw, int topic_len, char* data, int data_len) {
char* topic = strndup(topic_raw, topic_len);
#ifdef DEBUG_LOG
logging.printf("MQTT message arrived: [%.*s]\n", topic_len, topic);
#endif // DEBUG_LOG
#ifdef REMOTE_BMS_RESET
const char* bmsreset_topic = generateButtonTopic("BMSRESET").c_str();
if (strncmp(topic, bmsreset_topic, topic_len) == 0) {
if (strcmp(topic, bmsreset_topic) == 0) {
#ifdef DEBUG_LOG
logging.println("Triggering BMS reset");
#endif // DEBUG_LOG
@ -453,21 +467,21 @@ void mqtt_message_received(char* topic, int topic_len, char* data, int data_len)
}
#endif // REMOTE_BMS_RESET
if (strncmp(topic, generateButtonTopic("PAUSE").c_str(), topic_len) == 0) {
if (strcmp(topic, generateButtonTopic("PAUSE").c_str()) == 0) {
setBatteryPause(true, false);
}
if (strncmp(topic, generateButtonTopic("RESUME").c_str(), topic_len) == 0) {
if (strcmp(topic, generateButtonTopic("RESUME").c_str()) == 0) {
setBatteryPause(false, false, false);
}
if (strncmp(topic, generateButtonTopic("RESTART").c_str(), topic_len) == 0) {
if (strcmp(topic, generateButtonTopic("RESTART").c_str()) == 0) {
setBatteryPause(true, true, true, false);
delay(1000);
ESP.restart();
}
if (strncmp(topic, generateButtonTopic("STOP").c_str(), topic_len) == 0) {
if (strcmp(topic, generateButtonTopic("STOP").c_str()) == 0) {
setBatteryPause(true, false, true);
}
}

View file

@ -355,12 +355,11 @@ void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bo
}
//immediate check if we can send CAN messages
emulator_pause_state_transmit_can_battery();
update_pause_state();
}
/// @brief handle emulator pause status
/// @return true if CAN messages should be sent to battery, false if not
void emulator_pause_state_transmit_can_battery() {
/// @brief handle emulator pause status and CAN sending allowed
void update_pause_state() {
bool previous_allowed_to_send_CAN = allowed_to_send_CAN;
if (emulator_pause_status == NORMAL) {
@ -385,13 +384,13 @@ void emulator_pause_state_transmit_can_battery() {
logging.printf("Safety: Pausing CAN sending\n");
#endif
//completely force stop the CAN communication
ESP32Can.CANStop();
ESP32Can.CANStop(); //Note: This only stops the NATIVE_CAN port, it will no longer ACK messages
} else if (!previous_allowed_to_send_CAN && allowed_to_send_CAN) {
//resume CAN communication
#ifdef DEBUG_LOG
logging.printf("Safety: Resuming CAN sending\n");
#endif
ESP32Can.CANInit();
ESP32Can.CANInit(); //Note: This only resumes the NATIVE_CAN port
}
}

View file

@ -22,7 +22,7 @@ void update_machineryprotection();
//battery pause status begin
void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop = false, bool store_settings = true);
void emulator_pause_state_transmit_can_battery();
void update_pause_state();
std::string get_emulator_pause_status();
//battery pause status end

View file

@ -9,16 +9,13 @@ class LED {
led_color color = led_color::GREEN;
LED()
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
: pixels(1, LED_PIN, NEO_GRB),
max_brightness(LED_MAX_BRIGHTNESS),
brightness(LED_MAX_BRIGHTNESS),
mode(led_mode_enum::CLASSIC) {}
LED(led_mode_enum mode)
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
max_brightness(LED_MAX_BRIGHTNESS),
brightness(LED_MAX_BRIGHTNESS),
mode(mode) {}
: pixels(1, LED_PIN, NEO_GRB), max_brightness(LED_MAX_BRIGHTNESS), brightness(LED_MAX_BRIGHTNESS), mode(mode) {}
void exe(void);
void init(void) { pixels.begin(); }

View file

@ -135,16 +135,12 @@ void Logging::printf(const char* fmt, ...) {
#endif // DEBUG_LOG
}
void Logging::log_bms_status(real_bms_status_enum bms_status, int battery_id) {
void Logging::log_bms_status(real_bms_status_enum bms_status) {
static real_bms_status_enum previous_state = BMS_FAULT;
const char* id = "";
if (battery_id == 2) {
id = "2";
}
if (previous_state != bms_status) {
switch (bms_status) {
case BMS_ACTIVE:
logging.printf("Battery%s BMS state changed to: OK\n", id);
logging.printf("Battery%s BMS state changed to: OK\n");
break;
case BMS_DISCONNECTED:
logging.printf("Battery%s BMS state changed to: DISCONNECTED\n");

View file

@ -12,7 +12,7 @@ class Logging : public Print {
virtual size_t write(const uint8_t* buffer, size_t size);
virtual size_t write(uint8_t) { return 0; }
void printf(const char* fmt, ...);
void log_bms_status(real_bms_status_enum bms_status, int battery_id);
void log_bms_status(real_bms_status_enum bms_status);
Logging() {}
};

View file

@ -506,8 +506,8 @@ String advanced_battery_processor(const String& var) {
float energy_buffer_m1 = static_cast<float>(datalayer_extended.tesla.battery_energy_buffer_m1) * 0.01;
float expected_energy_remaining_m1 =
static_cast<float>(datalayer_extended.tesla.battery_expected_energy_remaining_m1) * 0.02;
float total_discharge = static_cast<float>(datalayer_extended.tesla.battery_total_discharge);
float total_charge = static_cast<float>(datalayer_extended.tesla.battery_total_charge);
float total_discharge = static_cast<float>(datalayer.battery.status.total_discharged_battery_Wh) * 0.001;
float total_charge = static_cast<float>(datalayer.battery.status.total_charged_battery_Wh) * 0.001;
float packMass = static_cast<float>(datalayer_extended.tesla.battery_packMass);
float platformMaxBusVoltage =
static_cast<float>(datalayer_extended.tesla.battery_platformMaxBusVoltage) * 0.1 + 375;
@ -1062,7 +1062,18 @@ String advanced_battery_processor(const String& var) {
default:
content += String("? ") + String(datalayer_extended.meb.status_HV_line);
}
content += "</h4><h4>Warning support: ";
content += "</h4>";
content += datalayer_extended.meb.BMS_fault_performance ? "<h4>BMS fault performance: Active!</h4>"
: "<h4>BMS fault performance: Off</h4>";
content += datalayer_extended.meb.BMS_fault_emergency_shutdown_crash
? "<h4>BMS fault emergency shutdown crash: Active!</h4>"
: "<h4>BMS fault emergency shutdown crash: Off</h4>";
content += datalayer_extended.meb.BMS_error_shutdown_request ? "<h4>BMS error shutdown request: Active!</h4>"
: "<h4>BMS error shutdown request: Inactive</h4>";
content += datalayer_extended.meb.BMS_error_shutdown ? "<h4>BMS error shutdown: Active!</h4>"
: "<h4>BMS error shutdown: Off</h4>";
content += "<h4>Warning support: ";
switch (datalayer_extended.meb.warning_support) {
case 0:
content += String("OK");
@ -1175,6 +1186,10 @@ String advanced_battery_processor(const String& var) {
}
content += " &deg;C</h4>";
}
content +=
"<h4>Total charged: " + String(datalayer.battery.status.total_charged_battery_Wh / 1000.0, 1) + " kWh</h4>";
content += "<h4>Total discharged: " + String(datalayer.battery.status.total_discharged_battery_Wh / 1000.0, 1) +
" kWh</h4>";
#endif //MEB_BATTERY
#ifdef RENAULT_ZOE_GEN2_BATTERY

View file

@ -1054,16 +1054,30 @@ String processor(const String& var) {
uint16_t cell_delta_mv =
datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV;
content += "<h4 style='color: white;'>Real SOC: " + String(socRealFloat, 2) + "</h4>";
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) + "</h4>";
content += "<h4 style='color: white;'>SOH: " + String(sohFloat, 2) + "</h4>";
content += "<h4 style='color: white;'>Voltage: " + String(voltageFloat, 1) + " V</h4>";
content += "<h4 style='color: white;'>Current: " + String(currentFloat, 1) + " A</h4>";
if (datalayer.battery.settings.soc_scaling_active)
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) +
"&percnt; (real: " + String(socRealFloat, 2) + "&percnt;)</h4>";
else
content += "<h4 style='color: white;'>SOC: " + String(socRealFloat, 2) + "&percnt;</h4>";
content += "<h4 style='color: white;'>SOH: " + String(sohFloat, 2) + "&percnt;</h4>";
content += "<h4 style='color: white;'>Voltage: " + String(voltageFloat, 1) +
" V &nbsp; Current: " + String(currentFloat, 1) + " A</h4>";
content += formatPowerValue("Power", powerFloat, "", 1);
content += formatPowerValue("Total capacity", datalayer.battery.info.total_capacity_Wh, "h", 0);
content += formatPowerValue("Real Remaining capacity", datalayer.battery.status.remaining_capacity_Wh, "h", 1);
content +=
formatPowerValue("Scaled Remaining capacity", datalayer.battery.status.reported_remaining_capacity_Wh, "h", 1);
if (datalayer.battery.settings.soc_scaling_active)
content += "<h4 style='color: white;'>Scaled total capacity: " +
formatPowerValue(datalayer.battery.info.reported_total_capacity_Wh, "h", 1) +
" (real: " + formatPowerValue(datalayer.battery.info.total_capacity_Wh, "h", 1) + ")</h4>";
else
content += formatPowerValue("Total capacity", datalayer.battery.info.total_capacity_Wh, "h", 1);
if (datalayer.battery.settings.soc_scaling_active)
content += "<h4 style='color: white;'>Scaled remaining capacity: " +
formatPowerValue(datalayer.battery.status.reported_remaining_capacity_Wh, "h", 1) +
" (real: " + formatPowerValue(datalayer.battery.status.remaining_capacity_Wh, "h", 1) + ")</h4>";
else
content += formatPowerValue("Remaining capacity", datalayer.battery.status.remaining_capacity_Wh, "h", 1);
if (datalayer.system.settings.equipment_stop_active) {
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red");
@ -1087,15 +1101,15 @@ String processor(const String& var) {
}
}
content += "<h4>Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";
content += "<h4>Cell min: " + String(datalayer.battery.status.cell_min_voltage_mV) + " mV</h4>";
content += "<h4>Cell min/max: " + String(datalayer.battery.status.cell_min_voltage_mV) + " mV / " +
String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";
if (cell_delta_mv > datalayer.battery.info.max_cell_voltage_deviation_mV) {
content += "<h4 style='color: red;'>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
} else {
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
}
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " &deg;C</h4>";
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " &deg;C</h4>";
content +=
"<h4>Temperature min/max: " + String(tempMinFloat, 1) + " &deg;C / " + String(tempMaxFloat, 1) + " &deg;C</h4>";
content += "<h4>System status: ";
switch (datalayer.battery.status.bms_status) {
@ -1261,16 +1275,30 @@ String processor(const String& var) {
tempMinFloat = static_cast<float>(datalayer.battery2.status.temperature_min_dC) / 10.0; // Convert to float
cell_delta_mv = datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV;
content += "<h4 style='color: white;'>Real SOC: " + String(socRealFloat, 2) + "</h4>";
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) + "</h4>";
content += "<h4 style='color: white;'>SOH: " + String(sohFloat, 2) + "</h4>";
content += "<h4 style='color: white;'>Voltage: " + String(voltageFloat, 1) + " V</h4>";
content += "<h4 style='color: white;'>Current: " + String(currentFloat, 1) + " A</h4>";
if (datalayer.battery.settings.soc_scaling_active)
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) +
"&percnt; (real: " + String(socRealFloat, 2) + "&percnt;)</h4>";
else
content += "<h4 style='color: white;'>SOC: " + String(socRealFloat, 2) + "&percnt;</h4>";
content += "<h4 style='color: white;'>SOH: " + String(sohFloat, 2) + "&percnt;</h4>";
content += "<h4 style='color: white;'>Voltage: " + String(voltageFloat, 1) +
" V &nbsp; Current: " + String(currentFloat, 1) + " A</h4>";
content += formatPowerValue("Power", powerFloat, "", 1);
content += formatPowerValue("Total capacity", datalayer.battery2.info.total_capacity_Wh, "h", 0);
content += formatPowerValue("Real Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
content +=
formatPowerValue("Scaled Remaining capacity", datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1);
if (datalayer.battery.settings.soc_scaling_active)
content += "<h4 style='color: white;'>Scaled total capacity: " +
formatPowerValue(datalayer.battery2.info.reported_total_capacity_Wh, "h", 1) +
" (real: " + formatPowerValue(datalayer.battery2.info.total_capacity_Wh, "h", 1) + ")</h4>";
else
content += formatPowerValue("Total capacity", datalayer.battery2.info.total_capacity_Wh, "h", 1);
if (datalayer.battery.settings.soc_scaling_active)
content += "<h4 style='color: white;'>Scaled remaining capacity: " +
formatPowerValue(datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1) +
" (real: " + formatPowerValue(datalayer.battery2.status.remaining_capacity_Wh, "h", 1) + ")</h4>";
else
content += formatPowerValue("Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
if (datalayer.system.settings.equipment_stop_active) {
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1, "red");
@ -1284,15 +1312,15 @@ String processor(const String& var) {
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
}
content += "<h4>Cell max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV</h4>";
content += "<h4>Cell min: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV</h4>";
content += "<h4>Cell min/max: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV / " +
String(datalayer.battery2.status.cell_max_voltage_mV) + " mV</h4>";
if (cell_delta_mv > datalayer.battery2.info.max_cell_voltage_deviation_mV) {
content += "<h4 style='color: red;'>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
} else {
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
}
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " &deg;C</h4>";
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " &deg;C</h4>";
content +=
"<h4>Temperature min/max: " + String(tempMinFloat, 1) + " &deg;C / " + String(tempMaxFloat, 1) + " &deg;C</h4>";
if (datalayer.battery.status.bms_status == ACTIVE) {
content += "<h4>System status: OK </h4>";
} else if (datalayer.battery.status.bms_status == UPDATING) {
@ -1564,6 +1592,13 @@ void onOTAEnd(bool success) {
template <typename T> // This function makes power values appear as W when under 1000, and kW when over
String formatPowerValue(String label, T value, String unit, int precision, String color) {
String result = "<h4 style='color: " + color + ";'>" + label + ": ";
result += formatPowerValue(value, unit, precision);
result += "</h4>";
return result;
}
template <typename T> // This function makes power values appear as W when under 1000, and kW when over
String formatPowerValue(T value, String unit, int precision) {
String result = "";
if (std::is_same<T, float>::value || std::is_same<T, uint16_t>::value || std::is_same<T, uint32_t>::value) {
float convertedValue = static_cast<float>(value);
@ -1575,6 +1610,6 @@ String formatPowerValue(String label, T value, String unit, int precision, Strin
}
}
result += unit + "</h4>";
result += unit;
return result;
}

View file

@ -103,6 +103,9 @@ void onOTAEnd(bool success);
template <typename T>
String formatPowerValue(String label, T value, String unit, int precision, String color = "white");
template <typename T> // This function makes power values appear as W when under 1000, and kW when over
String formatPowerValue(T value, String unit, int precision);
extern void store_settings();
void ota_monitor();

View file

@ -126,6 +126,8 @@ void update_values_can_inverter() { //This function maps all the values fetched
//SOC (100.00%)
BYD_150.data.u8[0] = (datalayer.battery.status.reported_soc >> 8);
BYD_150.data.u8[1] = (datalayer.battery.status.reported_soc & 0x00FF);
#ifdef BYD_CAN_DEYE
// Fix for avoiding offgrid Deye inverters to underdischarge batteries
if (datalayer.battery.status.max_charge_current_dA == 0) {
//Force to 100.00% incase battery no longer wants to charge
BYD_150.data.u8[0] = (10000 >> 8);
@ -136,6 +138,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
BYD_150.data.u8[0] = 0;
BYD_150.data.u8[1] = 0;
}
#endif //BYD_CAN_DEYE
//StateOfHealth (100.00%)
BYD_150.data.u8[2] = (datalayer.battery.status.soh_pptt >> 8);
BYD_150.data.u8[3] = (datalayer.battery.status.soh_pptt & 0x00FF);

View file

@ -137,6 +137,7 @@ CAN_frame GROWATT_3F00 = {.FD = false,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
static unsigned long previousMillisBatchSend = 0;
static uint32_t unix_time = 0;
static uint16_t ampere_hours_remaining = 0;
static uint16_t ampere_hours_full = 0;
@ -151,7 +152,10 @@ static uint8_t ISO_detection_command = 0;
static uint8_t sleep_wakeup_control = 0;
static uint8_t PCS_working_status = 0; //00 standby, 01 operating
static uint8_t serial_number_counter = 0; //0-1-2-0-1-2...
static uint8_t can_message_batch_index = 0;
static const uint8_t delay_between_batches_ms = 10;
static bool inverter_alive = false;
static bool time_to_send_1s_data = false;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
@ -181,6 +185,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
GROWATT_3110.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
GROWATT_3110.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
//Status bits (see documentation for all bits, most important are mapped
GROWATT_3110.data.u8[7] = 0x00; // Clear all bits
if (datalayer.battery.status.active_power_W < -1) { // Discharging
GROWATT_3110.data.u8[7] = (GROWATT_3110.data.u8[7] | 0b00000011);
} else if (datalayer.battery.status.active_power_W > 1) { // Charging
@ -526,29 +531,59 @@ void transmit_can_inverter() {
unsigned long currentMillis = millis();
//Send 1s periodic CAN messages
//Check if 1 second has passed, then we start sending!
if (currentMillis - previousMillis1s >= INTERVAL_1_S) {
previousMillis1s = currentMillis;
time_to_send_1s_data = true;
}
// Check if enough time has passed since the last batch
if (currentMillis - previousMillisBatchSend >= delay_between_batches_ms) {
previousMillisBatchSend = currentMillis; // Update the time of the last message batch
// Send a subset of messages per iteration to avoid overloading the CAN bus / transmit buffer
switch (can_message_batch_index) {
case 0:
transmit_can_frame(&GROWATT_3110, can_config.inverter);
transmit_can_frame(&GROWATT_3120, can_config.inverter);
transmit_can_frame(&GROWATT_3130, can_config.inverter);
transmit_can_frame(&GROWATT_3140, can_config.inverter);
break;
case 1:
transmit_can_frame(&GROWATT_3150, can_config.inverter);
transmit_can_frame(&GROWATT_3160, can_config.inverter);
transmit_can_frame(&GROWATT_3170, can_config.inverter);
transmit_can_frame(&GROWATT_3180, can_config.inverter);
break;
case 2:
transmit_can_frame(&GROWATT_3190, can_config.inverter);
transmit_can_frame(&GROWATT_3200, can_config.inverter);
transmit_can_frame(&GROWATT_3210, can_config.inverter);
transmit_can_frame(&GROWATT_3220, can_config.inverter);
break;
case 3:
transmit_can_frame(&GROWATT_3230, can_config.inverter);
transmit_can_frame(&GROWATT_3240, can_config.inverter);
transmit_can_frame(&GROWATT_3250, can_config.inverter);
transmit_can_frame(&GROWATT_3260, can_config.inverter);
break;
case 4:
transmit_can_frame(&GROWATT_3270, can_config.inverter);
transmit_can_frame(&GROWATT_3280, can_config.inverter);
transmit_can_frame(&GROWATT_3290, can_config.inverter);
transmit_can_frame(&GROWATT_3F00, can_config.inverter);
time_to_send_1s_data = false;
break;
default:
break;
}
// Increment message index and wrap around if needed
can_message_batch_index++;
if (time_to_send_1s_data == false) {
can_message_batch_index = 0;
}
}
}

View file

@ -7,6 +7,10 @@
#include "AFORE-CAN.h"
#endif
#ifdef BYD_CAN_DEYE
#define BYD_CAN
#endif
#ifdef BYD_CAN
#include "BYD-CAN.h"
#endif

View file

@ -363,6 +363,8 @@ void receive_RS485() // Runs as fast as possible to handle the serial stream
tmpframe[38] = calculate_kostal_crc(tmpframe, 38);
null_stuffer(tmpframe, 40);
send_kostal(tmpframe, 40);
datalayer.system.status.inverter_allows_contactor_closing = true;
dbg_message("inverter_allows_contactor_closing (battery_info) -> true");
if (!startupMillis) {
startupMillis = currentMillis;
}

View file

@ -117,10 +117,10 @@ void update_values_can_inverter() { //This function maps all the values fetched
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
// Batteries might be larger than uint16_t value can take
if (datalayer.battery.info.total_capacity_Wh > 65000) {
if (datalayer.battery.info.reported_total_capacity_Wh > 65000) {
capped_capacity_Wh = 65000;
} else {
capped_capacity_Wh = datalayer.battery.info.total_capacity_Wh;
capped_capacity_Wh = datalayer.battery.info.reported_total_capacity_Wh;
}
// Batteries might be larger than uint16_t value can take
if (datalayer.battery.status.reported_remaining_capacity_Wh > 65000) {
@ -187,10 +187,10 @@ void update_values_can_inverter() { //This function maps all the values fetched
SOLAX_1878.data.u8[0] = (uint8_t)(datalayer.battery.status.voltage_dV);
SOLAX_1878.data.u8[1] = ((datalayer.battery.status.voltage_dV) >> 8);
SOLAX_1878.data.u8[4] = (uint8_t)datalayer.battery.info.total_capacity_Wh;
SOLAX_1878.data.u8[5] = (datalayer.battery.info.total_capacity_Wh >> 8);
SOLAX_1878.data.u8[6] = (datalayer.battery.info.total_capacity_Wh >> 16);
SOLAX_1878.data.u8[7] = (datalayer.battery.info.total_capacity_Wh >> 24);
SOLAX_1878.data.u8[4] = (uint8_t)datalayer.battery.info.reported_total_capacity_Wh;
SOLAX_1878.data.u8[5] = (datalayer.battery.info.reported_total_capacity_Wh >> 8);
SOLAX_1878.data.u8[6] = (datalayer.battery.info.reported_total_capacity_Wh >> 16);
SOLAX_1878.data.u8[7] = (datalayer.battery.info.reported_total_capacity_Wh >> 24);
// BMS_Answer
SOLAX_1801.data.u8[0] = 2;
@ -198,10 +198,11 @@ void update_values_can_inverter() { //This function maps all the values fetched
SOLAX_1801.data.u8[4] = 1;
//Ultra messages
SOLAX_187E.data.u8[0] = (uint8_t)datalayer.battery.info.total_capacity_Wh;
SOLAX_187E.data.u8[1] = (datalayer.battery.info.total_capacity_Wh >> 8);
SOLAX_187E.data.u8[2] = (datalayer.battery.info.total_capacity_Wh >> 16);
SOLAX_187E.data.u8[3] = (datalayer.battery.info.total_capacity_Wh >> 24);
SOLAX_187E.data.u8[0] = (uint8_t)datalayer.battery.info.reported_total_capacity_Wh;
SOLAX_187E.data.u8[1] = (datalayer.battery.info.reported_total_capacity_Wh >> 8);
SOLAX_187E.data.u8[2] = (datalayer.battery.info.reported_total_capacity_Wh >> 16);
SOLAX_187E.data.u8[3] = (datalayer.battery.info.reported_total_capacity_Wh >> 24);
SOLAX_187E.data.u8[4] = (uint8_t)(datalayer.battery.status.soh_pptt / 100);
SOLAX_187E.data.u8[5] = (uint8_t)(datalayer.battery.status.reported_soc / 100);
}

View file

@ -36,30 +36,7 @@
#ifndef ADAFRUIT_NEOPIXEL_H
#define ADAFRUIT_NEOPIXEL_H
#ifdef ARDUINO
#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#include <pins_arduino.h>
#endif
#ifdef USE_TINYUSB // For Serial when selecting TinyUSB
#include <Adafruit_TinyUSB.h>
#endif
#endif
#ifdef TARGET_LPC1768
#include <Arduino.h>
#endif
#if defined(ARDUINO_ARCH_RP2040)
#include <stdlib.h>
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "rp2040_pio.h"
#endif
// The order of primary colors in the NeoPixel data stream can vary among
// device types, manufacturers and even different revisions of the same
@ -95,36 +72,6 @@
#define NEO_BRG ((1 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G
#define NEO_BGR ((2 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R
// RGBW NeoPixel permutations; all 4 offsets are distinct
// Offset: W R G B
#define NEO_WRGB ((0 << 6) | (1 << 4) | (2 << 2) | (3)) ///< Transmit as W,R,G,B
#define NEO_WRBG ((0 << 6) | (1 << 4) | (3 << 2) | (2)) ///< Transmit as W,R,B,G
#define NEO_WGRB ((0 << 6) | (2 << 4) | (1 << 2) | (3)) ///< Transmit as W,G,R,B
#define NEO_WGBR ((0 << 6) | (3 << 4) | (1 << 2) | (2)) ///< Transmit as W,G,B,R
#define NEO_WBRG ((0 << 6) | (2 << 4) | (3 << 2) | (1)) ///< Transmit as W,B,R,G
#define NEO_WBGR ((0 << 6) | (3 << 4) | (2 << 2) | (1)) ///< Transmit as W,B,G,R
#define NEO_RWGB ((1 << 6) | (0 << 4) | (2 << 2) | (3)) ///< Transmit as R,W,G,B
#define NEO_RWBG ((1 << 6) | (0 << 4) | (3 << 2) | (2)) ///< Transmit as R,W,B,G
#define NEO_RGWB ((2 << 6) | (0 << 4) | (1 << 2) | (3)) ///< Transmit as R,G,W,B
#define NEO_RGBW ((3 << 6) | (0 << 4) | (1 << 2) | (2)) ///< Transmit as R,G,B,W
#define NEO_RBWG ((2 << 6) | (0 << 4) | (3 << 2) | (1)) ///< Transmit as R,B,W,G
#define NEO_RBGW ((3 << 6) | (0 << 4) | (2 << 2) | (1)) ///< Transmit as R,B,G,W
#define NEO_GWRB ((1 << 6) | (2 << 4) | (0 << 2) | (3)) ///< Transmit as G,W,R,B
#define NEO_GWBR ((1 << 6) | (3 << 4) | (0 << 2) | (2)) ///< Transmit as G,W,B,R
#define NEO_GRWB ((2 << 6) | (1 << 4) | (0 << 2) | (3)) ///< Transmit as G,R,W,B
#define NEO_GRBW ((3 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B,W
#define NEO_GBWR ((2 << 6) | (3 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,W,R
#define NEO_GBRW ((3 << 6) | (2 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,R,W
#define NEO_BWRG ((1 << 6) | (2 << 4) | (3 << 2) | (0)) ///< Transmit as B,W,R,G
#define NEO_BWGR ((1 << 6) | (3 << 4) | (2 << 2) | (0)) ///< Transmit as B,W,G,R
#define NEO_BRWG ((2 << 6) | (1 << 4) | (3 << 2) | (0)) ///< Transmit as B,R,W,G
#define NEO_BRGW ((3 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G,W
#define NEO_BGWR ((2 << 6) | (3 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,W,R
#define NEO_BGRW ((3 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R,W
// Add NEO_KHZ400 to the color order value to indicate a 400 KHz device.
// All but the earliest v1 NeoPixels expect an 800 KHz data stream, this is
// the default if unspecified. Because flash space is very limited on ATtiny
@ -133,89 +80,9 @@
// but code will be bigger. Conversely, can disable the NEO_KHZ400 line on
// other MCUs to remove v1 support and save a little space.
#define NEO_KHZ800 0x0000 ///< 800 KHz data transmission
#ifndef __AVR_ATtiny85__
#define NEO_KHZ400 0x0100 ///< 400 KHz data transmission
#endif
// If 400 KHz support is enabled, the third parameter to the constructor
// requires a 16-bit value (in order to select 400 vs 800 KHz speed).
// If only 800 KHz is enabled (as is default on ATtiny), an 8-bit value
// is sufficient to encode pixel color order, saving some space.
#ifdef NEO_KHZ400
typedef uint16_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor
#else
typedef uint8_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor
#endif
// These two tables are declared outside the Adafruit_NeoPixel class
// because some boards may require oldschool compilers that don't
// handle the C++11 constexpr keyword.
/* A PROGMEM (flash mem) table containing 8-bit unsigned sine wave (0-255).
Copy & paste this snippet into a Python REPL to regenerate:
import math
for x in range(256):
print("{:3},".format(int((math.sin(x/128.0*math.pi)+1.0)*127.5+0.5))),
if x&15 == 15: print
*/
static const uint8_t PROGMEM _NeoPixelSineTable[256] = {
128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 162, 165, 167, 170,
173, 176, 179, 182, 185, 188, 190, 193, 196, 198, 201, 203, 206, 208, 211,
213, 215, 218, 220, 222, 224, 226, 228, 230, 232, 234, 235, 237, 238, 240,
241, 243, 244, 245, 246, 248, 249, 250, 250, 251, 252, 253, 253, 254, 254,
254, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 253, 253, 252, 251,
250, 250, 249, 248, 246, 245, 244, 243, 241, 240, 238, 237, 235, 234, 232,
230, 228, 226, 224, 222, 220, 218, 215, 213, 211, 208, 206, 203, 201, 198,
196, 193, 190, 188, 185, 182, 179, 176, 173, 170, 167, 165, 162, 158, 155,
152, 149, 146, 143, 140, 137, 134, 131, 128, 124, 121, 118, 115, 112, 109,
106, 103, 100, 97, 93, 90, 88, 85, 82, 79, 76, 73, 70, 67, 65,
62, 59, 57, 54, 52, 49, 47, 44, 42, 40, 37, 35, 33, 31, 29,
27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, 10, 9, 7, 6,
5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 9, 10, 11,
12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37,
40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76,
79, 82, 85, 88, 90, 93, 97, 100, 103, 106, 109, 112, 115, 118, 121,
124};
/* Similar to above, but for an 8-bit gamma-correction table.
Copy & paste this snippet into a Python REPL to regenerate:
import math
gamma=2.6
for x in range(256):
print("{:3},".format(int(math.pow((x)/255.0,gamma)*255.0+0.5))),
if x&15 == 15: print
*/
static const uint8_t PROGMEM _NeoPixelGammaTable[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3,
3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6,
6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17,
17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 34, 34, 35,
36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 68, 69, 70, 71, 72, 73, 75, 76, 77, 78, 80, 81,
82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96, 97, 99, 100, 102,
103, 105, 106, 108, 109, 111, 112, 114, 115, 117, 119, 120, 122, 124, 125,
127, 129, 130, 132, 134, 136, 137, 139, 141, 143, 145, 146, 148, 150, 152,
154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182,
184, 186, 188, 191, 193, 195, 197, 199, 202, 204, 206, 209, 211, 213, 215,
218, 220, 223, 225, 227, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252,
255};
/* Declare external methods required by the Adafruit_NeoPixel implementation
for specific hardware/library versions
*/
#if defined(ESP32)
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
extern "C" void espInit();
#endif
#endif
/*!
@brief Class that stores state and functions for interacting with
@ -226,7 +93,7 @@
public:
// Constructor: number of LEDs, pin number, LED type
Adafruit_NeoPixel(uint16_t n, int16_t pin = 6,
neoPixelType type = NEO_GRB + NEO_KHZ800);
neoPixelType type = NEO_GRB);
Adafruit_NeoPixel(void);
~Adafruit_NeoPixel();
@ -236,7 +103,6 @@
void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b);
void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w);
void setPixelColor(uint16_t n, uint32_t c);
void fill(uint32_t c = 0, uint16_t first = 0, uint16_t count = 0);
void setBrightness(uint8_t);
void clear(void);
void updateLength(uint16_t n);
@ -276,22 +142,7 @@
}
return (now - endTime) >= 300L;
}
/*!
@brief Get a pointer directly to the NeoPixel data buffer in RAM.
Pixel data is stored in a device-native format (a la the NEO_*
constants) and is not translated here. Applications that access
this buffer will need to be aware of the specific data format
and handle colors appropriately.
@return Pointer to NeoPixel buffer (uint8_t* array).
@note This is for high-performance applications where calling
setPixelColor() on every single pixel would be too slow (e.g.
POV or light-painting projects). There is no bounds checking
on the array, creating tremendous potential for mayhem if one
writes past the ends of the buffer. Great power, great
responsibility and all that.
*/
uint8_t *getPixels(void) const { return pixels; };
uint8_t getBrightness(void) const;
/*!
@brief Retrieve the pin number used for NeoPixel data output.
@return Arduino pin number (-1 if not set).
@ -302,36 +153,7 @@
@return Pixel count (0 if not set).
*/
uint16_t numPixels(void) const { return numLEDs; }
uint32_t getPixelColor(uint16_t n) const;
/*!
@brief An 8-bit integer sine wave function, not directly compatible
with standard trigonometric units like radians or degrees.
@param x Input angle, 0-255; 256 would loop back to zero, completing
the circle (equivalent to 360 degrees or 2 pi radians).
One can therefore use an unsigned 8-bit variable and simply
add or subtract, allowing it to overflow/underflow and it
still does the expected contiguous thing.
@return Sine result, 0 to 255, or -128 to +127 if type-converted to
a signed int8_t, but you'll most likely want unsigned as this
output is often used for pixel brightness in animation effects.
*/
static uint8_t sine8(uint8_t x) {
return pgm_read_byte(&_NeoPixelSineTable[x]); // 0-255 in, 0-255 out
}
/*!
@brief An 8-bit gamma-correction function for basic pixel brightness
adjustment. Makes color transitions appear more perceptially
correct.
@param x Input brightness, 0 (minimum or off/black) to 255 (maximum).
@return Gamma-adjusted brightness, can then be passed to one of the
setPixelColor() functions. This uses a fixed gamma correction
exponent of 2.6, which seems reasonably okay for average
NeoPixels in average tasks. If you need finer control you'll
need to provide your own gamma-correction function instead.
*/
static uint8_t gamma8(uint8_t x) {
return pgm_read_byte(&_NeoPixelGammaTable[x]); // 0-255 in, 0-255 out
}
/*!
@brief Convert separate red, green and blue values into a single
"packed" 32-bit RGB color.
@ -361,37 +183,10 @@
static uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
return ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
static uint32_t ColorHSV(uint16_t hue, uint8_t sat = 255, uint8_t val = 255);
/*!
@brief A gamma-correction function for 32-bit packed RGB or WRGB
colors. Makes color transitions appear more perceptially
correct.
@param x 32-bit packed RGB or WRGB color.
@return Gamma-adjusted packed color, can then be passed in one of the
setPixelColor() functions. Like gamma8(), this uses a fixed
gamma correction exponent of 2.6, which seems reasonably okay
for average NeoPixels in average tasks. If you need finer
control you'll need to provide your own gamma-correction
function instead.
*/
static uint32_t gamma32(uint32_t x);
void rainbow(uint16_t first_hue = 0, int8_t reps = 1,
uint8_t saturation = 255, uint8_t brightness = 255,
bool gammify = true);
static neoPixelType str2order(const char *v);
private:
#if defined(ARDUINO_ARCH_RP2040)
void rp2040Init(uint8_t pin, bool is800KHz);
void rp2040Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHz);
#endif
protected:
#ifdef NEO_KHZ400 // If 400 KHz NeoPixel support enabled...
bool is800KHz; ///< true if 800 KHz pixels
#endif
bool begun; ///< true if begin() previously called
uint16_t numLEDs; ///< Number of RGB LEDs in strip
uint16_t numBytes; ///< Size of 'pixels' buffer below
@ -403,20 +198,6 @@
uint8_t bOffset; ///< Index of blue byte
uint8_t wOffset; ///< Index of white (==rOffset if no white)
uint32_t endTime; ///< Latch timing reference
#ifdef __AVR__
volatile uint8_t *port; ///< Output PORT register
uint8_t pinMask; ///< Output PORT bitmask
#endif
#if defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32) || defined(ARDUINO_ARCH_CH32)
GPIO_TypeDef *gpioPort; ///< Output GPIO PORT
uint32_t gpioPin; ///< Output GPIO PIN
#endif
#if defined(ARDUINO_ARCH_RP2040)
PIO pio = pio0;
int sm = 0;
bool init = true;
#endif
};
#endif // ADAFRUIT_NEOPIXEL_H

View file

@ -17,113 +17,9 @@
* limitations under the License.
*/
#if defined(ESP32)
#include <Arduino.h>
#if defined(ESP_IDF_VERSION)
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
#define HAS_ESP_IDF_4
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define HAS_ESP_IDF_5
#endif
#endif
#ifdef HAS_ESP_IDF_5
static SemaphoreHandle_t show_mutex = NULL;
void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) {
// Note: Because rmtPin is shared between all instances, we will
// end up releasing/initializing the RMT channels each time we
// invoke on different pins. This is probably ok, just not
// efficient. led_data is shared between all instances but will
// be allocated with enough space for the largest instance; data
// is not used beyond the mutex lock so this should be fine.
#define SEMAPHORE_TIMEOUT_MS 50
static rmt_data_t *led_data = NULL;
static uint32_t led_data_size = 0;
static int rmtPin = -1;
if (show_mutex && xSemaphoreTake(show_mutex, SEMAPHORE_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) {
uint32_t requiredSize = numBytes * 8;
if (requiredSize > led_data_size) {
free(led_data);
if (led_data = (rmt_data_t *)malloc(requiredSize * sizeof(rmt_data_t))) {
led_data_size = requiredSize;
} else {
led_data_size = 0;
}
} else if (requiredSize == 0) {
// To release RMT resources (RMT channels and led_data), call
// .updateLength(0) to set number of pixels/bytes to zero,
// then call .show() to invoke this code and free resources.
free(led_data);
led_data = NULL;
if (rmtPin >= 0) {
rmtDeinit(rmtPin);
rmtPin = -1;
}
led_data_size = 0;
}
if (led_data_size > 0 && requiredSize <= led_data_size) {
if (pin != rmtPin) {
if (rmtPin >= 0) {
rmtDeinit(rmtPin);
rmtPin = -1;
}
if (!rmtInit(pin, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000)) {
log_e("Failed to init RMT TX mode on pin %d", pin);
return;
}
rmtPin = pin;
}
if (rmtPin >= 0) {
int i=0;
for (int b=0; b < numBytes; b++) {
for (int bit=0; bit<8; bit++){
if ( pixels[b] & (1<<(7-bit)) ) {
led_data[i].level0 = 1;
led_data[i].duration0 = 8;
led_data[i].level1 = 0;
led_data[i].duration1 = 4;
} else {
led_data[i].level0 = 1;
led_data[i].duration0 = 4;
led_data[i].level1 = 0;
led_data[i].duration1 = 8;
}
i++;
}
}
rmtWrite(pin, led_data, numBytes * 8, RMT_WAIT_FOR_EVER);
}
}
xSemaphoreGive(show_mutex);
}
}
// To avoid race condition initializing the mutex, all instances of
// Adafruit_NeoPixel must be constructed before launching and child threads
void espInit() {
if (!show_mutex) {
show_mutex = xSemaphoreCreateMutex();
}
}
#else
#include "driver/rmt.h"
// This code is adapted from the ESP-IDF v3.4 RMT "led_strip" example, altered
// to work with the Arduino version of the ESP-IDF (3.2)
@ -132,29 +28,13 @@
#define WS2812_T1H_NS (800)
#define WS2812_T1L_NS (450)
#define WS2811_T0H_NS (500)
#define WS2811_T0L_NS (2000)
#define WS2811_T1H_NS (1200)
#define WS2811_T1L_NS (1300)
static uint32_t t0h_ticks = 0;
static uint32_t t1h_ticks = 0;
static uint32_t t0l_ticks = 0;
static uint32_t t1l_ticks = 0;
// Limit the number of RMT channels available for the Neopixels. Defaults to all
// channels (8 on ESP32, 4 on ESP32-S2 and S3). Redefining this value will free
// any channels with a higher number for other uses, such as IR send-and-recieve
// libraries. Redefine as 1 to restrict Neopixels to only a single channel.
#define ADAFRUIT_RMT_CHANNEL_MAX RMT_CHANNEL_MAX
#define RMT_LL_HW_BASE (&RMT)
bool rmt_reserved_channels[ADAFRUIT_RMT_CHANNEL_MAX];
static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
size_t wanted_num, size_t *translated_size, size_t *item_num)
{
static void IRAM_ATTR ws2812_rmt_adapter(const void* src, rmt_item32_t* dest, size_t src_size, size_t wanted_num,
size_t* translated_size, size_t* item_num) {
if (src == NULL || dest == NULL) {
*translated_size = 0;
*item_num = 0;
@ -185,69 +65,30 @@
}
static bool rmt_initialized = false;
static bool rmt_adapter_initialized = false;
void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) {
void espShow(uint8_t pin, uint8_t* pixels, uint32_t numBytes) {
if (rmt_initialized == false) {
// Reserve channel
rmt_channel_t channel = 0;
#if defined(HAS_ESP_IDF_4)
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel);
config.clk_div = 2;
#else
// Match default TX config from ESP-IDF version 3.4
rmt_config_t config = {
.rmt_mode = RMT_MODE_TX,
.channel = channel,
.gpio_num = pin,
.clk_div = 2,
.mem_block_num = 1,
.tx_config = {
.carrier_freq_hz = 38000,
.carrier_level = RMT_CARRIER_LEVEL_HIGH,
.idle_level = RMT_IDLE_LEVEL_LOW,
.carrier_duty_percent = 33,
.carrier_en = false,
.loop_en = false,
.idle_output_en = true,
}
};
#endif
rmt_config(&config);
rmt_driver_install(config.channel, 0, 0);
// Convert NS timings to ticks
uint32_t counter_clk_hz = 0;
#if defined(HAS_ESP_IDF_4)
rmt_get_counter_clock(channel, &counter_clk_hz);
#else
// this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4
if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == RMT_BASECLK_REF) {
uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
uint32_t div = div_cnt == 0 ? 256 : div_cnt;
counter_clk_hz = REF_CLK_FREQ / (div);
} else {
uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
uint32_t div = div_cnt == 0 ? 256 : div_cnt;
counter_clk_hz = APB_CLK_FREQ / (div);
}
#endif
// NS to tick converter
float ratio = (float)counter_clk_hz / 1e9;
if (is800KHz) {
t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
} else {
t0h_ticks = (uint32_t)(ratio * WS2811_T0H_NS);
t0l_ticks = (uint32_t)(ratio * WS2811_T0L_NS);
t1h_ticks = (uint32_t)(ratio * WS2811_T1H_NS);
t1l_ticks = (uint32_t)(ratio * WS2811_T1L_NS);
}
// Initialize automatic timing translator
rmt_translator_init(0, ws2812_rmt_adapter);
@ -256,17 +97,4 @@
// Write and wait to finish
rmt_write_sample(0, pixels, (size_t)numBytes, false);
//rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100));
// Free channel again
//rmt_driver_uninstall(config.channel);
//rmt_reserved_channels[channel] = false;
//gpio_set_direction(pin, GPIO_MODE_OUTPUT);
}
#endif // ifndef IDF5
#endif // ifdef(ESP32)