- Add lora_receive_continuous() helper and use it after init and TX (ACK/time sync) - Ensure receiver returns to RX immediately after lora_send - Document continuous RX behavior in README
159 lines
4.3 KiB
C++
159 lines
4.3 KiB
C++
#include "lora_transport.h"
|
|
#include <LoRa.h>
|
|
#include <SPI.h>
|
|
#include <math.h>
|
|
|
|
static uint16_t crc16_ccitt(const uint8_t *data, size_t len) {
|
|
uint16_t crc = 0xFFFF;
|
|
for (size_t i = 0; i < len; ++i) {
|
|
crc ^= static_cast<uint16_t>(data[i]) << 8;
|
|
for (uint8_t b = 0; b < 8; ++b) {
|
|
if (crc & 0x8000) {
|
|
crc = (crc << 1) ^ 0x1021;
|
|
} else {
|
|
crc <<= 1;
|
|
}
|
|
}
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
void lora_init() {
|
|
SPI.begin(PIN_LORA_SCK, PIN_LORA_MISO, PIN_LORA_MOSI, PIN_LORA_NSS);
|
|
LoRa.setPins(PIN_LORA_NSS, PIN_LORA_RST, PIN_LORA_DIO0);
|
|
LoRa.begin(LORA_FREQUENCY);
|
|
LoRa.setSpreadingFactor(LORA_SPREADING_FACTOR);
|
|
LoRa.setSignalBandwidth(LORA_BANDWIDTH);
|
|
LoRa.setCodingRate4(LORA_CODING_RATE);
|
|
LoRa.enableCrc();
|
|
LoRa.setSyncWord(LORA_SYNC_WORD);
|
|
}
|
|
|
|
bool lora_send(const LoraPacket &pkt) {
|
|
if (LORA_SEND_BYPASS) {
|
|
return true;
|
|
}
|
|
uint8_t buffer[5 + LORA_MAX_PAYLOAD + 2];
|
|
size_t idx = 0;
|
|
buffer[idx++] = pkt.protocol_version;
|
|
buffer[idx++] = static_cast<uint8_t>(pkt.role);
|
|
buffer[idx++] = static_cast<uint8_t>(pkt.device_id_short >> 8);
|
|
buffer[idx++] = static_cast<uint8_t>(pkt.device_id_short & 0xFF);
|
|
buffer[idx++] = static_cast<uint8_t>(pkt.payload_type);
|
|
|
|
if (pkt.payload_len > LORA_MAX_PAYLOAD) {
|
|
return false;
|
|
}
|
|
|
|
memcpy(&buffer[idx], pkt.payload, pkt.payload_len);
|
|
idx += pkt.payload_len;
|
|
|
|
uint16_t crc = crc16_ccitt(buffer, idx);
|
|
buffer[idx++] = static_cast<uint8_t>(crc >> 8);
|
|
buffer[idx++] = static_cast<uint8_t>(crc & 0xFF);
|
|
|
|
LoRa.beginPacket();
|
|
LoRa.write(buffer, idx);
|
|
int result = LoRa.endPacket();
|
|
return result == 1;
|
|
}
|
|
|
|
bool lora_receive(LoraPacket &pkt, uint32_t timeout_ms) {
|
|
uint32_t start = millis();
|
|
while (true) {
|
|
int packet_size = LoRa.parsePacket();
|
|
if (packet_size > 0) {
|
|
if (packet_size < 7) {
|
|
while (LoRa.available()) {
|
|
LoRa.read();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint8_t buffer[5 + LORA_MAX_PAYLOAD + 2];
|
|
size_t len = 0;
|
|
while (LoRa.available() && len < sizeof(buffer)) {
|
|
buffer[len++] = LoRa.read();
|
|
}
|
|
|
|
if (len < 7) {
|
|
return false;
|
|
}
|
|
|
|
uint16_t crc_calc = crc16_ccitt(buffer, len - 2);
|
|
uint16_t crc_rx = static_cast<uint16_t>(buffer[len - 2] << 8) | buffer[len - 1];
|
|
if (crc_calc != crc_rx) {
|
|
return false;
|
|
}
|
|
if (buffer[0] != PROTOCOL_VERSION) {
|
|
return false;
|
|
}
|
|
|
|
pkt.protocol_version = buffer[0];
|
|
pkt.role = static_cast<DeviceRole>(buffer[1]);
|
|
pkt.device_id_short = static_cast<uint16_t>(buffer[2] << 8) | buffer[3];
|
|
pkt.payload_type = static_cast<PayloadType>(buffer[4]);
|
|
pkt.payload_len = len - 7;
|
|
if (pkt.payload_len > LORA_MAX_PAYLOAD) {
|
|
return false;
|
|
}
|
|
memcpy(pkt.payload, &buffer[5], pkt.payload_len);
|
|
pkt.rssi_dbm = static_cast<int16_t>(LoRa.packetRssi());
|
|
pkt.snr_db = LoRa.packetSnr();
|
|
return true;
|
|
}
|
|
|
|
if (timeout_ms == 0 || millis() - start >= timeout_ms) {
|
|
return false;
|
|
}
|
|
delay(5);
|
|
}
|
|
}
|
|
|
|
void lora_idle() {
|
|
LoRa.idle();
|
|
}
|
|
|
|
void lora_sleep() {
|
|
LoRa.sleep();
|
|
}
|
|
|
|
void lora_receive_continuous() {
|
|
LoRa.receive();
|
|
}
|
|
|
|
bool lora_receive_window(LoraPacket &pkt, uint32_t timeout_ms) {
|
|
if (timeout_ms == 0) {
|
|
return false;
|
|
}
|
|
LoRa.receive();
|
|
bool got = lora_receive(pkt, timeout_ms);
|
|
LoRa.sleep();
|
|
return got;
|
|
}
|
|
|
|
uint32_t lora_airtime_ms(size_t packet_len) {
|
|
if (packet_len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
const double bw = static_cast<double>(LORA_BANDWIDTH);
|
|
const double sf = static_cast<double>(LORA_SPREADING_FACTOR);
|
|
const double cr = static_cast<double>(LORA_CODING_RATE - 4); // coding rate denominator: 4/(4+cr)
|
|
const double tsym = (1 << LORA_SPREADING_FACTOR) / bw;
|
|
const double t_preamble = (static_cast<double>(LORA_PREAMBLE_LEN) + 4.25) * tsym;
|
|
|
|
const bool low_data_rate_opt = (LORA_SPREADING_FACTOR >= 11) && (LORA_BANDWIDTH <= 125000);
|
|
const double de = low_data_rate_opt ? 1.0 : 0.0;
|
|
const double ih = 0.0;
|
|
const double crc = 1.0;
|
|
|
|
const double payload_symb_nb = 8.0 + max(
|
|
ceil((8.0 * packet_len - 4.0 * sf + 28.0 + 16.0 * crc - 20.0 * ih) / (4.0 * (sf - 2.0 * de))) * (cr + 4.0),
|
|
0.0);
|
|
const double t_payload = payload_symb_nb * tsym;
|
|
const double t_packet = t_preamble + t_payload;
|
|
|
|
return static_cast<uint32_t>(ceil(t_packet * 1000.0));
|
|
}
|