#pragma once #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/optional.h" #include #include namespace esphome { namespace canbus { enum Error : uint8_t { ERROR_OK = 0, ERROR_FAIL = 1, ERROR_ALLTXBUSY = 2, ERROR_FAILINIT = 3, ERROR_FAILTX = 4, ERROR_NOMSG = 5 }; enum CanSpeed : uint8_t { CAN_1KBPS, CAN_5KBPS, CAN_10KBPS, CAN_12K5BPS, CAN_16KBPS, CAN_20KBPS, CAN_25KBPS, CAN_31K25BPS, CAN_33KBPS, CAN_40KBPS, CAN_50KBPS, CAN_80KBPS, CAN_83K3BPS, CAN_95KBPS, CAN_100KBPS, CAN_125KBPS, CAN_200KBPS, CAN_250KBPS, CAN_500KBPS, CAN_800KBPS, CAN_1000KBPS }; class CanbusTrigger; template class CanbusSendAction; /* CAN payload length definitions according to ISO 11898-1 */ static const uint8_t CAN_MAX_DATA_LENGTH = 8; /* Can Frame describes a normative CAN Frame The RTR = Remote Transmission Request is implemented in every CAN controller but rarely used So currently the flag is passed to and from the hardware but currently ignored to the user application. */ struct CanFrame { bool use_extended_id = false; bool remote_transmission_request = false; uint32_t can_id; /* 29 or 11 bit CAN_ID */ uint8_t can_data_length_code; /* frame payload length in byte (0 .. CAN_MAX_DATA_LENGTH) */ uint8_t data[CAN_MAX_DATA_LENGTH] __attribute__((aligned(8))); }; class Canbus : public Component { public: Canbus(){}; void setup() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } void loop() override; canbus::Error send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, const std::vector &data); canbus::Error send_data(uint32_t can_id, bool use_extended_id, const std::vector &data) { // for backwards compatibility only return this->send_data(can_id, use_extended_id, false, data); } void set_can_id(uint32_t can_id) { this->can_id_ = can_id; } void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; } void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; } void add_trigger(CanbusTrigger *trigger); /** * Add a callback to be called when a CAN message is received. All received messages * are passed to the callback without filtering. * * The callback function receives: * - can_id of the received data * - extended_id True if the can_id is an extended id * - rtr If this is a remote transmission request * - data The message data */ void add_callback( std::function &data)> callback) { this->callback_manager_.add(std::move(callback)); } protected: template friend class CanbusSendAction; std::vector triggers_{}; uint32_t can_id_; bool use_extended_id_; CanSpeed bit_rate_; CallbackManager &data)> callback_manager_{}; virtual bool setup_internal() = 0; virtual Error send_message(struct CanFrame *frame) = 0; virtual Error read_message(struct CanFrame *frame) = 0; }; template class CanbusSendAction : public Action, public Parented { public: void set_data_template(std::vector (*func)(Ts...)) { // Stateless lambdas (generated by ESPHome) implicitly convert to function pointers this->data_.func = func; this->len_ = -1; // Sentinel value indicates template mode } // Store pointer to static data in flash (no RAM copy) void set_data_static(const uint8_t *data, size_t len) { this->data_.data = data; this->len_ = len; // Length >= 0 indicates static mode } void set_can_id(uint32_t can_id) { this->can_id_ = can_id; } void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; } void set_remote_transmission_request(bool remote_transmission_request) { this->remote_transmission_request_ = remote_transmission_request; } void play(const Ts &...x) override { auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_; auto use_extended_id = this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_; std::vector data; if (this->len_ >= 0) { // Static mode: copy from flash to vector data.assign(this->data_.data, this->data_.data + this->len_); } else { // Template mode: call function data = this->data_.func(x...); } this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data); } protected: optional can_id_{}; optional use_extended_id_{}; bool remote_transmission_request_{false}; ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length union Data { std::vector (*func)(Ts...); // Function pointer (stateless lambdas) const uint8_t *data; // Pointer to static data in flash } data_; }; class CanbusTrigger : public Trigger, uint32_t, bool>, public Component { friend class Canbus; public: explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const std::uint32_t can_id_mask, const bool use_extended_id) : parent_(parent), can_id_(can_id), can_id_mask_(can_id_mask), use_extended_id_(use_extended_id){}; void set_remote_transmission_request(bool remote_transmission_request) { this->remote_transmission_request_ = remote_transmission_request; } void setup() override { this->parent_->add_trigger(this); } protected: Canbus *parent_; uint32_t can_id_; uint32_t can_id_mask_; bool use_extended_id_; optional remote_transmission_request_{}; }; } // namespace canbus } // namespace esphome