diff --git a/esp_isotp/examples/echo/CMakeLists.txt b/esp_isotp/examples/echo/CMakeLists.txt new file mode 100644 index 0000000000..a36fca47c1 --- /dev/null +++ b/esp_isotp/examples/echo/CMakeLists.txt @@ -0,0 +1,9 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +project(esp_isotp_echo_example) diff --git a/esp_isotp/examples/echo/README.md b/esp_isotp/examples/echo/README.md new file mode 100644 index 0000000000..4b728de63a --- /dev/null +++ b/esp_isotp/examples/echo/README.md @@ -0,0 +1,55 @@ +# ISO-TP Echo Example + +Simple ISO-TP echo service: receives data and sends it back. Supports single-frame and multi-frame transfers. + +## How to Use Example + +### Hardware Required + +* An ESP32 development board +* A transceiver (e.g., TJA1050) +* An USB cable for power supply and programming + +### Configuration + +Use `idf.py menuconfig` to configure the example: + +- **ISO-TP Echo Configuration → TWAI Basic Configuration**: + - TX GPIO Number (default: GPIO 5) + - RX GPIO Number (default: GPIO 4) + - TWAI Bitrate (default: 500000) + +- **ISO-TP Echo Configuration → ISO-TP Configuration**: + - TX/RX Message IDs (default: 0x7E0/0x7E8) + - Buffer sizes + +Connect the ESP32 to a CAN transceiver and the CAN bus. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +### Example Output + +Once the application is running, you will see the following output: + +``` +I (xxx) isotp_echo: ISO-TP Echo Demo started +I (xxx) isotp_echo: ISO-TP echo example's TX ID: 0x7E0, RX ID: 0x7E8 +``` + +To test the echo functionality, you can use a tool like `can-utils` on a Linux machine connected to the same CAN bus: + +```bash +# Send a message and wait for the echo (using default IDs from Kconfig) +candump -tA -e -c -a vcan0 & +(isotprecv -s 0x7E0 -d 0x7E8 vcan0 | hexdump -C) & (echo 11 22 33 44 55 66 DE AD BE EF | isotpsend -s 0x7E0 -d 0x7E8 vcan0) +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/idf-extra-components/issues) on GitHub. We will get back to you soon. diff --git a/esp_isotp/examples/echo/conftest.py b/esp_isotp/examples/echo/conftest.py new file mode 100644 index 0000000000..ca798fa449 --- /dev/null +++ b/esp_isotp/examples/echo/conftest.py @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 + +import pytest +import subprocess +import logging + + +@pytest.fixture(autouse=True, scope='session') +def setup_vcan_interface(): + """Ensure vcan0 interface is available for QEMU CAN testing.""" + created_interface = False + + try: + # Check if vcan0 exists + result = subprocess.run(['ip', 'link', 'show', 'vcan0'], + capture_output=True, text=True, check=False) + if result.returncode != 0: + logging.info("Creating vcan0 interface...") + # Try creating vcan0 interface + subprocess.run(['sudo', 'ip', 'link', 'add', 'dev', 'vcan0', 'type', 'vcan'], + capture_output=True, text=True, check=False) + created_interface = True + + # Ensure it's up + subprocess.run(['sudo', 'ip', 'link', 'set', 'up', 'vcan0'], + capture_output=True, text=True, check=False) + logging.info("vcan0 interface ready") + + except Exception as e: + logging.warning(f"Could not setup vcan0: {e}. QEMU will handle CAN interface setup.") + + yield # Test execution happens here + + # Cleanup: Remove vcan0 interface if we created it + if created_interface: + try: + subprocess.run(['sudo', 'ip', 'link', 'delete', 'vcan0'], + capture_output=True, text=True, check=False) + logging.info("vcan0 interface cleaned up") + except Exception as e: + logging.warning(f"Could not cleanup vcan0: {e}") \ No newline at end of file diff --git a/esp_isotp/examples/echo/main/CMakeLists.txt b/esp_isotp/examples/echo/main/CMakeLists.txt new file mode 100644 index 0000000000..648b893094 --- /dev/null +++ b/esp_isotp/examples/echo/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "isotp_echo_main.c" + INCLUDE_DIRS ".") diff --git a/esp_isotp/examples/echo/main/Kconfig.projbuild b/esp_isotp/examples/echo/main/Kconfig.projbuild new file mode 100644 index 0000000000..732461ace8 --- /dev/null +++ b/esp_isotp/examples/echo/main/Kconfig.projbuild @@ -0,0 +1,91 @@ +menu "ISO-TP Echo Configuration" + menu "TWAI Basic Configuration" + config EXAMPLE_TX_GPIO_NUM + int "TX GPIO Number" + default 5 + range 0 48 + help + GPIO pin number for TWAI transmission. + + config EXAMPLE_RX_GPIO_NUM + int "RX GPIO Number" + default 4 + range 0 48 + help + GPIO pin number for TWAI reception. + + config EXAMPLE_BITRATE + int "TWAI Bitrate" + default 500000 + range 25000 1000000 + help + TWAI bitrate in bits per second. + Common values: 125000, 250000, 500000, 1000000 + + config EXAMPLE_TWAI_TX_QUEUE_DEPTH + int "TWAI TX Queue Length" + default 16 + range 1 64 + help + Length of the TWAI transmit queue. + + endmenu + + menu "ISO-TP Configuration" + config EXAMPLE_ISOTP_TX_ID + hex "TX Message ID" + default 0x7E8 + help + TWAI ID for transmitting ISO-TP messages. + + config EXAMPLE_ISOTP_RX_ID + hex "RX Message ID" + default 0x7E0 + help + TWAI ID for receiving ISO-TP messages. + + config EXAMPLE_ISOTP_TX_BUFFER_SIZE + int "ISO-TP TX Buffer Size" + default 4096 + range 256 8192 + help + Size of the ISO-TP transmission buffer. + + config EXAMPLE_ISOTP_RX_BUFFER_SIZE + int "ISO-TP RX Buffer Size" + default 4096 + range 256 8192 + help + Size of the ISO-TP reception buffer. + + config EXAMPLE_ISOTP_TX_FRAME_POOL_SIZE + int "ISO-TP TX Frame Pool Size" + default 8 + range 1 32 + help + Number of TX frames in the ISO-TP transmission pool. + endmenu + + menu "Task Configuration" + config EXAMPLE_ECHO_TASK_STACK_SIZE + int "Task Stack Size" + default 4096 + range 2048 32768 + help + Stack size for the echo task. + + config EXAMPLE_ECHO_TASK_PRIORITY + int "Task Priority" + default 10 + range 1 25 + help + FreeRTOS priority for the echo task. + + config EXAMPLE_ECHO_POLL_DELAY_MS + int "Poll Interval (ms)" + default 1 + range 1 100 + help + Delay between consecutive ISO-TP protocol polls. + endmenu +endmenu diff --git a/esp_isotp/examples/echo/main/idf_component.yml b/esp_isotp/examples/echo/main/idf_component.yml new file mode 100644 index 0000000000..9e56e77ab7 --- /dev/null +++ b/esp_isotp/examples/echo/main/idf_component.yml @@ -0,0 +1,4 @@ +## IDF Component Manager Manifest File +dependencies: + esp_isotp: + override_path: '../../../' \ No newline at end of file diff --git a/esp_isotp/examples/echo/main/isotp_echo_main.c b/esp_isotp/examples/echo/main/isotp_echo_main.c new file mode 100644 index 0000000000..bc1c46b9af --- /dev/null +++ b/esp_isotp/examples/echo/main/isotp_echo_main.c @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_twai.h" +#include "esp_twai_onchip.h" +#include "esp_isotp.h" + +static const char *TAG = "isotp_echo"; + +// Global variables for cleanup +static esp_isotp_handle_t g_isotp_handle = NULL; +static twai_node_handle_t g_twai_node = NULL; +static esp_err_t isotp_echo_init(void); +static esp_err_t isotp_echo_deinit(void); + +static void on_tx_done(esp_isotp_handle_t handle, uint32_t tx_size, void *user_arg) +{ + ESP_EARLY_LOGI(TAG, "TX complete: %lu bytes", (unsigned long)tx_size); +} + +static void on_rx_done(esp_isotp_handle_t handle, const uint8_t *data, uint32_t size, void *user_arg) +{ + ESP_EARLY_LOGI(TAG, "RX complete: %lu bytes, echoing back...", (unsigned long)size); + + // Check handle validity using ESP-IDF standard macro for ISR context + ESP_RETURN_VOID_ON_FALSE_ISR(handle, TAG, "Echo send failed: invalid handle"); + + // Echo back the received data immediately (ISR-safe API) + esp_err_t err = esp_isotp_send(handle, data, size); + if (unlikely(err != ESP_OK && err != ESP_ERR_NOT_FINISHED)) { + ESP_EARLY_LOGE(TAG, "Echo send failed: %s", esp_err_to_name(err)); + } + +} + +void app_main(void) +{ + ESP_LOGI(TAG, "ISO-TP Echo Demo started"); + + // Initialize the ISO-TP echo example + ESP_ERROR_CHECK(isotp_echo_init()); + + // Main task will just sleep, the echo_task handles the ISO-TP communication + while (1) { + vTaskDelay(pdMS_TO_TICKS(10000)); + } + + // Deinitialize the ISO-TP echo example + isotp_echo_deinit(); +} + +static void echo_task(void *arg) +{ + esp_isotp_handle_t isotp_handle = (esp_isotp_handle_t)arg; + + ESP_LOGI(TAG, "ISO-TP Echo task started"); + + while (1) { + // Poll ISO-TP protocol state machine (timeouts, consecutive frames, etc.) + ESP_ERROR_CHECK(esp_isotp_poll(isotp_handle)); + + // Small delay to ensure accurate STmin timing and prevent 100% CPU usage + vTaskDelay(pdMS_TO_TICKS(CONFIG_EXAMPLE_ECHO_POLL_DELAY_MS)); + } + + ESP_LOGI(TAG, "ISO-TP Echo task finished"); + vTaskDelete(NULL); +} + +static esp_err_t isotp_echo_init(void) +{ + twai_onchip_node_config_t twai_cfg = { + .io_cfg = { + .tx = CONFIG_EXAMPLE_TX_GPIO_NUM, + .rx = CONFIG_EXAMPLE_RX_GPIO_NUM, + }, + .bit_timing.bitrate = CONFIG_EXAMPLE_BITRATE, + .tx_queue_depth = CONFIG_EXAMPLE_TWAI_TX_QUEUE_DEPTH, + .intr_priority = 0, + }; + + ESP_ERROR_CHECK(twai_new_node_onchip(&twai_cfg, &g_twai_node)); + + esp_isotp_config_t isotp_cfg = { + .tx_id = CONFIG_EXAMPLE_ISOTP_TX_ID, + .rx_id = CONFIG_EXAMPLE_ISOTP_RX_ID, + .tx_buffer_size = CONFIG_EXAMPLE_ISOTP_TX_BUFFER_SIZE, + .rx_buffer_size = CONFIG_EXAMPLE_ISOTP_RX_BUFFER_SIZE, + .tx_frame_pool_size = CONFIG_EXAMPLE_ISOTP_TX_FRAME_POOL_SIZE, + .rx_callback = on_rx_done, + .tx_callback = on_tx_done, + .callback_arg = NULL, + }; + + ESP_ERROR_CHECK(esp_isotp_new_transport(g_twai_node, &isotp_cfg, &g_isotp_handle)); + + // Create echo task + BaseType_t task_ret = xTaskCreate(echo_task, "isotp_echo", CONFIG_EXAMPLE_ECHO_TASK_STACK_SIZE, + g_isotp_handle, CONFIG_EXAMPLE_ECHO_TASK_PRIORITY, NULL); + ESP_RETURN_ON_FALSE(task_ret == pdPASS, ESP_FAIL, TAG, "Failed to create echo task"); + + ESP_LOGI(TAG, "ISO-TP echo example's TX ID: 0x%X, RX ID: 0x%X", + CONFIG_EXAMPLE_ISOTP_TX_ID, CONFIG_EXAMPLE_ISOTP_RX_ID); + + return ESP_OK; +} + +static esp_err_t isotp_echo_deinit(void) +{ + ESP_RETURN_ON_FALSE(g_isotp_handle != NULL, ESP_OK, TAG, "ISO-TP echo example is not initialized"); + + esp_isotp_delete(g_isotp_handle); + g_isotp_handle = NULL; + + if (g_twai_node) { + twai_node_delete(g_twai_node); + g_twai_node = NULL; + } + + ESP_LOGI(TAG, "ISO-TP echo example deinitialized"); + return ESP_OK; +} diff --git a/esp_isotp/examples/echo/pytest_isotp_echo.py b/esp_isotp/examples/echo/pytest_isotp_echo.py new file mode 100644 index 0000000000..ac512774a4 --- /dev/null +++ b/esp_isotp/examples/echo/pytest_isotp_echo.py @@ -0,0 +1,229 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 + +import pytest +import time +import can +import isotp +import threading +import logging +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize +from typing import List, Tuple + +# Test configuration - match sdkconfig defaults +ESP32C3_RX_ID = 0x7E0 # PC sends to ESP32C3 +ESP32C3_TX_ID = 0x7E8 # PC receives from ESP32C3 +DEFAULT_TIMEOUT = 5.0 + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +class ISOTPTester: + """ + A robust tester for ISO-TP echo functionality, designed for clear and maintainable testing. + This class can be used as a context manager to ensure proper resource handling. + """ + + def __init__(self, dut: Dut, block_size: int = 8, stmin: int = 3, rx_timeout: int = 2000) -> None: + self.dut = dut + self.bus = None + self.isotp_stack = None + self.running = False + self.process_thread = None + self.block_size = block_size + self.stmin = stmin + self.rx_timeout = rx_timeout + + def __enter__(self) -> 'ISOTPTester': + """Enter the runtime context and connect to the CAN bus.""" + try: + self.bus = can.Bus(interface='socketcan', channel='vcan0') + address = isotp.Address(txid=ESP32C3_RX_ID, rxid=ESP32C3_TX_ID) + self.isotp_stack = isotp.CanStack( + bus=self.bus, + address=address, + params={'stmin': self.stmin, 'blocksize': self.block_size, 'wftmax': 0, 'rx_flowcontrol_timeout': self.rx_timeout} + ) + self.running = True + self.process_thread = threading.Thread(target=self._process, daemon=True) + self.process_thread.start() + logging.info(f"Successfully connected to CAN bus with BS={self.block_size}, STmin={self.stmin}") + except Exception as e: + logging.error(f"Failed to connect to CAN bus: {e}") + self.close() + raise + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + """Exit the runtime context and clean up resources.""" + self.close() + + def _process(self) -> None: + """Background thread for processing ISO-TP stack events.""" + while self.running: + if self.isotp_stack: + self.isotp_stack.process() + time.sleep(0.001) + + def test_echo(self, data: bytes, test_name: str, timeout: float = DEFAULT_TIMEOUT) -> bool: + """ + Sends data and verifies that the received echo matches the original data. + + Args: + data: The byte string to be sent. + test_name: A descriptive name for the test case. + timeout: The maximum time to wait for the echo. + + Returns: + True if the test passes, False otherwise. + """ + if not self.isotp_stack: + logging.error(f"{test_name}: ISO-TP stack not available.") + return False + + frame_type = 'SF' if len(data) <= 7 else 'MF' + logging.info(f"Running test: {test_name} ({len(data)} bytes, {frame_type})") + + try: + send_start = time.time() + self.isotp_stack.send(data) + send_duration = time.time() - send_start + + recv_start = time.time() + while time.time() - recv_start < timeout: + if self.isotp_stack.available(): + received = self.isotp_stack.recv() + recv_duration = time.time() - recv_start + + if received == data: + logging.info(f"'{test_name}' PASSED (send: {send_duration:.3f}s, recv: {recv_duration:.3f}s)") + return True + else: + logging.error(f"'{test_name}' FAILED: Data mismatch. Got {len(received)} bytes, expected {len(data)}.") + return False + time.sleep(0.01) + + logging.error(f"'{test_name}' FAILED: Timeout after {timeout}s.") + return False + + except Exception as e: + logging.error(f"'{test_name}' FAILED with exception: {e}") + return False + + def close(self) -> None: + """Shuts down the CAN bus connection and stops the processing thread.""" + self.running = False + if self.process_thread and self.process_thread.is_alive(): + self.process_thread.join(timeout=1.0) + if self.bus: + self.bus.shutdown() + logging.info("Connection closed and resources released.") + +# Test data for basic and robustness tests +ALL_TEST_CASES = { + 'basic': [ + # Single Frame (SF) tests + (b'\x01', 'SF-1: 1 byte'), + (b'\x01\x02\x03\x04', 'SF-4: 4 bytes'), + (b'\x01\x02\x03\x04\x05\x06\x07', 'SF-7: Max SF'), + + # Multi-Frame (MF) tests + (bytes(range(8)), 'MF-8: Min MF'), + (bytes(range(16)), 'MF-16: 2 CF'), + + # Pattern tests + (b'\x00' * 8, 'MF-Pattern: All 0x00'), + (b'\xAA' * 32, 'MF-Pattern: All 0xAA'), + ], + 'robustness': [ + (b'\x00', 'Single null byte'), + (bytes(range(16)), 'Sequential pattern'), + (b'\xAA\x55' * 8, 'Alternating pattern'), + (b'\x00\xFF' * 8, 'Mixed pattern'), + (b'\x11\x22\x33\x44' * 4, 'Repeated pattern'), + ] +} + +@pytest.fixture(scope="function", autouse=True) +def ensure_dut_ready(dut: Dut) -> None: + """Auto-fixture to ensure the DUT is ready before running tests.""" + dut.expect("ISO-TP Echo Demo started", timeout=30) + # Wait a bit for system to fully initialize + time.sleep(2) + logging.info("DUT started and ISO-TP echo system initialized.") + +def run_test_batch(dut: Dut, test_cases: List[Tuple[bytes, str]]) -> None: + """Helper function to run a batch of test cases.""" + with ISOTPTester(dut) as tester: + for data, test_name in test_cases: + assert tester.test_echo(data, test_name), f"Test '{test_name}' failed" + time.sleep(0.2) # Brief pause between tests + +@pytest.mark.qemu +@pytest.mark.parametrize("category", ['basic', 'robustness']) +@idf_parametrize('target', ['esp32c3'], indirect=['target']) +def test_echo_basic_and_robustness(dut: Dut, target: str, category: str) -> None: + """ + Parameterized test for basic echo functionality and robustness edge cases. + """ + test_cases = ALL_TEST_CASES[category] + run_test_batch(dut, test_cases) + +@pytest.mark.qemu +@idf_parametrize('target', ['esp32c3'], indirect=['target']) +def test_echo_flow_control(dut: Dut, target: str) -> None: + """ + Tests different flow control (FC) parameters (Block Size and STmin). + This validates the ISO-TP protocol compliance under various FC conditions. + """ + flow_control_configs = [ + {'block_size': 4, 'stmin': 0, 'description': 'block size 4'}, + {'block_size': 8, 'stmin': 2, 'description': 'With STmin delay'}, + ] + test_data = bytes(range(64)) # Use a consistent multi-frame payload + + with ISOTPTester(dut) as tester: + for config in flow_control_configs: + logging.info(f"Testing Flow Control: {config['description']}") + address = isotp.Address(txid=ESP32C3_RX_ID, rxid=ESP32C3_TX_ID) + tester.isotp_stack = isotp.CanStack( + bus=tester.bus, + address=address, + params={'stmin': config['stmin'], 'blocksize': config['block_size'], 'wftmax': 0} + ) + test_name = f"FC-{config['description']}" + assert tester.test_echo(test_data, test_name), f"Flow control test '{test_name}' failed" + +@pytest.mark.qemu +@idf_parametrize('target', ['esp32c3'], indirect=['target']) +def test_echo_long_data(dut: Dut, target: str) -> None: + """ + Tests long data transmission with 20 iterations of 2K chunks. + Total 60KB data simulating firmware transmission. + """ + chunk_size = 2048 + iterations = 30 # 60KB total + test_data = bytes(range(256)) * 8 # 2K pattern data + + logging.info(f"Starting long data test: {iterations} x {chunk_size} bytes = {iterations * chunk_size / 1024:.1f}KB") + + with ISOTPTester(dut) as tester: + start_time = time.time() + for i in range(iterations): + test_name = f"Long-data-{i+1}/{iterations}" + assert tester.test_echo(test_data, test_name, timeout=10.0), f"Iteration {i+1} failed" + + # Brief pause and progress report every 10 chunks + time.sleep(0.2) # Small delay between chunks + if (i + 1) % 10 == 0: + elapsed = time.time() - start_time + progress = (i + 1) / iterations * 100 + speed = (i + 1) * chunk_size / elapsed / 1024 # KB/s + logging.info(f"Progress: {progress:.1f}% ({i+1}/{iterations}) - Speed: {speed:.1f} KB/s") + + total_time = time.time() - start_time + total_size = iterations * chunk_size / 1024 # KB + avg_speed = total_size / total_time # KB/s + logging.info(f"Long data test completed: {total_size:.1f}KB in {total_time:.1f}s - Speed: {avg_speed:.1f} KB/s") + diff --git a/esp_isotp/examples/echo/sdkconfig.defaults b/esp_isotp/examples/echo/sdkconfig.defaults new file mode 100644 index 0000000000..0576d13ca6 --- /dev/null +++ b/esp_isotp/examples/echo/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_TWAI_ISR_IN_IRAM=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_ISO_TP_DEFAULT_ST_MIN_US=3000 diff --git a/esp_isotp/pytest.ini b/esp_isotp/pytest.ini new file mode 100644 index 0000000000..d07a58636b --- /dev/null +++ b/esp_isotp/pytest.ini @@ -0,0 +1,23 @@ +[pytest] +# ESP-IDF ISO-TP component test configuration +python_files = pytest_*.py + +addopts = + -s -vv + --embedded-services=idf,qemu + --tb=short + --qemu-extra-args="-object can-bus,id=canbus0 -object can-host-socketcan,id=canhost0,if=vcan0,canbus=canbus0 -global driver=esp32c3.twai,property=canbus,value=canbus0 -machine esp32c3" + +markers = + generic: generic runner + qemu: QEMU runner + +# log related +log_cli = True +log_cli_level = INFO +log_cli_format = %(asctime)s %(levelname)s %(message)s +log_cli_date_format = %Y-%m-%d %H:%M:%S + +# ignore warnings +filterwarnings = + ignore::_pytest.warning_types.PytestExperimentalApiWarning \ No newline at end of file diff --git a/esp_isotp/src/esp_isotp.c b/esp_isotp/src/esp_isotp.c index 2166a40b90..c86a47e9a8 100644 --- a/esp_isotp/src/esp_isotp.c +++ b/esp_isotp/src/esp_isotp.c @@ -208,6 +208,9 @@ int isotp_user_send_can(const uint32_t arbitration_id, const uint8_t *data, cons esp_isotp_handle_t isotp_handle = (esp_isotp_handle_t) user_data; ESP_RETURN_ON_FALSE_ISR(isotp_handle != NULL, ISOTP_RET_ERROR, TAG, "Invalid ISO-TP handle"); + // Size validation - TWAI frames are max 8 bytes by protocol + ESP_RETURN_ON_FALSE_ISR(size <= 8, ISOTP_RET_ERROR, TAG, "Invalid TWAI frame size"); + twai_node_handle_t twai_node = isotp_handle->twai_node; // Get a pre-allocated frame from the SLIST pool. @@ -223,9 +226,6 @@ int isotp_user_send_can(const uint32_t arbitration_id, const uint8_t *data, cons tx_frame->frame.header.id = arbitration_id; tx_frame->frame.header.ide = is_extended_id(arbitration_id); // Extended (29-bit) vs Standard (11-bit) ID - // Size validation - TWAI frames are max 8 bytes by protocol - ESP_RETURN_ON_FALSE_ISR(size <= 8, ISOTP_RET_ERROR, TAG, "Invalid TWAI frame size"); - // Copy payload into the embedded buffer to ensure data lifetime during async transmission. memcpy(tx_frame->data_payload, data, size);