From 8eff3ef372094f68debc5056b7411ff80cf661d5 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sun, 13 Oct 2024 17:20:18 +0100 Subject: [PATCH] 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) {