Skip to content

File twai_receiver.h

File List > espos_n2k > include > espos_n2k > twai_receiver.h

Go to the documentation of this file

/* SPDX-FileCopyrightText: 2026 Dirk Wahrheit */
/* SPDX-License-Identifier: Apache-2.0 */
#ifndef COCKPIT_N2K_TWAI_RECEIVER_H_
#define COCKPIT_N2K_TWAI_RECEIVER_H_

#include <atomic>
#include <cstdint>

#include "driver/gpio.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"

#include <functional>
#include "espos_n2k/can_frame.h"

namespace espos_n2k {

struct TwaiReceiverConfig {
  gpio_num_t tx_pin = GPIO_NUM_NC;
  gpio_num_t rx_pin = GPIO_NUM_NC;
  uint32_t bitrate = 250000;  // NMEA 2000 standard
  size_t rx_queue_depth = CONFIG_ESPOS_N2K_RX_QUEUE_DEPTH;
};

class TwaiReceiver {
 public:
  using FrameFn = std::function<void(const CanMessage&)>;
  explicit TwaiReceiver(const TwaiReceiverConfig& config = {});
  ~TwaiReceiver();

  void start();
  void stop();

  void set_on_frame(FrameFn fn) { on_frame_ = std::move(fn); }

  bool ever_received() const {
    return last_rx_us_.load(std::memory_order_relaxed) != 0;
  }

  int64_t seconds_since_last_rx() const {
    int64_t last = last_rx_us_.load(std::memory_order_relaxed);
    if (last == 0) return INT64_MAX;
    return (esp_timer_get_time() - last) / 1000000;
  }

  uint32_t bus_off_count() const;

  bool bus_running() const;

  uint32_t frames_received() const;
  uint32_t frames_dropped() const;

  uint32_t error_count() const;
  uint32_t last_error_flags() const;

 private:
  static void sink(void* ctx, const CanMessage& msg);

  TwaiReceiverConfig config_;
  FrameFn on_frame_;
  std::atomic<bool> running_{false};
  // Microseconds-since-boot of last RX frame; 0 = nothing received yet.
  std::atomic<int64_t> last_rx_us_{0};
};

}  // namespace espos_n2k

#endif  // COCKPIT_N2K_TWAI_RECEIVER_H_