mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
Merge from main and fix conflicts
This commit is contained in:
commit
d8534b8fa0
71 changed files with 2202 additions and 1067 deletions
120
.github/workflows/compile-all-batteries.yml
vendored
120
.github/workflows/compile-all-batteries.yml
vendored
|
@ -1,120 +0,0 @@
|
|||
# This is the name of the workflow, visible on GitHub UI.
|
||||
name: 🔋 Compile All Batteries
|
||||
|
||||
# Here we tell GitHub when to run the workflow.
|
||||
on:
|
||||
# The workflow is run when a commit is pushed or for a
|
||||
# Pull Request.
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# 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.
|
||||
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.
|
||||
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
|
||||
- DALY_BMS
|
||||
- FOXESS_BATTERY
|
||||
- GEELY_GEOMETRY_C_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
|
||||
- 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:
|
||||
- BYD_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.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software
|
||||
|
291
.github/workflows/compile-all-combinations.yml
vendored
291
.github/workflows/compile-all-combinations.yml
vendored
|
@ -1,291 +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: [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.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software
|
|
@ -1,94 +0,0 @@
|
|||
# This is the name of the workflow, visible on GitHub UI.
|
||||
name: 🔋🔋 Compile All Double Batteries
|
||||
|
||||
# Here we tell GitHub when to run the workflow.
|
||||
on:
|
||||
# The workflow is run when a commit is pushed or for a
|
||||
# Pull Request.
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# 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.
|
||||
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.
|
||||
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
|
||||
- BYD_ATTO_3_BATTERY
|
||||
- KIA_HYUNDAI_64_BATTERY
|
||||
- PYLON_BATTERY
|
||||
- SANTA_FE_PHEV_BATTERY
|
||||
- TEST_FAKE_BATTERY
|
||||
# These are the emulated inverter communication protocols for which the code will be compiled.
|
||||
inverter:
|
||||
- BYD_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.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -DDOUBLE_BATTERY -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software
|
||||
|
108
.github/workflows/compile-all-inverters.yml
vendored
108
.github/workflows/compile-all-inverters.yml
vendored
|
@ -1,108 +0,0 @@
|
|||
# This is the name of the workflow, visible on GitHub UI.
|
||||
name: 🔌 Compile All Inverters
|
||||
|
||||
# Here we tell GitHub when to run the workflow.
|
||||
on:
|
||||
# The workflow is run when a commit is pushed or for a
|
||||
# Pull Request.
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# 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-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.
|
||||
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
|
||||
# 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
|
||||
- NISSANLEAF_CHARGER # Last element is not an inverter, but good to also test if the charger compiles
|
||||
# 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.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -D${{ matrix.battery}} -D${{ matrix.inverter}} -D${{ matrix.hardware}}" ./Software
|
|
@ -1,58 +0,0 @@
|
|||
# This is the name of the workflow, visible on GitHub UI.
|
||||
name: 🔋 Compile Common Image for Lilygo with debug logging
|
||||
|
||||
# Here we tell GitHub when to run the workflow.
|
||||
on:
|
||||
# The workflow is run when a commit is pushed or for a
|
||||
# Pull Request.
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# 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"]'
|
||||
|
||||
build-common-image:
|
||||
# This is the platform GitHub will use to run our workflow.
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- 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
|
||||
|
||||
- name: Compile Sketch
|
||||
run: arduino-cli compile --output-dir ./ --fqbn esp32:esp32:esp32 --build-property build.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -DDEBUG_VIA_USB -DCOMMON_IMAGE -DHW_LILYGO" ./Software
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: battery-emulator-lilygo-debug.bin
|
||||
path: Software.ino.bin
|
|
@ -1,7 +1,5 @@
|
|||
# This is the name of the workflow, visible on GitHub UI.
|
||||
name: 🔋 Compile Common Image for Lilygo
|
||||
|
||||
# Here we tell GitHub when to run the workflow.
|
||||
on:
|
||||
# The workflow is run when a commit is pushed or for a
|
||||
# Pull Request.
|
||||
|
@ -30,29 +28,31 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
name: Checkout code
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pip
|
||||
~/.platformio/.cache
|
||||
key: ${{ runner.os }}-pio
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install PlatformIO Core
|
||||
run: pip install --upgrade platformio
|
||||
|
||||
# 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
|
||||
- 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
|
||||
- name: Build image for Lilygo
|
||||
run: pio run -e lilygo_330
|
||||
|
||||
# We then install the platform.
|
||||
- name: Install platform
|
||||
run: |
|
||||
arduino-cli core update-index
|
||||
arduino-cli core install esp32:esp32
|
||||
|
||||
- name: Compile Sketch
|
||||
run: arduino-cli compile --output-dir ./ --fqbn esp32:esp32:esp32 --build-property build.partitions=min_spiffs --build-property upload.maximum_size=1966080 --build-property "build.extra_flags=-Wall -Wextra -Wpedantic -Werror -DESP32 -DCOMMON_IMAGE -DHW_LILYGO" ./Software
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: battery-emulator-lilygo.bin
|
||||
path: Software.ino.bin
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: battery-emulator-lilygo.bin
|
||||
path: .pio/build/lilygo_330/firmware.bin
|
||||
|
|
58
.github/workflows/compile-common-image-stark.yml
vendored
Normal file
58
.github/workflows/compile-common-image-stark.yml
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
name: 🔋 Compile Common Image for Stark CMR
|
||||
|
||||
on:
|
||||
# The workflow is run when a commit is pushed or for a
|
||||
# Pull Request.
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# 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"]'
|
||||
|
||||
build-common-image:
|
||||
# This is the platform GitHub will use to run our workflow.
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
name: Checkout code
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pip
|
||||
~/.platformio/.cache
|
||||
key: ${{ runner.os }}-pio
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install PlatformIO Core
|
||||
run: pip install --upgrade platformio
|
||||
|
||||
# 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
|
||||
|
||||
- name: Build image for Stark CMR
|
||||
run: pio run -e stark_330
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: battery-emulator-stark.bin
|
||||
path: .pio/build/stark_330/firmware.bin
|
|
@ -2,7 +2,7 @@
|
|||

|
||||

|
||||

|
||||

|
||||

|
||||

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