microReticulumTbeam/exercises/205_sustained_link/src/TBeamSupremeLoRaInterface.cpp
John Poole 16507c54e8 Changed:
main.cpp: fixed the “only sends once” bug by replacing last_tx_second with a UTC minute key, so Bob/CY/DAN can send once per active peer every minute at their scheduled second.
TBeamSupremeLoRaInterface.cpp: added RSSI/SNR to receive-side PHY logging, including accepted frames, malformed frames, and simulated PHY drops.
main.cpp: added RSSI/SNR to RX ANNOUNCE and inbound RX LINK establishment logs.
README.md: added updated RSSI/SNR examples and a table for RNSLINKREQ, RNSPROOF_DELAY, RNSPROOF, RNSLRRTT, RNSLINKRX, and RNSDEC.
2026-06-03 20:04:54 -07:00

263 lines
7.1 KiB
C++

#include "TBeamSupremeLoRaInterface.h"
#include <Cryptography/Random.h>
#include <Log.h>
#include <string.h>
#ifndef LORA_CS
#error "LORA_CS not defined"
#endif
#ifndef LORA_DIO1
#error "LORA_DIO1 not defined"
#endif
#ifndef LORA_RESET
#error "LORA_RESET not defined"
#endif
#ifndef LORA_BUSY
#error "LORA_BUSY not defined"
#endif
#ifndef NODE_SLOT_INDEX
#define NODE_SLOT_INDEX 255
#endif
#ifndef SIM_PHY_ENVELOPE
#define SIM_PHY_ENVELOPE 0
#endif
#ifndef SIM_PHY_BLOCK_BOB_CY
#define SIM_PHY_BLOCK_BOB_CY 0
#endif
using namespace RNS;
TBeamSupremeLoRaInterface::TBeamSupremeLoRaInterface(const char* name) : InterfaceImpl(name) {
_IN = true;
_OUT = true;
_bitrate = (double)LORA_SF * ((4.0 / LORA_CR) / (pow(2, LORA_SF) / LORA_BW_KHZ)) * 1000.0;
_HW_MTU = (uint16_t)(LORA_MAX_PAYLOAD * 2);
}
TBeamSupremeLoRaInterface::~TBeamSupremeLoRaInterface() {
stop();
delete _radio;
delete _module;
}
bool TBeamSupremeLoRaInterface::start() {
_online = false;
INFO("LoRa initializing for T-Beam Supreme...");
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
_module = new Module(LORA_CS, LORA_DIO1, LORA_RESET, LORA_BUSY, SPI);
_radio = new SX1262(_module);
int state = _radio->begin(
LORA_FREQ_MHZ,
LORA_BW_KHZ,
LORA_SF,
LORA_CR,
LORA_SYNC_WORD,
LORA_TX_POWER_DBM);
if (state != RADIOLIB_ERR_NONE) {
ERRORF("LoRa init failed, code %d", state);
return false;
}
state = _radio->startReceive();
if (state != RADIOLIB_ERR_NONE) {
ERRORF("LoRa startReceive failed, code %d", state);
return false;
}
_online = true;
INFO("LoRa init succeeded.");
return true;
}
void TBeamSupremeLoRaInterface::stop() {
if (_radio) {
_radio->standby();
}
_online = false;
}
void TBeamSupremeLoRaInterface::loop() {
if (!_online || !_radio) {
return;
}
if (!_radio->checkIrq(RADIOLIB_IRQ_RX_DONE)) {
return;
}
int len = _radio->getPacketLength();
uint8_t rx_buf[RADIO_MAX_PAYLOAD];
int state = _radio->readData(rx_buf, len);
if (state == RADIOLIB_ERR_NONE) {
_last_rssi = _radio->getRSSI();
_last_snr = _radio->getSNR();
uint8_t physical_tx = 255;
if (!unpack_frame(rx_buf, len, physical_tx)) {
_radio->startReceive();
return;
}
Serial.printf("RX PHY: phy=%u len=%d RSSI=%.1f SNR=%.1f frames=%lu\r\n",
(unsigned)physical_tx,
len,
_last_rssi,
_last_snr,
(unsigned long)_phy_rx_frames);
if (len <= 1) {
_radio->startReceive();
return;
}
uint8_t header = rx_buf[0];
uint8_t seq = packet_sequence(header);
if (is_split_packet(header)) {
if (_rx_seq == SEQ_UNSET || _rx_seq != seq) {
_rx_seq = seq;
_rx_buffer.clear();
_rx_buffer.append(rx_buf + 1, len - 1);
} else {
_rx_buffer.append(rx_buf + 1, len - 1);
_rx_seq = SEQ_UNSET;
on_incoming(_rx_buffer);
}
} else {
if (_rx_seq != SEQ_UNSET) {
_rx_buffer.clear();
_rx_seq = SEQ_UNSET;
}
_rx_buffer.clear();
_rx_buffer.append(rx_buf + 1, len - 1);
on_incoming(_rx_buffer);
}
} else if (state != RADIOLIB_ERR_NONE) {
DEBUGF("LoRa readData failed, code %d", state);
}
_radio->startReceive();
}
int TBeamSupremeLoRaInterface::transmit_frame(uint8_t header, const uint8_t* payload, size_t payload_len) {
uint8_t tx_buf[RADIO_MAX_PAYLOAD];
#if SIM_PHY_ENVELOPE
tx_buf[0] = PHY_MAGIC_0;
tx_buf[1] = PHY_MAGIC_1;
tx_buf[2] = PHY_VERSION;
tx_buf[3] = (uint8_t)NODE_SLOT_INDEX;
tx_buf[PHY_ENVELOPE_LEN] = header;
memcpy(tx_buf + PHY_ENVELOPE_LEN + 1, payload, payload_len);
return _radio->transmit(tx_buf, PHY_ENVELOPE_LEN + 1 + payload_len);
#else
tx_buf[0] = header;
memcpy(tx_buf + 1, payload, payload_len);
return _radio->transmit(tx_buf, 1 + payload_len);
#endif
}
bool TBeamSupremeLoRaInterface::unpack_frame(uint8_t* frame, int& len, uint8_t& physical_tx) {
#if SIM_PHY_ENVELOPE
if (len < PHY_ENVELOPE_LEN + 1) {
++_phy_bad_frames;
Serial.printf("RX PHY BAD: reason=short len=%d RSSI=%.1f SNR=%.1f bad=%lu\r\n",
len,
_last_rssi,
_last_snr,
(unsigned long)_phy_bad_frames);
DEBUGF("SIM PHY malformed: short frame len=%d", len);
return false;
}
if (frame[0] != PHY_MAGIC_0 || frame[1] != PHY_MAGIC_1 || frame[2] != PHY_VERSION) {
++_phy_bad_frames;
Serial.printf("RX PHY BAD: reason=envelope len=%d RSSI=%.1f SNR=%.1f bad=%lu\r\n",
len,
_last_rssi,
_last_snr,
(unsigned long)_phy_bad_frames);
DEBUGF("SIM PHY malformed: bad envelope len=%d", len);
return false;
}
physical_tx = frame[3];
if (should_drop_physical_tx(physical_tx)) {
++_phy_blocked_frames;
Serial.printf("SIM PHY DROP: rx=%u tx=%u len=%d RSSI=%.1f SNR=%.1f blocked=%lu\r\n",
(unsigned)NODE_SLOT_INDEX,
(unsigned)physical_tx,
len,
_last_rssi,
_last_snr,
(unsigned long)_phy_blocked_frames);
DEBUGF("SIM PHY DROP: rx=%u tx=%u len=%d",
(unsigned)NODE_SLOT_INDEX,
(unsigned)physical_tx,
len);
return false;
}
_last_physical_tx = physical_tx;
++_phy_rx_frames;
len -= PHY_ENVELOPE_LEN;
memmove(frame, frame + PHY_ENVELOPE_LEN, len);
return true;
#else
(void)frame;
physical_tx = 255;
_last_physical_tx = physical_tx;
++_phy_rx_frames;
return true;
#endif
}
bool TBeamSupremeLoRaInterface::should_drop_physical_tx(uint8_t physical_tx) {
#if SIM_PHY_BLOCK_BOB_CY
const uint8_t local = (uint8_t)NODE_SLOT_INDEX;
return (local == 1U && physical_tx == 2U) || (local == 2U && physical_tx == 1U);
#else
(void)physical_tx;
return false;
#endif
}
void TBeamSupremeLoRaInterface::send_outgoing(const Bytes& data) {
if (!_online || !_radio) {
return;
}
try {
uint8_t rand_nibble = (uint8_t)(Cryptography::randomnum(256)) & 0xF0;
if ((int)data.size() <= LORA_MAX_PAYLOAD) {
int state = transmit_frame(rand_nibble, data.data(), data.size());
if (state != RADIOLIB_ERR_NONE) {
ERRORF("LoRa transmit failed, code %d", state);
}
} else {
uint8_t seq = (_tx_seq_ctr++) & HEADER_SEQ_MASK;
uint8_t split_header = rand_nibble | HEADER_SPLIT | seq;
int state = transmit_frame(split_header, data.data(), LORA_MAX_PAYLOAD);
if (state != RADIOLIB_ERR_NONE) {
ERRORF("LoRa transmit part 1 failed, code %d", state);
}
size_t remainder = data.size() - LORA_MAX_PAYLOAD;
state = transmit_frame(split_header, data.data() + LORA_MAX_PAYLOAD, remainder);
if (state != RADIOLIB_ERR_NONE) {
ERRORF("LoRa transmit part 2 failed, code %d", state);
}
}
_radio->startReceive();
InterfaceImpl::handle_outgoing(data);
} catch (const std::exception& e) {
ERRORF("LoRa transmit exception: %s", e.what());
}
}
void TBeamSupremeLoRaInterface::on_incoming(const Bytes& data) {
InterfaceImpl::handle_incoming(data);
}