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

157 lines
3.2 KiB
C++

/*
* SPDX-License-Identifier: MIT
* Arduino Stream over an interrupt-driven Zephyr UART.
*/
#include "ZephyrUartStream.h"
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/util.h>
bool ZephyrUartStream::begin(const struct device *dev, void (*on_rx)(void),
void (*on_tx_idle)(void))
{
if (!device_is_ready(dev)) {
return false;
}
_dev = dev;
_on_rx = on_rx;
_on_tx_idle = on_tx_idle;
ring_buf_init(&_rx_ring, sizeof(_rx_data), _rx_data);
ring_buf_init(&_tx_ring, sizeof(_tx_data), _tx_data);
k_sem_init(&_tx_drained, 0, 1);
uart_irq_callback_user_data_set(dev, isr, this);
uart_irq_rx_enable(dev);
return true;
}
void ZephyrUartStream::isr(const struct device *dev, void *user_data)
{
ZephyrUartStream *self = static_cast<ZephyrUartStream *>(user_data);
for (;;) {
uart_irq_update(dev);
if (uart_irq_is_pending(dev) <= 0) {
break;
}
if (uart_irq_rx_ready(dev)) {
uint8_t buf[64];
uint32_t room = MIN(sizeof(buf), ring_buf_space_get(&self->_rx_ring));
if (room == 0) {
/* read() re-enables RX once it has made room. */
atomic_set(&self->_rx_paused, 1);
uart_irq_rx_disable(dev);
} else {
int n = uart_fifo_read(dev, buf, room);
if (n > 0) {
ring_buf_put(&self->_rx_ring, buf, n);
}
}
if (self->_on_rx) {
self->_on_rx();
}
}
if (uart_irq_tx_ready(dev)) {
uint8_t *out;
bool empty;
k_spinlock_key_t key = k_spin_lock(&self->_tx_lock);
uint32_t claimed = MIN(ring_buf_get_ptr(&self->_tx_ring, &out, 0), 64U);
if (claimed > 0) {
int sent = uart_fifo_fill(dev, out, claimed);
if (sent > 0) {
ring_buf_consume(&self->_tx_ring, sent);
}
}
empty = ring_buf_is_empty(&self->_tx_ring);
if (empty) {
/* Inside the lock: write() enables after its put. */
uart_irq_tx_disable(dev);
}
k_spin_unlock(&self->_tx_lock, key);
if (empty) {
k_sem_give(&self->_tx_drained);
if (self->_on_tx_idle) {
self->_on_tx_idle();
}
}
}
}
}
size_t ZephyrUartStream::write(const uint8_t *buffer, size_t size)
{
if (!_dev || size == 0) {
return 0;
}
k_spinlock_key_t key = k_spin_lock(&_tx_lock);
uint32_t n = ring_buf_put(&_tx_ring, buffer, size);
k_spin_unlock(&_tx_lock, key);
if (n > 0) {
uart_irq_tx_enable(_dev);
}
return n;
}
int ZephyrUartStream::availableForWrite()
{
if (!_dev) {
return 0;
}
k_spinlock_key_t key = k_spin_lock(&_tx_lock);
uint32_t space = ring_buf_space_get(&_tx_ring);
k_spin_unlock(&_tx_lock, key);
return (int)space;
}
int ZephyrUartStream::available()
{
return _dev ? (int)ring_buf_size_get(&_rx_ring) : 0;
}
int ZephyrUartStream::read()
{
uint8_t b;
if (!_dev || ring_buf_get(&_rx_ring, &b, 1) != 1) {
return -1;
}
if (atomic_cas(&_rx_paused, 1, 0)) {
uart_irq_rx_enable(_dev);
}
return b;
}
int ZephyrUartStream::peek()
{
uint8_t b;
if (!_dev || ring_buf_peek(&_rx_ring, &b, 1) != 1) {
return -1;
}
return b;
}
void ZephyrUartStream::flush()
{
if (!_dev) {
return;
}
k_sem_reset(&_tx_drained);
k_spinlock_key_t key = k_spin_lock(&_tx_lock);
bool empty = ring_buf_is_empty(&_tx_ring);
k_spin_unlock(&_tx_lock, key);
if (!empty) {
k_sem_take(&_tx_drained, K_MSEC(FLUSH_TIMEOUT_MS));
}
}