Merge pull request #68 from Genaker/testable

Testable
This commit is contained in:
Yegor Shytikov
2024-11-26 09:50:07 -08:00
committed by GitHub
10 changed files with 1041 additions and 41 deletions

3
.gitignore vendored
View File

@@ -3,5 +3,4 @@
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
out/
out/*

View File

@@ -43,7 +43,22 @@ def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, lengt
def parse_line(line):
"""Parse a JSON line from the serial input."""
return json.loads(line)
line = line[line.index('SCAN_RESULT '):] # support garbage interleaving with the string
_, count, rest = line.split(' ', 2)
return int(count), json.loads(rest.replace('(', '[').replace(')', ']'))
POLY = 0x1021
def crc16(s, c):
for ch in s:
c = c ^ (ord(ch) << 8)
for i in range(8):
if c & 0x8000:
c = ((c << 1) ^ POLY) & 0xffff
else:
c = (c << 1) & 0xffff
return c
def main():
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''\
@@ -62,13 +77,14 @@ def main():
help=f'Number of scanlines to record (defaults to {DEFAULT_SCAN_LEN})')
parser.add_argument('--offset', default=DEFAULT_RSSI_OFFSET, type=int,
help=f'Default RSSI offset in dBm (defaults to {DEFAULT_RSSI_OFFSET})')
parser.add_argument('--freq', default=-1, type=float,
help='Default starting frequency in MHz')
parser.add_argument('--buckets', default=-1, type=int,
help='Default number of buckets to group frequencies into; if < 1, will autodetect')
args = parser.parse_args()
# Create the result array
scan_len = args.len
arr = np.zeros((scan_len, SCAN_WIDTH))
arr = None
# Scanline counter
row = 0
@@ -76,42 +92,81 @@ def main():
# List of frequencies
freq_list = []
checksum = -1
so_far = 0
# Open the COM port
with serial.Serial(args.port, args.speed, timeout=None) as com:
while True:
com.write(bytes('SCAN -1 -1\n', 'ascii'))
lines = 0
errors = 0
while row < scan_len:
# Update the progress bar
print_progress_bar(row, scan_len)
# Read a single line
try:
line = com.readline().decode('utf-8').strip()
line = com.readline().decode('utf-8')
except UnicodeDecodeError:
errors += 1
continue
if line.startswith("{"):
if 'WRAP ' in line:
try:
data = parse_line(line)
except json.JSONDecodeError:
_, c, rest = line.split(' ', 2)
checksum = int(c, 16)
so_far = crc16(rest, 0)
except Exception as e:
errors += 1
continue
if 'SCAN_RESULT ' in line:
if checksum == -1:
errors += 1
continue
# get the lowest frequency for now, could be averaged too.
freq = data["low_range_freq"]
c16 = crc16(line, so_far)
if checksum != c16:
errors += 1
checksum = -1
continue
# value in negative, eg: -70
rssi = int(data["value"])
checksum = -1
lines += 1
try:
count, data = parse_line(line)
data.sort()
except json.JSONDecodeError:
errors += 1
continue
r = list(zip(*data))
if len(r) != 2 or len(data) != count:
errors += 1
continue
freqs, rssis = r
if arr is None:
w = count if args.buckets < 1 else args.buckets
arr = np.zeros((scan_len, w))
freq_list = freqs
for col in range(len(rssis)):
arr[row][col] = rssis[col]
if freq not in freq_list:
freq_list.append(freq)
col = freq_list.index(freq)
arr[row][col] = rssi
# Increment the row counter
row += 1
# Check if we're done
if row >= scan_len:
break
# tell it to stop producing SCAN_RESULTS
com.write(bytes('SCAN 0 -1\n', 'ascii'))
print("Read %d lines, encountered %d errors. Success rate: %.2f" %
(lines, errors, (100 - 100 * errors / lines) if lines > 0 else 0))
arr[arr == 0] = arr.min() - 20
# Create the figure
fig, ax = plt.subplots(figsize=(12, 8))

295
lib/comms/comms.cpp Normal file
View File

@@ -0,0 +1,295 @@
#include "comms.h"
#include <config.h>
#include <HardwareSerial.h>
#include <USB.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
Comms *Comms0;
void _onReceive0()
{
if (Comms0 == NULL)
{
return;
}
Comms0->_onReceive();
}
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*/);
}
}
}
bool Comms::initComms(Config &c)
{
if (c.listen_on_usb.equalsIgnoreCase("readline"))
{
// comms using readline plaintext protocol
Comms0 = new ReadlineComms(Serial);
Serial.onEvent(ARDUINO_HW_CDC_RX_EVENT, _onUsbEvent0);
Serial.begin();
Serial.println("Initialized communications on Serial using readline protocol");
return true;
}
else if (c.listen_on_serial0.equalsIgnoreCase("readline"))
{
// comms using readline plaintext protocol
Comms0 = new ReadlineComms(Serial0);
Serial0.onReceive(_onReceive0, false);
Serial.println("Initialized communications on Serial0 using readline protocol");
return true;
}
if (c.listen_on_serial0.equalsIgnoreCase("none"))
{
Comms0 = new NoopComms();
Serial.println("Configured none - Initialized no communications");
return false;
}
Comms0 = new NoopComms();
Serial.println("Nothing is configured - initialized no communications");
return false;
}
size_t Comms::available() { return received_pos; }
#define _RECEIVED_BUF_INCREMENT 10
#define _MAX_RECEIVED_SZ 100
bool Comms::_messageArrived(Message &m)
{
if (received_pos == received_sz)
{
// TODO: if received_sz exceeds a configurable bound, complain and drop the
// message on the floor
if (received_sz >= _MAX_RECEIVED_SZ)
{
Serial.println("Receive buffer backlog too large; dropping the message");
return false;
}
Message **m = new Message *[received_sz + _RECEIVED_BUF_INCREMENT];
if (received_sz > 0)
{
memcpy(m, received, received_sz * sizeof(Message *));
delete[] received;
}
received = m;
received_sz += _RECEIVED_BUF_INCREMENT;
}
received[received_pos] = &m;
received_pos++;
return true;
}
Message *Comms::receive()
{
if (received_pos == 0)
{
return NULL;
}
Message *m = received[0];
received_pos--;
memmove(received, received + 1, received_pos * sizeof(Message *));
return m;
}
Message *_parsePacket(String);
String _scan_str(ScanTask &);
String _scan_result_str(ScanTaskResult &);
String _wrap_str(String);
#define POLY 0x1021
uint16_t crc16(String v, uint16_t c)
{
for (int i = 0; i < v.length(); i++)
{
uint16_t ch = v.charAt(i);
c = c ^ (ch << 8);
for (int j = 0; j < 8; j++)
{
if (c & 0x8000)
{
c = (c << 1) ^ POLY;
}
else
{
c <<= 1;
}
}
}
return c;
}
void ReadlineComms::_onReceive()
{
while (serial.available() > 0)
{
partialPacket = partialPacket + serial.readString();
int i = partialPacket.indexOf('\n');
while (i >= 0)
{
String pack = partialPacket.substring(0, i);
bool messageOk = true;
if (wrap != NULL)
{
messageOk = pack.length() == wrap->payload.wrap.length;
if (messageOk)
{
messageOk = crc16(pack, 0) == wrap->payload.wrap.crc;
}
delete wrap;
wrap = NULL;
}
Message *m = messageOk ? _parsePacket(pack) : NULL;
if (m != NULL)
{
if (m->type == WRAP)
{
wrap = m;
}
else if (!_messageArrived(*m))
{
delete m;
}
}
partialPacket = partialPacket.substring(i + 1);
i = partialPacket.indexOf('\n');
}
}
}
bool ReadlineComms::send(Message &m)
{
String p;
switch (m.type)
{
case MessageType::SCAN:
p = _scan_str(m.payload.scan);
break;
case MessageType::SCAN_RESULT:
p = _scan_result_str(m.payload.dump);
break;
}
serial.print(_wrap_str(p));
return true;
}
int64_t _intParam(String &p, int64_t default_v)
{
p.trim();
int i = p.indexOf(' ');
if (i < 0)
{
i = p.length();
}
int64_t v = p.substring(0, i).toInt();
p = p.substring(i + 1);
return v;
}
int64_t _hexParam(String &p, int64_t default_v)
{
p.trim();
int i = p.indexOf(' ');
if (i < 0)
{
i = p.length();
}
int64_t v = strtol(p.substring(0, i).c_str(), 0, 16);
p = p.substring(i + 1);
return v;
}
Message *_parsePacket(String p)
{
p.trim();
if (p.length() == 0)
{
return NULL;
}
String cmd = p;
int i = p.indexOf(' ');
if (i < 0)
{
p = "";
}
else
{
cmd = p.substring(0, i);
p = p.substring(i + 1);
p.trim();
}
if (cmd.equalsIgnoreCase("wrap"))
{
Message *m = new Message();
m->type = MessageType::WRAP;
m->payload.wrap.crc = _hexParam(p, -1);
m->payload.wrap.length = _intParam(p, -1);
return m;
}
if (cmd.equalsIgnoreCase("scan"))
{
Message *m = new Message();
m->type = MessageType::SCAN;
m->payload.scan.count = _intParam(p, 1);
m->payload.scan.delay = _intParam(p, -1);
return m;
}
Serial.println("ignoring unknown message " + p);
return NULL;
}
String _scan_str(ScanTask &t)
{
return "SCAN " + String(t.count) + " " + String(t.delay) + "\n";
}
String _scan_result_str(ScanTaskResult &r)
{
String p = "SCAN_RESULT " + String(r.sz) + " [ ";
for (int i = 0; i < r.sz; i++)
{
p += (i == 0 ? "(" : ", (") + String(r.freqs_khz[i]) + ", " + String(r.rssis[i]) +
")";
}
return p + " ]\n";
}
String _wrap_str(String v)
{
String r = String(v.length()) + "\n" + v;
return "WRAP " + String(crc16(r, 0), 16) + " " + r;
}

88
lib/comms/comms.h Normal file
View File

@@ -0,0 +1,88 @@
#ifndef __COMMS_H
#define __COMMS_H
#include <HardwareSerial.h>
#include <config.h>
enum MessageType
{
WRAP = 0,
SCAN,
SCAN_RESULT,
_MAX_MESSAGE_TYPE = SCAN_RESULT
};
struct Wrapper
{
int32_t length;
uint16_t crc;
};
struct ScanTask
{
int64_t count;
int64_t delay;
};
struct ScanTaskResult
{
size_t sz;
uint32_t *freqs_khz;
int16_t *rssis;
};
struct Message
{
MessageType type;
union
{
Wrapper wrap;
ScanTask scan;
ScanTaskResult dump;
} payload;
};
struct Comms
{
Stream &serial;
Message **received;
size_t received_sz;
size_t received_pos;
Message *wrap;
Comms(Stream &serial)
: serial(serial), received(NULL), received_sz(0), received_pos(0), wrap(NULL) {};
virtual size_t available();
virtual bool send(Message &) = 0;
virtual Message *receive();
virtual void _onReceive() = 0;
virtual bool _messageArrived(Message &);
static bool initComms(Config &);
};
struct NoopComms : Comms
{
NoopComms() : Comms(Serial0) {};
virtual bool send(Message &) { return true; };
virtual void _onReceive() {};
};
struct ReadlineComms : Comms
{
String partialPacket;
ReadlineComms(Stream &serial) : Comms(serial), partialPacket("") {};
virtual bool send(Message &) override;
virtual void _onReceive() override;
};
extern Comms *Comms0;
#endif

211
lib/config/config.cpp Normal file
View File

@@ -0,0 +1,211 @@
#include "config.h"
void listDir(fs::FS &fs, const char *dirname, uint8_t levels)
{
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root)
{
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory())
{
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file)
{
if (file.isDirectory())
{
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels)
{
listDir(fs, file.path(), levels - 1);
}
}
else
{
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
#define LORA_CONFIG "/lora_config.txt"
Config Config::init()
{
if (SD.cardType() == sdcard_type_t::CARD_NONE)
{
Serial.println("No SD card found, will assume defaults");
return Config();
}
File f = SD.open(LORA_CONFIG, FILE_READ);
if (!f)
{
Serial.println("Listing root directory:");
listDir(SD, "/", 0);
Serial.println("Config file " LORA_CONFIG " not found, will assume defaults");
Config cfg;
if (cfg.create_missing_config)
{
cfg.write_config(LORA_CONFIG);
}
return cfg;
}
Config c = Config();
while (f.available() > 0)
{
String ln = f.readStringUntil('\n');
ParseResult r = parse_config_line(ln);
if (r.error.length() > 0)
{
Serial.printf("%s in '%s', will assume defaults\n", r.error, ln);
return Config();
}
if (r.key.length() == 0)
{
// blank line or comment - skip
continue;
}
// do something with known keys and values
if (r.key.equalsIgnoreCase("print_profile_time"))
{
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"))
{
c.log_data_json_interval = r.value.toInt();
continue;
}
if (r.key.equalsIgnoreCase("listen_on_serial0"))
{
c.listen_on_serial0 = r.value;
continue;
}
if (r.key.equalsIgnoreCase("listen_on_usb"))
{
c.listen_on_serial0 = r.value;
continue;
}
Serial.printf("Unknown key '%s' will be ignored\n", r.key);
}
f.close();
return c;
}
bool Config::write_config(const char *path)
{
File f = SD.open(path, FILE_WRITE, /*create = */ true);
if (!f)
{
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.close();
return true;
}
ParseResult parse_config_line(String ln)
{
ln.trim();
if (ln.length() == 0 || ln.charAt(0) == '#')
{
// blank line or comment - skip
return ParseResult(String(), String());
}
int i = ln.indexOf("="); // ok, this must exist
if (i < 0)
{
return ParseResult(String("Broken config: expected '='"));
}
String k = ln.substring(0, i);
k.trim();
String v = ln.substring(i + 1);
v.trim();
if (v.length() == 0 || v.charAt(0) == '#')
{
return ParseResult(String("Broken config: expected non-empty value"));
}
if (v.charAt(0) == '"')
{
// quoted strings get special treatment
int i = v.indexOf('"', 1);
while (i > 0)
{
if (v.length() == i + 1 || v.charAt(i + 1) != '"')
break;
v = v.substring(0, i + 1) + v.substring(i + 2);
i = v.indexOf('"', i + 1);
}
if (i < 0)
{
return ParseResult(String("Broken config: expected closing quotes"));
}
String c = v.substring(i + 1);
c.trim();
if (c.length() > 0 && c.charAt(0) != '#')
{
return ParseResult(
String("Broken config: expected nothing but whitespace and "
"comments after value"));
}
v = v.substring(1, i);
}
else
{
int i = v.indexOf('#');
if (i > 0)
{
v = v.substring(0, i);
v.trim();
}
}
return ParseResult(k, v);
}

35
lib/config/config.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef _LORA_CONFIG_H
#define _LORA_CONFIG_H
#include <SD.h>
#define CREATE_MISSING_CONFIG true
struct Config
{
bool create_missing_config;
bool print_profile_time;
int log_data_json_interval;
String listen_on_serial0;
String listen_on_usb;
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") {};
bool write_config(const char *path);
static Config init();
};
struct ParseResult
{
String key;
String value;
String error;
ParseResult(String e) : key(String()), value(String()), error(e) {};
ParseResult(String k, String v) : key(k), value(v), error(String()) {};
};
ParseResult parse_config_line(String ln);
#endif

View File

@@ -705,7 +705,33 @@ void setupBoards(bool disable_u8g2)
beginPower();
beginSDCard();
bool sdReady;
for (int i = 0; i < 5 && !(sdReady = beginSDCard()); i++)
{
Serial.println("SD card failed or not found");
delay(1000);
}
if (sdReady)
{
char *card_type = "UNKNOWN";
sdcard_type_t t = SD.cardType();
if (t == sdcard_type_t::CARD_MMC)
{
card_type = "MMC";
}
else if (t == sdcard_type_t::CARD_SD)
{
card_type = "SD";
}
else if (t == sdcard_type_t::CARD_SDHC)
{
card_type = "SDHC";
}
Serial.printf("SD card %s is ready.\n", card_type);
}
if (!disable_u8g2)
{

View File

@@ -47,15 +47,19 @@ struct Scan
bool animated;
float trigger_level;
bool comms_initialized;
Listener **eventListeners[(size_t)EventType::_MAX_EVENT_TYPE + 1];
size_t listener_count[(size_t)EventType::_MAX_EVENT_TYPE + 1];
Scan()
: epoch(0), current_frequency(0), fr_begin(0), fr_end(0),
drone_detection_level(0), sound_on(false), led_flag(false), detection_count(0),
animated(false), trigger_level(0), listener_count{
0,
} {};
animated(false), trigger_level(0),
listener_count{
0,
},
comms_initialized(false) {};
virtual float getRSSI() = 0;

170
spectrum_scan.c Normal file
View File

@@ -0,0 +1,170 @@
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
typedef struct {
uint16_t checksum;
uint16_t so_far;
size_t len;
} wrap_t;
#define POLY 0x1021
uint16_t crc16(char *p, char *end, uint16_t c) {
if (end == NULL) {
end = strchr(p, 0);
}
for (; p != end; p++) {
uint16_t ch = *p;
c = c ^ (ch << 8);
for (int j = 0; j < 8; j++) {
if (c & 0x8000) {
c = (c << 1) ^ POLY;
} else {
c <<= 1;
}
}
}
return c;
}
#define BUFSIZE 102400
#define LOOPS 200
int main(int argc, char** argv)
{
char *port = "/dev/ttyACM0";
int device = open(port, O_RDWR | O_NOCTTY | O_SYNC);
char *scan_ln = "SCAN -1 -1\n";
char *buffer = malloc(BUFSIZE + 1);
write(device, scan_ln, strlen(scan_ln));
int lines = 0;
int errors = 0;
int pos = 0;
int progress = 0;
int progress_ln = 0;
wrap_t wrapper;
wrapper.len = 0;
while(lines - errors < LOOPS)
{
if (pos == BUFSIZE)
{
buffer[pos] = 0;
printf("Something went completely wrong: %s\n", buffer);
break;
}
int n = read(device, buffer + pos, BUFSIZE - pos);
if (n > 0)
{
n += pos;
buffer[n] = 0;
char *nl = strchr(buffer + pos, '\n');
while (nl != NULL)
{
lines++;
// assert: n points at the first unused byte in buffer
pos = (nl - buffer) + 1;
char *wrap = "WRAP ";
char is_wrap = strncmp(buffer, wrap, strlen(wrap)) == 0;
if (is_wrap) {
char *end;
char *p = buffer + strlen(wrap);
wrapper.checksum = strtol(p, &end, 16);
wrapper.len = 0;
if (end == p) {
is_wrap = 0;
} else {
p = end;
wrapper.len = strtol(p, &end, 10);
if (end == p) {
is_wrap = 0;
} else {
wrapper.so_far = crc16(p+1, end+1, 0);
}
}
}
char *scan_result = "SCAN_RESULT ";
char is_scan_result = strncmp(buffer, scan_result, strlen(scan_result)) == 0;
if (is_scan_result) {
uint16_t c16 = crc16(buffer, buffer + pos, wrapper.so_far);
if (wrapper.len == 0 || wrapper.checksum != c16)
{
errors++;
if (wrapper.len == 0) {
int n = snprintf(buffer, pos, "E: no wrap"); // string definitely fits
buffer[n] = ' ';
} else {
int n = snprintf(buffer, pos, "E: bad crc"); // string definitely fits
buffer[n] = ' ';
int n1 = snprintf(buffer + n + 1, pos - n - 1, "want: %x, got: %x", wrapper.checksum, c16);
n1 += n + 1;
if (n1 <= pos - n - 1) {
buffer[n1] = ' ';
}
}
}
wrapper.len = 0; // ok, used up the wrapper
// assert: pos points at the first byte of the next line
write(1, buffer, pos);
// printf("Last char is: %c\n", buffer[pos-3]); - wow, HWCDC.println ends up producing \r\n
} else if (strncmp(buffer, "Wrote stuff in ", 15) == 0) {
lines--;
write(1, buffer, pos);
} else if (strncmp(buffer, "Unable to make ", 15) == 0) {
lines--;
write(1, buffer, pos);
} else if (!is_wrap) {
errors++;
}
if (lines > progress_ln)
{
progress_ln = lines + 10;
if (lines - errors > progress)
{
progress = lines - errors;
}
else
{
printf("D'oh! %d lines read, %d errors - no progress\n", lines, errors);
}
}
n -= pos;
memmove(buffer, nl + 1, n + 1);
// assert: n points at the first unused byte after moving out the parsed line
nl = strchr(buffer, '\n');
}
pos = n;
}
}
char *stop_scan_ln = "SCAN 0 -1\n";
write(device, stop_scan_ln, strlen(stop_scan_ln));
close(device);
free(buffer);
printf("Read %d lines, got %d errors. Success rate: %.2f\n",
lines, errors,
lines == 0 ? 0.0: 100 - 100.0 * errors / lines);
}

View File

@@ -52,6 +52,8 @@
#define RADIOLIB_CHECK_PARAMS (0)
#include <charts.h>
#include <comms.h>
#include <config.h>
#include <events.h>
#include <scan.h>
#include <stdlib.h>
@@ -207,8 +209,8 @@ uint64_t drone_detected_frequency_end = 0;
bool single_page_scan = false;
// #define PRINT_DEBUG
#define PRINT_PROFILE_TIME
#define PRINT_PROFILE_TIME
#ifdef PRINT_PROFILE_TIME
uint64_t loop_start = 0;
uint64_t loop_time = 0;
@@ -218,7 +220,6 @@ uint64_t scan_start_time = 0;
// log data via serial console, JSON format:
// #define LOG_DATA_JSON true
int LOG_DATA_JSON_INTERVAL = 1000; // Log at least every second
uint64_t x, y, range_item, w = WATERFALL_START, i = 0;
int osd_x = 1, osd_y = 2, col = 0, max_bin = 32;
@@ -364,6 +365,8 @@ void osdProcess()
}
#endif
Config config;
struct RadioScan : Scan
{
float getRSSI() override;
@@ -486,14 +489,50 @@ struct frequency_scan_result
uint64_t end;
uint64_t last_epoch;
int16_t rssi; // deliberately not a float; floats can pin task to wrong core forever
ScanTaskResult dump;
size_t readings_sz;
} frequency_scan_result;
TaskHandle_t logToSerial = NULL;
TaskHandle_t dumpToComms = NULL;
void eventListenerForMSP(void *arg, Event &e)
void eventListenerForReport(void *arg, Event &e)
{
if (e.type == EventType::DETECTED)
{
if (e.epoch != frequency_scan_result.last_epoch)
{
frequency_scan_result.dump.sz = 0;
}
if (frequency_scan_result.dump.sz >= frequency_scan_result.readings_sz)
{
size_t old_sz = frequency_scan_result.readings_sz;
frequency_scan_result.readings_sz = frequency_scan_result.dump.sz + 1;
uint32_t *f = new uint32_t[frequency_scan_result.readings_sz];
int16_t *r = new int16_t[frequency_scan_result.readings_sz];
if (old_sz > 0)
{
memcpy(f, frequency_scan_result.dump.freqs_khz,
old_sz * sizeof(uint32_t));
memcpy(r, frequency_scan_result.dump.rssis, old_sz * sizeof(int16_t));
delete[] frequency_scan_result.dump.freqs_khz;
delete[] frequency_scan_result.dump.rssis;
}
frequency_scan_result.dump.freqs_khz = f;
frequency_scan_result.dump.rssis = r;
}
frequency_scan_result.dump.freqs_khz[frequency_scan_result.dump.sz] =
e.detected.freq * 1000; // convert to kHz
frequency_scan_result.dump.rssis[frequency_scan_result.dump.sz] =
max(e.detected.rssi, -999.0f);
frequency_scan_result.dump.sz++;
if (e.epoch != frequency_scan_result.last_epoch ||
e.detected.rssi > frequency_scan_result.rssi)
{
@@ -511,10 +550,50 @@ void eventListenerForMSP(void *arg, Event &e)
{
xTaskNotifyGive(logToSerial);
}
if (dumpToComms != NULL)
{
xTaskNotifyGive(dumpToComms);
}
return;
}
}
ScanTask report_scans = ScanTask{
count : 0, // 0 => report none; < 0 => report forever; > 0 => report that many
delay : 0 // 0 => as and when it happens; > 0 => at least once that many ms
};
void dumpToCommsTask(void *parameter)
{
uint64_t last_epoch = frequency_scan_result.last_epoch;
for (;;)
{
int64_t delay = report_scans.delay;
if (delay == 0)
{
delay = (1ull << 63) - 1;
}
ulTaskNotifyTake(true, pdMS_TO_TICKS(delay));
if (report_scans.count == 0 || frequency_scan_result.last_epoch == last_epoch)
{
continue;
}
if (report_scans.count > 0)
{
report_scans.count--;
}
Message m;
m.type = MessageType::SCAN_RESULT;
m.payload.dump = frequency_scan_result.dump;
Comms0->send(m);
}
}
void logToSerialTask(void *parameter)
{
#ifdef HELTEC
@@ -527,7 +606,7 @@ void logToSerialTask(void *parameter)
for (;;)
{
ulTaskNotifyTake(true, pdMS_TO_TICKS(LOG_DATA_JSON_INTERVAL));
ulTaskNotifyTake(true, pdMS_TO_TICKS(config.log_data_json_interval));
if (frequency_scan_result.begin != frequency_scan_result.end ||
frequency_scan_result.last_epoch != last_epoch)
{
@@ -612,7 +691,7 @@ void setup(void)
#ifdef LILYGO
setupBoards(); // true for disable U8g2 display library
delay(500);
Serial.println("Setup LiLyGO board is done");
Serial.println("Setup LiLybeginSDCardGO board is done");
#endif
// LED brightness
@@ -637,6 +716,17 @@ void setup(void)
bt_start = millis();
wf_start = millis();
config = Config::init();
r.comms_initialized = Comms::initComms(config);
if (r.comms_initialized)
{
Serial.println("Comms initialized fine");
}
else
{
Serial.println("Comms did not initialize");
}
pinMode(LED, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(REB_PIN, OUTPUT);
@@ -799,6 +889,7 @@ void setup(void)
#ifdef LOG_DATA_JSON
xTaskCreate(logToSerialTask, "LOG_DATA_JSON", 2048, NULL, 1, &logToSerial);
#endif
xTaskCreate(dumpToCommsTask, "DUMP_RESPONSE_PROCESS", 2048, NULL, 1, &dumpToComms);
r.trigger_level = TRIGGER_LEVEL;
stacked.reset(0, 0, display.width(), display.height());
@@ -836,7 +927,9 @@ void setup(void)
r.addEventListener(DETECTED, drone_sound_alarm, &r);
r.addEventListener(SCAN_TASK_COMPLETE, stacked);
r.addEventListener(ALL_EVENTS, eventListenerForMSP, NULL);
frequency_scan_result.readings_sz = 0;
frequency_scan_result.dump.sz = 0;
r.addEventListener(ALL_EVENTS, eventListenerForReport, NULL);
#ifdef UPTIME_CLOCK
uptime = new UptimeClock(display, millis());
@@ -1026,6 +1119,24 @@ void check_ranges()
}
}
void checkComms()
{
while (Comms0->available() > 0)
{
Message *m = Comms0->receive();
if (m == NULL)
continue;
switch (m->type)
{
case MessageType::SCAN:
report_scans = m->payload.scan;
break;
}
delete m;
}
}
// MAX Frequency RSSI BIN value of the samples
int max_rssi_x = 999;
@@ -1037,11 +1148,14 @@ void loop(void)
drone_detected_frequency_start = 0;
ranges_count = 0;
// reset scan time
#ifdef PRINT_PROFILE_TIME
scan_time = 0;
loop_start = millis();
#endif
checkComms();
// reset scan time
if (config.print_profile_time)
{
scan_time = 0;
loop_start = millis();
}
r.epoch++;
if (!ANIMATED_RELOAD || !single_page_scan)
@@ -1436,10 +1550,13 @@ void loop(void)
joy_btn_clicked = false;
if (config.print_profile_time)
{
#ifdef PRINT_PROFILE_TIME
loop_time = millis() - loop_start;
Serial.printf("LOOP: %lld ms; SCAN: %lld ms;\n ", loop_time, scan_time);
loop_time = millis() - loop_start;
Serial.printf("LOOP: %lld ms; SCAN: %lld ms;\n ", loop_time, scan_time);
#endif
}
// No WiFi and BT Scan Without OSD
#ifdef OSD_ENABLED
#ifdef WIFI_SCANNING_ENABLED