Merge pull request #101 from Genaker/testable

Add support for communication over LoRa
This commit is contained in:
Yegor Shytikov
2024-12-24 14:24:24 -08:00
committed by GitHub
21 changed files with 1695 additions and 413 deletions
+2 -1
View File
@@ -50,6 +50,7 @@ def parse_line(line):
POLY = 0x1021
def crc16(s, c):
c = c ^ 0xffff
for ch in s:
c = c ^ (ord(ch) << 8)
for i in range(8):
@@ -58,7 +59,7 @@ def crc16(s, c):
else:
c = (c << 1) & 0xffff
return c
return c ^ 0xffff
def main():
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''\
+170 -27
View File
@@ -1,4 +1,3 @@
#ifdef SERIAL_OUT
#include "comms.h"
#include <config.h>
@@ -7,7 +6,22 @@
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
Comms *Comms0;
Comms *HostComms;
Comms *Comms0 = NULL;
Comms *Comms1 = NULL;
RadioComms *RxComms = NULL;
RadioComms *TxComms = NULL;
void _onReceiveUsb(size_t len)
{
if (HostComms == NULL)
{
return;
}
HostComms->_onReceive();
}
void _onReceive0()
{
@@ -19,54 +33,104 @@ void _onReceive0()
Comms0->_onReceive();
}
void _onReceive1()
{
if (Comms1 == NULL)
{
return;
}
Comms1->_onReceive();
}
#if ARDUINO_USB_MODE
#define IF_CDC_EVENT(e, data) \
arduino_hw_cdc_event_data_t *data = (arduino_hw_cdc_event_data_t *)event_data; \
if (event_base == ARDUINO_HW_CDC_EVENTS && event_id == ARDUINO_HW_CDC_##e)
#else
#define IF_CDC_EVENT(e, data) \
arduino_usb_cdc_event_data_t *data = (arduino_usb_cdc_event_data_t *)event_data; \
if (event_base == ARDUINO_USB_CDC_EVENTS && event_id == ARDUINO_USB_CDC_##e)
#endif
void _onUsbEvent0(void *arg, esp_event_base_t event_base, int32_t event_id,
void *event_data)
{
if (event_base == ARDUINO_HW_CDC_EVENTS)
{
// arduino_hw_cdc_event_data_t *data = (arduino_hw_cdc_event_data_t *)event_data;
if (event_id == ARDUINO_HW_CDC_RX_EVENT)
{
_onReceive0(/*data->rx.len*/);
}
}
IF_CDC_EVENT(RX_EVENT, data) { _onReceiveUsb(data->rx.len); }
}
bool Comms::initComms(Config &c)
{
bool fine = false;
#ifdef ARDUINO_USB_CDC_ON_BOOT
if (c.listen_on_usb.equalsIgnoreCase("readline"))
{
// comms using readline plaintext protocol
Comms0 = new ReadlineComms(Serial);
HostComms = new ReadlineComms("Host", Serial);
#if ARDUINO_USB_MODE
// if Serial is HWCDC...
Serial.onEvent(ARDUINO_HW_CDC_RX_EVENT, _onUsbEvent0);
#else
// if Serial is USBCDC...
Serial.onEvent(ARDUINO_USB_CDC_RX_EVENT, _onUsbEvent0);
#endif
Serial.begin();
Serial.println("Initialized communications on Serial using readline protocol");
return true;
fine = true;
}
else if (c.listen_on_serial0.equalsIgnoreCase("readline"))
#endif
if (c.listen_on_serial0.equalsIgnoreCase("readline"))
{
// comms using readline plaintext protocol
Comms0 = new ReadlineComms(Serial0);
Serial0.onReceive(_onReceive0, false);
Comms0 = new ReadlineComms("UART0", SERIAL0);
SERIAL0.onReceive(_onReceive0, false);
SERIAL0.begin(115200);
Serial.println("Initialized communications on Serial0 using readline protocol");
return true;
}
if (c.listen_on_serial0.equalsIgnoreCase("none"))
else
{
Comms0 = new NoopComms();
Serial.println("Configured none - Initialized no communications");
return false;
Serial.println("Configured none - Initialized no communications on Serial0");
}
Comms0 = new NoopComms();
Serial.println("Nothing is configured - initialized no communications");
return false;
if (c.listen_on_serial1.equalsIgnoreCase("readline"))
{
// comms using readline plaintext protocol
Comms1 = new ReadlineComms("UART1", Serial1);
Serial1.onReceive(_onReceive1, false);
Serial1.begin(115200);
Serial.println("Initialized communications on Serial1 using readline protocol");
}
else
{
Comms1 = new NoopComms();
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();
Serial.println("Nothing is configured - initialized no communications");
}
return fine;
}
size_t Comms::available() { return received_pos; }
@@ -123,6 +187,7 @@ String _wrap_str(String);
#define POLY 0x1021
uint16_t crc16(String v, uint16_t c)
{
c ^= 0xffff;
for (int i = 0; i < v.length(); i++)
{
uint16_t ch = v.charAt(i);
@@ -140,7 +205,7 @@ uint16_t crc16(String v, uint16_t c)
}
}
return c;
return c ^ 0xffff;
}
void ReadlineComms::_onReceive()
@@ -176,6 +241,10 @@ void ReadlineComms::_onReceive()
delete m;
}
}
else
{
Serial.println(name + ": discarding > " + pack);
}
partialPacket = partialPacket.substring(i + 1);
i = partialPacket.indexOf('\n');
}
@@ -190,16 +259,44 @@ bool ReadlineComms::send(Message &m)
{
case MessageType::SCAN:
p = _scan_str(m.payload.scan);
Serial.println(name + ": the message is: " + p);
break;
case MessageType::SCAN_RESULT:
p = _scan_result_str(m.payload.dump);
break;
case MessageType::CONFIG_TASK:
p = m.payload.config.is_set ? "SET " : "GET ";
p += *m.payload.config.key;
if (m.payload.config.is_set)
{
p += " " + *m.payload.config.value;
}
break;
}
serial.print(_wrap_str(p));
return true;
}
String _stringParam(String &p, String default_v)
{
p.trim();
int i = p.indexOf(' ');
if (i < 0)
{
i = p.length();
}
String v = p.substring(0, i);
p = p.substring(i + 1);
if (i == 0)
{
v = default_v;
}
return v;
}
int64_t _intParam(String &p, int64_t default_v)
{
p.trim();
@@ -267,7 +364,27 @@ Message *_parsePacket(String p)
return m;
}
Serial.println("ignoring unknown message " + p);
if (cmd.equalsIgnoreCase("get"))
{
Message *m = new Message();
m->type = MessageType::CONFIG_TASK;
m->payload.config.is_set = false;
m->payload.config.key = new String(_stringParam(p, ""));
m->payload.config.value = NULL;
return m;
}
if (cmd.equalsIgnoreCase("set"))
{
Message *m = new Message();
m->type = MessageType::CONFIG_TASK;
m->payload.config.is_set = true;
m->payload.config.key = new String(_stringParam(p, ""));
m->payload.config.value = new String(_stringParam(p, ""));
return m;
}
return NULL;
}
@@ -294,4 +411,30 @@ String _wrap_str(String v)
String r = String(v.length()) + "\n" + v;
return "WRAP " + String(crc16(r, 0), 16) + " " + r;
}
#endif
Message::~Message()
{
if (type == SCAN_RESULT)
{
if (payload.dump.sz > 0)
{
delete[] payload.dump.freqs_khz;
delete[] payload.dump.rssis;
payload.dump.sz = 0;
}
return;
}
if (type == CONFIG_TASK)
{
delete payload.config.key;
if (payload.config.is_set)
{
delete payload.config.value;
}
return;
}
}
+53 -7
View File
@@ -1,16 +1,25 @@
#ifndef __COMMS_H
#define __COMMS_H
#ifdef SERIAL_OUT
#include <HardwareSerial.h>
#include <LoRaBoards.h>
#include <LiLyGo.h>
#include <config.h>
#ifndef ARDUINO_USB_CDC_ON_BOOT
#define SERIAL0 Serial
#else
#define SERIAL0 Serial0
#endif
enum MessageType
{
WRAP = 0,
SCAN,
SCAN_RESULT,
_MAX_MESSAGE_TYPE = SCAN_RESULT
CONFIG_TASK,
_MAX_MESSAGE_TYPE = CONFIG_TASK
};
struct Wrapper
@@ -32,19 +41,30 @@ struct ScanTaskResult
int16_t *rssis;
};
struct ConfigTask
{
String *key;
String *value;
bool is_set;
};
struct Message
{
MessageType type;
union
{
Wrapper wrap;
ConfigTask config;
ScanTask scan;
ScanTaskResult dump;
} payload;
~Message();
};
struct Comms
{
String name;
Stream &serial;
Message **received;
size_t received_sz;
@@ -52,8 +72,9 @@ struct Comms
Message *wrap;
Comms(Stream &serial)
: serial(serial), received(NULL), received_sz(0), received_pos(0), wrap(NULL) {};
Comms(String name, Stream &serial)
: name(name), serial(serial), received(NULL), received_sz(0), received_pos(0),
wrap(NULL) {};
virtual size_t available();
virtual bool send(Message &) = 0;
@@ -67,7 +88,7 @@ struct Comms
struct NoopComms : Comms
{
NoopComms() : Comms(Serial0) {};
NoopComms() : Comms("no-op", SERIAL0) {};
virtual bool send(Message &) { return true; };
virtual void _onReceive() {};
@@ -77,14 +98,39 @@ struct ReadlineComms : Comms
{
String partialPacket;
ReadlineComms(Stream &serial) : Comms(serial), partialPacket("") {};
ReadlineComms(String name, Stream &serial)
: Comms(name, serial), partialPacket("") {};
virtual bool send(Message &) override;
virtual void _onReceive() override;
};
extern Comms *HostComms;
extern Comms *Comms0;
#endif
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
+238
View File
@@ -0,0 +1,238 @@
#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];
#ifdef USING_LR1121
Message *message = NULL;
#warning Radio Comms not fully supported for LR1121
#else
// 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;
}
#endif
return message;
}
+451 -27
View File
@@ -83,38 +83,20 @@ Config Config::init()
}
// do something with known keys and values
if (r.key.equalsIgnoreCase("print_profile_time"))
if (c.updateConfig(r.key, r.value))
{
String v = r.value;
bool p = v.equalsIgnoreCase("true");
if (!p && !v.equalsIgnoreCase("false"))
{
Serial.printf("Expected bool for '%s', found '%s' - ignoring\n",
r.key.c_str(), r.value.c_str());
}
else
{
c.print_profile_time = p;
}
continue;
}
if (r.key.equalsIgnoreCase("log_data_json_interval"))
if (r.key.equalsIgnoreCase("rx_lora"))
{
c.log_data_json_interval = r.value.toInt();
c.rx_lora = configureLora(r.value);
continue;
}
if (r.key.equalsIgnoreCase("listen_on_serial0"))
if (r.key.equalsIgnoreCase("tx_lora"))
{
c.listen_on_serial0 = r.value;
continue;
}
if (r.key.equalsIgnoreCase("listen_on_usb"))
{
c.listen_on_serial0 = r.value;
c.tx_lora = configureLora(r.value);
continue;
}
@@ -125,6 +107,391 @@ Config Config::init()
return c;
}
bool Config::updateConfig(String key, String value)
{
if (key.equalsIgnoreCase("print_profile_time"))
{
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
{
print_profile_time = p;
}
return true;
}
if (key.equalsIgnoreCase("log_data_json_interval"))
{
log_data_json_interval = value.toInt();
return true;
}
if (key.equalsIgnoreCase("listen_on_serial0"))
{
listen_on_serial0 = value;
return true;
}
if (key.equalsIgnoreCase("listen_on_serial1"))
{
listen_on_serial1 = value;
return true;
}
if (key.equalsIgnoreCase("listen_on_usb"))
{
listen_on_serial0 = value;
return true;
}
if (key.equalsIgnoreCase("detection_strategy"))
{
configureDetectionStrategy(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;
if (c.scan_ranges_sz > 0)
{
res += ":";
for (int i = 0; i < c.scan_ranges_sz; i++)
{
if (i > 0)
{
res += ",";
}
res += String(c.scan_ranges[i].start_khz);
int s = c.scan_ranges[i].end_khz - c.scan_ranges[i].start_khz;
if (s > 0)
{
res += ".." + String(c.scan_ranges[i].end_khz);
if (c.scan_ranges[i].step_khz < s)
{
res += ":" + String(c.scan_ranges[i].step_khz);
}
}
}
}
return res;
}
// findSepa looks for a sepa in s from position begin, and does two things:
// updates end with the index of the end of the string between begin and
// separator or end of string, and returns index of the next search position
// or -1 to stop the search.
//
// So you can do something like this:
// for (int i = 0, j = 0, k = 0; (i = findSepa(s, sepa, j, k)) >= 0; j = i)
// ...s.substring(j, k)
int findSepa(String s, String sepa, int begin, int &end)
{
int i = s.indexOf(sepa, begin);
if (i < 0)
{
end = s.length();
return begin == end ? -1 : end;
}
end = i;
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);
s = s.substring(v.length());
uint64_t r = v.toInt();
while (s.length() > 0)
{
r = r * ((uint64_t)1000000000000000ll) + s.substring(0, 15).toInt();
s = s.substring(15);
}
return r;
}
ScanRange parseScanRange(String &cfg, int &begin)
{
ScanRange res;
int end;
int i = findSepa(cfg, ",", begin, end);
String r = cfg.substring(begin, end);
begin = i;
if (i < 0)
{
res.start_khz = -1;
res.end_khz = -1;
res.step_khz = -1;
return res;
}
i = r.indexOf("..");
if (i < 0)
{
i = r.length();
}
res.start_khz = toUint64(r.substring(0, i));
if (i == r.length())
{
res.end_khz = res.start_khz;
res.step_khz = 0;
return res;
}
r = r.substring(i + 2);
i = r.indexOf("+");
if (i < 0)
{
i = r.indexOf("/");
if (i < 0)
i = r.length();
}
res.end_khz = toUint64(r.substring(0, i));
if (i == r.length())
{
res.step_khz = res.end_khz - res.start_khz;
}
else
{
res.step_khz = toUint64(r.substring(i + 1));
if (r.charAt(i) == '/')
{
// then it is not a literal increment, it is the number of steps
if (res.step_khz == 0)
res.step_khz = 1;
res.step_khz = round(((double)(res.end_khz - res.start_khz)) / res.step_khz);
}
}
return res;
}
LoRaConfig *configureLora(String cfg)
{
if (cfg.equalsIgnoreCase("none"))
{
return NULL;
}
LoRaConfig *lora = new LoRaConfig({
freq : 0,
bw : 500,
sf : 7,
cr : 5,
tx_power : 1,
preamble_len : 8,
sync_word : 0x1e,
crc : false,
implicit_header : 0
});
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);
param = param.substring(j + 1);
if (k.equalsIgnoreCase("sync_word"))
{
lora->sync_word = (uint8_t)fromHex(param);
continue;
}
if (k.equalsIgnoreCase("freq"))
{
lora->freq = param.toFloat();
continue;
}
int v = param.toInt();
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)
delete[] scan_ranges;
scan_ranges = NULL;
String method = cfg;
int i = cfg.indexOf(":");
if (i >= 0)
{
method = cfg.substring(0, i);
cfg = cfg.substring(i + 1);
}
else
{
cfg = "";
}
samples = 0;
i = method.indexOf(",");
if (i >= 0)
{
samples = method.substring(i + 1).toInt();
method = method.substring(0, i);
}
detection_strategy = method;
scan_ranges_sz = 0;
for (int i = 0, k = 0; (i = findSepa(cfg, ",", i, k)) >= 0; scan_ranges_sz++)
;
if (scan_ranges_sz == 0)
{
return;
}
scan_ranges = new ScanRange[scan_ranges_sz];
for (int i = 0, j = 0; i < scan_ranges_sz; i++)
{
scan_ranges[i] = parseScanRange(cfg, j);
}
}
bool Config::write_config(const char *path)
{
File f = SD.open(path, FILE_WRITE, /*create = */ true);
@@ -133,15 +500,72 @@ bool Config::write_config(const char *path)
return false;
}
f.println("print_profile_time = " + String(print_profile_time ? "true" : "false"));
f.println("log_data_json_interval = " + String(log_data_json_interval));
f.println("listen_on_serial0 = " + listen_on_serial0);
f.println("listen_on_usb = " + listen_on_usb);
f.println("print_profile_time = " + getConfig("print_profile_time"));
f.println("log_data_json_interval = " + getConfig("log_data_json_interval"));
f.println("listen_on_serial0 = " + getConfig("listen_on_serial0"));
f.println("listen_on_serial1 = " + getConfig("listen_on_serial1"));
f.println("listen_on_usb = " + getConfig("listen_on_usb"));
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;
}
String Config::getConfig(String key)
{
if (key.equalsIgnoreCase("print_profile_time"))
{
return String(print_profile_time ? "true" : "false");
}
if (key.equalsIgnoreCase("log_data_json_interval"))
{
return String(log_data_json_interval);
}
if (key.equalsIgnoreCase("listen_on_serial0"))
{
return listen_on_serial0;
}
if (key.equalsIgnoreCase("listen_on_serial1"))
{
return listen_on_serial1;
}
if (key.equalsIgnoreCase("listen_on_usb"))
{
return listen_on_usb;
}
if (key.equalsIgnoreCase("detection_strategy"))
{
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 "";
}
ParseResult parse_config_line(String ln)
{
ln.trim();
+43 -2
View File
@@ -3,22 +3,63 @@
#include <SD.h>
struct ScanRange
{
uint64_t start_khz;
uint64_t end_khz;
uint64_t step_khz;
};
struct LoRaConfig
{
float 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
{
bool create_missing_config;
bool print_profile_time;
String detection_strategy;
int samples;
size_t scan_ranges_sz;
ScanRange *scan_ranges;
int log_data_json_interval;
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),
log_data_json_interval(1000), listen_on_serial0(String("none")),
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),
// Enable Lora Send:
// rx_lora(configureLora("freq:920")),tx_lora(configureLora("freq:916"))
is_host(false) {};
bool write_config(const char *path);
static Config init();
bool updateConfig(String key, String value);
String getConfig(String key);
void configureDetectionStrategy(String cfg);
};
struct ParseResult
+13
View File
@@ -0,0 +1,13 @@
#ifndef LORASA_EVENT_TYPES_H
#define LORASA_EVENT_TYPES_H
struct Event;
enum EventType
{
ALL_EVENTS = 0, // used only at registration time
DETECTED,
SCAN_TASK_COMPLETE,
_MAX_EVENT_TYPE = SCAN_TASK_COMPLETE // unused as event type
};
struct Listener;
#endif
+1 -10
View File
@@ -1,17 +1,8 @@
#ifndef LORASA_EVENTS_H
#define LORASA_EVENTS_H
struct Event;
enum EventType
{
ALL_EVENTS = 0, // used only at registration time
DETECTED,
SCAN_TASK_COMPLETE,
_MAX_EVENT_TYPE = SCAN_TASK_COMPLETE // unused as event type
};
struct Listener;
#include <cstdint>
#include <event_types.h>
#include <scan.h>
struct Event
+82 -89
View File
@@ -1,4 +1,52 @@
#ifndef __LILY_GO_H
#define __LILY_GO_H
#include "RadioLib.h"
#ifdef HELTEC
#ifdef HELTEC_NO_DISPLAY
#define HELTEC_NO_DISPLAY_INSTANCE
#else
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
#include "SSD1306Wire.h"
#endif
#define RADIO_TYPE SX1262
#ifndef HELTEC_NO_RADIO_INSTANCE
#ifndef ARDUINO_heltec_wifi_32_lora_V3
// Assume MISO and MOSI being wrong when not using Heltec's board definition
// and use hspi to make it work anyway. See heltec_setup() for the actual SPI setup.
#include <SPI.h>
extern SPIClass *hspi;
#define RADIO_MODULE_INIT() new Module(SS, DIO1, RST_LoRa, BUSY_LoRa, *hspi);
#else // ARDUINO_heltec_wifi_32_lora_V3
#endif // end ARDUINO_heltec_wifi_32_lora_V3
#endif // end HELTEC_NO_RADIO_INSTANCE
extern RADIO_TYPE radio;
#ifndef HELTEC_NO_DISPLAY_INSTANCE
#ifdef HELTEC_WIRELESS_STICK
#define DISPLAY_GEOMETRY GEOMETRY_64_32
#else
#define DISPLAY_GEOMETRY GEOMETRY_128_64
#endif
#define SCREEN_ADDRESS 0x3C
#define DISPLAY_TYPE SSD1306Wire
#define DISPLAY_INIT() SSD1306Wire(SCREEN_ADDRESS, I2C_SDA, I2C_SCL, DISPLAY_GEOMETRY)
// SH1106Wire display(0x3c, I2C_SDA, I2C_SCL, DISPLAY_GEOMETRY);
#else
#define DISPLAY_TYPE void *
#endif
// This file contains a binary patch for the SX1262
#include "modules/SX126x/patches/SX126x_patch_scan.h"
#else
// Define for our code
#define RST_OLED UNUSED_PIN
#define LED BOARD_LED
@@ -8,9 +56,7 @@
// (See RadioLib_convenience.h)
#define RADIOLIB_DO_DURING_HALT heltec_delay(10)
#include "RadioLib_convenience.h"
#ifdef HELTEC_NO_DISPLAY
#define HELTEC_NO_DISPLAY_INSTANCE
#else
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
// #include "OLEDDisplayUi.h"
@@ -18,41 +64,38 @@
// #include "SSD1306Brzo.h"
#include "SSD1306Wire.h"
#endif
#define ARDUINO_heltec_wifi_32_lora_V3
#ifndef HELTEC_NO_RADIO_INSTANCE
#ifndef ARDUINO_heltec_wifi_32_lora_V3
// Assume MISO and MOSI being wrong when not using Heltec's board definition
// and use hspi to make it work anyway. See heltec_setup() for the actual SPI setup.
#include <SPI.h>
SPIClass *hspi = new SPIClass(2);
SX1262 radio = new Module(SS, DIO1, RST_LoRa, BUSY_LoRa, *hspi);
#else // ARDUINO_heltec_wifi_32_lora_V3
#ifdef USING_SX1280PA
SX1280 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
#define RADIO_TYPE SX1280
#define RADIO_MODULE_INIT() \
new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
#endif // end USING_SX1280PA
#ifdef USING_SX1262
// Default SPI on pins from pins_arduino.h
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
#define RADIO_TYPE SX1262
#define RADIO_MODULE_INIT() \
new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN)
#endif // end USING_SX1262
#ifdef USING_LR1121
// Default SPI on pins from pins_arduino.h
LR1121 radio = new Module(RADIO_CS_PIN, RADIO_DIO9_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
#define RADIO_TYPE LR1121
#define RADIO_MODULE_INIT() \
new Module(RADIO_CS_PIN, RADIO_DIO9_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
#endif // end USING_LR1121
#ifdef USING_SX1276
// Default SPI on pins from pins_arduino.h
SX1276 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
#define RADIO_TYPE SX1276
#define RADIO_MODULE_INIT() \
new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
#endif // end USING_SX1276
#endif // end ARDUINO_heltec_wifi_32_lora_V3
#endif // end HELTEC_NO_RADIO_INSTANCE
void heltec_led(int led) {}
extern RADIO_TYPE radio;
void heltec_deep_sleep() {}
void heltec_led(int led);
void heltec_delay(int millisec) { delay(millisec); }
void heltec_deep_sleep(int sleep_seconds = 0);
void heltec_delay(int millisec);
#ifndef HELTEC_NO_DISPLAY_INSTANCE
/**
* @class PrintSplitter
* @brief A class that splits the output of the Print class to two different
@@ -82,85 +125,35 @@ class PrintSplitter : public Print
Print &b;
};
#ifdef HELTEC_WIRELESS_STICK
#define DISPLAY_GEOMETRY GEOMETRY_64_32
#else
#define DISPLAY_GEOMETRY GEOMETRY_128_64
#endif
#define SCREEN_ADDRESS 0x3C
SSD1306Wire display(SCREEN_ADDRESS, I2C_SDA, I2C_SCL, DISPLAY_GEOMETRY);
#define DISPLAY_TYPE SSD1306Wire
#define DISPLAY_INIT() SSD1306Wire(SCREEN_ADDRESS, I2C_SDA, I2C_SCL, DISPLAY_GEOMETRY)
// SH1106Wire display(0x3c, I2C_SDA, I2C_SCL, DISPLAY_GEOMETRY);
PrintSplitter both(Serial, display);
#else
Print &both = Serial;
#endif
#define BOTH_TYPE PrintSplitter
#define BOTH_INIT() PrintSplitter(Serial, display)
extern DISPLAY_TYPE display;
extern BOTH_TYPE both;
// some fake pin
#ifdef T3_V1_6_SX1276
#define BUTTON_PIN 22
#endif
#define BUTTON BUTTON_PIN
#include "HotButton.h"
HotButton button(BUTTON);
void heltec_loop()
{
#ifndef DT3_V1_6_SX1276
button.update();
#endif
}
// This file contains a binary patch for the SX1262
#include "modules/SX126x/patches/SX126x_patch_scan.h"
void heltec_display_power(bool on)
{
#ifndef HELTEC_NO_DISPLAY_INSTANCE
if (on)
{
#ifdef HELTEC_WIRELESS_STICK
// They hooked the display to "external" power, and didn't tell anyone
heltec_ve(true);
delay(5);
#endif
pinMode(RST_OLED, OUTPUT);
digitalWrite(RST_OLED, HIGH);
delay(1);
digitalWrite(RST_OLED, LOW);
delay(20);
digitalWrite(RST_OLED, HIGH);
}
else
{
#ifdef HELTEC_WIRELESS_STICK
heltec_ve(false);
#else
display.displayOff();
#endif
}
#endif
}
void heltec_setup()
{
Serial.begin(115200);
Serial.println("LILYGO BOARD");
#define BUTTON BUTTON_PIN
#include "HotButton.h"
extern HotButton button;
#if defined(ARDUINO_ARCH_ESP32)
SPI.begin(RADIO_SCLK_PIN, RADIO_MISO_PIN, RADIO_MOSI_PIN);
#elif defined(ARDUINO_ARCH_STM32)
SPI.setMISO(RADIO_MISO_PIN);
SPI.setMOSI(RADIO_MOSI_PIN);
SPI.setSCLK(RADIO_SCLK_PIN);
SPI.begin();
#endif
void heltec_loop();
#ifndef ARDUINO_heltec_wifi_32_lora_V3
hspi->begin(SCK, MISO, MOSI, SS);
void heltec_display_power(bool on);
void heltec_setup();
#endif
#ifndef HELTEC_NO_DISPLAY_INSTANCE
heltec_display_power(true);
display.init();
// display.setContrast(200);
display.flipScreenVertically();
#endif
}
+84
View File
@@ -12,6 +12,90 @@
#include "LoRaBoards.h"
#include "LiLyGo.h"
// Implement stubs for functions that exist on Heltec, but not on LilyGo
void heltec_led(int led) {}
void heltec_deep_sleep(int sleep_seconds) {}
void heltec_delay(int millisec) { delay(millisec); }
DISPLAY_TYPE display = DISPLAY_INIT();
BOTH_TYPE both = BOTH_INIT();
HotButton button(BUTTON);
void heltec_loop()
{
#ifndef DT3_V1_6_SX1276
button.update();
#endif
}
void heltec_display_power(bool on)
{
#ifndef HELTEC_NO_DISPLAY_INSTANCE
if (on)
{
#ifdef HELTEC_WIRELESS_STICK
// They hooked the display to "external" power, and didn't tell anyone
heltec_ve(true);
delay(5);
#endif
pinMode(RST_OLED, OUTPUT);
digitalWrite(RST_OLED, HIGH);
delay(1);
digitalWrite(RST_OLED, LOW);
delay(20);
digitalWrite(RST_OLED, HIGH);
}
else
{
#ifdef HELTEC_WIRELESS_STICK
heltec_ve(false);
#else
display.displayOff();
#endif
}
#endif
}
void heltec_setup()
{
Serial.begin(115200);
Serial.println("LILYGO BOARD");
#if defined(ARDUINO_ARCH_ESP32)
SPI.begin(RADIO_SCLK_PIN, RADIO_MISO_PIN, RADIO_MOSI_PIN);
#elif defined(ARDUINO_ARCH_STM32)
SPI.setMISO(RADIO_MISO_PIN);
SPI.setMOSI(RADIO_MOSI_PIN);
SPI.setSCLK(RADIO_SCLK_PIN);
SPI.begin();
#endif
#ifdef HELTEC
#ifndef ARDUINO_heltec_wifi_32_lora_V3
hspi->begin(SCK, MISO, MOSI, SS);
#endif
#endif
#ifndef HELTEC_NO_DISPLAY_INSTANCE
heltec_display_power(true);
display.init();
// display.setContrast(200);
display.flipScreenVertically();
#endif
}
#ifdef HELTEC
#ifndef ARDUINO_heltec_wifi_32_lora_V3
SPIClass hspi = new SPIClass(2);
#endif
#endif
RADIO_TYPE radio = RADIO_MODULE_INIT();
#if defined(HAS_SDCARD)
SPIClass SDCardSPI(HSPI);
#endif
+2
View File
@@ -7,6 +7,7 @@
* @last-update 2024-08-07
*/
#ifdef LILYGO
#pragma once
#include "utilities.h"
@@ -95,3 +96,4 @@ extern SPIClass SDCardSPI;
#elif defined(ARDUINO_ARCH_STM32)
extern HardwareSerial SerialGPS;
#endif
#endif
+5 -2
View File
@@ -1,4 +1,7 @@
#include "models.h"
#include <algorithm>
using namespace std;
#include <cstring>
WaterfallModel::WaterfallModel(size_t w, uint64_t base_dt, size_t m_sz,
@@ -67,13 +70,13 @@ void WaterfallModel::reset(uint64_t t0, size_t w)
* and it gets added to incomplete minute. This gets repeated for incomplete
* minutes, etc.
*/
size_t WaterfallModel::updateModel(uint16_t t, size_t x, uint16_t y)
size_t WaterfallModel::updateModel(uint64_t t, size_t x, uint16_t y)
{
size_t changed = 1;
while (t > times[0])
{
changed = push();
changed = max(changed, push());
}
counts[0][x]++;
+1 -1
View File
@@ -18,7 +18,7 @@ struct WaterfallModel
WaterfallModel(size_t w, uint64_t base_dt, size_t m_sz, const size_t *multiples);
void reset(uint64_t t0, size_t width);
size_t updateModel(uint16_t t, size_t x, uint16_t y);
size_t updateModel(uint64_t t, size_t x, uint16_t y);
size_t push();
char *toString();
+4 -2
View File
@@ -4,9 +4,11 @@
#include "scan.h"
#include <cstdint>
#include <cstring>
#include <events.h>
#include <stdlib.h>
uint16_t Scan::rssiMethod(size_t samples, uint16_t *result, size_t res_size)
uint16_t Scan::rssiMethod(float (*getRSSI)(void *), void *param, size_t samples,
uint16_t *result, size_t res_size)
{
float scale((float)res_size / (HI_RSSI_THRESHOLD - LO_RSSI_THRESHOLD + 0.1));
@@ -18,7 +20,7 @@ uint16_t Scan::rssiMethod(size_t samples, uint16_t *result, size_t res_size)
// N of samples
for (int r = 0; r < samples; r++)
{
float rssi = getRSSI();
float rssi = getRSSI(param);
if (rssi < -65535)
rssi = -65535;
+23 -8
View File
@@ -1,11 +1,11 @@
#ifndef LORASA_SCAN_H
#define LORASA_SCAN_H
#include <config.h>
#include <cstdint>
#include <events.h>
#include <event_types.h>
#include <stdlib.h>
#ifndef LORASA_CORE_H
#define LORASA_CORE_H
#ifdef PRINT_DEBUG
#define LOG(args...) Serial.printf(args...)
#define LOG_IF(cond, args...) \
@@ -34,6 +34,22 @@ constexpr float LO_RSSI_THRESHOLD = HI_RSSI_THRESHOLD - 66;
#define SAMPLES_RSSI 20
#endif
struct ScanPage
{
uint64_t start_mhz;
uint64_t end_mhz;
size_t page_sz;
ScanRange *scan_ranges;
~ScanPage()
{
if (page_sz > 0)
{
delete[] scan_ranges;
}
}
};
struct Scan
{
uint64_t epoch;
@@ -61,11 +77,10 @@ struct Scan
},
comms_initialized(false) {};
virtual float getRSSI() = 0;
// rssiMethod gets the data similar to the scan method,
// but uses getRSSI directly.
uint16_t rssiMethod(size_t samples, uint16_t *result, size_t res_size);
uint16_t rssiMethod(float (*getRSSI)(void *), void *param, size_t samples,
uint16_t *result, size_t res_size);
// detect method analyses result, and produces filtered_result, marking
// those values that represent a detection event.
+2
View File
@@ -34,6 +34,8 @@ lib_deps =
build_flags =
-DHELTEC_POWER_BUTTON
-DHELTEC
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
[env:heltec_wifi_lora_32_V3-OSD]
platform = espressif32
+4 -1
View File
@@ -13,6 +13,7 @@ typedef struct {
#define POLY 0x1021
uint16_t crc16(char *p, char *end, uint16_t c) {
c ^= 0xffff;
if (end == NULL) {
end = strchr(p, 0);
}
@@ -29,7 +30,7 @@ uint16_t crc16(char *p, char *end, uint16_t c) {
}
}
return c;
return c ^ 0xffff;
}
#define BUFSIZE 102400
@@ -132,6 +133,8 @@ int main(int argc, char** argv)
lines--;
write(1, buffer, pos);
} else if (!is_wrap) {
write(1, "> ", 2);
write(1, buffer, pos);
errors++;
}
+450 -178
View File
@@ -55,10 +55,8 @@
#define RADIOLIB_CHECK_PARAMS (0)
#include <charts.h>
#ifdef SERIAL_OUT
#include <comms.h>
#include <config.h>
#endif
#include <events.h>
#include <scan.h>
#include <stdlib.h>
@@ -79,7 +77,7 @@
// #include "utilities.h"
// Our Code
#include "LiLyGo.h"
#include <LiLyGo.h>
#endif // end LILYGO
#define BT_SCAN_DELAY 60 * 1 * 1000
@@ -88,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
@@ -153,8 +153,12 @@ typedef enum
// constexpr int RSSI_OUTPUT_FORMULA = 2;
// Feature to scan diapasones. Other frequency settings will be ignored.
// int SCAN_RANGES[] = {850890, 920950};
int SCAN_RANGES[] = {};
// String SCAN_RANGES = String("850..890,920..950");
String SCAN_RANGES = "";
size_t scan_pages_sz = 0;
ScanPage *scan_pages;
size_t scan_page = 0;
// MHZ per page
// to put everything into one page set RANGE_PER_PAGE = FREQ_END - 800
@@ -213,8 +217,6 @@ bool detected_y[STEPS]; // 20 - ??? steps
// Used as a Led Light and Buzzer/count trigger
bool first_run, new_pixel, detected_x = false;
// drone detection flag
bool detected = false;
uint64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL;
#define TRIGGER_LEVEL -80.0
uint64_t drone_detected_frequency_start = 0;
@@ -243,9 +245,8 @@ HardwareSerial SerialPort(SERIAL_PORT);
// #define WEB_SERVER true
uint64_t x, y, range_item, w = WATERFALL_START, i = 0;
uint64_t x, y, w = WATERFALL_START, i = 0;
int osd_x = 1, osd_y = 2, col = 0, max_bin = 32;
uint64_t ranges_count = 0;
int rssi = 0;
int state = 0;
@@ -482,17 +483,11 @@ void osdProcess()
}
#endif
#ifdef SERIAL_OUT
Config config;
#endif
struct RadioScan : Scan
{
float getRSSI() override;
};
float RadioScan::getRSSI()
float getRSSI(void *param)
{
Scan *r = (Scan *)param;
#if defined(USING_SX1280PA)
// radio.startReceive();
// get instantaneous RSSI value
@@ -512,7 +507,26 @@ float RadioScan::getRSSI()
#endif
}
RadioScan r;
float getCAD(void *param)
{
Scan *r = (Scan *)param;
int16_t err = radio.scanChannel();
if (err != RADIOLIB_ERR_NONE)
{
return -999;
}
#ifdef USING_LR1121
// LR1121 doesn't implement getRSSI(bool), getRSSI always
// returns RSSI of the last packet
return radio.getRSSI();
#else
return radio.getRSSI(true);
#endif
}
Scan r;
#define WATERFALL_SENSITIVITY 0.05
DecoratedBarChart *bar;
@@ -521,19 +535,68 @@ 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);
#else
state = radio.setFrequency(r.current_frequency,
true); // 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
@@ -579,30 +642,13 @@ 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);
}
#ifdef SERIAL_OUT
struct frequency_scan_result
{
uint64_t begin;
@@ -613,12 +659,10 @@ struct frequency_scan_result
ScanTaskResult dump;
size_t readings_sz;
} frequency_scan_result;
#endif
TaskHandle_t logToSerial = NULL;
TaskHandle_t dumpToComms = NULL;
#ifdef SERIAL_OUT
void eventListenerForReport(void *arg, Event &e)
{
if (e.type == EventType::DETECTED)
@@ -686,6 +730,8 @@ ScanTask report_scans = ScanTask{
delay : 0 // 0 => as and when it happens; > 0 => at least once that many ms
};
bool requested_host = true;
void dumpToCommsTask(void *parameter)
{
uint64_t last_epoch = frequency_scan_result.last_epoch;
@@ -712,10 +758,22 @@ void dumpToCommsTask(void *parameter)
Message m;
m.type = MessageType::SCAN_RESULT;
m.payload.dump = frequency_scan_result.dump;
Comms0->send(m);
if (requested_host)
{
HostComms->send(m);
}
else
{
if (Comms0 != NULL)
Comms0->send(m);
if (Comms1 != NULL)
Comms1->send(m);
}
m.payload.dump.sz =
0; // dump is shared, so should not delete arrays in destructor
}
}
#endif
#ifdef LOG_DATA_JSON
void logToSerialTask(void *parameter)
@@ -752,6 +810,119 @@ void logToSerialTask(void *parameter)
void drone_sound_alarm(void *arg, Event &e);
void configurePages()
{
if (scan_pages_sz > 0)
delete[] scan_pages;
if (single_page_scan)
{
scan_pages_sz = 1;
ScanPage scan_page = {
start_mhz : CONF_FREQ_BEGIN,
end_mhz : CONF_FREQ_END,
page_sz : config.scan_ranges_sz
};
if (scan_page.page_sz > 0)
{
scan_page.scan_ranges = new ScanRange[scan_page.page_sz];
}
for (int i = 0; i < scan_page.page_sz; i++)
{
scan_page.scan_ranges[i] = config.scan_ranges[i];
}
scan_pages = new ScanPage[1]{scan_page};
scan_page.page_sz =
0; // make sure it doesn't free up the Scanranges that were just constructed
}
else
{
scan_pages_sz =
(CONF_FREQ_END - CONF_FREQ_BEGIN + RANGE_PER_PAGE - 1) / RANGE_PER_PAGE;
scan_pages = new ScanPage[scan_pages_sz];
for (int j = 0; j < scan_pages_sz; j++)
{
ScanPage scan_page = {
start_mhz : CONF_FREQ_BEGIN + j * RANGE_PER_PAGE,
end_mhz : CONF_FREQ_BEGIN + (j + 1) * RANGE_PER_PAGE,
page_sz : 0
};
for (int i = 0; i < config.scan_ranges_sz; i++)
{
if (config.scan_ranges[i].start_khz > scan_page.end_mhz * 1000 ||
config.scan_ranges[i].end_khz < scan_page.start_mhz * 1000)
{
continue;
}
scan_page.page_sz++;
}
if (scan_page.page_sz > 0)
{
scan_page.scan_ranges = new ScanRange[scan_page.page_sz];
for (int i = 0, r = 0; i < config.scan_ranges_sz; i++)
{
if (config.scan_ranges[i].start_khz > scan_page.end_mhz * 1000 ||
config.scan_ranges[i].end_khz < scan_page.start_mhz * 1000)
{
continue;
}
scan_page.scan_ranges[r] = {
start_khz : max(config.scan_ranges[i].start_khz,
scan_page.start_mhz * 1000),
end_khz :
min(config.scan_ranges[i].end_khz, scan_page.end_mhz * 1000),
step_khz : config.scan_ranges[i].step_khz
};
r++;
}
}
scan_pages[j] = scan_page;
scan_page.page_sz =
0; // we copied over the values, make sure the array doesn't get freed
}
}
}
void configureDetection()
{
if (config.scan_ranges_sz == 0)
{
config.scan_ranges_sz = 1;
config.scan_ranges = new ScanRange[1];
config.scan_ranges[0].start_khz = FREQ_BEGIN * 1000;
config.scan_ranges[0].end_khz = FREQ_END * 1000;
config.scan_ranges[0].step_khz =
(float)(FREQ_END - FREQ_BEGIN) * 1000 / (STEPS * SCAN_RBW_FACTOR);
}
if (config.samples <= 0)
{
config.samples = SAMPLES_RSSI;
}
CONF_SAMPLES = config.samples;
CONF_FREQ_BEGIN = config.scan_ranges[0].start_khz / 1000;
CONF_FREQ_END = config.scan_ranges[0].end_khz / 1000;
for (int i = 0; i < config.scan_ranges_sz; i++)
{
CONF_FREQ_BEGIN = min(CONF_FREQ_BEGIN, config.scan_ranges[i].start_khz / 1000);
CONF_FREQ_END = max(CONF_FREQ_END, config.scan_ranges[i].end_khz / 1000);
}
median_frequency = (CONF_FREQ_BEGIN + CONF_FREQ_END) / 2;
samples = CONF_SAMPLES;
RANGE_PER_PAGE = CONF_FREQ_END - CONF_FREQ_BEGIN; // FREQ_END - CONF_FREQ_BEGIN
RANGE = (int)(CONF_FREQ_END - CONF_FREQ_BEGIN);
range = RANGE;
configurePages();
}
void readConfigFile()
{
// writeFile(LittleFS, "/text.txt", "{WIFI:{name:\"sdfsdf\",
@@ -777,27 +948,22 @@ void readConfigFile()
smpls = readParameterFromParameterFile("samples");
Serial.println("SAMPLES: " + smpls);
CONF_SAMPLES = (smpls == "") ? samples : atoi(smpls.c_str());
samples = CONF_SAMPLES;
CONF_FREQ_BEGIN = (fstart == "") ? FREQ_BEGIN : atoi(fstart.c_str());
CONF_FREQ_END = (fend == "") ? FREQ_END : atoi(fend.c_str());
String detection = String("RSSI");
if (smpls.length() > 0)
detection += "," + smpls;
if (fstart.length() == 0)
fstart = String(FREQ_BEGIN * 1000);
if (fend.length() == 0)
fend = String(FREQ_END * 1000);
detection += ":" + fstart + ".." + fend + "/" + String(STEPS * SCAN_RBW_FACTOR);
config.configureDetectionStrategy(detection);
configureDetection();
both.println("C FREQ BEGIN:" + String(CONF_FREQ_BEGIN));
both.println("C FREQ END:" + String(CONF_FREQ_END));
both.println("C SAMPLES:" + String(CONF_SAMPLES));
RANGE_PER_PAGE = CONF_FREQ_END - CONF_FREQ_BEGIN; // FREQ_END - CONF_FREQ_BEGIN
RANGE = (int)(CONF_FREQ_END - CONF_FREQ_BEGIN);
SINGLE_STEP = (float)(RANGE / (STEPS * SCAN_RBW_FACTOR));
range = (int)(CONF_FREQ_END - CONF_FREQ_BEGIN);
iterations = RANGE / RANGE_PER_PAGE;
// uint64_t range_frequency = FREQ_END - CONF_FREQ_BEGIN;
median_frequency = (CONF_FREQ_BEGIN + CONF_FREQ_END) / 2;
}
void setup(void)
@@ -842,7 +1008,6 @@ void setup(void)
bt_start = millis();
wf_start = millis();
#ifdef SERIAL_OUT
config = Config::init();
r.comms_initialized = Comms::initComms(config);
if (r.comms_initialized)
@@ -853,7 +1018,6 @@ void setup(void)
{
Serial.println("Comms did not initialize");
}
#endif
pinMode(LED, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
@@ -913,24 +1077,19 @@ void setup(void)
initLittleFS();
readConfigFile();
#endif
#ifndef WEB_SERVER
CONF_SAMPLES = samples;
CONF_FREQ_BEGIN = FREQ_BEGIN;
CONF_FREQ_END = FREQ_END;
if (config.scan_ranges_sz == 0 && SCAN_RANGES.length() > 0)
{
config.configureDetectionStrategy(config.detection_strategy + ":" + SCAN_RANGES);
}
configureDetection();
both.println("FREQ BEGIN:" + String(CONF_FREQ_BEGIN));
both.println("FREQ END:" + String(CONF_FREQ_END));
both.println("SAMPLES:" + String(CONF_SAMPLES));
RANGE_PER_PAGE = CONF_FREQ_END - CONF_FREQ_BEGIN; // FREQ_END - CONF_FREQ_BEGIN
RANGE = (int)(CONF_FREQ_END - CONF_FREQ_BEGIN);
SINGLE_STEP = (float)(RANGE / (STEPS * SCAN_RBW_FACTOR));
range = (int)(CONF_FREQ_END - CONF_FREQ_BEGIN);
iterations = RANGE / RANGE_PER_PAGE;
median_frequency = (CONF_FREQ_BEGIN + CONF_FREQ_END) / 2;
#endif
init_radio();
@@ -1007,6 +1166,8 @@ void setup(void)
}
}
}
configurePages();
display.clear();
Serial.println();
@@ -1036,9 +1197,7 @@ void setup(void)
#ifdef LOG_DATA_JSON
xTaskCreate(logToSerialTask, "LOG_DATA_JSON", 2048, NULL, 1, &logToSerial);
#endif
#ifdef SERIAL_OUT
xTaskCreate(dumpToCommsTask, "DUMP_RESPONSE_PROCESS", 2048, NULL, 1, &dumpToComms);
#endif
r.trigger_level = TRIGGER_LEVEL;
stacked.reset(0, 0, display.width(), display.height());
@@ -1076,12 +1235,10 @@ void setup(void)
r.addEventListener(DETECTED, drone_sound_alarm, &r);
r.addEventListener(SCAN_TASK_COMPLETE, stacked);
#ifdef SERIAL_OUT
frequency_scan_result.readings_sz = 0;
frequency_scan_result.dump.sz = 0;
r.addEventListener(ALL_EVENTS, eventListenerForReport, NULL);
#endif
#ifdef UPTIME_CLOCK
uptime = new UptimeClock(display, millis());
@@ -1248,35 +1405,11 @@ bool is_new_x_pixel(int x)
return false;
}
void check_ranges()
{
if (RANGE_PER_PAGE == range)
{
single_page_scan = true;
}
else
{
single_page_scan = false;
}
for (int range : SCAN_RANGES)
{
ranges_count++;
}
if (ranges_count > 0)
{
iterations = ranges_count;
single_page_scan = false;
}
}
#ifdef SERIAL_OUT
void checkComms()
{
while (Comms0->available() > 0)
while (HostComms->available() > 0)
{
Message *m = Comms0->receive();
Message *m = HostComms->receive();
if (m == NULL)
continue;
@@ -1284,27 +1417,124 @@ void checkComms()
{
case MessageType::SCAN:
report_scans = m->payload.scan;
requested_host = true;
Serial.println("Host: forwarding message SCAN to peer");
Comms0->send(*m); // forward to peer
Comms1->send(*m); // forward to peer
break;
case MessageType::CONFIG_TASK:
if (m->payload.config.is_set)
{
String v = config.getConfig(*m->payload.config.key);
bool r =
config.updateConfig(*m->payload.config.key, *m->payload.config.value);
Serial.printf("SET config (%s): %s = %s (was: %s)\n", r ? "OK" : "failed",
m->payload.config.key->c_str(),
m->payload.config.value->c_str(), v.c_str());
}
else
{
Serial.printf("GET config: %s = %s\n", m->payload.config.key->c_str(),
config.getConfig(*m->payload.config.key).c_str());
}
break;
}
delete m;
}
while (Comms0->available() > 0)
{
Message *m = Comms0->receive();
Serial.println("Comms0: was available, but didn't receive");
if (m == NULL)
continue;
switch (m->type)
{
case MessageType::SCAN:
report_scans = m->payload.scan; // receive from peer
requested_host = false;
break;
case MessageType::SCAN_RESULT:
HostComms->send(*m); // forward from peer
break;
}
delete m;
}
while (Comms1->available() > 0)
{
Message *m = Comms1->receive();
Serial.println("Comms1: was available, but didn't receive");
if (m == NULL)
continue;
switch (m->type)
{
case MessageType::SCAN:
report_scans = m->payload.scan; // receive from peer
requested_host = false;
break;
case MessageType::SCAN_RESULT:
HostComms->send(*m); // forward from peer
break;
}
delete m;
}
}
#endif
// 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;
r.detection_count = 0;
drone_detected_frequency_start = 0;
ranges_count = 0;
#ifdef SERIAL_OUT
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)
{
@@ -1312,7 +1542,6 @@ void loop(void)
loop_start = millis();
}
r.epoch++;
#endif
if (!ANIMATED_RELOAD || !single_page_scan)
{
@@ -1331,35 +1560,13 @@ void loop(void)
r.fr_begin = CONF_FREQ_BEGIN;
r.fr_end = r.fr_begin;
// 50 is a single-screen range
// TODO: Make 50 a variable with the option to show the full range
iterations = range / RANGE_PER_PAGE;
#if 0 // disabled code
if (range % RANGE_PER_PAGE != 0)
for (scan_page = 0; scan_page < scan_pages_sz; scan_page++)
{
// add more scan
//++;
}
#endif
check_ranges();
// Iterating by small ranges by 50 Mhz each pixel is 0.4 Mhz
for (range_item = 0; range_item < iterations; range_item++)
{
range = RANGE_PER_PAGE;
if (ranges_count == 0)
{
r.fr_begin = (range_item == 0) ? r.fr_begin : r.fr_begin + range;
r.fr_end = r.fr_begin + RANGE_PER_PAGE;
}
else
{
r.fr_begin = SCAN_RANGES[range_item] / 1000;
r.fr_end = SCAN_RANGES[range_item] % 1000;
range = r.fr_end - r.fr_begin;
}
ScanPage &page = scan_pages[scan_page];
r.fr_begin = page.start_mhz;
r.fr_end = page.end_mhz;
range = r.fr_end - r.fr_begin;
median_frequency = (page.start_mhz + page.end_mhz) / 2;
#ifdef DISABLED_CODE
if (!ANIMATED_RELOAD || !single_page_scan)
@@ -1379,10 +1586,38 @@ void loop(void)
// horizontal (x axis) Frequency loop
osd_x = 1, osd_y = 2, col = 0, max_bin = 0;
// x loop
for (x = 0; x < STEPS * SCAN_RBW_FACTOR; x++)
for (int range = 0, step = 0; range < page.page_sz; range += (step == 0))
{
new_pixel = is_new_x_pixel(x);
// the logic is:
// 1. go through each scan_range in the order that they are declared
// in the page
// 2. start with scan_range.start and always end with scan_range.end
// if adding step lands us a little short of end, there will be
// extra iteration to scan actual end frequency
// 3. the next iteration after scanning end frequency will be next range
// 4. x is derived from the frequency we are going to scan with respect to
// the page size
ScanRange scan_range = page.scan_ranges[range];
uint64_t curr_freq = scan_range.start_khz + scan_range.step_khz * step;
if (curr_freq > scan_range.end_khz)
curr_freq = scan_range.end_khz;
// for now support legacy calculation of x that relies on SCAN_RBW_FACTOR
x = (curr_freq - page.start_mhz * 1000) * STEPS * SCAN_RBW_FACTOR /
((page.end_mhz - page.start_mhz) * 1000);
new_pixel = step == 0 || is_new_x_pixel(x);
if (curr_freq == scan_range.end_khz)
{
step = 0; // trigger switch to the next range on the next iteration
}
else
{
step++;
}
if (ANIMATED_RELOAD && SCAN_RBW_FACTOR == 1)
{
UI_drawCursor(x);
@@ -1399,35 +1634,8 @@ void loop(void)
// Because of the SCAN_RBW_FACTOR x is not a display coordinate anymore
// x > STEPS on SCAN_RBW_FACTOR
int display_x = x / SCAN_RBW_FACTOR;
float step = (range * ((float)x / (STEPS * SCAN_RBW_FACTOR)));
r.current_frequency = r.fr_begin + step;
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);
#else
state = radio.setFrequency(r.current_frequency,
true); // true = no calibration need 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
@@ -1461,7 +1669,23 @@ void loop(void)
// Spectrum analyzer using getRSSI
{
LOG("METHOD RSSI");
uint16_t max_rssi = r.rssiMethod(CONF_SAMPLES, result,
float (*g)(void *);
samples = CONF_SAMPLES;
if (config.detection_strategy.equalsIgnoreCase("RSSI"))
g = &getRSSI;
else if (config.detection_strategy.equalsIgnoreCase("CAD"))
{
g = &getCAD;
samples = min(
1,
CONF_SAMPLES); // TODO: do we need to support values other than 1
}
else
g = &getRSSI;
uint16_t max_rssi = r.rssiMethod(g, &r, samples, result,
RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE);
if (max_x_rssi[display_x] > max_rssi)
@@ -1499,10 +1723,8 @@ void loop(void)
max_rssi_x = detected_at;
}
detected = event.detected.detected;
detected_y[display_x] = false;
float rr = event.detected.rssi;
r.drone_detection_level = drone_detection_level;
if (event.detected.trigger)
@@ -1706,17 +1928,13 @@ void loop(void)
joy_btn_clicked = false;
#ifdef SERIAL_OUT
if (config.print_profile_time)
{
#endif
#ifdef PRINT_PROFILE_TIME
loop_time = millis() - loop_start;
Serial.printf("LOOP: %lld ms; SCAN: %lld ms;\n ", loop_time, scan_time);
#endif
#ifdef SERIAL_OUT
}
#endif
// No WiFi and BT Scan Without OSD
#ifdef OSD_ENABLED
@@ -1740,3 +1958,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;
}
}
+10 -13
View File
@@ -15,17 +15,14 @@
#define SCALE_TEXT_TOP (HEIGHT + X_AXIS_WEIGHT + MAJOR_TICK_LENGTH)
// temporary dirty import ... to be solved durring upcoming refactoring
extern unsigned int RANGE_PER_PAGE;
extern uint64_t CONF_FREQ_BEGIN;
extern uint64_t CONF_FREQ_END;
extern unsigned int median_frequency;
extern unsigned int drone_detected_frequency_start;
extern unsigned int drone_detected_frequency_end;
extern unsigned int ranges_count;
extern int SCAN_RANGES[];
extern unsigned int ranges_count;
extern unsigned int iterations;
extern unsigned int range_item;
extern size_t scan_pages_sz;
extern ScanPage *scan_pages;
extern size_t scan_page;
extern uint64_t loop_time;
@@ -166,7 +163,7 @@ void StatusBar::draw()
digitalWrite(LED, LOW);
}
if (ranges_count == 0)
if (scan_pages_sz == 1)
{
#ifdef DEBUG
display.setTextAlignment(TEXT_ALIGN_LEFT);
@@ -179,18 +176,18 @@ void StatusBar::draw()
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(pos_x + width, text_y, String(CONF_FREQ_END));
}
else if (ranges_count > 0)
else if (scan_pages_sz > 1)
{
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(pos_x, text_y,
String(SCAN_RANGES[range_item] / 1000) + "-" +
String(SCAN_RANGES[range_item] % 1000));
if (range_item + 1 < iterations)
String(scan_pages[scan_page].start_mhz) + "-" +
String(scan_pages[scan_page].end_mhz));
if (scan_page + 1 < scan_pages_sz)
{
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(pos_x + width, text_y,
String(SCAN_RANGES[range_item + 1] / 1000) + "-" +
String(SCAN_RANGES[range_item + 1] % 1000));
String(scan_pages[scan_page + 1].start_mhz) + "-" +
String(scan_pages[scan_page + 1].end_mhz));
}
}
ui_initialized = true;
+6 -6
View File
@@ -7,21 +7,21 @@ struct TestScan : Scan
{
TestScan(float *ctx, int sz) : ctx(ctx), sz(sz), idx(0) {}
float getRSSI() override;
float *ctx;
int sz;
int idx;
};
float TestScan::getRSSI()
float getRSSI(void *param)
{
if (idx >= sz)
TestScan *r = (TestScan *)param;
if (r->idx >= r->sz)
{
return -1000000;
}
return ctx[idx++];
return r->ctx[r->idx++];
}
constexpr int test_sz = 13;
@@ -35,7 +35,7 @@ void test_rssi(void)
TestScan t = TestScan(inputs, inputs_sz);
uint16_t r = t.rssiMethod(inputs_sz, samples, test_sz);
uint16_t r = t.rssiMethod(getRSSI, &t, inputs_sz, samples, test_sz);
uint16_t expect[test_sz] = {20, 50, 55, 60, 0, 70, 75, 80, 0, 90, 0, 100, 110};
+51 -39
View File
@@ -50,8 +50,11 @@ void test_push()
m.reset(0, 1);
uint64_t i = 0;
size_t update_to = 0;
for (; i < 10; i++)
m.updateModel(i, 0, 1);
update_to = max(update_to, m.updateModel(i, 0, 1));
TEST_ASSERT_EQUAL_INT(6, update_to);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
@@ -92,8 +95,11 @@ void test_push()
r);
delete r;
update_to = 0;
for (; i < 100; i += 10)
m.updateModel(i, 0, 1);
update_to = max(update_to, m.updateModel(i, 0, 1));
TEST_ASSERT_EQUAL_INT(13, update_to);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
@@ -134,8 +140,11 @@ void test_push()
r);
delete r;
update_to = 0;
for (; i < 10000; i++)
m.updateModel(i, 0, 1);
update_to = max(update_to, m.updateModel(i, 0, 1));
TEST_ASSERT_EQUAL_INT(34, update_to);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
@@ -176,45 +185,48 @@ void test_push()
r);
delete r;
for (; i < 5000; i++)
m.updateModel(i, 0, 1);
update_to = 0;
for (; i < 15000; i++)
update_to = max(update_to, m.updateModel(i, 0, 1));
TEST_ASSERT_EQUAL_INT(34, update_to);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
"[dt:1 t:9999 [ c:1 e:1 ]"
"dt:1 t:9998 [ c:1 e:1 ]"
"dt:1 t:9997 [ c:1 e:1 ]"
"dt:1 t:9996 [ c:1 e:1 ]"
"dt:1 t:9995 [ c:1 e:1 ]"
"dt:5 t:9995 [ c:4 e:4 ]"
"dt:5 t:9990 [ c:5 e:5 ]"
"dt:5 t:9985 [ c:5 e:5 ]"
"dt:15 t:9990 [ c:5 e:5 ]"
"dt:15 t:9975 [ c:15 e:15 ]"
"dt:15 t:9960 [ c:15 e:15 ]"
"dt:15 t:9945 [ c:15 e:15 ]"
"dt:60 t:9960 [ c:30 e:30 ]"
"dt:60 t:9900 [ c:60 e:60 ]"
"dt:60 t:9840 [ c:60 e:60 ]"
"dt:60 t:9780 [ c:60 e:60 ]"
"dt:60 t:9720 [ c:60 e:60 ]"
"dt:60 t:9660 [ c:60 e:60 ]"
"dt:60 t:9600 [ c:60 e:60 ]"
"dt:60 t:9540 [ c:60 e:60 ]"
"dt:60 t:9480 [ c:60 e:60 ]"
"dt:60 t:9420 [ c:60 e:60 ]"
"dt:60 t:9360 [ c:60 e:60 ]"
"dt:60 t:9300 [ c:60 e:60 ]"
"dt:60 t:9240 [ c:60 e:60 ]"
"dt:60 t:9180 [ c:60 e:60 ]"
"dt:60 t:9120 [ c:60 e:60 ]"
"dt:900 t:9900 [ c:60 e:60 ]"
"dt:900 t:9000 [ c:900 e:900 ]"
"dt:900 t:8100 [ c:900 e:900 ]"
"dt:900 t:7200 [ c:900 e:900 ]"
"dt:3600 t:7200 [ c:2700 e:2700 ]"
"dt:3600 t:3600 [ c:3520 e:3520 ]"
"dt:3600 t:3600 [ c:0 e:0 ] ]",
"[dt:1 t:14999 [ c:1 e:1 ]"
"dt:1 t:14998 [ c:1 e:1 ]"
"dt:1 t:14997 [ c:1 e:1 ]"
"dt:1 t:14996 [ c:1 e:1 ]"
"dt:1 t:14995 [ c:1 e:1 ]"
"dt:5 t:14995 [ c:4 e:4 ]"
"dt:5 t:14990 [ c:5 e:5 ]"
"dt:5 t:14985 [ c:5 e:5 ]"
"dt:15 t:14985 [ c:10 e:10 ]"
"dt:15 t:14970 [ c:15 e:15 ]"
"dt:15 t:14955 [ c:15 e:15 ]"
"dt:15 t:14940 [ c:15 e:15 ]"
"dt:60 t:14940 [ c:45 e:45 ]"
"dt:60 t:14880 [ c:60 e:60 ]"
"dt:60 t:14820 [ c:60 e:60 ]"
"dt:60 t:14760 [ c:60 e:60 ]"
"dt:60 t:14700 [ c:60 e:60 ]"
"dt:60 t:14640 [ c:60 e:60 ]"
"dt:60 t:14580 [ c:60 e:60 ]"
"dt:60 t:14520 [ c:60 e:60 ]"
"dt:60 t:14460 [ c:60 e:60 ]"
"dt:60 t:14400 [ c:60 e:60 ]"
"dt:60 t:14340 [ c:60 e:60 ]"
"dt:60 t:14280 [ c:60 e:60 ]"
"dt:60 t:14220 [ c:60 e:60 ]"
"dt:60 t:14160 [ c:60 e:60 ]"
"dt:60 t:14100 [ c:60 e:60 ]"
"dt:900 t:14400 [ c:540 e:540 ]"
"dt:900 t:13500 [ c:900 e:900 ]"
"dt:900 t:12600 [ c:900 e:900 ]"
"dt:900 t:11700 [ c:900 e:900 ]"
"dt:3600 t:10800 [ c:3600 e:3600 ]"
"dt:3600 t:7200 [ c:3600 e:3600 ]"
"dt:3600 t:3600 [ c:3520 e:3520 ] ]",
r);
delete r;
}