From 63458b080e6062c6d40620fee7a23515d7ec1a7f Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Fri, 11 Oct 2024 22:54:17 +0100 Subject: [PATCH 01/16] Fixup OSD output - do not use Json lib for lilygo --- src/main.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 0e91b25..ed469c1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,9 @@ // #define HELTEC_NO_DISPLAY #include +#ifdef HELTEC #include +#endif #include #include @@ -450,7 +452,9 @@ struct frequency_scan_result void logToSerialTask(void *parameter) { +#ifdef HELTEC JsonDocument doc; +#endif char jsonOutput[200]; for (;;) @@ -475,12 +479,19 @@ void logToSerialTask(void *parameter) continue; } +#ifdef HELTEC doc["low_range_freq"] = frequency_scan_result.begin; doc["high_range_freq"] = frequency_scan_result.end; doc["value"] = max_result; serializeJson(doc, jsonOutput); Serial.println(jsonOutput); +#else + Serial.printf("{\"low_range_freq\": %ull, \"high_range_freq\": %ull, " + "\"value\": \"%s\"}\n", + frequency_scan_result.begin, frequency_scan_result.end, + max_result); +#endif } vTaskDelay(LOG_DATA_JSON_INTERVAL / portTICK_PERIOD_MS); } From 309c78c0a9e87be9bfb2fa6b1828367d8a5953fd Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sun, 29 Sep 2024 07:44:14 +0100 Subject: [PATCH 02/16] vscode wants to update settings.json --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index c2f89c3..b5ca201 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,4 +12,7 @@ }, "files.insertFinalNewline": true, "files.autoSave": "onFocusChange", + "files.associations": { + "cstdint": "cpp" + }, } From 51e95485833ba3c24f4cd7c1da0ea4bdcc786ea3 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sun, 6 Oct 2024 14:50:57 +0100 Subject: [PATCH 03/16] fixup platformio spelling --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 4995061..52cad92 100644 --- a/platformio.ini +++ b/platformio.ini @@ -72,7 +72,7 @@ build_flags = -DARDUINO_USB_MODE=1 -[env:lilygo-T3S3-v1-2-xs1280] +[env:lilygo-T3S3-v1-2-sx1280] platform = espressif32 board = t3_s3_v1_x framework = arduino From 9eaf1437d28dc25c81b818d895c700526a71ac25 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Thu, 10 Oct 2024 22:12:17 +0100 Subject: [PATCH 04/16] Platformio update for SX1262 --- platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platformio.ini b/platformio.ini index 52cad92..32ddad0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -59,6 +59,8 @@ board_build.f_cpu = 240000000 lib_deps = ropg/Heltec_ESP32_LoRa_v3@^0.9.1 RadioLib + U8g2 + XPowersLib build_flags = -DLILYGO -DT3_S3_V1_2_SX1262 From 39073dd026708cf5d4024e02271260c962ca6a58 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sat, 28 Sep 2024 16:58:29 +0100 Subject: [PATCH 05/16] Filtered_results is not needed --- lib/scan/scan.cpp | 2 +- src/main.cpp | 24 +++++++++--------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/scan/scan.cpp b/lib/scan/scan.cpp index bc58902..5f584b4 100644 --- a/lib/scan/scan.cpp +++ b/lib/scan/scan.cpp @@ -69,7 +69,7 @@ uint16_t Scan::rssiMethod(size_t samples, uint16_t *result, size_t res_size) size_t Scan::detect(uint16_t *result, bool *filtered_result, size_t result_size, int samples) { - size_t max_rssi_x = 999; + size_t max_rssi_x = result_size; for (int y = 0; y < result_size; y++) { diff --git a/src/main.cpp b/src/main.cpp index ed469c1..6fd371a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1060,13 +1060,12 @@ void loop(void) detected_y[display_x] = false; #if FILTER_SPECTRUM_RESULTS - for (int y = 0; y < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE; y++) + if (detected) { // calculating max window x RSSI after filters x_window = (int)(display_x / WINDOW_SIZE); - int abs_result = abs(result[y]); - if (filtered_result[y] == 1 && result[y] != 0 && result[y] != 1 && - max_x_window[x_window] > abs_result) + int abs_result = abs(result[detected_at]); + if (max_x_window[x_window] > abs_result) { max_x_window[x_window] = abs_result; LOG("MAX x window: %i %i\n", x_window, abs_result); @@ -1147,23 +1146,18 @@ void loop(void) } #endif - for (int y = 0; y < min(RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE, - MAX_POWER_LEVELS - START_LOW); - y++) - { - if (filtered_result[y] == 1) - { - // Set MAIN signal level pixel - display.setPixelColor(display_x, y + START_LOW, WHITE); - } - } + display.setColor(WHITE); + display.drawVerticalLine(display_x, START_LOW + detected_at, + min(RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE, + MAX_POWER_LEVELS - START_LOW) - + detected_at); // ------------------------------------------------------------- // Draw "Detection Level line" every 2 pixel // ------------------------------------------------------------- if (display_x % 2 == 0) { - if (filtered_result[drone_detection_level] == 1) + if (detected_at <= drone_detection_level) { display.setColor(INVERSE); } From c78fa6de7aee3d051de662880d82d4160d14c2f0 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sat, 28 Sep 2024 22:36:48 +0100 Subject: [PATCH 06/16] Factor out drawing into Charts --- lib/charts/BarChart.cpp | 92 +++++++++++++++++++++++++++++++++++++++++ lib/charts/charts.h | 67 ++++++++++++++++++++++++++++++ src/main.cpp | 52 +++++++++++------------ 3 files changed, 185 insertions(+), 26 deletions(-) create mode 100644 lib/charts/BarChart.cpp create mode 100644 lib/charts/charts.h diff --git a/lib/charts/BarChart.cpp b/lib/charts/BarChart.cpp new file mode 100644 index 0000000..eacd78d --- /dev/null +++ b/lib/charts/BarChart.cpp @@ -0,0 +1,92 @@ +#include "charts.h" + +void BarChart::reset() +{ + memset(ys, 0, width * sizeof(float)); + memset(changed, false, width * sizeof(bool)); + redraw_all = true; +} + +int BarChart::updatePoint(float x, float y) +{ + if (x < min_x || x >= max_x) + { + return -1; + } + + size_t idx = width * (x - min_x) / (max_x - min_x); + if (idx >= width) + { + idx = width - 1; + } + + if (!changed[idx] || ys[idx] < y) + { + ys[idx] = y; + changed[idx] = true; + } + + return idx; +} + +void BarChart::draw() +{ + for (int x = 0; x < width; x++) + { + if (!changed[x] && !redraw_all) + continue; + + drawOne(x); + } + + redraw_all = false; +} + +void BarChart::drawOne(int x) +{ + if (x < 0) + return; + + int y = y2pos(ys[x]); + + if (y < height) + { + display.setColor(BLACK); + display.drawVerticalLine(pos_x + x, pos_y, y); + display.setColor(WHITE); + display.drawVerticalLine(pos_x + x, pos_y + y, height - y); + } + else + { + display.setColor(BLACK); + display.drawVerticalLine(pos_x + x, pos_y, height); + } + + if (x % 2 == 0) + { + display.setColor(INVERSE); + display.setPixel(pos_x + x, pos_y + y2pos(level_y)); + } + + changed[x] = false; +} + +int BarChart::x2pos(float x) +{ + if (x < min_x) + x = min_x; + if (x > max_x) + x = max_x; + + return width * (x - min_x) / (max_x - min_x); +} + +int BarChart::y2pos(float y) +{ + if (y < min_y) + y = min_y; + if (y > max_y) + y = max_y; + + return height - height * (y - min_y) / (max_y - min_y); +} diff --git a/lib/charts/charts.h b/lib/charts/charts.h new file mode 100644 index 0000000..5c8a4ce --- /dev/null +++ b/lib/charts/charts.h @@ -0,0 +1,67 @@ +#ifndef CHARTS_H +#define CHARTS_H + +#include +#include +#include + +struct Chart +{ + uint16_t pos_x, pos_y; + uint16_t width, height; + OLEDDisplay &display; + + Chart(OLEDDisplay &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h) + : display(d), pos_x(x), pos_y(y), width(w), height(h) {}; + + /* + * This method resets the state and sets the reference time. + */ + virtual void reset() = 0; + + /* + * Update one data point, and return what column needs redrawing. + */ + virtual int updatePoint(float x, float y) = 0; + + /* + * If you fancy animated progress, then pass the output of updatePoint to here. + */ + virtual void drawOne(int x) = 0; + + /* + * Redraw everything that needs redrawing. + */ + virtual void draw() = 0; +}; + +struct BarChart : Chart +{ + float min_x, max_x, min_y, max_y; + float level_y; + + float *ys; + bool *changed; + bool redraw_all; + + BarChart(OLEDDisplay &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), min_x(min_x), max_x(max_x), min_y(min_y), max_y(max_y), + level_y(level_y), redraw_all(true) + { + ys = new float[w]; + changed = new bool[w]; + + memset(ys, 0, w * sizeof(float)); + memset(changed, 0, w * sizeof(bool)); + }; + + void reset() override; + int updatePoint(float x, float y) override; + void drawOne(int x) override; + void draw() override; + + int x2pos(float x); + int y2pos(float y); +}; +#endif diff --git a/src/main.cpp b/src/main.cpp index 6fd371a..4e232ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,6 +41,7 @@ // library internals. #define RADIOLIB_GODMODE (1) +#include #include #ifndef LILYGO @@ -365,6 +366,8 @@ void osdProcess() } #endif +BarChart *bar; + void init_radio() { // initialize SX1262 FSK modem at the initial frequency @@ -653,6 +656,14 @@ void setup(void) #ifdef LOG_DATA_JSON xTaskCreate(logToSerialTask, "LOG_DATA_JSON", 2048, NULL, 1, NULL); #endif + + bar = new BarChart(display, 0, START_LOW, display.width(), + display.height() / 2 - START_LOW, FREQ_BEGIN, FREQ_END, + LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, + -(float)show_db_after); // LO_RSSI_THRESHOLD + (HI_RSSI_THRESHOLD - + // LO_RSSI_THRESHOLD) * 0.3); + + bar->reset(); } // Formula to translate 33 bin to approximate RSSI value @@ -1060,6 +1071,7 @@ void loop(void) detected_y[display_x] = false; #if FILTER_SPECTRUM_RESULTS + float rr; if (detected) { // calculating max window x RSSI after filters @@ -1070,6 +1082,18 @@ void loop(void) max_x_window[x_window] = abs_result; LOG("MAX x window: %i %i\n", x_window, abs_result); } + rr = -(float)result[detected_at]; + } + else + { + rr = LO_RSSI_THRESHOLD; + } + + int updated = bar->updatePoint(freq, rr); + + if (first_run || ANIMATED_RELOAD) + { + bar->drawOne(updated); } #endif @@ -1146,31 +1170,6 @@ void loop(void) } #endif - display.setColor(WHITE); - display.drawVerticalLine(display_x, START_LOW + detected_at, - min(RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE, - MAX_POWER_LEVELS - START_LOW) - - detected_at); - - // ------------------------------------------------------------- - // Draw "Detection Level line" every 2 pixel - // ------------------------------------------------------------- - if (display_x % 2 == 0) - { - if (detected_at <= drone_detection_level) - { - display.setColor(INVERSE); - } - else - { - display.setColor(WHITE); - } - display.setPixel(display_x, drone_detection_level + START_LOW); - // display.setPixel(display_x, y + START_LOW - 1); // 2 px wide - - display.setColor(WHITE); - } - #ifdef JOYSTICK_ENABLED // Draw joystick cursor and Frequency RSSI value if (display_x == cursor_x_position) @@ -1258,6 +1257,7 @@ void loop(void) } #endif + bar->draw(); #ifdef METHOD_RSSI // Printing Max Window DB. for (int x2 = 0; x2 < STEPS / WINDOW_SIZE; x2++) @@ -1270,7 +1270,7 @@ void loop(void) max_x_window[x2] = 999; } #endif - // Render display data here + // Render display data here display.display(); #ifdef OSD_ENABLED // Sometimes OSD prints entire screen with the digits. From 02df8efa1c9b67aac1bf796f9fc39217a00f447b Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sun, 29 Sep 2024 08:29:40 +0100 Subject: [PATCH 07/16] DecoratedBarChart - labels and axis with ticks --- lib/charts/BarChart.cpp | 59 +++++++++++++++++++++++++++++++++++++++++ lib/charts/charts.h | 21 +++++++++++++++ src/main.cpp | 32 +++------------------- src/ui.cpp | 12 --------- 4 files changed, 84 insertions(+), 40 deletions(-) diff --git a/lib/charts/BarChart.cpp b/lib/charts/BarChart.cpp index eacd78d..cdc04d8 100644 --- a/lib/charts/BarChart.cpp +++ b/lib/charts/BarChart.cpp @@ -90,3 +90,62 @@ int BarChart::y2pos(float y) return height - height * (y - min_y) / (max_y - min_y); } + +void DecoratedBarChart::draw() +{ + bool draw_axis = redraw_all; + + BarChart::draw(); + + display.setColor(BLACK); + display.fillRect(pos_x, text_y, width, pos_y - text_y); + + display.setColor(WHITE); + display.setTextAlignment(TEXT_ALIGN_LEFT); + + for (uint16_t x = 0; x < width; x++) + { + float y = ys[x]; + if (y >= level_y) + { + String s = String(ys[x], 0); + uint16_t w = display.getStringWidth(s); + uint16_t x1 = x; + for (; x < x1 + w; x++) + { + if (ys[x] > y) + { + y = ys[x]; + s = String(y, 0); + w = max(w, display.getStringWidth(s)); + } + } + + display.drawString(x1, text_y, s); + } + } + + if (draw_axis) + { + display.setColor(WHITE); + + uint16_t y = pos_y + height + 1; + display.fillRect(pos_x, y, width, X_AXIS_WEIGHT); + + // Start and end ticks + display.fillRect(pos_x, y + 1, 2, AXIS_HEIGHT); + display.fillRect(pos_x + width - 2, y + 1, 2, AXIS_HEIGHT); + + for (float step = 0; min_x + step * MAJOR_TICKS < max_x; step += 1) + { + int tick_pos = x2pos(min_x + step * MAJOR_TICKS); + display.drawVerticalLine(pos_x + tick_pos, y + 1, MAJOR_TICK_LENGTH); + } + + for (float step = 0; min_x + step * MINOR_TICKS < max_x; step += 1) + { + int tick_pos = x2pos(min_x + step * MINOR_TICKS); + display.drawVerticalLine(pos_x + tick_pos, y + 1, MINOR_TICK_LENGTH); + } + } +} diff --git a/lib/charts/charts.h b/lib/charts/charts.h index 5c8a4ce..83675a8 100644 --- a/lib/charts/charts.h +++ b/lib/charts/charts.h @@ -64,4 +64,25 @@ struct BarChart : Chart int x2pos(float x); int y2pos(float y); }; + +#define LABEL_HEIGHT 6 +#define X_AXIS_WEIGHT 1 +#define MAJOR_TICK_LENGTH 2 +#define MAJOR_TICKS 10 +#define MINOR_TICK_LENGTH 1 +#define MINOR_TICKS 5 +#define AXIS_HEIGHT (X_AXIS_WEIGHT + MAJOR_TICK_LENGTH + 1) +struct DecoratedBarChart : BarChart +{ + int text_y; + + DecoratedBarChart(OLEDDisplay &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) + : BarChart(d, x, y + LABEL_HEIGHT, w, h - LABEL_HEIGHT - AXIS_HEIGHT, min_x, + max_x, min_y, max_y, level_y), + text_y(y) {}; + + void draw() override; +}; + #endif diff --git a/src/main.cpp b/src/main.cpp index 4e232ab..939ffaa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -657,11 +657,9 @@ void setup(void) xTaskCreate(logToSerialTask, "LOG_DATA_JSON", 2048, NULL, 1, NULL); #endif - bar = new BarChart(display, 0, START_LOW, display.width(), - display.height() / 2 - START_LOW, FREQ_BEGIN, FREQ_END, - LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, - -(float)show_db_after); // LO_RSSI_THRESHOLD + (HI_RSSI_THRESHOLD - - // LO_RSSI_THRESHOLD) * 0.3); + bar = new DecoratedBarChart( + display, 0, 0, display.width(), display.height() / 2 + AXIS_HEIGHT, FREQ_BEGIN, + FREQ_END, LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, -(float)show_db_after); bar->reset(); } @@ -1070,18 +1068,9 @@ void loop(void) detected = detected_at < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE; detected_y[display_x] = false; -#if FILTER_SPECTRUM_RESULTS float rr; if (detected) { - // calculating max window x RSSI after filters - x_window = (int)(display_x / WINDOW_SIZE); - int abs_result = abs(result[detected_at]); - if (max_x_window[x_window] > abs_result) - { - max_x_window[x_window] = abs_result; - LOG("MAX x window: %i %i\n", x_window, abs_result); - } rr = -(float)result[detected_at]; } else @@ -1095,7 +1084,6 @@ void loop(void) { bar->drawOne(updated); } -#endif if (detected_at <= drone_detection_level) { @@ -1258,19 +1246,7 @@ void loop(void) #endif bar->draw(); -#ifdef METHOD_RSSI - // Printing Max Window DB. - for (int x2 = 0; x2 < STEPS / WINDOW_SIZE; x2++) - { - if (max_x_window[x2] < show_db_after && max_x_window[x2] != 0) - { - display.drawString(x2 * WINDOW_SIZE + WINDOW_SIZE, 0, - "-" + String(max_x_window[x2])); - } - max_x_window[x2] = 999; - } -#endif - // Render display data here + // Render display data here display.display(); #ifdef OSD_ENABLED // Sometimes OSD prints entire screen with the digits. diff --git a/src/ui.cpp b/src/ui.cpp index ee8bc92..ea376dd 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -184,9 +184,6 @@ void UI_displayDecorate(int begin = 0, int end = 0, bool redraw = false) { if (!ui_initialized) { - // Start and end ticks - display_instance->fillRect(0, HEIGHT + X_AXIS_WEIGHT, 2, MAJOR_TICK_LENGTH + 1); - display_instance->fillRect(126, HEIGHT + X_AXIS_WEIGHT, 2, MAJOR_TICK_LENGTH + 1); // Drone detection level display_instance->setTextAlignment(TEXT_ALIGN_RIGHT); display_instance->drawString(128, 0, String(drone_detection_level)); @@ -299,14 +296,5 @@ void UI_displayDecorate(int begin = 0, int end = 0, bool redraw = false) String(SCAN_RANGES[range_item + 1] % 1000)); } } - if (ui_initialized == false) - { - // X-axis - display_instance->fillRect(0, HEIGHT, STEPS, X_AXIS_WEIGHT); -// ticks -#ifdef MAJOR_TICKS - drawTicks(MAJOR_TICKS, MAJOR_TICK_LENGTH); -#endif - } ui_initialized = true; } From 384743ddb41db961cd9c3ae5ac9096b77525232a Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sun, 29 Sep 2024 13:09:22 +0100 Subject: [PATCH 08/16] StackedChart support --- lib/charts/BarChart.cpp | 17 +++++++- lib/charts/StackedChart.cpp | 87 +++++++++++++++++++++++++++++++++++++ lib/charts/charts.h | 56 +++++++++++++++++++----- src/main.cpp | 18 +++++--- 4 files changed, 162 insertions(+), 16 deletions(-) create mode 100644 lib/charts/StackedChart.cpp diff --git a/lib/charts/BarChart.cpp b/lib/charts/BarChart.cpp index cdc04d8..c3e8116 100644 --- a/lib/charts/BarChart.cpp +++ b/lib/charts/BarChart.cpp @@ -1,7 +1,22 @@ #include "charts.h" -void BarChart::reset() +void BarChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + pos_x = x; + pos_y = y; + + if (w != width) + { + delete[] ys; + delete[] changed; + + width = w; + ys = new float[width]; + changed = new bool[width]; + } + + height = h; + memset(ys, 0, width * sizeof(float)); memset(changed, false, width * sizeof(bool)); redraw_all = true; diff --git a/lib/charts/StackedChart.cpp b/lib/charts/StackedChart.cpp new file mode 100644 index 0000000..be602bd --- /dev/null +++ b/lib/charts/StackedChart.cpp @@ -0,0 +1,87 @@ +#include "charts.h" + +uint16_t trim_w(uint16_t pos, uint16_t width, uint16_t w) +{ + return min(width, (uint16_t)(max(w, pos) - pos)); +} + +size_t StackedChart::addChart(Chart *c) +{ + Chart **cc = new Chart *[charts_sz + 1]; + memcpy(cc, charts, charts_sz * sizeof(Chart *)); + cc[charts_sz] = c; + free(charts); + + c->reset(pos_x + c->pos_x, pos_y + c->pos_y, trim_w(c->pos_x, c->width, width), c->height); + charts = cc; + return charts_sz++; +} + +uint16_t StackedChart::setHeight(size_t c, uint16_t h) +{ + if (h < height) + { + charts[c]->reset(charts[c]->pos_x, charts[c]->pos_y, charts[c]->width, h); + + uint16_t used_space = 0; + for (int i = 0; i < charts_sz; i++) + { + used_space += charts[i]->height; + } + + return used_space; + } + // this chart gets special treatment - pack all other charts, + // and make this one as big as possible + uint16_t used_space = 0; + for (int i = 0; i < c; i++) + { + charts[i]->reset(charts[i]->pos_x, pos_y + used_space, charts[i]->width, + charts[i]->height); + used_space += charts[i]->height; + } + + uint16_t more_used_space = used_space; + for (int i = c + 1; i < charts_sz; i++) + { + more_used_space += charts[i]->height; + } + + if (more_used_space < height) + { + charts[c]->reset(charts[c]->pos_x, pos_y + used_space, charts[c]->width, + height - more_used_space); + used_space += charts[c]->height; + } + + for (int i = c + 1; i < charts_sz; i++) + { + charts[i]->reset(charts[i]->pos_x, pos_y + used_space, charts[i]->width, + charts[i]->height); + used_space += charts[i]->height; + } + + return used_space; +} + +void StackedChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) +{ + for (int i = 0; i < charts_sz; i++) + { + uint16_t rel_x = charts[i]->pos_x - pos_x; + uint16_t rel_y = charts[i]->pos_y - pos_y; + charts[i]->reset(x + rel_x, y + rel_y, trim_w(rel_x, charts[i]->width, w), + charts[i]->height); + } + + pos_x = x; + pos_y = y; + width = w; + height = h; +} + +void StackedChart::draw() +{ + for (int i = 0; i < charts_sz; i++) + charts[i]->draw(); +} diff --git a/lib/charts/charts.h b/lib/charts/charts.h index 83675a8..54fc2fa 100644 --- a/lib/charts/charts.h +++ b/lib/charts/charts.h @@ -17,7 +17,21 @@ struct Chart /* * This method resets the state and sets the reference time. */ - virtual void reset() = 0; + virtual void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {}; + + /* + * Redraw everything that needs redrawing. + */ + virtual void draw() {}; +}; + +/* + * ProgressChart supports updates with progressive redraw of just the affected area. + */ +struct ProgressChart : Chart +{ + ProgressChart(OLEDDisplay &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h) + : Chart(d, x, y, w, h) {}; /* * Update one data point, and return what column needs redrawing. @@ -28,14 +42,9 @@ struct Chart * If you fancy animated progress, then pass the output of updatePoint to here. */ virtual void drawOne(int x) = 0; - - /* - * Redraw everything that needs redrawing. - */ - virtual void draw() = 0; }; -struct BarChart : Chart +struct BarChart : ProgressChart { float min_x, max_x, min_y, max_y; float level_y; @@ -46,8 +55,8 @@ struct BarChart : Chart BarChart(OLEDDisplay &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), min_x(min_x), max_x(max_x), min_y(min_y), max_y(max_y), - level_y(level_y), redraw_all(true) + : ProgressChart(d, x, y, w, h), min_x(min_x), max_x(max_x), min_y(min_y), + max_y(max_y), level_y(level_y), redraw_all(true) { ys = new float[w]; changed = new bool[w]; @@ -56,7 +65,7 @@ struct BarChart : Chart memset(changed, 0, w * sizeof(bool)); }; - void reset() override; + void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override; int updatePoint(float x, float y) override; void drawOne(int x) override; void draw() override; @@ -85,4 +94,31 @@ struct DecoratedBarChart : BarChart void draw() override; }; +struct StackedChart : Chart +{ + Chart **charts; + size_t charts_sz; + + StackedChart(OLEDDisplay &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h) + : Chart(d, x, y, w, h), charts(NULL), charts_sz(0) {}; + + /* + * addChart adds c to the StackedChart, treats pos_x and pos_y of the chart + * as relative to this chart's origin, and trims width to fit. Adjust the + * height and pack charts using setHeight. + */ + size_t addChart(Chart *c); + + /* + * Adjust the height of the chart and return the resulting required height. + * If h is >= height, the chart gets a special treatment: packs all other + * charts, and uses up the rest of space. + */ + uint16_t setHeight(size_t c, uint16_t h); + + void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override; + + void draw() override; +}; + #endif diff --git a/src/main.cpp b/src/main.cpp index 939ffaa..2e8fbb6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -367,6 +367,7 @@ void osdProcess() #endif BarChart *bar; +StackedChart stacked(display, 0, 0, 0, 0); void init_radio() { @@ -657,11 +658,18 @@ void setup(void) xTaskCreate(logToSerialTask, "LOG_DATA_JSON", 2048, NULL, 1, NULL); #endif - bar = new DecoratedBarChart( - display, 0, 0, display.width(), display.height() / 2 + AXIS_HEIGHT, FREQ_BEGIN, - FREQ_END, LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, -(float)show_db_after); + bar = new DecoratedBarChart(display, 0, 0, display.width(), 0, FREQ_BEGIN, FREQ_END, + LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, + -(float)show_db_after); - bar->reset(); + stacked.reset(0, 0, display.width(), display.height() - 6); + + size_t b = stacked.addChart(bar); + size_t c = + stacked.addChart(new Chart(display, 0, 0, display.width(), 0)); + + stacked.setHeight(c, stacked.height - WATERFALL_START); + stacked.setHeight(b, stacked.height); } // Formula to translate 33 bin to approximate RSSI value @@ -1245,7 +1253,7 @@ void loop(void) } #endif - bar->draw(); + stacked.draw(); // Render display data here display.display(); #ifdef OSD_ENABLED From 6011c47f81faa958a740cf74820a270ea6706253 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Mon, 30 Sep 2024 07:37:18 +0100 Subject: [PATCH 09/16] Fixup Chart inheritance --- lib/charts/BarChart.cpp | 16 ++++++---------- lib/charts/StackedChart.cpp | 5 +---- lib/charts/charts.h | 8 +++++++- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/charts/BarChart.cpp b/lib/charts/BarChart.cpp index c3e8116..eb58770 100644 --- a/lib/charts/BarChart.cpp +++ b/lib/charts/BarChart.cpp @@ -2,24 +2,20 @@ void BarChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { - pos_x = x; - pos_y = y; - if (w != width) { delete[] ys; delete[] changed; - width = w; - ys = new float[width]; - changed = new bool[width]; + ys = new float[w]; + changed = new bool[w]; } - height = h; - - memset(ys, 0, width * sizeof(float)); - memset(changed, false, width * sizeof(bool)); + memset(ys, 0, w * sizeof(float)); + memset(changed, false, w * sizeof(bool)); redraw_all = true; + + Chart::reset(x, y, w, h); } int BarChart::updatePoint(float x, float y) diff --git a/lib/charts/StackedChart.cpp b/lib/charts/StackedChart.cpp index be602bd..1c879dc 100644 --- a/lib/charts/StackedChart.cpp +++ b/lib/charts/StackedChart.cpp @@ -74,10 +74,7 @@ void StackedChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) charts[i]->height); } - pos_x = x; - pos_y = y; - width = w; - height = h; + Chart::reset(x, y, w, h); } void StackedChart::draw() diff --git a/lib/charts/charts.h b/lib/charts/charts.h index 54fc2fa..59fc29e 100644 --- a/lib/charts/charts.h +++ b/lib/charts/charts.h @@ -17,7 +17,13 @@ struct Chart /* * This method resets the state and sets the reference time. */ - virtual void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {}; + virtual void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) + { + pos_x = x; + pos_y = y; + width = w; + height = h; + } /* * Redraw everything that needs redrawing. From 30883386c493c22161156974a85330a4ef6128f2 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Mon, 30 Sep 2024 07:59:34 +0100 Subject: [PATCH 10/16] Fixup DecoratedBarChart - incapsulate, not inherit --- lib/charts/BarChart.cpp | 44 +++++++++++++++++++++++------------------ lib/charts/charts.h | 13 ++++++------ src/main.cpp | 6 +++--- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/lib/charts/BarChart.cpp b/lib/charts/BarChart.cpp index eb58770..36c17d7 100644 --- a/lib/charts/BarChart.cpp +++ b/lib/charts/BarChart.cpp @@ -102,37 +102,43 @@ int BarChart::y2pos(float y) return height - height * (y - min_y) / (max_y - min_y); } +void DecoratedBarChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) +{ + Chart::reset(x, y, w, h); + bar.reset(x, y + LABEL_HEIGHT, w, h - LABEL_HEIGHT - AXIS_HEIGHT); +} + void DecoratedBarChart::draw() { - bool draw_axis = redraw_all; + bool draw_axis = bar.redraw_all; - BarChart::draw(); + bar.draw(); display.setColor(BLACK); - display.fillRect(pos_x, text_y, width, pos_y - text_y); + display.fillRect(pos_x, pos_y, width, bar.pos_y - pos_y); display.setColor(WHITE); display.setTextAlignment(TEXT_ALIGN_LEFT); for (uint16_t x = 0; x < width; x++) { - float y = ys[x]; - if (y >= level_y) + float y = bar.ys[x]; + if (y >= bar.level_y) { - String s = String(ys[x], 0); + String s = String(bar.ys[x], 0); uint16_t w = display.getStringWidth(s); uint16_t x1 = x; for (; x < x1 + w; x++) { - if (ys[x] > y) + if (bar.ys[x] > y) { - y = ys[x]; + y = bar.ys[x]; s = String(y, 0); w = max(w, display.getStringWidth(s)); } } - display.drawString(x1, text_y, s); + display.drawString(x1, pos_y, s); } } @@ -140,23 +146,23 @@ void DecoratedBarChart::draw() { display.setColor(WHITE); - uint16_t y = pos_y + height + 1; - display.fillRect(pos_x, y, width, X_AXIS_WEIGHT); + uint16_t y = pos_y + height - AXIS_HEIGHT + 2; + display.fillRect(pos_x, y - 1, width, X_AXIS_WEIGHT); // Start and end ticks - display.fillRect(pos_x, y + 1, 2, AXIS_HEIGHT); - display.fillRect(pos_x + width - 2, y + 1, 2, AXIS_HEIGHT); + display.fillRect(pos_x, y - 1, 2, AXIS_HEIGHT); + display.fillRect(pos_x + width - 2, y - 1, 2, AXIS_HEIGHT); - for (float step = 0; min_x + step * MAJOR_TICKS < max_x; step += 1) + for (float step = 0; bar.min_x + step * MAJOR_TICKS < bar.max_x; step += 1) { - int tick_pos = x2pos(min_x + step * MAJOR_TICKS); - display.drawVerticalLine(pos_x + tick_pos, y + 1, MAJOR_TICK_LENGTH); + int tick_pos = bar.x2pos(bar.min_x + step * MAJOR_TICKS); + display.drawVerticalLine(pos_x + tick_pos, y, MAJOR_TICK_LENGTH); } - for (float step = 0; min_x + step * MINOR_TICKS < max_x; step += 1) + for (float step = 0; bar.min_x + step * MINOR_TICKS < bar.max_x; step += 1) { - int tick_pos = x2pos(min_x + step * MINOR_TICKS); - display.drawVerticalLine(pos_x + tick_pos, y + 1, MINOR_TICK_LENGTH); + int tick_pos = bar.x2pos(bar.min_x + step * MINOR_TICKS); + display.drawVerticalLine(pos_x + tick_pos, y, MINOR_TICK_LENGTH); } } } diff --git a/lib/charts/charts.h b/lib/charts/charts.h index 59fc29e..f50df37 100644 --- a/lib/charts/charts.h +++ b/lib/charts/charts.h @@ -86,17 +86,18 @@ struct BarChart : ProgressChart #define MAJOR_TICKS 10 #define MINOR_TICK_LENGTH 1 #define MINOR_TICKS 5 -#define AXIS_HEIGHT (X_AXIS_WEIGHT + MAJOR_TICK_LENGTH + 1) -struct DecoratedBarChart : BarChart +#define AXIS_HEIGHT (X_AXIS_WEIGHT + MAJOR_TICK_LENGTH + 2) +struct DecoratedBarChart : Chart { - int text_y; + BarChart bar; DecoratedBarChart(OLEDDisplay &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) - : BarChart(d, x, y + LABEL_HEIGHT, w, h - LABEL_HEIGHT - AXIS_HEIGHT, min_x, - max_x, min_y, max_y, level_y), - text_y(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) {}; + void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override; void draw() override; }; diff --git a/src/main.cpp b/src/main.cpp index 2e8fbb6..639f350 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -366,7 +366,7 @@ void osdProcess() } #endif -BarChart *bar; +DecoratedBarChart *bar; StackedChart stacked(display, 0, 0, 0, 0); void init_radio() @@ -1086,11 +1086,11 @@ void loop(void) rr = LO_RSSI_THRESHOLD; } - int updated = bar->updatePoint(freq, rr); + int updated = bar->bar.updatePoint(freq, rr); if (first_run || ANIMATED_RELOAD) { - bar->drawOne(updated); + bar->bar.drawOne(updated); } if (detected_at <= drone_detection_level) From ad7ad3d8afd08b1a85f4d1af09f86ee5a737c16e Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Mon, 30 Sep 2024 07:36:36 +0100 Subject: [PATCH 11/16] Waterfall chart to capture history of detections --- lib/charts/WaterfallChart.cpp | 57 +++++++++ lib/charts/charts.h | 25 ++++ lib/models/WaterfallModel.cpp | 153 +++++++++++++++++++++++ lib/models/models.h | 27 +++++ src/main.cpp | 26 +++- test/test_rssi.cpp | 14 --- test/test_waterfall.cpp | 220 ++++++++++++++++++++++++++++++++++ test/tests.cpp | 21 ++++ 8 files changed, 524 insertions(+), 19 deletions(-) create mode 100644 lib/charts/WaterfallChart.cpp create mode 100644 lib/models/WaterfallModel.cpp create mode 100644 lib/models/models.h create mode 100644 test/test_waterfall.cpp create mode 100644 test/tests.cpp diff --git a/lib/charts/WaterfallChart.cpp b/lib/charts/WaterfallChart.cpp new file mode 100644 index 0000000..af83090 --- /dev/null +++ b/lib/charts/WaterfallChart.cpp @@ -0,0 +1,57 @@ +#include "charts.h" +#include + +void WaterfallChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) +{ + Chart::reset(x, y, w, h); + + model->reset(model->times[0], w); + + update_to = model->buckets; +} + +void WaterfallChart::updatePoint(uint64_t t, float x, float y) +{ + if (x < min_x || x >= max_x) + { + return; + } + + update_to = max(update_to, model->updateModel(t, x2pos(x), y >= level_y)); +} + +void WaterfallChart::draw() +{ + size_t h = min(update_to, (size_t)height); + + for (int y = 0; y < h; y++) + { + for (int x = 0; x < width; x++) + { + bool b = model->counts[y][x] > 0 && + (model->events[y][x] >= model->counts[y][x] * threshold); + if (b) + { + display.setColor(WHITE); + } + else + { + display.setColor(BLACK); + } + + display.setPixel(pos_x + x, pos_y + y); + } + } + + update_to = 0; +} + +int WaterfallChart::x2pos(float x) +{ + if (x < min_x) + x = min_x; + if (x > max_x) + x = max_x; + + return width * (x - min_x) / (max_x - min_x); +} diff --git a/lib/charts/charts.h b/lib/charts/charts.h index f50df37..c2c7dfb 100644 --- a/lib/charts/charts.h +++ b/lib/charts/charts.h @@ -3,6 +3,7 @@ #include #include +#include #include struct Chart @@ -128,4 +129,28 @@ struct StackedChart : Chart void draw() override; }; +struct WaterfallChart : Chart +{ + float min_x, max_x; + float level_y, threshold; + + size_t update_to; + + WaterfallModel *model; + + WaterfallChart(OLEDDisplay &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h, + float min_x, float max_x, float level_y, float threshold, + WaterfallModel *m) + : Chart(d, x, y, w, h), model(m), min_x(min_x), max_x(max_x), level_y(level_y), + threshold(threshold), update_to(m->buckets) {}; + + void updatePoint(uint64_t t, float x, float y); + + void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override; + + void draw() override; + + int x2pos(float x); +}; + #endif diff --git a/lib/models/WaterfallModel.cpp b/lib/models/WaterfallModel.cpp new file mode 100644 index 0000000..99f7816 --- /dev/null +++ b/lib/models/WaterfallModel.cpp @@ -0,0 +1,153 @@ +#include "models.h" +#include + +WaterfallModel::WaterfallModel(size_t w, uint64_t base_dt, size_t m_sz, + const size_t *multiples) +{ + width = w; + + size_t dt_sz = 0; + for (int i = 0; i < m_sz; i++) + dt_sz += multiples[i]; + + buckets = dt_sz; + dt = new uint64_t[buckets]; + events = new uint32_t *[buckets]; + counts = new uint32_t *[buckets]; + times = new uint64_t[buckets]; + + uint64_t m = base_dt; + for (int i = 0, j = 0; i < m_sz; i++) + { + for (int k = 0; k < multiples[i]; k++, j++) + { + dt[j] = m; + + events[j] = new uint32_t[width]; + counts[j] = new uint32_t[width]; + } + + m *= multiples[i]; + } +} + +void WaterfallModel::reset(uint64_t t0, size_t w) +{ + if (w != width) + { + width = w; + for (int i = 0; i < buckets; i++) + { + delete[] counts[i]; + delete[] events[i]; + + counts[i] = new uint32_t[w]; + events[i] = new uint32_t[w]; + } + } + + for (int i = 0; i < buckets; i++) + { + memset(counts[i], 0, width * sizeof(uint32_t)); + memset(events[i], 0, width * sizeof(uint32_t)); + times[i] = t0 + dt[i]; + } +} + +/* + * The model is literally a stack of counters: + * - incomplete second + * - n complete seconds + * - incomplete minute + * - n complete minutes + * - ... + * + * updateModel updates incomplete second. When the second becomes complete, it + * gets pushed to complete seconds, and the last complete second is rotated out + * and it gets added to incomplete minute. This gets repeated for incomplete + * minutes, etc. + */ +size_t WaterfallModel::updateModel(uint16_t t, size_t x, uint16_t y) +{ + size_t changed = 1; + + while (t > times[0]) + { + changed = push(); + } + + counts[0][x]++; + events[0][x] += y; + return changed; +} + +size_t WaterfallModel::push() +{ + size_t i = 1; + for (; i < buckets; i++) + { + if (dt[i - 1] == dt[i]) + continue; + if (times[i - 1] <= times[i]) + break; + times[i - 1] = times[i] + dt[i]; + } + + uint64_t t0 = times[0]; + + uint32_t *cc = counts[i - 1]; + uint32_t *ee = events[i - 1]; + memmove(times + 1, times, (i - 1) * sizeof(uint64_t)); + memmove(counts + 1, counts, (i - 1) * sizeof(uint32_t *)); + memmove(events + 1, events, (i - 1) * sizeof(uint32_t *)); + + if (i < buckets) + { + for (int j = 0; j < width; j++) + { + counts[i][j] += cc[j]; + events[i][j] += ee[j]; + } + + i++; + } + + memset(cc, 0, width * sizeof(uint32_t)); + memset(ee, 0, width * sizeof(uint32_t)); + counts[0] = cc; + events[0] = ee; + times[0] = t0 + dt[0]; + + return i; +} + +#ifdef TO_STRING + +#include +#include + +#endif + +char *WaterfallModel::toString() +{ +#ifdef TO_STRING + std::stringstream r; + r << "w:" << width << " b:" << buckets << " ["; + + for (int i = 0; i < buckets; i++) + { + r << "dt:" << dt[i] << " t:" << times[i] << " ["; + for (int j = 0; j < width; j++) + r << " c:" << counts[i][j] << " e:" << events[i][j]; + r << " ]"; + } + + r << " ]"; + + char *ret = new char[r.str().length() + 1]; + strncpy(ret, r.str().c_str(), r.str().length()); +#else + char *ret = NULL; +#endif + return ret; +} diff --git a/lib/models/models.h b/lib/models/models.h new file mode 100644 index 0000000..ce8de8f --- /dev/null +++ b/lib/models/models.h @@ -0,0 +1,27 @@ +#ifndef CHARTS_MODELS_H +#define CHARTS_MODELS_H + +#include +#include +#include + +struct WaterfallModel +{ + uint32_t **events; + uint32_t **counts; + uint64_t *times; + uint64_t *dt; + + size_t buckets; + size_t width; + + WaterfallModel(size_t w, uint64_t base_dt, size_t m_sz, const size_t *multiples); + + void reset(uint64_t t0, size_t width); + size_t updateModel(uint16_t t, size_t x, uint16_t y); + size_t push(); + + char *toString(); +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index 639f350..d04b6ec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -366,7 +366,9 @@ void osdProcess() } #endif +#define WATERFALL_SENSITIVITY 0.05 DecoratedBarChart *bar; +WaterfallChart *waterChart; StackedChart stacked(display, 0, 0, 0, 0); void init_radio() @@ -664,9 +666,21 @@ void setup(void) stacked.reset(0, 0, display.width(), display.height() - 6); + size_t *multiples = new size_t[6]{5, 3, 4, 15, 4, 3}; + WaterfallModel *model = + new WaterfallModel((size_t)display.width(), 1000, 6, multiples); + model->reset(millis(), display.width()); + + delete[] multiples; + + waterChart = + new WaterfallChart(display, 0, WATERFALL_START, display.width(), 0, FREQ_BEGIN, + FREQ_END, -(float)show_db_after, WATERFALL_SENSITIVITY, model); + + stacked.reset(0, 0, display.width(), display.height() - 6); + size_t b = stacked.addChart(bar); - size_t c = - stacked.addChart(new Chart(display, 0, 0, display.width(), 0)); + size_t c = stacked.addChart(waterChart); stacked.setHeight(c, stacked.height - WATERFALL_START); stacked.setHeight(b, stacked.height); @@ -1086,6 +1100,8 @@ void loop(void) rr = LO_RSSI_THRESHOLD; } + waterChart->updatePoint(millis(), freq, rr); + int updated = bar->bar.updatePoint(freq, rr); if (first_run || ANIMATED_RELOAD) @@ -1108,7 +1124,7 @@ void loop(void) { waterfall[display_x] = true; display.setColor(WHITE); - display.setPixel(display_x, w); + // display.setPixel(display_x, w); } } #endif @@ -1150,7 +1166,7 @@ void loop(void) // TODO: make something like scrolling up if possible waterfall[display_x] = false; display.setColor(BLACK); - display.setPixel(display_x, w); + // display.setPixel(display_x, w); display.setColor(WHITE); } #endif @@ -1248,7 +1264,7 @@ void loop(void) if (single_page_scan) { display.setColor(BLACK); - display.drawHorizontalLine(0, w, STEPS); + // display.drawHorizontalLine(0, w, STEPS); display.setColor(WHITE); } #endif diff --git a/test/test_rssi.cpp b/test/test_rssi.cpp index 8a75b60..3d82f29 100644 --- a/test/test_rssi.cpp +++ b/test/test_rssi.cpp @@ -3,10 +3,6 @@ #include "../lib/scan/scan.cpp" #include -void setUp(void) {} - -void tearDown(void) {} - struct TestScan : Scan { TestScan(float *ctx, int sz) : ctx(ctx), sz(sz), idx(0) {} @@ -66,13 +62,3 @@ void test_detect() TEST_ASSERT_EQUAL_INT16(1, r); TEST_ASSERT_EQUAL_INT8_ARRAY(expect2, result, test_sz); } - -int main(int argc, char **argv) -{ - UNITY_BEGIN(); - - RUN_TEST(test_rssi); - RUN_TEST(test_detect); - - UNITY_END(); -} diff --git a/test/test_waterfall.cpp b/test/test_waterfall.cpp new file mode 100644 index 0000000..1875065 --- /dev/null +++ b/test/test_waterfall.cpp @@ -0,0 +1,220 @@ +#define TO_STRING +#include "../lib/models/WaterfallModel.cpp" +#include +#include + +void test_push() +{ + size_t *ms = new size_t[6]{5, 3, 4, 15, 4, 3}; + WaterfallModel m(1, 1, 6, ms); + delete ms; + + char *r = m.toString(); + TEST_ASSERT_EQUAL_STRING("w:1 b:34 " + "[dt:1 t:0 [ c:0 e:0 ]" + "dt:1 t:0 [ c:0 e:0 ]" + "dt:1 t:0 [ c:0 e:0 ]" + "dt:1 t:0 [ c:0 e:0 ]" + "dt:1 t:0 [ c:0 e:0 ]" + "dt:5 t:0 [ c:0 e:0 ]" + "dt:5 t:0 [ c:0 e:0 ]" + "dt:5 t:0 [ c:0 e:0 ]" + "dt:15 t:0 [ c:0 e:0 ]" + "dt:15 t:0 [ c:0 e:0 ]" + "dt:15 t:0 [ c:0 e:0 ]" + "dt:15 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:60 t:0 [ c:0 e:0 ]" + "dt:900 t:0 [ c:0 e:0 ]" + "dt:900 t:0 [ c:0 e:0 ]" + "dt:900 t:0 [ c:0 e:0 ]" + "dt:900 t:0 [ c:0 e:0 ]" + "dt:3600 t:0 [ c:0 e:0 ]" + "dt:3600 t:0 [ c:0 e:0 ]" + "dt:3600 t:0 [ c:0 e:0 ] ]", + r); + delete r; + + m.reset(0, 1); + uint64_t i = 0; + for (; i < 10; i++) + m.updateModel(i, 0, 1); + + r = m.toString(); + TEST_ASSERT_EQUAL_STRING("w:1 b:34 " + "[dt:1 t:9 [ c:1 e:1 ]" + "dt:1 t:8 [ c:1 e:1 ]" + "dt:1 t:7 [ c:1 e:1 ]" + "dt:1 t:6 [ c:1 e:1 ]" + "dt:1 t:5 [ c:1 e:1 ]" + "dt:5 t:5 [ c:5 e:5 ]" + "dt:5 t:5 [ c:0 e:0 ]" + "dt:5 t:5 [ c:0 e:0 ]" + "dt:15 t:15 [ c:0 e:0 ]" + "dt:15 t:15 [ c:0 e:0 ]" + "dt:15 t:15 [ c:0 e:0 ]" + "dt:15 t:15 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:900 t:900 [ c:0 e:0 ]" + "dt:900 t:900 [ c:0 e:0 ]" + "dt:900 t:900 [ c:0 e:0 ]" + "dt:900 t:900 [ c:0 e:0 ]" + "dt:3600 t:3600 [ c:0 e:0 ]" + "dt:3600 t:3600 [ c:0 e:0 ]" + "dt:3600 t:3600 [ c:0 e:0 ] ]", + r); + delete r; + + for (; i < 100; i += 10) + m.updateModel(i, 0, 1); + + r = m.toString(); + TEST_ASSERT_EQUAL_STRING("w:1 b:34 " + "[dt:1 t:90 [ c:1 e:1 ]" + "dt:1 t:89 [ c:0 e:0 ]" + "dt:1 t:88 [ c:0 e:0 ]" + "dt:1 t:87 [ c:0 e:0 ]" + "dt:1 t:86 [ c:0 e:0 ]" + "dt:5 t:85 [ c:0 e:0 ]" + "dt:5 t:80 [ c:1 e:1 ]" + "dt:5 t:75 [ c:0 e:0 ]" + "dt:15 t:75 [ c:1 e:1 ]" + "dt:15 t:60 [ c:2 e:2 ]" + "dt:15 t:45 [ c:1 e:1 ]" + "dt:15 t:30 [ c:2 e:2 ]" + "dt:60 t:60 [ c:11 e:11 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:60 t:60 [ c:0 e:0 ]" + "dt:900 t:900 [ c:0 e:0 ]" + "dt:900 t:900 [ c:0 e:0 ]" + "dt:900 t:900 [ c:0 e:0 ]" + "dt:900 t:900 [ c:0 e:0 ]" + "dt:3600 t:3600 [ c:0 e:0 ]" + "dt:3600 t:3600 [ c:0 e:0 ]" + "dt:3600 t:3600 [ c:0 e:0 ] ]", + r); + delete r; + + for (; i < 10000; i++) + m.updateModel(i, 0, 1); + + r = m.toString(); + TEST_ASSERT_EQUAL_STRING("w:1 b:34 " + "[dt:1 t:9999 [ c:1 e:1 ]" + "dt:1 t:9998 [ c:1 e:1 ]" + "dt:1 t:9997 [ c:1 e:1 ]" + "dt:1 t:9996 [ c:1 e:1 ]" + "dt:1 t:9995 [ c:1 e:1 ]" + "dt:5 t:9995 [ c:4 e:4 ]" + "dt:5 t:9990 [ c:5 e:5 ]" + "dt:5 t:9985 [ c:5 e:5 ]" + "dt:15 t:9990 [ c:5 e:5 ]" + "dt:15 t:9975 [ c:15 e:15 ]" + "dt:15 t:9960 [ c:15 e:15 ]" + "dt:15 t:9945 [ c:15 e:15 ]" + "dt:60 t:9960 [ c:30 e:30 ]" + "dt:60 t:9900 [ c:60 e:60 ]" + "dt:60 t:9840 [ c:60 e:60 ]" + "dt:60 t:9780 [ c:60 e:60 ]" + "dt:60 t:9720 [ c:60 e:60 ]" + "dt:60 t:9660 [ c:60 e:60 ]" + "dt:60 t:9600 [ c:60 e:60 ]" + "dt:60 t:9540 [ c:60 e:60 ]" + "dt:60 t:9480 [ c:60 e:60 ]" + "dt:60 t:9420 [ c:60 e:60 ]" + "dt:60 t:9360 [ c:60 e:60 ]" + "dt:60 t:9300 [ c:60 e:60 ]" + "dt:60 t:9240 [ c:60 e:60 ]" + "dt:60 t:9180 [ c:60 e:60 ]" + "dt:60 t:9120 [ c:60 e:60 ]" + "dt:900 t:9900 [ c:60 e:60 ]" + "dt:900 t:9000 [ c:900 e:900 ]" + "dt:900 t:8100 [ c:900 e:900 ]" + "dt:900 t:7200 [ c:900 e:900 ]" + "dt:3600 t:7200 [ c:2700 e:2700 ]" + "dt:3600 t:3600 [ c:3520 e:3520 ]" + "dt:3600 t:3600 [ c:0 e:0 ] ]", + r); + delete r; + + for (; i < 5000; i++) + m.updateModel(i, 0, 1); + + r = m.toString(); + TEST_ASSERT_EQUAL_STRING("w:1 b:34 " + "[dt:1 t:9999 [ c:1 e:1 ]" + "dt:1 t:9998 [ c:1 e:1 ]" + "dt:1 t:9997 [ c:1 e:1 ]" + "dt:1 t:9996 [ c:1 e:1 ]" + "dt:1 t:9995 [ c:1 e:1 ]" + "dt:5 t:9995 [ c:4 e:4 ]" + "dt:5 t:9990 [ c:5 e:5 ]" + "dt:5 t:9985 [ c:5 e:5 ]" + "dt:15 t:9990 [ c:5 e:5 ]" + "dt:15 t:9975 [ c:15 e:15 ]" + "dt:15 t:9960 [ c:15 e:15 ]" + "dt:15 t:9945 [ c:15 e:15 ]" + "dt:60 t:9960 [ c:30 e:30 ]" + "dt:60 t:9900 [ c:60 e:60 ]" + "dt:60 t:9840 [ c:60 e:60 ]" + "dt:60 t:9780 [ c:60 e:60 ]" + "dt:60 t:9720 [ c:60 e:60 ]" + "dt:60 t:9660 [ c:60 e:60 ]" + "dt:60 t:9600 [ c:60 e:60 ]" + "dt:60 t:9540 [ c:60 e:60 ]" + "dt:60 t:9480 [ c:60 e:60 ]" + "dt:60 t:9420 [ c:60 e:60 ]" + "dt:60 t:9360 [ c:60 e:60 ]" + "dt:60 t:9300 [ c:60 e:60 ]" + "dt:60 t:9240 [ c:60 e:60 ]" + "dt:60 t:9180 [ c:60 e:60 ]" + "dt:60 t:9120 [ c:60 e:60 ]" + "dt:900 t:9900 [ c:60 e:60 ]" + "dt:900 t:9000 [ c:900 e:900 ]" + "dt:900 t:8100 [ c:900 e:900 ]" + "dt:900 t:7200 [ c:900 e:900 ]" + "dt:3600 t:7200 [ c:2700 e:2700 ]" + "dt:3600 t:3600 [ c:3520 e:3520 ]" + "dt:3600 t:3600 [ c:0 e:0 ] ]", + r); + delete r; +} diff --git a/test/tests.cpp b/test/tests.cpp new file mode 100644 index 0000000..46fa133 --- /dev/null +++ b/test/tests.cpp @@ -0,0 +1,21 @@ +#include + +void test_rssi(); +void test_detect(); +void test_push(); + +void setUp(void) {} + +void tearDown(void) {} + +int main(int argc, char **argv) +{ + UNITY_BEGIN(); + + RUN_TEST(test_rssi); + RUN_TEST(test_detect); + + RUN_TEST(test_push); + + UNITY_END(); +} From 645e9988c8a8934099968a33dea962c3b00ad5f9 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Tue, 1 Oct 2024 19:32:46 +0100 Subject: [PATCH 12/16] Test stability: uptime clock --- lib/charts/UptimeClock.cpp | 28 ++++++++++++++++++++++++++++ lib/charts/charts.h | 10 ++++++++++ src/main.cpp | 10 ++++++++++ 3 files changed, 48 insertions(+) create mode 100644 lib/charts/UptimeClock.cpp diff --git a/lib/charts/UptimeClock.cpp b/lib/charts/UptimeClock.cpp new file mode 100644 index 0000000..f7e229e --- /dev/null +++ b/lib/charts/UptimeClock.cpp @@ -0,0 +1,28 @@ +#include "charts.h" + +void UptimeClock::draw(uint64_t t) +{ + t1 = t; + draw(); +} + +void UptimeClock::draw() +{ + uint64_t uptime = t1 - t0; + int mils = uptime % 1000; + int seconds = (uptime / 1000) % 60; + int minutes = (uptime / 60000) % 60; + int hours = uptime / 3600000; + String s = String(hours) + (minutes < 10 ? ":0" : ":") + String(minutes) + + (seconds < 10 ? ":0" : ":") + String(seconds) + + (mils < 10 ? ".00" + : mils < 100 ? ".0" + : ".") + + String(mils); + int w = display.getStringWidth(s); + display.setColor(BLACK); + display.fillRect((display.width() - w) / 2, display.height() / 2 - 3, w, 7); + display.setColor(WHITE); + display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH); + display.drawString(display.width() / 2, display.height() / 2, s); +} diff --git a/lib/charts/charts.h b/lib/charts/charts.h index c2c7dfb..53b4f31 100644 --- a/lib/charts/charts.h +++ b/lib/charts/charts.h @@ -153,4 +153,14 @@ struct WaterfallChart : Chart int x2pos(float x); }; +struct UptimeClock : Chart +{ + uint64_t t0; + uint64_t t1; + UptimeClock(OLEDDisplay &d, uint64_t t0) : Chart(d, 0, 0, 0, 0), t0(t0), t1(t0) {}; + + void draw(uint64_t t); + virtual void draw() override; +}; + #endif diff --git a/src/main.cpp b/src/main.cpp index d04b6ec..b26b32d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -371,6 +371,8 @@ DecoratedBarChart *bar; WaterfallChart *waterChart; StackedChart stacked(display, 0, 0, 0, 0); +UptimeClock *uptime; + void init_radio() { // initialize SX1262 FSK modem at the initial frequency @@ -684,6 +686,10 @@ void setup(void) stacked.setHeight(c, stacked.height - WATERFALL_START); stacked.setHeight(b, stacked.height); + +#ifdef UPTIME_CLOCK + uptime = new UptimeClock(display, millis()); +#endif } // Formula to translate 33 bin to approximate RSSI value @@ -1271,6 +1277,10 @@ void loop(void) stacked.draw(); // Render display data here + +#ifdef UPTIME_CLOCK + uptime->draw(millis()); +#endif display.display(); #ifdef OSD_ENABLED // Sometimes OSD prints entire screen with the digits. From 0c760610dfd3f37633252b683cdf47a5df4e2f41 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Thu, 3 Oct 2024 07:28:51 +0100 Subject: [PATCH 13/16] Status Bar --- include/ui.h | 26 +++-- lib/charts/charts.h | 25 +++-- lib/scan/scan.h | 14 +++ src/main.cpp | 195 +++++++++++++-------------------- src/ui.cpp | 256 +++++++++++++------------------------------- 5 files changed, 198 insertions(+), 318 deletions(-) diff --git a/include/ui.h b/include/ui.h index 2f18a7b..ddf7a73 100644 --- a/include/ui.h +++ b/include/ui.h @@ -8,6 +8,9 @@ #include #endif +#include +#include + // #include // (optional) major and minor tick-marks at x MHz @@ -31,14 +34,21 @@ #define SCREEN_HEIGHT 64 // ???? not used -// publish functions -#ifdef Vision_Master_E290 -extern void UI_Init(DEPG0290BxS800FxX_BW *); -#else -extern void UI_Init(SSD1306Wire *); -#endif -extern void UI_displayDecorate(int, int, bool); -extern void UI_setLedFlag(bool); +extern void UI_Init(Display_t *); extern void UI_clearPlotter(void); extern void UI_clearTopStatus(void); extern void UI_drawCursor(int16_t); + +struct StatusBar : Chart +{ + Scan &r; + bool ui_initialized; + uint16_t scan_progress_count; + + StatusBar(Display_t &d, uint16_t x, uint16_t y, uint16_t w, Scan &r) + : Chart(d, x, y, w, LABEL_HEIGHT), r(r), ui_initialized(false), + scan_progress_count(0) {}; + + virtual void clearStatus(); + virtual void draw() override; +}; diff --git a/lib/charts/charts.h b/lib/charts/charts.h index 53b4f31..d6d11b4 100644 --- a/lib/charts/charts.h +++ b/lib/charts/charts.h @@ -1,7 +1,14 @@ #ifndef CHARTS_H #define CHARTS_H +#ifdef Vision_Master_E290 +#include "HT_DEPG0290BxS800FxX_BW.h" +typedef DEPG0290BxS800FxX_BW Display_t; +#else #include +typedef OLEDDisplay Display_t; +#endif + #include #include #include @@ -10,9 +17,9 @@ struct Chart { uint16_t pos_x, pos_y; uint16_t width, height; - OLEDDisplay &display; + Display_t &display; - Chart(OLEDDisplay &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h) + Chart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h) : display(d), pos_x(x), pos_y(y), width(w), height(h) {}; /* @@ -37,7 +44,7 @@ struct Chart */ struct ProgressChart : Chart { - ProgressChart(OLEDDisplay &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h) + ProgressChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h) : Chart(d, x, y, w, h) {}; /* @@ -60,7 +67,7 @@ struct BarChart : ProgressChart bool *changed; bool redraw_all; - BarChart(OLEDDisplay &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h, float min_x, + BarChart(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) : ProgressChart(d, x, y, w, h), min_x(min_x), max_x(max_x), min_y(min_y), max_y(max_y), level_y(level_y), redraw_all(true) @@ -81,7 +88,7 @@ struct BarChart : ProgressChart int y2pos(float y); }; -#define LABEL_HEIGHT 6 +#define LABEL_HEIGHT 7 #define X_AXIS_WEIGHT 1 #define MAJOR_TICK_LENGTH 2 #define MAJOR_TICKS 10 @@ -92,7 +99,7 @@ struct DecoratedBarChart : Chart { BarChart bar; - DecoratedBarChart(OLEDDisplay &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h, + 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, @@ -107,7 +114,7 @@ struct StackedChart : Chart Chart **charts; size_t charts_sz; - StackedChart(OLEDDisplay &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h) + StackedChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h) : Chart(d, x, y, w, h), charts(NULL), charts_sz(0) {}; /* @@ -138,7 +145,7 @@ struct WaterfallChart : Chart WaterfallModel *model; - WaterfallChart(OLEDDisplay &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h, + WaterfallChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h, float min_x, float max_x, float level_y, float threshold, WaterfallModel *m) : Chart(d, x, y, w, h), model(m), min_x(min_x), max_x(max_x), level_y(level_y), @@ -157,7 +164,7 @@ struct UptimeClock : Chart { uint64_t t0; uint64_t t1; - UptimeClock(OLEDDisplay &d, uint64_t t0) : Chart(d, 0, 0, 0, 0), t0(t0), t1(t0) {}; + UptimeClock(Display_t &d, uint64_t t0) : Chart(d, 0, 0, 0, 0), t0(t0), t1(t0) {}; void draw(uint64_t t); virtual void draw() override; diff --git a/lib/scan/scan.h b/lib/scan/scan.h index f5af2c6..b471e7e 100644 --- a/lib/scan/scan.h +++ b/lib/scan/scan.h @@ -33,6 +33,20 @@ constexpr float LO_RSSI_THRESHOLD = HI_RSSI_THRESHOLD - 66; struct Scan { + uint64_t epoch; + float current_frequency; + uint64_t fr_begin; + uint64_t fr_end; + uint64_t drone_detection_level; + bool sound_on; + bool led_flag; + uint64_t detection_count; + + 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) {}; + virtual float getRSSI() = 0; // rssiMethod gets the data similar to the scan method, diff --git a/src/main.cpp b/src/main.cpp index b26b32d..1a6590a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -172,13 +172,11 @@ constexpr int WINDOW_SIZE = 15; #define SINGLE_STEP (float)(RANGE / (STEPS * SCAN_RBW_FACTOR)) uint64_t range = (int)(FREQ_END - FREQ_BEGIN); -uint64_t fr_begin = FREQ_BEGIN; -uint64_t fr_end = FREQ_BEGIN; uint64_t iterations = RANGE / RANGE_PER_PAGE; // uint64_t range_frequency = FREQ_END - FREQ_BEGIN; -uint64_t median_frequency = FREQ_BEGIN + FREQ_END - FREQ_BEGIN / 2; +uint64_t median_frequency = (FREQ_BEGIN + FREQ_END) / 2; // #define DISABLE_PLOT_CHART false // unused @@ -190,8 +188,7 @@ bool filtered_result[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE]; int max_bins_array_value[MAX_POWER_LEVELS]; int max_step_range = 32; -// Waterfall array -bool waterfall[STEPS], detected_y[STEPS]; // 20 - ??? steps of the waterfall +bool detected_y[STEPS]; // 20 - ??? steps // global variable @@ -203,9 +200,7 @@ uint64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL; uint64_t show_db_after = 80; uint64_t drone_detected_frequency_start = 0; uint64_t drone_detected_frequency_end = 0; -uint64_t detection_count = 0; bool single_page_scan = false; -bool SOUND_ON = false; // #define PRINT_DEBUG #define PRINT_PROFILE_TIME @@ -225,7 +220,6 @@ uint64_t x, y, range_item, w = WATERFALL_START, i = 0; int osd_x = 1, osd_y = 2, col = 0, max_bin = 32; uint64_t ranges_count = 0; -float freq = 0; int rssi = 0; int state = 0; @@ -238,7 +232,6 @@ constexpr int samples = SAMPLES_RSSI; uint8_t result_index = 0; uint8_t button_pressed_counter = 0; -uint64_t loop_cnt = 0; #ifndef LILYGO // #define JOYSTICK_ENABLED @@ -366,6 +359,27 @@ void osdProcess() } #endif +struct RadioScan : Scan +{ + float getRSSI() override; +}; + +float RadioScan::getRSSI() +{ +#ifdef USING_SX1280PA + // radio.startReceive(); + // get instantaneous RSSI value + // When PR will be merged we can use radi.getRSSI(false); + uint8_t data[3] = {0, 0, 0}; // RssiInst, Status, RFU + radio.mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_RSSI_INST, data, 3); + return ((float)data[0] / (-2.0)); +#else + return radio.getRSSI(false); +#endif +} + +RadioScan r; + #define WATERFALL_SENSITIVITY 0.05 DecoratedBarChart *bar; WaterfallChart *waterChart; @@ -532,7 +546,6 @@ void setup(void) #endif float vbat; float resolution; - loop_cnt = 0; bt_start = millis(); wf_start = millis(); @@ -551,7 +564,7 @@ void setup(void) delay(10); if (button.pressed()) { - SOUND_ON = !SOUND_ON; + r.sound_on = !r.sound_on; tone(BUZZER_PIN, 205, 100); delay(50); tone(BUZZER_PIN, 205, 100); @@ -662,12 +675,17 @@ void setup(void) xTaskCreate(logToSerialTask, "LOG_DATA_JSON", 2048, NULL, 1, NULL); #endif + stacked.reset(0, 0, display.width(), display.height()); + bar = new DecoratedBarChart(display, 0, 0, display.width(), 0, FREQ_BEGIN, FREQ_END, LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, -(float)show_db_after); - stacked.reset(0, 0, display.width(), display.height() - 6); + size_t b = stacked.addChart(bar); + Chart *statusBar = new StatusBar(display, 0, 0, display.width(), r); + +#if (WATERFALL_ENABLED == true) size_t *multiples = new size_t[6]{5, 3, 4, 15, 4, 3}; WaterfallModel *model = new WaterfallModel((size_t)display.width(), 1000, 6, multiples); @@ -679,12 +697,11 @@ void setup(void) new WaterfallChart(display, 0, WATERFALL_START, display.width(), 0, FREQ_BEGIN, FREQ_END, -(float)show_db_after, WATERFALL_SENSITIVITY, model); - stacked.reset(0, 0, display.width(), display.height() - 6); - - size_t b = stacked.addChart(bar); size_t c = stacked.addChart(waterChart); + stacked.setHeight(c, stacked.height - WATERFALL_START - statusBar->height); +#endif - stacked.setHeight(c, stacked.height - WATERFALL_START); + size_t d = stacked.addChart(statusBar); stacked.setHeight(b, stacked.height); #ifdef UPTIME_CLOCK @@ -788,12 +805,12 @@ void drone_sound_alarm(int drone_detection_level, int detection_count, tone_freq_db = 285 - tone_freq_db; } - if (detection_count == 1 && SOUND_ON) + if (r.detection_count == 1 && r.sound_on) { tone(BUZZER_PIN, tone_freq_db, 10); // same action ??? but first time } - if (detection_count % 5 == 0 && SOUND_ON) + if (r.detection_count % 5 == 0 && r.sound_on) { tone(BUZZER_PIN, tone_freq_db, 10); // same action ??? but every 5th time @@ -801,7 +818,7 @@ void drone_sound_alarm(int drone_detection_level, int detection_count, } else { - if (detection_count % 20 == 0 && SOUND_ON) + if (r.detection_count % 20 == 0 && r.sound_on) { tone(BUZZER_PIN, 205, 10); // same action ??? but every 20th detection @@ -815,7 +832,7 @@ void joystickMoveCursor(int joy_x_pressed) if (joy_x_pressed > 0) { cursor_x_position--; - display.drawString(cursor_x_position, 0, String((int)freq)); + display.drawString(cursor_x_position, 0, String((int)r.current_frequency)); display.drawLine(cursor_x_position, 1, cursor_x_position, 10); display.display(); delay(10); @@ -823,7 +840,7 @@ void joystickMoveCursor(int joy_x_pressed) else if (joy_x_pressed < 0) { cursor_x_position++; - display.drawString(cursor_x_position, 0, String((int)freq)); + display.drawString(cursor_x_position, 0, String((int)r.current_frequency)); display.drawLine(cursor_x_position, 1, cursor_x_position, 10); display.display(); delay(10); @@ -831,7 +848,7 @@ void joystickMoveCursor(int joy_x_pressed) if (cursor_x_position > DISPLAY_WIDTH || cursor_x_position < 0) { cursor_x_position = 0; - display.drawString(cursor_x_position, 0, String((int)freq)); + display.drawString(cursor_x_position, 0, String((int)r.current_frequency)); display.drawLine(cursor_x_position, 1, cursor_x_position, 10); display.display(); delay(10); @@ -869,45 +886,23 @@ void check_ranges() } } -struct RadioScan : Scan -{ - float getRSSI() override; -}; - -float RadioScan::getRSSI() -{ -#ifdef USING_SX1280PA - // radio.startReceive(); - // get instantaneous RSSI value - // When PR will be merged we can use radi.getRSSI(false); - uint8_t data[3] = {0, 0, 0}; // RssiInst, Status, RFU - radio.mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_RSSI_INST, data, 3); - return ((float)data[0] / (-2.0)); -#else - return radio.getRSSI(false); -#endif -} - // MAX Frequency RSSI BIN value of the samples int max_rssi_x = 999; -RadioScan r; - void loop(void) { - UI_displayDecorate(0, 0, false); // some default values + r.led_flag = false; - detection_count = 0; + r.detection_count = 0; drone_detected_frequency_start = 0; ranges_count = 0; // reset scan time #ifdef PRINT_PROFILE_TIME scan_time = 0; - // general purpose loop counter - loop_cnt++; loop_start = millis(); #endif + r.epoch++; if (!ANIMATED_RELOAD || !single_page_scan) { @@ -923,8 +918,8 @@ void loop(void) RANGE_PER_PAGE = range; } - fr_begin = FREQ_BEGIN; - fr_end = fr_begin; + r.fr_begin = FREQ_BEGIN; + r.fr_end = r.fr_begin; // 50 is a single-screen range // TODO: Make 50 a variable with the option to show the full range @@ -946,14 +941,14 @@ void loop(void) range = RANGE_PER_PAGE; if (ranges_count == 0) { - fr_begin = (range_item == 0) ? fr_begin : fr_begin += range; - fr_end = fr_begin + RANGE_PER_PAGE; + r.fr_begin = (range_item == 0) ? r.fr_begin : r.fr_begin + range; + r.fr_end = r.fr_begin + RANGE_PER_PAGE; } else { - fr_begin = SCAN_RANGES[range_item] / 1000; - fr_end = SCAN_RANGES[range_item] % 1000; - range = fr_end - fr_begin; + r.fr_begin = SCAN_RANGES[range_item] / 1000; + r.fr_end = SCAN_RANGES[range_item] % 1000; + range = r.fr_end - r.fr_begin; } #ifdef DISABLED_CODE @@ -964,11 +959,6 @@ void loop(void) } #endif - if (single_page_scan == false) - { - UI_displayDecorate(fr_begin, fr_end, true); - } - drone_detected_frequency_start = 0; display.setTextAlignment(TEXT_ALIGN_RIGHT); @@ -999,26 +989,29 @@ void loop(void) // Because of the SCAN_RBW_FACTOR x is not a display coordinate anymore // x > STEPS on SCAN_RBW_FACTOR int display_x = x / SCAN_RBW_FACTOR; - waterfall[display_x] = false; float step = (range * ((float)x / (STEPS * SCAN_RBW_FACTOR))); - freq = fr_begin + step; - LOG("setFrequency:%f\n", freq); + r.current_frequency = r.fr_begin + step; + LOG("setFrequency:%f\n", r.current_frequency); #ifdef USING_SX1280PA - state = radio.setFrequency(freq); // 1280 doesn't have calibration + state = + radio.setFrequency(r.current_frequency); // 1280 doesn't have calibration radio.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF); #elif USING_SX1276 state = radio.setFrequency(freq); #else - state = radio.setFrequency(freq, false); // false = no calibration need here + state = radio.setFrequency(r.current_frequency, + false); // false = no calibration need here #endif int radio_error_count = 0; if (state != RADIOLIB_ERR_NONE) { - display.drawString( - 0, 64 - 10, "E(" + String(state) + "):setFrequency:" + String(freq)); - Serial.println("E(" + String(state) + "):setFrequency:" + String(freq)); + display.drawString(0, 64 - 10, + "E(" + String(state) + + "):setFrequency:" + String(r.current_frequency)); + Serial.println("E(" + String(state) + + "):setFrequency:" + String(r.current_frequency)); display.display(); delay(2); radio_error_count++; @@ -1026,7 +1019,7 @@ void loop(void) continue; } - LOG("Step:%d Freq: %f\n", x, freq); + LOG("Step:%d Freq: %f\n", x, r.current_frequency); // SpectralScan Method #ifdef METHOD_SPECTRAL { @@ -1106,9 +1099,13 @@ void loop(void) rr = LO_RSSI_THRESHOLD; } - waterChart->updatePoint(millis(), freq, rr); + r.drone_detection_level = drone_detection_level; - int updated = bar->bar.updatePoint(freq, rr); +#if (WATERFALL_ENABLED == true) + waterChart->updatePoint(millis(), r.current_frequency, rr); +#endif + + int updated = bar->bar.updatePoint(r.current_frequency, rr); if (first_run || ANIMATED_RELOAD) { @@ -1121,30 +1118,18 @@ void loop(void) if (detected_y[display_x] == false) // detection threshold match { // Set LED to ON (filtered in UI component) - UI_setLedFlag(true); -#if (WATERFALL_ENABLED == true) - if (single_page_scan) - { - // Drone detection true for waterfall - if (!waterfall[display_x]) - { - waterfall[display_x] = true; - display.setColor(WHITE); - // display.setPixel(display_x, w); - } - } -#endif + r.led_flag = true; if (drone_detected_frequency_start == 0) { // mark freq start - drone_detected_frequency_start = freq; + drone_detected_frequency_start = r.current_frequency; } // mark freq end ... will shift right to last detected range - drone_detected_frequency_end = freq; - if (SOUND_ON == true) + drone_detected_frequency_end = r.current_frequency; + if (r.sound_on) { - drone_sound_alarm(drone_detection_level, detection_count, + drone_sound_alarm(r.drone_detection_level, r.detection_count, max_rssi_x * 2); } @@ -1165,34 +1150,13 @@ void loop(void) #endif } } -#if (WATERFALL_ENABLED == true) - if ((single_page_scan) && (waterfall[display_x] != true) && new_pixel) - { - // If drone not found set dark pixel on the waterfall - // TODO: make something like scrolling up if possible - waterfall[display_x] = false; - display.setColor(BLACK); - // display.setPixel(display_x, w); - display.setColor(WHITE); - } -#endif } -#ifdef PRINT_DEBUG - for (int y = 0; y < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE; y++) - { - if (filtered_result[y] == 1) - { - LOG("Pixel:%i(%i):%i,", display_x, x, y); - } - } -#endif - #ifdef JOYSTICK_ENABLED // Draw joystick cursor and Frequency RSSI value if (display_x == cursor_x_position) { - display.drawString(display_x - 1, 0, String((int)freq)); + display.drawString(display_x - 1, 0, String((int)r.current_frequency)); display.drawLine(display_x, 1, display_x, 12); // if method scan RSSI we can get exact RSSI value display.drawString(display_x + 17, 0, "-" + String((int)max_rssi_x * 4)); @@ -1205,7 +1169,7 @@ void loop(void) // count detected if (detected) { - detection_count++; + r.detection_count++; } #ifdef PRINT_DEBUG @@ -1216,7 +1180,7 @@ void loop(void) display.display(); } - if (buttonPressHandler(freq) == false) + if (buttonPressHandler(r.current_frequency) == false) break; // wait a little bit before the next scan, @@ -1265,15 +1229,6 @@ void loop(void) { w = WATERFALL_START; } -#if (WATERFALL_ENABLED == true) - // Draw waterfall position cursor - if (single_page_scan) - { - display.setColor(BLACK); - // display.drawHorizontalLine(0, w, STEPS); - display.setColor(WHITE); - } -#endif stacked.draw(); // Render display data here diff --git a/src/ui.cpp b/src/ui.cpp index ea376dd..e488060 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -2,6 +2,8 @@ #include "RadioLib.h" #include "global_config.h" #include "images.h" +#include +#include // ------------------------------------------------- // LOCAL DEFINES @@ -12,24 +14,9 @@ // #define SCALE_TEXT_TOP (HEIGHT + X_AXIS_WEIGHT + MAJOR_TICK_LENGTH) -static unsigned int start_scan_text = (128 / 2) - 3; -// initialized flag -static bool ui_initialized = false; -static bool led_flag = false; -static unsigned short int scan_progress_count = 0; - -#ifdef Vision_Master_E290 -static DEPG0290BxS800FxX_BW *display_instance; -#else -//(0x3c, SDA_OLED, SCL_OLED, DISPLAY_GEOMETRY); -static SSD1306Wire *display_instance; -#endif // temporary dirty import ... to be solved durring upcoming refactoring -extern unsigned int drone_detection_level; extern unsigned int RANGE_PER_PAGE; extern unsigned int median_frequency; -extern unsigned int detection_count; -extern bool SOUND_ON; extern unsigned int drone_detected_frequency_start; extern unsigned int drone_detected_frequency_end; extern unsigned int ranges_count; @@ -40,230 +27,138 @@ extern unsigned int range_item; extern uint64_t loop_time; -#ifndef Vision_Master_E290 -void UI_Init(SSD1306Wire *display_ptr) +void UI_Init(Display_t *display_ptr) { - // init pointer to display instance. - display_instance = display_ptr; // check for null ??? - display_instance->clear(); + display_ptr->clear(); // draw the UCOG welcome logo - display_instance->drawXbm(0, 2, 128, 64, epd_bitmap_ucog); - display_instance->display(); + display_ptr->drawXbm(0, 2, 128, 64, epd_bitmap_ucog); + display_ptr->display(); } -#endif -#ifdef Vision_Master_E290 -void UI_Init(DEPG0290BxS800FxX_BW *display_ptr) -{ - // init pointer to display instance. - display_instance = display_ptr; - // check for null ??? - display_instance->clear(); - // draw the UCOG welcome logo - display_instance->drawXbm(0, 2, 128, 64, epd_bitmap_ucog); - display_instance->display(); -} -#endif - -void UI_setLedFlag(bool new_status) { led_flag = new_status; } - -void clearStatus(void) +void StatusBar::clearStatus(void) { // clear status line - display_instance->setColor(BLACK); - display_instance->fillRect(0, ROW_STATUS_TEXT + 2, 128, 13); - display_instance->setColor(WHITE); + display.setColor(BLACK); + display.fillRect(pos_x, pos_y, width, height); } void UI_clearPlotter(void) { // clear the scan plot rectangle (top part) - display_instance->setColor(BLACK); - display_instance->fillRect(0, 10, STEPS, HEIGHT - 10); - display_instance->setColor(WHITE); + // display_instance->setColor(BLACK); + // display_instance->fillRect(0, 10, STEPS, HEIGHT - 10); + // display_instance->setColor(WHITE); } void UI_clearTopStatus(void) { // clear the scan plot rectangle (top part) - display_instance->setColor(BLACK); - display_instance->fillRect(0, 0, STEPS, 10); - display_instance->setColor(WHITE); -} - -/** - * @brief Draws ticks on the display at regular whole intervals. - * - * @param every The interval between ticks in MHz. - * @param length The length of each tick in pixels. - */ -void drawTicks(float every, int length) -{ - int first_tick; - bool correction; - int pixels_per_step; - int correction_number; - int tick; - int tick_minor; - int median; - - first_tick = 0; - //+ (every - (fr_begin - (int)(fr_begin / every) * every)); - /*if (first_tick < fr_begin) - { - first_tick += every; - }*/ - correction = false; - pixels_per_step = STEPS / (RANGE_PER_PAGE / every); - if (STEPS / RANGE_PER_PAGE != 0) - { - correction = true; - } - correction_number = STEPS - (int)(pixels_per_step * (RANGE_PER_PAGE / every)); - tick = 0; - tick_minor = 0; - median = (RANGE_PER_PAGE / every) / 2; - // TODO: (RANGE_PER_PAGE / every) - // * 2 has twice extra steps we need to figureout correct logic or minor - // ticks is not showing to the end - for (int t = 0; t <= (RANGE_PER_PAGE / every) * 2; t++) - { - // fix if pixels per step is not int and we have shift - if (correction && t % 2 != 0 && correction_number > 1) - { - // pixels_per_step++; - correction_number--; - } - tick += pixels_per_step; - tick_minor = tick / 2; - if (tick <= 128 - 3) - { - display_instance->drawLine(tick, HEIGHT + X_AXIS_WEIGHT, tick, - HEIGHT + X_AXIS_WEIGHT + length); - // Central tick - if (tick > (128 / 2) - 3 && tick < (128 / 2) + 3) - { - display_instance->drawLine(tick + 1, HEIGHT + X_AXIS_WEIGHT, tick + 1, - HEIGHT + X_AXIS_WEIGHT + length); - } - } -#ifdef MINOR_TICKS - // Fix two ticks together - if ((tick_minor + 1 != tick) && (tick_minor - 1 != tick) && - (tick_minor + 2 != tick) && (tick_minor - 2 != tick)) - { - display_instance->drawLine(tick_minor, HEIGHT + X_AXIS_WEIGHT, tick_minor, - HEIGHT + X_AXIS_WEIGHT + MINOR_TICK_LENGTH); - } - // Central tick - if (tick_minor > (128 / 2) - 3 && tick_minor < (128 / 2) + 3) - { - display_instance->drawLine(tick_minor + 1, HEIGHT + X_AXIS_WEIGHT, - tick_minor + 1, - HEIGHT + X_AXIS_WEIGHT + MINOR_TICK_LENGTH); - } -#endif - } + // display_instance->setColor(BLACK); + // display_instance->fillRect(0, 0, STEPS, 10); + // display_instance->setColor(WHITE); } void UI_drawCursor(int16_t possition) { // Draw animated vertical cursor on reload process - display_instance->setColor(BLACK); - display_instance->drawVerticalLine(possition, 0, HEIGHT); - display_instance->drawVerticalLine(possition + 1, 0, HEIGHT); - display_instance->drawVerticalLine(possition + 2, 0, HEIGHT); - display_instance->setColor(WHITE); + // display_instance->setColor(BLACK); + // display_instance->drawVerticalLine(possition, 0, HEIGHT); + // display_instance->drawVerticalLine(possition + 1, 0, HEIGHT); + // display_instance->drawVerticalLine(possition + 2, 0, HEIGHT); + // display_instance->setColor(WHITE); } /** * @brief Decorates the display: everything but the plot itself. */ -void UI_displayDecorate(int begin = 0, int end = 0, bool redraw = false) +void StatusBar::draw() { + uint16_t text_y = pos_y + height - 10; + if (!ui_initialized) { // Drone detection level - display_instance->setTextAlignment(TEXT_ALIGN_RIGHT); - display_instance->drawString(128, 0, String(drone_detection_level)); + display.setTextAlignment(TEXT_ALIGN_RIGHT); + display.drawString(width, 0, String(r.drone_detection_level)); } - if (!ui_initialized || redraw) + if (!ui_initialized) { // Clear something - display_instance->setColor(BLACK); - display_instance->fillRect(0, SCALE_TEXT_TOP + 1, 128, 12); - display_instance->setColor(WHITE); - + /* display_instance->setColor(BLACK); + display_instance->fillRect(0, SCALE_TEXT_TOP + 1, 128, 12); + display_instance->setColor(WHITE); + */ // Drone detection level - display_instance->setTextAlignment(TEXT_ALIGN_RIGHT); - display_instance->drawString(128, 0, String(drone_detection_level)); + display.setTextAlignment(TEXT_ALIGN_RIGHT); + display.drawString(pos_x + width, 0, String(r.drone_detection_level)); // Frequency start - display_instance->setTextAlignment(TEXT_ALIGN_LEFT); - display_instance->drawString(0, ROW_STATUS_TEXT, - (begin == 0) ? String(FREQ_BEGIN) : String(begin)); + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.drawString(pos_x, text_y, + (r.fr_begin == 0) ? String(FREQ_BEGIN) : String(r.fr_begin)); // Frequency detected - display_instance->setTextAlignment(TEXT_ALIGN_CENTER); - display_instance->drawString(128 / 2, ROW_STATUS_TEXT, - (begin == 0) ? String(median_frequency) - : String(begin + ((end - begin) / 2))); + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.drawString(pos_x + width / 2, text_y, + (r.fr_begin == 0) + ? String(median_frequency) + : String(r.fr_begin + ((r.fr_end - r.fr_begin) / 2))); // Frequency end - display_instance->setTextAlignment(TEXT_ALIGN_RIGHT); - display_instance->drawString(128, ROW_STATUS_TEXT, - (end == 0) ? String(FREQ_END) : String(end)); + display.setTextAlignment(TEXT_ALIGN_RIGHT); + display.drawString(pos_x + width, text_y, + (r.fr_end == 0) ? String(FREQ_END) : String(r.fr_end)); } // Status text block - if (led_flag) // 'drone' detected + if (r.led_flag) // 'drone' detected { - display_instance->setTextAlignment(TEXT_ALIGN_CENTER); + display.setTextAlignment(TEXT_ALIGN_CENTER); // clear status line clearStatus(); - display_instance->drawString(start_scan_text + 2, ROW_STATUS_TEXT, - String(drone_detected_frequency_start) + ">RF<" + - String(drone_detected_frequency_end)); + display.setColor(WHITE); + display.drawString(pos_x + width / 2, text_y, + String(drone_detected_frequency_start) + ">RF<" + + String(drone_detected_frequency_end)); } else { // "Scanning" - display_instance->setTextAlignment(TEXT_ALIGN_CENTER); + display.setTextAlignment(TEXT_ALIGN_CENTER); // clear status line clearStatus(); - if (scan_progress_count == 0) + String s = "Scan \\"; + if (scan_progress_count == 1) { - display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan \\"); - } - else if (scan_progress_count == 1) - { - display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan |"); + s = "Scan |"; } else if (scan_progress_count == 2) { - display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan /"); + s = "Scan /"; } else if (scan_progress_count == 3) { - display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan -"); + s = "Scan -"; } scan_progress_count++; if (scan_progress_count >= 4) { scan_progress_count = 0; } + display.setColor(WHITE); + display.drawString(pos_x + width / 2 - 3, text_y, s); } - if (led_flag == true && detection_count >= 5) + if (r.led_flag && r.detection_count >= 5) { digitalWrite(LED, HIGH); - if (SOUND_ON) + if (r.sound_on) { tone(BUZZER_PIN, 104, 100); } digitalWrite(REB_PIN, HIGH); - led_flag = false; + r.led_flag = false; } - else if (!redraw) + else if (!r.led_flag) { digitalWrite(LED, LOW); } @@ -271,29 +166,28 @@ void UI_displayDecorate(int begin = 0, int end = 0, bool redraw = false) if (ranges_count == 0) { #ifdef DEBUG - display_instance->setTextAlignment(TEXT_ALIGN_LEFT); - display_instance->drawString(0, ROW_STATUS_TEXT, String(loop_time)); + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.drawString(pos_x, text_y, String(loop_time)); #else - display_instance->setTextAlignment(TEXT_ALIGN_LEFT); - display_instance->drawString(0, ROW_STATUS_TEXT, String(FREQ_BEGIN)); + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.drawString(pos_x, text_y, String(FREQ_BEGIN)); #endif - display_instance->setTextAlignment(TEXT_ALIGN_RIGHT); - display_instance->drawString(128, ROW_STATUS_TEXT, String(FREQ_END)); + display.setTextAlignment(TEXT_ALIGN_RIGHT); + display.drawString(pos_x + width, text_y, String(FREQ_END)); } else if (ranges_count > 0) { - display_instance->setTextAlignment(TEXT_ALIGN_LEFT); - display_instance->drawString(0, ROW_STATUS_TEXT, - String(SCAN_RANGES[range_item] / 1000) + "-" + - String(SCAN_RANGES[range_item] % 1000)); + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.drawString(pos_x, text_y, + String(SCAN_RANGES[range_item] / 1000) + "-" + + String(SCAN_RANGES[range_item] % 1000)); if (range_item + 1 < iterations) { - display_instance->setTextAlignment(TEXT_ALIGN_RIGHT); - display_instance->drawString(128, ROW_STATUS_TEXT, - String(SCAN_RANGES[range_item + 1] / 1000) + - "-" + - String(SCAN_RANGES[range_item + 1] % 1000)); + display.setTextAlignment(TEXT_ALIGN_RIGHT); + display.drawString(pos_x + width, text_y, + String(SCAN_RANGES[range_item + 1] / 1000) + "-" + + String(SCAN_RANGES[range_item + 1] % 1000)); } } ui_initialized = true; From bdd00039b26b3e9cd9c1fbfd0369a0496bbff4d6 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sat, 5 Oct 2024 16:35:01 +0100 Subject: [PATCH 14/16] Events for modular UI and other listeners --- lib/charts/BarChart.cpp | 14 ++++++++ lib/charts/StackedChart.cpp | 13 +++++++- lib/charts/WaterfallChart.cpp | 10 ++++++ lib/charts/charts.h | 11 +++++-- lib/events/events.h | 45 ++++++++++++++++++++++++++ lib/scan/scan.cpp | 45 ++++++++++++++++++++++++-- lib/scan/scan.h | 19 ++++++++--- src/main.cpp | 61 ++++++++++++++--------------------- test/test_rssi.cpp | 7 ++-- 9 files changed, 175 insertions(+), 50 deletions(-) create mode 100644 lib/events/events.h diff --git a/lib/charts/BarChart.cpp b/lib/charts/BarChart.cpp index 36c17d7..f92c63a 100644 --- a/lib/charts/BarChart.cpp +++ b/lib/charts/BarChart.cpp @@ -102,6 +102,20 @@ int BarChart::y2pos(float y) return height - height * (y - min_y) / (max_y - min_y); } +void BarChart::onEvent(Event &e) +{ + if (e.type != DETECTED) + { + return; + } + + int u = updatePoint(e.emitter.current_frequency, e.detected.rssi); + if (e.emitter.animated) + { + drawOne(u); + } +} + void DecoratedBarChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { Chart::reset(x, y, w, h); diff --git a/lib/charts/StackedChart.cpp b/lib/charts/StackedChart.cpp index 1c879dc..2c6ca73 100644 --- a/lib/charts/StackedChart.cpp +++ b/lib/charts/StackedChart.cpp @@ -12,7 +12,8 @@ size_t StackedChart::addChart(Chart *c) cc[charts_sz] = c; free(charts); - c->reset(pos_x + c->pos_x, pos_y + c->pos_y, trim_w(c->pos_x, c->width, width), c->height); + c->reset(pos_x + c->pos_x, pos_y + c->pos_y, trim_w(c->pos_x, c->width, width), + c->height); charts = cc; return charts_sz++; } @@ -82,3 +83,13 @@ void StackedChart::draw() for (int i = 0; i < charts_sz; i++) charts[i]->draw(); } + +void StackedChart::onEvent(Event &e) +{ + if (e.type != SCAN_TASK_COMPLETE) + { + return; + } + + draw(); +} diff --git a/lib/charts/WaterfallChart.cpp b/lib/charts/WaterfallChart.cpp index af83090..9828794 100644 --- a/lib/charts/WaterfallChart.cpp +++ b/lib/charts/WaterfallChart.cpp @@ -55,3 +55,13 @@ int WaterfallChart::x2pos(float x) return width * (x - min_x) / (max_x - min_x); } + +void WaterfallChart::onEvent(Event &e) +{ + if (e.type != DETECTED) + { + return; + } + + updatePoint(e.time_ms, e.detected.freq, e.detected.rssi); +} diff --git a/lib/charts/charts.h b/lib/charts/charts.h index d6d11b4..d159cc0 100644 --- a/lib/charts/charts.h +++ b/lib/charts/charts.h @@ -10,6 +10,7 @@ typedef OLEDDisplay Display_t; #endif #include +#include #include #include @@ -58,7 +59,7 @@ struct ProgressChart : Chart virtual void drawOne(int x) = 0; }; -struct BarChart : ProgressChart +struct BarChart : ProgressChart, Listener { float min_x, max_x, min_y, max_y; float level_y; @@ -83,6 +84,7 @@ struct BarChart : ProgressChart int updatePoint(float x, float y) override; void drawOne(int x) override; void draw() override; + void onEvent(Event &) override; int x2pos(float x); int y2pos(float y); @@ -109,7 +111,7 @@ struct DecoratedBarChart : Chart void draw() override; }; -struct StackedChart : Chart +struct StackedChart : Chart, Listener { Chart **charts; size_t charts_sz; @@ -134,9 +136,11 @@ struct StackedChart : Chart void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override; void draw() override; + + void onEvent(Event &e) override; }; -struct WaterfallChart : Chart +struct WaterfallChart : Chart, Listener { float min_x, max_x; float level_y, threshold; @@ -156,6 +160,7 @@ struct WaterfallChart : Chart void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override; void draw() override; + void onEvent(Event &e) override; int x2pos(float x); }; diff --git a/lib/events/events.h b/lib/events/events.h new file mode 100644 index 0000000..539277c --- /dev/null +++ b/lib/events/events.h @@ -0,0 +1,45 @@ +#ifndef LORASA_EVENTS_H +#define LORASA_EVENTS_H + +struct Event; +enum EventType +{ + DETECTED = 0, + SCAN_TASK_COMPLETE, + _MAX_EVENT_TYPE // unused as event type +}; +struct Listener; + +#include +#include + +struct Event +{ + EventType type; + uint64_t epoch; + uint64_t time_ms; + + Scan &emitter; + + union + { + struct + { + float rssi; + float freq; + bool trigger; + bool detected; + size_t detected_at; + } detected; + }; + + Event(Scan &emitter, EventType type, uint64_t time_ms) + : emitter(emitter), type(type), epoch(emitter.epoch), time_ms(time_ms) {}; +}; + +struct Listener +{ + virtual void onEvent(Event &event) = 0; +}; + +#endif diff --git a/lib/scan/scan.cpp b/lib/scan/scan.cpp index 5f584b4..028cf42 100644 --- a/lib/scan/scan.cpp +++ b/lib/scan/scan.cpp @@ -66,8 +66,8 @@ uint16_t Scan::rssiMethod(size_t samples, uint16_t *result, size_t res_size) return max_signal; } -size_t Scan::detect(uint16_t *result, bool *filtered_result, size_t result_size, - int samples) +Event Scan::detect(uint16_t *result, bool *filtered_result, size_t result_size, + int samples) { size_t max_rssi_x = result_size; @@ -122,7 +122,46 @@ size_t Scan::detect(uint16_t *result, bool *filtered_result, size_t result_size, } } - return max_rssi_x; + Event event(*this, EventType::DETECTED, 0); + event.epoch = epoch; + event.detected.detected = max_rssi_x < result_size; + event.detected.freq = current_frequency; + event.detected.rssi = + event.detected.detected ? -(float)result[max_rssi_x] : LO_RSSI_THRESHOLD; + event.detected.detected_at = max_rssi_x; + event.detected.trigger = + event.detected.detected && event.detected.rssi >= trigger_level; + detection_count++; + + return event; +} + +size_t Scan::addEventListener(EventType t, Listener &l) +{ + size_t c = listener_count[(size_t)t]; + Listener **new_list = new Listener *[c + 1]; + new_list[c] = &l; + listener_count[(size_t)t] = c + 1; + + if (c > 0) + { + Listener **old_list = eventListeners[(size_t)t]; + memcpy(new_list, old_list, c * sizeof(Listener *)); + delete[] old_list; + } + + eventListeners[(size_t)t] = new_list; + return c; +} + +void Scan::fireEvent(Event &event) +{ + Listener **list = eventListeners[(size_t)event.type]; + size_t c = listener_count[(size_t)event.type]; + for (int i = 0; i < c; i++) + { + list[i]->onEvent(event); + } } #endif diff --git a/lib/scan/scan.h b/lib/scan/scan.h index b471e7e..68b8cb5 100644 --- a/lib/scan/scan.h +++ b/lib/scan/scan.h @@ -1,4 +1,5 @@ #include +#include #include #ifndef LORASA_CORE_H @@ -41,11 +42,18 @@ struct Scan bool sound_on; bool led_flag; uint64_t detection_count; + bool animated; + float trigger_level; + + Listener **eventListeners[(size_t)EventType::_MAX_EVENT_TYPE]; + size_t listener_count[(size_t)EventType::_MAX_EVENT_TYPE]; 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) {}; + drone_detection_level(0), sound_on(false), led_flag(false), detection_count(0), + animated(false), trigger_level(0), listener_count{ + 0, + } {}; virtual float getRSSI() = 0; @@ -57,8 +65,11 @@ struct Scan // those values that represent a detection event. // It returns index that represents strongest signal at which a detection event // occurred. - static size_t detect(uint16_t *result, bool *filtered_result, size_t result_size, - int samples); + Event detect(uint16_t *result, bool *filtered_result, size_t result_size, + int samples); + + size_t addEventListener(EventType t, Listener &l); + void fireEvent(Event &e); }; // Remove reading without neighbors diff --git a/src/main.cpp b/src/main.cpp index 1a6590a..c6e6514 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,6 +42,7 @@ #define RADIOLIB_GODMODE (1) #include +#include #include #ifndef LILYGO @@ -197,7 +198,7 @@ bool first_run, new_pixel, detected_x = false; // drone detection flag bool detected = false; uint64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL; -uint64_t show_db_after = 80; +#define TRIGGER_LEVEL -80.0 uint64_t drone_detected_frequency_start = 0; uint64_t drone_detected_frequency_end = 0; bool single_page_scan = false; @@ -675,12 +676,14 @@ void setup(void) xTaskCreate(logToSerialTask, "LOG_DATA_JSON", 2048, NULL, 1, NULL); #endif + r.trigger_level = TRIGGER_LEVEL; stacked.reset(0, 0, display.width(), display.height()); + r.addEventListener(SCAN_TASK_COMPLETE, stacked); bar = new DecoratedBarChart(display, 0, 0, display.width(), 0, FREQ_BEGIN, FREQ_END, - LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, - -(float)show_db_after); + LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, r.trigger_level); + r.addEventListener(DETECTED, bar->bar); size_t b = stacked.addChart(bar); Chart *statusBar = new StatusBar(display, 0, 0, display.width(), r); @@ -695,10 +698,12 @@ void setup(void) waterChart = new WaterfallChart(display, 0, WATERFALL_START, display.width(), 0, FREQ_BEGIN, - FREQ_END, -(float)show_db_after, WATERFALL_SENSITIVITY, model); + FREQ_END, r.trigger_level, WATERFALL_SENSITIVITY, model); size_t c = stacked.addChart(waterChart); stacked.setHeight(c, stacked.height - WATERFALL_START - statusBar->height); + + r.addEventListener(DETECTED, *waterChart); #endif size_t d = stacked.addChart(statusBar); @@ -1053,6 +1058,7 @@ void loop(void) LOG("METHOD RSSI"); uint16_t max_rssi = r.rssiMethod(SAMPLES_RSSI, result, RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE); + if (max_x_rssi[display_x] > max_rssi) { max_x_rssi[display_x] = max_rssi; @@ -1077,42 +1083,24 @@ void loop(void) display.setColor(WHITE); } #endif - size_t detected_at = r.detect( - result, filtered_result, RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE, samples); + Event event = r.detect(result, filtered_result, + RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE, samples); + event.time_ms = millis(); + size_t detected_at = event.detected.detected_at; if (max_rssi_x > detected_at) { // MAx bin Value not RSSI max_rssi_x = detected_at; } - detected = detected_at < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE; + detected = event.detected.detected; detected_y[display_x] = false; - float rr; - if (detected) - { - rr = -(float)result[detected_at]; - } - else - { - rr = LO_RSSI_THRESHOLD; - } - + float rr = event.detected.rssi; r.drone_detection_level = drone_detection_level; -#if (WATERFALL_ENABLED == true) - waterChart->updatePoint(millis(), r.current_frequency, rr); -#endif - - int updated = bar->bar.updatePoint(r.current_frequency, rr); - - if (first_run || ANIMATED_RELOAD) - { - bar->bar.drawOne(updated); - } - - if (detected_at <= drone_detection_level) + if (event.detected.trigger) { // check if we should alarm about a drone presence if (detected_y[display_x] == false) // detection threshold match @@ -1152,6 +1140,8 @@ void loop(void) } } + r.fireEvent(event); + #ifdef JOYSTICK_ENABLED // Draw joystick cursor and Frequency RSSI value if (display_x == cursor_x_position) @@ -1166,16 +1156,10 @@ void loop(void) #ifdef PRINT_PROFILE_TIME scan_time += (millis() - scan_start_time); #endif - // count detected - if (detected) - { - r.detection_count++; - } - #ifdef PRINT_DEBUG Serial.println("....\n"); #endif - if (first_run || ANIMATED_RELOAD) + if (r.animated) { display.display(); } @@ -1230,7 +1214,10 @@ void loop(void) w = WATERFALL_START; } - stacked.draw(); + { + Event event(r, SCAN_TASK_COMPLETE, millis()); + r.fireEvent(event); + } // Render display data here #ifdef UPTIME_CLOCK diff --git a/test/test_rssi.cpp b/test/test_rssi.cpp index 3d82f29..a38d166 100644 --- a/test/test_rssi.cpp +++ b/test/test_rssi.cpp @@ -48,14 +48,17 @@ void test_detect() uint16_t samples[test_sz] = {20, 50, 55, 60, 0, 70, 75, 80, 0, 90, 0, 100, 110}; bool result[test_sz]; - size_t r = Scan::detect(samples, result, test_sz, 1); + TestScan test_scan({}, 0); + Event e = test_scan.detect(samples, result, test_sz, 1); + size_t r = e.detected.detected_at; bool expect[test_sz] = {1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1}; TEST_ASSERT_EQUAL_INT16(0, r); TEST_ASSERT_EQUAL_INT8_ARRAY(expect, result, test_sz); - r = Scan::detect(samples, result, test_sz, 2); + Event e2 = test_scan.detect(samples, result, test_sz, 2); + r = e2.detected.detected_at; bool expect2[test_sz] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0}; From b9e2bffb93cbcae1abfbbab9dd1f73ca93a21ef5 Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Sun, 6 Oct 2024 17:26:08 +0100 Subject: [PATCH 15/16] drone_sound_alarm as event listener --- lib/scan/scan.cpp | 15 +++++++++++++++ lib/scan/scan.h | 1 + src/main.cpp | 29 ++++++++++++++++++++--------- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/scan/scan.cpp b/lib/scan/scan.cpp index 028cf42..8c4fbed 100644 --- a/lib/scan/scan.cpp +++ b/lib/scan/scan.cpp @@ -154,6 +154,21 @@ size_t Scan::addEventListener(EventType t, Listener &l) return c; } +struct CallbackFunction : Listener +{ + void (*cb)(void *arg, Event &e); + void *arg; + + CallbackFunction(void cb(void *arg, Event &e), void *arg) : cb(cb), arg(arg) {} + + void onEvent(Event &e) { cb(arg, e); } +}; + +size_t Scan::addEventListener(EventType t, void cb(void *arg, Event &e), void *arg) +{ + return addEventListener(t, *(new CallbackFunction(cb, arg))); +} + void Scan::fireEvent(Event &event) { Listener **list = eventListeners[(size_t)event.type]; diff --git a/lib/scan/scan.h b/lib/scan/scan.h index 68b8cb5..b46ef83 100644 --- a/lib/scan/scan.h +++ b/lib/scan/scan.h @@ -69,6 +69,7 @@ struct Scan int samples); size_t addEventListener(EventType t, Listener &l); + size_t addEventListener(EventType t, void cb(void *, Event &), void *arg); void fireEvent(Event &e); }; diff --git a/src/main.cpp b/src/main.cpp index c6e6514..9ddba7a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -520,6 +520,8 @@ void logToSerialTask(void *parameter) } } +void drone_sound_alarm(void *arg, Event &e); + void setup(void) { #ifdef LILYGO @@ -678,12 +680,10 @@ void setup(void) r.trigger_level = TRIGGER_LEVEL; stacked.reset(0, 0, display.width(), display.height()); - r.addEventListener(SCAN_TASK_COMPLETE, stacked); bar = new DecoratedBarChart(display, 0, 0, display.width(), 0, FREQ_BEGIN, FREQ_END, LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, r.trigger_level); - r.addEventListener(DETECTED, bar->bar); size_t b = stacked.addChart(bar); Chart *statusBar = new StatusBar(display, 0, 0, display.width(), r); @@ -709,6 +709,10 @@ void setup(void) size_t d = stacked.addChart(statusBar); stacked.setHeight(b, stacked.height); + r.addEventListener(DETECTED, bar->bar); + r.addEventListener(DETECTED, drone_sound_alarm, &r); + r.addEventListener(SCAN_TASK_COMPLETE, stacked); + #ifdef UPTIME_CLOCK uptime = new UptimeClock(display, millis()); #endif @@ -796,9 +800,21 @@ bool buttonPressHandler(float freq) return true; } -void drone_sound_alarm(int drone_detection_level, int detection_count, - int tone_freq_db = 205) +void drone_sound_alarm(void *arg, Event &e) { + if (e.type != DETECTED) + { + return; + } + + Scan &r = *((Scan *)arg); + if (!r.sound_on) + return; + + int tone_freq_db = e.detected.detected_at * 2; + int drone_detection_level = r.drone_detection_level; + int detection_count = r.detection_count; + // If level is set to sensitive, // start beeping every 10th frequency and shorter // it improves performance less short beep delays... @@ -1115,11 +1131,6 @@ void loop(void) // mark freq end ... will shift right to last detected range drone_detected_frequency_end = r.current_frequency; - if (r.sound_on) - { - drone_sound_alarm(r.drone_detection_level, r.detection_count, - max_rssi_x * 2); - } #ifdef LOG_DATA_JSON frequency_scan_result.begin = drone_detected_frequency_start; From fe0bf7d658f2cdfa723b49b1dfa3c1510c39725a Mon Sep 17 00:00:00 2001 From: Sassa NF Date: Wed, 9 Oct 2024 19:36:49 +0100 Subject: [PATCH 16/16] Fixup button interaction --- lib/charts/BarChart.cpp | 16 +++- lib/charts/WaterfallChart.cpp | 2 + src/main.cpp | 176 ++++++++++++++++++++++------------ 3 files changed, 131 insertions(+), 63 deletions(-) diff --git a/lib/charts/BarChart.cpp b/lib/charts/BarChart.cpp index f92c63a..305cdbd 100644 --- a/lib/charts/BarChart.cpp +++ b/lib/charts/BarChart.cpp @@ -109,6 +109,8 @@ void BarChart::onEvent(Event &e) return; } + level_y = e.emitter.trigger_level; + int u = updatePoint(e.emitter.current_frequency, e.detected.rssi); if (e.emitter.animated) { @@ -134,6 +136,8 @@ void DecoratedBarChart::draw() display.setColor(WHITE); display.setTextAlignment(TEXT_ALIGN_LEFT); + uint16_t first_untouched = 0; + for (uint16_t x = 0; x < width; x++) { float y = bar.ys[x]; @@ -142,7 +146,7 @@ void DecoratedBarChart::draw() String s = String(bar.ys[x], 0); uint16_t w = display.getStringWidth(s); uint16_t x1 = x; - for (; x < x1 + w; x++) + for (; x < x1 + w && x < width; x++) { if (bar.ys[x] > y) { @@ -152,7 +156,15 @@ void DecoratedBarChart::draw() } } - display.drawString(x1, pos_y, s); + if (x > width && first_untouched <= width - w) + { + x1 = width - w; + } + + first_untouched = x; + + if (x1 + w <= width) + display.drawString(pos_x + x1, pos_y, s); } } diff --git a/lib/charts/WaterfallChart.cpp b/lib/charts/WaterfallChart.cpp index 9828794..f3b6553 100644 --- a/lib/charts/WaterfallChart.cpp +++ b/lib/charts/WaterfallChart.cpp @@ -63,5 +63,7 @@ void WaterfallChart::onEvent(Event &e) return; } + level_y = e.emitter.trigger_level; + updatePoint(e.time_ms, e.detected.freq, e.detected.rssi); } diff --git a/src/main.cpp b/src/main.cpp index 9ddba7a..bda4cde 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -725,10 +725,9 @@ int binToRSSI(int bin) return 11 + (bin * 4); } -// return true if continue the code is false break the loop -bool buttonPressHandler(float freq) +// is there an input using Hot Button or joystick +bool buttonInputRequested() { - // Detection level button short press if (button.pressedFor(100) #ifdef JOYSTICK_ENABLED || joy_btn_click() @@ -736,68 +735,63 @@ bool buttonPressHandler(float freq) ) { button.update(); - button_pressed_counter = 0; - // if long press stop - while (button.pressedNow() + if (button.pressedNow() #ifdef JOYSTICK_ENABLED - || joy_btn_click() + || joy_btn_click() #endif ) { - // Print Curent frequency once - if (button_pressed_counter == 0) - { - display.setTextAlignment(TEXT_ALIGN_CENTER); - display.drawString(128 / 2, 0, String(freq)); - display.display(); - } - delay(10); - button_pressed_counter++; - if (button_pressed_counter > 150) - { - digitalWrite(LED, HIGH); - delay(150); - digitalWrite(LED, LOW); - } - } - if (button_pressed_counter > 150) - { - // Remove Curent Frequency Text - display.setTextAlignment(TEXT_ALIGN_CENTER); - display.setColor(BLACK); - display.drawString(128 / 2, 0, String(freq)); - display.setColor(WHITE); - display.display(); - return false; - } - if (button_pressed_counter > 50 && button_pressed_counter < 150) - { - if (!joy_btn_clicked) - { - // Visually confirm it's off so user releases button - display.displayOff(); - // Deep sleep (has wait for release so we don't wake up - // immediately) - heltec_deep_sleep(); - } - return false; - } - button.update(); - display.setTextAlignment(TEXT_ALIGN_RIGHT); - // erase old drone detection level value - display.setColor(BLACK); - display.fillRect(128 - 13, 0, 13, 13); - display.setColor(WHITE); - drone_detection_level++; - // print new value - display.drawString(128, 0, String(drone_detection_level)); - tone(BUZZER_PIN, 104, 150); - if (drone_detection_level > 30) - { - drone_detection_level = 1; + return true; } } - return true; + + return false; +} + +enum ButtonEvent +{ + NONE = 0, + LONG_PRESS, + SHORT_PRESS, + TOO_SHORT, + SUSPEND +}; + +ButtonEvent buttonPressEvent() +{ + button_pressed_counter = 0; + // if long press stop + while (button.pressedNow() +#ifdef JOYSTICK_ENABLED + || joy_btn_click() +#endif + ) + { + delay(10); + button_pressed_counter++; + if (button_pressed_counter > 150) + { + digitalWrite(LED, HIGH); + delay(150); + digitalWrite(LED, LOW); + } + } + if (button_pressed_counter > 150) + { + return LONG_PRESS; + } + + if (button_pressed_counter > 50) + { + if (!joy_btn_clicked) + { + return SUSPEND; + } + return SHORT_PRESS; + } + button.update(); + + return TOO_SHORT; } void drone_sound_alarm(void *arg, Event &e) @@ -1175,8 +1169,68 @@ void loop(void) display.display(); } - if (buttonPressHandler(r.current_frequency) == false) - break; + if (buttonInputRequested()) + { + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.drawString(display.width() / 2, 0, String(r.current_frequency)); + display.display(); + + ButtonEvent e = buttonPressEvent(); + + if (e == LONG_PRESS) + { + // Remove Curent Frequency Text + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.setColor(BLACK); + display.drawString(display.width() / 2, 0, + String(r.current_frequency)); + display.setColor(WHITE); + display.display(); + + break; + } + + if (e == SUSPEND) + { + // Visually confirm it's off so user releases button + display.displayOff(); + // Deep sleep (has wait for release so we don't wake up + // immediately) + heltec_deep_sleep(); + break; + } + + if (e == SHORT_PRESS) + break; + + if (e == TOO_SHORT) + { + String v = String(r.trigger_level) + " dB"; + uint16_t w = display.getStringWidth(v); + display.setTextAlignment(TEXT_ALIGN_RIGHT); + // erase old drone detection level value + display.setColor(BLACK); + display.fillRect(display.width() - w, 0, 13, w); + display.setColor(WHITE); + + // dt is roughly single-pixel increment + float dt = + bar->bar.height == 0 + ? 0.0 + : (LO_RSSI_THRESHOLD - HI_RSSI_THRESHOLD) / bar->bar.height; + r.trigger_level += dt; + if (r.trigger_level <= LO_RSSI_THRESHOLD) + { + r.trigger_level = HI_RSSI_THRESHOLD; + } + + // print new value + display.drawString(display.width(), 0, v); + tone(BUZZER_PIN, 104, 150); + + bar->bar.redraw_all = true; + } + } // wait a little bit before the next scan, // otherwise the SX1262 hangs