diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index 0899a0dc2b..0768b35507 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -19,6 +19,7 @@ from esphome.components.esp32 import ( import esphome.config_validation as cv from esphome.const import ( CONF_ID, + CONF_MODE, CONF_RX_PIN, CONF_RX_QUEUE_LEN, CONF_TX_PIN, @@ -33,6 +34,13 @@ CONF_TX_ENQUEUE_TIMEOUT = "tx_enqueue_timeout" esp32_can_ns = cg.esphome_ns.namespace("esp32_can") esp32_can = esp32_can_ns.class_("ESP32Can", CanbusComponent) +# Mode options - consistent with MCP2515 component +CanMode = esp32_can_ns.enum("CanMode") +CAN_MODES = { + "NORMAL": CanMode.CAN_MODE_NORMAL, + "LISTENONLY": CanMode.CAN_MODE_LISTEN_ONLY, +} + # Currently the driver only supports a subset of the bit rates defined in canbus # The supported bit rates differ between ESP32 variants. # See ESP-IDF Programming Guide --> API Reference --> Two-Wire Automotive Interface (TWAI) @@ -95,6 +103,7 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend( cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate, cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number, cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_MODE, default="NORMAL"): cv.enum(CAN_MODES, upper=True), cv.Optional(CONF_RX_QUEUE_LEN): cv.uint32_t, cv.Optional(CONF_TX_QUEUE_LEN): cv.uint32_t, cv.Optional(CONF_TX_ENQUEUE_TIMEOUT): cv.positive_time_period_milliseconds, @@ -117,6 +126,7 @@ async def to_code(config): cg.add(var.set_rx(config[CONF_RX_PIN])) cg.add(var.set_tx(config[CONF_TX_PIN])) + cg.add(var.set_mode(config[CONF_MODE])) if (rx_queue_len := config.get(CONF_RX_QUEUE_LEN)) is not None: cg.add(var.set_rx_queue_len(rx_queue_len)) if (tx_queue_len := config.get(CONF_TX_QUEUE_LEN)) is not None: diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index d50964187d..f521b63430 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -75,8 +75,15 @@ bool ESP32Can::setup_internal() { return false; } + // Select TWAI mode based on configuration + twai_mode_t twai_mode = (this->mode_ == CAN_MODE_LISTEN_ONLY) ? TWAI_MODE_LISTEN_ONLY : TWAI_MODE_NORMAL; + + if (this->mode_ == CAN_MODE_LISTEN_ONLY) { + ESP_LOGI(TAG, "CAN bus configured in LISTEN_ONLY mode (passive, no ACKs)"); + } + twai_general_config_t g_config = - TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL); + TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, twai_mode); g_config.controller_id = next_twai_ctrl_num++; if (this->tx_queue_len_.has_value()) { g_config.tx_queue_len = this->tx_queue_len_.value(); @@ -111,6 +118,12 @@ bool ESP32Can::setup_internal() { } canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) { + // In listen-only mode, we cannot transmit + if (this->mode_ == CAN_MODE_LISTEN_ONLY) { + ESP_LOGW(TAG, "Cannot send messages in LISTEN_ONLY mode"); + return canbus::ERROR_FAIL; + } + if (this->twai_handle_ == nullptr) { // not setup yet or setup failed return canbus::ERROR_FAIL; diff --git a/esphome/components/esp32_can/esp32_can.h b/esphome/components/esp32_can/esp32_can.h index dc44aceb36..c3f200271b 100644 --- a/esphome/components/esp32_can/esp32_can.h +++ b/esphome/components/esp32_can/esp32_can.h @@ -10,10 +10,16 @@ namespace esphome { namespace esp32_can { +enum CanMode : uint8_t { + CAN_MODE_NORMAL = 0, + CAN_MODE_LISTEN_ONLY = 1, +}; + class ESP32Can : public canbus::Canbus { public: void set_rx(int rx) { rx_ = rx; } void set_tx(int tx) { tx_ = tx; } + void set_mode(CanMode mode) { mode_ = mode; } void set_tx_queue_len(uint32_t tx_queue_len) { this->tx_queue_len_ = tx_queue_len; } void set_rx_queue_len(uint32_t rx_queue_len) { this->rx_queue_len_ = rx_queue_len; } void set_tx_enqueue_timeout_ms(uint32_t tx_enqueue_timeout_ms) { @@ -28,6 +34,7 @@ class ESP32Can : public canbus::Canbus { int rx_{-1}; int tx_{-1}; + CanMode mode_{CAN_MODE_NORMAL}; TickType_t tx_enqueue_timeout_ticks_{}; optional tx_queue_len_{}; optional rx_queue_len_{}; diff --git a/tests/components/esp32_can/common.yaml b/tests/components/esp32_can/common.yaml index 4349c470f3..3b9b33c048 100644 --- a/tests/components/esp32_can/common.yaml +++ b/tests/components/esp32_can/common.yaml @@ -18,6 +18,7 @@ canbus: tx_pin: ${tx_pin} can_id: 4 bit_rate: 50kbps + mode: NORMAL on_frame: - can_id: 500 then: diff --git a/tests/components/esp32_can/test.esp32-c6-idf.yaml b/tests/components/esp32_can/test.esp32-c6-idf.yaml index 6ef730c378..ac978482fc 100644 --- a/tests/components/esp32_can/test.esp32-c6-idf.yaml +++ b/tests/components/esp32_can/test.esp32-c6-idf.yaml @@ -12,17 +12,7 @@ esphome: canbus_id: esp32_internal_can can_id: 0x100 data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] - - canbus.send: - # Extended ID explicit - canbus_id: esp32_internal_can_2 - use_extended_id: true - can_id: 0x100 - data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] - - canbus.send: - # Standard ID by default - canbus_id: esp32_internal_can_2 - can_id: 0x100 - data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + # Note: esp32_internal_can_2 uses LISTENONLY mode, so no send actions canbus: - platform: esp32_can @@ -31,6 +21,7 @@ canbus: tx_pin: GPIO7 can_id: 4 bit_rate: 50kbps + mode: NORMAL on_frame: - can_id: 500 then: @@ -62,6 +53,7 @@ canbus: tx_pin: GPIO9 can_id: 4 bit_rate: 50kbps + mode: LISTENONLY on_frame: - can_id: 500 then: