Merge pull request #113 from Genaker/rtl-sdr

Rtl sdr
This commit is contained in:
Yegor Shytikov
2025-01-05 15:55:53 -08:00
committed by GitHub
11 changed files with 1089 additions and 265 deletions
+26
View File
@@ -18,6 +18,16 @@ void BarChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
Chart::reset(x, y, w, h);
}
void BarChart::clear()
{
for (int i = 0; i < width; i++)
{
ys[i] = min_y;
}
redraw_all = true;
}
int BarChart::updatePoint(float x, float y)
{
if (x < min_x || x >= max_x)
@@ -190,5 +200,21 @@ void DecoratedBarChart::draw()
int tick_pos = bar.x2pos(bar.min_x + step * MINOR_TICKS);
display.drawVerticalLine(pos_x + tick_pos, y, MINOR_TICK_LENGTH);
}
if (draw_labels)
{ // TODO: adjust chart size; for now we'll just assume that we are not ruining
// display
display.setColor(BLACK);
display.fillRect(pos_x, y + MAJOR_TICK_LENGTH, width,
display.getStringWidth("00000"));
display.setColor(WHITE);
for (float step = 1; bar.min_x + step * MAJOR_TICKS < bar.max_x; step += 1)
{
int tick_pos = bar.x2pos(bar.min_x + step * MAJOR_TICKS);
drawVerticalString(display, ArialMT_Plain_10_Vert, pos_x + tick_pos - 5,
y + MAJOR_TICK_LENGTH,
String((int)(bar.min_x + step * MAJOR_TICKS)));
}
}
}
}
+147
View File
@@ -0,0 +1,147 @@
#include "charts.h"
extern const uint8_t ArialMT_Plain_10[]; // borrowed from OLEDDisplayFonts.h
uint8_t *ArialMT_Plain_10_Vert = NULL;
uint64_t _horz_line(const uint8_t *font, size_t chr_off, size_t height, size_t bytes,
size_t ln)
{
size_t raster_height = (height + 7) / 8;
uint8_t b = 1 << (ln & 7);
uint64_t ret = 0;
uint64_t mask = 1;
for (size_t y = ln / 8; y < bytes; y += raster_height, mask <<= 1)
{
if (font[chr_off + y] & b)
{
ret |= mask;
}
}
return ret;
}
uint64_t _flip(uint64_t v, size_t width)
{
for (uint64_t b = 1, e = 1 << (width - 1); b < e; b <<= 1, e >>= 1)
{
if (!!(v & b) != !!(v & e))
{
v = v ^ (b | e);
}
}
return v;
}
uint8_t *_rot(const uint8_t *font)
{
size_t sz = 10000;
size_t p = 0;
uint8_t *vert = (uint8_t *)malloc(sz);
size_t height = font[1];
vert[0] = (uint8_t)height; // height -> width
vert[1] = font[0]; // width -> height
vert[2] = font[2]; // first char
size_t chars = font[3]; // char count
vert[3] = (uint8_t)chars;
size_t map_off = chars * 4 + 4;
for (int i = 0; i < chars; i++)
{
uint32_t v = ((uint32_t *)font)[i + 1];
size_t chr_off = ((v & 0xff) << 8) | ((v >> 8) & 0xff);
if (chr_off != 0xffff)
{
size_t bs = (v >> 16) & 0xff;
size_t width = (v >> 24) & 0xff;
size_t raster_height =
(width + 7) / 8; // how many bytes needed to store one vertical line
size_t bytes = height * raster_height;
v = (v & 0xff000000L) | (bytes << 16) | ((p >> 8) & 0xff) | ((p & 0xff) << 8);
chr_off += map_off;
if (map_off + p + bytes > sz)
{
uint8_t *vv = vert;
sz += 1000;
vert = (uint8_t *)malloc(sz);
memcpy(vert, vv, sz - 1000);
free(vv);
}
for (size_t j = 0; j < height; j++)
{
uint64_t ln = _flip(_horz_line(font, chr_off, height, bs, j), width);
for (size_t k = 0; k < raster_height; k++, p++, ln >>= 8)
{
vert[map_off + p] = (uint8_t)ln;
}
}
}
((uint32_t *)vert)[i + 1] = v;
}
if (map_off + p < sz)
{
uint8_t *vv = vert;
sz = map_off + p;
vert = (uint8_t *)malloc(sz);
memcpy(vert, vv, sz);
free(vv);
}
return vert;
}
void init_fonts()
{
if (ArialMT_Plain_10_Vert != NULL)
{
free(ArialMT_Plain_10_Vert);
ArialMT_Plain_10_Vert = NULL;
}
ArialMT_Plain_10_Vert = _rot(ArialMT_Plain_10);
}
void drawVerticalString(Display_t &display, const uint8_t *v_font, int x, int y, String s)
{
if (v_font == NULL)
return;
size_t w = v_font[0];
char init_ch = v_font[2];
size_t chars = v_font[3];
const uint8_t *v_chars = v_font + (chars + 1) * 4;
for (int i = s.length(); i-- > 0;)
{
char c = s.charAt(i);
if (c < init_ch)
continue;
c -= init_ch;
if (c >= chars)
continue;
size_t map_off = (c + 1) * 4;
size_t chr_off = (((uint16_t)v_font[map_off]) << 8) | (v_font[map_off + 1]);
size_t bs = v_font[map_off + 2];
size_t h = v_font[map_off + 3];
if (chr_off == 0xffff)
{
y += h;
continue;
}
display.drawFastImage(x, y, w, h, v_chars + chr_off);
y += h;
}
}
+10 -1
View File
@@ -81,6 +81,7 @@ struct BarChart : ProgressChart, Listener
};
void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override;
void clear();
int updatePoint(float x, float y) override;
void drawOne(int x) override;
void draw() override;
@@ -100,12 +101,14 @@ struct BarChart : ProgressChart, Listener
struct DecoratedBarChart : Chart
{
BarChart bar;
bool draw_labels;
DecoratedBarChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
float min_x, float max_x, float min_y, float max_y, float level_y)
: Chart(d, x, y, w, h),
bar(d, x, y + LABEL_HEIGHT, w, h - LABEL_HEIGHT - AXIS_HEIGHT, min_x, max_x,
min_y, max_y, level_y) {};
min_y, max_y, level_y),
draw_labels(false) {};
void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override;
void draw() override;
@@ -175,4 +178,10 @@ struct UptimeClock : Chart
virtual void draw() override;
};
extern uint8_t *ArialMT_Plain_10_Vert;
void init_fonts();
void drawVerticalString(Display_t &display, const uint8_t *v_font, int x, int y,
String s);
#endif
+14 -6
View File
@@ -262,15 +262,22 @@ bool ReadlineComms::send(Message &m)
Serial.println(name + ": the message is: " + p);
break;
case MessageType::SCAN_RESULT:
case MessageType::SCAN_MAX_RESULT:
p = _scan_result_str(m.payload.dump);
break;
case MessageType::CONFIG_TASK:
p = m.payload.config.is_set ? "SET " : "GET ";
ConfigTaskType ctt = m.payload.config.task_type;
p = ctt == GET ? "GET "
: ctt == SET ? "SET "
: ctt == GETSET_SUCCESS ? "Success: "
: "Failed to set: ";
p += *m.payload.config.key;
if (m.payload.config.is_set)
if (ctt == SET || ctt == GETSET_SUCCESS)
{
p += " " + *m.payload.config.value;
}
p += "\n";
break;
}
@@ -368,7 +375,7 @@ Message *_parsePacket(String p)
{
Message *m = new Message();
m->type = MessageType::CONFIG_TASK;
m->payload.config.is_set = false;
m->payload.config.task_type = GET;
m->payload.config.key = new String(_stringParam(p, ""));
m->payload.config.value = NULL;
return m;
@@ -378,7 +385,7 @@ Message *_parsePacket(String p)
{
Message *m = new Message();
m->type = MessageType::CONFIG_TASK;
m->payload.config.is_set = true;
m->payload.config.task_type = SET;
m->payload.config.key = new String(_stringParam(p, ""));
m->payload.config.value = new String(_stringParam(p, ""));
@@ -414,7 +421,7 @@ String _wrap_str(String v)
Message::~Message()
{
if (type == SCAN_RESULT)
if (type == SCAN_RESULT || type == SCAN_MAX_RESULT)
{
if (payload.dump.sz > 0)
{
@@ -430,7 +437,8 @@ Message::~Message()
{
delete payload.config.key;
if (payload.config.is_set)
ConfigTaskType ctt = payload.config.task_type;
if (ctt == GETSET_SUCCESS || ctt == SET)
{
delete payload.config.value;
}
+40 -1
View File
@@ -13,15 +13,31 @@
#define SERIAL0 Serial0
#endif
#ifndef SCAN_MAX_RESULT_KHZ_SCALE
// kHz scale: round frequency, so it fits into 2 bytes
// 2500000 / 40 = 62500, scale 40 fits 2.5GHz into two bytes
#define SCAN_MAX_RESULT_KHZ_SCALE 40
#endif
enum MessageType
{
WRAP = 0,
SCAN,
SCAN_RESULT,
SCAN_MAX_RESULT,
CONFIG_TASK,
_MAX_MESSAGE_TYPE = CONFIG_TASK
};
enum ConfigTaskType
{
GET = 0,
SET,
GETSET_SUCCESS,
SET_FAIL,
_MAX_CONFIG_TASK_TYPE = SET_FAIL
};
struct Wrapper
{
int32_t length;
@@ -45,7 +61,7 @@ struct ConfigTask
{
String *key;
String *value;
bool is_set;
ConfigTaskType task_type;
};
struct Message
@@ -62,6 +78,29 @@ struct Message
~Message();
};
struct Endpoint
{
union
{
struct
{
uint8_t loop : 1, // self
uart0 : 1, uart1 : 1,
lora : 1, // rx or tx_lora, depending on is_host
host : 1; // USB
};
uint8_t addr;
};
};
struct RoutedMessage
{
Endpoint from;
Endpoint to;
Message *message;
};
struct Comms
{
String name;
+252 -48
View File
@@ -48,24 +48,25 @@ size_t _write(uint8_t *m, size_t sz, size_t p, uint8_t *v, size_t v_sz)
#define RSSI_HI -80
#define DETAIL_RSSIS 8
#define RSSI_LO (RSSI_HI - 1 - DETAIL_RSSIS)
int16_t RadioComms::send(Message &m)
uint8_t *_serialize_scan_result(Message &m, size_t &p, uint8_t *msg)
{
if (m.type != SCAN_RESULT)
{
return RADIOLIB_ERR_INVALID_FUNCTION;
return NULL;
}
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);
size_t max_msg = p;
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);
p = _write(msg, max_msg, p, (uint8_t *)&dump_sz, 2);
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);
size_t rem = MAX_MSG - p;
size_t rem = max_msg - p;
if (rem > dump_sz)
rem = dump_sz;
@@ -75,7 +76,7 @@ int16_t RadioComms::send(Message &m)
{
if (i * rem / dump_sz > p - pp)
{
p = _write(msg, MAX_MSG, p, bits);
p = _write(msg, max_msg, p, bits);
bits = 0;
}
int16_t v = m.payload.dump.rssis[i];
@@ -97,10 +98,121 @@ int16_t RadioComms::send(Message &m)
if (dump_sz > 0)
{
p = _write(msg, MAX_MSG, p, bits);
p = _write(msg, max_msg, p, bits);
}
return radio.transmit(msg, p);
return msg;
}
uint8_t *_serialize_scan_max_result(Message &m, size_t &p, uint8_t *msg)
{
if (m.type != SCAN_MAX_RESULT)
{
return NULL;
}
size_t dump_sz = m.payload.dump.sz;
size_t max_msg = p;
p = _write(msg, max_msg, 0, (uint8_t)m.type);
p = _write(msg, max_msg, p, (uint8_t *)&dump_sz, 2);
int16_t b = SCAN_MAX_RESULT_KHZ_SCALE; // scale to fit khz into 2 bytes
p = _write(msg, max_msg, p, (uint8_t)b);
for (int i = 0; i < dump_sz; i++)
{
b = m.payload.dump.freqs_khz[i] / SCAN_MAX_RESULT_KHZ_SCALE;
p = _write(msg, max_msg, p, (uint8_t *)&b, 2);
b = m.payload.dump.rssis[i];
if (b >= 0)
{
b = 255;
}
else
{
b += 255;
if (b < 0)
{
b = 0;
}
}
p = _write(msg, max_msg, p, (uint8_t)b);
}
return msg;
}
uint8_t *_serialize_config_task(Message &m, size_t &p, uint8_t *msg)
{
if (m.type != CONFIG_TASK)
{
return NULL;
}
size_t max_msg = p;
ConfigTaskType ctt = m.payload.config.task_type;
p = _write(msg, max_msg, 0, (uint8_t)m.type);
p = _write(msg, max_msg, p, (uint8_t)ctt);
int key_len = m.payload.config.key->length();
if (max_msg - p < key_len + 1)
{
return NULL;
}
p = _write(msg, max_msg, p, (uint8_t)key_len);
p = _write(msg, max_msg, p, (uint8_t *)m.payload.config.key->c_str(), key_len);
if (ctt == GET || ctt == SET_FAIL)
{
return msg;
}
int v_len = m.payload.config.value->length();
if (max_msg - p < v_len + 1)
{
return NULL;
}
p = _write(msg, max_msg, p, (uint8_t)v_len);
p = _write(msg, max_msg, p, (uint8_t *)m.payload.config.value->c_str(), v_len);
return msg;
}
int16_t RadioComms::send(Message &m)
{
uint8_t msg_buf[MAX_MSG];
size_t p = MAX_MSG;
uint8_t *msg = NULL;
if (m.type == SCAN_RESULT)
{
msg = _serialize_scan_result(m, p, msg_buf);
}
else if (m.type == MessageType::SCAN_MAX_RESULT)
{
msg = _serialize_scan_max_result(m, p, msg_buf);
}
else if (m.type == MessageType::CONFIG_TASK)
{
msg = _serialize_config_task(m, p, msg_buf);
}
if (msg == NULL)
{
Serial.printf("Failed to encode message\n");
return RADIOLIB_ERR_INVALID_FUNCTION;
}
int16_t status = radio.transmit(msg, p);
if (msg != msg_buf)
{
delete[] msg;
}
return status;
}
size_t _read(uint8_t *buf, size_t sz, size_t p, uint8_t *v, size_t len)
@@ -118,6 +230,126 @@ size_t _read(uint8_t *buf, size_t sz, size_t p, uint8_t *v)
return _read(buf, sz, p, v, 1);
}
Message *_deserialize_scan_result(size_t len, size_t &p, uint8_t *packet)
{
Message *message = new Message();
message->type = SCAN_RESULT;
uint32_t s, e;
size_t dump_sz = 0;
p = _read(packet, len, p, (uint8_t *)&dump_sz, 2);
p = _read(packet, len, p, (uint8_t *)&s, 4);
p = _read(packet, len, p, (uint8_t *)&e, 4);
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;
}
}
}
return message;
}
Message *_deserialize_scan_max_result(size_t len, size_t &p, uint8_t *packet)
{
Message *message = new Message();
message->type = SCAN_MAX_RESULT;
uint32_t b = 0;
size_t dump_sz = 0;
p = _read(packet, len, p, (uint8_t *)&dump_sz, 2);
uint32_t *freqs = new uint32_t[dump_sz];
int16_t *rssis = new int16_t[dump_sz];
message->payload.dump.sz = dump_sz;
message->payload.dump.freqs_khz = freqs;
message->payload.dump.rssis = rssis;
uint32_t scale = 0;
p = _read(packet, len, p, (uint8_t *)&scale);
for (int i = 0; i < dump_sz; i++)
{
p = _read(packet, len, p, (uint8_t *)&b, 2);
freqs[i] = scale * b;
b = 0;
p = _read(packet, len, p, (uint8_t *)&b);
rssis[i] = ((int16_t)b) - 255;
}
return message;
}
Message *_deserialize_config_task(size_t len, size_t &p, uint8_t *packet)
{
Message *message = new Message();
message->type = CONFIG_TASK;
ConfigTaskType ctt = GET;
p = _read(packet, len, p, (uint8_t *)&ctt);
message->payload.config.task_type = ctt;
size_t key_len = 0;
size_t p1 = _read(packet, len, p, (uint8_t *)&key_len);
memmove(packet + p, packet + p + 1, key_len);
packet[p + key_len] = 0;
String *key = new String((char *)packet + p);
message->payload.config.key = key;
p = p1 + key_len;
if (key->length() != key_len)
{
delete message;
return NULL;
}
if (ctt == GET || ctt == SET_FAIL)
{
return message;
}
size_t v_len = 0;
p1 = _read(packet, len, p, (uint8_t *)&v_len);
memmove(packet + p, packet + p + 1, v_len);
packet[p + v_len] = 0;
String *value = new String((char *)packet + p);
message->payload.config.value = value;
p = p1 + v_len;
if (value->length() != v_len)
{
delete message;
return NULL;
}
return message;
}
volatile bool _received = false;
void _rcv() { _received = true; }
@@ -184,43 +416,15 @@ Message *RadioComms::receive(uint16_t timeout_ms)
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;
}
}
}
message = _deserialize_scan_result(len, p, packet);
}
else if (b == SCAN_MAX_RESULT)
{
message = _deserialize_scan_max_result(len, p, packet);
}
else if (b == CONFIG_TASK)
{
message = _deserialize_config_task(len, p, packet);
}
else
{
+24 -31
View File
@@ -107,23 +107,26 @@ Config Config::init()
return c;
}
#define UPDATE_BOOL(val, key, value) \
if (key.equalsIgnoreCase(#val)) \
{ \
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 \
{ \
val = p; \
} \
return true; \
}
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;
}
UPDATE_BOOL(print_profile_time, key, value);
if (key.equalsIgnoreCase("log_data_json_interval"))
{
@@ -167,21 +170,9 @@ bool Config::updateConfig(String key, String 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;
}
UPDATE_BOOL(is_host, key, value);
UPDATE_BOOL(lora_enabled, key, value);
return false;
}
@@ -221,7 +212,7 @@ String detectionStrategyToStr(Config &c)
res += ".." + String(c.scan_ranges[i].end_khz);
if (c.scan_ranges[i].step_khz < s)
{
res += ":" + String(c.scan_ranges[i].step_khz);
res += "+" + String(c.scan_ranges[i].step_khz);
}
}
}
@@ -512,6 +503,8 @@ bool Config::write_config(const char *path)
f.println("tx_lora = " + getConfig("tx_lora"));
f.println("is_host = " + getConfig("is_host"));
f.println("lora_enabled = " + getConfig("lora_enabled"));
f.close();
return true;
}
+52 -4
View File
@@ -3,6 +3,16 @@
#include <SD.h>
template <typename L, typename R> struct Result
{
bool is_ok;
union
{
L not_ok;
R ok;
};
};
struct ScanRange
{
uint64_t start_khz;
@@ -10,6 +20,22 @@ struct ScanRange
uint64_t step_khz;
};
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 LoRaConfig
{
float freq;
@@ -25,6 +51,28 @@ struct LoRaConfig
LoRaConfig *configureLora(String cfg);
#ifndef FREQ_RX
#define FREQ_RX 866
#endif
#ifndef FREQ_TX
#define FREQ_TX (FREQ_RX + 2)
#endif
#define DEFAULT_RX String("freq:") + String(FREQ_RX)
#define DEFAULT_TX String("freq:") + String(FREQ_TX)
#ifndef DEFAULT_TX_SCAN_ON_BOOT
#define DEFAULT_TX_SCAN_ON_BOOT false
#endif
#ifndef DEFAULT_IS_LORA_HOST
#define DEFAULT_IS_LORA_HOST false
#endif
#ifndef DEFAULT_LORA_ENABLED
#define DEFAULT_LORA_ENABLED false
#endif
#define CREATE_MISSING_CONFIG true
struct Config
{
@@ -42,16 +90,16 @@ struct Config
LoRaConfig *tx_lora;
bool is_host;
bool lora_enabled;
Config()
: create_missing_config(CREATE_MISSING_CONFIG), print_profile_time(false),
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) {};
listen_on_usb(String("readline")), rx_lora(configureLora(DEFAULT_RX)),
tx_lora(configureLora(DEFAULT_TX)), is_host(DEFAULT_IS_LORA_HOST),
lora_enabled(DEFAULT_LORA_ENABLED) {};
bool write_config(const char *path);
-16
View File
@@ -34,22 +34,6 @@ 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;
+54
View File
@@ -35,6 +35,26 @@ build_flags =
-DHELTEC
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
[env:heltec_wifi_lora_32_V3_lora-TX-scanner]
platform = espressif32
board = heltec_wifi_lora_32_V3
framework = arduino
upload_speed = 921600
monitor_speed = 115200
board_build.f_cpu = 240000000
board_build.filesystem = littlefs
lib_deps =
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
ESP Async WebServer
build_flags =
-DHELTEC_POWER_BUTTON
-DHELTEC
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
-DDEFAULT_RX=916
-DDEFAULT_TX=915
-DDEFAULT_LORA_ENABLED=true
[env:heltec_wifi_lora_32_V3-OSD]
platform = espressif32
@@ -70,6 +90,7 @@ build_flags =
-DHELTEC
-DFREQ_BEGIN=130
-DFREQ_END=180
-DFREQ_RX=133
-DRADIOLIB_CHECK_PARAMS=0
[env:lilygo-T3S3-v1-2-sx1262]
@@ -98,6 +119,37 @@ build_flags =
-DARDUINO_LILYGO_T3_S3_V1_X
-DARDUINO_USB_MODE=1
[env:lilygo-T3S3-v1-2-sx1262-lora-RX-host-reciver]
platform = espressif32
board = t3_s3_v1_x
framework = arduino
upload_speed = 921600
monitor_speed = 115200
board_build.f_cpu = 240000000
board_build.filesystem = littlefs
lib_deps =
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
RadioLib
U8g2
XPowersLib
ESP Async WebServer
build_flags =
-DLILYGO
-DT3_S3_V1_2_SX1262
-DT3_V1_3_SX1262
-DARDUINO_LILYGO_T3S3_SX1262
-DESP32
-DUSING_SX1262
-DARDUINO_ARCH_ESP32
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_LILYGO_T3_S3_V1_X
-DARDUINO_USB_MODE=1
-DDEFAULT_RX=916
-DDEFAULT_TX=915
-DDEFAULT_LORA_ENABLED=true
-DDEFAULT_IS_LORA_HOST=true
-DDISPLAY_RAW_SCAN=1
[env:lilygo-T3S3-v1-2-lr1121]
platform = espressif32
board = t3_s3_v1_x
@@ -122,6 +174,7 @@ build_flags =
-DUSING_LR1121
-DFREQ_BEGIN=2400
-DFREQ_END=2500
-DFREQ_RX=2440
-DARDUINO_ARCH_ESP32
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_LILYGO_T3_S3_V1_X
@@ -150,6 +203,7 @@ build_flags =
-DUSING_SX1280PA
-DFREQ_BEGIN=2400
-DFREQ_END=2500
-DFREQ_RX=2440
-DARDUINO_ARCH_ESP32
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_LILYGO_T3_S3_V1_X
+470 -158
View File
@@ -894,12 +894,63 @@ 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.detection_strategy.equalsIgnoreCase("RSSI_MAX"))
{
size_t sz = 10;
uint32_t f_khz = FREQ_BEGIN * 1000;
uint32_t ssz = (FREQ_END - FREQ_BEGIN) * 1000 / 10;
uint32_t step =
(float)(FREQ_END - FREQ_BEGIN) * 1000 / (STEPS * SCAN_RBW_FACTOR);
uint32_t rx_b = FREQ_END * 1000 + 100000;
uint32_t rx_e = rx_b + 500;
if (RxComms != NULL)
{
rx_e = RxComms->loraCfg.bw;
rx_b = RxComms->loraCfg.freq * 1000 - rx_e;
rx_e = rx_b + 2 * rx_e;
if (rx_e / ssz == rx_b / ssz && rx_e > f_khz && rx_b < FREQ_END * 1000)
{
// entire exclusion range is in one bucket
sz++;
}
}
config.scan_ranges_sz = sz;
config.scan_ranges = new ScanRange[sz];
for (int i = 0; i < sz; i++)
{
config.scan_ranges[i].step_khz = step;
config.scan_ranges[i].start_khz = f_khz > rx_b ? max(f_khz, rx_e) : f_khz;
bool starts_before = f_khz < rx_e;
bool ends_after = f_khz + ssz > rx_b;
if (starts_before && ends_after)
{
config.scan_ranges[i].end_khz = rx_b;
i++;
config.scan_ranges[i].start_khz = rx_e;
config.scan_ranges[i].step_khz = step;
}
f_khz += ssz;
config.scan_ranges[i].end_khz =
f_khz - step < rx_e ? min(f_khz - step, rx_b) : f_khz - step;
}
if (config.scan_ranges[sz - 1].end_khz > rx_e)
config.scan_ranges[sz - 1].end_khz = FREQ_END * 1000;
}
else
{
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)
@@ -911,7 +962,7 @@ void configureDetection()
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++)
for (int i = 1; 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);
@@ -926,6 +977,12 @@ void configureDetection()
range = RANGE;
configurePages();
if (bar != NULL)
{
bar->bar.min_x = CONF_FREQ_BEGIN;
bar->bar.max_x = CONF_FREQ_END;
}
}
void readConfigFile()
@@ -1173,6 +1230,7 @@ void setup(void)
configurePages();
display.clear();
init_fonts();
Serial.println();
#ifdef METHOD_RSSI
@@ -1409,41 +1467,230 @@ bool is_new_x_pixel(int x)
return false;
}
void checkComms()
/*
* Given message type and config, modify from/to to route the message to the
* right destination.
*/
void routeMessage(RoutedMessage &m)
{
if (m.message == NULL)
{
return;
}
if (m.message->type == MessageType::SCAN_RESULT ||
m.message->type == MessageType::SCAN_MAX_RESULT)
{
m.to.host = 1;
return;
}
if (m.message->type == MessageType::CONFIG_TASK &&
(m.message->payload.config.task_type == ConfigTaskType::GETSET_SUCCESS ||
m.message->payload.config.task_type == ConfigTaskType::SET_FAIL))
{
m.to.addr = 0;
m.to.host = 1;
return;
}
if (config.is_host &&
(m.message->type == MessageType::SCAN ||
m.message->type == MessageType::CONFIG_TASK &&
m.message->payload.config.key->equalsIgnoreCase("detection_strategy")))
{
m.to.lora = 1; // apply to self, and send over to lora
return;
}
}
int16_t sendMessage(RadioComms &c, Message &m);
void loraSendMessage(Message &m);
Result<int16_t, Message *> checkRadio(RadioComms &c);
void display_scan_result(ScanTaskResult &dump);
std::unordered_map<int, int16_t> findMaxRssi(int16_t *rssis, uint32_t *freqs_khz,
int dump_sz, int level = 80);
void display_raw_scan(ScanTaskResult &dump)
{
// display.setDisplayRotation(1);
// display.println("Host Mode ->");
size_t dump_sz = dump.sz;
int16_t *rssi = dump.rssis;
uint32_t *fr = dump.freqs_khz;
std::unordered_map<int, int16_t> maxMhzRssi =
findMaxRssi(rssi, fr, dump_sz, abs(TRIGGER_LEVEL));
Serial.println("PRINT SIZE :" + String(maxMhzRssi.size()));
int lx = 0;
int ly = 0;
int i = 0;
for (const auto &pair : maxMhzRssi)
{
if (i == 0 && maxMhzRssi.size() > 0)
{
display.clear();
}
int16_t rssi = pair.second;
int16_t fr = (int)pair.first;
Serial.println("PRINT FR:" + String(fr) + ":" + String(rssi) + " lx " +
String(lx));
// screen overflow protection
if (lx < 130)
{
display.drawString(lx, ly, String(fr) + ":" + String(rssi));
Serial.println("PRINT FR:" + String(fr) + ":" + String(rssi));
// go to next line
ly = ly + 10;
if (ly > 60)
{
ly = 0;
// go to next column
lx = lx + 45;
}
}
i++;
}
if (maxMhzRssi.size() > 0)
{
display.display();
}
}
/*
* If m.to is LOOP, the message is directed at this module; enact the message.
* If m.to is not LOOP, send the message via the respective interface.
*/
void sendMessage(RoutedMessage &m)
{
if (m.message == NULL)
{
return;
}
Message *msg = m.message;
if (m.to.loop)
{
switch (msg->type)
{
case MessageType::SCAN:
report_scans = msg->payload.scan;
requested_host = !!m.from.host;
break;
case MessageType::CONFIG_TASK:
{
ConfigTaskType ctt = msg->payload.config.task_type;
// sanity check; GETSET_SUCCESS and SET_FAIL should get routed to HOST
if (ctt == ConfigTaskType::GET || ctt == ConfigTaskType::SET)
{
// must be GET or SET - both require sending back a response
RoutedMessage resp;
resp.to.addr = m.from.addr;
resp.to.loop = 0;
resp.from.addr = 0;
resp.from.loop = 1;
resp.message = new Message();
resp.message->type = msg->type;
String old_v = config.getConfig(*msg->payload.config.key);
bool success = true;
if (ctt == ConfigTaskType::SET)
{
success = config.updateConfig(*msg->payload.config.key,
*msg->payload.config.value);
if (success &&
msg->payload.config.key->equalsIgnoreCase("detection_strategy"))
{
configureDetection(); // redo the pages and scan ranges
}
}
resp.message->payload.config.key = new String(*msg->payload.config.key);
if (success)
{
resp.message->payload.config.task_type = GETSET_SUCCESS;
resp.message->payload.config.value = new String(old_v);
}
else
{
resp.message->payload.config.task_type = SET_FAIL;
}
sendMessage(resp);
delete resp.message;
}
}
break;
case SCAN_RESULT:
case SCAN_MAX_RESULT:
if (config.is_host)
{
#ifdef DISPLAY_RAW_SCAN
display_raw_scan(m.message->payload.dump);
#else
display_scan_result(m.message->payload.dump);
#endif
}
break;
}
}
if (m.to.host)
{
HostComms->send(*m.message);
}
if (m.to.uart0)
{
Comms0->send(*m.message);
}
if (m.to.uart1)
{
Comms1->send(*m.message);
}
if (m.to.lora)
{
if (config.is_host && TxComms != NULL)
{
checkRadio(*TxComms); // waiting for peer to squak first, so message sending
// will land on the receiving cycle
}
loraSendMessage(*m.message);
}
}
RoutedMessage checkComms()
{
RoutedMessage mess;
mess.from.addr = 0;
mess.to.addr = 0;
mess.to.loop = 1;
mess.message = NULL;
while (HostComms->available() > 0)
{
Message *m = HostComms->receive();
if (m == NULL)
continue;
switch (m->type)
{
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;
mess.from.host = 1;
mess.message = m;
return mess;
}
while (Comms0->available() > 0)
@@ -1453,18 +1700,9 @@ void checkComms()
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;
mess.from.uart0 = 1;
mess.message = m;
return mess;
}
while (Comms1->available() > 0)
@@ -1474,19 +1712,38 @@ void checkComms()
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;
mess.from.uart1 = 1;
mess.message = m;
return mess;
}
// NB: swapping the use of Tx and Rx comms, so a pair of modules
// with identical rx/tx_lora config can talk
RadioComms *rx = config.is_host ? TxComms : RxComms;
if (rx != NULL && (config.is_host || config.lora_enabled))
{
Result<int16_t, Message *> res = checkRadio(*rx);
if (!res.is_ok)
{
int16_t status = res.not_ok;
if (status != RADIOLIB_ERR_NONE)
{
Serial.printf("Error getting a message: %d\n", status);
}
}
else
{
mess.from.lora = 1;
mess.message = res.ok;
}
return mess;
}
mess.from.loop = 1;
return mess;
}
// MAX Frequency RSSI BIN value of the samples
@@ -1494,9 +1751,7 @@ int max_rssi_x = 999;
void doScan();
void reportScan(RadioComms &c);
int16_t checkRadio(RadioComms &c);
void reportScan();
void loop(void)
{
@@ -1505,28 +1760,17 @@ void loop(void)
r.detection_count = 0;
drone_detected_frequency_start = 0;
checkComms();
if (config.is_host)
for (RoutedMessage mess = checkComms(); mess.message != NULL; mess = checkComms())
{
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);
}
}
routeMessage(mess);
sendMessage(mess);
delete mess.message;
}
else
if (!config.is_host)
{
doScan();
if (TxComms != NULL)
reportScan(*TxComms);
if (RxComms != NULL)
checkRadio(*RxComms);
reportScan();
}
}
@@ -1677,14 +1921,15 @@ void doScan()
float (*g)(void *);
samples = CONF_SAMPLES;
if (config.detection_strategy.equalsIgnoreCase("RSSI"))
if (config.detection_strategy.equalsIgnoreCase("RSSI") ||
config.detection_strategy.equalsIgnoreCase("RSSI_MAX"))
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
samples = min(1,
CONF_SAMPLES); // TODO: do we need to support values
// other than 1
}
else
g = &getRSSI;
@@ -1974,7 +2219,7 @@ void doScan()
std::unordered_map<int, int> previousPac = {/*{916, true}, {915, true}*/};
std::unordered_map<int, int16_t> findMaxRssi(int16_t *rssis, uint32_t *freqs_khz,
int dump_sz, int level = 80)
int dump_sz, int level)
{
std::unordered_map<int, int16_t> maxRssiPerMHz; // Map to store max RSSI per MHz
@@ -1987,7 +2232,7 @@ std::unordered_map<int, int16_t> findMaxRssi(int16_t *rssis, uint32_t *freqs_khz
if (maxRssiPerMHz.find(freq_mhz) == maxRssiPerMHz.end() ||
maxRssiPerMHz[freq_mhz] < rssi)
{
if (abs(rssi) < level)
if (abs(rssi) <= level)
{
maxRssiPerMHz[freq_mhz] = rssi;
}
@@ -1998,99 +2243,166 @@ std::unordered_map<int, int16_t> findMaxRssi(int16_t *rssis, uint32_t *freqs_khz
}
bool lock = false;
int16_t checkRadio(RadioComms &comms)
Result<int16_t, Message *> checkRadio(RadioComms &comms)
{
radioIsScan = false;
int16_t status = comms.configureRadio();
if (status != RADIOLIB_ERR_NONE)
return status;
Result<int16_t, Message *> ret;
ret.is_ok = false;
ret.not_ok = comms.configureRadio();
if (ret.not_ok != RADIOLIB_ERR_NONE)
return ret;
ret.is_ok = true;
Message *msg = comms.receive(
config.is_host
? 2000
: 500); // 200ms should be enough to receive 500 bytes at SF 7 and BW 500
if (msg == NULL)
{
return status;
}
: 200); // 200ms should be enough to receive 500 bytes at SF 7 and BW 500
ret.ok = msg;
if (msg->type == SCAN_RESULT)
{
// if (lock == false)
{
lock = true;
// display.setDisplayRotation(1);
// display.println("Host Mode ->");
size_t dump_sz = msg->payload.dump.sz;
int16_t *rssi = msg->payload.dump.rssis;
uint32_t *fr = msg->payload.dump.freqs_khz;
std::unordered_map<int, int16_t> maxMhzRssi =
findMaxRssi(rssi, fr, dump_sz, 85);
int lx, ly, i = 0;
for (const auto &pair : maxMhzRssi)
{
if (i == 0 && maxMhzRssi.size() > 0)
{
display.clear();
}
// screen overflow protection
if (lx < 130)
{
int16_t rssi = pair.second;
int16_t fr = (int)pair.first;
display.drawString(lx, ly, String(fr) + ":" + String(rssi));
// go to next line
ly += 10;
if (ly > 60)
{
ly = 0;
// go to next column
lx += 50;
}
display.display();
}
i++;
}
lock = false;
}
HostComms->send(*msg);
}
else
{
Serial.printf("Received a message of unsupported type: %d\n", msg->type);
}
delete msg;
return status;
return ret;
}
void reportScan(RadioComms &comms)
int16_t sendMessage(RadioComms &comms, Message &msg)
{
radioIsScan = false;
int16_t status = comms.configureRadio();
if (status != RADIOLIB_ERR_NONE)
{
Serial.printf("Failed to configure Radio: %d\n", status);
return;
return status;
}
Message m;
m.type = SCAN_RESULT;
m.payload.dump = frequency_scan_result.dump;
status = comms.send(m);
if (false)
{
lock = true;
m.payload.dump.sz = 0; // dump is shared, so should not delete underlying arrays
lock = false;
}
status = comms.send(msg);
if (status != RADIOLIB_ERR_NONE)
{
Serial.printf("Failed to send scan result: %d\n", status);
Serial.printf("Failed to send message of type %d: %d\n", msg.type, status);
return status;
}
return status;
}
void loraSendMessage(Message &msg)
{
RadioComms *tx = config.is_host ? RxComms : TxComms;
if (tx == NULL)
{
return;
}
sendMessage(*tx, msg);
}
void reportScan()
{
if (!config.lora_enabled)
return;
Message m;
m.type = SCAN_RESULT;
m.payload.dump.sz = 0;
if (config.detection_strategy.equalsIgnoreCase("RSSI"))
{
size_t sz = frequency_scan_result.dump.sz;
m.payload.dump.sz = sz;
m.payload.dump.freqs_khz = new uint32_t[sz];
m.payload.dump.rssis = new int16_t[sz];
memcpy(m.payload.dump.freqs_khz, frequency_scan_result.dump.freqs_khz,
sizeof(uint32_t) * sz);
memcpy(m.payload.dump.rssis, frequency_scan_result.dump.rssis,
sizeof(int16_t) * sz);
}
else if (config.detection_strategy.equalsIgnoreCase("RSSI_MAX"))
{
m.type = SCAN_MAX_RESULT;
size_t sz = config.scan_ranges_sz;
m.payload.dump.sz = sz;
m.payload.dump.freqs_khz = new uint32_t[sz];
m.payload.dump.rssis = new int16_t[sz];
for (int i = 0; i < sz; i++)
{
int16_t rssi = -999;
for (int j = 0; j < frequency_scan_result.dump.sz; j++)
{
uint32_t f = frequency_scan_result.dump.freqs_khz[j];
if (config.scan_ranges[i].start_khz > f ||
config.scan_ranges[i].end_khz < f)
continue;
rssi = max(rssi, frequency_scan_result.dump.rssis[j]);
}
m.payload.dump.freqs_khz[i] =
(config.scan_ranges[i].start_khz + config.scan_ranges[i].end_khz) / 2;
m.payload.dump.rssis[i] = rssi;
}
}
else
{
return;
}
loraSendMessage(m);
}
void display_scan_result(ScanTaskResult &dump)
{
if (bar == NULL)
return;
// assuming this module and the peer are in sync w.r.t. scan ranges
if (config.detection_strategy.equalsIgnoreCase("RSSI"))
{
for (int i = 0; i < dump.sz; i++)
bar->bar.updatePoint(dump.freqs_khz[i] / 1000, dump.rssis[i]);
bar->draw();
display.display();
return;
}
if (config.detection_strategy.equalsIgnoreCase("RSSI_MAX"))
{
float step = (bar->bar.max_x - bar->bar.min_x) / bar->bar.width;
bar->bar.clear();
bar->draw_labels = true;
for (int i = 0; i < config.scan_ranges_sz; i++)
{
int j;
for (j = 0; j < dump.sz; j++)
{
if (config.scan_ranges[i].start_khz <= dump.freqs_khz[j] &&
config.scan_ranges[i].end_khz >= dump.freqs_khz[j])
break;
}
int16_t rssi = j < dump.sz ? dump.rssis[j] : bar->bar.min_y;
for (float f = config.scan_ranges[i].start_khz / 1000;
f <= config.scan_ranges[i].end_khz / 1000; f += step)
bar->bar.updatePoint(f, rssi);
}
bar->draw();
display.display();
return;
}
}