Merge pull request #67 from Genaker/main

Merge
This commit is contained in:
Yegor Shytikov
2024-11-26 09:45:58 -08:00
committed by GitHub
38 changed files with 3281 additions and 813 deletions
+2
View File
@@ -3,3 +3,5 @@
.vscode/c_cpp_properties.json .vscode/c_cpp_properties.json
.vscode/launch.json .vscode/launch.json
.vscode/ipch .vscode/ipch
out/
+3
View File
@@ -12,4 +12,7 @@
}, },
"files.insertFinalNewline": true, "files.insertFinalNewline": true,
"files.autoSave": "onFocusChange", "files.autoSave": "onFocusChange",
"files.associations": {
"cstdint": "cpp"
},
} }
+42 -1
View File
@@ -6,7 +6,9 @@
- Heltec Wireless Stick Lite V3 No Display (Not Tested) - Heltec Wireless Stick Lite V3 No Display (Not Tested)
- Heltec Vision Master E290 - e-Ink 296 x 128 (No OSD) - Heltec Vision Master E290 - e-Ink 296 x 128 (No OSD)
- Heltec Vision MAster T190 - color TFT 320X170 (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 ## 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 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://docs.heltec.org/general/establish_serial_connection.html#for-windows
https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers?tab=downloads 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 <br/>
I used legacy driver
5. Clone this Git Repo or download zip of the sources 5. Clone this Git Repo or download zip of the sources
![image](https://github.com/user-attachments/assets/971b6592-3b71-414c-971c-2ecd20f0f0b7) ![image](https://github.com/user-attachments/assets/971b6592-3b71-414c-971c-2ecd20f0f0b7)
@@ -174,6 +180,15 @@ If less, ESP32 will turn off. Fast pressing(less than 0.5 second) P button chang
7. Select Proper Environment 7. Select Proper Environment
![image](https://github.com/user-attachments/assets/a9c6557b-a387-4457-b59b-b3d7242d2826) ![image](https://github.com/user-attachments/assets/a9c6557b-a387-4457-b59b-b3d7242d2826)
---
>**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 8. Select ESP32 USB Device to program
![image](https://github.com/user-attachments/assets/af76c4b1-7122-45e1-b26b-08b59e03ca3b) ![image](https://github.com/user-attachments/assets/af76c4b1-7122-45e1-b26b-08b59e03ca3b)
Note: It is theoretically possible to program via WiFi and BTH. Note: It is theoretically possible to program via WiFi and BTH.
@@ -222,6 +237,24 @@ or buy :
![image](https://github.com/user-attachments/assets/a1e00b51-5566-4ff5-98fe-67eaeb5bc81f) ![image](https://github.com/user-attachments/assets/a1e00b51-5566-4ff5-98fe-67eaeb5bc81f)
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. 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 </br>
```
// #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 ## DFRobot OSD Wiring
**Heltec V3 -> DFRobot OSD** <br /> **Heltec V3 -> DFRobot OSD** <br />
GND -> GND <br /> GND -> GND <br />
@@ -287,3 +320,11 @@ Edit **paltformio.io** uncommenting/selecting your sources
``` ```
for LilyGo use env:heltec_wifi_lora_32_V3 for LilyGo use env:heltec_wifi_lora_32_V3
# WiFi and Bluetooth BT Scanning
Works only with OSD enabled <br/>
Uncomment this lines
```
// #define OSD_ENABLED true
// #define WIFI_SCANNING_ENABLED true
// #define BT_SCANNING_ENABLED true
```
+68 -109
View File
@@ -7,30 +7,21 @@ import sys
import numpy as np import numpy as np
import matplotlib as mpl import matplotlib as mpl
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import json
from datetime import datetime from datetime import datetime
from argparse import RawTextHelpFormatter from argparse import RawTextHelpFormatter
# number of samples in each scanline # Constants
SCAN_WIDTH = 33 SCAN_WIDTH = 33 # number of samples in each scanline
OUT_PATH = "out" # output path for saved files
# scanline Serial start/end markers # Default settings
SCAN_MARK_START = 'SCAN '
SCAN_MARK_FREQ = 'FREQ '
SCAN_MARK_END = ' END'
# output path
OUT_PATH = 'out'
# default settings
DEFAULT_BAUDRATE = 115200 DEFAULT_BAUDRATE = 115200
DEFAULT_COLOR_MAP = 'viridis' DEFAULT_COLOR_MAP = 'viridis'
DEFAULT_SCAN_LEN = 200 DEFAULT_SCAN_LEN = 200
DEFAULT_RSSI_OFFSET = -11 DEFAULT_RSSI_OFFSET = -11
# Print iterations progress def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=50, fill='', print_end="\r"):
# 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"):
""" """
Call in a loop to create terminal progress bar Call in a loop to create terminal progress bar
@params: @params:
@@ -41,136 +32,104 @@ def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1,
decimals - Optional : positive number of decimals in percent complete (Int) decimals - Optional : positive number of decimals in percent complete (Int)
length - Optional : character length of bar (Int) length - Optional : character length of bar (Int)
fill - Optional : bar fill character (Str) 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))) percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
filledLength = int(length * iteration // total) filled_length = int(length * iteration // total)
bar = fill * filledLength + '-' * (length - filledLength) bar = fill * filled_length + '-' * (length - filled_length)
print(f'\r{prefix} |{bar}| {percent}% {suffix}', end = printEnd) print(f'\r{prefix} |{bar}| {percent}% {suffix}', end=print_end)
if iteration == total: if iteration == total:
print() print()
def parse_line(line):
"""Parse a JSON line from the serial input."""
return json.loads(line)
def main(): def main():
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description=''' parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''\
RadioLib SX126x_Spectrum_Scan plotter script. Displays output from SX126x_Spectrum_Scan example Parse serial data from LOG_DATA_JSON functionality.
as grayscale and
Depends on pyserial and matplotlib, install by: 1. #define LOG_DATA_JSON true - add this line in main.cpp, upload to device
'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.
2. Run the script with appropriate arguments. 2. Run the script with appropriate arguments.
3. Once the scan is complete, output files will be saved to out/ 3. Once the scan is complete, output files will be saved to out/
''') ''')
parser.add_argument('port', parser.add_argument('port', type=str, help='COM port to connect to the device')
type=str, parser.add_argument('--speed', default=DEFAULT_BAUDRATE, type=int,
help='COM port to connect to the device') help=f'COM port baudrate (defaults to {DEFAULT_BAUDRATE})')
parser.add_argument('--speed', parser.add_argument('--map', default=DEFAULT_COLOR_MAP, type=str,
default=DEFAULT_BAUDRATE, help=f'Matplotlib color map to use for the output (defaults to "{DEFAULT_COLOR_MAP}")')
type=int, parser.add_argument('--len', default=DEFAULT_SCAN_LEN, type=int,
help=f'COM port baudrate (defaults to {DEFAULT_BAUDRATE})') help=f'Number of scanlines to record (defaults to {DEFAULT_SCAN_LEN})')
parser.add_argument('--map', parser.add_argument('--offset', default=DEFAULT_RSSI_OFFSET, type=int,
default=DEFAULT_COLOR_MAP, help=f'Default RSSI offset in dBm (defaults to {DEFAULT_RSSI_OFFSET})')
type=str, parser.add_argument('--freq', default=-1, type=float,
help=f'Matplotlib color map to use for the output (defaults to "{DEFAULT_COLOR_MAP}")') help='Default starting frequency in MHz')
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')
args = parser.parse_args() args = parser.parse_args()
freq_mode = False # Create the result array
scan_len = args.len scan_len = args.len
if (args.freq != -1): arr = np.zeros((scan_len, SCAN_WIDTH))
freq_mode = True
scan_len = 1000
# create the color map and the result array # Scanline counter
arr = np.zeros((SCAN_WIDTH, scan_len))
# scanline counter
row = 0 row = 0
# list of frequencies in frequency mode # List of frequencies
freq_list = [] freq_list = []
# open the COM port # Open the COM port
with serial.Serial(args.port, args.speed, timeout=None) as com: with serial.Serial(args.port, args.speed, timeout=None) as com:
while(True): while True:
# update the progress bar # Update the progress bar
if not freq_mode: print_progress_bar(row, scan_len)
printProgressBar(row, scan_len)
# read a single line # Read a single line
try: try:
line = com.readline().decode('utf-8') line = com.readline().decode('utf-8').strip()
except: except UnicodeDecodeError:
continue continue
if SCAN_MARK_FREQ in line: if line.startswith("{"):
new_freq = float(line.split(' ')[1]) try:
if (len(freq_list) > 1) and (new_freq < freq_list[-1]): data = parse_line(line)
break except json.JSONDecodeError:
continue
freq_list.append(new_freq) # get the lowest frequency for now, could be averaged too.
print('{:.3f}'.format(new_freq), end = '\r') freq = data["low_range_freq"]
continue
# check the markers # value in negative, eg: -70
if (SCAN_MARK_START in line) and (SCAN_MARK_END in line): rssi = int(data["value"])
# get the values
scanline = line[len(SCAN_MARK_START):-len(SCAN_MARK_END)].split(',') if freq not in freq_list:
for col in range(SCAN_WIDTH): freq_list.append(freq)
arr[col][row] = int(scanline[col])
# increment the row counter col = freq_list.index(freq)
row = row + 1 arr[row][col] = rssi
# Increment the row counter
row += 1
# check if we're done # Check if we're done
if (not freq_mode) and (row >= scan_len): if row >= scan_len:
break 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: # Create the figure
scan_len = len(freq_list) fig, ax = plt.subplots(figsize=(12, 8))
# create the figure # Display the result as heatmap
fig, ax = plt.subplots() 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 # Set plot properties and show
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
timestamp = datetime.now().strftime('%y-%m-%d %H-%M-%S') timestamp = datetime.now().strftime('%y-%m-%d %H-%M-%S')
title = f'RadioLib SX126x Spectral Scan {timestamp}' title = f'LoraSA Spectral Scan {timestamp}'
if freq_mode: plt.xlabel("Time (sample)")
plt.xlabel("Frequency [Hz]") plt.ylabel("Frequency (MHz)")
else:
plt.xlabel("Time [sample]")
plt.ylabel("RSSI [dBm]")
ax.set_aspect('auto')
fig.suptitle(title) fig.suptitle(title)
fig.canvas.manager.set_window_title(title) fig.canvas.manager.set_window_title(title)
plt.savefig(f'{OUT_PATH}/{title.replace(" ", "_")}.png', dpi=300) plt.savefig(f'{OUT_PATH}/{title.replace(" ", "_")}.png', dpi=300)
plt.show() plt.show()
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+42
View File
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>ESP Wi-Fi Manager</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="topnav">
<h1>LORA SA ESP32 CONFIG</h1>
</div>
<div class="content">
<div class="card-grid">
<div class="card">
<form action="/" method="POST">
<p>
<label for="ssid">SSID</label>
<input type="text" id="ssid" name="ssid"><br>
<label for="pass">Password</label>
<input type="text" id="pass" name="pass"><br>
<label for="ip">IP Address</label>
<input type="text" id="ip" name="ip" value="192.168.1.200"><br>
<label for="gateway">Gateway Address</label>
<input type="text" id="gateway" name="gateway" value="192.168.1.1"><br>
<label for="fstart">FREQ START</label>
<input type="number" id="fstart" name="fstart" value="800"><br>
<label for="fend">FREQ END</label>
<input type="number" id="fend" name="fend" value="960"><br>
<label for="samples">SCAN SAMPLES</label>
<input type="number" id="samples" name="samples" value="10"><br>
<input type="submit" value="Submit">
</p>
</form>
</div>
</div>
</div>
</body>
</html>
+118
View File
@@ -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;
}
View File
+108 -14
View File
@@ -23,6 +23,7 @@
#define DISPLAY_WIDTH 296 #define DISPLAY_WIDTH 296
#define DISPLAY_HEIGHT 128 #define DISPLAY_HEIGHT 128
// Without this line Lora Radio doesn't work with heltec lib // Without this line Lora Radio doesn't work with heltec lib
#define BUTTON 21
#define ARDUINO_heltec_wifi_32_lora_V3 #define ARDUINO_heltec_wifi_32_lora_V3
#include "heltec_unofficial.h" #include "heltec_unofficial.h"
@@ -96,6 +97,7 @@ bool first_run, new_pixel, detected_x = false;
// drone detection flag // drone detection flag
bool detected = false; bool detected = false;
uint64_t drone_detection_level = 90; uint64_t drone_detection_level = 90;
bool detection_level_changed = false;
uint64_t drone_detected_frequency_start = 0; uint64_t drone_detected_frequency_start = 0;
uint64_t drone_detected_frequency_end = 0; uint64_t drone_detected_frequency_end = 0;
uint64_t detection_count = 0; uint64_t detection_count = 0;
@@ -250,18 +252,21 @@ void VextOFF(void) // Vext default OFF
constexpr int lower_level = 108; constexpr int lower_level = 108;
constexpr int up_level = 40; constexpr int up_level = 40;
constexpr int start_pixel = 80;
int rssiToPix(int rssi) int rssiToPix(int rssi)
{ {
// Bigger is lower signal // Bigger is lower signal
if (abs(rssi) >= lower_level) if (abs(rssi) >= lower_level)
{ {
return lower_level - 1; return start_pixel - 1;
} }
if (abs(rssi) <= up_level) 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; long timeSinceLastModeSwitch = 0;
@@ -287,10 +292,61 @@ long display_scan_start = 0;
long display_scan_end = 0; long display_scan_end = 0;
long display_scan_i_end = 0; long display_scan_i_end = 0;
int scan_iterations = 0; int scan_iterations = 0;
bool waterfall_values[DISPLAY_HEIGHT][DISPLAY_WIDTH] = {false};
constexpr unsigned int SCANS_PER_DISPLAY = 5; constexpr unsigned int SCANS_PER_DISPLAY = 5;
constexpr unsigned int STATUS_BAR_HEIGHT = 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() void loop()
{ {
if (screen_update_loop_counter == 0) if (screen_update_loop_counter == 0)
@@ -307,9 +363,13 @@ void loop()
int u = 0; int u = 0;
for (int i = 0; i < SAMPLES_RSSI; i++) for (int i = 0; i < SAMPLES_RSSI; i++)
{ {
state = radio.setFrequency((float)fr + (float)(rssi_mhz_step * u),
radio.setFrequency((float)fr + (float)(rssi_mhz_step * u), true); // false = no calibration need here
false); // false = no calibration need here int radio_error_count = 0;
if (state != RADIOLIB_ERR_NONE)
{
Serial.println("E:setFrequency:" + String(freq));
}
u++; u++;
if (rssi_mhz_step * u >= mhz_step) if (rssi_mhz_step * u >= mhz_step)
{ {
@@ -319,10 +379,14 @@ void loop()
rssi2 = radio.getRSSI(false); rssi2 = radio.getRSSI(false);
scan_iterations++; scan_iterations++;
if (rssi2 > lower_level) if (rssi2 > lower_level)
{
max_scan_rssi[x1] = rssi2;
continue; continue;
}
#ifdef PRINT_DEBUG #ifdef PRINT_DEBUG
Serial.println(String(fr) + ":" + String(rssi2)); Serial.println(String(fr) + ":" + String(rssi2));
#endif #endif
button_logic();
// display.drawString(x1, (int)y2, String(fr) + ":" + String(rssi2)); // display.drawString(x1, (int)y2, String(fr) + ":" + String(rssi2));
display.setPixel(x1, rssiToPix(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 // drone detection level line
if (x1 % 2 == 0) if (x1 % 2 == 0)
{ {
@@ -343,9 +417,15 @@ void loop()
{ {
display_scan_i_end = millis(); display_scan_i_end = millis();
} }
button_logic();
// Main N x-axis full loop end logic // Main N x-axis full loop end logic
if (x1 >= STEPS) if (x1 >= STEPS)
{ {
w++;
if (w >= DISPLAY_HEIGHT - start_pixel - 13)
{
w = 0;
}
if (screen_update_loop_counter == SCANS_PER_DISPLAY) if (screen_update_loop_counter == SCANS_PER_DISPLAY)
{ {
// max Mhz and dB in window // max Mhz and dB in window
@@ -372,17 +452,29 @@ void loop()
display.drawString(i - rssi_window_size + 5, y2 + 10, display.drawString(i - rssi_window_size + 5, y2 + 10,
String(window_max_fr)); String(window_max_fr));
// Vertical lines between windows // 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); display.setPixel(i, l);
} }
} }
window_max_rssi = -999; 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(); display_scan_end = millis();
if (detection_level_changed == true)
{
clear_rectangle(0, 0, 100, 10);
}
display.setFont(ArialMT_Plain_10); display.setFont(ArialMT_Plain_10);
display.drawString(0, 0, display.drawString(0, 0,
"T:" + String(display_scan_end - display_scan_start) + "T:" + String(display_scan_end - display_scan_start) +
@@ -396,6 +488,7 @@ void loop()
esp_restart(); esp_restart();
} }
// ToDo: it doesn't work
battery(); battery();
// iteration full scan / samples pixel step / numbers of scan per display // iteration full scan / samples pixel step / numbers of scan per display
display.drawString(DISPLAY_WIDTH - ((DISPLAY_WIDTH / 6) * 2) - 5, 0, display.drawString(DISPLAY_WIDTH - ((DISPLAY_WIDTH / 6) * 2) - 5, 0,
@@ -409,21 +502,21 @@ void loop()
"s:" + String(mhz_step)); "s:" + String(mhz_step));
// Draw a line horizontally // Draw a line horizontally
display.drawHorizontalLine(0, lower_level + 1, DISPLAY_WIDTH); display.drawHorizontalLine(0, 1 + start_pixel, DISPLAY_WIDTH);
// Generate Ticks // Generate Ticks
for (int x = 0; x < DISPLAY_WIDTH; x++) for (int x = 0; x < DISPLAY_WIDTH; x++)
{ {
if (x % (DISPLAY_WIDTH / 2) == 0 && x > 5) 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 // central tick width
// display.drawVerticalLine(x - 1, lower_level + 1, 8); // display.drawVerticalLine(x - 1, lower_level + 1, 8);
// display.drawVerticalLine(x + 1, lower_level + 1, 8); // display.drawVerticalLine(x + 1, lower_level + 1, 8);
} }
if (x % 10 == 0 || x == 0) if (x % 10 == 0 || x == 0)
display.drawVerticalLine(x, lower_level + 1, 6); display.drawVerticalLine(x, 1 + start_pixel, 6);
if (x % 5 == 0) if (x % 5 == 0)
display.drawVerticalLine(x, lower_level + 1, 3); display.drawVerticalLine(x, 1 + start_pixel, 3);
} }
display.setFont(ArialMT_Plain_10); display.setFont(ArialMT_Plain_10);
@@ -441,13 +534,14 @@ void loop()
String(FREQ_BEGIN + (((int)fr - FREQ_BEGIN) - String(FREQ_BEGIN + (((int)fr - FREQ_BEGIN) -
((int)fr - FREQ_BEGIN) / 4))); ((int)fr - FREQ_BEGIN) / 4)));
// End Mhz // 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.display();
// display will be cleared next scan iteration. it is just buffer clear // display will be cleared next scan iteration. it is just buffer clear
// memset(buffer, 0, displayBufferSize); // memset(buffer, 0, displayBufferSize);
display.clear(); display.clear();
screen_update_loop_counter = 0; screen_update_loop_counter = 0;
scan_iterations = 0; scan_iterations = 0;
display_scan_i_end = 0; display_scan_i_end = 0;
} }
@@ -480,7 +574,7 @@ void setup()
delay(1000); delay(1000);
display.clear(); display.clear();
Serial.begin(115200); Serial.begin(115200);
w = WATERFALL_START; w = 0; // WATERFALL_START;
init_radio(); init_radio();
state = radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_NONE); state = radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_NONE);
if (state != RADIOLIB_ERR_NONE) if (state != RADIOLIB_ERR_NONE)
+126
View File
@@ -0,0 +1,126 @@
#include <TinyGPS++.h>
#include <time.h>
// 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("-----------------------------");
}
+59
View File
@@ -13,8 +13,67 @@
#ifndef _DFRobot_OSD_H_ #ifndef _DFRobot_OSD_H_
#define _DFRobot_OSD_H_ #define _DFRobot_OSD_H_
#define MAX_POWER_LEVELS 33
#include <Arduino.h> #include <Arduino.h>
/*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 // #define ENABLE_DBG //!< Open this macro and you can see the details of the program
#ifdef ENABLE_DBG #ifdef ENABLE_DBG
#define DBG(...) \ #define DBG(...) \
+55
View File
@@ -0,0 +1,55 @@
#include "FS.h"
#include <LittleFS.h>
// 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();
}
+36 -37
View File
@@ -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 for our code
#define RST_OLED UNUSED_PIN #define RST_OLED UNUSED_PIN
#define LED BOARD_LED #define LED BOARD_LED
@@ -41,8 +13,11 @@
#else #else
#define DISPLAY_WIDTH 128 #define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64 #define DISPLAY_HEIGHT 64
#include "OLEDDisplayUi.h" // #include "OLEDDisplayUi.h"
// #include "SH1106Wire.h"
// #include "SSD1306Brzo.h"
#include "SSD1306Wire.h" #include "SSD1306Wire.h"
#endif #endif
#define ARDUINO_heltec_wifi_32_lora_V3 #define ARDUINO_heltec_wifi_32_lora_V3
#ifndef HELTEC_NO_RADIO_INSTANCE #ifndef HELTEC_NO_RADIO_INSTANCE
@@ -52,13 +27,24 @@
#include <SPI.h> #include <SPI.h>
SPIClass *hspi = new SPIClass(2); SPIClass *hspi = new SPIClass(2);
SX1262 radio = new Module(SS, DIO1, RST_LoRa, BUSY_LoRa, *hspi); 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 // Default SPI on pins from pins_arduino.h
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN); SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
#endif #endif // end USING_SX1262
#endif #ifdef USING_LR1121
// Default SPI on pins from pins_arduino.h
void heltec_loop() {} 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) {} void heltec_led(int led) {}
@@ -101,16 +87,29 @@ class PrintSplitter : public Print
#else #else
#define DISPLAY_GEOMETRY GEOMETRY_128_64 #define DISPLAY_GEOMETRY GEOMETRY_128_64
#endif #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); PrintSplitter both(Serial, display);
#else #else
Print &both = Serial; Print &both = Serial;
#endif #endif
// some fake pin // some fake pin
#define BUTTON 38 #ifdef T3_V1_6_SX1276
#define BUTTON_PIN 22
#endif
#define BUTTON BUTTON_PIN
#include "HotButton.h" #include "HotButton.h"
HotButton button(BUTTON); HotButton button(BUTTON);
void heltec_loop()
{
#ifndef DT3_V1_6_SX1276
button.update();
#endif
}
// This file contains a binary patch for the SX1262 // This file contains a binary patch for the SX1262
#include "modules/SX126x/patches/SX126x_patch_scan.h" #include "modules/SX126x/patches/SX126x_patch_scan.h"
@@ -161,7 +160,7 @@ void heltec_setup()
#ifndef HELTEC_NO_DISPLAY_INSTANCE #ifndef HELTEC_NO_DISPLAY_INSTANCE
heltec_display_power(true); heltec_display_power(true);
display.init(); display.init();
display.setContrast(200); // display.setContrast(200);
display.flipScreenVertically(); display.flipScreenVertically();
#endif #endif
} }
+205
View File
@@ -0,0 +1,205 @@
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <WiFi.h>
// 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();
}
}
+23 -1
View File
@@ -1,28 +1,50 @@
#ifndef __GLOBAL_CONFIG_H__ #ifndef __GLOBAL_CONFIG_H__
#define __GLOBAL_CONFIG_H__ #define __GLOBAL_CONFIG_H__
#include "utilities.h"
#ifndef FREQ_BEGIN
// frequency range in MHz to scan // frequency range in MHz to scan
#define FREQ_BEGIN 850 #define FREQ_BEGIN 850
#endif
#ifndef FREQ_END
// TODO: if % RANGE_PER_PAGE != 0 // TODO: if % RANGE_PER_PAGE != 0
#define FREQ_END 950 #define FREQ_END 950
#endif
// Measurement bandwidth. Allowed bandwidth values (in kHz) are: // 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, // 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 // 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 467.0
#define BANDWIDTH_SX1280 406.
// Detection level from the 33 levels. The higher number is more sensitive // Detection level from the 33 levels. The higher number is more sensitive
#define DEFAULT_DRONE_DETECTION_LEVEL 18 #define DEFAULT_DRONE_DETECTION_LEVEL 18
#define BUZZER_PIN 41 #define BUZZER_PIN 41
#ifdef LILYGO
#define BUZZER_PIN 45
#endif
#ifdef T3_V1_6_SX1276
#define BUZZER_PIN 35
#endif
// REB trigger PIN // REB trigger PIN
#define REB_PIN 42 #define REB_PIN 42
#ifdef T3_V1_6_SX1276
#define REB_PIN 35
#endif
#define WATERFALL_ENABLED true #define WATERFALL_ENABLED true
#define WATERFALL_START 37 #define WATERFALL_START 37
#ifdef LILYGO #ifdef LILYGO
#define LED 46 #define LED BOARD_LED
#endif // end not LILYGO #endif // end not LILYGO
#ifdef T3_V1_6_SX1276
#define LED BOARD_LED
#endif
#endif #endif
+19 -8
View File
@@ -8,6 +8,9 @@
#include <Arduino.h> #include <Arduino.h>
#endif #endif
#include <charts.h>
#include <scan.h>
// #include <heltec_unofficial.h> // #include <heltec_unofficial.h>
// (optional) major and minor tick-marks at x MHz // (optional) major and minor tick-marks at x MHz
@@ -31,13 +34,21 @@
#define SCREEN_HEIGHT 64 // ???? not used #define SCREEN_HEIGHT 64 // ???? not used
// publish functions extern void UI_Init(Display_t *);
#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_clearPlotter(void); extern void UI_clearPlotter(void);
extern void UI_clearTopStatus(void);
extern void UI_drawCursor(int16_t); 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;
};
+194
View File
@@ -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);
}
}
}
+95
View File
@@ -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();
}
+28
View File
@@ -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);
}
+69
View File
@@ -0,0 +1,69 @@
#include "charts.h"
#include <cstdint>
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);
}
+178
View File
@@ -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 <OLEDDisplay.h>
typedef OLEDDisplay Display_t;
#endif
#include <cstdint>
#include <events.h>
#include <models.h>
#include <stdlib.h>
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
+46
View File
@@ -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 <cstdint>
#include <scan.h>
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
@@ -8,6 +8,8 @@
* *
*/ */
#ifdef LILYGO
#include "LoRaBoards.h" #include "LoRaBoards.h"
#if defined(HAS_SDCARD) #if defined(HAS_SDCARD)
@@ -927,3 +929,4 @@ bool beginGPS()
return result; return result;
} }
#endif #endif
#endif // #ifdef LILYGO
@@ -486,6 +486,8 @@
#define USING_DIO2_AS_RF_SWITCH #define USING_DIO2_AS_RF_SWITCH
#elif defined(HELTEC)
// just to prevent error
#elif defined(T_BEAM_S3_BPF) #elif defined(T_BEAM_S3_BPF)
#ifndef USING_SX1278 #ifndef USING_SX1278
+153
View File
@@ -0,0 +1,153 @@
#include "models.h"
#include <cstring>
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 <sstream>
#include <string>
#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;
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef CHARTS_MODELS_H
#define CHARTS_MODELS_H
#include <cstdint>
#include <cstring>
#include <stdlib.h>
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
+189
View File
@@ -0,0 +1,189 @@
#ifndef LORASA_CORE_CPP
#define LORASA_CORE_CPP
#include "scan.h"
#include <cstdint>
#include <cstring>
#include <stdlib.h>
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
+81
View File
@@ -0,0 +1,81 @@
#include <cstdint>
#include <events.h>
#include <stdlib.h>
#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
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

+13
View File
@@ -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
![Diagram ML](./Diagram_ML.svg)
+128 -8
View File
@@ -9,12 +9,15 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[platformio] [platformio]
default_envs = heltec_wifi_lora_32_V3
; for env:vision-master-e190 ; for env:vision-master-e190
; src_dir = tft_src ; src_dir = tft_src
; for env:vision-master-e290 ; for env:vision-master-e290
; src_dir = eink_src ; src_dir = eink_src
; for env:heltec_wifi_lora_32_V3 ; for env:heltec_wifi_lora_32_V3
; src_dir = src ;;Default ; src_dir = src ;;Default
; for heltec_wifi_lora_32_V3-test-signal-generator
; src_dir = trans_src
[env:heltec_wifi_lora_32_V3] [env:heltec_wifi_lora_32_V3]
platform = espressif32 platform = espressif32
@@ -23,22 +26,49 @@ framework = arduino
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200
board_build.f_cpu = 240000000 board_build.f_cpu = 240000000
board_build.filesystem = littlefs
lib_deps = lib_deps =
ropg/Heltec_ESP32_LoRa_v3@^0.9.1 ropg/Heltec_ESP32_LoRa_v3@^0.9.1
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4 bblanchon/ArduinoJson@^7.2.0
build_flags = -DHELTEC_POWER_BUTTON 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 platform = espressif32
board = t3_s3_v1_x board = t3_s3_v1_x
framework = arduino framework = arduino
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200
board_build.f_cpu = 240000000 board_build.f_cpu = 240000000
board_build.filesystem = littlefs
lib_deps = lib_deps =
ropg/Heltec_ESP32_LoRa_v3@^0.9.1 ropg/Heltec_ESP32_LoRa_v3@^0.9.1
RadioLib RadioLib
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4 U8g2
XPowersLib
ESP Async WebServer
build_flags = build_flags =
-DLILYGO -DLILYGO
-DT3_S3_V1_2_SX1262 -DT3_S3_V1_2_SX1262
@@ -51,6 +81,91 @@ build_flags =
-DARDUINO_LILYGO_T3_S3_V1_X -DARDUINO_LILYGO_T3_S3_V1_X
-DARDUINO_USB_MODE=1 -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] [env:heltec_wifi_lora_32_V3-test-signal-generator]
platform = espressif32 platform = espressif32
board = heltec_wifi_lora_32_V3 board = heltec_wifi_lora_32_V3
@@ -61,8 +176,9 @@ board_build.f_cpu = 240000000
board_build.flash_size = 80000000L board_build.flash_size = 80000000L
lib_deps = lib_deps =
ropg/Heltec_ESP32_LoRa_v3@^0.9.1 ropg/Heltec_ESP32_LoRa_v3@^0.9.1
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4 build_flags =
build_flags = -DLILYGO -DHELTEC
-DHELTEC_POWER_BUTTON
[env:vision-master-e290] [env:vision-master-e290]
platform = espressif32 platform = espressif32
@@ -71,7 +187,8 @@ framework = arduino
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
board_upload.use_1200bps_touch = true board_upload.use_1200bps_touch = true
build_flags = build_flags =
-DHELTEC
-DHELTEC_BOARD=37 -DHELTEC_BOARD=37
-DSLOW_CLK_TPYE=1 -DSLOW_CLK_TPYE=1
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1
@@ -95,7 +212,6 @@ lib_deps =
https://github.com/HelTecAutomation/Heltec_ESP32/ https://github.com/HelTecAutomation/Heltec_ESP32/
adafruit/Adafruit GFX Library@^1.11.10 adafruit/Adafruit GFX Library@^1.11.10
ropg/Heltec_ESP32_LoRa_v3@^0.9.1 ropg/Heltec_ESP32_LoRa_v3@^0.9.1
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
[env:vision-master-t190] [env:vision-master-t190]
platform = espressif32 platform = espressif32
@@ -105,6 +221,7 @@ monitor_speed = 115200
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
board_upload.use_1200bps_touch = true board_upload.use_1200bps_touch = true
build_flags = build_flags =
-DHELTEC
-DHELTEC_BOARD=38 -DHELTEC_BOARD=38
-DSLOW_CLK_TPYE=1 -DSLOW_CLK_TPYE=1
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1
@@ -129,3 +246,6 @@ lib_deps =
adafruit/Adafruit GFX Library@^1.11.10 adafruit/Adafruit GFX Library@^1.11.10
ropg/Heltec_ESP32_LoRa_v3@^0.9.1 ropg/Heltec_ESP32_LoRa_v3@^0.9.1
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4 adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
[env:native]
platform = native
+590 -401
View File
File diff suppressed because it is too large Load Diff
+80 -187
View File
@@ -2,6 +2,8 @@
#include "RadioLib.h" #include "RadioLib.h"
#include "global_config.h" #include "global_config.h"
#include "images.h" #include "images.h"
#include <charts.h>
#include <scan.h>
// ------------------------------------------------- // -------------------------------------------------
// LOCAL DEFINES // LOCAL DEFINES
@@ -12,24 +14,11 @@
// //
#define SCALE_TEXT_TOP (HEIGHT + X_AXIS_WEIGHT + MAJOR_TICK_LENGTH) #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 // temporary dirty import ... to be solved durring upcoming refactoring
extern unsigned int drone_detection_level;
extern unsigned int RANGE_PER_PAGE; 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 median_frequency;
extern unsigned int detection_count;
extern bool SOUND_ON;
extern unsigned int drone_detected_frequency_start; extern unsigned int drone_detected_frequency_start;
extern unsigned int drone_detected_frequency_end; extern unsigned int drone_detected_frequency_end;
extern unsigned int ranges_count; extern unsigned int ranges_count;
@@ -40,225 +29,139 @@ extern unsigned int range_item;
extern uint64_t loop_time; extern uint64_t loop_time;
#ifndef Vision_Master_E290 void UI_Init(Display_t *display_ptr)
void UI_Init(SSD1306Wire *display_ptr)
{ {
// init pointer to display instance.
display_instance = display_ptr;
// check for null ??? // check for null ???
display_instance->clear(); display_ptr->clear();
// draw the UCOG welcome logo // draw the UCOG welcome logo
display_instance->drawXbm(0, 2, 128, 64, epd_bitmap_ucog); display_ptr->drawXbm(0, 2, 128, 64, epd_bitmap_ucog);
display_instance->display(); display_ptr->display();
} }
#endif
#ifdef Vision_Master_E290 void StatusBar::clearStatus(void)
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)
{ {
// clear status line // clear status line
display_instance->setColor(BLACK); display.setColor(BLACK);
display_instance->fillRect(0, ROW_STATUS_TEXT + 2, 128, 13); display.fillRect(pos_x, pos_y, width, height);
display_instance->setColor(WHITE);
} }
void UI_clearPlotter(void) void UI_clearPlotter(void)
{ {
// clear the scan plot rectangle (top part) // clear the scan plot rectangle (top part)
display_instance->setColor(BLACK); // display_instance->setColor(BLACK);
display_instance->fillRect(0, 0, STEPS, HEIGHT); // display_instance->fillRect(0, 10, STEPS, HEIGHT - 10);
display_instance->setColor(WHITE); // display_instance->setColor(WHITE);
} }
/** void UI_clearTopStatus(void)
* @brief Draws ticks on the display at regular whole intervals.
*
* @param every The interval between ticks in MHz.
* @param length The length of each tick in pixels.
*/
void drawTicks(float every, int length)
{ {
int first_tick; // clear the scan plot rectangle (top part)
bool correction; // display_instance->setColor(BLACK);
int pixels_per_step; // display_instance->fillRect(0, 0, STEPS, 10);
int correction_number; // display_instance->setColor(WHITE);
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
}
} }
void UI_drawCursor(int16_t possition) void UI_drawCursor(int16_t possition)
{ {
// Draw animated vertical cursor on reload process // Draw animated vertical cursor on reload process
display_instance->setColor(BLACK); // display_instance->setColor(BLACK);
display_instance->drawVerticalLine(possition, 0, HEIGHT); // display_instance->drawVerticalLine(possition, 0, HEIGHT);
display_instance->drawVerticalLine(possition + 1, 0, HEIGHT); // display_instance->drawVerticalLine(possition + 1, 0, HEIGHT);
display_instance->drawVerticalLine(possition + 2, 0, HEIGHT); // display_instance->drawVerticalLine(possition + 2, 0, HEIGHT);
display_instance->setColor(WHITE); // display_instance->setColor(WHITE);
} }
/** /**
* @brief Decorates the display: everything but the plot itself. * @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) 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 // Drone detection level
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT); display.setTextAlignment(TEXT_ALIGN_RIGHT);
display_instance->drawString(128, 0, String(drone_detection_level)); display.drawString(width, 0, String(r.drone_detection_level));
} }
if (!ui_initialized || redraw) if (!ui_initialized)
{ {
// Clear something // Clear something
display_instance->setColor(BLACK); /* display_instance->setColor(BLACK);
display_instance->fillRect(0, SCALE_TEXT_TOP + 1, 128, 12); display_instance->fillRect(0, SCALE_TEXT_TOP + 1, 128, 12);
display_instance->setColor(WHITE); display_instance->setColor(WHITE);
*/
// Drone detection level // Drone detection level
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT); display.setTextAlignment(TEXT_ALIGN_RIGHT);
display_instance->drawString(128, 0, String(drone_detection_level)); display.drawString(pos_x + width, 0, String(r.drone_detection_level));
// Frequency start // Frequency start
display_instance->setTextAlignment(TEXT_ALIGN_LEFT); display.setTextAlignment(TEXT_ALIGN_LEFT);
display_instance->drawString(0, ROW_STATUS_TEXT, display.drawString(pos_x, text_y,
(begin == 0) ? String(FREQ_BEGIN) : String(begin)); (r.fr_begin == 0) ? String(CONF_FREQ_BEGIN)
: String(r.fr_begin));
// Frequency detected // Frequency detected
display_instance->setTextAlignment(TEXT_ALIGN_CENTER); display.setTextAlignment(TEXT_ALIGN_CENTER);
display_instance->drawString(128 / 2, ROW_STATUS_TEXT, display.drawString(pos_x + width / 2, text_y,
(begin == 0) ? String(median_frequency) (r.fr_begin == 0)
: String(begin + ((end - begin) / 2))); ? String(median_frequency)
: String(r.fr_begin + ((r.fr_end - r.fr_begin) / 2)));
// Frequency end // Frequency end
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT); display.setTextAlignment(TEXT_ALIGN_RIGHT);
display_instance->drawString(128, ROW_STATUS_TEXT, display.drawString(pos_x + width, text_y,
(end == 0) ? String(FREQ_END) : String(end)); (r.fr_end == 0) ? String(CONF_FREQ_END) : String(r.fr_end));
} }
// Status text block // 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 // clear status line
clearStatus(); clearStatus();
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, display.setColor(WHITE);
String(drone_detected_frequency_start) + ">RF<" + display.drawString(pos_x + width / 2, text_y,
String(drone_detected_frequency_end)); String(drone_detected_frequency_start) + ">RF<" +
String(drone_detected_frequency_end));
} }
else else
{ {
// "Scanning" // "Scanning"
display_instance->setTextAlignment(TEXT_ALIGN_CENTER); display.setTextAlignment(TEXT_ALIGN_CENTER);
// clear status line // clear status line
clearStatus(); clearStatus();
if (scan_progress_count == 0) String s = "Scan \\";
if (scan_progress_count == 1)
{ {
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan \\"); s = "Scan |";
}
else if (scan_progress_count == 1)
{
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan |");
} }
else if (scan_progress_count == 2) else if (scan_progress_count == 2)
{ {
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan /"); s = "Scan /";
} }
else if (scan_progress_count == 3) else if (scan_progress_count == 3)
{ {
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan -"); s = "Scan -";
} }
scan_progress_count++; scan_progress_count++;
if (scan_progress_count >= 4) if (scan_progress_count >= 4)
{ {
scan_progress_count = 0; 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); digitalWrite(LED, HIGH);
if (SOUND_ON) if (r.sound_on)
{ {
tone(BUZZER_PIN, 104, 100); tone(BUZZER_PIN, 104, 100);
} }
digitalWrite(REB_PIN, HIGH); digitalWrite(REB_PIN, HIGH);
led_flag = false; r.led_flag = false;
} }
else if (!redraw) else if (!r.led_flag)
{ {
digitalWrite(LED, LOW); digitalWrite(LED, LOW);
} }
@@ -266,39 +169,29 @@ void UI_displayDecorate(int begin = 0, int end = 0, bool redraw = false)
if (ranges_count == 0) if (ranges_count == 0)
{ {
#ifdef DEBUG #ifdef DEBUG
display_instance->setTextAlignment(TEXT_ALIGN_LEFT); display.setTextAlignment(TEXT_ALIGN_LEFT);
display_instance->drawString(0, ROW_STATUS_TEXT, String(loop_time)); display.drawString(pos_x, text_y, String(loop_time));
#else #else
display_instance->setTextAlignment(TEXT_ALIGN_LEFT); display.setTextAlignment(TEXT_ALIGN_LEFT);
display_instance->drawString(0, ROW_STATUS_TEXT, String(FREQ_BEGIN)); display.drawString(pos_x, text_y, String(CONF_FREQ_BEGIN));
#endif #endif
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT); display.setTextAlignment(TEXT_ALIGN_RIGHT);
display_instance->drawString(128, ROW_STATUS_TEXT, String(FREQ_END)); display.drawString(pos_x + width, text_y, String(CONF_FREQ_END));
} }
else if (ranges_count > 0) else if (ranges_count > 0)
{ {
display_instance->setTextAlignment(TEXT_ALIGN_LEFT); display.setTextAlignment(TEXT_ALIGN_LEFT);
display_instance->drawString(0, ROW_STATUS_TEXT, display.drawString(pos_x, text_y,
String(SCAN_RANGES[range_item] / 1000) + "-" + String(SCAN_RANGES[range_item] / 1000) + "-" +
String(SCAN_RANGES[range_item] % 1000)); String(SCAN_RANGES[range_item] % 1000));
if (range_item + 1 < iterations) if (range_item + 1 < iterations)
{ {
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT); display.setTextAlignment(TEXT_ALIGN_RIGHT);
display_instance->drawString(128, ROW_STATUS_TEXT, display.drawString(pos_x + width, text_y,
String(SCAN_RANGES[range_item + 1] / 1000) + String(SCAN_RANGES[range_item + 1] / 1000) + "-" +
"-" + 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; ui_initialized = true;
} }
+67
View File
@@ -0,0 +1,67 @@
#include <stdio.h>
#define LOG(args...) printf(args)
#include "../lib/scan/scan.cpp"
#include <unity.h>
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);
}
+220
View File
@@ -0,0 +1,220 @@
#define TO_STRING
#include "../lib/models/WaterfallModel.cpp"
#include <stdio.h>
#include <unity.h>
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;
}
+21
View File
@@ -0,0 +1,21 @@
#include <unity.h>
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();
}
+157 -30
View File
@@ -13,11 +13,28 @@
// #define ARDUINO_USB_CDC_ON_BOOT 1 // #define ARDUINO_USB_CDC_ON_BOOT 1
// #define LoRaWAN_DEBUG_LEVEL 0 // #define LoRaWAN_DEBUG_LEVEL 0
#include "HT_ST7789spi.h" #include "HT_ST7789spi.h"
#include "global_config.h" // #include "global_config.h"
#include "images.h" #include "images.h"
#include "ui.h" // #include "ui.h"
#include <Adafruit_GFX.h> #include <Adafruit_GFX.h>
#include <Arduino.h> #include <Arduino.h>
#include <vector>
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<Entry> 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_CS_Pin 39
#define st7789_REST_Pin 40 #define st7789_REST_Pin 40
@@ -70,16 +87,20 @@ constexpr bool DRAW_DETECTION_TICKS = true;
// number of samples for RSSI method // number of samples for RSSI method
#define SAMPLES_RSSI 5 // 21 // #define SAMPLES_RSSI 5 // 21 //
#define FREQ_BEGIN 650 #define FREQ_BEGIN 150
#define DEFAULT_DRONE_DETECTION_LEVEL 90 #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 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 range = (int)(FREQ_END - FREQ_BEGIN);
uint64_t fr_begin = 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. // Feature to scan diapasones. Other frequency settings will be ignored.
// int SCAN_RANGES[] = {850890, 920950}; // 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 // uint64_t RANGE_PER_PAGE = FREQ_END - FREQ_BEGIN; // FREQ_END - FREQ_BEGIN
// Override or e-ink // 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; 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; bool first_run, new_pixel, detected_x = false;
// drone detection flag // drone detection flag
bool detected = false; 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_start = 0;
uint64_t drone_detected_frequency_end = 0; uint64_t drone_detected_frequency_end = 0;
uint64_t detection_count = 0; uint64_t detection_count = 0;
@@ -131,9 +152,24 @@ uint64_t scan_time = 0;
uint64_t scan_start_time = 0; uint64_t scan_start_time = 0;
#endif #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 #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; uint64_t x, y, range_item, w = WATERFALL_START, i = 0;
int osd_x = 1, osd_y = 2, col = 0, max_bin = 32; int osd_x = 1, osd_y = 2, col = 0, max_bin = 32;
uint64_t ranges_count = 0; uint64_t ranges_count = 0;
@@ -247,8 +283,27 @@ void battery()
} }
} }
constexpr int lower_level = 108; void drawDroneLegend()
constexpr int up_level = 40; {
// 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) int rssiToPix(int rssi)
{ {
// Bigger is lower signal // Bigger is lower signal
@@ -256,11 +311,25 @@ int rssiToPix(int rssi)
{ {
return lower_level - 1; return lower_level - 1;
} }
if (abs(rssi) <= up_level) if (abs(rssi) <= up_level && lower_level < 130)
{ {
return up_level; 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; float fr = FREQ_BEGIN, fr_x[STEPS + 5], vbat = 0;
// MHz in one screen pix step // MHz in one screen pix step
// END will be Begin + 289 * mhz_step // END will be Begin + 289 * mhz_step
constexpr int mhz_step = 1; float mhz_step = MHZ_PX;
// TODO: make end_freq // TODO: make end_freq
// Measure RSS every step // Measure RSS every step
constexpr float rssi_mhz_step = 0.33; constexpr float rssi_mhz_step = 0.33;
@@ -301,6 +370,7 @@ int window_max_rssi = -999;
int window_max_fr = -999; int window_max_fr = -999;
int max_scan_rssi[STEPS + 2]; int max_scan_rssi[STEPS + 2];
int max_history_rssi[STEPS + 2]; int max_history_rssi[STEPS + 2];
int historical_loops = 50, h = 0;
long display_scan_start = 0; long display_scan_start = 0;
long display_scan_end = 0; long display_scan_end = 0;
long display_scan_i_end = 0; long display_scan_i_end = 0;
@@ -315,6 +385,7 @@ constexpr unsigned int STATUS_BAR_HEIGHT = 5;
void loop() void loop()
{ {
// Serial.println("Loop");
if (screen_update_loop_counter == 0) if (screen_update_loop_counter == 0)
{ {
fr_x[x1] = 0; fr_x[x1] = 0;
@@ -331,11 +402,20 @@ void loop()
fr_x[x1] = fr; fr_x[x1] = fr;
int u = 0; int u = 0;
int additional_samples = 10; int additional_samples = 0;
// Clear old data with the cursor ... // Clear old data with the cursor ...
st7789->drawFastVLine(x1, lower_level, -lower_level + 11, ST7789_BLACK); st7789->drawFastVLine(x1, lower_level, -lower_level + 11, ST7789_BLACK);
// Draw max history line // 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, st7789->drawLine(x1, rssiToPix(max_history_rssi[x1]), x1, lower_level,
12710 /*gray*/); 12710 /*gray*/);
// Fetch samples // Fetch samples
@@ -351,8 +431,15 @@ void loop()
additional_samples--; additional_samples--;
} }
radio.setFrequency((float)fr + (float)(rssi_mhz_step * u), bool calibrate = true;
false); // false = no calibration need here 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++; u++;
if (rssi_mhz_step * u >= mhz_step) if (rssi_mhz_step * u >= mhz_step)
{ {
@@ -363,6 +450,7 @@ void loop()
rssi_single_start = millis(); rssi_single_start = millis();
} }
rssi2 = radio.getRSSI(false); rssi2 = radio.getRSSI(false);
// Serial.print(" RSSI : " + String(rssi2));
scan_iterations++; scan_iterations++;
if (rssi_single_end == 0) if (rssi_single_end == 0)
{ {
@@ -383,13 +471,24 @@ void loop()
#ifdef PRINT_DEBUG #ifdef PRINT_DEBUG
Serial.println(String(fr) + ":" + String(rssi2)); Serial.println(String(fr) + ":" + String(rssi2));
#endif #endif
int lineHeight = 0;
st7789->drawPixel(x1, rssiToPix(rssi2), rssiToColor(abs(rssi2))); st7789->drawPixel(x1, rssiToPix(rssi2), rssiToColor(abs(rssi2)));
st7789->drawPixel(x1, rssiToPix(rssi2) - 1, 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) - 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 // Draw Update Cursor
st7789->drawFastVLine(x1 + 1, lower_level, -lower_level + 11, ST7789_BLACK); 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 + 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) if (max_scan_rssi[x1] == -999)
{ {
@@ -417,10 +516,13 @@ void loop()
} }
} }
// Writing pixel only if it is bigger than drone detection level // 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 if (DISABLE_WATERFALL == 0)
st7789->drawPixel(x1, w, rssiToColor(abs(max_scan_rssi[x1]), true)); {
// Waterfall Pixel
st7789->drawPixel(x1, w, rssiToColor(abs(max_scan_rssi[x1]), true));
}
detailed_scan_candidate[(int)fr] = (int)fr; detailed_scan_candidate[(int)fr] = (int)fr;
} }
@@ -431,7 +533,7 @@ void loop()
// Draw legend for windows // Draw legend for windows
if (x1 % rssi_window_size == 0 || x1 == DISPLAY_WIDTH) 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) window_max_rssi != -999)
{ {
y2 = 15; y2 = 15;
@@ -450,9 +552,15 @@ void loop()
window_max_rssi = -999; window_max_rssi = -999;
} }
// Waterfall cursor if (DISABLE_WATERFALL == 0)
st7789->drawFastHLine(0, w + 1, DISPLAY_WIDTH, ST7789_BLACK); {
st7789->drawFastHLine(0, w + 2, DISPLAY_WIDTH, ST7789_BLACK); // 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 // drone detection level line
if (x1 % 2 == 0) if (x1 % 2 == 0)
@@ -470,9 +578,9 @@ void loop()
button_pressed_counter = 0; button_pressed_counter = 0;
if (button.pressed()) if (button.pressed())
{ {
drone_detection_level++; drone_detection_level--;
if (drone_detection_level > 107) if (drone_detection_level < -107)
drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL - 20; drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL + 20;
while (button.pressedNow()) while (button.pressedNow())
{ {
delay(100); delay(100);
@@ -491,6 +599,14 @@ void loop()
heltec_deep_sleep(); 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 // Main N x-axis full loop end logic
if (x1 >= STEPS) if (x1 >= STEPS)
{ {
@@ -502,17 +618,28 @@ void loop()
#ifdef PRINT_DEBUG #ifdef PRINT_DEBUG
Serial.println("Screen End for Output: " + String(screen_update_loop_counter)); Serial.println("Screen End for Output: " + String(screen_update_loop_counter));
#endif #endif
// Doing output only after full scan // Doing output only after full scan
if (screen_update_loop_counter + 1 == SCANS_PER_DISPLAY) 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 // Scan results to max Mhz and dB in window
display_scan_end = millis(); display_scan_end = millis();
st7789->fillRect(0, 0, DISPLAY_WIDTH, 11, ST7789_BLACK); st7789->fillRect(0, 0, DISPLAY_WIDTH, 11, ST7789_BLACK);
drawText(0, 0, drawText(0, 0,
"T:" + String(display_scan_end - display_scan_start) + "/" + "T:" + String(display_scan_end - display_scan_start) + "/" +
String(rssi_single_end - rssi_single_start) + " L:-" + String(rssi_single_end - rssi_single_start) +
String(drone_detection_level) + "dB", " L:" + String(drone_detection_level) + "dB",
ST7789_BLUE); ST7789_BLUE);
/// battery(); /// battery();
+30 -17
View File
@@ -8,6 +8,7 @@
* This works on the stick, but the output on the screen gets cut off. * This works on the stick, but the output on the screen gets cut off.
*/ */
#include <Arduino.h>
// Turns the 'PRG' button into the power button, long press is off // Turns the 'PRG' button into the power button, long press is off
#define HELTEC_POWER_BUTTON // must be before "#include <heltec_unofficial.h>" #define HELTEC_POWER_BUTTON // must be before "#include <heltec_unofficial.h>"
#include <heltec_unofficial.h> #include <heltec_unofficial.h>
@@ -15,11 +16,11 @@
// Pause between transmited packets in mseconds. // Pause between transmited packets in mseconds.
// Set to zero to only transmit a packet when pressing the user button // 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. // 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. // Frequency in MHz. Keep the decimal point to designate float.
// Check your own rules and regulations to see what is legal where you are. // 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 // #define FREQUENCY 905.2 // for US
// LoRa bandwidth. Keep the decimal point to designate float. // 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", // Number from 5 to 12. Higher means slower but higher "processor gain",
// meaning (in nutshell) longer range and more robust against interference. // 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 // 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 // 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 // (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. // transmissting without an antenna can damage your hardware.
#define TRANSMIT_POWER -9 #define TRANSMIT_POWER 22
String rxdata; String rxdata;
volatile bool rxFlag = false; volatile bool rxFlag = false;
@@ -67,35 +68,41 @@ void setup()
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF)); RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
} }
int FHSS = 0.25;
float FHSS_counter = -10;
void loop() void loop()
{ {
heltec_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 // Transmit a packet every PAUSE seconds or when the button is pressed
if ((PAUSE && tx_legal && millis() - last_tx > (PAUSE)) || button.isSingleClick()) if ((PAUSE && tx_legal && millis() - last_tx > (PAUSE)) || button.isSingleClick())
{ {
// In case of button click, tell user to wait if (button.isSingleClick())
if (!tx_legal)
{ {
both.printf("Legal limit, wait %i sec.\n", fr = 1000;
(int)((minimum_pause - (millis() - last_tx)) / 1000) + 1); RADIOLIB_OR_HALT(radio.setFrequency(fr, false));
return;
} }
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(); radio.clearDio1Action();
heltec_led(50); // 50% brightness is plenty for this LED heltec_led(50); // 50% brightness is plenty for this LED
tx_time = millis(); 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; tx_time = millis() - tx_time;
heltec_led(0); heltec_led(0);
if (_radiolib_status == RADIOLIB_ERR_NONE) 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 else
{ {
both.printf("fail (%i)\n", _radiolib_status); display.printf("fail (%i)\n", _radiolib_status);
heltec_delay(100);
} }
// Maximum 1% duty cycle // Maximum 1% duty cycle
minimum_pause = tx_time * 100; minimum_pause = tx_time * 100;
@@ -111,10 +118,16 @@ void loop()
radio.readData(rxdata); radio.readData(rxdata);
if (_radiolib_status == RADIOLIB_ERR_NONE) if (_radiolib_status == RADIOLIB_ERR_NONE)
{ {
both.printf("RX [%s]\n", rxdata.c_str()); display.printf("RX [%s]\n", rxdata.c_str());
both.printf(" RSSI: %.2f dBm\n", radio.getRSSI()); display.printf(" RSSI: %.2f dBm\n", radio.getRSSI());
both.printf(" SNR: %.2f dB\n", radio.getSNR()); display.printf(" SNR: %.2f dB\n", radio.getSNR());
} }
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF)); RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
} }
FHSS_counter += 0.250;
counter++;
if (FHSS_counter > 20)
{
FHSS_counter = -10;
}
} }