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.
263 lines
7.1 KiB
C++
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);
|
|
}
|