From 1011d8bf13cb83c3df9d7227d2c52807fb01ce01 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Tue, 8 Oct 2024 21:26:58 +0100 Subject: [PATCH 1/6] Config on SD card --- lib/config/config.cpp | 196 ++++++++++++++++++++++++++++++++++ lib/config/config.h | 32 ++++++ lib/loraboards/LoRaBoards.cpp | 28 ++++- src/main.cpp | 30 ++++-- 4 files changed, 274 insertions(+), 12 deletions(-) create mode 100644 lib/config/config.cpp create mode 100644 lib/config/config.h diff --git a/lib/config/config.cpp b/lib/config/config.cpp new file mode 100644 index 0000000..d35cfda --- /dev/null +++ b/lib/config/config.cpp @@ -0,0 +1,196 @@ +#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(); + } + + 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.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); +} diff --git a/lib/config/config.h b/lib/config/config.h new file mode 100644 index 0000000..0e24043 --- /dev/null +++ b/lib/config/config.h @@ -0,0 +1,32 @@ +#ifndef _LORA_CONFIG_H +#define _LORA_CONFIG_H + +#include + +#define CREATE_MISSING_CONFIG true +struct Config +{ + bool create_missing_config; + bool print_profile_time; + int log_data_json_interval; + + Config() + : create_missing_config(CREATE_MISSING_CONFIG), print_profile_time(false), + log_data_json_interval(1000) {}; + 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 diff --git a/lib/loraboards/LoRaBoards.cpp b/lib/loraboards/LoRaBoards.cpp index 0331489..5afdb70 100644 --- a/lib/loraboards/LoRaBoards.cpp +++ b/lib/loraboards/LoRaBoards.cpp @@ -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) { diff --git a/src/main.cpp b/src/main.cpp index 1d96db0..7aac9af 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,6 +42,7 @@ #define RADIOLIB_GODMODE (1) #include +#include #include #include @@ -204,8 +205,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; @@ -215,7 +216,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; @@ -360,6 +360,8 @@ void osdProcess() } #endif +Config config; + struct RadioScan : Scan { float getRSSI() override; @@ -514,7 +516,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) { @@ -551,7 +553,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 @@ -576,6 +578,8 @@ void setup(void) bt_start = millis(); wf_start = millis(); + config = Config::init(); + pinMode(LED, OUTPUT); pinMode(BUZZER_PIN, OUTPUT); pinMode(REB_PIN, OUTPUT); @@ -938,11 +942,12 @@ 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 + // reset scan time + if (config.print_profile_time) + { + scan_time = 0; + loop_start = millis(); + } r.epoch++; if (!ANIMATED_RELOAD || !single_page_scan) @@ -1337,10 +1342,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 From 8eff3ef372094f68debc5056b7411ff80cf661d5 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sun, 13 Oct 2024 17:20:18 +0100 Subject: [PATCH 2/6] Add interaction over Serial0 --- lib/comms/comms.cpp | 226 ++++++++++++++++++++++++++++++++++++++++++ lib/comms/comms.h | 78 +++++++++++++++ lib/config/config.cpp | 15 +++ lib/config/config.h | 5 +- lib/scan/scan.h | 10 +- src/main.cpp | 113 ++++++++++++++++++++- 6 files changed, 441 insertions(+), 6 deletions(-) create mode 100644 lib/comms/comms.cpp create mode 100644 lib/comms/comms.h diff --git a/lib/comms/comms.cpp b/lib/comms/comms.cpp new file mode 100644 index 0000000..c350efc --- /dev/null +++ b/lib/comms/comms.cpp @@ -0,0 +1,226 @@ +#include "comms.h" +#include + +#include +#include +#include + +Comms *Comms0; + +TaskHandle_t monitorSerial = NULL; + +void _onReceive0(); + +void monitorSerialTask(void *) +{ + Serial.println("Spawned a task to monitor Serial.available()"); + for (;;) + { + vTaskDelay(pdMS_TO_TICKS(10)); + _onReceive0(); + } +} + +void _onReceive0() +{ + if (Comms0 == NULL) + { + return; + } + + Comms0->_onReceive(); +} + +bool Comms::initComms(Config &c) +{ + if (c.listen_on_usb.equalsIgnoreCase("readline")) + { + // comms using readline plaintext protocol + Comms0 = new ReadlineComms(Serial); + // Serial.onEvent(_onUsbEvent0); + // Serial.begin(); + xTaskCreate(monitorSerialTask, "CHECK_SERIAL_PROCESS", 2048, NULL, 1, + &monitorSerial); + + 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 &); + +void ReadlineComms::_onReceive() +{ + while (serial.available() > 0) + { + partialPacket = partialPacket + serial.readString(); + int i = partialPacket.indexOf('\n'); + while (i >= 0) + { + Message *m = _parsePacket(partialPacket.substring(0, i)); + if (m != NULL) + { + 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.println(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; +} + +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("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); +} + +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 + " ]"; +} diff --git a/lib/comms/comms.h b/lib/comms/comms.h new file mode 100644 index 0000000..1c35dd8 --- /dev/null +++ b/lib/comms/comms.h @@ -0,0 +1,78 @@ +#ifndef __COMMS_H +#define __COMMS_H + +#include +#include + +enum MessageType +{ + SCAN = 0, + SCAN_RESULT, + _MAX_MESSAGE_TYPE = SCAN_RESULT +}; + +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 + { + ScanTask scan; + ScanTaskResult dump; + } payload; +}; + +struct Comms +{ + Stream &serial; + Message **received; + size_t received_sz; + size_t received_pos; + + Comms(Stream &serial) + : serial(serial), received(NULL), received_sz(0), received_pos(0) {}; + + 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 diff --git a/lib/config/config.cpp b/lib/config/config.cpp index d35cfda..879174d 100644 --- a/lib/config/config.cpp +++ b/lib/config/config.cpp @@ -103,6 +103,19 @@ Config Config::init() 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); @@ -122,6 +135,8 @@ bool Config::write_config(const char *path) 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; diff --git a/lib/config/config.h b/lib/config/config.h index 0e24043..1b057f3 100644 --- a/lib/config/config.h +++ b/lib/config/config.h @@ -9,10 +9,13 @@ 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) {}; + log_data_json_interval(1000), listen_on_serial0(String("none")), + listen_on_usb("readline") {}; bool write_config(const char *path); static Config init(); diff --git a/lib/scan/scan.h b/lib/scan/scan.h index c6b113c..9c17e1c 100644 --- a/lib/scan/scan.h +++ b/lib/scan/scan.h @@ -45,15 +45,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; diff --git a/src/main.cpp b/src/main.cpp index 7aac9af..97dba55 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,6 +42,7 @@ #define RADIOLIB_GODMODE (1) #include +#include #include #include #include @@ -475,14 +476,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) { @@ -500,10 +537,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 = (1 << 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 @@ -579,6 +656,15 @@ void setup(void) 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); @@ -705,6 +791,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()); @@ -741,7 +828,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()); @@ -931,6 +1020,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; @@ -942,6 +1049,8 @@ void loop(void) drone_detected_frequency_start = 0; ranges_count = 0; + checkComms(); + // reset scan time if (config.print_profile_time) { From 086101834115d42fee93e8bda89586b1f7860b82 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Thu, 17 Oct 2024 21:32:28 +0100 Subject: [PATCH 3/6] Update SpectrumScan.py --- SpectrumScan.py | 190 ++++++++++++++++++++---------------------------- 1 file changed, 80 insertions(+), 110 deletions(-) diff --git a/SpectrumScan.py b/SpectrumScan.py index acc37a0..82b84bb 100644 --- a/SpectrumScan.py +++ b/SpectrumScan.py @@ -7,30 +7,21 @@ import sys import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt - +import json from datetime import datetime from argparse import RawTextHelpFormatter -# number of samples in each scanline -SCAN_WIDTH = 33 +# Constants +SCAN_WIDTH = 33 # number of samples in each scanline +OUT_PATH = "out" # output path for saved files -# scanline Serial start/end markers -SCAN_MARK_START = 'SCAN ' -SCAN_MARK_FREQ = 'FREQ ' -SCAN_MARK_END = ' END' - -# output path -OUT_PATH = 'out' - -# default settings +# Default settings DEFAULT_BAUDRATE = 115200 DEFAULT_COLOR_MAP = 'viridis' DEFAULT_SCAN_LEN = 200 DEFAULT_RSSI_OFFSET = -11 -# Print iterations progress -# from https://stackoverflow.com/questions/3173320/text-progress-bar-in-terminal-with-block-characters -def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1, length = 50, fill = '█', printEnd = "\r"): +def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=50, fill='█', print_end="\r"): """ Call in a loop to create terminal progress bar @params: @@ -41,136 +32,115 @@ def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1, decimals - Optional : positive number of decimals in percent complete (Int) length - Optional : character length of bar (Int) fill - Optional : bar fill character (Str) - printEnd - Optional : end character (e.g. "\r", "\r\n") (Str) + print_end - Optional : end character (e.g. "\r", "\r\n") (Str) """ percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total))) - filledLength = int(length * iteration // total) - bar = fill * filledLength + '-' * (length - filledLength) - print(f'\r{prefix} |{bar}| {percent}% {suffix}', end = printEnd) + filled_length = int(length * iteration // total) + bar = fill * filled_length + '-' * (length - filled_length) + print(f'\r{prefix} |{bar}| {percent}% {suffix}', end=print_end) if iteration == total: print() +def parse_line(line): + """Parse a JSON line from the serial input.""" + + 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(')', ']')) + def main(): - parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description=''' - RadioLib SX126x_Spectrum_Scan plotter script. Displays output from SX126x_Spectrum_Scan example - as grayscale and + parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''\ + Parse serial data from LOG_DATA_JSON functionality. - Depends on pyserial and matplotlib, install by: - 'python3 -m pip install pyserial matplotlib' - - Step-by-step guide on how to use the script: - 1. Upload the SX126x_Spectrum_Scan example to your Arduino board with SX1262 connected. + 1. #define LOG_DATA_JSON true - add this line in main.cpp, upload to device 2. Run the script with appropriate arguments. 3. Once the scan is complete, output files will be saved to out/ ''') - parser.add_argument('port', - type=str, - help='COM port to connect to the device') - parser.add_argument('--speed', - default=DEFAULT_BAUDRATE, - type=int, - help=f'COM port baudrate (defaults to {DEFAULT_BAUDRATE})') - parser.add_argument('--map', - default=DEFAULT_COLOR_MAP, - type=str, - help=f'Matplotlib color map to use for the output (defaults to "{DEFAULT_COLOR_MAP}")') - parser.add_argument('--len', - default=DEFAULT_SCAN_LEN, - type=int, - 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=f'Default starting frequency in MHz') + parser.add_argument('port', type=str, help='COM port to connect to the device') + parser.add_argument('--speed', default=DEFAULT_BAUDRATE, type=int, + help=f'COM port baudrate (defaults to {DEFAULT_BAUDRATE})') + parser.add_argument('--map', default=DEFAULT_COLOR_MAP, type=str, + help=f'Matplotlib color map to use for the output (defaults to "{DEFAULT_COLOR_MAP}")') + parser.add_argument('--len', default=DEFAULT_SCAN_LEN, type=int, + 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('--buckets', default=-1, type=int, + help='Default number of buckets to group frequencies into; if < 1, will autodetect') + args = parser.parse_args() - freq_mode = False + # Create the result array scan_len = args.len - if (args.freq != -1): - freq_mode = True - scan_len = 1000 + arr = None - # create the color map and the result array - arr = np.zeros((SCAN_WIDTH, scan_len)) - - # scanline counter + # Scanline counter row = 0 - # list of frequencies in frequency mode + # List of frequencies freq_list = [] - # open the COM port + # Open the COM port with serial.Serial(args.port, args.speed, timeout=None) as com: - while(True): - # update the progress bar - if not freq_mode: - printProgressBar(row, scan_len) - # read a single line + com.write(bytes('SCAN -1 -1\n', 'ascii')) + + 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') - except: + line = com.readline().decode('utf-8').strip() + except UnicodeDecodeError: continue - if SCAN_MARK_FREQ in line: - new_freq = float(line.split(' ')[1]) - if (len(freq_list) > 1) and (new_freq < freq_list[-1]): - break + if 'SCAN_RESULT ' in line: + try: + count, data = parse_line(line) + data.sort() + except json.JSONDecodeError: + continue - freq_list.append(new_freq) - print('{:.3f}'.format(new_freq), end = '\r') - continue + r = list(zip(*data)) + if len(r) != 2: + continue - # check the markers - if (SCAN_MARK_START in line) and (SCAN_MARK_END in line): - # get the values - scanline = line[len(SCAN_MARK_START):-len(SCAN_MARK_END)].split(',') - for col in range(SCAN_WIDTH): - arr[col][row] = int(scanline[col]) - - # increment the row counter - row = row + 1 + freqs, rssis = r - # check if we're done - if (not freq_mode) and (row >= scan_len): - break - - # scale to the number of scans (sum of any given scanline) - num_samples = arr.sum(axis=0)[0] - arr *= (num_samples/arr.max()) + if arr is None: + w = count if args.buckets < 1 else args.buckets + arr = np.zeros((scan_len, w)) + arr[:] = arr[:] - 120 + freq_list = freqs - if freq_mode: - scan_len = len(freq_list) + for col in range(len(rssis)): + arr[row][col] = rssis[col] - # create the figure - fig, ax = plt.subplots() + # Increment the row counter + row += 1 - # display the result as heatmap - extent = [0, scan_len, -4*(SCAN_WIDTH + 1), args.offset] - if freq_mode: - extent[0] = freq_list[0] - extent[1] = freq_list[-1] - im = ax.imshow(arr[:,:scan_len], cmap=args.map, extent=extent) - fig.colorbar(im) + # tell it to stop producing SCAN_RESULTS + com.write(bytes('SCAN 0 -1\n', 'ascii')) - # set some properites and show + # Create the figure + fig, ax = plt.subplots(figsize=(12, 8)) + + # Display the result as heatmap + extent = [0, scan_len, freq_list[0], freq_list[-1]] + im = ax.imshow(arr.T, cmap=args.map, extent=extent, aspect='auto', origin='lower') + fig.colorbar(im, label='RSSI (dBm)') + + # Set plot properties and show timestamp = datetime.now().strftime('%y-%m-%d %H-%M-%S') - title = f'RadioLib SX126x Spectral Scan {timestamp}' - if freq_mode: - plt.xlabel("Frequency [Hz]") - else: - plt.xlabel("Time [sample]") - plt.ylabel("RSSI [dBm]") - ax.set_aspect('auto') + title = f'LoraSA Spectral Scan {timestamp}' + plt.xlabel("Time (sample)") + plt.ylabel("Frequency (MHz)") fig.suptitle(title) fig.canvas.manager.set_window_title(title) plt.savefig(f'{OUT_PATH}/{title.replace(" ", "_")}.png', dpi=300) plt.show() if __name__ == "__main__": - main() \ No newline at end of file + main() From ba46499ee70619bc3e5a0bd35bada13f1c09a86d Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sat, 19 Oct 2024 17:40:27 +0100 Subject: [PATCH 4/6] Adjust SpectrumScan --- SpectrumScan.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/SpectrumScan.py b/SpectrumScan.py index 82b84bb..11d1c25 100644 --- a/SpectrumScan.py +++ b/SpectrumScan.py @@ -86,6 +86,8 @@ def main(): 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) @@ -97,14 +99,17 @@ def main(): continue if 'SCAN_RESULT ' in line: + lines += 1 try: count, data = parse_line(line) data.sort() except json.JSONDecodeError: + errors += 1 continue r = list(zip(*data)) - if len(r) != 2: + if len(r) != 2 or len(data) != count: + errors += 1 continue freqs, rssis = r @@ -112,7 +117,6 @@ def main(): if arr is None: w = count if args.buckets < 1 else args.buckets arr = np.zeros((scan_len, w)) - arr[:] = arr[:] - 120 freq_list = freqs for col in range(len(rssis)): @@ -124,6 +128,10 @@ def main(): # 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)) From 9d42fc106150526c354b75aea5db1aabdb10a88d Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sat, 19 Oct 2024 20:51:43 +0100 Subject: [PATCH 5/6] Use onEvent instead of periodic polling --- lib/comms/comms.cpp | 83 ++++++++++++++++++++++++++-------- spectrum_scan.c | 107 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 19 deletions(-) create mode 100644 spectrum_scan.c diff --git a/lib/comms/comms.cpp b/lib/comms/comms.cpp index c350efc..2576f81 100644 --- a/lib/comms/comms.cpp +++ b/lib/comms/comms.cpp @@ -2,25 +2,12 @@ #include #include +#include #include #include Comms *Comms0; -TaskHandle_t monitorSerial = NULL; - -void _onReceive0(); - -void monitorSerialTask(void *) -{ - Serial.println("Spawned a task to monitor Serial.available()"); - for (;;) - { - vTaskDelay(pdMS_TO_TICKS(10)); - _onReceive0(); - } -} - void _onReceive0() { if (Comms0 == NULL) @@ -31,16 +18,27 @@ void _onReceive0() 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(_onUsbEvent0); - // Serial.begin(); - xTaskCreate(monitorSerialTask, "CHECK_SERIAL_PROCESS", 2048, NULL, 1, - &monitorSerial); + Serial.onEvent(ARDUINO_HW_CDC_RX_EVENT, _onUsbEvent0); + Serial.begin(); Serial.println("Initialized communications on Serial using readline protocol"); @@ -155,7 +153,54 @@ bool ReadlineComms::send(Message &m) p = _scan_result_str(m.payload.dump); break; } - serial.println(p); + + const char *cstr = p.c_str(); + size_t cstr_len = strlen(cstr); + + int loops = 0; + uint64_t t0 = millis(); + uint64_t idle_started = 0; + + for (size_t a = serial.availableForWrite(); a < cstr_len; + a = serial.availableForWrite(), loops++) + { + uint64_t now = millis(); + if (now - t0 > 1000) + { + Serial.printf("Unable to make progress after %d loops; %d bytes available " + "for write, %d chars still to write\n", + loops, a, cstr_len); + break; + } + + if (a == 0) + { + if (idle_started == 0) + { + idle_started = now; + } + + if (now - idle_started > 2) + { + vTaskDelay(pdMS_TO_TICKS(2)); + } + else + { + yield(); + } + continue; + } + + idle_started = 0; + + serial.write(cstr, a); + cstr += a; + cstr_len -= a; + } + serial.println(cstr); + + uint64_t dt = millis() - t0; + Serial.printf("Wrote stuff in %d iterations and %" PRIu64 " ms.\n", loops, dt); return true; } diff --git a/spectrum_scan.c b/spectrum_scan.c new file mode 100644 index 0000000..4cadba6 --- /dev/null +++ b/spectrum_scan.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include + +#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; + + 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 *scan_result = "SCAN_RESULT "; + char is_scan_result = strncmp(buffer, scan_result, strlen(scan_result)) == 0; + + if (is_scan_result) { + if ( + !(buffer[pos-2] == ']' || // that's where it should "normally" appear + buffer[pos-3] == ']') // that's where it actually appears - because HWCDC.println produces \r\n + ) + { + errors++; + } + // 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 { + 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); +} From d44bfcdbe20e765b47199f2e070a27a9869a7d69 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sun, 3 Nov 2024 15:52:19 +0000 Subject: [PATCH 6/6] Support Wrap with checksum --- .gitignore | 1 + SpectrumScan.py | 38 ++++++++++++- lib/comms/comms.cpp | 130 ++++++++++++++++++++++++++------------------ lib/comms/comms.h | 14 ++++- spectrum_scan.c | 73 +++++++++++++++++++++++-- src/main.cpp | 2 +- 6 files changed, 196 insertions(+), 62 deletions(-) diff --git a/.gitignore b/.gitignore index 89cc49c..3da96e1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +out/* diff --git a/SpectrumScan.py b/SpectrumScan.py index 11d1c25..fb17802 100644 --- a/SpectrumScan.py +++ b/SpectrumScan.py @@ -48,6 +48,17 @@ def parse_line(line): _, 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='''\ @@ -81,6 +92,9 @@ 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: @@ -94,11 +108,33 @@ def main(): # Read a single line try: - line = com.readline().decode('utf-8').strip() + line = com.readline().decode('utf-8') except UnicodeDecodeError: + errors += 1 + continue + + if 'WRAP ' in line: + try: + _, 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 + + c16 = crc16(line, so_far) + if checksum != c16: + errors += 1 + checksum = -1 + continue + + checksum = -1 + lines += 1 try: count, data = parse_line(line) diff --git a/lib/comms/comms.cpp b/lib/comms/comms.cpp index 2576f81..cbf95df 100644 --- a/lib/comms/comms.cpp +++ b/lib/comms/comms.cpp @@ -22,7 +22,7 @@ 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) { @@ -117,6 +117,30 @@ Message *Comms::receive() 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() { @@ -126,10 +150,27 @@ void ReadlineComms::_onReceive() int i = partialPacket.indexOf('\n'); while (i >= 0) { - Message *m = _parsePacket(partialPacket.substring(0, i)); + 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 (!_messageArrived(*m)) + if (m->type == WRAP) + { + wrap = m; + } + else if (!_messageArrived(*m)) { delete m; } @@ -154,53 +195,7 @@ bool ReadlineComms::send(Message &m) break; } - const char *cstr = p.c_str(); - size_t cstr_len = strlen(cstr); - - int loops = 0; - uint64_t t0 = millis(); - uint64_t idle_started = 0; - - for (size_t a = serial.availableForWrite(); a < cstr_len; - a = serial.availableForWrite(), loops++) - { - uint64_t now = millis(); - if (now - t0 > 1000) - { - Serial.printf("Unable to make progress after %d loops; %d bytes available " - "for write, %d chars still to write\n", - loops, a, cstr_len); - break; - } - - if (a == 0) - { - if (idle_started == 0) - { - idle_started = now; - } - - if (now - idle_started > 2) - { - vTaskDelay(pdMS_TO_TICKS(2)); - } - else - { - yield(); - } - continue; - } - - idle_started = 0; - - serial.write(cstr, a); - cstr += a; - cstr_len -= a; - } - serial.println(cstr); - - uint64_t dt = millis() - t0; - Serial.printf("Wrote stuff in %d iterations and %" PRIu64 " ms.\n", loops, dt); + serial.print(_wrap_str(p)); return true; } @@ -218,6 +213,20 @@ int64_t _intParam(String &p, int64_t default_v) 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(); @@ -239,6 +248,15 @@ Message *_parsePacket(String p) 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(); @@ -254,12 +272,12 @@ Message *_parsePacket(String p) String _scan_str(ScanTask &t) { - return "SCAN " + String(t.count) + " " + String(t.delay); + return "SCAN " + String(t.count) + " " + String(t.delay) + "\n"; } String _scan_result_str(ScanTaskResult &r) { - String p = "SCAN_RESULT " + String(r.sz) + " ["; + String p = "SCAN_RESULT " + String(r.sz) + " [ "; for (int i = 0; i < r.sz; i++) { @@ -267,5 +285,11 @@ String _scan_result_str(ScanTaskResult &r) ")"; } - return p + " ]"; + return p + " ]\n"; +} + +String _wrap_str(String v) +{ + String r = String(v.length()) + "\n" + v; + return "WRAP " + String(crc16(r, 0), 16) + " " + r; } diff --git a/lib/comms/comms.h b/lib/comms/comms.h index 1c35dd8..a37fe12 100644 --- a/lib/comms/comms.h +++ b/lib/comms/comms.h @@ -6,11 +6,18 @@ enum MessageType { - SCAN = 0, + WRAP = 0, + SCAN, SCAN_RESULT, _MAX_MESSAGE_TYPE = SCAN_RESULT }; +struct Wrapper +{ + int32_t length; + uint16_t crc; +}; + struct ScanTask { int64_t count; @@ -29,6 +36,7 @@ struct Message MessageType type; union { + Wrapper wrap; ScanTask scan; ScanTaskResult dump; } payload; @@ -41,8 +49,10 @@ struct Comms size_t received_sz; size_t received_pos; + Message *wrap; + Comms(Stream &serial) - : serial(serial), received(NULL), received_sz(0), received_pos(0) {}; + : serial(serial), received(NULL), received_sz(0), received_pos(0), wrap(NULL) {}; virtual size_t available(); virtual bool send(Message &) = 0; diff --git a/spectrum_scan.c b/spectrum_scan.c index 4cadba6..aa8c9c9 100644 --- a/spectrum_scan.c +++ b/spectrum_scan.c @@ -3,6 +3,34 @@ #include #include #include +#include + +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 @@ -24,6 +52,9 @@ int main(int argc, char** argv) int progress = 0; int progress_ln = 0; + wrap_t wrapper; + wrapper.len = 0; + while(lines - errors < LOOPS) { if (pos == BUFSIZE) @@ -48,17 +79,49 @@ int main(int argc, char** argv) // 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) { - if ( - !(buffer[pos-2] == ']' || // that's where it should "normally" appear - buffer[pos-3] == ']') // that's where it actually appears - because HWCDC.println produces \r\n - ) + 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 @@ -68,7 +131,7 @@ int main(int argc, char** argv) } else if (strncmp(buffer, "Unable to make ", 15) == 0) { lines--; write(1, buffer, pos); - } else { + } else if (!is_wrap) { errors++; } diff --git a/src/main.cpp b/src/main.cpp index 97dba55..2e9bf97 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -560,7 +560,7 @@ void dumpToCommsTask(void *parameter) int64_t delay = report_scans.delay; if (delay == 0) { - delay = (1 << 63) - 1; + delay = (1ull << 63) - 1; } ulTaskNotifyTake(true, pdMS_TO_TICKS(delay));