diff --git a/.gitignore b/.gitignore
index 89cc49c..3711360 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
+
+out/
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"
+ },
}
diff --git a/README.md b/README.md
index 957a5a0..3e7cc40 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,9 @@
- Heltec Wireless Stick Lite V3 No Display (Not Tested)
- Heltec Vision Master E290 - e-Ink 296 x 128 (No OSD)
- Heltec Vision MAster T190 - color TFT 320X170 (No OSD)
-- LilyGo Radio Lora T3S3 V.2 SX1262
+- LilyGo Radio Lora T3S3 V.2 SX1262
+- LilyGo Radio Lora T3S3 V.2 SX1280
+- LilyGo Radio Lora T3_V1.6.1 SX1276 (Not Tested)
## RF Spectrum Analyzer using Lora Radio
@@ -158,6 +160,10 @@ If less, ESP32 will turn off. Fast pressing(less than 0.5 second) P button chang
3. Connect ESP32 to USB. Install USB CP2101 drivers for Windows or other OS
https://docs.heltec.org/general/establish_serial_connection.html#for-windows
https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers?tab=downloads
+
+ ## NOTE: MACOS Heltec USB driver
+ https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers?tab=downloads
+ I used legacy driver
5. Clone this Git Repo or download zip of the sources

@@ -174,6 +180,15 @@ If less, ESP32 will turn off. Fast pressing(less than 0.5 second) P button chang
7. Select Proper Environment

+
+ ---
+
+ >**Important note:** If using a Heltec V3 board, make sure your ESP32 Expressif catalog is up to date before selecting environment, otherwise might get a build time error such as: `Error: Unknown board ID 'heltec_wifi_lora_32_V3'` when trying to select the environment.
+ >
+ >Open a PlatformIO CLI: https://docs.platformio.org/en/latest/integration/ide/vscode.html#platformio-core-cli
+ >
+ >Run: `pio pkg update -g -p espressif32`
+
8. Select ESP32 USB Device to program

Note: It is theoretically possible to program via WiFi and BTH.
@@ -222,6 +237,24 @@ or buy :

We are using pin 41 as a Buzzer trigger. Connect buzzer + leg with pin 41 and - leg with the ground (GND). You can change the buzzer pin in the code.
+
+## Analog FPV OSD (ON SCREEN DISPLAY)
+To Enable OSD, Uncomment these lines
+```
+// #define OSD_ENABLED true
+```
+**OSD sidebar enabled/disable**
+comment or uncomment this line
+```
+#define OSD_SIDE_BAR true
+```
+
+Or you can set this and other variables as a build parameter:
+```
+build_flags =
+ -DOSD_ENABLED
+```
+
## DFRobot OSD Wiring
**Heltec V3 -> DFRobot OSD**
GND -> GND
@@ -287,3 +320,11 @@ Edit **paltformio.io** uncommenting/selecting your sources
```
for LilyGo use env:heltec_wifi_lora_32_V3
+# WiFi and Bluetooth BT Scanning
+Works only with OSD enabled
+Uncomment this lines
+```
+// #define OSD_ENABLED true
+// #define WIFI_SCANNING_ENABLED true
+// #define BT_SCANNING_ENABLED true
+```
diff --git a/SpectrumScan.py b/SpectrumScan.py
index acc37a0..ee713a3 100644
--- a/SpectrumScan.py
+++ b/SpectrumScan.py
@@ -7,30 +7,21 @@ import sys
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
-
+import json
from datetime import datetime
from argparse import RawTextHelpFormatter
-# number of samples in each scanline
-SCAN_WIDTH = 33
+# Constants
+SCAN_WIDTH = 33 # number of samples in each scanline
+OUT_PATH = "out" # output path for saved files
-# scanline Serial start/end markers
-SCAN_MARK_START = 'SCAN '
-SCAN_MARK_FREQ = 'FREQ '
-SCAN_MARK_END = ' END'
-
-# output path
-OUT_PATH = 'out'
-
-# default settings
+# Default settings
DEFAULT_BAUDRATE = 115200
DEFAULT_COLOR_MAP = 'viridis'
DEFAULT_SCAN_LEN = 200
DEFAULT_RSSI_OFFSET = -11
-# Print iterations progress
-# from https://stackoverflow.com/questions/3173320/text-progress-bar-in-terminal-with-block-characters
-def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1, length = 50, fill = '█', printEnd = "\r"):
+def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=50, fill='█', print_end="\r"):
"""
Call in a loop to create terminal progress bar
@params:
@@ -41,136 +32,104 @@ def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1,
decimals - Optional : positive number of decimals in percent complete (Int)
length - Optional : character length of bar (Int)
fill - Optional : bar fill character (Str)
- printEnd - Optional : end character (e.g. "\r", "\r\n") (Str)
+ print_end - Optional : end character (e.g. "\r", "\r\n") (Str)
"""
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
- filledLength = int(length * iteration // total)
- bar = fill * filledLength + '-' * (length - filledLength)
- print(f'\r{prefix} |{bar}| {percent}% {suffix}', end = printEnd)
+ filled_length = int(length * iteration // total)
+ bar = fill * filled_length + '-' * (length - filled_length)
+ print(f'\r{prefix} |{bar}| {percent}% {suffix}', end=print_end)
if iteration == total:
print()
+def parse_line(line):
+ """Parse a JSON line from the serial input."""
+ return json.loads(line)
def main():
- parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''
- RadioLib SX126x_Spectrum_Scan plotter script. Displays output from SX126x_Spectrum_Scan example
- as grayscale and
+ parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''\
+ Parse serial data from LOG_DATA_JSON functionality.
- Depends on pyserial and matplotlib, install by:
- 'python3 -m pip install pyserial matplotlib'
-
- Step-by-step guide on how to use the script:
- 1. Upload the SX126x_Spectrum_Scan example to your Arduino board with SX1262 connected.
+ 1. #define LOG_DATA_JSON true - add this line in main.cpp, upload to device
2. Run the script with appropriate arguments.
3. Once the scan is complete, output files will be saved to out/
''')
- parser.add_argument('port',
- type=str,
- help='COM port to connect to the device')
- parser.add_argument('--speed',
- default=DEFAULT_BAUDRATE,
- type=int,
- help=f'COM port baudrate (defaults to {DEFAULT_BAUDRATE})')
- parser.add_argument('--map',
- default=DEFAULT_COLOR_MAP,
- type=str,
- help=f'Matplotlib color map to use for the output (defaults to "{DEFAULT_COLOR_MAP}")')
- parser.add_argument('--len',
- default=DEFAULT_SCAN_LEN,
- type=int,
- help=f'Number of scanlines to record (defaults to {DEFAULT_SCAN_LEN})')
- parser.add_argument('--offset',
- default=DEFAULT_RSSI_OFFSET,
- type=int,
- help=f'Default RSSI offset in dBm (defaults to {DEFAULT_RSSI_OFFSET})')
- parser.add_argument('--freq',
- default=-1,
- type=float,
- help=f'Default starting frequency in MHz')
+ parser.add_argument('port', type=str, help='COM port to connect to the device')
+ parser.add_argument('--speed', default=DEFAULT_BAUDRATE, type=int,
+ help=f'COM port baudrate (defaults to {DEFAULT_BAUDRATE})')
+ parser.add_argument('--map', default=DEFAULT_COLOR_MAP, type=str,
+ help=f'Matplotlib color map to use for the output (defaults to "{DEFAULT_COLOR_MAP}")')
+ parser.add_argument('--len', default=DEFAULT_SCAN_LEN, type=int,
+ help=f'Number of scanlines to record (defaults to {DEFAULT_SCAN_LEN})')
+ parser.add_argument('--offset', default=DEFAULT_RSSI_OFFSET, type=int,
+ help=f'Default RSSI offset in dBm (defaults to {DEFAULT_RSSI_OFFSET})')
+ parser.add_argument('--freq', default=-1, type=float,
+ help='Default starting frequency in MHz')
args = parser.parse_args()
- freq_mode = False
+ # Create the result array
scan_len = args.len
- if (args.freq != -1):
- freq_mode = True
- scan_len = 1000
+ arr = np.zeros((scan_len, SCAN_WIDTH))
- # create the color map and the result array
- arr = np.zeros((SCAN_WIDTH, scan_len))
-
- # scanline counter
+ # Scanline counter
row = 0
- # list of frequencies in frequency mode
+ # List of frequencies
freq_list = []
- # open the COM port
+ # Open the COM port
with serial.Serial(args.port, args.speed, timeout=None) as com:
- while(True):
- # update the progress bar
- if not freq_mode:
- printProgressBar(row, scan_len)
+ while True:
+ # Update the progress bar
+ print_progress_bar(row, scan_len)
- # read a single line
+ # Read a single line
try:
- line = com.readline().decode('utf-8')
- except:
+ line = com.readline().decode('utf-8').strip()
+ except UnicodeDecodeError:
continue
- if SCAN_MARK_FREQ in line:
- new_freq = float(line.split(' ')[1])
- if (len(freq_list) > 1) and (new_freq < freq_list[-1]):
- break
+ if line.startswith("{"):
+ try:
+ data = parse_line(line)
+ except json.JSONDecodeError:
+ continue
- freq_list.append(new_freq)
- print('{:.3f}'.format(new_freq), end = '\r')
- continue
+ # get the lowest frequency for now, could be averaged too.
+ freq = data["low_range_freq"]
- # check the markers
- if (SCAN_MARK_START in line) and (SCAN_MARK_END in line):
- # get the values
- scanline = line[len(SCAN_MARK_START):-len(SCAN_MARK_END)].split(',')
- for col in range(SCAN_WIDTH):
- arr[col][row] = int(scanline[col])
+ # value in negative, eg: -70
+ rssi = int(data["value"])
+
+ if freq not in freq_list:
+ freq_list.append(freq)
- # increment the row counter
- row = row + 1
+ col = freq_list.index(freq)
+ arr[row][col] = rssi
+
+ # Increment the row counter
+ row += 1
- # check if we're done
- if (not freq_mode) and (row >= scan_len):
+ # Check if we're done
+ if row >= scan_len:
break
-
- # scale to the number of scans (sum of any given scanline)
- num_samples = arr.sum(axis=0)[0]
- arr *= (num_samples/arr.max())
- if freq_mode:
- scan_len = len(freq_list)
+ # Create the figure
+ fig, ax = plt.subplots(figsize=(12, 8))
- # create the figure
- fig, ax = plt.subplots()
+ # Display the result as heatmap
+ extent = [0, scan_len, freq_list[0], freq_list[-1]]
+ im = ax.imshow(arr.T, cmap=args.map, extent=extent, aspect='auto', origin='lower')
+ fig.colorbar(im, label='RSSI (dBm)')
- # display the result as heatmap
- extent = [0, scan_len, -4*(SCAN_WIDTH + 1), args.offset]
- if freq_mode:
- extent[0] = freq_list[0]
- extent[1] = freq_list[-1]
- im = ax.imshow(arr[:,:scan_len], cmap=args.map, extent=extent)
- fig.colorbar(im)
-
- # set some properites and show
+ # Set plot properties and show
timestamp = datetime.now().strftime('%y-%m-%d %H-%M-%S')
- title = f'RadioLib SX126x Spectral Scan {timestamp}'
- if freq_mode:
- plt.xlabel("Frequency [Hz]")
- else:
- plt.xlabel("Time [sample]")
- plt.ylabel("RSSI [dBm]")
- ax.set_aspect('auto')
+ title = f'LoraSA Spectral Scan {timestamp}'
+ plt.xlabel("Time (sample)")
+ plt.ylabel("Frequency (MHz)")
fig.suptitle(title)
fig.canvas.manager.set_window_title(title)
plt.savefig(f'{OUT_PATH}/{title.replace(" ", "_")}.png', dpi=300)
plt.show()
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/data/index.html b/data/index.html
new file mode 100644
index 0000000..82bfe6a
--- /dev/null
+++ b/data/index.html
@@ -0,0 +1,42 @@
+
+
+
+
+ ESP Wi-Fi Manager
+
+
+
+
+
+
+
+
LORA SA ESP32 CONFIG
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/style.css b/data/style.css
new file mode 100644
index 0000000..491d8c4
--- /dev/null
+++ b/data/style.css
@@ -0,0 +1,118 @@
+html {
+ font-family: Arial, Helvetica, sans-serif;
+ display: inline-block;
+ text-align: center;
+}
+
+h1 {
+ font-size: 1.8rem;
+ color: white;
+}
+
+p {
+ font-size: 1.4rem;
+}
+
+.topnav {
+ overflow: hidden;
+ background-color: #0A1128;
+}
+
+body {
+ margin: 0;
+}
+
+.content {
+ padding: 5%;
+}
+
+.card-grid {
+ max-width: 800px;
+ margin: 0 auto;
+ display: grid;
+ grid-gap: 2rem;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+}
+
+.card {
+ background-color: white;
+ box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, .5);
+}
+
+.card-title {
+ font-size: 1.2rem;
+ font-weight: bold;
+ color: #034078
+}
+
+input[type=submit] {
+ border: none;
+ color: #FEFCFB;
+ background-color: #034078;
+ padding: 15px 15px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ width: 100px;
+ margin-right: 10px;
+ border-radius: 4px;
+ transition-duration: 0.4s;
+}
+
+input[type=submit]:hover {
+ background-color: #1282A2;
+}
+
+input[type=text],
+input[type=number],
+select {
+ width: 50%;
+ padding: 12px 20px;
+ margin: 18px;
+ display: inline-block;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+}
+
+label {
+ font-size: 1.2rem;
+}
+
+.value {
+ font-size: 1.2rem;
+ color: #1282A2;
+}
+
+.state {
+ font-size: 1.2rem;
+ color: #1282A2;
+}
+
+button {
+ border: none;
+ color: #FEFCFB;
+ padding: 15px 32px;
+ text-align: center;
+ font-size: 16px;
+ width: 100px;
+ border-radius: 4px;
+ transition-duration: 0.4s;
+}
+
+.button-on {
+ background-color: #034078;
+}
+
+.button-on:hover {
+ background-color: #1282A2;
+}
+
+.button-off {
+ background-color: #858585;
+}
+
+.button-off:hover {
+ background-color: #252524;
+}
diff --git a/data/text.txt b/data/text.txt
new file mode 100644
index 0000000..e69de29
diff --git a/eink_src/main.cpp b/eink_src/main.cpp
index a87ece8..ee82ccb 100644
--- a/eink_src/main.cpp
+++ b/eink_src/main.cpp
@@ -23,6 +23,7 @@
#define DISPLAY_WIDTH 296
#define DISPLAY_HEIGHT 128
// Without this line Lora Radio doesn't work with heltec lib
+#define BUTTON 21
#define ARDUINO_heltec_wifi_32_lora_V3
#include "heltec_unofficial.h"
@@ -96,6 +97,7 @@ bool first_run, new_pixel, detected_x = false;
// drone detection flag
bool detected = false;
uint64_t drone_detection_level = 90;
+bool detection_level_changed = false;
uint64_t drone_detected_frequency_start = 0;
uint64_t drone_detected_frequency_end = 0;
uint64_t detection_count = 0;
@@ -250,18 +252,21 @@ void VextOFF(void) // Vext default OFF
constexpr int lower_level = 108;
constexpr int up_level = 40;
+constexpr int start_pixel = 80;
+
int rssiToPix(int rssi)
{
// Bigger is lower signal
if (abs(rssi) >= lower_level)
{
- return lower_level - 1;
+ return start_pixel - 1;
}
if (abs(rssi) <= up_level)
{
- return up_level;
+ return start_pixel - up_level;
}
- return abs(rssi);
+
+ return start_pixel - (lower_level - abs(rssi));
}
long timeSinceLastModeSwitch = 0;
@@ -287,10 +292,61 @@ long display_scan_start = 0;
long display_scan_end = 0;
long display_scan_i_end = 0;
int scan_iterations = 0;
+bool waterfall_values[DISPLAY_HEIGHT][DISPLAY_WIDTH] = {false};
constexpr unsigned int SCANS_PER_DISPLAY = 5;
constexpr unsigned int STATUS_BAR_HEIGHT = 5;
+void clear_rectangle(int x, int y, int width, int height)
+{
+ for (int x = 0; x < width; x++)
+ {
+ for (int y = 0; y < height; y++)
+ {
+ display.clearPixel(x, y);
+ }
+ }
+}
+
+void button_logic(void)
+{
+ heltec_loop();
+ button_pressed_counter = 0;
+ if (button.pressed())
+ {
+ drone_detection_level++;
+ detection_level_changed = true;
+ if (drone_detection_level > 107)
+ drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL - 20;
+ clear_rectangle(0, 0, 10, 10);
+ display.setFont(ArialMT_Plain_10);
+
+ display.drawString(0, 0,
+ "T:" + String(display_scan_end - display_scan_start) + "/" +
+ String(display_scan_i_end - display_scan_start) + " L:-" +
+ String(drone_detection_level) + "dB");
+ while (button.pressedNow())
+ {
+
+ display.display();
+ button_pressed_counter++;
+ // button.update();
+
+ if (button_pressed_counter > 18)
+ {
+ // Some sign there
+ }
+ }
+ }
+
+ if (button_pressed_counter < 9 && button_pressed_counter > 5)
+ {
+ display.clear();
+ display.display();
+ heltec_deep_sleep();
+ }
+}
+
void loop()
{
if (screen_update_loop_counter == 0)
@@ -307,9 +363,13 @@ void loop()
int u = 0;
for (int i = 0; i < SAMPLES_RSSI; i++)
{
-
- radio.setFrequency((float)fr + (float)(rssi_mhz_step * u),
- false); // false = no calibration need here
+ state = radio.setFrequency((float)fr + (float)(rssi_mhz_step * u),
+ true); // false = no calibration need here
+ int radio_error_count = 0;
+ if (state != RADIOLIB_ERR_NONE)
+ {
+ Serial.println("E:setFrequency:" + String(freq));
+ }
u++;
if (rssi_mhz_step * u >= mhz_step)
{
@@ -319,10 +379,14 @@ void loop()
rssi2 = radio.getRSSI(false);
scan_iterations++;
if (rssi2 > lower_level)
+ {
+ max_scan_rssi[x1] = rssi2;
continue;
+ }
#ifdef PRINT_DEBUG
Serial.println(String(fr) + ":" + String(rssi2));
#endif
+ button_logic();
// display.drawString(x1, (int)y2, String(fr) + ":" + String(rssi2));
display.setPixel(x1, rssiToPix(rssi2));
@@ -332,6 +396,16 @@ void loop()
}
}
+ // Waterfall per scan not per screen
+ if (abs(max_scan_rssi[x1]) <= drone_detection_level)
+ {
+ waterfall_values[w][x1] = true;
+ }
+ else
+ {
+ waterfall_values[w][x1] = false;
+ }
+
// drone detection level line
if (x1 % 2 == 0)
{
@@ -343,9 +417,15 @@ void loop()
{
display_scan_i_end = millis();
}
+ button_logic();
// Main N x-axis full loop end logic
if (x1 >= STEPS)
{
+ w++;
+ if (w >= DISPLAY_HEIGHT - start_pixel - 13)
+ {
+ w = 0;
+ }
if (screen_update_loop_counter == SCANS_PER_DISPLAY)
{
// max Mhz and dB in window
@@ -372,17 +452,29 @@ void loop()
display.drawString(i - rssi_window_size + 5, y2 + 10,
String(window_max_fr));
// Vertical lines between windows
- for (int l = y2; l < 100; l += 4)
+ for (int l = y2; l < start_pixel; l += 4)
{
display.setPixel(i, l);
}
}
window_max_rssi = -999;
}
+
+ // Draw Waterfall
+ for (int y = 0; y < DISPLAY_HEIGHT; y++)
+ if (waterfall_values[y][i] == true)
+ {
+ display.setPixel(i, start_pixel + 5 + y);
+ }
}
+ // Draw Waterfall cursor
+ display.drawHorizontalLine(0, start_pixel + 5 + w, DISPLAY_WIDTH);
display_scan_end = millis();
-
+ if (detection_level_changed == true)
+ {
+ clear_rectangle(0, 0, 100, 10);
+ }
display.setFont(ArialMT_Plain_10);
display.drawString(0, 0,
"T:" + String(display_scan_end - display_scan_start) +
@@ -396,6 +488,7 @@ void loop()
esp_restart();
}
+ // ToDo: it doesn't work
battery();
// iteration full scan / samples pixel step / numbers of scan per display
display.drawString(DISPLAY_WIDTH - ((DISPLAY_WIDTH / 6) * 2) - 5, 0,
@@ -409,21 +502,21 @@ void loop()
"s:" + String(mhz_step));
// Draw a line horizontally
- display.drawHorizontalLine(0, lower_level + 1, DISPLAY_WIDTH);
+ display.drawHorizontalLine(0, 1 + start_pixel, DISPLAY_WIDTH);
// Generate Ticks
for (int x = 0; x < DISPLAY_WIDTH; x++)
{
if (x % (DISPLAY_WIDTH / 2) == 0 && x > 5)
{
- display.drawVerticalLine(x, lower_level + 1, 11);
+ display.drawVerticalLine(x, 1 + start_pixel, 11);
// central tick width
// display.drawVerticalLine(x - 1, lower_level + 1, 8);
// display.drawVerticalLine(x + 1, lower_level + 1, 8);
}
if (x % 10 == 0 || x == 0)
- display.drawVerticalLine(x, lower_level + 1, 6);
+ display.drawVerticalLine(x, 1 + start_pixel, 6);
if (x % 5 == 0)
- display.drawVerticalLine(x, lower_level + 1, 3);
+ display.drawVerticalLine(x, 1 + start_pixel, 3);
}
display.setFont(ArialMT_Plain_10);
@@ -441,13 +534,14 @@ void loop()
String(FREQ_BEGIN + (((int)fr - FREQ_BEGIN) -
((int)fr - FREQ_BEGIN) / 4)));
// End Mhz
- display.drawString(DISPLAY_WIDTH - 24, DISPLAY_HEIGHT - 10, String((int)fr));
+ display.drawString(DISPLAY_WIDTH - 20, DISPLAY_HEIGHT - 10, String((int)fr));
display.display();
// display will be cleared next scan iteration. it is just buffer clear
// memset(buffer, 0, displayBufferSize);
display.clear();
screen_update_loop_counter = 0;
+
scan_iterations = 0;
display_scan_i_end = 0;
}
@@ -480,7 +574,7 @@ void setup()
delay(1000);
display.clear();
Serial.begin(115200);
- w = WATERFALL_START;
+ w = 0; // WATERFALL_START;
init_radio();
state = radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_NONE);
if (state != RADIOLIB_ERR_NONE)
diff --git a/gps_src/main.cpp b/gps_src/main.cpp
new file mode 100644
index 0000000..0ea23d7
--- /dev/null
+++ b/gps_src/main.cpp
@@ -0,0 +1,126 @@
+#include
+#include
+
+// Objects for GNSS parsing and serial communication
+TinyGPSPlus gps;
+HardwareSerial GNSSSerial(2);
+
+// Pin definitions for GNSS module communication
+const int GNSS_RXPin = 34;
+const int GNSS_TXPin = 33;
+const int GNSS_RSTPin = 35; // There is a function built for this in the example below-
+ // currently it isn't used
+const int GNSS_PPS_Pin = 36;
+
+// Flags for PPS handling and synchronization status
+volatile bool ppsFlag = false;
+volatile bool initialSyncDone = false;
+
+// Timestamp for the last valid GNSS data received
+unsigned long lastGNSSDataMillis = 0;
+
+void setup()
+{
+ // Initialize serial communication for debugging
+ USBSerial.begin(115200);
+ while (!USBSerial)
+ ;
+
+ // Start GNSS module communication
+ GNSSSerial.begin(115200, SERIAL_8N1, GNSS_TXPin, GNSS_RXPin);
+ while (!GNSSSerial)
+ ;
+
+ // Configure GNSS reset pin
+ pinMode(GNSS_RSTPin, OUTPUT);
+ digitalWrite(GNSS_RSTPin, HIGH);
+
+ // Set up PPS pin and attach an interrupt handler
+ pinMode(GNSS_PPS_Pin, INPUT);
+ attachInterrupt(digitalPinToInterrupt(GNSS_PPS_Pin), ppsInterrupt, RISING);
+
+ // Short delay for GNSS module initialization
+ delay(1000);
+}
+
+void loop()
+{
+ // Process incoming GNSS data
+ while (GNSSSerial.available())
+ {
+ if (gps.encode(GNSSSerial.read()))
+ {
+ // Update the timestamp when valid GNSS data is received
+ lastGNSSDataMillis = millis();
+ displayGNSSData(); // Display GNSS data for debugging
+ }
+ }
+
+ // Perform initial synchronization using NMEA time data
+ if (!initialSyncDone && gps.date.isValid() && gps.time.isValid())
+ {
+ setSystemTime();
+ initialSyncDone = true;
+ USBSerial.println("Initial time synchronization done using NMEA data.");
+ }
+
+ // Disable interrupts to safely check and reset the PPS flag
+ noInterrupts();
+ if (ppsFlag)
+ {
+ fineTuneSystemTime(); // Adjust system time based on the PPS pulse
+ ppsFlag = false;
+ }
+ // Re-enable interrupts
+ interrupts();
+
+ // Check if GNSS data has been absent for more than a minute
+ if (millis() - lastGNSSDataMillis > 60000)
+ {
+ USBSerial.println("Warning: Haven't received GNSS data for more than 1 minute!");
+ // Additional actions can be added here, like alerts or module resets.
+ }
+}
+
+// Interrupt handler for the PPS signal
+void ppsInterrupt() { ppsFlag = true; }
+
+// Function to set system time using GNSS data
+void setSystemTime()
+{
+ struct tm timeinfo;
+ timeinfo.tm_year = gps.date.year() - 1900;
+ timeinfo.tm_mon = gps.date.month() - 1;
+ timeinfo.tm_mday = gps.date.day();
+ timeinfo.tm_hour = gps.time.hour();
+ timeinfo.tm_min = gps.time.minute();
+ timeinfo.tm_sec = gps.time.second();
+ time_t t = mktime(&timeinfo);
+
+ timeval tv = {t, 0};
+ settimeofday(&tv, NULL); // Update system time
+}
+
+// Function to fine-tune system time using the PPS pulse
+void fineTuneSystemTime()
+{
+ timeval tv;
+ gettimeofday(&tv, NULL);
+ tv.tv_usec = 0; // Reset microseconds to zero
+ settimeofday(&tv, NULL); // Update system time
+ USBSerial.println("System time fine-tuned using PPS signal.");
+}
+
+// Debugging function to display GNSS data
+void displayGNSSData()
+{
+ USBSerial.print("Latitude: ");
+ USBSerial.println(gps.location.lat(), 6);
+ USBSerial.print("Longitude: ");
+ USBSerial.println(gps.location.lng(), 6);
+ USBSerial.print("Altitude: ");
+ USBSerial.println(gps.altitude.meters());
+ USBSerial.print("Speed: ");
+ USBSerial.println(gps.speed.kmph());
+ USBSerial.println("-----------------------------");
+}
diff --git a/include/DFRobot_OSD.h b/include/DFRobot_OSD.h
index d446a86..063753c 100644
--- a/include/DFRobot_OSD.h
+++ b/include/DFRobot_OSD.h
@@ -13,8 +13,67 @@
#ifndef _DFRobot_OSD_H_
#define _DFRobot_OSD_H_
+#define MAX_POWER_LEVELS 33
+
#include
+/*Define Custom characters Example*/
+static const int buf0[36] = {0x02, 0x80, 0x02, 0x40, 0x7F, 0xE0, 0x42, 0x00,
+ 0x42, 0x00, 0x7A, 0x40, 0x4A, 0x40, 0x4A, 0x80,
+ 0x49, 0x20, 0x5A, 0xA0, 0x44, 0x60, 0x88, 0x20};
+
+static constexpr uint16_t levels[10] = {
+ 0x105, // 0
+ 0x10E, // 1
+ 0x10D, // 2
+ 0x10C, // 3
+ 0x10B, // 4
+ 0x10A, // 5
+ 0x109, // 6
+ 0x108, // 7
+ 0x107, // 8
+ 0x106, // 9
+};
+
+static constexpr uint16_t power_level[MAX_POWER_LEVELS + 1] = {
+ 0x10E, // 0
+ 0x10E, // 1
+ 0x10D, // 2
+ 0x10C, // 3
+ 0x10B, // 4
+ 0x10A, // 5
+ 0x109, // 6
+ 0x108, // 7
+ 0x107, // 8
+ 0x106, // 9 not using 106 to accent rise
+ // new line
+ 0x10E, // 10
+ 0x10D, // 11
+ 0x10C, // 12
+ 0x10B, // 13
+ 0x10A, // 14
+ 0x109, // 15
+ 0x108, // 16
+ 0x107, // 17
+ 0x106, // 18 not using 106
+ // new line
+ 0x10E, // 19
+ 0x10D, // 20
+ 0x10C, // 21
+ 0x10B, // 22
+ 0x10A, // 23
+ 0x109, // 24
+ 0x108, // 25
+ 0x107, // 26
+ 0x106, // 27
+ 0x105, // 28 ---
+ 0x105, // 29
+ 0x105, // 30
+ 0x105, // 31
+ 0x105, // 32
+ 0x105 // 33
+};
+
// #define ENABLE_DBG //!< Open this macro and you can see the details of the program
#ifdef ENABLE_DBG
#define DBG(...) \
diff --git a/include/File.h b/include/File.h
new file mode 100644
index 0000000..6f502ec
--- /dev/null
+++ b/include/File.h
@@ -0,0 +1,55 @@
+#include "FS.h"
+#include
+
+// Initialize LittleFS
+void initLittleFS()
+{
+ if (!LittleFS.begin(true))
+ {
+ Serial.println("An error has occurred while mounting LittleFS");
+ }
+ Serial.println("LittleFS mounted successfully");
+}
+
+String readFile(fs::FS &fs, const char *path)
+{
+ Serial.printf("Reading file: %s\r\n", path);
+
+ File file = fs.open(path);
+ if (!file || file.isDirectory())
+ {
+ Serial.println("- failed to open file for reading");
+ return String("");
+ }
+ String content;
+ Serial.println("- read from file:");
+ while (file.available())
+ {
+ content = file.readStringUntil('\n');
+ }
+ file.close();
+ return content;
+}
+
+void writeFile(fs::FS &fs, const char *path, const char *message)
+{
+ Serial.printf("Writing file: %s\r\n", path);
+ Serial.printf("Content: %s\r\n", message);
+
+ File file = fs.open(path, FILE_WRITE);
+ if (!file)
+ {
+ Serial.println("- failed to open file for writing");
+ return;
+ }
+ if (file.print(message))
+ {
+ Serial.println("- file written");
+ delay(500);
+ }
+ else
+ {
+ Serial.println("- write failed");
+ }
+ file.close();
+}
diff --git a/include/LiLyGo.h b/include/LiLyGo.h
index ec8754c..1d44603 100644
--- a/include/LiLyGo.h
+++ b/include/LiLyGo.h
@@ -1,32 +1,4 @@
-#define UNUSED_PIN (0)
-// LilyGo defined
-
-#define I2C_SDA 18
-#define I2C_SCL 17
-#define OLED_RST UNUSED_PIN
-
-#define RADIO_SCLK_PIN 5
-#define RADIO_MISO_PIN 3
-#define RADIO_MOSI_PIN 6
-#define RADIO_CS_PIN 7
-
-#define SDCARD_MOSI 11
-#define SDCARD_MISO 2
-#define SDCARD_SCLK 14
-#define SDCARD_CS 13
-
-#define BOARD_LED 37
-#define LED_ON HIGH
-
-#define BUTTON_PIN 0
-#define ADC_PIN 1
-
-#define RADIO_RST_PIN 8
-
-#define RADIO_DIO1_PIN 33
-#define RADIO_BUSY_PIN 34
-
// Define for our code
#define RST_OLED UNUSED_PIN
#define LED BOARD_LED
@@ -41,8 +13,11 @@
#else
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
-#include "OLEDDisplayUi.h"
+// #include "OLEDDisplayUi.h"
+// #include "SH1106Wire.h"
+// #include "SSD1306Brzo.h"
#include "SSD1306Wire.h"
+
#endif
#define ARDUINO_heltec_wifi_32_lora_V3
#ifndef HELTEC_NO_RADIO_INSTANCE
@@ -52,13 +27,24 @@
#include
SPIClass *hspi = new SPIClass(2);
SX1262 radio = new Module(SS, DIO1, RST_LoRa, BUSY_LoRa, *hspi);
-#else
+#else // ARDUINO_heltec_wifi_32_lora_V3
+#ifdef USING_SX1280PA
+SX1280 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
+#endif // end USING_SX1280PA
+#ifdef USING_SX1262
// Default SPI on pins from pins_arduino.h
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
-#endif
-#endif
-
-void heltec_loop() {}
+#endif // end USING_SX1262
+#ifdef USING_LR1121
+// Default SPI on pins from pins_arduino.h
+LR1121 radio = new Module(RADIO_CS_PIN, RADIO_DIO9_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
+#endif // end USING_LR1121
+#ifdef USING_SX1276
+// Default SPI on pins from pins_arduino.h
+SX1276 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
+#endif // end USING_SX1276
+#endif // end ARDUINO_heltec_wifi_32_lora_V3
+#endif // end HELTEC_NO_RADIO_INSTANCE
void heltec_led(int led) {}
@@ -101,16 +87,29 @@ class PrintSplitter : public Print
#else
#define DISPLAY_GEOMETRY GEOMETRY_128_64
#endif
-SSD1306Wire display(0x3c, 18, 17, DISPLAY_GEOMETRY);
+#define SCREEN_ADDRESS 0x3C
+
+SSD1306Wire display(SCREEN_ADDRESS, I2C_SDA, I2C_SCL, DISPLAY_GEOMETRY);
+// SH1106Wire display(0x3c, I2C_SDA, I2C_SCL, DISPLAY_GEOMETRY);
PrintSplitter both(Serial, display);
#else
Print &both = Serial;
#endif
// some fake pin
-#define BUTTON 38
+#ifdef T3_V1_6_SX1276
+#define BUTTON_PIN 22
+#endif
+#define BUTTON BUTTON_PIN
#include "HotButton.h"
HotButton button(BUTTON);
+void heltec_loop()
+{
+#ifndef DT3_V1_6_SX1276
+ button.update();
+#endif
+}
+
// This file contains a binary patch for the SX1262
#include "modules/SX126x/patches/SX126x_patch_scan.h"
@@ -161,7 +160,7 @@ void heltec_setup()
#ifndef HELTEC_NO_DISPLAY_INSTANCE
heltec_display_power(true);
display.init();
- display.setContrast(200);
+ // display.setContrast(200);
display.flipScreenVertically();
#endif
}
diff --git a/include/WIFI_SERVER.h b/include/WIFI_SERVER.h
new file mode 100644
index 0000000..93a715d
--- /dev/null
+++ b/include/WIFI_SERVER.h
@@ -0,0 +1,205 @@
+#include
+#include
+#include
+#include
+
+// Create AsyncWebServer object on port 80
+AsyncWebServer server(80);
+
+// Search for parameter in HTTP POST request
+const String SSID = "ssid";
+const String PASS = "pass";
+const String IP = "ip";
+const String GATEWAY = "gateway";
+const String FSTART = "fstart";
+const String FEND = "fend";
+
+// File paths to save input values permanently
+// const char *ssidPath = "/ssid.txt";
+
+// Variables to save values from HTML form
+String ssid = "LoraSA", pass = "1234567890", ip = "192.168.1.100",
+ gateway = "192.168.1.1", fstart = "", fend = "", smpls = "";
+
+IPAddress localIP;
+// Set your Gateway IP address
+IPAddress localGateway;
+IPAddress subnet(255, 255, 0, 0);
+
+// Timer variables
+unsigned long previousMillis = 0;
+const long interval = 10000; // interval to wait for Wi-Fi connection (milliseconds)
+
+// Initialize WiFi
+bool initWiFi()
+{
+ Serial.println("SSID:" + ssid);
+ Serial.println("PSWD:" + pass);
+ Serial.println("IP:" + ip);
+ Serial.println("SUB:" + subnet);
+ Serial.println("GATAWAY:" + gateway);
+ if (ssid == "" || ip == "")
+ {
+ Serial.println("Undefined SSID or IP address.");
+ return false;
+ }
+
+ WiFi.mode(WIFI_STA);
+ localIP.fromString(ip.c_str());
+ localGateway.fromString(gateway.c_str());
+
+ if (!WiFi.config(localIP, localGateway, subnet))
+ {
+ Serial.println("STA Failed to configure");
+ return false;
+ }
+ WiFi.begin(ssid.c_str(), pass.c_str());
+ Serial.println("Connecting to WiFi...");
+
+ unsigned long currentMillis = millis();
+ previousMillis = currentMillis;
+
+ while (WiFi.status() != WL_CONNECTED)
+ {
+ currentMillis = millis();
+ if (currentMillis - previousMillis >= interval)
+ {
+ Serial.println("Failed to connect.");
+ return false;
+ }
+ }
+
+ Serial.println(WiFi.localIP());
+ return true;
+}
+
+void writeParameterToFile(String value, String file)
+{
+ // Write file to save value
+ writeFile(LittleFS, file.c_str(), value.c_str());
+}
+
+void writeParameterToParameterFile(String param, String value)
+{
+ String file = String("/" + param + ".txt");
+ // Write file to save value
+ writeParameterToFile(value, file.c_str());
+}
+
+String readParameterFromParameterFile(String param)
+{
+ String file = String("/" + param + ".txt");
+ return readFile(LittleFS, file.c_str());
+}
+
+void serverServer()
+{
+ // Route for root / web page
+ server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
+ { request->send(LittleFS, "/index.html", "text/html"); });
+
+ server.serveStatic("/", LittleFS, "/");
+
+ server.on("/", HTTP_POST,
+ [](AsyncWebServerRequest *request)
+ {
+ int params = request->params();
+ for (int i = 0; i < params; i++)
+ {
+ Serial.println("Parameter " + String(i) + ": " +
+ request->getParam(i)->value());
+ }
+ Serial.println(request->params());
+
+ String p;
+ if (request->hasParam(IP, true))
+ {
+ p = request->getParam(IP, true)->value();
+ writeParameterToParameterFile(IP, p);
+ }
+
+ if (request->hasParam(IP, true))
+ {
+ p = request->getParam(IP, true)->value();
+ writeParameterToParameterFile(IP, p);
+ }
+
+ if (request->hasParam(IP, true))
+ {
+ p = request->getParam(IP, true)->value();
+ writeParameterToParameterFile(IP, p);
+ }
+
+ if (request->hasParam(GATEWAY, true))
+ {
+ p = request->getParam(GATEWAY, true)->value();
+ writeParameterToParameterFile(GATEWAY, p);
+ }
+
+ if (request->hasParam(FSTART, true))
+ {
+ p = request->getParam(FSTART, true)->value();
+ writeParameterToParameterFile(FSTART, p);
+ }
+
+ if (request->hasParam(FEND, true))
+ {
+ p = request->getParam(FEND, true)->value();
+ writeParameterToParameterFile(FEND, p);
+ }
+
+ if (request->hasParam("samples", true))
+ {
+ p = request->getParam("samples", true)->value();
+ writeParameterToParameterFile("samples", p);
+ }
+
+ request->send(200, "text/plain",
+ "Done. ESP will restart, connect to your router and "
+ "go to IP address: " +
+ ip);
+ delay(3000);
+ ESP.restart();
+ });
+
+ /* // Route to set GPIO state to HIGH
+ server.on("/on", HTTP_GET,
+ [](AsyncWebServerRequest *request)
+ {
+ digitalWrite(ledPin, HIGH);
+ request->send(LittleFS, "/index.html", "text/html", false,
+ processor);
+ });
+
+ // Route to set GPIO state to LOW
+ server.on("/off", HTTP_GET,
+ [](AsyncWebServerRequest *request)
+ {
+ digitalWrite(ledPin, LOW);
+ request->send(LittleFS, "/index.html", "text/html", false,
+ processor);
+ });*/
+ server.begin();
+}
+
+void serverStart()
+{
+ if (initWiFi())
+ {
+ Serial.println("Setting Secure WIFI (Access Point)");
+ serverServer();
+ }
+ else
+ {
+ // Connect to Wi-Fi network with default SSID and password
+ Serial.println("Setting AP (Access Point)");
+ // NULL sets an open Access Point
+ WiFi.softAP("LoraSA", NULL);
+
+ IPAddress IP = WiFi.softAPIP();
+ Serial.print("AP IP address: ");
+ Serial.println(IP);
+
+ serverServer();
+ }
+}
diff --git a/include/global_config.h b/include/global_config.h
index d169fff..a2014f7 100644
--- a/include/global_config.h
+++ b/include/global_config.h
@@ -1,28 +1,50 @@
#ifndef __GLOBAL_CONFIG_H__
#define __GLOBAL_CONFIG_H__
+#include "utilities.h"
+
+#ifndef FREQ_BEGIN
// frequency range in MHz to scan
#define FREQ_BEGIN 850
+#endif
+
+#ifndef FREQ_END
// TODO: if % RANGE_PER_PAGE != 0
#define FREQ_END 950
+#endif
// Measurement bandwidth. Allowed bandwidth values (in kHz) are:
// 4.8, 5.8, 7.3, 9.7, 11.7, 14.6, 19.5, 23.4, 29.3, 39.0, 46.9, 58.6,
// 78.2, 93.8, 117.3, 156.2, 187.2, 234.3, 312.0, 373.6 and 467.0
#define BANDWIDTH 467.0
+#define BANDWIDTH_SX1280 406.
// Detection level from the 33 levels. The higher number is more sensitive
#define DEFAULT_DRONE_DETECTION_LEVEL 18
#define BUZZER_PIN 41
+#ifdef LILYGO
+#define BUZZER_PIN 45
+#endif
+#ifdef T3_V1_6_SX1276
+#define BUZZER_PIN 35
+#endif
+
// REB trigger PIN
#define REB_PIN 42
+#ifdef T3_V1_6_SX1276
+#define REB_PIN 35
+#endif
#define WATERFALL_ENABLED true
#define WATERFALL_START 37
#ifdef LILYGO
-#define LED 46
+#define LED BOARD_LED
#endif // end not LILYGO
+#ifdef T3_V1_6_SX1276
+#define LED BOARD_LED
+#endif
+
#endif
diff --git a/include/ui.h b/include/ui.h
index 2ca7525..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,13 +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/BarChart.cpp b/lib/charts/BarChart.cpp
new file mode 100644
index 0000000..305cdbd
--- /dev/null
+++ b/lib/charts/BarChart.cpp
@@ -0,0 +1,194 @@
+#include "charts.h"
+
+void BarChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
+{
+ if (w != width)
+ {
+ delete[] ys;
+ delete[] changed;
+
+ ys = new float[w];
+ changed = new bool[w];
+ }
+
+ 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)
+{
+ 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);
+}
+
+void BarChart::onEvent(Event &e)
+{
+ if (e.type != DETECTED)
+ {
+ return;
+ }
+
+ level_y = e.emitter.trigger_level;
+
+ 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);
+ bar.reset(x, y + LABEL_HEIGHT, w, h - LABEL_HEIGHT - AXIS_HEIGHT);
+}
+
+void DecoratedBarChart::draw()
+{
+ bool draw_axis = bar.redraw_all;
+
+ bar.draw();
+
+ display.setColor(BLACK);
+ display.fillRect(pos_x, pos_y, width, bar.pos_y - pos_y);
+
+ 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];
+ if (y >= bar.level_y)
+ {
+ String s = String(bar.ys[x], 0);
+ uint16_t w = display.getStringWidth(s);
+ uint16_t x1 = x;
+ for (; x < x1 + w && x < width; x++)
+ {
+ if (bar.ys[x] > y)
+ {
+ y = bar.ys[x];
+ s = String(y, 0);
+ w = max(w, display.getStringWidth(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);
+ }
+ }
+
+ if (draw_axis)
+ {
+ display.setColor(WHITE);
+
+ 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);
+
+ for (float step = 0; bar.min_x + step * MAJOR_TICKS < bar.max_x; step += 1)
+ {
+ 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; bar.min_x + step * MINOR_TICKS < bar.max_x; step += 1)
+ {
+ 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/StackedChart.cpp b/lib/charts/StackedChart.cpp
new file mode 100644
index 0000000..2c6ca73
--- /dev/null
+++ b/lib/charts/StackedChart.cpp
@@ -0,0 +1,95 @@
+#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);
+ }
+
+ Chart::reset(x, y, w, h);
+}
+
+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/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/WaterfallChart.cpp b/lib/charts/WaterfallChart.cpp
new file mode 100644
index 0000000..f3b6553
--- /dev/null
+++ b/lib/charts/WaterfallChart.cpp
@@ -0,0 +1,69 @@
+#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);
+}
+
+void WaterfallChart::onEvent(Event &e)
+{
+ if (e.type != DETECTED)
+ {
+ return;
+ }
+
+ level_y = e.emitter.trigger_level;
+
+ updatePoint(e.time_ms, e.detected.freq, e.detected.rssi);
+}
diff --git a/lib/charts/charts.h b/lib/charts/charts.h
new file mode 100644
index 0000000..d159cc0
--- /dev/null
+++ b/lib/charts/charts.h
@@ -0,0 +1,178 @@
+#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
+#include
+
+struct Chart
+{
+ uint16_t pos_x, pos_y;
+ uint16_t width, height;
+ Display_t &display;
+
+ 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) {};
+
+ /*
+ * 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)
+ {
+ pos_x = x;
+ pos_y = y;
+ width = w;
+ height = h;
+ }
+
+ /*
+ * Redraw everything that needs redrawing.
+ */
+ virtual void draw() {};
+};
+
+/*
+ * ProgressChart supports updates with progressive redraw of just the affected area.
+ */
+struct ProgressChart : Chart
+{
+ ProgressChart(Display_t &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.
+ */
+ 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;
+};
+
+struct BarChart : ProgressChart, Listener
+{
+ float min_x, max_x, min_y, max_y;
+ float level_y;
+
+ float *ys;
+ bool *changed;
+ bool redraw_all;
+
+ 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)
+ {
+ ys = new float[w];
+ changed = new bool[w];
+
+ memset(ys, 0, w * sizeof(float));
+ memset(changed, 0, w * sizeof(bool));
+ };
+
+ 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;
+ void onEvent(Event &) override;
+
+ int x2pos(float x);
+ int y2pos(float y);
+};
+
+#define LABEL_HEIGHT 7
+#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 + 2)
+struct DecoratedBarChart : Chart
+{
+ BarChart bar;
+
+ DecoratedBarChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
+ float min_x, float max_x, float min_y, float max_y, float level_y)
+ : Chart(d, x, y, w, h),
+ bar(d, x, y + LABEL_HEIGHT, w, h - LABEL_HEIGHT - AXIS_HEIGHT, min_x, max_x,
+ min_y, max_y, level_y) {};
+
+ void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override;
+ void draw() override;
+};
+
+struct StackedChart : Chart, Listener
+{
+ Chart **charts;
+ size_t charts_sz;
+
+ 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) {};
+
+ /*
+ * 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;
+
+ void onEvent(Event &e) override;
+};
+
+struct WaterfallChart : Chart, Listener
+{
+ float min_x, max_x;
+ float level_y, threshold;
+
+ size_t update_to;
+
+ WaterfallModel *model;
+
+ 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),
+ 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;
+ void onEvent(Event &e) override;
+
+ int x2pos(float x);
+};
+
+struct UptimeClock : Chart
+{
+ uint64_t t0;
+ uint64_t t1;
+ 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;
+};
+
+#endif
diff --git a/lib/events/events.h b/lib/events/events.h
new file mode 100644
index 0000000..329dea5
--- /dev/null
+++ b/lib/events/events.h
@@ -0,0 +1,46 @@
+#ifndef LORASA_EVENTS_H
+#define LORASA_EVENTS_H
+
+struct Event;
+enum EventType
+{
+ ALL_EVENTS = 0, // used only at registration time
+ DETECTED,
+ SCAN_TASK_COMPLETE,
+ _MAX_EVENT_TYPE = SCAN_TASK_COMPLETE // 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/include/LoRaBoards.cpp b/lib/loraboards/LoRaBoards.cpp
similarity index 99%
rename from include/LoRaBoards.cpp
rename to lib/loraboards/LoRaBoards.cpp
index 04c93a6..0331489 100644
--- a/include/LoRaBoards.cpp
+++ b/lib/loraboards/LoRaBoards.cpp
@@ -8,6 +8,8 @@
*
*/
+#ifdef LILYGO
+
#include "LoRaBoards.h"
#if defined(HAS_SDCARD)
@@ -927,3 +929,4 @@ bool beginGPS()
return result;
}
#endif
+#endif // #ifdef LILYGO
diff --git a/include/LoRaBoards.h b/lib/loraboards/LoRaBoards.h
similarity index 100%
rename from include/LoRaBoards.h
rename to lib/loraboards/LoRaBoards.h
diff --git a/include/utilities.h b/lib/loraboards/utilities.h
similarity index 99%
rename from include/utilities.h
rename to lib/loraboards/utilities.h
index 1d1b3ef..32a804b 100644
--- a/include/utilities.h
+++ b/lib/loraboards/utilities.h
@@ -486,6 +486,8 @@
#define USING_DIO2_AS_RF_SWITCH
+#elif defined(HELTEC)
+// just to prevent error
#elif defined(T_BEAM_S3_BPF)
#ifndef USING_SX1278
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/lib/scan/scan.cpp b/lib/scan/scan.cpp
new file mode 100644
index 0000000..89ff530
--- /dev/null
+++ b/lib/scan/scan.cpp
@@ -0,0 +1,189 @@
+#ifndef LORASA_CORE_CPP
+#define LORASA_CORE_CPP
+
+#include "scan.h"
+#include
+#include
+#include
+
+uint16_t Scan::rssiMethod(size_t samples, uint16_t *result, size_t res_size)
+{
+ float scale((float)res_size / (HI_RSSI_THRESHOLD - LO_RSSI_THRESHOLD + 0.1));
+
+ memset(result, 0, res_size * sizeof(uint16_t));
+ int result_index = 0;
+
+ //
+ uint16_t max_signal = 65535;
+ // N of samples
+ for (int r = 0; r < samples; r++)
+ {
+ float rssi = getRSSI();
+ if (rssi < -65535)
+ rssi = -65535;
+
+ uint16_t abs_rssi = abs(rssi);
+ if (abs_rssi < max_signal)
+ {
+ max_signal = abs_rssi;
+ }
+ // ToDO: check if 4 is correct value for 33 power bins
+ // Now we have more space because we are ignoring low dB values
+ // we can / 3 default 4
+ if (RSSI_OUTPUT_FORMULA == 1)
+ {
+ result_index =
+ /// still not clear formula but it works
+ uint8_t(abs(rssi) / 4);
+ }
+ else if (RSSI_OUTPUT_FORMULA == 2)
+ {
+ if (rssi > HI_RSSI_THRESHOLD)
+ {
+ rssi = HI_RSSI_THRESHOLD;
+ }
+ else if (rssi < LO_RSSI_THRESHOLD)
+ {
+ rssi = LO_RSSI_THRESHOLD;
+ }
+
+ result_index = uint8_t((HI_RSSI_THRESHOLD - rssi) * scale);
+ }
+
+ if (result_index >= res_size)
+ {
+ // Maximum index possible
+ result_index = res_size - 1;
+ }
+
+ LOG("RSSI: %f IDX: %d\n", rssi, result_index);
+ if (result[result_index] == 0 || result[result_index] > abs_rssi)
+ {
+ result[result_index] = abs_rssi;
+ }
+ }
+
+ return max_signal;
+}
+
+Event Scan::detect(uint16_t *result, bool *filtered_result, size_t result_size,
+ int samples)
+{
+ size_t max_rssi_x = result_size;
+
+ for (int y = 0; y < result_size; y++)
+ {
+
+ LOG("%i:%i,", y, result[y]);
+#if !defined(FILTER_SPECTRUM_RESULTS) || FILTER_SPECTRUM_RESULTS == false
+ if (result[y] && result[y] != 0)
+ {
+ filtered_result[y] = 1;
+ }
+ else
+ {
+ filtered_result[y] = 0;
+ }
+#endif
+
+// if samples low ~1 filter removes all values
+#if FILTER_SPECTRUM_RESULTS
+
+ filtered_result[y] = 0;
+ // Filter Elements without neighbors
+ // if RSSI method actual value is -xxx dB
+ if (result[y] > 0 && samples > 1)
+ {
+ // do not process 'first' and 'last' row to avoid out of index
+ // access.
+ if ((y > 0) && (y < (result_size - 2)))
+ {
+ if (((result[y + 1] != 0) && (result[y + 2] != 0)) ||
+ (result[y - 1] != 0))
+ {
+ filtered_result[y] = 1;
+ // Fill empty pixel
+ result[y + 1] = 1;
+ }
+ else
+ {
+ LOG("Filtered::%i,", y);
+ }
+ }
+ } // not filtering if samples == 1 because it will be filtered
+ else if (result[y] > 0 && samples == 1)
+ {
+ filtered_result[y] = 1;
+ }
+#endif
+ if (filtered_result[y] && max_rssi_x > y)
+ {
+ max_rssi_x = y;
+ }
+ }
+
+ 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;
+}
+
+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];
+ size_t c = listener_count[(size_t)event.type];
+ for (int i = 0; i < c; i++)
+ {
+ list[i]->onEvent(event);
+ }
+
+ list = eventListeners[(size_t)EventType::ALL_EVENTS];
+ c = listener_count[(size_t)EventType::ALL_EVENTS];
+ for (int i = 0; i < c; i++)
+ {
+ list[i]->onEvent(event);
+ }
+}
+
+#endif
diff --git a/lib/scan/scan.h b/lib/scan/scan.h
new file mode 100644
index 0000000..38b610e
--- /dev/null
+++ b/lib/scan/scan.h
@@ -0,0 +1,81 @@
+#include
+#include
+#include
+
+#ifndef LORASA_CORE_H
+
+#define LORASA_CORE_H
+
+#ifdef PRINT_DEBUG
+#define LOG(args...) Serial.printf(args...)
+#define LOG_IF(cond, args...) \
+ if (cond) \
+ LOG(args...)
+#elif !defined(LOG)
+#define LOG(args...)
+#define LOG_IF(cond, args...)
+#endif
+
+// Output Pixel Formula
+// 1 = rssi / 4, 2 = (rssi / 2) - 22 or 20
+constexpr int RSSI_OUTPUT_FORMULA = 2;
+
+// based on the formula for RSSI_OUTPUT_FORMULA == 2
+// -2 * (22 + RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE) < rssi =< -44
+// practice may require a better pair of thresholds
+constexpr float HI_RSSI_THRESHOLD = -44.0;
+constexpr float LO_RSSI_THRESHOLD = HI_RSSI_THRESHOLD - 66;
+
+// number of samples for RSSI method
+#ifndef SAMPLES_RSSI
+#define SAMPLES_RSSI 13 // 21 //
+#endif
+#ifdef USING_SX1280PA
+#define SAMPLES_RSSI 20
+#endif
+
+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;
+ bool animated;
+ float trigger_level;
+
+ Listener **eventListeners[(size_t)EventType::_MAX_EVENT_TYPE + 1];
+ size_t listener_count[(size_t)EventType::_MAX_EVENT_TYPE + 1];
+
+ Scan()
+ : epoch(0), current_frequency(0), fr_begin(0), fr_end(0),
+ drone_detection_level(0), sound_on(false), led_flag(false), detection_count(0),
+ animated(false), trigger_level(0), listener_count{
+ 0,
+ } {};
+
+ virtual float getRSSI() = 0;
+
+ // rssiMethod gets the data similar to the scan method,
+ // but uses getRSSI directly.
+ uint16_t rssiMethod(size_t samples, uint16_t *result, size_t res_size);
+
+ // detect method analyses result, and produces filtered_result, marking
+ // those values that represent a detection event.
+ // It returns index that represents strongest signal at which a detection event
+ // occurred.
+ Event detect(uint16_t *result, bool *filtered_result, size_t result_size,
+ int samples);
+
+ size_t addEventListener(EventType t, Listener &l);
+ size_t addEventListener(EventType t, void cb(void *, Event &), void *arg);
+ void fireEvent(Event &e);
+};
+
+// Remove reading without neighbors
+#define FILTER_SPECTRUM_RESULTS true
+
+#endif
diff --git a/ml_research/Diagram_ML.svg b/ml_research/Diagram_ML.svg
new file mode 100644
index 0000000..f96baa4
--- /dev/null
+++ b/ml_research/Diagram_ML.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/ml_research/datasets.md b/ml_research/datasets.md
new file mode 100644
index 0000000..ebf796a
--- /dev/null
+++ b/ml_research/datasets.md
@@ -0,0 +1,13 @@
+## Overview of ML Dataets
+
+| dataset name | data content | data format | frequencies / Sampling rate | other infos | source paper/website | source data | open questions |
+|----------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
+| Noisy Drone RF Signal Classification (Glüge et al.) | Drones: DJI, FutabaT14, FutabaT7, Graupner, Taranis, Turnigy | I/Q Data as well as generated Spectrograms available | The device can scan a range of 6Ghz. But the newest plot is very weird. +/- 7 Mhz, can this really be? "non-overlapping signal vectors of length of 1048576 samples, which corresponds to approx. 74.9ms at 14MHz" | mixed with either Labnoise (50%) or Gaussian noise (50%). The noise class was created by mixing Labnoise and Gaussian noise in all possible combinations. Several levels of SNR were used over the entire dataset. | https://www.scitepress.org/Link.aspx?doi=10.5220/0012176800003595 https://github.com/sgluege/Noisy-Drone-RF-Signal-Classification https://github.com/sgluege/Noisy-Drone-RF-Signal-Classification-v2/tree/main | https://www.kaggle.com/datasets/sgluege/noisy-drone-rf-signal-classification-v2/data https://www.kaggle.com/datasets/sgluege/noisy-drone-rf-signal-classification | Not sure about the frequencies. |
+| DroneDataset (Swinney and Woods) | Drones: new DJI Mavic 2 Air S, DJI Mavic Pro, DJI Mavic Pro 2, DJI Inspire 2, DJI Mavic Mini, DJI Phantom 4 and the Parrot Disco. | Raw I/Q Data | TODO; Recordings were collected using a Nuand BladeRF SDR and using open source software GNURadio | There are 4 subsets of data included in this dataset, the UAS signals in the presence of Bluetooth interference, in the presence of Wi-Fi signals, in the presence of both and with no interference. 3 flight modes are captured - switched on, hovering and flying. | No paper seen | https://ieee-dataport.org/open-access/dronedetect-dataset-radio-frequency-dataset-unmanned-aerial-system-uas-signals-machine | Sampling rate? |
+| DroneRF (Allahham et al.) | Drones: Bepop; AR; Phantom | "the dataset contains only time series data, and not the complex IQ signals" (From Glüge and not from the authors) - but what does this exactly mean? | capture the whole 2.4GHz bandwidth, we have used 2 RF receivers. Each RF receiver has a maximum instantaneous bandwidth of 40 MHz, so both receivers must be operating simultaneously to at least capture a technology spectrum such as WiFi (i.e. 80 MHz). Recorded using universal soft- ware radio peripheral (USRP) software-defined radio (SDR) transceivers. Signals that could be considered noise in the 2.4 GHz band (Bluetooth, Wi-Fi) were not recorded. | modes, including off, on and connected, hovering, flying, and video recording. | https://www.sciencedirect.com/science/article/pii/S2352340919306675?ref=pdf_download&fr=RR-2&rr=8bf5de727fa35d7f | https://data.mendeley.com/datasets/f4c2b4n755/1 | Apparently time versus db? Not very clear what is in the data |
+| Radio-Frequency Control and Video Signal Recordings of Drones (Vuorenmaa et al.) | Drones: DJI Inspire 2 (2.44 and 5.8 GHz), DJI Matrice 100 (2.44 GHz), DJI Matrice 210 (2.44 and 5.8 GHz), DJI Mavic Mini (2.44 GHz), DJI Mavic Pro (2.44 GHz), DJI Phantom 4 (2.44 GHz), DJI Phantom 4 Pro Plus (2.44 and 5.8 GHz), Parrot Disco (2.44 GHz), Parrot Mambo (2.44 GHz), Yuneec Typhoon H (2.44 and 5.7 GHz) | I/Q Data (And Video data) | TODO: not clear yet | | https://zenodo.org/records/4264467 | | |
+| Spectrogram Dataset (Wicht et al.) | Wi-Fi and Bluetooth signals (NOT DRONES) | Sepctrograms | | | https://www.mdpi.com/2306-5729/7/12/168 | | |
+
+
+## Current Proposal for ML
+
diff --git a/platformio.ini b/platformio.ini
index 0b7648d..e85aa6e 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -9,12 +9,15 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
+default_envs = heltec_wifi_lora_32_V3
; for env:vision-master-e190
; src_dir = tft_src
; for env:vision-master-e290
; src_dir = eink_src
; for env:heltec_wifi_lora_32_V3
; src_dir = src ;;Default
+; for heltec_wifi_lora_32_V3-test-signal-generator
+; src_dir = trans_src
[env:heltec_wifi_lora_32_V3]
platform = espressif32
@@ -23,22 +26,49 @@ framework = arduino
upload_speed = 921600
monitor_speed = 115200
board_build.f_cpu = 240000000
+board_build.filesystem = littlefs
lib_deps =
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
- adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
-build_flags = -DHELTEC_POWER_BUTTON
+ bblanchon/ArduinoJson@^7.2.0
+ ESP Async WebServer
+build_flags =
+ -DHELTEC_POWER_BUTTON
+ -DHELTEC
+
-[env:lilygo-T3S3-v1-2]
+[env:heltec_wifi_lora_32_V3_433]
+platform = espressif32
+board = heltec_wifi_lora_32_V3
+framework = arduino
+upload_speed = 921600
+monitor_speed = 115200
+board_build.f_cpu = 240000000
+board_build.filesystem = littlefs
+lib_deps =
+ ropg/Heltec_ESP32_LoRa_v3@^0.9.1
+ bblanchon/ArduinoJson@^7.2.0
+ ESP Async WebServer
+build_flags =
+ -DHELTEC_POWER_BUTTON
+ -DHELTEC
+ -DFREQ_BEGIN=130
+ -DFREQ_END=180
+ -DRADIOLIB_CHECK_PARAMS=0
+
+[env:lilygo-T3S3-v1-2-sx1262]
platform = espressif32
board = t3_s3_v1_x
framework = arduino
upload_speed = 921600
monitor_speed = 115200
board_build.f_cpu = 240000000
+board_build.filesystem = littlefs
lib_deps =
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
RadioLib
- adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
+ U8g2
+ XPowersLib
+ ESP Async WebServer
build_flags =
-DLILYGO
-DT3_S3_V1_2_SX1262
@@ -51,6 +81,91 @@ build_flags =
-DARDUINO_LILYGO_T3_S3_V1_X
-DARDUINO_USB_MODE=1
+[env:lilygo-T3S3-v1-2-lr1121]
+platform = espressif32
+board = t3_s3_v1_x
+framework = arduino
+upload_speed = 921600
+monitor_speed = 115200
+board_build.f_cpu = 240000000
+board_build.filesystem = littlefs
+lib_deps =
+ ropg/Heltec_ESP32_LoRa_v3@^0.9.1
+ RadioLib
+ U8g2
+ XPowersLib
+ bblanchon/ArduinoJson@^7.2.0
+ ESP Async WebServer
+build_flags =
+ -DLILYGO
+ -DT3_S3_V1_2_LR1121
+ -DT3_V1_3_SX1262
+ -DARDUINO_LILYGO_T3S3_LR1121
+ -DESP32
+ -DSAMPLES_RSSI=5
+ -DUSING_LR1121
+ -DFREQ_BEGIN=2400
+ -DFREQ_END=2500
+ -DARDUINO_ARCH_ESP32
+ -DARDUINO_USB_CDC_ON_BOOT=1
+ -DARDUINO_LILYGO_T3_S3_V1_X
+ -DARDUINO_USB_MODE=1
+
+
+[env:lilygo-T3S3-v1-2-sx1280]
+platform = espressif32
+board = t3_s3_v1_x
+framework = arduino
+upload_speed = 921600
+monitor_speed = 115200
+board_build.f_cpu = 240000000
+board_build.filesystem = littlefs
+lib_deps =
+ ropg/Heltec_ESP32_LoRa_v3@^0.9.1
+ RadioLib
+ U8g2
+ XPowersLib
+ ESP Async WebServer
+build_flags =
+ -DLILYGO
+ -DT3_S3_V1_2_SX1280_PA
+ -DARDUINO_LILYGO_T3S3_SX1280_PA
+ -DESP32
+ -DUSING_SX1280PA
+ -DFREQ_BEGIN=2400
+ -DFREQ_END=2500
+ -DARDUINO_ARCH_ESP32
+ -DARDUINO_USB_CDC_ON_BOOT=1
+ -DARDUINO_LILYGO_T3_S3_V1_X
+ -DARDUINO_USB_MODE=1
+
+[env:lilygo-T3-v1-6-xs1276]
+platform = espressif32
+board = esp32dev
+framework = arduino
+upload_speed = 115200
+monitor_speed = 115200
+board_build.filesystem = littlefs
+lib_deps =
+ ropg/Heltec_ESP32_LoRa_v3@^0.9.1
+ RadioLib
+ U8g2
+ XPowersLib
+ ESP Async WebServer
+build_flags =
+ -DLILYGO
+ -DT3_V1_6_SX1276
+ -DUSING_SX1276
+ -DESP32
+ -DARDUINO_ARCH_ESP32
+ -DARDUINO_USB_CDC_ON_BOOT=0 ;; if not 0 - Serial issue
+ -DARDUINO_LILYGO_T3_V1_6
+ -DARDUINO_USB_MODE=1
+
+;; More old lylygo/titygo boeads defenitions you can find here:
+;; https://github.com/PTR-projects/PTR_GroundStation_firmware/blob/main/platformio.ini
+;; https://github.com/Xinyuan-LilyGO/LilyGo-LoRa-Series/blob/master/platformio.ini
+
[env:heltec_wifi_lora_32_V3-test-signal-generator]
platform = espressif32
board = heltec_wifi_lora_32_V3
@@ -61,8 +176,9 @@ board_build.f_cpu = 240000000
board_build.flash_size = 80000000L
lib_deps =
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
- adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
-build_flags = -DLILYGO
+build_flags =
+ -DHELTEC
+ -DHELTEC_POWER_BUTTON
[env:vision-master-e290]
platform = espressif32
@@ -71,7 +187,8 @@ framework = arduino
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
board_upload.use_1200bps_touch = true
-build_flags =
+build_flags =
+ -DHELTEC
-DHELTEC_BOARD=37
-DSLOW_CLK_TPYE=1
-DARDUINO_USB_CDC_ON_BOOT=1
@@ -95,7 +212,6 @@ lib_deps =
https://github.com/HelTecAutomation/Heltec_ESP32/
adafruit/Adafruit GFX Library@^1.11.10
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
- adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
[env:vision-master-t190]
platform = espressif32
@@ -105,6 +221,7 @@ monitor_speed = 115200
monitor_filters = esp32_exception_decoder
board_upload.use_1200bps_touch = true
build_flags =
+ -DHELTEC
-DHELTEC_BOARD=38
-DSLOW_CLK_TPYE=1
-DARDUINO_USB_CDC_ON_BOOT=1
@@ -129,3 +246,6 @@ lib_deps =
adafruit/Adafruit GFX Library@^1.11.10
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
+
+[env:native]
+platform = native
diff --git a/src/main.cpp b/src/main.cpp
index acfc13b..76803a8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -21,28 +21,58 @@
https://jgromes.github.io/RadioLib/
*/
-// #define HELTEC_NO_DISPLAY
+// #define HELTEC_NO_DISPLAY
#include
+#ifdef HELTEC
+#include
+#endif
+#include "FS.h"
+#include
+#include
+#include
+#include
+#include
+#include
-// #define OSD_ENABLED true
-// #define WIFI_SCANNING_ENABLED true
-// #define BT_SCANNING_ENABLED true
+#include "WIFI_SERVER.h"
+
+#define FORMAT_LITTLEFS_IF_FAILED true
+
+// #define OSD_ENABLED true
+// #define WIFI_SCANNING_ENABLED true
+// #define BT_SCANNING_ENABLED true
+
+// Direct access to the low-level SPI communication between RadioLib and the radio module.
+#define RADIOLIB_LOW_LEVEL (1)
+// In this mode, all methods and member variables of all RadioLib classes will be made
+// public and so will be exposed to the user. This allows direct manipulation of the
+// library internals.
+#define RADIOLIB_GODMODE (1)
+#define RADIOLIB_CHECK_PARAMS (0)
+
+#include
+#include
+#include
+#include
#ifndef LILYGO
#include
// This file contains a binary patch for the SX1262
#include "modules/SX126x/patches/SX126x_patch_scan.h"
-#elif defined(LILYGO)
+#endif // end ifndef LILYGO
+
+#if defined(LILYGO)
// LiLyGO device does not support the auto download mode, you need to get into the
// download mode manually. To do so, press and hold the BOOT button and then press the
// RESET button once. After that release the BOOT button. Or OFF->ON together with BOOT
// Default LilyGO code
-#include "utilities.h"
-// Our Code
-#include "LiLyGo.h"
+#include
+// #include "utilities.h"
+// Our Code
+#include "LiLyGo.h"
#endif // end LILYGO
#define BT_SCAN_DELAY 60 * 1 * 1000
@@ -62,58 +92,6 @@ uint64_t bt_start = 0;
#include "DFRobot_OSD.h"
#define OSD_SIDE_BAR true
-static constexpr uint16_t levels[10] = {
- 0x105, // 0
- 0x10E, // 1
- 0x10D, // 2
- 0x10C, // 3
- 0x10B, // 4
- 0x10A, // 5
- 0x109, // 6
- 0x108, // 7
- 0x107, // 8
- 0x106, // 9
-};
-
-static constexpr uint16_t power_level[MAX_POWER_LEVELS + 1] = {
- 0x10E, // 0
- 0x10E, // 1
- 0x10D, // 2
- 0x10C, // 3
- 0x10B, // 4
- 0x10A, // 5
- 0x109, // 6
- 0x108, // 7
- 0x107, // 8
- 0x106, // 9 not using 106 to accent rise
- // new line
- 0x10E, // 10
- 0x10D, // 11
- 0x10C, // 12
- 0x10B, // 13
- 0x10A, // 14
- 0x109, // 15
- 0x108, // 16
- 0x107, // 17
- 0x106, // 18 not using 106
- // new line
- 0x10E, // 19
- 0x10D, // 20
- 0x10C, // 21
- 0x10B, // 22
- 0x10A, // 23
- 0x109, // 24
- 0x108, // 25
- 0x107, // 26
- 0x106, // 27
- 0x105, // 28 ---
- 0x105, // 29
- 0x105, // 30
- 0x105, // 31
- 0x105, // 32
- 0x105 // 33
-};
-
// SPI pins
#define OSD_CS 47
#define OSD_MISO 33
@@ -138,11 +116,6 @@ int global_counter = 0;
DFRobot_OSD osd(OSD_CS);
#endif
-/*Define Custom characters Example*/
-static const int buf0[36] = {0x02, 0x80, 0x02, 0x40, 0x7F, 0xE0, 0x42, 0x00,
- 0x42, 0x00, 0x7A, 0x40, 0x4A, 0x40, 0x4A, 0x80,
- 0x49, 0x20, 0x5A, 0xA0, 0x44, 0x60, 0x88, 0x20};
-
#include "global_config.h"
#include "ui.h"
@@ -156,13 +129,15 @@ typedef enum
METHOD_SPECTRAL
} TSCAN_METOD_ENUM;
+// #define SCAN_METHOD METHOD_SPECTRAL
+
#define SCAN_METHOD
// #define METHOD_SPECTRAL // Spectral scan method
#define METHOD_RSSI // Uncomment this and comment METHOD_SPECTRAL fot RSSI
// Output Pixel Formula
// 1 = rssi / 4, 2 = (rssi / 2) - 22 or 20
-constexpr int RSSI_OUTPUT_FORMULA = 2;
+// constexpr int RSSI_OUTPUT_FORMULA = 2;
// Feature to scan diapasones. Other frequency settings will be ignored.
// int SCAN_RANGES[] = {850890, 920950};
@@ -170,16 +145,20 @@ int SCAN_RANGES[] = {};
// MHZ per page
// to put everything into one page set RANGE_PER_PAGE = FREQ_END - 800
-uint64_t RANGE_PER_PAGE = FREQ_END - FREQ_BEGIN; // FREQ_END - FREQ_BEGIN
+uint64_t RANGE_PER_PAGE; // FREQ_END - CONF_FREQ_BEGIN
+
+uint64_t CONF_FREQ_END, CONF_FREQ_BEGIN; // To Enable Multi Screen scan
+// uint64_t RANGE_PER_PAGE = 50;
+// Default Range on Menu Button Switch
// multiplies STEPS * N to increase scan resolution.
#define SCAN_RBW_FACTOR 2
-constexpr int OSD_PIXELS_PER_CHAR = (STEPS * SCAN_RBW_FACTOR) / OSD_CHART_WIDTH;
+#ifdef USING_SX1280PA
+#define SCAN_RBW_FACTOR 2
+#endif
-// To Enable Multi Screen scan
-// uint64_t RANGE_PER_PAGE = 50;
-// Default Range on Menu Button Switch
+constexpr int OSD_PIXELS_PER_CHAR = (STEPS * SCAN_RBW_FACTOR) / OSD_CHART_WIDTH;
#define DEFAULT_RANGE_PER_PAGE 50
@@ -190,43 +169,30 @@ bool ANIMATED_RELOAD = false;
#define UP_FILTER 5
// Trim low signals - nose level
#define START_LOW 6
-// Remove reading without neighbors
-#define FILTER_SPECTRUM_RESULTS true
#define FILTER_SAMPLES_MIN
constexpr bool DRAW_DETECTION_TICKS = true;
-
+int16_t max_x_rssi[STEPS] = {999};
+int16_t max_x_window[STEPS / 14] = {999};
+int x_window = 0;
+constexpr int WINDOW_SIZE = 15;
// Number of samples for each frequency scan. Fewer samples = better temporal resolution.
// if more than 100 it can freeze
#define SAMPLES 35 //(scan time = 1294)
-// number of samples for RSSI method
-#define SAMPLES_RSSI 20 // 21 //
-#define RANGE (int)(FREQ_END - FREQ_BEGIN)
-
-#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 RANGE, range, iterations, median_frequency;
+float SINGLE_STEP;
// #define DISABLE_PLOT_CHART false // unused
// Array to store the scan results
uint16_t result[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE];
-uint16_t result_display_set[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE];
-uint16_t result_detections[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE];
-uint16_t filtered_result[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE];
+
+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
@@ -235,11 +201,10 @@ bool first_run, new_pixel, detected_x = false;
// drone detection flag
bool detected = false;
uint64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL;
+#define TRIGGER_LEVEL -80.0
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
@@ -251,24 +216,27 @@ uint64_t scan_time = 0;
uint64_t scan_start_time = 0;
#endif
+// log data via serial console, JSON format:
+// #define LOG_DATA_JSON true
+int LOG_DATA_JSON_INTERVAL = 1000; // Log at least every second
+
uint64_t x, y, range_item, w = WATERFALL_START, i = 0;
int osd_x = 1, osd_y = 2, col = 0, max_bin = 32;
uint64_t ranges_count = 0;
-float freq = 0;
int rssi = 0;
int state = 0;
+int CONF_SAMPLES;
#ifdef METHOD_SPECTRAL
-constexpr int samples = SAMPLES;
+int samples = SAMPLES;
#endif
#ifdef METHOD_RSSI
-constexpr int samples = SAMPLES_RSSI;
+int samples = SAMPLES_RSSI;
#endif
uint8_t result_index = 0;
uint8_t button_pressed_counter = 0;
-uint64_t loop_cnt = 0;
#ifndef LILYGO
// #define JOYSTICK_ENABLED
@@ -337,8 +305,8 @@ void osdProcess()
// memset(max_step_range, 33, 30);
max_bin = 32;
- osd.displayString(12, 1, String(FREQ_BEGIN));
- osd.displayString(12, OSD_WIDTH - 8, String(FREQ_END));
+ osd.displayString(12, 1, String(CONF_FREQ_BEGIN));
+ osd.displayString(12, OSD_WIDTH - 8, String(CONF_FREQ_END));
// Finding biggest in result
// Skiping 0 and 32 31 to avoid overflow
for (int i = 1; i < MAX_POWER_LEVELS - 3; i++)
@@ -375,7 +343,7 @@ void osdProcess()
#ifdef OSD_SIDE_BAR
{
osd.displayString(col, OSD_WIDTH - 7,
- String(FREQ_BEGIN + (col * osd_mhz_in_bin)) + "-" +
+ String(CONF_FREQ_BEGIN + (col * osd_mhz_in_bin)) + "-" +
String(max_step_range) + " ");
}
#endif
@@ -396,18 +364,59 @@ void osdProcess()
}
#endif
+struct RadioScan : Scan
+{
+ float getRSSI() override;
+};
+
+float RadioScan::getRSSI()
+{
+#if defined(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));
+
+#elif defined(USING_LR1121)
+ // Try getRssiInst
+ float rssi;
+ radio.getRssiInst(&rssi);
+ // pass the replies
+ return rssi;
+#else
+ return radio.getRSSI(false);
+#endif
+}
+
+RadioScan r;
+
+#define WATERFALL_SENSITIVITY 0.05
+DecoratedBarChart *bar;
+WaterfallChart *waterChart;
+StackedChart stacked(display, 0, 0, 0, 0);
+
+UptimeClock *uptime;
+
void init_radio()
{
// initialize SX1262 FSK modem at the initial frequency
both.println("Init radio");
- state == radio.beginFSK(FREQ_BEGIN);
-
+#if defined(USING_SX1280PA)
+ state = radio.beginGFSK(CONF_FREQ_BEGIN);
+#elif defined(USING_LR1121)
+ state = radio.beginGFSK(CONF_FREQ_BEGIN, 4.8F, 5.0F, 156.2F, 10, 16U, 1.7F);
+#else
+ state = radio.beginFSK(CONF_FREQ_BEGIN);
+#endif
if (state == RADIOLIB_ERR_NONE)
{
Serial.println(F("success!"));
}
else
{
+ display.println("Error:" + String(state));
Serial.print(F("failed, code "));
Serial.println(state);
while (true)
@@ -429,20 +438,183 @@ void init_radio()
#endif
both.println("Setting up radio");
+#ifdef USING_SX1280PA
+ // RADIOLIB_OR_HALT(radio.setBandwidth(RADIOLIB_SX128X_LORA_BW_406_25));
+#elif USING_SX1276
+ // Receiver bandwidth in kHz. Allowed values
+ // are 2.6, 3.1, 3.9, 5.2, 6.3, 7.8, 10.4, 12.5, 15.6, 20.8, 25, 31.3, 41.7,
+ // 50, 62.5, 83.3, 100, 125, 166.7, 200 and 250 kHz.
+ RADIOLIB_OR_HALT(radio.setRxBandwidth(250));
+#else
RADIOLIB_OR_HALT(radio.setRxBandwidth(BANDWIDTH));
+#endif
// and disable the data shaping
- RADIOLIB_OR_HALT(radio.setDataShaping(RADIOLIB_SHAPING_NONE));
+ state = radio.setDataShaping(RADIOLIB_SHAPING_NONE);
+ if (state != RADIOLIB_ERR_NONE)
+ {
+ Serial.println("Error:setDataShaping:" + String(state));
+ }
both.println("Starting scanning...");
- // calibrate only once ,,, at startup
- // TODO: check documentation (9.2.1) if we must calibrate in certain ranges
- radio.setFrequency(FREQ_BEGIN, true);
+// calibrate only once ,,, at startup
+// TODO: check documentation (9.2.1) if we must calibrate in certain ranges
+#ifdef USING_SX1280PA
+ state = radio.setFrequency(CONF_FREQ_BEGIN);
+ if (state != RADIOLIB_ERR_NONE)
+ {
+ Serial.println("Error:setFrequency:" + String(state));
+ }
+ state = radio.startReceive();
+ if (state != RADIOLIB_ERR_NONE)
+ {
+ Serial.println("Error:startReceive:" + String(state));
+ }
+#elif USING_SX1276
+ // Sets carrier frequency. Allowed values range from 137.0 MHz to 1020.0 MHz.
+ radio.setFrequency(CONF_FREQ_BEGIN);
+#else
+ radio.setFrequency(CONF_FREQ_BEGIN, true);
+#endif
+
delay(50);
}
+struct frequency_scan_result
+{
+ uint64_t begin;
+ uint64_t end;
+ uint64_t last_epoch;
+ int16_t rssi; // deliberately not a float; floats can pin task to wrong core forever
+} frequency_scan_result;
+
+TaskHandle_t logToSerial = NULL;
+
+void eventListenerForMSP(void *arg, Event &e)
+{
+ if (e.type == EventType::DETECTED)
+ {
+ if (e.epoch != frequency_scan_result.last_epoch ||
+ e.detected.rssi > frequency_scan_result.rssi)
+ {
+ frequency_scan_result.last_epoch = e.epoch;
+ frequency_scan_result.rssi = e.detected.rssi;
+ }
+
+ return;
+ }
+
+ if (e.type == EventType::SCAN_TASK_COMPLETE)
+ {
+ // notify async communication that the data is ready
+ if (logToSerial != NULL)
+ {
+ xTaskNotifyGive(logToSerial);
+ }
+ return;
+ }
+}
+
+void logToSerialTask(void *parameter)
+{
+#ifdef HELTEC
+ JsonDocument doc;
+ char jsonOutput[200];
+#endif
+
+ uint64_t last_epoch = frequency_scan_result.last_epoch;
+ frequency_scan_result.rssi = -999;
+
+ for (;;)
+ {
+ ulTaskNotifyTake(true, pdMS_TO_TICKS(LOG_DATA_JSON_INTERVAL));
+ if (frequency_scan_result.begin != frequency_scan_result.end ||
+ frequency_scan_result.last_epoch != last_epoch)
+ {
+ int16_t highest_value_scanned = frequency_scan_result.rssi;
+ frequency_scan_result.rssi = -999;
+ last_epoch = frequency_scan_result.last_epoch;
+ if (highest_value_scanned == -999)
+ {
+ continue;
+ }
+
+#ifdef HELTEC
+ doc["low_range_freq"] = frequency_scan_result.begin;
+ doc["high_range_freq"] = frequency_scan_result.end;
+ doc["value"] = String(highest_value_scanned);
+
+ serializeJson(doc, jsonOutput);
+ Serial.println(jsonOutput);
+#else
+ Serial.printf("{\"low_range_freq\": %" PRIu64
+ ", \"high_range_freq\": %" PRIu64 ", "
+ "\"value\": \"%" PRIi16 "\"}\n",
+ frequency_scan_result.begin, frequency_scan_result.end,
+ highest_value_scanned);
+#endif
+ }
+ }
+}
+
+void drone_sound_alarm(void *arg, Event &e);
+
+void readConfigFile()
+{
+ // writeFile(LittleFS, "/text.txt", "{WIFI:{name:\"sdfsdf\", Password:\"sdfsdf\"}");
+ ssid = readParameterFromParameterFile(SSID);
+ Serial.println("SSID: " + ssid);
+
+ pass = readParameterFromParameterFile(PASS);
+ Serial.println("PASS: " + pass);
+
+ ip = readParameterFromParameterFile(IP);
+ Serial.println("PASS: " + ip);
+
+ gateway = readParameterFromParameterFile(GATEWAY);
+ Serial.println("GATEWAY: " + gateway);
+
+ fstart = readParameterFromParameterFile(FSTART);
+ Serial.println("FSTART: " + fstart);
+
+ fend = readParameterFromParameterFile(FEND);
+ Serial.println("FEND: " + fend);
+
+ smpls = readParameterFromParameterFile("samples");
+ Serial.println("SAMPLES: " + smpls);
+
+ CONF_SAMPLES = (smpls == "") ? samples : atoi(smpls.c_str());
+ samples = CONF_SAMPLES;
+ CONF_FREQ_BEGIN = (fstart == "") ? FREQ_BEGIN : atoi(fstart.c_str());
+ CONF_FREQ_END = (fend == "") ? FREQ_END : atoi(fend.c_str());
+
+ both.println("C FREQ BEGIN:" + String(CONF_FREQ_BEGIN));
+ both.println("C FREQ END:" + String(CONF_FREQ_END));
+ both.println("C SAMPLES:" + String(CONF_SAMPLES));
+
+ RANGE_PER_PAGE = CONF_FREQ_END - CONF_FREQ_BEGIN; // FREQ_END - CONF_FREQ_BEGIN
+
+ RANGE = (int)(CONF_FREQ_END - CONF_FREQ_BEGIN);
+
+ SINGLE_STEP = (float)(RANGE / (STEPS * SCAN_RBW_FACTOR));
+
+ range = (int)(CONF_FREQ_END - CONF_FREQ_BEGIN);
+
+ iterations = RANGE / RANGE_PER_PAGE;
+
+ // uint64_t range_frequency = FREQ_END - CONF_FREQ_BEGIN;
+ median_frequency = (CONF_FREQ_BEGIN + CONF_FREQ_END) / 2;
+}
+
void setup(void)
{
+
+#ifdef LILYGO
+ setupBoards(); // true for disable U8g2 display library
+ delay(500);
+ Serial.println("Setup LiLyGO board is done");
+#endif
+
// LED brightness
heltec_led(25);
#ifdef OSD_ENABLED
@@ -462,7 +634,6 @@ void setup(void)
#endif
float vbat;
float resolution;
- loop_cnt = 0;
bt_start = millis();
wf_start = millis();
@@ -470,6 +641,7 @@ void setup(void)
pinMode(BUZZER_PIN, OUTPUT);
pinMode(REB_PIN, OUTPUT);
heltec_setup();
+
#ifdef JOYSTICK_ENABLED
calibrate_joy();
pinMode(JOY_BTN_PIN, INPUT_PULLUP);
@@ -481,7 +653,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);
@@ -489,10 +661,46 @@ void setup(void)
}
}
+ display.clear();
+
+ both.println("CLICK for WIFI settings.");
+
+ for (int i = 0; i < 200; i++)
+ {
+ both.print(".");
+
+ button.update();
+ delay(10);
+ if (button.pressedNow())
+ {
+ both.println("-----------");
+ both.println("Starting WIFI-SERVER...");
+ // Error here: E (15752) ledc: ledc_get_duty(745): LEDC is not initialized
+ tone(BUZZER_PIN, 205, 100);
+ delay(50);
+ tone(BUZZER_PIN, 205, 500);
+ tone(BUZZER_PIN, 205, 100);
+ delay(50);
+
+ serverStart();
+ both.println("Ready to Connect: 192.168.4.1");
+ delay(600);
+ break;
+ }
+ }
+ both.print("\n");
+
+ both.println("Init File System");
+ initLittleFS();
+
+ readConfigFile();
+
init_radio();
+
#ifndef LILYGO
vbat = heltec_vbat();
both.printf("V battery: %.2fV (%d%%)\n", vbat, heltec_battery_percent(vbat));
+ delay(1000);
#endif // end not LILYGO
#ifdef WIFI_SCANNING_ENABLED
WiFi.mode(WIFI_STA);
@@ -504,7 +712,7 @@ void setup(void)
delay(400);
display.clear();
- resolution = RANGE / (STEPS * SCAN_RBW_FACTOR);
+ resolution = (float)RANGE / (STEPS * SCAN_RBW_FACTOR);
single_page_scan = (RANGE_PER_PAGE == range);
@@ -567,7 +775,12 @@ void setup(void)
#ifdef METHOD_RSSI
// TODO: try RADIOLIB_SX126X_RX_TIMEOUT_INF
+#ifdef USING_SX1280PA
+ state = radio.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_NONE);
+#else
state = radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_NONE);
+#endif
+
if (state != RADIOLIB_ERR_NONE)
{
Serial.print(F("Failed to start receive mode, error code: "));
@@ -582,6 +795,52 @@ void setup(void)
#ifdef OSD_ENABLED
osd.clear();
#endif
+
+#ifdef LOG_DATA_JSON
+ xTaskCreate(logToSerialTask, "LOG_DATA_JSON", 2048, NULL, 1, &logToSerial);
+#endif
+
+ r.trigger_level = TRIGGER_LEVEL;
+ stacked.reset(0, 0, display.width(), display.height());
+
+ bar = new DecoratedBarChart(display, 0, 0, display.width(), 0, CONF_FREQ_BEGIN,
+ CONF_FREQ_END, LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD,
+ r.trigger_level);
+
+ 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);
+ model->reset(millis(), display.width());
+
+ delete[] multiples;
+
+ waterChart = new WaterfallChart(display, 0, WATERFALL_START, display.width(), 0,
+ CONF_FREQ_BEGIN, CONF_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);
+ stacked.setHeight(b, stacked.height);
+
+ r.addEventListener(DETECTED, bar->bar);
+ r.addEventListener(DETECTED, drone_sound_alarm, &r);
+ r.addEventListener(SCAN_TASK_COMPLETE, stacked);
+
+ r.addEventListener(ALL_EVENTS, eventListenerForMSP, NULL);
+
+#ifdef UPTIME_CLOCK
+ uptime = new UptimeClock(display, millis());
+#endif
}
// Formula to translate 33 bin to approximate RSSI value
@@ -591,10 +850,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()
@@ -602,88 +860,105 @@ 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
)
{
- delay(10);
- // Print Curent frequency
- display.setTextAlignment(TEXT_ALIGN_CENTER);
- display.drawString(128 / 2, 0, String(freq));
- display.display();
- 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;
}
-void drone_sound_alarm(int drone_detection_level, int detection_count)
+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)
+{
+ 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...
if (drone_detection_level <= 25)
{
- if (detection_count == 1 && SOUND_ON)
+
+ if (tone_freq_db != 205)
{
- tone(BUZZER_PIN, 205,
+ tone_freq_db = 285 - tone_freq_db;
+ }
+
+ 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, 205,
+ tone(BUZZER_PIN, tone_freq_db,
10); // same action ??? but every 5th time
}
}
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
@@ -697,7 +972,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);
@@ -705,7 +980,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);
@@ -713,7 +988,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);
@@ -750,42 +1025,41 @@ void check_ranges()
single_page_scan = false;
}
}
-// MAX Frequency RSSI value of the samples
+
+// MAX Frequency RSSI BIN value of the samples
int max_rssi_x = 999;
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
- scan_time = 0;
-
- // general purpose loop counter
- loop_cnt++;
-
+// reset scan time
#ifdef PRINT_PROFILE_TIME
+ scan_time = 0;
loop_start = millis();
#endif
+ r.epoch++;
if (!ANIMATED_RELOAD || !single_page_scan)
{
// clear the scan plot rectangle
UI_clearPlotter();
+ UI_clearTopStatus();
}
// do the scan
- range = FREQ_END - FREQ_BEGIN;
+ range = CONF_FREQ_END - CONF_FREQ_BEGIN;
if (RANGE_PER_PAGE > range)
{
RANGE_PER_PAGE = range;
}
- fr_begin = FREQ_BEGIN;
- fr_end = fr_begin;
+ r.fr_begin = CONF_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
@@ -807,14 +1081,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
@@ -825,11 +1099,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);
@@ -860,25 +1129,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;
-#ifdef PRINT_DEBUG
- Serial.println("setFrequency:" + String(freq));
-#endif
+ r.current_frequency = r.fr_begin + step;
+ LOG("setFrequency:%f\n", r.current_frequency);
-#ifdef LILYGO
- state = radio.setFrequency(freq, false); // false = no calibration need here
+#ifdef USING_SX1280PA
+ 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,
+ true); // true = no calibration need here
#endif
int radio_error_count = 0;
if (state != RADIOLIB_ERR_NONE)
{
- display.drawString(0, 64 - 10, "E:setFrequency:" + String(freq));
- // display.drawString(0, 64 - 10, "E:setFrequency:" + String(freq));
- Serial.println("E: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++;
@@ -886,9 +1159,7 @@ void loop(void)
continue;
}
-#ifdef PRINT_DEBUG
- Serial.printf("Step:%d Freq: %f\n", x, freq);
-#endif
+ LOG("Step:%d Freq: %f\n", x, r.current_frequency);
// SpectralScan Method
#ifdef METHOD_SPECTRAL
{
@@ -919,59 +1190,20 @@ void loop(void)
#ifdef METHOD_RSSI
// Spectrum analyzer using getRSSI
{
-#ifdef PRINT_DEBUG
- Serial.println("METHOD RSSI");
-#endif
- // memset
- // memset(result, 0, RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE);
- // Some issues with memset function
- for (i = 0; i < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE; i++)
- {
- result[i] = 0;
- }
- result_index = 0;
- // N of samples
- for (int r = 0; r < SAMPLES_RSSI; r++)
- {
- rssi = radio.getRSSI(false);
- // ToDO: check if 4 is correct value for 33 power bins
- // Now we have more space because we are ignoring low dB values
- // we can / 3 default 4
- if (RSSI_OUTPUT_FORMULA == 1)
- {
- result_index =
- /// still not clear formula but it works
- uint8_t(abs(rssi) / 4);
- }
- else if (RSSI_OUTPUT_FORMULA == 2)
- {
- // I like this formula better
- result_index = uint8_t(abs(rssi) / 2) - 22;
- }
+ LOG("METHOD RSSI");
+ uint16_t max_rssi = r.rssiMethod(CONF_SAMPLES, result,
+ RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE);
-#ifdef PRINT_DEBUG
- Serial.printf("RSSI: %d IDX: %d\n", rssi, result_index);
-#endif
- // avoid buffer overflow
- if (result_index < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE)
- {
- // Saving max value only rss is negative so smaller is bigger
- if (result[result_index] > rssi)
- {
- result[result_index] = rssi;
- }
- }
- else
- {
- Serial.print("Out-of-Range: result_index %d\n");
- }
+ if (max_x_rssi[display_x] > max_rssi)
+ {
+ max_x_rssi[display_x] = max_rssi;
}
}
#endif // SCAN_METHOD == METHOD_RSSI
// if this code is not executed LORA radio doesn't work
// basically SX1262 requires delay
- // osd.displayString(12, 1, String(FREQ_BEGIN));
+ // osd.displayString(12, 1, String(CONF_FREQ_BEGIN));
// osd.displayString(12, 30 - 8, String(FREQ_END));
// delay(2);
@@ -986,158 +1218,65 @@ void loop(void)
display.setColor(WHITE);
}
#endif
- detected = false;
- detected_y[display_x] = false;
- max_rssi_x = 999;
+ Event event = r.detect(result, filtered_result,
+ RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE, samples);
+ event.time_ms = millis();
- for (y = 0; y < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE; y++)
+ size_t detected_at = event.detected.detected_at;
+ if (max_rssi_x > detected_at)
{
+ // MAx bin Value not RSSI
+ max_rssi_x = detected_at;
+ }
-#ifdef PRINT_DEBUG
- Serial.print(String(y) + ":");
- Serial.print(String(result[y]) + ",");
-#endif
-#if !defined(FILTER_SPECTRUM_RESULTS) || FILTER_SPECTRUM_RESULTS == false
- if (result[y] && result[y] != 0)
- {
- filtered_result[y] = 1;
- }
- else
- {
- filtered_result[y] = 0;
- }
-#endif
+ detected = event.detected.detected;
+ detected_y[display_x] = false;
-// if samples low ~1 filter removes all values
-#if FILTER_SPECTRUM_RESULTS
+ float rr = event.detected.rssi;
+ r.drone_detection_level = drone_detection_level;
- filtered_result[y] = 0;
- // Filter Elements without neighbors
- // if RSSI method actual value is -xxx dB
- if (result[y] > 0 && samples > 1)
- {
- // do not process 'first' and 'last' row to avoid out of index
- // access.
- if ((y > 0) && (y != (RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE - 3)))
- {
- if (((result[y + 1] != 0) && (result[y + 2] != 0)) ||
- (result[y - 1] != 0))
- {
- filtered_result[y] = 1;
- }
- else
- {
-#ifdef PRINT_DEBUG
- Serial.print("Filtered:" + String(x) + ":" + String(y) + ",");
-#endif
- }
- }
- } // not filtering if samples == 1
- else if (result[y] > 0 && samples == 1)
- {
- filtered_result[y] = 1;
- }
-
-#endif
+ if (event.detected.trigger)
+ {
// check if we should alarm about a drone presence
- if ((filtered_result[y] == 1) // we have some data and
- && (y <= drone_detection_level) &&
- detected_y[display_x] == false) // detection threshold match
+ 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_sound_alarm(drone_detection_level, detection_count);
- }
+ drone_detected_frequency_end = r.current_frequency;
+#ifdef LOG_DATA_JSON
+ frequency_scan_result.begin = drone_detected_frequency_start;
+ frequency_scan_result.end = drone_detected_frequency_end;
+#endif
if (DRAW_DETECTION_TICKS == true)
{
- // draw vertical line on top of display for "drone detected"
- // frequencies
+// draw vertical line on top of display for "drone detected"
+// frequencies
+#ifdef METHOD_SPECTRAL
if (!detected_y[display_x])
{
display.drawLine(display_x, 1, display_x, 4);
detected_y[display_x] = true;
}
- }
- }
-#if (WATERFALL_ENABLED == true)
- if ((filtered_result[y] == 1) && (y <= drone_detection_level) &&
- (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
- // next 2 If's ... adds !!!! 10ms of runtime ......tfk ???
- if (filtered_result[y] == 1)
- {
-#ifdef PRINT_DEBUG
- Serial.print("Pixel:" + String(display_x) + "(" + String(x) + ")" +
- ":" + String(y) + ",");
-#endif
- if (max_rssi_x > y)
- {
- max_rssi_x = y;
}
- // Set signal level pixel
- if (y < MAX_POWER_LEVELS - START_LOW)
- {
- display.setPixel(display_x, y + START_LOW);
- }
- if (!detected)
- {
- detected = true;
- }
- }
-
- // -------------------------------------------------------------
- // Draw "Detection Level line" every 2 pixel
- // -------------------------------------------------------------
- if ((y == drone_detection_level) && (display_x % 2 == 0))
- {
- display.setColor(WHITE);
- if (filtered_result[y] == 1)
- {
- display.setColor(INVERSE);
- }
- display.setPixel(display_x, y + START_LOW);
- // display.setPixel(display_x, y + START_LOW - 1); // 2 px wide
-
- display.setColor(WHITE);
}
}
+ r.fireEvent(event);
+
#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));
@@ -1147,26 +1286,76 @@ void loop(void)
#ifdef PRINT_PROFILE_TIME
scan_time += (millis() - scan_start_time);
#endif
- // count detected
- if (detected)
- {
- detection_count++;
- }
-
#ifdef PRINT_DEBUG
Serial.println("....\n");
#endif
- if (first_run || ANIMATED_RELOAD)
+ if (r.animated)
{
display.display();
}
-// LiLyGo doesn't have button ;(
-// ToDO: Check if we use BOOT button
-#ifndef LILYGO
- if (buttonPressHandler(freq) == false)
- break;
-#endif // END LILYGO
+ 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
@@ -1214,16 +1403,16 @@ 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);
+ Event event(r, SCAN_TASK_COMPLETE, millis());
+ r.fireEvent(event);
}
-#endif
// Render display data here
+
+#ifdef UPTIME_CLOCK
+ uptime->draw(millis());
+#endif
display.display();
#ifdef OSD_ENABLED
// Sometimes OSD prints entire screen with the digits.
@@ -1242,13 +1431,13 @@ void loop(void)
#endif
}
#ifdef PRINT_DEBUG
- // Serial.println("----");
+// Serial.println("----");
#endif
- loop_time = millis() - loop_start;
joy_btn_clicked = false;
#ifdef PRINT_PROFILE_TIME
+ loop_time = millis() - loop_start;
Serial.printf("LOOP: %lld ms; SCAN: %lld ms;\n ", loop_time, scan_time);
#endif
// No WiFi and BT Scan Without OSD
diff --git a/src/ui.cpp b/src/ui.cpp
index 48a7bf1..77bdc58 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,11 @@
//
#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 uint64_t CONF_FREQ_BEGIN;
+extern uint64_t CONF_FREQ_END;
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,225 +29,139 @@ 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, 0, STEPS, HEIGHT);
- display_instance->setColor(WHITE);
+ // display_instance->setColor(BLACK);
+ // display_instance->fillRect(0, 10, STEPS, HEIGHT - 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)
+void UI_clearTopStatus(void)
{
- 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
- }
+ // clear the scan plot rectangle (top part)
+ // 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)
{
- // 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));
+ 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(CONF_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(CONF_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, 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);
}
@@ -266,39 +169,29 @@ 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(CONF_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(CONF_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));
}
}
- 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;
}
diff --git a/test/test_rssi.cpp b/test/test_rssi.cpp
new file mode 100644
index 0000000..a38d166
--- /dev/null
+++ b/test/test_rssi.cpp
@@ -0,0 +1,67 @@
+#include
+#define LOG(args...) printf(args)
+#include "../lib/scan/scan.cpp"
+#include
+
+struct TestScan : Scan
+{
+ TestScan(float *ctx, int sz) : ctx(ctx), sz(sz), idx(0) {}
+
+ float getRSSI() override;
+
+ float *ctx;
+ int sz;
+ int idx;
+};
+
+float TestScan::getRSSI()
+{
+ if (idx >= sz)
+ {
+ return -1000000;
+ }
+
+ return ctx[idx++];
+}
+
+constexpr int test_sz = 13;
+constexpr int inputs_sz = 13;
+
+void test_rssi(void)
+{
+ uint16_t samples[test_sz];
+ float inputs[inputs_sz] = {-40.0, -100.0, -200.0, -50.0, -400.0, -60.0, -20,
+ -75.5, -70, -80, -90, -55.9, -110};
+
+ TestScan t = TestScan(inputs, inputs_sz);
+
+ uint16_t r = t.rssiMethod(inputs_sz, samples, test_sz);
+
+ uint16_t expect[test_sz] = {20, 50, 55, 60, 0, 70, 75, 80, 0, 90, 0, 100, 110};
+
+ TEST_ASSERT_EQUAL_INT16(20, r);
+ TEST_ASSERT_EQUAL_INT16_ARRAY(expect, samples, test_sz);
+}
+
+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];
+
+ 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);
+
+ 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};
+
+ TEST_ASSERT_EQUAL_INT16(1, r);
+ TEST_ASSERT_EQUAL_INT8_ARRAY(expect2, result, test_sz);
+}
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();
+}
diff --git a/tft_src/main.cpp b/tft_src/main.cpp
index 9eae332..ac220ef 100644
--- a/tft_src/main.cpp
+++ b/tft_src/main.cpp
@@ -13,11 +13,28 @@
// #define ARDUINO_USB_CDC_ON_BOOT 1
// #define LoRaWAN_DEBUG_LEVEL 0
#include "HT_ST7789spi.h"
-#include "global_config.h"
+// #include "global_config.h"
#include "images.h"
-#include "ui.h"
+// #include "ui.h"
#include
#include
+#include
+
+struct Entry
+{
+ String drone; // Drone name
+ int fstart; // Fr Start
+ int fend; // Fr End
+ int y; // y(vertical) position
+ uint16_t color; // color
+};
+
+// Define and initialize the vector
+std::vector fpvArray = {{"FPV-ELRS", 160, 350, 100, ST7789_BLUE},
+ {"915-ELRS", 700, 1000, 100, ST7789_ORANGE},
+ {"FPV433-ELRS", 350, 530, 100, ST7789_YELLOW},
+ {"Orlan", 820, 940, 98, ST7789_GREEN},
+ {"Zala", 830, 950, 80, ST7789_MAGENTA}};
#define st7789_CS_Pin 39
#define st7789_REST_Pin 40
@@ -70,16 +87,20 @@ constexpr bool DRAW_DETECTION_TICKS = true;
// number of samples for RSSI method
#define SAMPLES_RSSI 5 // 21 //
-#define FREQ_BEGIN 650
-#define DEFAULT_DRONE_DETECTION_LEVEL 90
+#define FREQ_BEGIN 150
+#define FREQ_END 950
+#define BANDWIDTH 467.0
+#define MHZ_PX (float)((float)(FREQ_END - FREQ_BEGIN) / DISPLAY_WIDTH)
+#define DEFAULT_DRONE_DETECTION_LEVEL -90
+#define DRONE_LEGEND 1;
#define RANGE (int)(FREQ_END - FREQ_BEGIN)
-#define SINGLE_STEP (float)(RANGE / (STEPS * SCAN_RBW_FACTOR))
+// #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 fr_end = FREQ_END;
// Feature to scan diapasones. Other frequency settings will be ignored.
// int SCAN_RANGES[] = {850890, 920950};
@@ -90,7 +111,7 @@ int SCAN_RANGES[] = {};
// uint64_t RANGE_PER_PAGE = FREQ_END - FREQ_BEGIN; // FREQ_END - FREQ_BEGIN
// Override or e-ink
-uint64_t RANGE_PER_PAGE = FREQ_BEGIN + DISPLAY_WIDTH;
+uint64_t RANGE_PER_PAGE = FREQ_END - FREQ_BEGIN; // FREQ_BEGIN + DISPLAY_WIDTH;
uint64_t iterations = RANGE / RANGE_PER_PAGE;
@@ -114,7 +135,7 @@ bool waterfall[STEPS], detected_y[STEPS]; // 20 - ??? steps of the waterfall
bool first_run, new_pixel, detected_x = false;
// drone detection flag
bool detected = false;
-uint64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL;
+int64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL;
uint64_t drone_detected_frequency_start = 0;
uint64_t drone_detected_frequency_end = 0;
uint64_t detection_count = 0;
@@ -131,9 +152,24 @@ uint64_t scan_time = 0;
uint64_t scan_start_time = 0;
#endif
-#define WATERFALL_START 115
+// To remove waterfall adjust this and this
+#define ZERO_LEVEL 110 // Equal to minimal RSSI
+#define ZERO_SHIFT 42
+#define LOWER_LEVEL ZERO_LEVEL + ZERO_SHIFT // 108(zero) - (40 moving down)
+#define SPECTR_CHART_STAR_TOP 42;
+#define WATERFALL_START 119
#define WATERFALL_END DISPLAY_HEIGHT - 10 - 2
+#ifndef DISABLE_WATERFALL
+#define DISABLE_WATERFALL 1 // to disable set to 1
+#endif
+
+#if DISABLE_WATERFALL == 0
+#define ZERO_LEVEL 110
+#define ZERO_SHIFT 0
+#define LOWER_LEVEL ZERO_LEVEL
+#endif
+
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;
@@ -247,8 +283,27 @@ void battery()
}
}
-constexpr int lower_level = 108;
-constexpr int up_level = 40;
+void drawDroneLegend()
+{
+ // Draw FPV array Names
+ for (const auto &entry : fpvArray)
+ {
+ int pixelStart = (entry.fstart - FREQ_BEGIN) / MHZ_PX;
+ int pixelEnd = (entry.fend - FREQ_BEGIN) / MHZ_PX;
+ int length = (pixelEnd - pixelStart);
+ // Serial.println("Pixel Start: " + String(pixelStart));
+ // Serial.println("MHinPIX: " + String(MHZ_PX));
+ int median = length / 2;
+ if (entry.fstart < FREQ_END)
+ {
+ st7789->drawFastHLine(pixelStart, entry.y, length, entry.color);
+ drawText(pixelStart, entry.y - 10, entry.drone, entry.color);
+ }
+ }
+}
+
+constexpr int lower_level = LOWER_LEVEL;
+constexpr int up_level = SPECTR_CHART_STAR_TOP;
int rssiToPix(int rssi)
{
// Bigger is lower signal
@@ -256,11 +311,25 @@ int rssiToPix(int rssi)
{
return lower_level - 1;
}
- if (abs(rssi) <= up_level)
+ if (abs(rssi) <= up_level && lower_level < 130)
{
return up_level;
}
- return abs(rssi);
+ // if chart moved to the bottom
+ if (lower_level > 130)
+ {
+ int returnRssi = abs(rssi - ZERO_SHIFT);
+ // Serial.println("RSSI: " + String(rssi));
+ if (returnRssi >= lower_level)
+ {
+ return lower_level - 1;
+ }
+ return returnRssi;
+ }
+ else
+ {
+ return abs(rssi);
+ }
}
//
@@ -286,7 +355,7 @@ long timeSinceLastModeSwitch = 0;
float fr = FREQ_BEGIN, fr_x[STEPS + 5], vbat = 0;
// MHz in one screen pix step
// END will be Begin + 289 * mhz_step
-constexpr int mhz_step = 1;
+float mhz_step = MHZ_PX;
// TODO: make end_freq
// Measure RSS every step
constexpr float rssi_mhz_step = 0.33;
@@ -301,6 +370,7 @@ int window_max_rssi = -999;
int window_max_fr = -999;
int max_scan_rssi[STEPS + 2];
int max_history_rssi[STEPS + 2];
+int historical_loops = 50, h = 0;
long display_scan_start = 0;
long display_scan_end = 0;
long display_scan_i_end = 0;
@@ -315,6 +385,7 @@ constexpr unsigned int STATUS_BAR_HEIGHT = 5;
void loop()
{
+ // Serial.println("Loop");
if (screen_update_loop_counter == 0)
{
fr_x[x1] = 0;
@@ -331,11 +402,20 @@ void loop()
fr_x[x1] = fr;
int u = 0;
- int additional_samples = 10;
+ int additional_samples = 0;
// Clear old data with the cursor ...
st7789->drawFastVLine(x1, lower_level, -lower_level + 11, ST7789_BLACK);
+
// Draw max history line
+ if (h == historical_loops)
+ {
+ st7789->drawLine(x1, rssiToPix(max_history_rssi[x1]), x1, lower_level,
+ ST7789_BLACK /*gray*/);
+ // clear history
+ max_history_rssi[x1] = -999;
+ }
+
st7789->drawLine(x1, rssiToPix(max_history_rssi[x1]), x1, lower_level,
12710 /*gray*/);
// Fetch samples
@@ -351,8 +431,15 @@ void loop()
additional_samples--;
}
- radio.setFrequency((float)fr + (float)(rssi_mhz_step * u),
- false); // false = no calibration need here
+ bool calibrate = true;
+ float freq = (float)fr + (float)(rssi_mhz_step * u);
+ if ((int)freq % 10 == 0)
+ {
+ calibrate = true;
+ }
+ radio.setFrequency(freq,
+ /*false*/ calibrate); // false = no calibration need here
+ // Serial.println((float)fr + (float)(rssi_mhz_step * u));
u++;
if (rssi_mhz_step * u >= mhz_step)
{
@@ -363,6 +450,7 @@ void loop()
rssi_single_start = millis();
}
rssi2 = radio.getRSSI(false);
+ // Serial.print(" RSSI : " + String(rssi2));
scan_iterations++;
if (rssi_single_end == 0)
{
@@ -383,13 +471,24 @@ void loop()
#ifdef PRINT_DEBUG
Serial.println(String(fr) + ":" + String(rssi2));
#endif
+ int lineHeight = 0;
+
st7789->drawPixel(x1, rssiToPix(rssi2), rssiToColor(abs(rssi2)));
st7789->drawPixel(x1, rssiToPix(rssi2) - 1, rssiToColor(abs(rssi2)));
st7789->drawPixel(x1, rssiToPix(rssi2) - 2, rssiToColor(abs(rssi2)));
+ st7789->drawPixel(x1, rssiToPix(rssi2) - 3, rssiToColor(abs(rssi2)));
+ st7789->drawPixel(x1, rssiToPix(rssi2) - 4, rssiToColor(abs(rssi2)));
+
+ if (true /*draw full line*/)
+ {
+ st7789->drawFastVLine(x1, rssiToPix(rssi2), lower_level - rssiToPix(rssi2),
+ rssiToColor(abs(rssi2)));
+ }
// Draw Update Cursor
st7789->drawFastVLine(x1 + 1, lower_level, -lower_level + 11, ST7789_BLACK);
st7789->drawFastVLine(x1 + 2, lower_level, -lower_level + 11, ST7789_BLACK);
- st7789->drawFastVLine(x1 + 3, lower_level, -lower_level + 11, ST7789_BLACK);
+ // st7789->drawFastVLine(x1 + 3, lower_level, -lower_level + 11,
+ // ST7789_BLACK);
if (max_scan_rssi[x1] == -999)
{
@@ -417,10 +516,13 @@ void loop()
}
}
// Writing pixel only if it is bigger than drone detection level
- if (abs(max_scan_rssi[x1]) < drone_detection_level)
+ if (abs(max_scan_rssi[x1]) < abs(drone_detection_level))
{
- // Waterfall Pixel
- st7789->drawPixel(x1, w, rssiToColor(abs(max_scan_rssi[x1]), true));
+ if (DISABLE_WATERFALL == 0)
+ {
+ // Waterfall Pixel
+ st7789->drawPixel(x1, w, rssiToColor(abs(max_scan_rssi[x1]), true));
+ }
detailed_scan_candidate[(int)fr] = (int)fr;
}
@@ -431,7 +533,7 @@ void loop()
// Draw legend for windows
if (x1 % rssi_window_size == 0 || x1 == DISPLAY_WIDTH)
{
- if (abs(window_max_rssi) < drone_detection_level && window_max_rssi != 0 &&
+ if (abs(window_max_rssi) < abs(drone_detection_level) && window_max_rssi != 0 &&
window_max_rssi != -999)
{
y2 = 15;
@@ -450,9 +552,15 @@ void loop()
window_max_rssi = -999;
}
- // Waterfall cursor
- st7789->drawFastHLine(0, w + 1, DISPLAY_WIDTH, ST7789_BLACK);
- st7789->drawFastHLine(0, w + 2, DISPLAY_WIDTH, ST7789_BLACK);
+ if (DISABLE_WATERFALL == 0)
+ {
+ // Waterfall cursor
+ st7789->drawFastHLine(0, w + 1, DISPLAY_WIDTH, ST7789_BLACK);
+ if (w < WATERFALL_END)
+ {
+ st7789->drawFastHLine(0, w + 2, DISPLAY_WIDTH, ST7789_ORANGE);
+ }
+ }
// drone detection level line
if (x1 % 2 == 0)
@@ -470,9 +578,9 @@ void loop()
button_pressed_counter = 0;
if (button.pressed())
{
- drone_detection_level++;
- if (drone_detection_level > 107)
- drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL - 20;
+ drone_detection_level--;
+ if (drone_detection_level < -107)
+ drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL + 20;
while (button.pressedNow())
{
delay(100);
@@ -491,6 +599,14 @@ void loop()
heltec_deep_sleep();
}
+ // Drone legend every 1/4 of the screen
+ if (x1 % (STEPS / 4) == 0)
+ {
+#ifdef DRONE_LEGEND
+ drawDroneLegend();
+#endif
+ }
+
// Main N x-axis full loop end logic
if (x1 >= STEPS)
{
@@ -502,17 +618,28 @@ void loop()
#ifdef PRINT_DEBUG
Serial.println("Screen End for Output: " + String(screen_update_loop_counter));
#endif
+
// Doing output only after full scan
if (screen_update_loop_counter + 1 == SCANS_PER_DISPLAY)
{
+#ifdef DRONE_LEGEND
+ drawDroneLegend();
+#endif
+
+ h++;
+ if (h == historical_loops - 1)
+ {
+ h = 0;
+ }
+
// Scan results to max Mhz and dB in window
display_scan_end = millis();
st7789->fillRect(0, 0, DISPLAY_WIDTH, 11, ST7789_BLACK);
drawText(0, 0,
"T:" + String(display_scan_end - display_scan_start) + "/" +
- String(rssi_single_end - rssi_single_start) + " L:-" +
- String(drone_detection_level) + "dB",
+ String(rssi_single_end - rssi_single_start) +
+ " L:" + String(drone_detection_level) + "dB",
ST7789_BLUE);
/// battery();
diff --git a/trans_src/main.cpp b/trans_src/main.cpp
index 2010f48..e40a4c2 100644
--- a/trans_src/main.cpp
+++ b/trans_src/main.cpp
@@ -8,6 +8,7 @@
* This works on the stick, but the output on the screen gets cut off.
*/
+#include
// Turns the 'PRG' button into the power button, long press is off
#define HELTEC_POWER_BUTTON // must be before "#include "
#include
@@ -15,11 +16,11 @@
// Pause between transmited packets in mseconds.
// Set to zero to only transmit a packet when pressing the user button
// Will not exceed 1% duty cycle, even if you set a lower value.
-#define PAUSE 20
+#define PAUSE 10
// Frequency in MHz. Keep the decimal point to designate float.
// Check your own rules and regulations to see what is legal where you are.
-#define FREQUENCY 866.3 // for Europe
+#define FREQUENCY 915 // for Europe
// #define FREQUENCY 905.2 // for US
// LoRa bandwidth. Keep the decimal point to designate float.
@@ -29,13 +30,13 @@
// Number from 5 to 12. Higher means slower but higher "processor gain",
// meaning (in nutshell) longer range and more robust against interference.
-#define SPREADING_FACTOR 9
+#define SPREADING_FACTOR 7
// Transmit power in dBm. 0 dBm = 1 mW, enough for tabletop-testing. This value can be
// set anywhere between -9 dBm (0.125 mW) to 22 dBm (158 mW). Note that the maximum ERP
// (which is what your antenna maximally radiates) on the EU ISM band is 25 mW, and that
// transmissting without an antenna can damage your hardware.
-#define TRANSMIT_POWER -9
+#define TRANSMIT_POWER 22
String rxdata;
volatile bool rxFlag = false;
@@ -67,35 +68,41 @@ void setup()
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
}
+int FHSS = 0.25;
+float FHSS_counter = -10;
void loop()
{
heltec_loop();
- bool tx_legal = millis() > last_tx + minimum_pause;
+ bool tx_legal = true; // millis() > last_tx + minimum_pause;
+ // Emulate frequency hopping spread spectrum (FHSS) is a method of transmitting radio
+ // signals by rapidly switching the carrier between different frequency channels.
+ float fr = (float)(FREQUENCY + (float)(FHSS_counter));
+ RADIOLIB_OR_HALT(radio.setFrequency(fr, false));
// Transmit a packet every PAUSE seconds or when the button is pressed
if ((PAUSE && tx_legal && millis() - last_tx > (PAUSE)) || button.isSingleClick())
{
- // In case of button click, tell user to wait
- if (!tx_legal)
+ if (button.isSingleClick())
{
- both.printf("Legal limit, wait %i sec.\n",
- (int)((minimum_pause - (millis() - last_tx)) / 1000) + 1);
- return;
+ fr = 1000;
+ RADIOLIB_OR_HALT(radio.setFrequency(fr, false));
}
- both.printf("TX [%s] ", String(counter).c_str());
+ // In case of button click, tell user to wait
+ display.printf("TX[%s]", String(counter).c_str());
radio.clearDio1Action();
heltec_led(50); // 50% brightness is plenty for this LED
tx_time = millis();
- RADIOLIB(radio.transmit(String(counter++).c_str()));
+ RADIOLIB(radio.transmit(String("Putin Huylo!!! LA-LA-LA-LA").c_str()));
tx_time = millis() - tx_time;
heltec_led(0);
if (_radiolib_status == RADIOLIB_ERR_NONE)
{
- both.printf("OK (%i ms)\n", (int)tx_time);
+ display.printf("OK(%ims)/%.2fMhz\n", (int)tx_time, fr);
}
else
{
- both.printf("fail (%i)\n", _radiolib_status);
+ display.printf("fail (%i)\n", _radiolib_status);
+ heltec_delay(100);
}
// Maximum 1% duty cycle
minimum_pause = tx_time * 100;
@@ -111,10 +118,16 @@ void loop()
radio.readData(rxdata);
if (_radiolib_status == RADIOLIB_ERR_NONE)
{
- both.printf("RX [%s]\n", rxdata.c_str());
- both.printf(" RSSI: %.2f dBm\n", radio.getRSSI());
- both.printf(" SNR: %.2f dB\n", radio.getSNR());
+ display.printf("RX [%s]\n", rxdata.c_str());
+ display.printf(" RSSI: %.2f dBm\n", radio.getRSSI());
+ display.printf(" SNR: %.2f dB\n", radio.getSNR());
}
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
}
+ FHSS_counter += 0.250;
+ counter++;
+ if (FHSS_counter > 20)
+ {
+ FHSS_counter = -10;
+ }
}