Files
Rastislav VysokyandClaude Opus 5.5 93c1a589d5 add KISS modem role (port of upstream examples/kiss_modem)
Raw LoRa packets to and from a host over the USB/console serial port, plus
upstream's SetHardware extensions (identity, crypto, radio config, stats).

- app/KissModem.cpp/h: upstream text, ZephCore changes fenced: an LBT
  refusal returns to CSMA until the wait-clear bound; a TX times out only
  once the radio has concluded (as Dispatcher::loop); SetRadio outside the
  ZC_RADIO_* ranges is refused; msUntilNextTx() for the event loop.
- src/main_kiss_modem.cpp: event-driven composition root; runs the radio
  maintenance the Dispatcher would. Identity in /lfs/repeater/_main.id.
- adapters/transport/ZephyrUartStream: Arduino Stream over IRQ RX/TX rings.
- boards/common/kiss_modem.conf: role, BT off, console driver off.
- LoRaRadio::getCurrentRSSI(), Print::availableForWrite().
- UI mesh-action stubs for every non-companion role.

Tested on 2x XIAO ESP32-C3 + Wio-SX1262 at 869.423 MHz SF7/62.5/CR5.
Built for xiao_esp32c3, rak4631, lora_e5_mini; host tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:44:36 +02:00

53 lines
1.6 KiB
C++

/*
* SPDX-License-Identifier: MIT
* Arduino Stream over an interrupt-driven Zephyr UART (CDC-ACM or plain UART),
* for code ported from upstream that talks to `Serial` (the KISS modem).
*
* RX and TX each go through a ring drained by the UART ISR. A full RX ring
* stops reading the port until the main thread has made room, so the host is
* held off instead of losing bytes. Writes take what fits (availableForWrite()).
* The callbacks run in the ISR: post events only.
*/
#pragma once
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/sys/ring_buffer.h>
#include <Stream.h>
class ZephyrUartStream : public Stream {
public:
/* on_rx: bytes arrived. on_tx_idle: the TX ring has drained. */
bool begin(const struct device *dev, void (*on_rx)(void), void (*on_tx_idle)(void));
using Print::write;
size_t write(uint8_t c) override { return write(&c, 1); }
size_t write(const uint8_t *buffer, size_t size) override;
int availableForWrite() override;
int available() override;
int read() override;
int peek() override;
/* Waits for the TX ring to drain, at most FLUSH_TIMEOUT_MS. */
void flush() override;
private:
static constexpr size_t RX_BUF_SIZE = 512;
static constexpr size_t TX_BUF_SIZE = 1024;
static constexpr uint32_t FLUSH_TIMEOUT_MS = 200;
static void isr(const struct device *dev, void *user_data);
const struct device *_dev = nullptr;
void (*_on_rx)(void) = nullptr;
void (*_on_tx_idle)(void) = nullptr;
uint8_t _rx_data[RX_BUF_SIZE];
uint8_t _tx_data[TX_BUF_SIZE];
struct ring_buf _rx_ring;
struct ring_buf _tx_ring;
struct k_spinlock _tx_lock;
struct k_sem _tx_drained;
atomic_t _rx_paused = ATOMIC_INIT(0);
};