From 465046b74311e0fe6ae38a98214d140563edec8d Mon Sep 17 00:00:00 2001 From: eternal-echo Date: Thu, 28 Aug 2025 18:47:32 +0800 Subject: [PATCH 1/3] feat: Add isotp-c library Imported the isotp-c library code from a specific commit of 083709f57d8fc964e7ac5dd0c7ac7aef6364f156. https://github.com/lishen2/isotp-c https://github.com/SimonCahill/isotp-c --- .github/ISSUE_TEMPLATE/bug-report.yml | 1 + .github/setup_qemu.sh | 2 +- .github/workflows/upload_component.yml | 1 + .idf_build_apps.toml | 1 + esp_isotp/.build-test-rules.yml | 0 esp_isotp/isotp-c/CMakeLists.txt | 119 +++++ esp_isotp/isotp-c/CONTRIBUTORS.md | 20 + esp_isotp/isotp-c/LICENSE | 22 + esp_isotp/isotp-c/README.md | 384 ++++++++++++++ esp_isotp/isotp-c/isotp.c | 695 +++++++++++++++++++++++++ esp_isotp/isotp-c/isotp.h | 184 +++++++ esp_isotp/isotp-c/isotp_config.h | 58 +++ esp_isotp/isotp-c/isotp_defines.h | 274 ++++++++++ esp_isotp/isotp-c/isotp_user.h | 42 ++ 14 files changed, 1802 insertions(+), 1 deletion(-) create mode 100644 esp_isotp/.build-test-rules.yml create mode 100755 esp_isotp/isotp-c/CMakeLists.txt create mode 100644 esp_isotp/isotp-c/CONTRIBUTORS.md create mode 100755 esp_isotp/isotp-c/LICENSE create mode 100644 esp_isotp/isotp-c/README.md create mode 100755 esp_isotp/isotp-c/isotp.c create mode 100755 esp_isotp/isotp-c/isotp.h create mode 100755 esp_isotp/isotp-c/isotp_config.h create mode 100755 esp_isotp/isotp-c/isotp_defines.h create mode 100755 esp_isotp/isotp-c/isotp_user.h diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 5206687b8a..3ebddcdc95 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -32,6 +32,7 @@ body: - esp_delta_ota - esp_encrypted_img - esp_gcov + - esp_isotp - esp_jpeg - esp_lcd_qemu_rgb - esp_serial_slave_link diff --git a/.github/setup_qemu.sh b/.github/setup_qemu.sh index 06d023e3b3..5bb771d0b8 100755 --- a/.github/setup_qemu.sh +++ b/.github/setup_qemu.sh @@ -3,7 +3,7 @@ set -e # QEMU version and date string for easy maintenance QEMU_VERSION="9.2.2" -QEMU_DATE="20250228" +QEMU_DATE="20250817" QEMU_RELEASE="esp-develop-${QEMU_VERSION}-${QEMU_DATE}" # 1. Detect host architecture diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index 0e66f76d80..3be1932f83 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -44,6 +44,7 @@ jobs: esp_delta_ota; esp_encrypted_img; esp_gcov; + esp_isotp; esp_lcd_qemu_rgb; esp_jpeg; esp_serial_slave_link; diff --git a/.idf_build_apps.toml b/.idf_build_apps.toml index 94d7ab73f5..140b580e17 100644 --- a/.idf_build_apps.toml +++ b/.idf_build_apps.toml @@ -13,6 +13,7 @@ manifest_file = [ "esp_serial_slave_link/.build-test-rules.yml", "expat/.build-test-rules.yml", "iqmath/.build-test-rules.yml", + "esp_isotp/.build-test-rules.yml", "jsmn/.build-test-rules.yml", "json_generator/.build-test-rules.yml", "json_parser/.build-test-rules.yml", diff --git a/esp_isotp/.build-test-rules.yml b/esp_isotp/.build-test-rules.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esp_isotp/isotp-c/CMakeLists.txt b/esp_isotp/isotp-c/CMakeLists.txt new file mode 100755 index 0000000000..f1c1eb5a03 --- /dev/null +++ b/esp_isotp/isotp-c/CMakeLists.txt @@ -0,0 +1,119 @@ +cmake_minimum_required(VERSION 3.10) + +### +# Project definition +### +project(isotp LANGUAGES C VERSION 1.6.0 DESCRIPTION "A platform-agnostic ISOTP implementation in C for embedded devices.") + +option(isotpc_USE_INCLUDE_DIR "Copy header files to separate include directory in current binary dir for better separation of header files to combat potential naming conflicts." OFF) +option(isotpc_STATIC_LIBRARY "Compile libisotpc as a static library, instead of a shared library." OFF) +option(isotpc_STATIC_LIBRARY_PIC "Make use of position independent code (PIC), when compiling as a static library (enabled automatically when building shared libraries)." OFF) +option(isotpc_PAD_CAN_FRAMES "Pad CAN frames to their full size." ON) +set(isotpc_CAN_FRAME_PAD_VALUE "0xAA" CACHE STRING "Padding byte value to be used in CAN frames if enabled") +option(isotpc_ENABLE_CAN_SEND_ARG "Adds an extra argument to isotp_user_send_can to better support multiple CAN interfaces." OFF) +option(isotpc_ENABLE_TRANSCEIVE_EVENTS "Enable events/callbacks for transmission and reception complete." OFF) +option(isotpc_ENABLE_TRANSMIT_COMPLETE_CALLBACK "Enable transmit complete callback." ON) # These can be disabled separately, as they are controlled by the main event option +option(isotpc_ENABLE_RECEIVE_COMPLETE_CALLBACK "Enable receive complete callback." ON) # These can be disabled separately, as they are controlled by the main event option +# option(isotpc_ENABLE_TESTING "Enable building of test suite." OFF) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if (isotpc_STATIC_LIBRARY OR MSVC) + add_library(isotp STATIC ${CMAKE_CURRENT_SOURCE_DIR}/isotp.c) +else() + add_library(isotp SHARED ${CMAKE_CURRENT_SOURCE_DIR}/isotp.c) +endif() + +### +# Strict building policies +### +if (MSVC) + target_compile_options( + isotp PRIVATE + /W4 + ) +else() + target_compile_options( + isotp PRIVATE + -Werror + -Wall + -Wpedantic + -Wextra + -Wno-unknown-pragmas # ignore unknown pragmas, such as #pragma region + ) +endif() + +### +# Enable position independent code (PIC) if required +### +if (NOT isotpc_STATIC_LIBRARY OR isotpc_STATIC_LIBRARY_PIC) + # Building a shared library or a static library with PIC enabled + target_compile_options(isotp PRIVATE -fPIC) +endif() + +### +# Provide padding configuration +### +if (isotpc_PAD_CAN_FRAMES) + target_compile_definitions(isotp PRIVATE -DISO_TP_FRAME_PADDING -DISO_TP_FRAME_PADDING_VALUE=${isotpc_CAN_FRAME_PAD_VALUE}) +endif() + +### +# Include additional arg in isotp_user_send_can if required +### +if (isotpc_ENABLE_CAN_SEND_ARG) + target_compile_options(isotp PRIVATE -DISO_TP_USER_SEND_CAN_ARG) +endif() + +### +# Add support for options added in #51 +### +if (isotpc_ENABLE_TRANSCEIVE_EVENTS) + message(STATUS "[isotp-c] Enabling ISOTP events; backwards compatibility may BREAK!") + if (isotpc_ENABLE_TRANSMIT_COMPLETE_CALLBACK) + target_compile_definitions(isotp PRIVATE -DISO_TP_TRANSMIT_COMPLETE_CALLBACK) + endif() + + if (isotpc_ENABLE_RECEIVE_COMPLETE_CALLBACK) + target_compile_definitions(isotp PRIVATE -DISO_TP_RECEIVE_COMPLETE_CALLBACK) + endif() +endif() + +### +# Check for debug builds +### +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_options(isotp PRIVATE -O0 -g) # don't optimise and add debugging symbols +else() + target_compile_options(isotp PRIVATE -O2) # optimise code + target_link_libraries(isotp PRIVATE -s) # strip the library +endif() + +if (isotpc_USE_INCLUDE_DIR) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/isotp_c) + + file(GLOB HEADERS FOLLOW_SYMLINKS ${CMAKE_CURRENT_SOURCE_DIR}/*.h) + file(COPY ${HEADERS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/include/isotp_c/) + + target_include_directories(isotp PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/include) + +else() + target_include_directories(isotp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +endif() + +### +# Add library aliases +### +add_library(isotpc ALIAS isotp) +add_library(isotp_c ALIAS isotp) +add_library(simon_cahill::isotp ALIAS isotp) +add_library(simon_cahill::isotpc ALIAS isotp) +add_library(simon_cahill::isotp_c ALIAS isotp) + +### +# Enable testing +### +# if (isotpc_ENABLE_TESTING) +# enable_testing() +# add_subdirectory(test) +# endif() diff --git a/esp_isotp/isotp-c/CONTRIBUTORS.md b/esp_isotp/isotp-c/CONTRIBUTORS.md new file mode 100644 index 0000000000..68689b103d --- /dev/null +++ b/esp_isotp/isotp-c/CONTRIBUTORS.md @@ -0,0 +1,20 @@ +| Name and Profile | Contribution | PR | +|-----------------------------------------------------|------------------------------------------------------------------------------------------------|--------| +| [Li Shen](https://github.com/lishen2) | Original Author | | +| [Simon Cahill](https://github.com/SimonCahill) | CMake support, documentation improvements, code simplification, fork owner | | +| [Brandon Ros](https://github.com/brandonros) | Migrated timestamps from milliseconds to microseconds. | #12 | +| [Nick James Kirkby](https://github.com/driftregion) | Fixed include path warnings on Win64 | #13 | +| [Mark A Clark](https://github.com/maclark88) | Fixed documentation inconsistencies | #18 | +| [speedy-h](https://github.com/speedy-h) | Fixed compile- and link-time options | #23 | +| [Phil Greenland](https://github.com/pgreenland) | Added padding support in ISOTP messages | #24 | +| [Phil Greenland](https://github.com/pgreenland) | Added support for message retries if the remote controller was busy | #26 | +| [Phil Greenland](https://github.com/pgreenland) | Fixed PIC being generated for static binaries | #27 | +| [speedy-h](https://github.com/speedy-h) | Fixed compiler error when compiling with `-Wextra` caused by unused function params. | #32 | +| [kazetsukaimiko](https://github.com/kazetsukaimiko) | Added support for PlatformIO, a platform for distributing libraries for a variety of systems. | #35 | +| [driftregion](https://github.com/driftregion) | Added support for optional CAN arguments for adapter-specific information. | #36 | +| [andyneubacher](https://github.com/AndyNeubacher) | Added support for larger frame sizes (ISO15765-2:2016). | #46 | +| [mws262](https://github.com/mws262) | Fixed snprintf format string for embedded devices (replaced %d w/ %u) | #47 | +| [eternal-echo](https://github.com/eternal-echo) | Fixed minor issues with conditional compilation regarding user-args to can_send. | #50 | + +Thank you everyone for contributing to this library and improving it! +Have you contributed and I've forgotten to mention you? Please let me know and I'll add you here! diff --git a/esp_isotp/isotp-c/LICENSE b/esp_isotp/isotp-c/LICENSE new file mode 100755 index 0000000000..949e27ae93 --- /dev/null +++ b/esp_isotp/isotp-c/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2019-2024 Li Shen & Co-Operators +Copyright (c) 2024 Simon Cahill & Contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/esp_isotp/isotp-c/README.md b/esp_isotp/isotp-c/README.md new file mode 100644 index 0000000000..a08d0d05bc --- /dev/null +++ b/esp_isotp/isotp-c/README.md @@ -0,0 +1,384 @@ +ISO-TP (ISO 15765-2) Support Library in C +================================ + +**This project is inspired by [openxc isotp-c](https://github.com/openxc/isotp-c), but the code has been completely re-written.** + +This is a platform agnostic C library that implements the [ISO 15765-2](https://en.wikipedia.org/wiki/ISO_15765-2) (also known as ISO-TP) protocol, which runs over a CAN bus. Quoting Wikipedia: + +>ISO 15765-2, or ISO-TP, is an international standard for sending data packets over a CAN-Bus. +>The protocol allows for the transport of messages that exceed the eight byte maximum payload of CAN frames. +>ISO-TP segments longer messages into multiple frames, adding metadata that allows the interpretation of individual frames and reassembly +>into a complete message packet by the recipient. It can carry up to 4095 bytes of payload per message packet. + +This library doesn't assume anything about the source of the ISO-TP messages or the underlying interface to CAN. It uses dependency injection to give you complete control. + +**The current version supports [ISO-15765-2](https://en.wikipedia.org/wiki/ISO_15765-2) single and multiple frame transmission, and works in Full-duplex mode.** + +## Builds + +[![CMake](https://github.com/SimonCahill/isotp-c/actions/workflows/cmake.yml/badge.svg)](https://github.com/SimonCahill/isotp-c/actions/workflows/cmake.yml) +[![CMake w/ changes from #36](https://github.com/SimonCahill/isotp-c/actions/workflows/build-w-opt-can-arg.yml/badge.svg)](https://github.com/SimonCahill/isotp-c/actions/workflows/build-w-opt-can-arg.yml) +[![Windows MSVC Build](https://github.com/SimonCahill/isotp-c/actions/workflows/build-msvc.yml/badge.svg)](https://github.com/SimonCahill/isotp-c/actions/workflows/build-msvc.yml) + +## Contributors + +It's at this point where I'd like to point out all the fantastic contributions made to this fork by the amazing people using it! +[List of contributors](https://github.com/SimonCahill/isotp-c/blob/master/CONTRIBUTORS.md) + +Thank you all! + +## Building ISOTP-C + +This library may be built using either straight Makefiles, or using CMake. + +### make +To build this library using Make, simply call: + +```bash +$ make all +``` + +### CMake + +The CMake build system allows for more flexibility at generation and build time, so it is recommended you use this for building this library. +Of course, if your project does not use CMake, you don't *have* to use it. +If your projects use a different build system, you are more than welcome to include it in this repository. + +The Makefile generator for isotpc will automatically detect whether or not your build system is using the `Debug` or `Release` build type and will adjust compiler parameters accordingly. + +#### Debug Build +If your project is configured to build as `Debug`, then the library will be compiled with **no** optimisations and **with** debug symbols. +`-DCMAKE_BUILD_TYPE=Debug` + +#### Release Build +If your project is configured to build as `Release`, then the library code will be **optimised** using `-O2` and will be **stripped**. +`-DCMAKE_BUILD_TYPE=Release` + +#### External Include Directories +It is generally considered good practice to segregate header files from each other, depending on the project. For this reason, you may opt in to this behaviour for this library. + +If you pass `-Disotpc_USE_INCLUDE_DIR=ON` on the command-line, or you set `set(isotpc_USE_INCLUDE_DIR ON CACHE BOOL "Use external include dir for isotp-c")` in your CMakeLists.txt, then a separate `include/` directory will +be added to the project. +This happens at generation time, and the CMake project will automatically reference `${CMAKE_CURRENT_BINARY_DIR}/include` as the include directory for the project. This will be propagated to your projects, too. + +In your code: + +```c +// if -Disotpc_USE_INCLUDE_DIR=ON +#include + +// else +#include +``` + +#### Static Library +In some cases, it is required that a static library be used instead of a shared library. +isotp-c supports this also, via options. + +> ![NOTE] This option is enabled by default when building using MSVC. + +Either pass `-Disotpc_STATIC_LIBRARY=ON` via command-line or `set(isotpc_STATIC_LIBRARY ON CACHE BOOL "Enable static library for isotp-c")` in your CMakeLists.txt and the library will be built as a static library (`*.a|*.lib`) for your project to include. + +#### Use of multiple CAN interfaces +For applications requiring multiple CAN interfaces, it is necessary to specify the interface in `isotp_user_send_can`. + +In this case the config option `-DISO_TP_USER_SEND_CAN_ARG` may be enabled. The library may then be used as follows: + +```c +// Objects representing two CAN interfaces: a and b. +CAN_IFACE_t can_a, can_b; + +void init() { + // Two IsoTpLinks assumed to be bound to different CAN interfaces. + IsoTpLink link_a, link_b; + + isotp_init_link(&link_a, ...); + isotp_init_link(&link_b, ...); + + // After link initialization, the relevant CAN interface may be + // attached to the link. + link_a.user_send_can_arg = &can_a; + link_a.user_send_can_arg = &can_b; +} + +int isotp_user_send_can( + const uint32_t arbitration_id, + const uint8_t *data, + const uint8_t size, + void *user_send_can_arg) +{ + // It is then available for use inside isotp_user_send_can + int err = CAN_SEND((CAN_IFACE_t *)(user_send_can_arg), arbitration_id, data, size); + if (err) { + return ISOTP_RET_ERROR; + } else { + return ISOTP_RET_OK; + } +} + +``` + +#### Enable event-driven messaging + +Version 1.6.0 features a new event-driven messaging model, which is **disabled by default**. +In order to enable this feature, the following CMake option(s) must be passed: + +```cmake +set(isotpc_ENABLE_TRANSCEIVE_EVENTS ON CACHE BOOL "Enable message events in isotp-c") + +# Optionally enable/disable send/receive events: +# set(isotpc_ENABLE_TRANSMIT_COMPLETE_CALLBACK OFF CACHE BOOL "Optionally enables or disables sending/receiving events") +# set(isotpc_ENABLE_RECEIVE_COMPLETE_CALLBACK OFF CACHE BOOL "Optionally enables or disables sending/receiving events") +``` + +These options can also be passed via the command-line: `-Disotpc_ENABLE_TRANSCEIVE_EVENTS=ON`. + +##### Enabling these options using Makefiles + +If you're still using Makefiles (**NOT** recommended for this project!), then you will have to modify the `isotp_config.h` header file and enable the options manually. +This is **NOT RECOMMENDED**, however, as this file will be overwritten by new versions of the library. + +#### Inclusion in your CMake project +```cmake +### +# Set your desired options +### +set(isotpc_USE_INCLUDE_DIR ON CACHE BOOL "Use external include directory for isotp-c") # optional +set(isotpc_STATIC_LIBRARY ON CACHE BOOL "Build isotp-c as a static library instead of shared") # optional + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/path/to/isotp-c) # add to current project + +target_link_libraries( + mytarget + + # ... other libs + simon_cahill::isotp_c +) +``` + + +## Usage + +First, create some [shim](https://en.wikipedia.org/wiki/Shim_(computing)) functions to let this library use your lower level system: + +```C + /* required, this must send a single CAN message with the given arbitration + * ID (i.e. the CAN message ID) and data. The size will never be more than 8 + * bytes. Should return ISOTP_RET_OK if frame sent successfully. + * May return ISOTP_RET_NOSPACE if the frame could not be sent but may be + * retried later. Should return ISOTP_RET_ERROR in case frame could not be sent. + */ + int isotp_user_send_can(const uint32_t arbitration_id, + const uint8_t* data, const uint8_t size) { + // ... + } + + /* required, return system tick, unit is micro-second */ + uint32_t isotp_user_get_us(void) { + // ... + } + + /* optional, provide to receive debugging log messages */ + void isotp_user_debug(const char* message, ...) { + // ... + } +``` + +### API + +You can use isotp-c in the following way: + +#### Traditional polling mode + +```C + /* Alloc IsoTpLink statically in RAM */ + static IsoTpLink g_link; + + /* Alloc send and receive buffer statically in RAM */ + static uint8_t g_isotpRecvBuf[ISOTP_BUFSIZE]; + static uint8_t g_isotpSendBuf[ISOTP_BUFSIZE]; + + int main(void) { + /* Initialize CAN and other peripherals */ + + /* Initialize link, 0x7TT is the CAN ID you send with */ + isotp_init_link(&g_link, 0x7TT, + g_isotpSendBuf, sizeof(g_isotpSendBuf), + g_isotpRecvBuf, sizeof(g_isotpRecvBuf)); + + while(1) { + + /* If receive any interested can message, call isotp_on_can_message to handle message */ + ret = can_receive(&id, &data, &len); + + /* 0x7RR is CAN ID you want to receive */ + if (RET_OK == ret && 0x7RR == id) { + isotp_on_can_message(&g_link, data, len); + } + + /* Poll link to handle multiple frame transmission */ + isotp_poll(&g_link); + + /* You can receive message with isotp_receive. + payload is upper layer message buffer, usually UDS; + payload_size is payload buffer size; + out_size is the actually read size; + */ + ret = isotp_receive(&g_link, payload, payload_size, &out_size); + if (ISOTP_RET_OK == ret) { + /* Handle received message */ + } + + /* And send message with isotp_send */ + ret = isotp_send(&g_link, payload, payload_size); + if (ISOTP_RET_OK == ret) { + /* Send ok */ + } else { + /* An error occurred */ + } + + /* In case you want to send data w/ functional addressing, use isotp_send_with_id */ + ret = isotp_send_with_id(&g_link, 0x7df, payload, payload_size); + if (ISOTP_RET_OK == ret) { + /* Send ok */ + } else { + /* Error occur */ + } + } + + return; + } +``` + +You can call isotp_poll as frequently as you want, as it internally uses isotp_user_get_ms to measure timeout occurrences. +If you need handle functional addressing, you must use two separate links, one for each. + +```C + /* Alloc IsoTpLink statically in RAM */ + static IsoTpLink g_phylink; + static IsoTpLink g_funclink; + + /* Allocate send and receive buffer statically in RAM */ + static uint8_t g_isotpPhyRecvBuf[512]; + static uint8_t g_isotpPhySendBuf[512]; + /* currently functional addressing is not supported with multi-frame messages */ + static uint8_t g_isotpFuncRecvBuf[8]; + static uint8_t g_isotpFuncSendBuf[8]; + + int main(void) { + /* Initialize CAN and other peripherals */ + + /* Initialize link, 0x7TT is the CAN ID you send with */ + isotp_init_link(&g_phylink, 0x7TT, + g_isotpPhySendBuf, sizeof(g_isotpPhySendBuf), + g_isotpPhyRecvBuf, sizeof(g_isotpPhyRecvBuf)); + isotp_init_link(&g_funclink, 0x7TT, + g_isotpFuncSendBuf, sizeof(g_isotpFuncSendBuf), + g_isotpFuncRecvBuf, sizeof(g_isotpFuncRecvBuf)); + + while(1) { + + /* If any CAN messages are received, which are of interest, call isotp_on_can_message to handle the message */ + ret = can_receive(&id, &data, &len); + + /* 0x7RR is CAN ID you want to receive */ + if (RET_OK == ret) { + if (0x7RR == id) { + isotp_on_can_message(&g_phylink, data, len); + } else if (0x7df == id) { + isotp_on_can_message(&g_funclink, data, len); + } + } + + /* Poll link to handle multiple frame transmission */ + isotp_poll(&g_phylink); + isotp_poll(&g_funclink); + + /* You can receive message with isotp_receive. + payload is upper layer message buffer, usually UDS; + payload_size is payload buffer size; + out_size is the actually read size; + */ + ret = isotp_receive(&g_phylink, payload, payload_size, &out_size); + if (ISOTP_RET_OK == ret) { + /* Handle physical addressing message */ + } + + ret = isotp_receive(&g_funclink, payload, payload_size, &out_size); + if (ISOTP_RET_OK == ret) { + /* Handle functional addressing message */ + } + + /* And send message with isotp_send */ + ret = isotp_send(&g_phylink, payload, payload_size); + if (ISOTP_RET_OK == ret) { + /* Send ok */ + } else { + /* An error occurred */ + } + } + + return; + } +``` + + +#### Event-driven mode (optional) + +If you enabled callback support during build, you can use event-driven programming instead of polling: + +```C + /* Optional: Set up callbacks for event-driven programming */ + void on_message_sent(void* link, uint32_t size, void* user_arg) { + printf("Message transmission complete: %u bytes\n", size); + // Handle transmission complete event + } + + void on_message_received(void* link, const uint8_t* data, uint32_t size, void* user_arg) { + printf("Message received: %u bytes\n", size); + // Process received data directly - no need to call isotp_receive() + process_isotp_message(data, size); + } + + int main(void) { + /* Initialize CAN and other peripherals */ + + /* Initialize link */ + isotp_init_link(&g_link, 0x7TT, + g_isotpSendBuf, sizeof(g_isotpSendBuf), + g_isotpRecvBuf, sizeof(g_isotpRecvBuf)); + + /* Set callbacks (optional - if callbacks not set, use traditional polling) */ + isotp_set_tx_done_cb(&g_link, on_message_sent, &g_link); + isotp_set_rx_done_cb(&g_link, on_message_received, &g_link); + + while(1) { + /* Handle incoming CAN messages */ + ret = can_receive(&id, &data, &len); + if (RET_OK == ret && 0x7RR == id) { + isotp_on_can_message(&g_link, data, len); + } + + /* Poll link - callbacks will be called automatically when complete */ + isotp_poll(&g_link); + + /* Send message */ + ret = isotp_send(&g_link, payload, payload_size); + if (ISOTP_RET_OK == ret) { + /* Send initiated - on_message_sent will be called when complete */ + } + + /* Note: No need to poll isotp_receive() when using rx callback */ + } + + return; + } +``` + +## Authors + +Please view [Contributors](#contributors) to see a list of all contributors. + +## License + +Licensed under the MIT license. diff --git a/esp_isotp/isotp-c/isotp.c b/esp_isotp/isotp-c/isotp.c new file mode 100755 index 0000000000..ac0beeba4a --- /dev/null +++ b/esp_isotp/isotp-c/isotp.c @@ -0,0 +1,695 @@ +//////////////////////////////////////////////////////////////////////// +// ___ ___ ___ _____ ___ ___ // +// |_ _/ __|/ _ \_ _| _ \___ / __| // +// | |\__ \ (_) || | | _/___| (__ // +// |___|___/\___/ |_| |_| \___| // +// // +//////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "isotp.h" + +/////////////////////////////////////////////////////// +/// STATIC FUNCTIONS /// +/////////////////////////////////////////////////////// + +/* st_min to microsecond */ +static uint8_t isotp_us_to_st_min(uint32_t us) +{ + // ISO 15765-2:2016 defines STmin encoding: + // 0x00..0x7F: value in milliseconds (0..127 ms) + // 0xF1..0xF9: value in 100 microsecond steps (100..900 us) + const uint32_t STMIN_MS_MAX = 127000; // 127 ms in us + const uint32_t STMIN_US_MIN = 100; // 100 us + const uint32_t STMIN_US_MAX = 900; // 900 us + const uint8_t STMIN_US_BASE = 0xF0; // base for 100us steps + + if (us <= STMIN_MS_MAX) { + if (us >= STMIN_US_MIN && us <= STMIN_US_MAX) { + return (uint8_t)(STMIN_US_BASE + (us / 100)); + } else { + return (uint8_t)(us / 1000u); + } + } + + return 0; +} + +/* st_min to usec */ +static uint32_t isotp_st_min_to_us(uint8_t st_min) +{ + // ISO 15765-2:2016 defines STmin encoding: + // 0x00..0x7F: value in milliseconds (0..127 ms) + // 0xF1..0xF9: value in 100 microsecond steps (100..900 us) + const uint8_t STMIN_MS_MAX = 0x7F; // 127 ms + const uint8_t STMIN_US_MIN_CODE = 0xF1; // 100 us + const uint8_t STMIN_US_MAX_CODE = 0xF9; // 900 us + const uint8_t STMIN_US_BASE = 0xF0; // base for 100us steps + const uint32_t US_PER_MS = 1000; + const uint32_t US_STEP = 100; + + if (st_min <= STMIN_MS_MAX) { + return st_min * US_PER_MS; + } else if (st_min >= STMIN_US_MIN_CODE && st_min <= STMIN_US_MAX_CODE) { + return (st_min - STMIN_US_BASE) * US_STEP; + } + return 0; +} + +static int isotp_send_flow_control(const IsoTpLink *link, uint8_t flow_status, uint8_t block_size, uint32_t st_min_us) +{ + IsoTpCanMessage message; + (void)memset(&message, 0, sizeof(message)); + int ret; + uint8_t size = 0; + + /* setup message */ + message.as.flow_control.type = ISOTP_PCI_TYPE_FLOW_CONTROL_FRAME; + message.as.flow_control.FS = flow_status; + message.as.flow_control.BS = block_size; + message.as.flow_control.STmin = isotp_us_to_st_min(st_min_us); + + /* send message */ +#ifdef ISO_TP_FRAME_PADDING + (void)memset(message.as.flow_control.reserve, ISO_TP_FRAME_PADDING_VALUE, sizeof(message.as.flow_control.reserve)); + size = sizeof(message); +#else + size = 3; +#endif + + ret = isotp_user_send_can(link->send_arbitration_id, message.as.data_array.ptr, size +#if defined(ISO_TP_USER_SEND_CAN_ARG) + , link->user_send_can_arg +#endif + ); + + return ret; +} + +static int isotp_send_single_frame(const IsoTpLink *link, uint32_t id) +{ + (void)id; // Prevent unused variable warning + + IsoTpCanMessage message; + int ret; + uint8_t size = 0; + + /* multi frame message length must greater than 7 */ + assert(link->send_size <= 7); + + /* setup message */ + message.as.single_frame.type = ISOTP_PCI_TYPE_SINGLE; + message.as.single_frame.SF_DL = (uint8_t)link->send_size; + (void)memcpy(message.as.single_frame.data, link->send_buffer, link->send_size); + + /* send message */ +#ifdef ISO_TP_FRAME_PADDING + (void)memset(message.as.single_frame.data + link->send_size, ISO_TP_FRAME_PADDING_VALUE, sizeof(message.as.single_frame.data) - link->send_size); + size = sizeof(message); +#else + size = link->send_size + (uint8_t)1; +#endif + + ret = isotp_user_send_can(link->send_arbitration_id, message.as.data_array.ptr, size +#if defined(ISO_TP_USER_SEND_CAN_ARG) + , link->user_send_can_arg +#endif + ); + + return ret; +} +static int isotp_send_first_frame(IsoTpLink *link, uint32_t id) +{ + IsoTpCanMessage message = {0}; + int ret = 0; + + /* multi frame message length must greater than 7 */ + assert(link->send_size > 7); + + if (link->send_size <= 4095) { + /* setup 'short' message */ + message.as.first_frame_short.type = ISOTP_PCI_TYPE_FIRST_FRAME; + message.as.first_frame_short.FF_DL_low = (uint8_t)link->send_size; + message.as.first_frame_short.FF_DL_high = (uint8_t)(0x0F & (link->send_size >> 8)); + (void)memcpy(message.as.first_frame_short.data, link->send_buffer, sizeof(message.as.first_frame_short.data)); + + /* send 'short' message */ + ret = isotp_user_send_can(id, message.as.data_array.ptr, sizeof(message) +#if defined(ISO_TP_USER_SEND_CAN_ARG) + , link->user_send_can_arg +#endif + ); + + if (ISOTP_RET_OK == ret) { + link->send_offset += sizeof(message.as.first_frame_short.data); + } + } else { // ISO15765-2:2016 + /* setup 'long' message */ + message.as.first_frame_long.set_to_zero_high = 0; + message.as.first_frame_long.set_to_zero_low = 0; + message.as.first_frame_long.type = ISOTP_PCI_TYPE_FIRST_FRAME; + message.as.first_frame_long.FF_DL = LE32TOH(link->send_size); + (void)memcpy(message.as.first_frame_long.data, link->send_buffer, sizeof(message.as.first_frame_long.data)); + + /* send 'long' message */ + ret = isotp_user_send_can(id, message.as.data_array.ptr, sizeof(message) +#if defined(ISO_TP_USER_SEND_CAN_ARG) + , + link->user_send_can_arg +#endif + ); + + if (ISOTP_RET_OK == ret) { + link->send_offset += sizeof(message.as.first_frame_long.data); + } + } + + link->send_sn = 1; + + return ret; +} + +static int isotp_send_consecutive_frame(IsoTpLink *link) +{ + IsoTpCanMessage message; + uint32_t data_length; + int ret; + uint8_t size = 0; + + /* multi frame message length must greater than 7 */ + assert(link->send_size > 7); + + /* setup message */ + message.as.consecutive_frame.type = ISOTP_PCI_TYPE_CONSECUTIVE_FRAME; + message.as.consecutive_frame.SN = link->send_sn; + data_length = link->send_size - link->send_offset; + if (data_length > sizeof(message.as.consecutive_frame.data)) { + data_length = sizeof(message.as.consecutive_frame.data); + } + (void)memcpy(message.as.consecutive_frame.data, link->send_buffer + link->send_offset, data_length); + + /* send message */ +#ifdef ISO_TP_FRAME_PADDING + (void)memset(message.as.consecutive_frame.data + data_length, ISO_TP_FRAME_PADDING_VALUE, sizeof(message.as.consecutive_frame.data) - data_length); + size = sizeof(message); +#else + size = data_length + 1; +#endif + + ret = isotp_user_send_can(link->send_arbitration_id, message.as.data_array.ptr, size +#if defined(ISO_TP_USER_SEND_CAN_ARG) + , link->user_send_can_arg +#endif + ); + + if (ISOTP_RET_OK == ret) { + link->send_offset += data_length; + if (++(link->send_sn) > 0x0F) { + link->send_sn = 0; + } + } + + return ret; +} + +static int isotp_receive_single_frame(IsoTpLink *link, const IsoTpCanMessage *message, uint8_t len) +{ + /* check data length */ + if ((0 == message->as.single_frame.SF_DL) || (message->as.single_frame.SF_DL > (len - 1))) { + isotp_user_debug("Single-frame length too small."); + return ISOTP_RET_LENGTH; + } + + /* copying data */ + (void)memcpy(link->receive_buffer, message->as.single_frame.data, message->as.single_frame.SF_DL); + link->receive_size = message->as.single_frame.SF_DL; + + return ISOTP_RET_OK; +} + +static int isotp_receive_first_frame(IsoTpLink *link, IsoTpCanMessage *message, uint8_t len) +{ + uint8_t is_long_packet = 0; + uint32_t payload_length; + + if (8 != len) { + isotp_user_debug("First frame should be 8 bytes in length."); + return ISOTP_RET_LENGTH; + } + + /* check data length */ + payload_length = message->as.first_frame_short.FF_DL_high; + payload_length = (payload_length << 8) + message->as.first_frame_short.FF_DL_low; + + /* if length is ZERO we get a long message > 4095bytes of payload */ + if (payload_length == 0) { + is_long_packet = 1; + payload_length = LE32TOH(message->as.first_frame_long.FF_DL); + } + + /* should not use multiple frame transmission */ + if (payload_length <= 7) { + isotp_user_debug("Should not use multiple frame transmission."); + return ISOTP_RET_LENGTH; + } + + if (payload_length > link->receive_buf_size) { + isotp_user_debug("Multi-frame response too large for receiving buffer."); + return ISOTP_RET_OVERFLOW; + } + + /* copying data */ + if (is_long_packet) { + (void)memcpy(link->receive_buffer, message->as.first_frame_long.data, sizeof(message->as.first_frame_long.data)); + link->receive_offset = sizeof(message->as.first_frame_long.data); + } else { + (void)memcpy(link->receive_buffer, message->as.first_frame_short.data, sizeof(message->as.first_frame_short.data)); + link->receive_offset = sizeof(message->as.first_frame_short.data); + } + + link->receive_size = payload_length; + link->receive_sn = 1; + + return ISOTP_RET_OK; +} + +static int isotp_receive_consecutive_frame(IsoTpLink *link, const IsoTpCanMessage *message, uint8_t len) +{ + uint32_t remaining_bytes; + + /* check sn */ + if (link->receive_sn != message->as.consecutive_frame.SN) { + return ISOTP_RET_WRONG_SN; + } + + /* check data length */ + remaining_bytes = link->receive_size - link->receive_offset; + if (remaining_bytes > sizeof(message->as.consecutive_frame.data)) { + remaining_bytes = sizeof(message->as.consecutive_frame.data); + } + if (remaining_bytes > (uint32_t)(len - 1)) { + isotp_user_debug("Consecutive frame too short."); + return ISOTP_RET_LENGTH; + } + + /* copying data */ + (void)memcpy(link->receive_buffer + link->receive_offset, message->as.consecutive_frame.data, remaining_bytes); + + link->receive_offset += remaining_bytes; + if (++(link->receive_sn) > 0x0F) { + link->receive_sn = 0; + } + + return ISOTP_RET_OK; +} + +static int isotp_receive_flow_control_frame(IsoTpLink *link, IsoTpCanMessage *message, uint8_t len) +{ + /* unused args */ + (void)link; + (void)message; + + /* check message length */ + if (len < 3) { + isotp_user_debug("Flow control frame too short."); + return ISOTP_RET_LENGTH; + } + + return ISOTP_RET_OK; +} + +/////////////////////////////////////////////////////// +/// PUBLIC FUNCTIONS /// +/////////////////////////////////////////////////////// + +int isotp_send(IsoTpLink *link, const uint8_t payload[], uint32_t size) +{ + return isotp_send_with_id(link, link->send_arbitration_id, payload, size); +} + +int isotp_send_with_id(IsoTpLink *link, uint32_t id, const uint8_t payload[], uint32_t size) +{ + int ret; + + if (link == 0x0) { + isotp_user_debug("Link is null!"); + return ISOTP_RET_ERROR; + } + + if (size > link->send_buf_size) { + isotp_user_debug("Message size too large. Increase ISO_TP_MAX_MESSAGE_SIZE to set a larger buffer\n"); + + char message[ISOTP_MAX_ERROR_MSG_SIZE] = {0}; + int32_t writtenChars = snprintf(&message[0], ISOTP_MAX_ERROR_MSG_SIZE, "Attempted to send %u bytes; max size is %u!\n", (unsigned int)size, + (unsigned int)link->send_buf_size); + + assert(writtenChars <= ISOTP_MAX_ERROR_MSG_SIZE); + (void)writtenChars; + + isotp_user_debug(message); + return ISOTP_RET_OVERFLOW; + } + + if (ISOTP_SEND_STATUS_INPROGRESS == link->send_status) { + isotp_user_debug("Abort previous message, transmission in progress.\n"); + return ISOTP_RET_INPROGRESS; + } + + /* copy into local buffer */ + link->send_size = size; + link->send_offset = 0; + (void)memcpy(link->send_buffer, payload, size); + + if (link->send_size < 8) { + /* send single frame */ + ret = isotp_send_single_frame(link, id); +#ifdef ISO_TP_TRANSMIT_COMPLETE_CALLBACK + if (ret == ISOTP_RET_OK && link->tx_done_cb) { + link->tx_done_cb(link, link->send_size, link->tx_done_cb_arg); + } +#endif + } else { + /* send multi-frame */ + ret = isotp_send_first_frame(link, id); + + /* init multi-frame control flags */ + if (ISOTP_RET_OK == ret) { + link->send_bs_remain = 0; + link->send_st_min_us = 0; + link->send_wtf_count = 0; + link->send_timer_st = isotp_user_get_us(); + link->send_timer_bs = isotp_user_get_us() + ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US; + link->send_protocol_result = ISOTP_PROTOCOL_RESULT_OK; + link->send_status = ISOTP_SEND_STATUS_INPROGRESS; + } + } + + return ret; +} + +void isotp_on_can_message(IsoTpLink *link, const uint8_t *data, uint8_t len) +{ + IsoTpCanMessage message; + int ret; + + if (len < 2 || len > 8) { + return; + } + + memcpy(message.as.data_array.ptr, data, len); + memset(message.as.data_array.ptr + len, 0, sizeof(message.as.data_array.ptr) - len); + + switch (message.as.common.type) { + case ISOTP_PCI_TYPE_SINGLE: { + /* update protocol result */ + if (ISOTP_RECEIVE_STATUS_INPROGRESS == link->receive_status) { + link->receive_protocol_result = ISOTP_PROTOCOL_RESULT_UNEXP_PDU; + } else { + link->receive_protocol_result = ISOTP_PROTOCOL_RESULT_OK; + } + + /* handle message */ + ret = isotp_receive_single_frame(link, &message, len); + + if (ISOTP_RET_OK == ret) { + /* change status */ + link->receive_status = ISOTP_RECEIVE_STATUS_FULL; + } + break; + } + case ISOTP_PCI_TYPE_FIRST_FRAME: { + /* update protocol result */ + if (ISOTP_RECEIVE_STATUS_INPROGRESS == link->receive_status) { + link->receive_protocol_result = ISOTP_PROTOCOL_RESULT_UNEXP_PDU; + } else { + link->receive_protocol_result = ISOTP_PROTOCOL_RESULT_OK; + } + + /* handle message */ + ret = isotp_receive_first_frame(link, &message, len); + + /* if overflow happened */ + if (ISOTP_RET_OVERFLOW == ret) { + /* update protocol result */ + link->receive_protocol_result = ISOTP_PROTOCOL_RESULT_BUFFER_OVFLW; + /* change status */ + link->receive_status = ISOTP_RECEIVE_STATUS_IDLE; + /* send error message */ + isotp_send_flow_control(link, PCI_FLOW_STATUS_OVERFLOW, 0, 0); + break; + } + + /* if receive successful */ + if (ISOTP_RET_OK == ret) { + /* change status */ + link->receive_status = ISOTP_RECEIVE_STATUS_INPROGRESS; + /* send fc frame */ + link->receive_bs_count = ISO_TP_DEFAULT_BLOCK_SIZE; + isotp_send_flow_control(link, PCI_FLOW_STATUS_CONTINUE, link->receive_bs_count, ISO_TP_DEFAULT_ST_MIN_US); + /* refresh timer cs */ + link->receive_timer_cr = isotp_user_get_us() + ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US; + } + + break; + } + case ISOTP_PCI_TYPE_CONSECUTIVE_FRAME: { + /* check if in receiving status */ + if (ISOTP_RECEIVE_STATUS_INPROGRESS != link->receive_status) { + link->receive_protocol_result = ISOTP_PROTOCOL_RESULT_UNEXP_PDU; + break; + } + + /* handle message */ + ret = isotp_receive_consecutive_frame(link, &message, len); + + /* if wrong sn */ + if (ISOTP_RET_WRONG_SN == ret) { + link->receive_protocol_result = ISOTP_PROTOCOL_RESULT_WRONG_SN; + link->receive_status = ISOTP_RECEIVE_STATUS_IDLE; + break; + } + + /* if success */ + if (ISOTP_RET_OK == ret) { + /* refresh timer cs */ + link->receive_timer_cr = isotp_user_get_us() + ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US; + + /* receive finished */ + if (link->receive_offset >= link->receive_size) { + link->receive_status = ISOTP_RECEIVE_STATUS_FULL; + } else { + /* send fc when bs reaches limit */ + if (0 == --link->receive_bs_count) { + link->receive_bs_count = ISO_TP_DEFAULT_BLOCK_SIZE; + isotp_send_flow_control(link, PCI_FLOW_STATUS_CONTINUE, link->receive_bs_count, ISO_TP_DEFAULT_ST_MIN_US); + } + } + } + + break; + } + case ISOTP_PCI_TYPE_FLOW_CONTROL_FRAME: + /* handle fc frame only when sending in progress */ + if (ISOTP_SEND_STATUS_INPROGRESS != link->send_status) { + break; + } + + /* handle message */ + ret = isotp_receive_flow_control_frame(link, &message, len); + + if (ISOTP_RET_OK == ret) { + /* refresh bs timer */ + link->send_timer_bs = isotp_user_get_us() + ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US; + + /* overflow */ + if (PCI_FLOW_STATUS_OVERFLOW == message.as.flow_control.FS) { + link->send_protocol_result = ISOTP_PROTOCOL_RESULT_BUFFER_OVFLW; + link->send_status = ISOTP_SEND_STATUS_ERROR; + } + + /* wait */ + else if (PCI_FLOW_STATUS_WAIT == message.as.flow_control.FS) { + link->send_wtf_count += 1; + /* wait exceed allowed count */ + if (link->send_wtf_count > ISO_TP_MAX_WFT_NUMBER) { + link->send_protocol_result = ISOTP_PROTOCOL_RESULT_WFT_OVRN; + link->send_status = ISOTP_SEND_STATUS_ERROR; + } + } + + /* permit send */ + else if (PCI_FLOW_STATUS_CONTINUE == message.as.flow_control.FS) { + if (0 == message.as.flow_control.BS) { + link->send_bs_remain = ISOTP_INVALID_BS; + } else { + link->send_bs_remain = message.as.flow_control.BS; + } + uint32_t message_st_min_us = isotp_st_min_to_us(message.as.flow_control.STmin); + link->send_st_min_us = message_st_min_us > ISO_TP_DEFAULT_ST_MIN_US + ? message_st_min_us + : ISO_TP_DEFAULT_ST_MIN_US; // prefer as much st_min as possible for stability? + link->send_wtf_count = 0; + } + } + break; + default: break; + }; + +#ifdef ISO_TP_RECEIVE_COMPLETE_CALLBACK + /* Notify user via callback if registered */ + if (link->receive_status == ISOTP_RECEIVE_STATUS_FULL && link->rx_done_cb != NULL) { + link->rx_done_cb(link, link->receive_buffer, link->receive_size, link->rx_done_cb_arg); + link->receive_status = ISOTP_RECEIVE_STATUS_IDLE; + } +#endif + return; +} + +int isotp_receive(IsoTpLink *link, uint8_t *payload, const uint32_t payload_size, uint32_t *out_size) +{ + uint32_t copylen; + +#ifdef ISO_TP_RECEIVE_COMPLETE_CALLBACK + /* If callback is registered, isotp_receive should not be used */ + if (link->rx_done_cb != NULL) { + return ISOTP_RET_ERROR; /* Callback mode active, use callback instead */ + } +#endif + + if (ISOTP_RECEIVE_STATUS_FULL != link->receive_status) { + return ISOTP_RET_NO_DATA; + } + + copylen = link->receive_size; + if (copylen > payload_size) { + copylen = payload_size; + } + + memcpy(payload, link->receive_buffer, copylen); + *out_size = copylen; + + link->receive_status = ISOTP_RECEIVE_STATUS_IDLE; + + return ISOTP_RET_OK; +} + +void isotp_init_link(IsoTpLink *link, uint32_t sendid, uint8_t *sendbuf, uint32_t sendbufsize, uint8_t *recvbuf, uint32_t recvbufsize) +{ + memset(link, 0, sizeof(*link)); + link->receive_status = ISOTP_RECEIVE_STATUS_IDLE; + link->send_status = ISOTP_SEND_STATUS_IDLE; + link->send_arbitration_id = sendid; + link->send_buffer = sendbuf; + link->send_buf_size = sendbufsize; + link->receive_buffer = recvbuf; + link->receive_buf_size = recvbufsize; + +#ifdef ISO_TP_TRANSMIT_COMPLETE_CALLBACK + link->tx_done_cb = NULL; + link->tx_done_cb_arg = NULL; +#endif + +#ifdef ISO_TP_RECEIVE_COMPLETE_CALLBACK + link->rx_done_cb = NULL; + link->rx_done_cb_arg = NULL; +#endif + + return; +} + +void isotp_destroy_link(IsoTpLink *link) +{ + if (link == NULL) { + return; + } + + // Clear callbacks +#ifdef ISO_TP_TRANSMIT_COMPLETE_CALLBACK + link->tx_done_cb = NULL; + link->tx_done_cb_arg = NULL; +#endif + +#ifdef ISO_TP_RECEIVE_COMPLETE_CALLBACK + link->rx_done_cb = NULL; + link->rx_done_cb_arg = NULL; +#endif + + // Reset link state (optional, but good practice) + memset(link, 0, sizeof(IsoTpLink)); +} + +void isotp_poll(IsoTpLink *link) +{ + int ret = 0; + + /* only polling when operation in progress */ + if (ISOTP_SEND_STATUS_INPROGRESS == link->send_status) { + /* continue send data */ + if (/* send data if bs_remain is invalid or bs_remain large than zero */ + (ISOTP_INVALID_BS == link->send_bs_remain || link->send_bs_remain > 0) && + /* and if st_min is zero or go beyond interval time */ + (0 == link->send_st_min_us || IsoTpTimeAfter(isotp_user_get_us(), link->send_timer_st))) { + ret = isotp_send_consecutive_frame(link); + if (ISOTP_RET_OK == ret) { + if (ISOTP_INVALID_BS != link->send_bs_remain) { + link->send_bs_remain -= 1; + } + link->send_timer_bs = isotp_user_get_us() + ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US; + link->send_timer_st = isotp_user_get_us() + link->send_st_min_us; + + /* check if send finish */ + if (link->send_offset >= link->send_size) { + link->send_status = ISOTP_SEND_STATUS_IDLE; +#ifdef ISO_TP_TRANSMIT_COMPLETE_CALLBACK + if (link->tx_done_cb != NULL) { + link->tx_done_cb(link, link->send_size, link->tx_done_cb_arg); + } +#endif + } + } else if (ISOTP_RET_NOSPACE == ret) { + /* shim reported that it isn't able to send a frame at present, retry on next call */ + } else { + link->send_status = ISOTP_SEND_STATUS_ERROR; + } + } + + /* check timeout */ + if (IsoTpTimeAfter(isotp_user_get_us(), link->send_timer_bs)) { + link->send_protocol_result = ISOTP_PROTOCOL_RESULT_TIMEOUT_BS; + link->send_status = ISOTP_SEND_STATUS_ERROR; + } + } + + /* only polling when operation in progress */ + if (ISOTP_RECEIVE_STATUS_INPROGRESS == link->receive_status) { + /* check timeout */ + if ((link->receive_timer_cr > 0) && IsoTpTimeAfter(isotp_user_get_us(), link->receive_timer_cr)) { + link->receive_protocol_result = ISOTP_PROTOCOL_RESULT_TIMEOUT_CR; + link->receive_status = ISOTP_RECEIVE_STATUS_IDLE; + } + } + + return; +} + +#ifdef ISO_TP_TRANSMIT_COMPLETE_CALLBACK +void isotp_set_tx_done_cb(IsoTpLink *link, isotp_tx_done_cb cb, void *arg) +{ + if (link != NULL) { + link->tx_done_cb = cb; + link->tx_done_cb_arg = arg; + } +} +#endif + +#ifdef ISO_TP_RECEIVE_COMPLETE_CALLBACK +void isotp_set_rx_done_cb(IsoTpLink *link, isotp_rx_done_cb cb, void *arg) +{ + if (link != NULL) { + link->rx_done_cb = cb; + link->rx_done_cb_arg = arg; + } +} +#endif diff --git a/esp_isotp/isotp-c/isotp.h b/esp_isotp/isotp-c/isotp.h new file mode 100755 index 0000000000..e8af6388e3 --- /dev/null +++ b/esp_isotp/isotp-c/isotp.h @@ -0,0 +1,184 @@ +//////////////////////////////////////////////////////////////////////// +// ___ ___ ___ _____ ___ ___ // +// |_ _/ __|/ _ \_ _| _ \___ / __| // +// | |\__ \ (_) || | | _/___| (__ // +// |___|___/\___/ |_| |_| \___| // +// // +//////////////////////////////////////////////////////////////////////// + +#ifndef ISOTPC_H +#define ISOTPC_H + +#include +#include + +#ifdef __cplusplus +#include + +extern "C" { +#endif + +#include "isotp_config.h" +#include "isotp_defines.h" +#include "isotp_user.h" + +/** + * @brief Struct containing the data for linking an application to a CAN instance. + * The data stored in this struct is used internally and may be used by software programs + * using this library. + */ +typedef struct IsoTpLink { + /* sender parameters */ + uint32_t send_arbitration_id; /* used to reply consecutive frame */ + + /* message buffer */ + uint8_t *send_buffer; + uint32_t send_buf_size; + uint32_t send_size; + uint32_t send_offset; + + /* multi-frame flags */ + uint8_t send_sn; + uint32_t send_bs_remain; /* Remaining block size */ + uint32_t send_st_min_us; /* Separation Time between consecutive frames */ + uint8_t send_wtf_count; /* Maximum number of FC.Wait frame transmissions */ + uint32_t send_timer_st; /* Last time send consecutive frame */ + uint32_t send_timer_bs; /* Time until reception of the next FlowControl N_PDU + start at sending FF, CF, receive FC + end at receive FC */ + int32_t send_protocol_result; + uint8_t send_status; + + /* receiver parameters */ + uint32_t receive_arbitration_id; + + /* message buffer */ + uint8_t *receive_buffer; + uint32_t receive_buf_size; + uint32_t receive_size; + uint32_t receive_offset; + + /* multi-frame control */ + uint8_t receive_sn; + uint8_t receive_bs_count; /* Maximum number of FC.Wait frame transmissions */ + uint32_t receive_timer_cr; /* Time until transmission of the next ConsecutiveFrame N_PDU + start at sending FC, receive CF + end at receive FC */ + int receive_protocol_result; + uint8_t receive_status; + +#if defined(ISO_TP_USER_SEND_CAN_ARG) + void *user_send_can_arg; +#endif + +#ifdef ISO_TP_TRANSMIT_COMPLETE_CALLBACK + isotp_tx_done_cb tx_done_cb; /* User callback for transmission complete */ + void *tx_done_cb_arg; /* User argument for callback */ +#endif + +#ifdef ISO_TP_RECEIVE_COMPLETE_CALLBACK + isotp_rx_done_cb rx_done_cb; /* User callback for receive complete */ + void *rx_done_cb_arg; /* User argument for callback */ +#endif + +} IsoTpLink; + +/** + * @brief Initialises the ISO-TP library. + * + * @param link The @code IsoTpLink @endcode instance used for transceiving data. + * @param sendid The ID used to send data to other CAN nodes. + * @param sendbuf A pointer to an area in memory which can be used as a buffer for data to be sent. + * @param sendbufsize The size of the buffer area. + * @param recvbuf A pointer to an area in memory which can be used as a buffer for data to be received. + * @param recvbufsize The size of the buffer area. + */ +void isotp_init_link(IsoTpLink *link, uint32_t sendid, uint8_t *sendbuf, uint32_t sendbufsize, uint8_t *recvbuf, uint32_t recvbufsize); + +/** + * @brief Destroys the ISO-TP link and releases associated resources. + * + * @param link The @code IsoTpLink @endcode instance to destroy. + */ +void isotp_destroy_link(IsoTpLink *link); + +/** + * @brief Polling function; call this function periodically to handle timeouts, send consecutive frames, etc. + * + * @param link The @code IsoTpLink @endcode instance used. + */ +void isotp_poll(IsoTpLink *link); + +/** + * @brief Handles incoming CAN messages. + * Determines whether an incoming message is a valid ISO-TP frame or not and handles it accordingly. + * + * @param link The @code IsoTpLink @endcode instance used for transceiving data. + * @param data The data received via CAN. + * @param len The length of the data received. + */ +void isotp_on_can_message(IsoTpLink *link, const uint8_t *data, uint8_t len); + +/** + * @brief Sends ISO-TP frames via CAN, using the ID set in the initialising function. + * + * Single-frame messages will be sent immediately when calling this function. + * Multi-frame messages will be sent consecutively when calling isotp_poll. + * + * @param link The @code IsoTpLink @endcode instance used for transceiving data. + * @param payload The payload to be sent. (Up to 4095 bytes). + * @param size The size of the payload to be sent. + * + * @return Possible return values: + * - @code ISOTP_RET_OVERFLOW @endcode + * - @code ISOTP_RET_INPROGRESS @endcode + * - @code ISOTP_RET_OK @endcode + * - The return value of the user shim function isotp_user_send_can(). + */ +int isotp_send(IsoTpLink *link, const uint8_t payload[], uint32_t size); + +/** + * @brief See @link isotp_send @endlink, with the exception that this function is used only for functional addressing. + */ +int isotp_send_with_id(IsoTpLink *link, uint32_t id, const uint8_t payload[], uint32_t size); + +/** + * @brief Receives and parses the received data and copies the parsed data in to the internal buffer. + * @param link The @link IsoTpLink @endlink instance used to transceive data. + * @param payload A pointer to an area in memory where the raw data is copied from. + * @param payload_size The size of the received (raw) CAN data. + * @param out_size A reference to a variable which will contain the size of the actual (parsed) data. + * + * @return Possible return values: + * - @link ISOTP_RET_OK @endlink + * - @link ISOTP_RET_NO_DATA @endlink + */ +int isotp_receive(IsoTpLink *link, uint8_t *payload, const uint32_t payload_size, uint32_t *out_size); + +#ifdef ISO_TP_TRANSMIT_COMPLETE_CALLBACK +/** + * @brief Sets the callback function for transmission complete notification. + * + * @param link The @code IsoTpLink @endcode instance used for transceiving data. + * @param cb The callback function to be called when transmission is complete. + * @param arg A pointer that will be passed to the callback function. + */ +void isotp_set_tx_done_cb(IsoTpLink *link, isotp_tx_done_cb cb, void *arg); +#endif + +#ifdef ISO_TP_RECEIVE_COMPLETE_CALLBACK +/** + * @brief Sets the callback function for receive complete notification. + * + * @param link The @code IsoTpLink @endcode instance used for transceiving data. + * @param cb The callback function to be called when a message is received. + * @param arg A pointer that will be passed to the callback function. + */ +void isotp_set_rx_done_cb(IsoTpLink *link, isotp_rx_done_cb cb, void *arg); +#endif + +#ifdef __cplusplus +} +#endif + +#endif // ISOTPC_H diff --git a/esp_isotp/isotp-c/isotp_config.h b/esp_isotp/isotp-c/isotp_config.h new file mode 100755 index 0000000000..6062ae3b87 --- /dev/null +++ b/esp_isotp/isotp-c/isotp_config.h @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////// +// ___ ___ ___ _____ ___ ___ // +// |_ _/ __|/ _ \_ _| _ \___ / __| // +// | |\__ \ (_) || | | _/___| (__ // +// |___|___/\___/ |_| |_| \___| // +// // +// ___ ___ _ _ ___ ___ ___ // +// / __/ _ \| \| | __|_ _/ __| // +// | (_| (_) | .` | _| | | (_ | // +// \___\___/|_|\_|_| |___\___| // +// // +//////////////////////////////////////////////////////////////////////// + +#ifndef ISOTPC_CONFIG_H +#define ISOTPC_CONFIG_H + +/* Max number of messages the receiver can receive at one time, this value + * is affected by can driver queue length + */ +#define ISO_TP_DEFAULT_BLOCK_SIZE 8 + +/* The STmin parameter value specifies the minimum time gap allowed between + * the transmission of consecutive frame network protocol data units + */ +#define ISO_TP_DEFAULT_ST_MIN_US 1000 + +/* This parameter indicate how many FC N_PDU WTs can be transmitted by the + * receiver in a row. + */ +#define ISO_TP_MAX_WFT_NUMBER 1 + +/* Private: The default timeout to use when waiting for a response during a + * multi-frame send or receive. + */ +#define ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US 100000 + +/* Private: Determines if by default, padding is added to ISO-TP message frames. + */ +// #define ISO_TP_FRAME_PADDING + +/* Private: Value to use when padding frames if enabled by ISO_TP_FRAME_PADDING + */ +#ifndef ISO_TP_FRAME_PADDING_VALUE +#define ISO_TP_FRAME_PADDING_VALUE 0xAA +#endif + +/* Private: Determines if by default, an additional argument is present in the + * definition of isotp_user_send_can. + */ +#define ISO_TP_USER_SEND_CAN_ARG + +/* Enable support for transmission complete callback */ +#define ISO_TP_TRANSMIT_COMPLETE_CALLBACK + +/* Enable support for receive complete callback */ +#define ISO_TP_RECEIVE_COMPLETE_CALLBACK + +#endif // ISOTPC_CONFIG_H diff --git a/esp_isotp/isotp-c/isotp_defines.h b/esp_isotp/isotp-c/isotp_defines.h new file mode 100755 index 0000000000..ee7167da99 --- /dev/null +++ b/esp_isotp/isotp-c/isotp_defines.h @@ -0,0 +1,274 @@ +#ifndef ISOTPC_USER_DEFINITIONS_H +#define ISOTPC_USER_DEFINITIONS_H + +#include + +/************************************************************** + * compiler specific defines + *************************************************************/ +#ifdef __GNUC__ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define ISOTP_BYTE_ORDER_LITTLE_ENDIAN +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#else +#error "unsupported byte ordering" +#endif + +#define ISOTP_PACKED_STRUCT(content) typedef struct __attribute__((packed)) content +#endif + +/************************************************************** + * OS specific defines + *************************************************************/ +#ifdef _WIN32 +#define ISOTP_PACKED_STRUCT(content) __pragma(pack(push, 1)) typedef struct content __pragma(pack(pop)) + +#define snprintf _snprintf + +#include +#define ISOTP_BYTE_ORDER_LITTLE_ENDIAN +#define __builtin_bswap8 _byteswap_uint8 +#define __builtin_bswap16 _byteswap_uint16 +#define __builtin_bswap32 _byteswap_uint32 +#define __builtin_bswap64 _byteswap_uint64 +#endif + +#define LE32TOH(le) ((uint32_t)(((le) << 24) | (((le) & 0x0000FF00) << 8) | (((le) & 0x00FF0000) >> 8) | ((le) >> 24))) + +/************************************************************** + * internal used defines + *************************************************************/ +#define ISOTP_RET_OK 0 +#define ISOTP_RET_ERROR -1 +#define ISOTP_RET_INPROGRESS -2 +#define ISOTP_RET_OVERFLOW -3 +#define ISOTP_RET_WRONG_SN -4 +#define ISOTP_RET_NO_DATA -5 +#define ISOTP_RET_TIMEOUT -6 +#define ISOTP_RET_LENGTH -7 +#define ISOTP_RET_NOSPACE -8 + +/* return logic true if 'a' is after 'b' */ +#define IsoTpTimeAfter(a, b) ((int32_t)((int32_t)(b) - (int32_t)(a)) < 0) + +/* invalid bs */ +#define ISOTP_INVALID_BS 0xFFFF + +/* Define the maximum amount of characters allowed in an error message. This fixes code which would otherwise break on Microsoft's dumb platform. */ +#define ISOTP_MAX_ERROR_MSG_SIZE 128 + +/* ISOTP sender status */ +typedef enum { + ISOTP_SEND_STATUS_IDLE, + ISOTP_SEND_STATUS_INPROGRESS, + ISOTP_SEND_STATUS_ERROR, +} IsoTpSendStatusTypes; + +/* ISOTP receiver status */ +typedef enum { + ISOTP_RECEIVE_STATUS_IDLE, + ISOTP_RECEIVE_STATUS_INPROGRESS, + ISOTP_RECEIVE_STATUS_FULL, +} IsoTpReceiveStatusTypes; + +/* can frame definition */ +#if defined(ISOTP_BYTE_ORDER_LITTLE_ENDIAN) +typedef struct { + uint8_t reserve_1 : 4; + uint8_t type : 4; + uint8_t reserve_2[7]; +} IsoTpPciType; + +typedef struct { + uint8_t SF_DL : 4; + uint8_t type : 4; + uint8_t data[7]; +} IsoTpSingleFrame; + +typedef struct { + uint8_t FF_DL_high : 4; + uint8_t type : 4; + uint8_t FF_DL_low; + uint8_t data[6]; +} IsoTpFirstFrameShort; + +ISOTP_PACKED_STRUCT({ + uint8_t set_to_zero_high : 4; + uint8_t type : 4; + uint8_t set_to_zero_low; + uint32_t FF_DL; + uint8_t data[2]; +} IsoTpFirstFrameLong); + +typedef struct { + uint8_t SN : 4; + uint8_t type : 4; + uint8_t data[7]; +} IsoTpConsecutiveFrame; + +typedef struct { + uint8_t FS : 4; + uint8_t type : 4; + uint8_t BS; + uint8_t STmin; + uint8_t reserve[5]; +} IsoTpFlowControl; + +#else + +typedef struct { + uint8_t type : 4; + uint8_t reserve_1 : 4; + uint8_t reserve_2[7]; +} IsoTpPciType; + +/* + * single frame + * +-------------------------+-----+ + * | byte #0 | ... | + * +-------------------------+-----+ + * | nibble #0 | nibble #1 | ... | + * +-------------+-----------+ ... + + * | PCIType = 0 | SF_DL | ... | + * +-------------+-----------+-----+ + */ +typedef struct { + uint8_t type : 4; + uint8_t SF_DL : 4; + uint8_t data[7]; +} IsoTpSingleFrame; + +/* + * first frame short + * +-------------------------+-----------------------+-----+ + * | byte #0 | byte #1 | ... | + * +-------------------------+-----------+-----------+-----+ + * | nibble #0 | nibble #1 | nibble #2 | nibble #3 | ... | + * +-------------+-----------+-----------+-----------+-----+ + * | PCIType = 1 | FF_DL | ... | + * +-------------+-----------+-----------------------+-----+ + */ +typedef struct { + uint8_t FF_DL_high : 4; + uint8_t type : 4; + uint8_t FF_DL_low; + uint8_t data[6]; +} IsoTpFirstFrameShort; + +/* + * first frame long + * +-------------------------+-----------------------+---------+---------+---------+---------+ + * | byte #0 | byte #1 | byte #2 | byte #3 | byte #4 | byte #5 | + * +-------------------------+-----------+-----------+---------+---------+---------+---------+ + * | nibble #0 | nibble #1 | nibble #2 | nibble #3 | ... | + * +-------------+-----------+-----------+-----------+---------------------------------------+ + * | PCIType = 1 | unused=0 | escape sequence = 0 | FF_DL | + * +-------------+-----------+-----------------------+---------------------------------------+ + */ +ISOTP_PACKED_STRUCT({ + uint8_t set_to_zero_high : 4; + uint8_t type : 4; + uint8_t set_to_zero_low; + uint32_t FF_DL; + uint8_t data[2]; +} IsoTpFirstFrameLong); + +/* + * consecutive frame + * +-------------------------+-----+ + * | byte #0 | ... | + * +-------------------------+-----+ + * | nibble #0 | nibble #1 | ... | + * +-------------+-----------+ ... + + * | PCIType = 0 | SN | ... | + * +-------------+-----------+-----+ + */ +typedef struct { + uint8_t type : 4; + uint8_t SN : 4; + uint8_t data[7]; +} IsoTpConsecutiveFrame; + +/* + * flow control frame + * +-------------------------+-----------------------+-----------------------+-----+ + * | byte #0 | byte #1 | byte #2 | ... | + * +-------------------------+-----------+-----------+-----------+-----------+-----+ + * | nibble #0 | nibble #1 | nibble #2 | nibble #3 | nibble #4 | nibble #5 | ... | + * +-------------+-----------+-----------+-----------+-----------+-----------+-----+ + * | PCIType = 1 | FS | BS | STmin | ... | + * +-------------+-----------+-----------------------+-----------------------+-----+ + */ +typedef struct { + uint8_t type : 4; + uint8_t FS : 4; + uint8_t BS; + uint8_t STmin; + uint8_t reserve[5]; +} IsoTpFlowControl; + +#endif + +typedef struct { + uint8_t ptr[8]; +} IsoTpDataArray; + +typedef struct { + union { + IsoTpPciType common; + IsoTpSingleFrame single_frame; + IsoTpFirstFrameShort first_frame_short; + IsoTpFirstFrameLong first_frame_long; + IsoTpConsecutiveFrame consecutive_frame; + IsoTpFlowControl flow_control; + IsoTpDataArray data_array; + } as; +} IsoTpCanMessage; + +/************************************************************** + * protocol specific defines + *************************************************************/ + +#ifdef ISO_TP_TRANSMIT_COMPLETE_CALLBACK +/* Private: Function pointer type for transmission done callback + * Called when any transmission (single-frame or multi-frame) is completed successfully + */ +typedef void (*isotp_tx_done_cb)(void *link, uint32_t tx_size, void *user_arg); +#endif + +#ifdef ISO_TP_RECEIVE_COMPLETE_CALLBACK +/* Private: Function pointer type for receive done callback + * Called when a complete message (single-frame or multi-frame) has been received successfully + */ +typedef void (*isotp_rx_done_cb)(void *link, const uint8_t *data, uint32_t size, void *user_arg); +#endif + +/* Private: Protocol Control Information (PCI) types, for identifying each frame of an ISO-TP message. + */ +typedef enum { + ISOTP_PCI_TYPE_SINGLE = 0x0, + ISOTP_PCI_TYPE_FIRST_FRAME = 0x1, + TSOTP_PCI_TYPE_CONSECUTIVE_FRAME = 0x2, + ISOTP_PCI_TYPE_FLOW_CONTROL_FRAME = 0x3, + + ISOTP_PCI_TYPE_CONSECUTIVE_FRAME = 0x2, // Typo fix; but keep broken value for backwards-compat. +} IsoTpProtocolControlInformation; + +/* Private: Protocol Control Information (PCI) flow control identifiers. + */ +typedef enum { PCI_FLOW_STATUS_CONTINUE = 0x0, PCI_FLOW_STATUS_WAIT = 0x1, PCI_FLOW_STATUS_OVERFLOW = 0x2 } IsoTpFlowStatus; + +/* Private: network layer resault code. + */ +#define ISOTP_PROTOCOL_RESULT_OK 0 +#define ISOTP_PROTOCOL_RESULT_TIMEOUT_A -1 +#define ISOTP_PROTOCOL_RESULT_TIMEOUT_BS -2 +#define ISOTP_PROTOCOL_RESULT_TIMEOUT_CR -3 +#define ISOTP_PROTOCOL_RESULT_WRONG_SN -4 +#define ISOTP_PROTOCOL_RESULT_INVALID_FS -5 +#define ISOTP_PROTOCOL_RESULT_UNEXP_PDU -6 +#define ISOTP_PROTOCOL_RESULT_WFT_OVRN -7 +#define ISOTP_PROTOCOL_RESULT_BUFFER_OVFLW -8 +#define ISOTP_PROTOCOL_RESULT_ERROR -9 + +#endif // ISOTPC_USER_DEFINITIONS_H \ No newline at end of file diff --git a/esp_isotp/isotp-c/isotp_user.h b/esp_isotp/isotp-c/isotp_user.h new file mode 100755 index 0000000000..768d375d3e --- /dev/null +++ b/esp_isotp/isotp-c/isotp_user.h @@ -0,0 +1,42 @@ +//////////////////////////////////////////////////////////////////////// +// ___ ___ ___ _____ ___ ___ // +// |_ _/ __|/ _ \_ _| _ \___ / __| // +// | |\__ \ (_) || | | _/___| (__ // +// |___|___/\___/ |_| |_| \___| // +// // +//////////////////////////////////////////////////////////////////////// + +#ifndef ISOTPC_USER_H +#define ISOTPC_USER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief user implemented, print debug message */ +void isotp_user_debug(const char *message, ...); + +/** + * @brief user implemented, send can message. should return ISOTP_RET_OK when success. + * + * @return may return ISOTP_RET_NOSPACE if the CAN transfer should be retried later + * or ISOTP_RET_ERROR if transmission couldn't be completed + */ +int isotp_user_send_can(const uint32_t arbitration_id, const uint8_t *data, const uint8_t size +#ifdef ISO_TP_USER_SEND_CAN_ARG + , void *arg +#endif + ); + +/** + * @brief user implemented, gets the amount of time passed since the last call in microseconds + */ +uint32_t isotp_user_get_us(void); + +#ifdef __cplusplus +} +#endif + +#endif // ISOTPC_USER_H From 233b082eacca9079fcdd7dc5de6208666d6e8128 Mon Sep 17 00:00:00 2001 From: eternal-echo Date: Thu, 28 Aug 2025 23:50:43 +0800 Subject: [PATCH 2/3] refactor(isotp): update configuration to use SDK config values --- esp_isotp/Kconfig | 75 ++++++++++++++++++++++++++++++++ esp_isotp/isotp-c/isotp_config.h | 24 ++++++---- 2 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 esp_isotp/Kconfig diff --git a/esp_isotp/Kconfig b/esp_isotp/Kconfig new file mode 100644 index 0000000000..455213cf77 --- /dev/null +++ b/esp_isotp/Kconfig @@ -0,0 +1,75 @@ +menu "ISO-TP Protocol Configuration" + + config ISO_TP_DEFAULT_BLOCK_SIZE + int "Default Block Size" + default 8 + range 1 32 + help + Maximum number of consecutive frames the receiver can receive at one time. + This value is affected by CAN driver queue length. + Higher values improve throughput but require more buffer space. + + config ISO_TP_DEFAULT_ST_MIN_US + int "Default STmin (microseconds)" + default 1000 + range 0 50000 + help + The STmin parameter specifies the minimum time gap allowed between + the transmission of consecutive frame network protocol data units. + 0 = no delay, 1-50000 microseconds for timing control. + + config ISO_TP_MAX_WFT_NUMBER + int "Maximum Wait Frame Number" + default 1 + range 1 10 + help + This parameter indicates how many FC N_PDU WTs (Wait frames) can be + transmitted by the receiver in a row before giving up. + + config ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US + int "Default Response Timeout (microseconds)" + default 100000 + range 10000 5000000 + help + The default timeout to use when waiting for a response during a + multi-frame send or receive operation. + Common values: 100000 (100ms), 1000000 (1s). + + config ISO_TP_FRAME_PADDING + bool "Enable Frame Padding" + default n + help + Enable padding of ISO-TP message frames to full CAN frame size. + When enabled, frames are padded with ISO_TP_FRAME_PADDING_VALUE. + + config ISO_TP_FRAME_PADDING_VALUE + hex "Frame Padding Value" + default 0xAA + range 0x00 0xFF + depends on ISO_TP_FRAME_PADDING + help + Value used for padding when ISO_TP_FRAME_PADDING is enabled. + Common values: 0x00, 0xAA, 0xCC, 0xFF. + + config ISO_TP_USER_SEND_CAN_ARG + bool "Enable User Send CAN Argument" + default y + help + Determines if an additional argument is present in the + definition of isotp_user_send_can function. + + config ISO_TP_TRANSMIT_COMPLETE_CALLBACK + bool "Enable Transmit Complete Callback" + default y + help + Enable support for transmission complete callback functions. + Allows applications to be notified when transmission is finished. + + config ISO_TP_RECEIVE_COMPLETE_CALLBACK + bool "Enable Receive Complete Callback" + default y + help + Enable support for receive complete callback functions. + Allows applications to be notified when a message is received. + +endmenu \ No newline at end of file diff --git a/esp_isotp/isotp-c/isotp_config.h b/esp_isotp/isotp-c/isotp_config.h index 6062ae3b87..f81ecf3f19 100755 --- a/esp_isotp/isotp-c/isotp_config.h +++ b/esp_isotp/isotp-c/isotp_config.h @@ -14,45 +14,53 @@ #ifndef ISOTPC_CONFIG_H #define ISOTPC_CONFIG_H +#include "sdkconfig.h" + /* Max number of messages the receiver can receive at one time, this value * is affected by can driver queue length */ -#define ISO_TP_DEFAULT_BLOCK_SIZE 8 +#define ISO_TP_DEFAULT_BLOCK_SIZE CONFIG_ISO_TP_DEFAULT_BLOCK_SIZE /* The STmin parameter value specifies the minimum time gap allowed between * the transmission of consecutive frame network protocol data units */ -#define ISO_TP_DEFAULT_ST_MIN_US 1000 +#define ISO_TP_DEFAULT_ST_MIN_US CONFIG_ISO_TP_DEFAULT_ST_MIN_US /* This parameter indicate how many FC N_PDU WTs can be transmitted by the * receiver in a row. */ -#define ISO_TP_MAX_WFT_NUMBER 1 +#define ISO_TP_MAX_WFT_NUMBER CONFIG_ISO_TP_MAX_WFT_NUMBER /* Private: The default timeout to use when waiting for a response during a * multi-frame send or receive. */ -#define ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US 100000 +#define ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US CONFIG_ISO_TP_DEFAULT_RESPONSE_TIMEOUT_US /* Private: Determines if by default, padding is added to ISO-TP message frames. */ -// #define ISO_TP_FRAME_PADDING +#ifdef CONFIG_ISO_TP_FRAME_PADDING +#define ISO_TP_FRAME_PADDING +#endif /* Private: Value to use when padding frames if enabled by ISO_TP_FRAME_PADDING */ -#ifndef ISO_TP_FRAME_PADDING_VALUE -#define ISO_TP_FRAME_PADDING_VALUE 0xAA -#endif +#define ISO_TP_FRAME_PADDING_VALUE CONFIG_ISO_TP_FRAME_PADDING_VALUE /* Private: Determines if by default, an additional argument is present in the * definition of isotp_user_send_can. */ +#ifdef CONFIG_ISO_TP_USER_SEND_CAN_ARG #define ISO_TP_USER_SEND_CAN_ARG +#endif /* Enable support for transmission complete callback */ +#ifdef CONFIG_ISO_TP_TRANSMIT_COMPLETE_CALLBACK #define ISO_TP_TRANSMIT_COMPLETE_CALLBACK +#endif /* Enable support for receive complete callback */ +#ifdef CONFIG_ISO_TP_RECEIVE_COMPLETE_CALLBACK #define ISO_TP_RECEIVE_COMPLETE_CALLBACK +#endif #endif // ISOTPC_CONFIG_H From fce267a0fc19104e70b954e53863e8573de61f53 Mon Sep 17 00:00:00 2001 From: eternal-echo Date: Fri, 29 Aug 2025 16:53:29 +0800 Subject: [PATCH 3/3] feat(isotp): add the esp_isotp component. --- esp_isotp/CMakeLists.txt | 7 ++ esp_isotp/LICENSE | 22 ++++ esp_isotp/README.md | 72 +++++++++++++ esp_isotp/idf_component.yml | 7 ++ esp_isotp/inc/esp_isotp.h | 147 ++++++++++++++++++++++++++ esp_isotp/src/esp_isotp.c | 199 ++++++++++++++++++++++++++++++++++++ 6 files changed, 454 insertions(+) create mode 100644 esp_isotp/CMakeLists.txt create mode 100644 esp_isotp/LICENSE create mode 100644 esp_isotp/README.md create mode 100644 esp_isotp/idf_component.yml create mode 100644 esp_isotp/inc/esp_isotp.h create mode 100644 esp_isotp/src/esp_isotp.c diff --git a/esp_isotp/CMakeLists.txt b/esp_isotp/CMakeLists.txt new file mode 100644 index 0000000000..c17892bf78 --- /dev/null +++ b/esp_isotp/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register( + SRCS "src/esp_isotp.c" "isotp-c/isotp.c" + INCLUDE_DIRS "inc" + PRIV_INCLUDE_DIRS "isotp-c" + REQUIRES esp_driver_twai + PRIV_REQUIRES esp_timer +) diff --git a/esp_isotp/LICENSE b/esp_isotp/LICENSE new file mode 100644 index 0000000000..949e27ae93 --- /dev/null +++ b/esp_isotp/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2019-2024 Li Shen & Co-Operators +Copyright (c) 2024 Simon Cahill & Contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/esp_isotp/README.md b/esp_isotp/README.md new file mode 100644 index 0000000000..472da8a4b6 --- /dev/null +++ b/esp_isotp/README.md @@ -0,0 +1,72 @@ +# ESP-IDF ISO-TP Component + +[![Component Registry](https://components.espressif.com/components/espressif/esp_isotp/badge.svg)](https://components.espressif.com/components/espressif/esp_isotp) + +ISO 15765-2 (ISO-TP) transport protocol implementation for ESP-IDF, enabling reliable transmission of large data payloads (up to 4095 bytes) over TWAI networks with automatic segmentation and reassembly. + +## Key Features + +- **Automatic segmentation** for messages >7 bytes with flow control +- **Non-blocking API** with ISR-based frame processing +- **Multi-instance support** for concurrent communication channels +- **Automotive compliance** (UDS, OBD-II compatible) +- **Robust error handling** with timeout and sequence validation + +> [!NOTE] +> TWAI-FD (Flexible Data-rate) is not supported in this version. + +## Installation + +```bash +idf.py add-dependency espressif/esp_isotp +``` + +## Configuration + +Configure ISO-TP protocol parameters: + +```bash +idf.py menuconfig +# Navigate to: Component config → ISO-TP Protocol Configuration +``` + +## Quick Start + +```c +#include "esp_isotp.h" +#include "esp_twai_onchip.h" + +void app_main(void) { + // 1. Initialize TWAI + twai_onchip_node_config_t twai_cfg = { + .io_cfg = {.tx = GPIO_NUM_5, .rx = GPIO_NUM_4}, + .bit_timing = {.bitrate = 500000}, + .tx_queue_depth = 16, + }; + twai_node_handle_t twai_node; + ESP_ERROR_CHECK(twai_new_node_onchip(&twai_cfg, &twai_node)); + + // 2. Create ISO-TP transport + esp_isotp_config_t config = { + .tx_id = 0x7E0, .rx_id = 0x7E8, + .tx_buffer_size = 4096, .rx_buffer_size = 4096, + }; + esp_isotp_handle_t isotp_handle; + ESP_ERROR_CHECK(esp_isotp_new_transport(twai_node, &config, &isotp_handle)); + + // 3. Communication loop + uint8_t buffer[4096]; + uint32_t received_size; + + while (1) { + esp_isotp_poll(isotp_handle); // CRITICAL: Call every 1-10ms + + if (esp_isotp_receive(isotp_handle, buffer, sizeof(buffer), &received_size) == ESP_OK) { + printf("Received %lu bytes\n", received_size); + esp_isotp_send(isotp_handle, buffer, received_size); // Echo back + } + + vTaskDelay(pdMS_TO_TICKS(5)); + } +} +``` diff --git a/esp_isotp/idf_component.yml b/esp_isotp/idf_component.yml new file mode 100644 index 0000000000..5b8f300f28 --- /dev/null +++ b/esp_isotp/idf_component.yml @@ -0,0 +1,7 @@ +version: "0.1.0" +description: ISO-TP (ISO 15765-2) protocol implementation for ESP-IDF +url: https://github.com/espressif/idf-extra-components/tree/master/esp_isotp +repository: https://github.com/espressif/idf-extra-components.git +issues: https://github.com/espressif/idf-extra-components/issues +dependencies: + idf: ">=5.5.0" diff --git a/esp_isotp/inc/esp_isotp.h b/esp_isotp/inc/esp_isotp.h new file mode 100644 index 0000000000..0afadb2a5d --- /dev/null +++ b/esp_isotp/inc/esp_isotp.h @@ -0,0 +1,147 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +/** + * @file esp_isotp.h + * @brief ISO-TP (ISO 15765-2) Transport Protocol Implementation + * + * ISO-TP enables transmission of data larger than 8 bytes over TWAI networks + * through automatic fragmentation and reassembly. + * + * ## How it Works + * + * **Small packets (≤7 bytes)**: Sent in a single TWAI frame immediately. + * **Large packets (>7 bytes)**: Split into multiple frames - first frame sent immediately, + * remaining frames sent during esp_isotp_poll() calls. + * + * ## Basic Usage + * ```c + * esp_isotp_handle_t handle; + * esp_isotp_new_transport(twai_node, &config, &handle); + * + * while (1) { + * esp_isotp_poll(handle); // MUST call regularly! + * + * // Send data (non-blocking) + * esp_isotp_send(handle, data, size); + * + * // Check for received data (non-blocking) + * uint16_t received_size; + * if (esp_isotp_receive(handle, buffer, sizeof(buffer), &received_size) == ESP_OK) { + * // Complete message received + * } + * + * vTaskDelay(pdMS_TO_TICKS(5)); + * } + * ``` + */ + +#include "esp_err.h" +#include "esp_twai.h" +#include "esp_twai_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ISO-TP link handle + */ +typedef struct esp_isotp_link_t *esp_isotp_handle_t; + +/** + * @brief Configuration structure for creating a new ISO-TP link + */ +typedef struct { + uint32_t tx_id; /*!< TWAI ID for transmitting ISO-TP frames */ + uint32_t rx_id; /*!< TWAI ID for receiving ISO-TP frames */ + uint32_t tx_buffer_size; /*!< Size of the transmit buffer (max message size to send) */ + uint32_t rx_buffer_size; /*!< Size of the receive buffer (max message size to receive) */ +} esp_isotp_config_t; + +/** + * @brief Create a new ISO-TP link + * + * @param twai_node TWAI node handle + * @param config Pointer to the configuration structure + * @param[out] out_handle Pointer to store the created ISO-TP handle + * @return esp_err_t + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_NO_MEM: Out of memory + */ +esp_err_t esp_isotp_new_transport(twai_node_handle_t twai_node, const esp_isotp_config_t *config, esp_isotp_handle_t *out_handle); + +/** + * @brief Send data over an ISO-TP link (non-blocking) + * + * Immediately sends first/single frame and returns. For multi-frame messages, + * remaining frames are sent during subsequent esp_isotp_poll() calls. + * + * @param handle ISO-TP handle + * @param data Data to send + * @param size Data length in bytes + * @return + * - ESP_OK: Send initiated successfully + * - ESP_ERR_NOT_FINISHED: Previous send still in progress + * - ESP_ERR_NO_MEM: Data too large for buffer + * - ESP_ERR_INVALID_ARG: Invalid parameters + */ +esp_err_t esp_isotp_send(esp_isotp_handle_t handle, const uint8_t *data, uint32_t size); + +/** + * @brief Extract a complete received message (non-blocking) + * + * This function only extracts data that has already been assembled by esp_isotp_poll(). + * It does NOT process incoming TWAI frames - that happens in esp_isotp_poll(). + * + * Process: TWAI frames → esp_isotp_poll() assembles → esp_isotp_receive() extracts + * + * @param handle ISO-TP handle + * @param data Buffer to store received data + * @param size Buffer size in bytes + * @param[out] received_size Actual received data length + * @return + * - ESP_OK: Complete message extracted and internal buffer cleared + * - ESP_ERR_NOT_FOUND: No complete message ready for extraction + * - ESP_ERR_INVALID_ARG: Invalid parameters + */ +esp_err_t esp_isotp_receive(esp_isotp_handle_t handle, uint8_t *data, uint32_t size, uint32_t *received_size); + +/** + * @brief Poll the ISO-TP link to process messages (CRITICAL - call regularly!) + * + * This function drives the ISO-TP state machine. Call every 1-10ms for proper operation. + * + * What it does: + * - Sends remaining frames for multi-frame messages + * - Processes incoming TWAI frames and assembles complete messages + * - Handles flow control and timeouts + * - Updates internal state machine + * + * Without regular polling: multi-frame sends will stall and receives won't complete. + * + * @param handle ISO-TP handle + * @return + * - ESP_OK: Processing successful + * - ESP_ERR_INVALID_ARG: Invalid parameters + */ +esp_err_t esp_isotp_poll(esp_isotp_handle_t handle); + +/** + * @brief Delete an ISO-TP link + * + * @param handle The handle of the ISO-TP link to delete + * @return + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: Invalid argument + */ +esp_err_t esp_isotp_delete(esp_isotp_handle_t handle); + +#ifdef __cplusplus +} +#endif diff --git a/esp_isotp/src/esp_isotp.c b/esp_isotp/src/esp_isotp.c new file mode 100644 index 0000000000..71b96fb3f9 --- /dev/null +++ b/esp_isotp/src/esp_isotp.c @@ -0,0 +1,199 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "esp_log.h" +#include "esp_timer.h" +#include "esp_twai.h" +#include "esp_check.h" +#include "esp_isotp.h" +// Ensure configuration is included before isotp.h +#include "isotp_config.h" +#include "isotp.h" + +static const char *TAG = "esp_isotp"; + +/** + * @brief ISO-TP link structure + */ +typedef struct esp_isotp_link_t { + IsoTpLink link; + twai_node_handle_t twai_node; + uint8_t *tx_buffer; + uint8_t *rx_buffer; +} esp_isotp_link_t; + +/** + * @brief TWAI receive callback function + * @note This function runs in ISR context + */ +static IRAM_ATTR bool esp_isotp_rx_callback(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx) +{ + esp_isotp_handle_t link_handle = (esp_isotp_handle_t) user_ctx; + + uint8_t frame_data[TWAI_FRAME_MAX_LEN]; + twai_frame_t rx_frame = { + .buffer = frame_data, + .buffer_len = sizeof(frame_data), + }; + + if (twai_node_receive_from_isr(handle, &rx_frame) == ESP_OK) { + if (rx_frame.header.id == link_handle->link.receive_arbitration_id) { + // Process received TWAI message and send flow control frames if needed + isotp_on_can_message(&link_handle->link, frame_data, rx_frame.buffer_len); + } + } + return false; +} + +/// isotp-c library stub function: gets the amount of time passed since the last call in microseconds +uint32_t isotp_user_get_us(void) +{ + return (uint32_t)esp_timer_get_time(); +} + +/// isotp-c library stub function: send twai message +int isotp_user_send_can(const uint32_t arbitration_id, const uint8_t *data, const uint8_t size +#ifdef ISO_TP_USER_SEND_CAN_ARG + , void *user_data +#endif + ) +{ +#ifdef ISO_TP_USER_SEND_CAN_ARG + twai_node_handle_t twai_node = (twai_node_handle_t) user_data; + ESP_RETURN_ON_FALSE(twai_node != NULL, ISOTP_RET_ERROR, TAG, "Invalid TWAI node"); +#else + // Without user_data, we need to get TWAI node from somewhere else + // This shouldn't happen with current design + ESP_LOGE(TAG, "No TWAI node available for TWAI transmission"); + return ISOTP_RET_ERROR; +#endif + + twai_frame_t tx_msg = {0}; + tx_msg.header.id = arbitration_id; + tx_msg.header.ide = false; + tx_msg.header.rtr = false; + tx_msg.buffer = (uint8_t *)data; + tx_msg.buffer_len = size; + + esp_err_t ret = twai_node_transmit(twai_node, &tx_msg, 0); + ESP_RETURN_ON_ERROR(ret, TAG, "Failed to send TWAI frame"); + return ISOTP_RET_OK; +} + +/// isotp-c library stub function: print debug message +void isotp_user_debug(const char *message, ...) +{ + va_list args; + va_start(args, message); + esp_log_writev(ESP_LOG_DEBUG, "isotp_c", message, args); + va_end(args); +} + +esp_err_t esp_isotp_new_transport(twai_node_handle_t twai_node, const esp_isotp_config_t *config, esp_isotp_handle_t *out_handle) +{ + esp_err_t ret = ESP_OK; + esp_isotp_handle_t isotp = NULL; + ESP_RETURN_ON_FALSE(twai_node && config && out_handle, ESP_ERR_INVALID_ARG, TAG, "Invalid parameters"); + + // Allocate memory for handle + isotp = calloc(1, sizeof(esp_isotp_link_t)); + ESP_RETURN_ON_FALSE(isotp, ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for ISO-TP link"); + + // Allocate memory for buffers + isotp->tx_buffer = calloc(config->tx_buffer_size, sizeof(uint8_t)); + isotp->rx_buffer = calloc(config->rx_buffer_size, sizeof(uint8_t)); + ESP_GOTO_ON_FALSE(isotp->rx_buffer && isotp->tx_buffer, ESP_ERR_NO_MEM, err, TAG, "Failed to allocate buffer memory"); + + // Initialize ISO-TP link + isotp_init_link(&isotp->link, config->tx_id, isotp->tx_buffer, + config->tx_buffer_size, isotp->rx_buffer, config->rx_buffer_size); + isotp->link.receive_arbitration_id = config->rx_id; + + // Set user argument for TWAI operations +#ifdef ISO_TP_USER_SEND_CAN_ARG + isotp->link.user_send_can_arg = twai_node; +#endif + + // Register TWAI callback + twai_event_callbacks_t cbs = { + .on_rx_done = esp_isotp_rx_callback, + }; + ret = twai_node_register_event_callbacks(twai_node, &cbs, isotp); + ESP_GOTO_ON_ERROR(ret, err, TAG, "Failed to register event callbacks"); + + // Enable TWAI node + ret = twai_node_enable(twai_node); + ESP_GOTO_ON_ERROR(ret, err, TAG, "Failed to enable TWAI node"); + + isotp->twai_node = twai_node; + *out_handle = isotp; + + return ESP_OK; + +err: + if (isotp->rx_buffer) { + free(isotp->rx_buffer); + } + if (isotp->tx_buffer) { + free(isotp->tx_buffer); + } + free(isotp); + return ret; +} + +esp_err_t esp_isotp_poll(esp_isotp_handle_t handle) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid parameters"); + + // Run ISO-TP state machine to check timeout and send continue frames + isotp_poll(&handle->link); + + return ESP_OK; +} + +esp_err_t esp_isotp_send(esp_isotp_handle_t handle, const uint8_t *data, uint32_t size) +{ + ESP_RETURN_ON_FALSE(handle && data && size, ESP_ERR_INVALID_ARG, TAG, "Invalid parameters"); + + int ret = isotp_send(&handle->link, data, size); + if (ret == ISOTP_RET_OK) { + return ESP_OK; + } else if (ret == ISOTP_RET_INPROGRESS) { + return ESP_ERR_NOT_FINISHED; + } else if (ret == ISOTP_RET_OVERFLOW) { + return ESP_ERR_NO_MEM; + } else { + return ESP_FAIL; + } +} + +esp_err_t esp_isotp_receive(esp_isotp_handle_t handle, uint8_t *data, uint32_t size, uint32_t *received_size) +{ + ESP_RETURN_ON_FALSE(handle && data && size && received_size, ESP_ERR_INVALID_ARG, TAG, "Invalid parameters"); + + int ret = isotp_receive(&handle->link, data, size, received_size); + if (ret == ISOTP_RET_OK) { + return ESP_OK; + } else { + return ESP_FAIL; + } +} + +esp_err_t esp_isotp_delete(esp_isotp_handle_t handle) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid parameters"); + + // Disable TWAI node + ESP_RETURN_ON_ERROR(twai_node_disable(handle->twai_node), TAG, "Failed to disable TWAI node"); + + isotp_destroy_link(&handle->link); + free(handle->tx_buffer); + free(handle->rx_buffer); + free(handle); + return ESP_OK; +}