Add SCAN_RESULT binary reporting over LoRa

This commit is contained in:
Sassa NF
2024-12-19 20:42:37 +00:00
parent ae7bef7b3e
commit cb4ae1c0d7
6 changed files with 640 additions and 59 deletions
+13
View File
@@ -10,6 +10,9 @@ Comms *HostComms;
Comms *Comms0 = NULL;
Comms *Comms1 = NULL;
RadioComms *RxComms = NULL;
RadioComms *TxComms = NULL;
void _onReceiveUsb(size_t len)
{
if (HostComms == NULL)
@@ -102,6 +105,16 @@ bool Comms::initComms(Config &c)
Serial.println("Configured none - Initialized no communications on Serial1");
}
if (c.rx_lora != NULL)
{
RxComms = new RadioComms("RxComms", radio, *c.rx_lora);
}
if (c.tx_lora != NULL)
{
TxComms = new RadioComms("TxComms", radio, *c.tx_lora);
}
if (!fine)
{
HostComms = new NoopComms();
+24
View File
@@ -2,6 +2,9 @@
#define __COMMS_H
#include <HardwareSerial.h>
#include <LoRaBoards.h>
#include <LiLyGo.h>
#include <config.h>
#ifdef HELTEC
@@ -109,4 +112,25 @@ extern Comms *Comms0;
extern Comms *Comms1;
struct RadioComms
{
String name;
RADIO_TYPE &radio;
LoRaConfig &loraCfg;
RadioComms(String name, RADIO_TYPE &radio, LoRaConfig &cfg)
: name(name), radio(radio), loraCfg(cfg)
{
}
Message **received;
int16_t configureRadio();
int16_t send(Message &);
Message *receive(uint16_t timeout_ms);
};
extern RadioComms *RxComms;
extern RadioComms *TxComms;
#endif
+234
View File
@@ -0,0 +1,234 @@
#include "comms.h"
int16_t RadioComms::configureRadio()
{
int16_t status =
radio.begin(loraCfg.freq, loraCfg.bw, loraCfg.sf, loraCfg.cr, loraCfg.sync_word,
loraCfg.tx_power, loraCfg.preamble_len);
if (status != RADIOLIB_ERR_NONE)
{
Serial.printf("could not configure Lora: %d\n", status);
return status;
}
if (!loraCfg.crc)
{
status = radio.setCRC(0);
if (status != RADIOLIB_ERR_NONE)
{
Serial.printf("could not configure CRC for Lora: %d\n", status);
return status;
}
}
return status;
}
size_t _write(uint8_t *m, size_t sz, size_t p, uint8_t v)
{
if (p >= sz)
return sz;
m[p] = v;
return p + 1;
}
size_t _write(uint8_t *m, size_t sz, size_t p, uint8_t *v, size_t v_sz)
{
while (v_sz > 0)
{
p = _write(m, sz, p, *v);
v++;
v_sz--;
}
return p;
}
#define MAX_MSG 128
#define RSSI_HI -80
#define DETAIL_RSSIS 8
#define RSSI_LO (RSSI_HI - 1 - DETAIL_RSSIS)
int16_t RadioComms::send(Message &m)
{
if (m.type != SCAN_RESULT)
{
return RADIOLIB_ERR_INVALID_FUNCTION;
}
uint8_t msg[MAX_MSG];
size_t dump_sz = m.payload.dump.sz;
size_t p = _write(msg, MAX_MSG, 0, (uint8_t)m.type);
// first cut: dump the RSSI as-is
// optimize the message size later
p = _write(msg, MAX_MSG, p, (uint8_t *)&m.payload.dump.freqs_khz[0], 4);
p = _write(msg, MAX_MSG, p, (uint8_t *)&m.payload.dump.freqs_khz[dump_sz - 1], 4);
p = _write(msg, MAX_MSG, p, (uint8_t *)&dump_sz, 2);
size_t rem = MAX_MSG - p;
if (rem > dump_sz)
rem = dump_sz;
uint8_t bits = 0;
size_t pp = p;
for (int i = 0; i < dump_sz; i++)
{
if (i * rem / dump_sz > p - pp)
{
p = _write(msg, MAX_MSG, p, bits);
bits = 0;
}
int16_t v = m.payload.dump.rssis[i];
if (v >= 0)
{
v = 255;
}
else
{
v += 255;
if (v < 0)
{
v = 0;
}
}
bits = max(bits, (uint8_t)v);
}
if (dump_sz > 0)
{
p = _write(msg, MAX_MSG, p, bits);
}
return radio.transmit(msg, p);
}
size_t _read(uint8_t *buf, size_t sz, size_t p, uint8_t *v, size_t len)
{
for (; p < sz && len > 0; v++, p++, len--)
{
*v = buf[p];
}
return p;
}
size_t _read(uint8_t *buf, size_t sz, size_t p, uint8_t *v)
{
return _read(buf, sz, p, v, 1);
}
volatile bool _received = false;
void _rcv() { _received = true; }
Message *RadioComms::receive(uint16_t timeout_ms)
{
uint8_t msg[MAX_MSG];
// because of this, receive is single-threaded, single-device
_received = false;
radio.setDio1Action(_rcv);
uint32_t timeout_ticks = (uint32_t)timeout_ms * (1000000 / 15625);
int16_t status = radio.startReceive(timeout_ticks);
if (status != RADIOLIB_ERR_NONE)
{
radio.clearDio1Action();
Serial.printf("Failed to start receive: %d\n", status);
return NULL;
}
// wait on a semaphore
while (!_received)
{
yield();
}
radio.clearDio1Action();
size_t len = radio.getPacketLength(true);
uint8_t *packet = msg;
if (len > MAX_MSG)
{
packet = new uint8_t[len];
}
status = radio.readData(packet, len);
if (status == RADIOLIB_ERR_RX_TIMEOUT)
{
return NULL;
}
if (status != RADIOLIB_ERR_NONE || len == 0)
{
if (packet != msg)
delete[] packet;
Serial.printf("Failed to read data: E(%d), len == %d\n", status, len);
return NULL;
}
if (len > MAX_MSG)
{
Serial.printf("Packet is longer than expected: %d\n", len);
}
size_t p;
uint8_t b;
p = _read(packet, len, 0, &b);
Message *message = NULL;
if (b == SCAN_RESULT)
{
message = new Message();
message->type = SCAN_RESULT;
uint32_t s, e;
size_t dump_sz = 0;
p = _read(packet, len, p, (uint8_t *)&s, 4);
p = _read(packet, len, p, (uint8_t *)&e, 4);
p = _read(packet, len, p, (uint8_t *)&dump_sz, 2);
size_t rem = len - p;
message->payload.dump.sz = dump_sz;
if (dump_sz > 0)
{
message->payload.dump.rssis = new int16_t[dump_sz];
message->payload.dump.freqs_khz = new uint32_t[dump_sz];
message->payload.dump.freqs_khz[0] = s;
message->payload.dump.freqs_khz[dump_sz - 1] = e;
for (int i = 1; i < dump_sz - 1; i++)
{
uint32_t incr = (e - s) / (dump_sz - i);
s += incr;
message->payload.dump.freqs_khz[i] = s;
}
for (int i = 0, k = 0; i < rem; i++)
{
int j = (i + 1) * dump_sz / rem;
int16_t rssi = 0;
p = _read(packet, len, p, (uint8_t *)&rssi);
rssi -= 255;
for (; k < j; k++)
{
message->payload.dump.rssis[k] = rssi;
}
}
}
}
else
{
Serial.printf("Received message type %" PRIu8 ", length %d, but expecting %" PRIu8
" - ignoring\n",
b, len, (uint8_t)MessageType::SCAN_RESULT);
}
if (packet != msg)
{
delete[] packet;
}
return message;
}
+187
View File
@@ -88,6 +88,18 @@ Config Config::init()
continue;
}
if (r.key.equalsIgnoreCase("rx_lora"))
{
c.rx_lora = configureLora(r.value);
continue;
}
if (r.key.equalsIgnoreCase("tx_lora"))
{
c.tx_lora = configureLora(r.value);
continue;
}
Serial.printf("Unknown key '%s' will be ignored\n", r.key);
}
@@ -143,9 +155,52 @@ bool Config::updateConfig(String key, String value)
return true;
}
if (key.equalsIgnoreCase("rx_lora"))
{
rx_lora = configureLora(value);
return true;
}
if (key.equalsIgnoreCase("tx_lora"))
{
tx_lora = configureLora(value);
return true;
}
if (key.equalsIgnoreCase("is_host"))
{
String v = value;
bool p = v.equalsIgnoreCase("true");
if (!p && !v.equalsIgnoreCase("false"))
{
Serial.printf("Expected bool for '%s', found '%s' - ignoring\n", key.c_str(),
value.c_str());
}
else
{
is_host = p;
}
return true;
}
return false;
}
String loraConfigToStr(LoRaConfig *cfg)
{
if (cfg == NULL)
{
return String("none");
}
return String("freq:") + String(cfg->freq) + String(",bw:") + String(cfg->bw) +
String(",sf:") + String(cfg->sf) + String(",cr:") + String(cfg->cr) +
String(",tx_power:") + String(cfg->tx_power) + String(",preamble_len:") +
String(cfg->preamble_len) + String(",sync_word:") +
String(cfg->sync_word, 16) + String(",crc:") + String(cfg->crc ? "1" : "0") +
String(",implicit_header:") + String(cfg->implicit_header);
}
String detectionStrategyToStr(Config &c)
{
String res = c.detection_strategy;
@@ -195,6 +250,31 @@ int findSepa(String s, String sepa, int begin, int &end)
return i + sepa.length();
}
uint64_t fromHex(String s)
{
uint64_t r = 0;
for (char c : s)
{
uint64_t v;
if (c >= 'A' && c <= 'F')
{
v = c - 'A' + 10;
}
else if (c >= 'a' && c <= 'f')
{
v = c - 'a' + 10;
}
else
{
v = c - '0';
}
r = (r << 4) + v;
}
return r;
}
uint64_t toUint64(String s)
{
String v = s.substring(0, s.length() % 15);
@@ -269,6 +349,94 @@ ScanRange parseScanRange(String &cfg, int &begin)
return res;
}
LoRaConfig *configureLora(String cfg)
{
if (cfg.equalsIgnoreCase("none"))
{
return NULL;
}
LoRaConfig *lora = new LoRaConfig();
int begin = 0;
int end, i;
while ((i = findSepa(cfg, ",", begin, end)) >= 0)
{
String param = cfg.substring(begin, end);
begin = i;
int j = param.indexOf(":");
if (j < 0)
{
Serial.printf("Expected ':' to be present in '%s' - ignoring config\n",
param);
continue;
}
String k = param.substring(0, j);
if (k.equalsIgnoreCase("sync_word"))
{
lora->sync_word = (uint8_t)fromHex(param.substring(j + 1));
continue;
}
int v = param.substring(j + 1).toInt();
if (k.equalsIgnoreCase("freq"))
{
lora->freq = (uint16_t)v;
continue;
}
if (k.equalsIgnoreCase("bw"))
{
lora->bw = (uint16_t)v;
continue;
}
if (k.equalsIgnoreCase("sf"))
{
lora->sf = (uint8_t)v;
continue;
}
if (k.equalsIgnoreCase("cr"))
{
lora->cr = (uint8_t)v;
continue;
}
if (k.equalsIgnoreCase("tx_power"))
{
lora->tx_power = (uint8_t)v;
continue;
}
if (k.equalsIgnoreCase("preamble_len"))
{
lora->preamble_len = (uint8_t)v;
continue;
}
if (k.equalsIgnoreCase("crc"))
{
lora->crc = v != 0;
continue;
}
if (k.equalsIgnoreCase("implicit_header"))
{
lora->implicit_header = (uint8_t)v;
continue;
}
Serial.printf("Unknown key '%s' will be ignored\n", k);
}
return lora;
}
void Config::configureDetectionStrategy(String cfg)
{
if (scan_ranges_sz > 0)
@@ -329,6 +497,10 @@ bool Config::write_config(const char *path)
f.println("detection_strategy = " + getConfig("detection_strategy"));
f.println("rx_lora = " + getConfig("rx_lora"));
f.println("tx_lora = " + getConfig("tx_lora"));
f.println("is_host = " + getConfig("is_host"));
f.close();
return true;
}
@@ -365,6 +537,21 @@ String Config::getConfig(String key)
return detectionStrategyToStr(*this);
}
if (key.equalsIgnoreCase("rx_lora"))
{
return loraConfigToStr(rx_lora);
}
if (key.equalsIgnoreCase("tx_lora"))
{
return loraConfigToStr(tx_lora);
}
if (key.equalsIgnoreCase("is_host"))
{
return String(is_host ? "true" : "false");
}
return "";
}
+25 -3
View File
@@ -10,6 +10,21 @@ struct ScanRange
uint64_t step_khz;
};
struct LoRaConfig
{
uint16_t freq;
uint16_t bw;
uint8_t sf;
uint8_t cr;
uint8_t tx_power;
uint16_t preamble_len;
uint8_t sync_word;
bool crc;
uint8_t implicit_header;
};
LoRaConfig *configureLora(String cfg);
#define CREATE_MISSING_CONFIG true
struct Config
{
@@ -23,12 +38,19 @@ struct Config
String listen_on_serial0;
String listen_on_serial1;
String listen_on_usb;
LoRaConfig *rx_lora;
LoRaConfig *tx_lora;
bool is_host;
Config()
: create_missing_config(CREATE_MISSING_CONFIG), print_profile_time(false),
detection_strategy("RSSI"), samples(0), scan_ranges_sz(0), scan_ranges(NULL),
log_data_json_interval(1000), listen_on_serial0(String("none")),
listen_on_serial1(String("readline")), listen_on_usb("readline") {};
detection_strategy(String("RSSI")), samples(0), scan_ranges_sz(0),
scan_ranges(NULL), log_data_json_interval(1000),
listen_on_serial0(String("none")), listen_on_serial1(String("readline")),
listen_on_usb(String("readline")), rx_lora(NULL), tx_lora(NULL),
is_host(false) {};
bool write_config(const char *path);
static Config init();
+157 -56
View File
@@ -86,6 +86,8 @@ long noDevicesMillis = 0, cycleCnt = 0;
bool present = false;
bool scanFinished = true;
bool radioIsScan = false;
// time to scan BT
#define BT_SCAN_TIME 10
@@ -533,19 +535,71 @@ StackedChart stacked(display, 0, 0, 0, 0);
UptimeClock *uptime;
int16_t initForScan(float freq)
{
int16_t state;
#if defined(USING_SX1280PA)
state = radio.beginGFSK(freq);
#elif defined(USING_LR1121)
state = radio.beginGFSK(freq, 4.8F, 5.0F, 156.2F, 10, 16U, 1.7F);
#else
state = radio.beginFSK(freq);
#endif
return state;
}
bool setFrequency(float curr_freq)
{
r.current_frequency = curr_freq;
LOG("setFrequency:%f\n", r.current_frequency);
int16_t state;
#ifdef USING_SX1280PA
int16_t state1 =
radio.setFrequency(r.current_frequency); // 1280 doesn't have calibration
state = radio.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF);
if (state != RADIOLIB_ERR_NONE)
{
Serial.println("Error:startReceive:" + String(state));
}
state = state1;
#elif USING_SX1276
state = radio.setFrequency(freq);
#elif defined(USING_LR1121)
state = radio.setFrequency(r.current_frequency,
true); // true = no calibration need here
#else
state = radio.setFrequency(r.current_frequency,
false); // false = calibration is needed here
#endif
if (state != RADIOLIB_ERR_NONE)
{
display.drawString(0, 64 - 10,
"E(" + String(state) +
"):setFrequency:" + String(r.current_frequency));
Serial.println("E(" + String(state) +
"):setFrequency:" + String(r.current_frequency));
display.display();
delay(2);
return false;
}
return true;
}
void init_radio()
{
// initialize SX1262 FSK modem at the initial frequency
both.println("Init radio");
#if defined(USING_SX1280PA)
state = radio.beginGFSK(CONF_FREQ_BEGIN);
#elif defined(USING_LR1121)
state = radio.beginGFSK(CONF_FREQ_BEGIN, 4.8F, 5.0F, 156.2F, 10, 16U, 1.7F);
#else
state = radio.beginFSK(CONF_FREQ_BEGIN);
#endif
state = initForScan(CONF_FREQ_BEGIN);
if (state == RADIOLIB_ERR_NONE)
{
radioIsScan = true;
Serial.println(F("success!"));
}
else
@@ -591,25 +645,9 @@ void init_radio()
}
both.println("Starting scanning...");
// calibrate only once ,,, at startup
// TODO: check documentation (9.2.1) if we must calibrate in certain ranges
#ifdef USING_SX1280PA
state = radio.setFrequency(CONF_FREQ_BEGIN);
if (state != RADIOLIB_ERR_NONE)
{
Serial.println("Error:setFrequency:" + String(state));
}
state = radio.startReceive();
if (state != RADIOLIB_ERR_NONE)
{
Serial.println("Error:startReceive:" + String(state));
}
#elif USING_SX1276
// Sets carrier frequency. Allowed values range from 137.0 MHz to 1020.0 MHz.
radio.setFrequency(CONF_FREQ_BEGIN);
#else
radio.setFrequency(CONF_FREQ_BEGIN, true);
#endif
// calibrate only once ,,, at startup
// TODO: check documentation (9.2.1) if we must calibrate in certain ranges
setFrequency(CONF_FREQ_BEGIN);
delay(50);
}
@@ -1453,6 +1491,12 @@ void checkComms()
// MAX Frequency RSSI BIN value of the samples
int max_rssi_x = 999;
void doScan();
void reportScan(RadioComms &c);
int16_t checkRadio(RadioComms &c);
void loop(void)
{
r.led_flag = false;
@@ -1462,6 +1506,38 @@ void loop(void)
checkComms();
if (config.is_host)
{
if (TxComms != NULL)
{
// NB: swapping the use of Tx and Rx comms, so a pair of modules
// with identical rx/tx_lora config can talk
int16_t status = checkRadio(*TxComms);
if (status != RADIOLIB_ERR_NONE)
{
Serial.printf("Error getting a message: %d\n", status);
}
}
}
else
{
doScan();
if (TxComms != NULL)
reportScan(*TxComms);
if (RxComms != NULL)
checkRadio(*RxComms);
}
}
void doScan()
{
if (!radioIsScan)
{
radioIsScan = true;
initForScan(CONF_FREQ_BEGIN);
state = radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_NONE);
}
// reset scan time
if (config.print_profile_time)
{
@@ -1562,36 +1638,7 @@ void loop(void)
// x > STEPS on SCAN_RBW_FACTOR
int display_x = x / SCAN_RBW_FACTOR;
r.current_frequency = (float)curr_freq / 1000.0;
LOG("setFrequency:%f\n", r.current_frequency);
#ifdef USING_SX1280PA
state =
radio.setFrequency(r.current_frequency); // 1280 doesn't have calibration
radio.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF);
#elif USING_SX1276
state = radio.setFrequency(freq);
#elif defined(USING_LR1121)
state = radio.setFrequency(r.current_frequency,
true); // true = no calibration need here
#else
state = radio.setFrequency(r.current_frequency,
false); // false = calibration is needed here
#endif
int radio_error_count = 0;
if (state != RADIOLIB_ERR_NONE)
{
display.drawString(0, 64 - 10,
"E(" + String(state) +
"):setFrequency:" + String(r.current_frequency));
Serial.println("E(" + String(state) +
"):setFrequency:" + String(r.current_frequency));
display.display();
delay(2);
radio_error_count++;
if (radio_error_count > 10)
continue;
}
setFrequency(curr_freq / 1000.0);
LOG("Step:%d Freq: %f\n", x, r.current_frequency);
// SpectralScan Method
@@ -1914,3 +1961,57 @@ void loop(void)
sideBarCol = SIDEBAR_START_COL;
#endif
}
int16_t checkRadio(RadioComms &comms)
{
radioIsScan = false;
int16_t status = comms.configureRadio();
if (status != RADIOLIB_ERR_NONE)
return status;
Message *msg = comms.receive(
config.is_host
? 2000
: 200); // 200ms should be enough to receive 500 bytes at SF 7 and BW 500
if (msg == NULL)
{
return status;
}
if (msg->type == SCAN_RESULT)
{
HostComms->send(*msg);
}
else
{
Serial.printf("Received a message of unsupported type: %d\n", msg->type);
}
delete msg;
return status;
}
void reportScan(RadioComms &comms)
{
radioIsScan = false;
int16_t status = comms.configureRadio();
if (status != RADIOLIB_ERR_NONE)
{
Serial.printf("Failed to configure Radio: %d\n", status);
return;
}
Message m;
m.type = SCAN_RESULT;
m.payload.dump = frequency_scan_result.dump;
status = comms.send(m);
m.payload.dump.sz = 0; // dump is shared, so should not delete underlying arrays
if (status != RADIOLIB_ERR_NONE)
{
Serial.printf("Failed to send scan result: %d\n", status);
return;
}
}