mirror of
https://github.com/Genaker/LoraSA.git
synced 2026-03-28 17:42:59 +01:00
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,3 +3,5 @@
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
|
||||
out/
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -12,4 +12,7 @@
|
||||
},
|
||||
"files.insertFinalNewline": true,
|
||||
"files.autoSave": "onFocusChange",
|
||||
"files.associations": {
|
||||
"cstdint": "cpp"
|
||||
},
|
||||
}
|
||||
|
||||
43
README.md
43
README.md
@@ -6,7 +6,9 @@
|
||||
- Heltec Wireless Stick Lite V3 No Display (Not Tested)
|
||||
- Heltec Vision Master E290 - e-Ink 296 x 128 (No OSD)
|
||||
- Heltec Vision MAster T190 - color TFT 320X170 (No OSD)
|
||||
- LilyGo Radio Lora T3S3 V.2 SX1262
|
||||
- LilyGo Radio Lora T3S3 V.2 SX1262
|
||||
- LilyGo Radio Lora T3S3 V.2 SX1280
|
||||
- LilyGo Radio Lora T3_V1.6.1 SX1276 (Not Tested)
|
||||
|
||||
## RF Spectrum Analyzer using Lora Radio
|
||||
|
||||
@@ -158,6 +160,10 @@ If less, ESP32 will turn off. Fast pressing(less than 0.5 second) P button chang
|
||||
3. Connect ESP32 to USB. Install USB CP2101 drivers for Windows or other OS
|
||||
https://docs.heltec.org/general/establish_serial_connection.html#for-windows
|
||||
https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers?tab=downloads
|
||||
|
||||
## NOTE: MACOS Heltec USB driver
|
||||
https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers?tab=downloads <br/>
|
||||
I used legacy driver
|
||||
|
||||
5. Clone this Git Repo or download zip of the sources
|
||||

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

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

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

|
||||
We are using pin 41 as a Buzzer trigger. Connect buzzer + leg with pin 41 and - leg with the ground (GND). You can change the buzzer pin in the code.
|
||||
|
||||
|
||||
## Analog FPV OSD (ON SCREEN DISPLAY)
|
||||
To Enable OSD, Uncomment these lines </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
|
||||
**Heltec V3 -> DFRobot OSD** <br />
|
||||
GND -> GND <br />
|
||||
@@ -287,3 +320,11 @@ Edit **paltformio.io** uncommenting/selecting your sources
|
||||
```
|
||||
for LilyGo use env:heltec_wifi_lora_32_V3
|
||||
|
||||
# WiFi and Bluetooth BT Scanning
|
||||
Works only with OSD enabled <br/>
|
||||
Uncomment this lines
|
||||
```
|
||||
// #define OSD_ENABLED true
|
||||
// #define WIFI_SCANNING_ENABLED true
|
||||
// #define BT_SCANNING_ENABLED true
|
||||
```
|
||||
|
||||
177
SpectrumScan.py
177
SpectrumScan.py
@@ -7,30 +7,21 @@ import sys
|
||||
import numpy as np
|
||||
import matplotlib as mpl
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from argparse import RawTextHelpFormatter
|
||||
|
||||
# number of samples in each scanline
|
||||
SCAN_WIDTH = 33
|
||||
# Constants
|
||||
SCAN_WIDTH = 33 # number of samples in each scanline
|
||||
OUT_PATH = "out" # output path for saved files
|
||||
|
||||
# scanline Serial start/end markers
|
||||
SCAN_MARK_START = 'SCAN '
|
||||
SCAN_MARK_FREQ = 'FREQ '
|
||||
SCAN_MARK_END = ' END'
|
||||
|
||||
# output path
|
||||
OUT_PATH = 'out'
|
||||
|
||||
# default settings
|
||||
# Default settings
|
||||
DEFAULT_BAUDRATE = 115200
|
||||
DEFAULT_COLOR_MAP = 'viridis'
|
||||
DEFAULT_SCAN_LEN = 200
|
||||
DEFAULT_RSSI_OFFSET = -11
|
||||
|
||||
# Print iterations progress
|
||||
# from https://stackoverflow.com/questions/3173320/text-progress-bar-in-terminal-with-block-characters
|
||||
def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1, length = 50, fill = '█', printEnd = "\r"):
|
||||
def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=50, fill='█', print_end="\r"):
|
||||
"""
|
||||
Call in a loop to create terminal progress bar
|
||||
@params:
|
||||
@@ -41,136 +32,104 @@ def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1,
|
||||
decimals - Optional : positive number of decimals in percent complete (Int)
|
||||
length - Optional : character length of bar (Int)
|
||||
fill - Optional : bar fill character (Str)
|
||||
printEnd - Optional : end character (e.g. "\r", "\r\n") (Str)
|
||||
print_end - Optional : end character (e.g. "\r", "\r\n") (Str)
|
||||
"""
|
||||
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
|
||||
filledLength = int(length * iteration // total)
|
||||
bar = fill * filledLength + '-' * (length - filledLength)
|
||||
print(f'\r{prefix} |{bar}| {percent}% {suffix}', end = printEnd)
|
||||
filled_length = int(length * iteration // total)
|
||||
bar = fill * filled_length + '-' * (length - filled_length)
|
||||
print(f'\r{prefix} |{bar}| {percent}% {suffix}', end=print_end)
|
||||
if iteration == total:
|
||||
print()
|
||||
|
||||
def parse_line(line):
|
||||
"""Parse a JSON line from the serial input."""
|
||||
return json.loads(line)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''
|
||||
RadioLib SX126x_Spectrum_Scan plotter script. Displays output from SX126x_Spectrum_Scan example
|
||||
as grayscale and
|
||||
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''\
|
||||
Parse serial data from LOG_DATA_JSON functionality.
|
||||
|
||||
Depends on pyserial and matplotlib, install by:
|
||||
'python3 -m pip install pyserial matplotlib'
|
||||
|
||||
Step-by-step guide on how to use the script:
|
||||
1. Upload the SX126x_Spectrum_Scan example to your Arduino board with SX1262 connected.
|
||||
1. #define LOG_DATA_JSON true - add this line in main.cpp, upload to device
|
||||
2. Run the script with appropriate arguments.
|
||||
3. Once the scan is complete, output files will be saved to out/
|
||||
''')
|
||||
parser.add_argument('port',
|
||||
type=str,
|
||||
help='COM port to connect to the device')
|
||||
parser.add_argument('--speed',
|
||||
default=DEFAULT_BAUDRATE,
|
||||
type=int,
|
||||
help=f'COM port baudrate (defaults to {DEFAULT_BAUDRATE})')
|
||||
parser.add_argument('--map',
|
||||
default=DEFAULT_COLOR_MAP,
|
||||
type=str,
|
||||
help=f'Matplotlib color map to use for the output (defaults to "{DEFAULT_COLOR_MAP}")')
|
||||
parser.add_argument('--len',
|
||||
default=DEFAULT_SCAN_LEN,
|
||||
type=int,
|
||||
help=f'Number of scanlines to record (defaults to {DEFAULT_SCAN_LEN})')
|
||||
parser.add_argument('--offset',
|
||||
default=DEFAULT_RSSI_OFFSET,
|
||||
type=int,
|
||||
help=f'Default RSSI offset in dBm (defaults to {DEFAULT_RSSI_OFFSET})')
|
||||
parser.add_argument('--freq',
|
||||
default=-1,
|
||||
type=float,
|
||||
help=f'Default starting frequency in MHz')
|
||||
parser.add_argument('port', type=str, help='COM port to connect to the device')
|
||||
parser.add_argument('--speed', default=DEFAULT_BAUDRATE, type=int,
|
||||
help=f'COM port baudrate (defaults to {DEFAULT_BAUDRATE})')
|
||||
parser.add_argument('--map', default=DEFAULT_COLOR_MAP, type=str,
|
||||
help=f'Matplotlib color map to use for the output (defaults to "{DEFAULT_COLOR_MAP}")')
|
||||
parser.add_argument('--len', default=DEFAULT_SCAN_LEN, type=int,
|
||||
help=f'Number of scanlines to record (defaults to {DEFAULT_SCAN_LEN})')
|
||||
parser.add_argument('--offset', default=DEFAULT_RSSI_OFFSET, type=int,
|
||||
help=f'Default RSSI offset in dBm (defaults to {DEFAULT_RSSI_OFFSET})')
|
||||
parser.add_argument('--freq', default=-1, type=float,
|
||||
help='Default starting frequency in MHz')
|
||||
args = parser.parse_args()
|
||||
|
||||
freq_mode = False
|
||||
# Create the result array
|
||||
scan_len = args.len
|
||||
if (args.freq != -1):
|
||||
freq_mode = True
|
||||
scan_len = 1000
|
||||
arr = np.zeros((scan_len, SCAN_WIDTH))
|
||||
|
||||
# create the color map and the result array
|
||||
arr = np.zeros((SCAN_WIDTH, scan_len))
|
||||
|
||||
# scanline counter
|
||||
# Scanline counter
|
||||
row = 0
|
||||
|
||||
# list of frequencies in frequency mode
|
||||
# List of frequencies
|
||||
freq_list = []
|
||||
|
||||
# open the COM port
|
||||
# Open the COM port
|
||||
with serial.Serial(args.port, args.speed, timeout=None) as com:
|
||||
while(True):
|
||||
# update the progress bar
|
||||
if not freq_mode:
|
||||
printProgressBar(row, scan_len)
|
||||
while True:
|
||||
# Update the progress bar
|
||||
print_progress_bar(row, scan_len)
|
||||
|
||||
# read a single line
|
||||
# Read a single line
|
||||
try:
|
||||
line = com.readline().decode('utf-8')
|
||||
except:
|
||||
line = com.readline().decode('utf-8').strip()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
if SCAN_MARK_FREQ in line:
|
||||
new_freq = float(line.split(' ')[1])
|
||||
if (len(freq_list) > 1) and (new_freq < freq_list[-1]):
|
||||
break
|
||||
if line.startswith("{"):
|
||||
try:
|
||||
data = parse_line(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
freq_list.append(new_freq)
|
||||
print('{:.3f}'.format(new_freq), end = '\r')
|
||||
continue
|
||||
# get the lowest frequency for now, could be averaged too.
|
||||
freq = data["low_range_freq"]
|
||||
|
||||
# check the markers
|
||||
if (SCAN_MARK_START in line) and (SCAN_MARK_END in line):
|
||||
# get the values
|
||||
scanline = line[len(SCAN_MARK_START):-len(SCAN_MARK_END)].split(',')
|
||||
for col in range(SCAN_WIDTH):
|
||||
arr[col][row] = int(scanline[col])
|
||||
# value in negative, eg: -70
|
||||
rssi = int(data["value"])
|
||||
|
||||
if freq not in freq_list:
|
||||
freq_list.append(freq)
|
||||
|
||||
# increment the row counter
|
||||
row = row + 1
|
||||
col = freq_list.index(freq)
|
||||
arr[row][col] = rssi
|
||||
|
||||
# Increment the row counter
|
||||
row += 1
|
||||
|
||||
# check if we're done
|
||||
if (not freq_mode) and (row >= scan_len):
|
||||
# Check if we're done
|
||||
if row >= scan_len:
|
||||
break
|
||||
|
||||
# scale to the number of scans (sum of any given scanline)
|
||||
num_samples = arr.sum(axis=0)[0]
|
||||
arr *= (num_samples/arr.max())
|
||||
|
||||
if freq_mode:
|
||||
scan_len = len(freq_list)
|
||||
# Create the figure
|
||||
fig, ax = plt.subplots(figsize=(12, 8))
|
||||
|
||||
# create the figure
|
||||
fig, ax = plt.subplots()
|
||||
# Display the result as heatmap
|
||||
extent = [0, scan_len, freq_list[0], freq_list[-1]]
|
||||
im = ax.imshow(arr.T, cmap=args.map, extent=extent, aspect='auto', origin='lower')
|
||||
fig.colorbar(im, label='RSSI (dBm)')
|
||||
|
||||
# display the result as heatmap
|
||||
extent = [0, scan_len, -4*(SCAN_WIDTH + 1), args.offset]
|
||||
if freq_mode:
|
||||
extent[0] = freq_list[0]
|
||||
extent[1] = freq_list[-1]
|
||||
im = ax.imshow(arr[:,:scan_len], cmap=args.map, extent=extent)
|
||||
fig.colorbar(im)
|
||||
|
||||
# set some properites and show
|
||||
# Set plot properties and show
|
||||
timestamp = datetime.now().strftime('%y-%m-%d %H-%M-%S')
|
||||
title = f'RadioLib SX126x Spectral Scan {timestamp}'
|
||||
if freq_mode:
|
||||
plt.xlabel("Frequency [Hz]")
|
||||
else:
|
||||
plt.xlabel("Time [sample]")
|
||||
plt.ylabel("RSSI [dBm]")
|
||||
ax.set_aspect('auto')
|
||||
title = f'LoraSA Spectral Scan {timestamp}'
|
||||
plt.xlabel("Time (sample)")
|
||||
plt.ylabel("Frequency (MHz)")
|
||||
fig.suptitle(title)
|
||||
fig.canvas.manager.set_window_title(title)
|
||||
plt.savefig(f'{OUT_PATH}/{title.replace(" ", "_")}.png', dpi=300)
|
||||
plt.show()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
42
data/index.html
Normal file
42
data/index.html
Normal 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
data/style.css
Normal file
118
data/style.css
Normal 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;
|
||||
}
|
||||
0
data/text.txt
Normal file
0
data/text.txt
Normal file
@@ -23,6 +23,7 @@
|
||||
#define DISPLAY_WIDTH 296
|
||||
#define DISPLAY_HEIGHT 128
|
||||
// Without this line Lora Radio doesn't work with heltec lib
|
||||
#define BUTTON 21
|
||||
#define ARDUINO_heltec_wifi_32_lora_V3
|
||||
#include "heltec_unofficial.h"
|
||||
|
||||
@@ -96,6 +97,7 @@ bool first_run, new_pixel, detected_x = false;
|
||||
// drone detection flag
|
||||
bool detected = false;
|
||||
uint64_t drone_detection_level = 90;
|
||||
bool detection_level_changed = false;
|
||||
uint64_t drone_detected_frequency_start = 0;
|
||||
uint64_t drone_detected_frequency_end = 0;
|
||||
uint64_t detection_count = 0;
|
||||
@@ -250,18 +252,21 @@ void VextOFF(void) // Vext default OFF
|
||||
|
||||
constexpr int lower_level = 108;
|
||||
constexpr int up_level = 40;
|
||||
constexpr int start_pixel = 80;
|
||||
|
||||
int rssiToPix(int rssi)
|
||||
{
|
||||
// Bigger is lower signal
|
||||
if (abs(rssi) >= lower_level)
|
||||
{
|
||||
return lower_level - 1;
|
||||
return start_pixel - 1;
|
||||
}
|
||||
if (abs(rssi) <= up_level)
|
||||
{
|
||||
return up_level;
|
||||
return start_pixel - up_level;
|
||||
}
|
||||
return abs(rssi);
|
||||
|
||||
return start_pixel - (lower_level - abs(rssi));
|
||||
}
|
||||
|
||||
long timeSinceLastModeSwitch = 0;
|
||||
@@ -287,10 +292,61 @@ long display_scan_start = 0;
|
||||
long display_scan_end = 0;
|
||||
long display_scan_i_end = 0;
|
||||
int scan_iterations = 0;
|
||||
bool waterfall_values[DISPLAY_HEIGHT][DISPLAY_WIDTH] = {false};
|
||||
|
||||
constexpr unsigned int SCANS_PER_DISPLAY = 5;
|
||||
constexpr unsigned int STATUS_BAR_HEIGHT = 5;
|
||||
|
||||
void clear_rectangle(int x, int y, int width, int height)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
display.clearPixel(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void button_logic(void)
|
||||
{
|
||||
heltec_loop();
|
||||
button_pressed_counter = 0;
|
||||
if (button.pressed())
|
||||
{
|
||||
drone_detection_level++;
|
||||
detection_level_changed = true;
|
||||
if (drone_detection_level > 107)
|
||||
drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL - 20;
|
||||
clear_rectangle(0, 0, 10, 10);
|
||||
display.setFont(ArialMT_Plain_10);
|
||||
|
||||
display.drawString(0, 0,
|
||||
"T:" + String(display_scan_end - display_scan_start) + "/" +
|
||||
String(display_scan_i_end - display_scan_start) + " L:-" +
|
||||
String(drone_detection_level) + "dB");
|
||||
while (button.pressedNow())
|
||||
{
|
||||
|
||||
display.display();
|
||||
button_pressed_counter++;
|
||||
// button.update();
|
||||
|
||||
if (button_pressed_counter > 18)
|
||||
{
|
||||
// Some sign there
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (button_pressed_counter < 9 && button_pressed_counter > 5)
|
||||
{
|
||||
display.clear();
|
||||
display.display();
|
||||
heltec_deep_sleep();
|
||||
}
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (screen_update_loop_counter == 0)
|
||||
@@ -307,9 +363,13 @@ void loop()
|
||||
int u = 0;
|
||||
for (int i = 0; i < SAMPLES_RSSI; i++)
|
||||
{
|
||||
|
||||
radio.setFrequency((float)fr + (float)(rssi_mhz_step * u),
|
||||
false); // false = no calibration need here
|
||||
state = radio.setFrequency((float)fr + (float)(rssi_mhz_step * u),
|
||||
true); // false = no calibration need here
|
||||
int radio_error_count = 0;
|
||||
if (state != RADIOLIB_ERR_NONE)
|
||||
{
|
||||
Serial.println("E:setFrequency:" + String(freq));
|
||||
}
|
||||
u++;
|
||||
if (rssi_mhz_step * u >= mhz_step)
|
||||
{
|
||||
@@ -319,10 +379,14 @@ void loop()
|
||||
rssi2 = radio.getRSSI(false);
|
||||
scan_iterations++;
|
||||
if (rssi2 > lower_level)
|
||||
{
|
||||
max_scan_rssi[x1] = rssi2;
|
||||
continue;
|
||||
}
|
||||
#ifdef PRINT_DEBUG
|
||||
Serial.println(String(fr) + ":" + String(rssi2));
|
||||
#endif
|
||||
button_logic();
|
||||
// display.drawString(x1, (int)y2, String(fr) + ":" + String(rssi2));
|
||||
display.setPixel(x1, rssiToPix(rssi2));
|
||||
|
||||
@@ -332,6 +396,16 @@ void loop()
|
||||
}
|
||||
}
|
||||
|
||||
// Waterfall per scan not per screen
|
||||
if (abs(max_scan_rssi[x1]) <= drone_detection_level)
|
||||
{
|
||||
waterfall_values[w][x1] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
waterfall_values[w][x1] = false;
|
||||
}
|
||||
|
||||
// drone detection level line
|
||||
if (x1 % 2 == 0)
|
||||
{
|
||||
@@ -343,9 +417,15 @@ void loop()
|
||||
{
|
||||
display_scan_i_end = millis();
|
||||
}
|
||||
button_logic();
|
||||
// Main N x-axis full loop end logic
|
||||
if (x1 >= STEPS)
|
||||
{
|
||||
w++;
|
||||
if (w >= DISPLAY_HEIGHT - start_pixel - 13)
|
||||
{
|
||||
w = 0;
|
||||
}
|
||||
if (screen_update_loop_counter == SCANS_PER_DISPLAY)
|
||||
{
|
||||
// max Mhz and dB in window
|
||||
@@ -372,17 +452,29 @@ void loop()
|
||||
display.drawString(i - rssi_window_size + 5, y2 + 10,
|
||||
String(window_max_fr));
|
||||
// Vertical lines between windows
|
||||
for (int l = y2; l < 100; l += 4)
|
||||
for (int l = y2; l < start_pixel; l += 4)
|
||||
{
|
||||
display.setPixel(i, l);
|
||||
}
|
||||
}
|
||||
window_max_rssi = -999;
|
||||
}
|
||||
|
||||
// Draw Waterfall
|
||||
for (int y = 0; y < DISPLAY_HEIGHT; y++)
|
||||
if (waterfall_values[y][i] == true)
|
||||
{
|
||||
display.setPixel(i, start_pixel + 5 + y);
|
||||
}
|
||||
}
|
||||
// Draw Waterfall cursor
|
||||
display.drawHorizontalLine(0, start_pixel + 5 + w, DISPLAY_WIDTH);
|
||||
|
||||
display_scan_end = millis();
|
||||
|
||||
if (detection_level_changed == true)
|
||||
{
|
||||
clear_rectangle(0, 0, 100, 10);
|
||||
}
|
||||
display.setFont(ArialMT_Plain_10);
|
||||
display.drawString(0, 0,
|
||||
"T:" + String(display_scan_end - display_scan_start) +
|
||||
@@ -396,6 +488,7 @@ void loop()
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
// ToDo: it doesn't work
|
||||
battery();
|
||||
// iteration full scan / samples pixel step / numbers of scan per display
|
||||
display.drawString(DISPLAY_WIDTH - ((DISPLAY_WIDTH / 6) * 2) - 5, 0,
|
||||
@@ -409,21 +502,21 @@ void loop()
|
||||
"s:" + String(mhz_step));
|
||||
|
||||
// Draw a line horizontally
|
||||
display.drawHorizontalLine(0, lower_level + 1, DISPLAY_WIDTH);
|
||||
display.drawHorizontalLine(0, 1 + start_pixel, DISPLAY_WIDTH);
|
||||
// Generate Ticks
|
||||
for (int x = 0; x < DISPLAY_WIDTH; x++)
|
||||
{
|
||||
if (x % (DISPLAY_WIDTH / 2) == 0 && x > 5)
|
||||
{
|
||||
display.drawVerticalLine(x, lower_level + 1, 11);
|
||||
display.drawVerticalLine(x, 1 + start_pixel, 11);
|
||||
// central tick width
|
||||
// display.drawVerticalLine(x - 1, lower_level + 1, 8);
|
||||
// display.drawVerticalLine(x + 1, lower_level + 1, 8);
|
||||
}
|
||||
if (x % 10 == 0 || x == 0)
|
||||
display.drawVerticalLine(x, lower_level + 1, 6);
|
||||
display.drawVerticalLine(x, 1 + start_pixel, 6);
|
||||
if (x % 5 == 0)
|
||||
display.drawVerticalLine(x, lower_level + 1, 3);
|
||||
display.drawVerticalLine(x, 1 + start_pixel, 3);
|
||||
}
|
||||
display.setFont(ArialMT_Plain_10);
|
||||
|
||||
@@ -441,13 +534,14 @@ void loop()
|
||||
String(FREQ_BEGIN + (((int)fr - FREQ_BEGIN) -
|
||||
((int)fr - FREQ_BEGIN) / 4)));
|
||||
// End Mhz
|
||||
display.drawString(DISPLAY_WIDTH - 24, DISPLAY_HEIGHT - 10, String((int)fr));
|
||||
display.drawString(DISPLAY_WIDTH - 20, DISPLAY_HEIGHT - 10, String((int)fr));
|
||||
|
||||
display.display();
|
||||
// display will be cleared next scan iteration. it is just buffer clear
|
||||
// memset(buffer, 0, displayBufferSize);
|
||||
display.clear();
|
||||
screen_update_loop_counter = 0;
|
||||
|
||||
scan_iterations = 0;
|
||||
display_scan_i_end = 0;
|
||||
}
|
||||
@@ -480,7 +574,7 @@ void setup()
|
||||
delay(1000);
|
||||
display.clear();
|
||||
Serial.begin(115200);
|
||||
w = WATERFALL_START;
|
||||
w = 0; // WATERFALL_START;
|
||||
init_radio();
|
||||
state = radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_NONE);
|
||||
if (state != RADIOLIB_ERR_NONE)
|
||||
|
||||
126
gps_src/main.cpp
Normal file
126
gps_src/main.cpp
Normal 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("-----------------------------");
|
||||
}
|
||||
@@ -13,8 +13,67 @@
|
||||
#ifndef _DFRobot_OSD_H_
|
||||
#define _DFRobot_OSD_H_
|
||||
|
||||
#define MAX_POWER_LEVELS 33
|
||||
|
||||
#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
|
||||
#ifdef ENABLE_DBG
|
||||
#define DBG(...) \
|
||||
|
||||
55
include/File.h
Normal file
55
include/File.h
Normal 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();
|
||||
}
|
||||
@@ -1,32 +1,4 @@
|
||||
|
||||
#define UNUSED_PIN (0)
|
||||
// LilyGo defined
|
||||
|
||||
#define I2C_SDA 18
|
||||
#define I2C_SCL 17
|
||||
#define OLED_RST UNUSED_PIN
|
||||
|
||||
#define RADIO_SCLK_PIN 5
|
||||
#define RADIO_MISO_PIN 3
|
||||
#define RADIO_MOSI_PIN 6
|
||||
#define RADIO_CS_PIN 7
|
||||
|
||||
#define SDCARD_MOSI 11
|
||||
#define SDCARD_MISO 2
|
||||
#define SDCARD_SCLK 14
|
||||
#define SDCARD_CS 13
|
||||
|
||||
#define BOARD_LED 37
|
||||
#define LED_ON HIGH
|
||||
|
||||
#define BUTTON_PIN 0
|
||||
#define ADC_PIN 1
|
||||
|
||||
#define RADIO_RST_PIN 8
|
||||
|
||||
#define RADIO_DIO1_PIN 33
|
||||
#define RADIO_BUSY_PIN 34
|
||||
|
||||
// Define for our code
|
||||
#define RST_OLED UNUSED_PIN
|
||||
#define LED BOARD_LED
|
||||
@@ -41,8 +13,11 @@
|
||||
#else
|
||||
#define DISPLAY_WIDTH 128
|
||||
#define DISPLAY_HEIGHT 64
|
||||
#include "OLEDDisplayUi.h"
|
||||
// #include "OLEDDisplayUi.h"
|
||||
// #include "SH1106Wire.h"
|
||||
// #include "SSD1306Brzo.h"
|
||||
#include "SSD1306Wire.h"
|
||||
|
||||
#endif
|
||||
#define ARDUINO_heltec_wifi_32_lora_V3
|
||||
#ifndef HELTEC_NO_RADIO_INSTANCE
|
||||
@@ -52,13 +27,24 @@
|
||||
#include <SPI.h>
|
||||
SPIClass *hspi = new SPIClass(2);
|
||||
SX1262 radio = new Module(SS, DIO1, RST_LoRa, BUSY_LoRa, *hspi);
|
||||
#else
|
||||
#else // ARDUINO_heltec_wifi_32_lora_V3
|
||||
#ifdef USING_SX1280PA
|
||||
SX1280 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
|
||||
#endif // end USING_SX1280PA
|
||||
#ifdef USING_SX1262
|
||||
// Default SPI on pins from pins_arduino.h
|
||||
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void heltec_loop() {}
|
||||
#endif // end USING_SX1262
|
||||
#ifdef USING_LR1121
|
||||
// Default SPI on pins from pins_arduino.h
|
||||
LR1121 radio = new Module(RADIO_CS_PIN, RADIO_DIO9_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
|
||||
#endif // end USING_LR1121
|
||||
#ifdef USING_SX1276
|
||||
// Default SPI on pins from pins_arduino.h
|
||||
SX1276 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
|
||||
#endif // end USING_SX1276
|
||||
#endif // end ARDUINO_heltec_wifi_32_lora_V3
|
||||
#endif // end HELTEC_NO_RADIO_INSTANCE
|
||||
|
||||
void heltec_led(int led) {}
|
||||
|
||||
@@ -101,16 +87,29 @@ class PrintSplitter : public Print
|
||||
#else
|
||||
#define DISPLAY_GEOMETRY GEOMETRY_128_64
|
||||
#endif
|
||||
SSD1306Wire display(0x3c, 18, 17, DISPLAY_GEOMETRY);
|
||||
#define SCREEN_ADDRESS 0x3C
|
||||
|
||||
SSD1306Wire display(SCREEN_ADDRESS, I2C_SDA, I2C_SCL, DISPLAY_GEOMETRY);
|
||||
// SH1106Wire display(0x3c, I2C_SDA, I2C_SCL, DISPLAY_GEOMETRY);
|
||||
PrintSplitter both(Serial, display);
|
||||
#else
|
||||
Print &both = Serial;
|
||||
#endif
|
||||
// some fake pin
|
||||
#define BUTTON 38
|
||||
#ifdef T3_V1_6_SX1276
|
||||
#define BUTTON_PIN 22
|
||||
#endif
|
||||
#define BUTTON BUTTON_PIN
|
||||
#include "HotButton.h"
|
||||
HotButton button(BUTTON);
|
||||
|
||||
void heltec_loop()
|
||||
{
|
||||
#ifndef DT3_V1_6_SX1276
|
||||
button.update();
|
||||
#endif
|
||||
}
|
||||
|
||||
// This file contains a binary patch for the SX1262
|
||||
#include "modules/SX126x/patches/SX126x_patch_scan.h"
|
||||
|
||||
@@ -161,7 +160,7 @@ void heltec_setup()
|
||||
#ifndef HELTEC_NO_DISPLAY_INSTANCE
|
||||
heltec_display_power(true);
|
||||
display.init();
|
||||
display.setContrast(200);
|
||||
// display.setContrast(200);
|
||||
display.flipScreenVertically();
|
||||
#endif
|
||||
}
|
||||
|
||||
205
include/WIFI_SERVER.h
Normal file
205
include/WIFI_SERVER.h
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,50 @@
|
||||
#ifndef __GLOBAL_CONFIG_H__
|
||||
#define __GLOBAL_CONFIG_H__
|
||||
|
||||
#include "utilities.h"
|
||||
|
||||
#ifndef FREQ_BEGIN
|
||||
// frequency range in MHz to scan
|
||||
#define FREQ_BEGIN 850
|
||||
#endif
|
||||
|
||||
#ifndef FREQ_END
|
||||
// TODO: if % RANGE_PER_PAGE != 0
|
||||
#define FREQ_END 950
|
||||
#endif
|
||||
|
||||
// Measurement bandwidth. Allowed bandwidth values (in kHz) are:
|
||||
// 4.8, 5.8, 7.3, 9.7, 11.7, 14.6, 19.5, 23.4, 29.3, 39.0, 46.9, 58.6,
|
||||
// 78.2, 93.8, 117.3, 156.2, 187.2, 234.3, 312.0, 373.6 and 467.0
|
||||
#define BANDWIDTH 467.0
|
||||
#define BANDWIDTH_SX1280 406.
|
||||
|
||||
// Detection level from the 33 levels. The higher number is more sensitive
|
||||
#define DEFAULT_DRONE_DETECTION_LEVEL 18
|
||||
|
||||
#define BUZZER_PIN 41
|
||||
|
||||
#ifdef LILYGO
|
||||
#define BUZZER_PIN 45
|
||||
#endif
|
||||
#ifdef T3_V1_6_SX1276
|
||||
#define BUZZER_PIN 35
|
||||
#endif
|
||||
|
||||
// REB trigger PIN
|
||||
#define REB_PIN 42
|
||||
#ifdef T3_V1_6_SX1276
|
||||
#define REB_PIN 35
|
||||
#endif
|
||||
|
||||
#define WATERFALL_ENABLED true
|
||||
#define WATERFALL_START 37
|
||||
|
||||
#ifdef LILYGO
|
||||
#define LED 46
|
||||
#define LED BOARD_LED
|
||||
#endif // end not LILYGO
|
||||
#ifdef T3_V1_6_SX1276
|
||||
#define LED BOARD_LED
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
27
include/ui.h
27
include/ui.h
@@ -8,6 +8,9 @@
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#include <charts.h>
|
||||
#include <scan.h>
|
||||
|
||||
// #include <heltec_unofficial.h>
|
||||
|
||||
// (optional) major and minor tick-marks at x MHz
|
||||
@@ -31,13 +34,21 @@
|
||||
|
||||
#define SCREEN_HEIGHT 64 // ???? not used
|
||||
|
||||
// publish functions
|
||||
#ifdef Vision_Master_E290
|
||||
extern void UI_Init(DEPG0290BxS800FxX_BW *);
|
||||
#else
|
||||
extern void UI_Init(SSD1306Wire *);
|
||||
#endif
|
||||
extern void UI_displayDecorate(int, int, bool);
|
||||
extern void UI_setLedFlag(bool);
|
||||
extern void UI_Init(Display_t *);
|
||||
extern void UI_clearPlotter(void);
|
||||
extern void UI_clearTopStatus(void);
|
||||
extern void UI_drawCursor(int16_t);
|
||||
|
||||
struct StatusBar : Chart
|
||||
{
|
||||
Scan &r;
|
||||
bool ui_initialized;
|
||||
uint16_t scan_progress_count;
|
||||
|
||||
StatusBar(Display_t &d, uint16_t x, uint16_t y, uint16_t w, Scan &r)
|
||||
: Chart(d, x, y, w, LABEL_HEIGHT), r(r), ui_initialized(false),
|
||||
scan_progress_count(0) {};
|
||||
|
||||
virtual void clearStatus();
|
||||
virtual void draw() override;
|
||||
};
|
||||
|
||||
194
lib/charts/BarChart.cpp
Normal file
194
lib/charts/BarChart.cpp
Normal 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
lib/charts/StackedChart.cpp
Normal file
95
lib/charts/StackedChart.cpp
Normal 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
lib/charts/UptimeClock.cpp
Normal file
28
lib/charts/UptimeClock.cpp
Normal 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
lib/charts/WaterfallChart.cpp
Normal file
69
lib/charts/WaterfallChart.cpp
Normal 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
lib/charts/charts.h
Normal file
178
lib/charts/charts.h
Normal 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
lib/events/events.h
Normal file
46
lib/events/events.h
Normal 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"
|
||||
|
||||
#if defined(HAS_SDCARD)
|
||||
@@ -927,3 +929,4 @@ bool beginGPS()
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
#endif // #ifdef LILYGO
|
||||
@@ -486,6 +486,8 @@
|
||||
|
||||
#define USING_DIO2_AS_RF_SWITCH
|
||||
|
||||
#elif defined(HELTEC)
|
||||
// just to prevent error
|
||||
#elif defined(T_BEAM_S3_BPF)
|
||||
|
||||
#ifndef USING_SX1278
|
||||
153
lib/models/WaterfallModel.cpp
Normal file
153
lib/models/WaterfallModel.cpp
Normal 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
lib/models/models.h
Normal file
27
lib/models/models.h
Normal 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
lib/scan/scan.cpp
Normal file
189
lib/scan/scan.cpp
Normal 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
lib/scan/scan.h
Normal file
81
lib/scan/scan.h
Normal 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
|
||||
4
ml_research/Diagram_ML.svg
Normal file
4
ml_research/Diagram_ML.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 18 KiB |
13
ml_research/datasets.md
Normal file
13
ml_research/datasets.md
Normal 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
|
||||

|
||||
136
platformio.ini
136
platformio.ini
@@ -9,12 +9,15 @@
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = heltec_wifi_lora_32_V3
|
||||
; for env:vision-master-e190
|
||||
; src_dir = tft_src
|
||||
; for env:vision-master-e290
|
||||
; src_dir = eink_src
|
||||
; for env:heltec_wifi_lora_32_V3
|
||||
; src_dir = src ;;Default
|
||||
; for heltec_wifi_lora_32_V3-test-signal-generator
|
||||
; src_dir = trans_src
|
||||
|
||||
[env:heltec_wifi_lora_32_V3]
|
||||
platform = espressif32
|
||||
@@ -23,22 +26,49 @@ framework = arduino
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000
|
||||
board_build.filesystem = littlefs
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
|
||||
build_flags = -DHELTEC_POWER_BUTTON
|
||||
bblanchon/ArduinoJson@^7.2.0
|
||||
ESP Async WebServer
|
||||
build_flags =
|
||||
-DHELTEC_POWER_BUTTON
|
||||
-DHELTEC
|
||||
|
||||
|
||||
[env:lilygo-T3S3-v1-2]
|
||||
[env:heltec_wifi_lora_32_V3_433]
|
||||
platform = espressif32
|
||||
board = heltec_wifi_lora_32_V3
|
||||
framework = arduino
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000
|
||||
board_build.filesystem = littlefs
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
bblanchon/ArduinoJson@^7.2.0
|
||||
ESP Async WebServer
|
||||
build_flags =
|
||||
-DHELTEC_POWER_BUTTON
|
||||
-DHELTEC
|
||||
-DFREQ_BEGIN=130
|
||||
-DFREQ_END=180
|
||||
-DRADIOLIB_CHECK_PARAMS=0
|
||||
|
||||
[env:lilygo-T3S3-v1-2-sx1262]
|
||||
platform = espressif32
|
||||
board = t3_s3_v1_x
|
||||
framework = arduino
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000
|
||||
board_build.filesystem = littlefs
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
RadioLib
|
||||
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
|
||||
U8g2
|
||||
XPowersLib
|
||||
ESP Async WebServer
|
||||
build_flags =
|
||||
-DLILYGO
|
||||
-DT3_S3_V1_2_SX1262
|
||||
@@ -51,6 +81,91 @@ build_flags =
|
||||
-DARDUINO_LILYGO_T3_S3_V1_X
|
||||
-DARDUINO_USB_MODE=1
|
||||
|
||||
[env:lilygo-T3S3-v1-2-lr1121]
|
||||
platform = espressif32
|
||||
board = t3_s3_v1_x
|
||||
framework = arduino
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000
|
||||
board_build.filesystem = littlefs
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
RadioLib
|
||||
U8g2
|
||||
XPowersLib
|
||||
bblanchon/ArduinoJson@^7.2.0
|
||||
ESP Async WebServer
|
||||
build_flags =
|
||||
-DLILYGO
|
||||
-DT3_S3_V1_2_LR1121
|
||||
-DT3_V1_3_SX1262
|
||||
-DARDUINO_LILYGO_T3S3_LR1121
|
||||
-DESP32
|
||||
-DSAMPLES_RSSI=5
|
||||
-DUSING_LR1121
|
||||
-DFREQ_BEGIN=2400
|
||||
-DFREQ_END=2500
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DARDUINO_LILYGO_T3_S3_V1_X
|
||||
-DARDUINO_USB_MODE=1
|
||||
|
||||
|
||||
[env:lilygo-T3S3-v1-2-sx1280]
|
||||
platform = espressif32
|
||||
board = t3_s3_v1_x
|
||||
framework = arduino
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000
|
||||
board_build.filesystem = littlefs
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
RadioLib
|
||||
U8g2
|
||||
XPowersLib
|
||||
ESP Async WebServer
|
||||
build_flags =
|
||||
-DLILYGO
|
||||
-DT3_S3_V1_2_SX1280_PA
|
||||
-DARDUINO_LILYGO_T3S3_SX1280_PA
|
||||
-DESP32
|
||||
-DUSING_SX1280PA
|
||||
-DFREQ_BEGIN=2400
|
||||
-DFREQ_END=2500
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DARDUINO_LILYGO_T3_S3_V1_X
|
||||
-DARDUINO_USB_MODE=1
|
||||
|
||||
[env:lilygo-T3-v1-6-xs1276]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
upload_speed = 115200
|
||||
monitor_speed = 115200
|
||||
board_build.filesystem = littlefs
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
RadioLib
|
||||
U8g2
|
||||
XPowersLib
|
||||
ESP Async WebServer
|
||||
build_flags =
|
||||
-DLILYGO
|
||||
-DT3_V1_6_SX1276
|
||||
-DUSING_SX1276
|
||||
-DESP32
|
||||
-DARDUINO_ARCH_ESP32
|
||||
-DARDUINO_USB_CDC_ON_BOOT=0 ;; if not 0 - Serial issue
|
||||
-DARDUINO_LILYGO_T3_V1_6
|
||||
-DARDUINO_USB_MODE=1
|
||||
|
||||
;; More old lylygo/titygo boeads defenitions you can find here:
|
||||
;; https://github.com/PTR-projects/PTR_GroundStation_firmware/blob/main/platformio.ini
|
||||
;; https://github.com/Xinyuan-LilyGO/LilyGo-LoRa-Series/blob/master/platformio.ini
|
||||
|
||||
[env:heltec_wifi_lora_32_V3-test-signal-generator]
|
||||
platform = espressif32
|
||||
board = heltec_wifi_lora_32_V3
|
||||
@@ -61,8 +176,9 @@ board_build.f_cpu = 240000000
|
||||
board_build.flash_size = 80000000L
|
||||
lib_deps =
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
|
||||
build_flags = -DLILYGO
|
||||
build_flags =
|
||||
-DHELTEC
|
||||
-DHELTEC_POWER_BUTTON
|
||||
|
||||
[env:vision-master-e290]
|
||||
platform = espressif32
|
||||
@@ -71,7 +187,8 @@ framework = arduino
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_upload.use_1200bps_touch = true
|
||||
build_flags =
|
||||
build_flags =
|
||||
-DHELTEC
|
||||
-DHELTEC_BOARD=37
|
||||
-DSLOW_CLK_TPYE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
@@ -95,7 +212,6 @@ lib_deps =
|
||||
https://github.com/HelTecAutomation/Heltec_ESP32/
|
||||
adafruit/Adafruit GFX Library@^1.11.10
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
|
||||
|
||||
[env:vision-master-t190]
|
||||
platform = espressif32
|
||||
@@ -105,6 +221,7 @@ monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_upload.use_1200bps_touch = true
|
||||
build_flags =
|
||||
-DHELTEC
|
||||
-DHELTEC_BOARD=38
|
||||
-DSLOW_CLK_TPYE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
@@ -129,3 +246,6 @@ lib_deps =
|
||||
adafruit/Adafruit GFX Library@^1.11.10
|
||||
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
|
||||
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.4
|
||||
|
||||
[env:native]
|
||||
platform = native
|
||||
|
||||
991
src/main.cpp
991
src/main.cpp
File diff suppressed because it is too large
Load Diff
267
src/ui.cpp
267
src/ui.cpp
@@ -2,6 +2,8 @@
|
||||
#include "RadioLib.h"
|
||||
#include "global_config.h"
|
||||
#include "images.h"
|
||||
#include <charts.h>
|
||||
#include <scan.h>
|
||||
|
||||
// -------------------------------------------------
|
||||
// LOCAL DEFINES
|
||||
@@ -12,24 +14,11 @@
|
||||
//
|
||||
#define SCALE_TEXT_TOP (HEIGHT + X_AXIS_WEIGHT + MAJOR_TICK_LENGTH)
|
||||
|
||||
static unsigned int start_scan_text = (128 / 2) - 3;
|
||||
// initialized flag
|
||||
static bool ui_initialized = false;
|
||||
static bool led_flag = false;
|
||||
static unsigned short int scan_progress_count = 0;
|
||||
|
||||
#ifdef Vision_Master_E290
|
||||
static DEPG0290BxS800FxX_BW *display_instance;
|
||||
#else
|
||||
//(0x3c, SDA_OLED, SCL_OLED, DISPLAY_GEOMETRY);
|
||||
static SSD1306Wire *display_instance;
|
||||
#endif
|
||||
// temporary dirty import ... to be solved durring upcoming refactoring
|
||||
extern unsigned int drone_detection_level;
|
||||
extern unsigned int RANGE_PER_PAGE;
|
||||
extern uint64_t CONF_FREQ_BEGIN;
|
||||
extern uint64_t CONF_FREQ_END;
|
||||
extern unsigned int median_frequency;
|
||||
extern unsigned int detection_count;
|
||||
extern bool SOUND_ON;
|
||||
extern unsigned int drone_detected_frequency_start;
|
||||
extern unsigned int drone_detected_frequency_end;
|
||||
extern unsigned int ranges_count;
|
||||
@@ -40,225 +29,139 @@ extern unsigned int range_item;
|
||||
|
||||
extern uint64_t loop_time;
|
||||
|
||||
#ifndef Vision_Master_E290
|
||||
void UI_Init(SSD1306Wire *display_ptr)
|
||||
void UI_Init(Display_t *display_ptr)
|
||||
{
|
||||
// init pointer to display instance.
|
||||
display_instance = display_ptr;
|
||||
// check for null ???
|
||||
display_instance->clear();
|
||||
display_ptr->clear();
|
||||
// draw the UCOG welcome logo
|
||||
display_instance->drawXbm(0, 2, 128, 64, epd_bitmap_ucog);
|
||||
display_instance->display();
|
||||
display_ptr->drawXbm(0, 2, 128, 64, epd_bitmap_ucog);
|
||||
display_ptr->display();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Vision_Master_E290
|
||||
void UI_Init(DEPG0290BxS800FxX_BW *display_ptr)
|
||||
{
|
||||
// init pointer to display instance.
|
||||
display_instance = display_ptr;
|
||||
// check for null ???
|
||||
display_instance->clear();
|
||||
// draw the UCOG welcome logo
|
||||
display_instance->drawXbm(0, 2, 128, 64, epd_bitmap_ucog);
|
||||
display_instance->display();
|
||||
}
|
||||
#endif
|
||||
|
||||
void UI_setLedFlag(bool new_status) { led_flag = new_status; }
|
||||
|
||||
void clearStatus(void)
|
||||
void StatusBar::clearStatus(void)
|
||||
{
|
||||
// clear status line
|
||||
display_instance->setColor(BLACK);
|
||||
display_instance->fillRect(0, ROW_STATUS_TEXT + 2, 128, 13);
|
||||
display_instance->setColor(WHITE);
|
||||
display.setColor(BLACK);
|
||||
display.fillRect(pos_x, pos_y, width, height);
|
||||
}
|
||||
|
||||
void UI_clearPlotter(void)
|
||||
{
|
||||
// clear the scan plot rectangle (top part)
|
||||
display_instance->setColor(BLACK);
|
||||
display_instance->fillRect(0, 0, STEPS, HEIGHT);
|
||||
display_instance->setColor(WHITE);
|
||||
// display_instance->setColor(BLACK);
|
||||
// display_instance->fillRect(0, 10, STEPS, HEIGHT - 10);
|
||||
// display_instance->setColor(WHITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draws ticks on the display at regular whole intervals.
|
||||
*
|
||||
* @param every The interval between ticks in MHz.
|
||||
* @param length The length of each tick in pixels.
|
||||
*/
|
||||
void drawTicks(float every, int length)
|
||||
void UI_clearTopStatus(void)
|
||||
{
|
||||
int first_tick;
|
||||
bool correction;
|
||||
int pixels_per_step;
|
||||
int correction_number;
|
||||
int tick;
|
||||
int tick_minor;
|
||||
int median;
|
||||
|
||||
first_tick = 0;
|
||||
//+ (every - (fr_begin - (int)(fr_begin / every) * every));
|
||||
/*if (first_tick < fr_begin)
|
||||
{
|
||||
first_tick += every;
|
||||
}*/
|
||||
correction = false;
|
||||
pixels_per_step = STEPS / (RANGE_PER_PAGE / every);
|
||||
if (STEPS / RANGE_PER_PAGE != 0)
|
||||
{
|
||||
correction = true;
|
||||
}
|
||||
correction_number = STEPS - (int)(pixels_per_step * (RANGE_PER_PAGE / every));
|
||||
tick = 0;
|
||||
tick_minor = 0;
|
||||
median = (RANGE_PER_PAGE / every) / 2;
|
||||
// TODO: (RANGE_PER_PAGE / every)
|
||||
// * 2 has twice extra steps we need to figureout correct logic or minor
|
||||
// ticks is not showing to the end
|
||||
for (int t = 0; t <= (RANGE_PER_PAGE / every) * 2; t++)
|
||||
{
|
||||
// fix if pixels per step is not int and we have shift
|
||||
if (correction && t % 2 != 0 && correction_number > 1)
|
||||
{
|
||||
// pixels_per_step++;
|
||||
correction_number--;
|
||||
}
|
||||
tick += pixels_per_step;
|
||||
tick_minor = tick / 2;
|
||||
if (tick <= 128 - 3)
|
||||
{
|
||||
display_instance->drawLine(tick, HEIGHT + X_AXIS_WEIGHT, tick,
|
||||
HEIGHT + X_AXIS_WEIGHT + length);
|
||||
// Central tick
|
||||
if (tick > (128 / 2) - 3 && tick < (128 / 2) + 3)
|
||||
{
|
||||
display_instance->drawLine(tick + 1, HEIGHT + X_AXIS_WEIGHT, tick + 1,
|
||||
HEIGHT + X_AXIS_WEIGHT + length);
|
||||
}
|
||||
}
|
||||
#ifdef MINOR_TICKS
|
||||
// Fix two ticks together
|
||||
if ((tick_minor + 1 != tick) && (tick_minor - 1 != tick) &&
|
||||
(tick_minor + 2 != tick) && (tick_minor - 2 != tick))
|
||||
{
|
||||
display_instance->drawLine(tick_minor, HEIGHT + X_AXIS_WEIGHT, tick_minor,
|
||||
HEIGHT + X_AXIS_WEIGHT + MINOR_TICK_LENGTH);
|
||||
}
|
||||
// Central tick
|
||||
if (tick_minor > (128 / 2) - 3 && tick_minor < (128 / 2) + 3)
|
||||
{
|
||||
display_instance->drawLine(tick_minor + 1, HEIGHT + X_AXIS_WEIGHT,
|
||||
tick_minor + 1,
|
||||
HEIGHT + X_AXIS_WEIGHT + MINOR_TICK_LENGTH);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// clear the scan plot rectangle (top part)
|
||||
// display_instance->setColor(BLACK);
|
||||
// display_instance->fillRect(0, 0, STEPS, 10);
|
||||
// display_instance->setColor(WHITE);
|
||||
}
|
||||
|
||||
void UI_drawCursor(int16_t possition)
|
||||
{
|
||||
// Draw animated vertical cursor on reload process
|
||||
display_instance->setColor(BLACK);
|
||||
display_instance->drawVerticalLine(possition, 0, HEIGHT);
|
||||
display_instance->drawVerticalLine(possition + 1, 0, HEIGHT);
|
||||
display_instance->drawVerticalLine(possition + 2, 0, HEIGHT);
|
||||
display_instance->setColor(WHITE);
|
||||
// display_instance->setColor(BLACK);
|
||||
// display_instance->drawVerticalLine(possition, 0, HEIGHT);
|
||||
// display_instance->drawVerticalLine(possition + 1, 0, HEIGHT);
|
||||
// display_instance->drawVerticalLine(possition + 2, 0, HEIGHT);
|
||||
// display_instance->setColor(WHITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decorates the display: everything but the plot itself.
|
||||
*/
|
||||
void UI_displayDecorate(int begin = 0, int end = 0, bool redraw = false)
|
||||
void StatusBar::draw()
|
||||
{
|
||||
uint16_t text_y = pos_y + height - 10;
|
||||
|
||||
if (!ui_initialized)
|
||||
{
|
||||
// Start and end ticks
|
||||
display_instance->fillRect(0, HEIGHT + X_AXIS_WEIGHT, 2, MAJOR_TICK_LENGTH + 1);
|
||||
display_instance->fillRect(126, HEIGHT + X_AXIS_WEIGHT, 2, MAJOR_TICK_LENGTH + 1);
|
||||
// Drone detection level
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display_instance->drawString(128, 0, String(drone_detection_level));
|
||||
display.setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display.drawString(width, 0, String(r.drone_detection_level));
|
||||
}
|
||||
if (!ui_initialized || redraw)
|
||||
if (!ui_initialized)
|
||||
{
|
||||
// Clear something
|
||||
display_instance->setColor(BLACK);
|
||||
display_instance->fillRect(0, SCALE_TEXT_TOP + 1, 128, 12);
|
||||
display_instance->setColor(WHITE);
|
||||
|
||||
/* display_instance->setColor(BLACK);
|
||||
display_instance->fillRect(0, SCALE_TEXT_TOP + 1, 128, 12);
|
||||
display_instance->setColor(WHITE);
|
||||
*/
|
||||
// Drone detection level
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display_instance->drawString(128, 0, String(drone_detection_level));
|
||||
display.setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display.drawString(pos_x + width, 0, String(r.drone_detection_level));
|
||||
// Frequency start
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display_instance->drawString(0, ROW_STATUS_TEXT,
|
||||
(begin == 0) ? String(FREQ_BEGIN) : String(begin));
|
||||
display.setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display.drawString(pos_x, text_y,
|
||||
(r.fr_begin == 0) ? String(CONF_FREQ_BEGIN)
|
||||
: String(r.fr_begin));
|
||||
|
||||
// Frequency detected
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display_instance->drawString(128 / 2, ROW_STATUS_TEXT,
|
||||
(begin == 0) ? String(median_frequency)
|
||||
: String(begin + ((end - begin) / 2)));
|
||||
display.setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display.drawString(pos_x + width / 2, text_y,
|
||||
(r.fr_begin == 0)
|
||||
? String(median_frequency)
|
||||
: String(r.fr_begin + ((r.fr_end - r.fr_begin) / 2)));
|
||||
// Frequency end
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display_instance->drawString(128, ROW_STATUS_TEXT,
|
||||
(end == 0) ? String(FREQ_END) : String(end));
|
||||
display.setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display.drawString(pos_x + width, text_y,
|
||||
(r.fr_end == 0) ? String(CONF_FREQ_END) : String(r.fr_end));
|
||||
}
|
||||
|
||||
// Status text block
|
||||
if (led_flag) // 'drone' detected
|
||||
if (r.led_flag) // 'drone' detected
|
||||
{
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display.setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
// clear status line
|
||||
clearStatus();
|
||||
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT,
|
||||
String(drone_detected_frequency_start) + ">RF<" +
|
||||
String(drone_detected_frequency_end));
|
||||
display.setColor(WHITE);
|
||||
display.drawString(pos_x + width / 2, text_y,
|
||||
String(drone_detected_frequency_start) + ">RF<" +
|
||||
String(drone_detected_frequency_end));
|
||||
}
|
||||
else
|
||||
{
|
||||
// "Scanning"
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display.setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
// clear status line
|
||||
clearStatus();
|
||||
if (scan_progress_count == 0)
|
||||
String s = "Scan \\";
|
||||
if (scan_progress_count == 1)
|
||||
{
|
||||
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan \\");
|
||||
}
|
||||
else if (scan_progress_count == 1)
|
||||
{
|
||||
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan |");
|
||||
s = "Scan |";
|
||||
}
|
||||
else if (scan_progress_count == 2)
|
||||
{
|
||||
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan /");
|
||||
s = "Scan /";
|
||||
}
|
||||
else if (scan_progress_count == 3)
|
||||
{
|
||||
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan -");
|
||||
s = "Scan -";
|
||||
}
|
||||
scan_progress_count++;
|
||||
if (scan_progress_count >= 4)
|
||||
{
|
||||
scan_progress_count = 0;
|
||||
}
|
||||
display.setColor(WHITE);
|
||||
display.drawString(pos_x + width / 2 - 3, text_y, s);
|
||||
}
|
||||
|
||||
if (led_flag == true && detection_count >= 5)
|
||||
if (r.led_flag && r.detection_count >= 5)
|
||||
{
|
||||
digitalWrite(LED, HIGH);
|
||||
if (SOUND_ON)
|
||||
if (r.sound_on)
|
||||
{
|
||||
tone(BUZZER_PIN, 104, 100);
|
||||
}
|
||||
digitalWrite(REB_PIN, HIGH);
|
||||
led_flag = false;
|
||||
r.led_flag = false;
|
||||
}
|
||||
else if (!redraw)
|
||||
else if (!r.led_flag)
|
||||
{
|
||||
digitalWrite(LED, LOW);
|
||||
}
|
||||
@@ -266,39 +169,29 @@ void UI_displayDecorate(int begin = 0, int end = 0, bool redraw = false)
|
||||
if (ranges_count == 0)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display_instance->drawString(0, ROW_STATUS_TEXT, String(loop_time));
|
||||
display.setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display.drawString(pos_x, text_y, String(loop_time));
|
||||
#else
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display_instance->drawString(0, ROW_STATUS_TEXT, String(FREQ_BEGIN));
|
||||
display.setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display.drawString(pos_x, text_y, String(CONF_FREQ_BEGIN));
|
||||
|
||||
#endif
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display_instance->drawString(128, ROW_STATUS_TEXT, String(FREQ_END));
|
||||
display.setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display.drawString(pos_x + width, text_y, String(CONF_FREQ_END));
|
||||
}
|
||||
else if (ranges_count > 0)
|
||||
{
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display_instance->drawString(0, ROW_STATUS_TEXT,
|
||||
String(SCAN_RANGES[range_item] / 1000) + "-" +
|
||||
String(SCAN_RANGES[range_item] % 1000));
|
||||
display.setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display.drawString(pos_x, text_y,
|
||||
String(SCAN_RANGES[range_item] / 1000) + "-" +
|
||||
String(SCAN_RANGES[range_item] % 1000));
|
||||
if (range_item + 1 < iterations)
|
||||
{
|
||||
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display_instance->drawString(128, ROW_STATUS_TEXT,
|
||||
String(SCAN_RANGES[range_item + 1] / 1000) +
|
||||
"-" +
|
||||
String(SCAN_RANGES[range_item + 1] % 1000));
|
||||
display.setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
display.drawString(pos_x + width, text_y,
|
||||
String(SCAN_RANGES[range_item + 1] / 1000) + "-" +
|
||||
String(SCAN_RANGES[range_item + 1] % 1000));
|
||||
}
|
||||
}
|
||||
if (ui_initialized == false)
|
||||
{
|
||||
// X-axis
|
||||
display_instance->fillRect(0, HEIGHT, STEPS, X_AXIS_WEIGHT);
|
||||
// ticks
|
||||
#ifdef MAJOR_TICKS
|
||||
drawTicks(MAJOR_TICKS, MAJOR_TICK_LENGTH);
|
||||
#endif
|
||||
}
|
||||
ui_initialized = true;
|
||||
}
|
||||
|
||||
67
test/test_rssi.cpp
Normal file
67
test/test_rssi.cpp
Normal 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
test/test_waterfall.cpp
Normal file
220
test/test_waterfall.cpp
Normal 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
test/tests.cpp
Normal file
21
test/tests.cpp
Normal 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();
|
||||
}
|
||||
187
tft_src/main.cpp
187
tft_src/main.cpp
@@ -13,11 +13,28 @@
|
||||
// #define ARDUINO_USB_CDC_ON_BOOT 1
|
||||
// #define LoRaWAN_DEBUG_LEVEL 0
|
||||
#include "HT_ST7789spi.h"
|
||||
#include "global_config.h"
|
||||
// #include "global_config.h"
|
||||
#include "images.h"
|
||||
#include "ui.h"
|
||||
// #include "ui.h"
|
||||
#include <Adafruit_GFX.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_REST_Pin 40
|
||||
@@ -70,16 +87,20 @@ constexpr bool DRAW_DETECTION_TICKS = true;
|
||||
// number of samples for RSSI method
|
||||
#define SAMPLES_RSSI 5 // 21 //
|
||||
|
||||
#define FREQ_BEGIN 650
|
||||
#define DEFAULT_DRONE_DETECTION_LEVEL 90
|
||||
#define FREQ_BEGIN 150
|
||||
#define FREQ_END 950
|
||||
#define BANDWIDTH 467.0
|
||||
#define MHZ_PX (float)((float)(FREQ_END - FREQ_BEGIN) / DISPLAY_WIDTH)
|
||||
#define DEFAULT_DRONE_DETECTION_LEVEL -90
|
||||
#define DRONE_LEGEND 1;
|
||||
|
||||
#define RANGE (int)(FREQ_END - FREQ_BEGIN)
|
||||
|
||||
#define SINGLE_STEP (float)(RANGE / (STEPS * SCAN_RBW_FACTOR))
|
||||
// #define SINGLE_STEP (float)(RANGE / (STEPS * SCAN_RBW_FACTOR))
|
||||
|
||||
uint64_t range = (int)(FREQ_END - FREQ_BEGIN);
|
||||
uint64_t fr_begin = FREQ_BEGIN;
|
||||
uint64_t fr_end = FREQ_BEGIN;
|
||||
uint64_t fr_end = FREQ_END;
|
||||
|
||||
// Feature to scan diapasones. Other frequency settings will be ignored.
|
||||
// int SCAN_RANGES[] = {850890, 920950};
|
||||
@@ -90,7 +111,7 @@ int SCAN_RANGES[] = {};
|
||||
// uint64_t RANGE_PER_PAGE = FREQ_END - FREQ_BEGIN; // FREQ_END - FREQ_BEGIN
|
||||
|
||||
// Override or e-ink
|
||||
uint64_t RANGE_PER_PAGE = FREQ_BEGIN + DISPLAY_WIDTH;
|
||||
uint64_t RANGE_PER_PAGE = FREQ_END - FREQ_BEGIN; // FREQ_BEGIN + DISPLAY_WIDTH;
|
||||
|
||||
uint64_t iterations = RANGE / RANGE_PER_PAGE;
|
||||
|
||||
@@ -114,7 +135,7 @@ bool waterfall[STEPS], detected_y[STEPS]; // 20 - ??? steps of the waterfall
|
||||
bool first_run, new_pixel, detected_x = false;
|
||||
// drone detection flag
|
||||
bool detected = false;
|
||||
uint64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL;
|
||||
int64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL;
|
||||
uint64_t drone_detected_frequency_start = 0;
|
||||
uint64_t drone_detected_frequency_end = 0;
|
||||
uint64_t detection_count = 0;
|
||||
@@ -131,9 +152,24 @@ uint64_t scan_time = 0;
|
||||
uint64_t scan_start_time = 0;
|
||||
#endif
|
||||
|
||||
#define WATERFALL_START 115
|
||||
// To remove waterfall adjust this and this
|
||||
#define ZERO_LEVEL 110 // Equal to minimal RSSI
|
||||
#define ZERO_SHIFT 42
|
||||
#define LOWER_LEVEL ZERO_LEVEL + ZERO_SHIFT // 108(zero) - (40 moving down)
|
||||
#define SPECTR_CHART_STAR_TOP 42;
|
||||
#define WATERFALL_START 119
|
||||
#define WATERFALL_END DISPLAY_HEIGHT - 10 - 2
|
||||
|
||||
#ifndef DISABLE_WATERFALL
|
||||
#define DISABLE_WATERFALL 1 // to disable set to 1
|
||||
#endif
|
||||
|
||||
#if DISABLE_WATERFALL == 0
|
||||
#define ZERO_LEVEL 110
|
||||
#define ZERO_SHIFT 0
|
||||
#define LOWER_LEVEL ZERO_LEVEL
|
||||
#endif
|
||||
|
||||
uint64_t x, y, range_item, w = WATERFALL_START, i = 0;
|
||||
int osd_x = 1, osd_y = 2, col = 0, max_bin = 32;
|
||||
uint64_t ranges_count = 0;
|
||||
@@ -247,8 +283,27 @@ void battery()
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int lower_level = 108;
|
||||
constexpr int up_level = 40;
|
||||
void drawDroneLegend()
|
||||
{
|
||||
// Draw FPV array Names
|
||||
for (const auto &entry : fpvArray)
|
||||
{
|
||||
int pixelStart = (entry.fstart - FREQ_BEGIN) / MHZ_PX;
|
||||
int pixelEnd = (entry.fend - FREQ_BEGIN) / MHZ_PX;
|
||||
int length = (pixelEnd - pixelStart);
|
||||
// Serial.println("Pixel Start: " + String(pixelStart));
|
||||
// Serial.println("MHinPIX: " + String(MHZ_PX));
|
||||
int median = length / 2;
|
||||
if (entry.fstart < FREQ_END)
|
||||
{
|
||||
st7789->drawFastHLine(pixelStart, entry.y, length, entry.color);
|
||||
drawText(pixelStart, entry.y - 10, entry.drone, entry.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int lower_level = LOWER_LEVEL;
|
||||
constexpr int up_level = SPECTR_CHART_STAR_TOP;
|
||||
int rssiToPix(int rssi)
|
||||
{
|
||||
// Bigger is lower signal
|
||||
@@ -256,11 +311,25 @@ int rssiToPix(int rssi)
|
||||
{
|
||||
return lower_level - 1;
|
||||
}
|
||||
if (abs(rssi) <= up_level)
|
||||
if (abs(rssi) <= up_level && lower_level < 130)
|
||||
{
|
||||
return up_level;
|
||||
}
|
||||
return abs(rssi);
|
||||
// if chart moved to the bottom
|
||||
if (lower_level > 130)
|
||||
{
|
||||
int returnRssi = abs(rssi - ZERO_SHIFT);
|
||||
// Serial.println("RSSI: " + String(rssi));
|
||||
if (returnRssi >= lower_level)
|
||||
{
|
||||
return lower_level - 1;
|
||||
}
|
||||
return returnRssi;
|
||||
}
|
||||
else
|
||||
{
|
||||
return abs(rssi);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -286,7 +355,7 @@ long timeSinceLastModeSwitch = 0;
|
||||
float fr = FREQ_BEGIN, fr_x[STEPS + 5], vbat = 0;
|
||||
// MHz in one screen pix step
|
||||
// END will be Begin + 289 * mhz_step
|
||||
constexpr int mhz_step = 1;
|
||||
float mhz_step = MHZ_PX;
|
||||
// TODO: make end_freq
|
||||
// Measure RSS every step
|
||||
constexpr float rssi_mhz_step = 0.33;
|
||||
@@ -301,6 +370,7 @@ int window_max_rssi = -999;
|
||||
int window_max_fr = -999;
|
||||
int max_scan_rssi[STEPS + 2];
|
||||
int max_history_rssi[STEPS + 2];
|
||||
int historical_loops = 50, h = 0;
|
||||
long display_scan_start = 0;
|
||||
long display_scan_end = 0;
|
||||
long display_scan_i_end = 0;
|
||||
@@ -315,6 +385,7 @@ constexpr unsigned int STATUS_BAR_HEIGHT = 5;
|
||||
|
||||
void loop()
|
||||
{
|
||||
// Serial.println("Loop");
|
||||
if (screen_update_loop_counter == 0)
|
||||
{
|
||||
fr_x[x1] = 0;
|
||||
@@ -331,11 +402,20 @@ void loop()
|
||||
fr_x[x1] = fr;
|
||||
|
||||
int u = 0;
|
||||
int additional_samples = 10;
|
||||
int additional_samples = 0;
|
||||
|
||||
// Clear old data with the cursor ...
|
||||
st7789->drawFastVLine(x1, lower_level, -lower_level + 11, ST7789_BLACK);
|
||||
|
||||
// Draw max history line
|
||||
if (h == historical_loops)
|
||||
{
|
||||
st7789->drawLine(x1, rssiToPix(max_history_rssi[x1]), x1, lower_level,
|
||||
ST7789_BLACK /*gray*/);
|
||||
// clear history
|
||||
max_history_rssi[x1] = -999;
|
||||
}
|
||||
|
||||
st7789->drawLine(x1, rssiToPix(max_history_rssi[x1]), x1, lower_level,
|
||||
12710 /*gray*/);
|
||||
// Fetch samples
|
||||
@@ -351,8 +431,15 @@ void loop()
|
||||
additional_samples--;
|
||||
}
|
||||
|
||||
radio.setFrequency((float)fr + (float)(rssi_mhz_step * u),
|
||||
false); // false = no calibration need here
|
||||
bool calibrate = true;
|
||||
float freq = (float)fr + (float)(rssi_mhz_step * u);
|
||||
if ((int)freq % 10 == 0)
|
||||
{
|
||||
calibrate = true;
|
||||
}
|
||||
radio.setFrequency(freq,
|
||||
/*false*/ calibrate); // false = no calibration need here
|
||||
// Serial.println((float)fr + (float)(rssi_mhz_step * u));
|
||||
u++;
|
||||
if (rssi_mhz_step * u >= mhz_step)
|
||||
{
|
||||
@@ -363,6 +450,7 @@ void loop()
|
||||
rssi_single_start = millis();
|
||||
}
|
||||
rssi2 = radio.getRSSI(false);
|
||||
// Serial.print(" RSSI : " + String(rssi2));
|
||||
scan_iterations++;
|
||||
if (rssi_single_end == 0)
|
||||
{
|
||||
@@ -383,13 +471,24 @@ void loop()
|
||||
#ifdef PRINT_DEBUG
|
||||
Serial.println(String(fr) + ":" + String(rssi2));
|
||||
#endif
|
||||
int lineHeight = 0;
|
||||
|
||||
st7789->drawPixel(x1, rssiToPix(rssi2), rssiToColor(abs(rssi2)));
|
||||
st7789->drawPixel(x1, rssiToPix(rssi2) - 1, rssiToColor(abs(rssi2)));
|
||||
st7789->drawPixel(x1, rssiToPix(rssi2) - 2, rssiToColor(abs(rssi2)));
|
||||
st7789->drawPixel(x1, rssiToPix(rssi2) - 3, rssiToColor(abs(rssi2)));
|
||||
st7789->drawPixel(x1, rssiToPix(rssi2) - 4, rssiToColor(abs(rssi2)));
|
||||
|
||||
if (true /*draw full line*/)
|
||||
{
|
||||
st7789->drawFastVLine(x1, rssiToPix(rssi2), lower_level - rssiToPix(rssi2),
|
||||
rssiToColor(abs(rssi2)));
|
||||
}
|
||||
// Draw Update Cursor
|
||||
st7789->drawFastVLine(x1 + 1, lower_level, -lower_level + 11, ST7789_BLACK);
|
||||
st7789->drawFastVLine(x1 + 2, lower_level, -lower_level + 11, ST7789_BLACK);
|
||||
st7789->drawFastVLine(x1 + 3, lower_level, -lower_level + 11, ST7789_BLACK);
|
||||
// st7789->drawFastVLine(x1 + 3, lower_level, -lower_level + 11,
|
||||
// ST7789_BLACK);
|
||||
|
||||
if (max_scan_rssi[x1] == -999)
|
||||
{
|
||||
@@ -417,10 +516,13 @@ void loop()
|
||||
}
|
||||
}
|
||||
// Writing pixel only if it is bigger than drone detection level
|
||||
if (abs(max_scan_rssi[x1]) < drone_detection_level)
|
||||
if (abs(max_scan_rssi[x1]) < abs(drone_detection_level))
|
||||
{
|
||||
// Waterfall Pixel
|
||||
st7789->drawPixel(x1, w, rssiToColor(abs(max_scan_rssi[x1]), true));
|
||||
if (DISABLE_WATERFALL == 0)
|
||||
{
|
||||
// Waterfall Pixel
|
||||
st7789->drawPixel(x1, w, rssiToColor(abs(max_scan_rssi[x1]), true));
|
||||
}
|
||||
|
||||
detailed_scan_candidate[(int)fr] = (int)fr;
|
||||
}
|
||||
@@ -431,7 +533,7 @@ void loop()
|
||||
// Draw legend for windows
|
||||
if (x1 % rssi_window_size == 0 || x1 == DISPLAY_WIDTH)
|
||||
{
|
||||
if (abs(window_max_rssi) < drone_detection_level && window_max_rssi != 0 &&
|
||||
if (abs(window_max_rssi) < abs(drone_detection_level) && window_max_rssi != 0 &&
|
||||
window_max_rssi != -999)
|
||||
{
|
||||
y2 = 15;
|
||||
@@ -450,9 +552,15 @@ void loop()
|
||||
window_max_rssi = -999;
|
||||
}
|
||||
|
||||
// Waterfall cursor
|
||||
st7789->drawFastHLine(0, w + 1, DISPLAY_WIDTH, ST7789_BLACK);
|
||||
st7789->drawFastHLine(0, w + 2, DISPLAY_WIDTH, ST7789_BLACK);
|
||||
if (DISABLE_WATERFALL == 0)
|
||||
{
|
||||
// Waterfall cursor
|
||||
st7789->drawFastHLine(0, w + 1, DISPLAY_WIDTH, ST7789_BLACK);
|
||||
if (w < WATERFALL_END)
|
||||
{
|
||||
st7789->drawFastHLine(0, w + 2, DISPLAY_WIDTH, ST7789_ORANGE);
|
||||
}
|
||||
}
|
||||
|
||||
// drone detection level line
|
||||
if (x1 % 2 == 0)
|
||||
@@ -470,9 +578,9 @@ void loop()
|
||||
button_pressed_counter = 0;
|
||||
if (button.pressed())
|
||||
{
|
||||
drone_detection_level++;
|
||||
if (drone_detection_level > 107)
|
||||
drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL - 20;
|
||||
drone_detection_level--;
|
||||
if (drone_detection_level < -107)
|
||||
drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL + 20;
|
||||
while (button.pressedNow())
|
||||
{
|
||||
delay(100);
|
||||
@@ -491,6 +599,14 @@ void loop()
|
||||
heltec_deep_sleep();
|
||||
}
|
||||
|
||||
// Drone legend every 1/4 of the screen
|
||||
if (x1 % (STEPS / 4) == 0)
|
||||
{
|
||||
#ifdef DRONE_LEGEND
|
||||
drawDroneLegend();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Main N x-axis full loop end logic
|
||||
if (x1 >= STEPS)
|
||||
{
|
||||
@@ -502,17 +618,28 @@ void loop()
|
||||
#ifdef PRINT_DEBUG
|
||||
Serial.println("Screen End for Output: " + String(screen_update_loop_counter));
|
||||
#endif
|
||||
|
||||
// Doing output only after full scan
|
||||
if (screen_update_loop_counter + 1 == SCANS_PER_DISPLAY)
|
||||
{
|
||||
#ifdef DRONE_LEGEND
|
||||
drawDroneLegend();
|
||||
#endif
|
||||
|
||||
h++;
|
||||
if (h == historical_loops - 1)
|
||||
{
|
||||
h = 0;
|
||||
}
|
||||
|
||||
// Scan results to max Mhz and dB in window
|
||||
display_scan_end = millis();
|
||||
|
||||
st7789->fillRect(0, 0, DISPLAY_WIDTH, 11, ST7789_BLACK);
|
||||
drawText(0, 0,
|
||||
"T:" + String(display_scan_end - display_scan_start) + "/" +
|
||||
String(rssi_single_end - rssi_single_start) + " L:-" +
|
||||
String(drone_detection_level) + "dB",
|
||||
String(rssi_single_end - rssi_single_start) +
|
||||
" L:" + String(drone_detection_level) + "dB",
|
||||
ST7789_BLUE);
|
||||
|
||||
/// battery();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* 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
|
||||
#define HELTEC_POWER_BUTTON // must be before "#include <heltec_unofficial.h>"
|
||||
#include <heltec_unofficial.h>
|
||||
@@ -15,11 +16,11 @@
|
||||
// Pause between transmited packets in mseconds.
|
||||
// Set to zero to only transmit a packet when pressing the user button
|
||||
// Will not exceed 1% duty cycle, even if you set a lower value.
|
||||
#define PAUSE 20
|
||||
#define PAUSE 10
|
||||
|
||||
// Frequency in MHz. Keep the decimal point to designate float.
|
||||
// Check your own rules and regulations to see what is legal where you are.
|
||||
#define FREQUENCY 866.3 // for Europe
|
||||
#define FREQUENCY 915 // for Europe
|
||||
// #define FREQUENCY 905.2 // for US
|
||||
|
||||
// LoRa bandwidth. Keep the decimal point to designate float.
|
||||
@@ -29,13 +30,13 @@
|
||||
|
||||
// Number from 5 to 12. Higher means slower but higher "processor gain",
|
||||
// meaning (in nutshell) longer range and more robust against interference.
|
||||
#define SPREADING_FACTOR 9
|
||||
#define SPREADING_FACTOR 7
|
||||
|
||||
// Transmit power in dBm. 0 dBm = 1 mW, enough for tabletop-testing. This value can be
|
||||
// set anywhere between -9 dBm (0.125 mW) to 22 dBm (158 mW). Note that the maximum ERP
|
||||
// (which is what your antenna maximally radiates) on the EU ISM band is 25 mW, and that
|
||||
// transmissting without an antenna can damage your hardware.
|
||||
#define TRANSMIT_POWER -9
|
||||
#define TRANSMIT_POWER 22
|
||||
|
||||
String rxdata;
|
||||
volatile bool rxFlag = false;
|
||||
@@ -67,35 +68,41 @@ void setup()
|
||||
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
|
||||
}
|
||||
|
||||
int FHSS = 0.25;
|
||||
float FHSS_counter = -10;
|
||||
void loop()
|
||||
{
|
||||
heltec_loop();
|
||||
|
||||
bool tx_legal = millis() > last_tx + minimum_pause;
|
||||
bool tx_legal = true; // millis() > last_tx + minimum_pause;
|
||||
// Emulate frequency hopping spread spectrum (FHSS) is a method of transmitting radio
|
||||
// signals by rapidly switching the carrier between different frequency channels.
|
||||
float fr = (float)(FREQUENCY + (float)(FHSS_counter));
|
||||
RADIOLIB_OR_HALT(radio.setFrequency(fr, false));
|
||||
// Transmit a packet every PAUSE seconds or when the button is pressed
|
||||
if ((PAUSE && tx_legal && millis() - last_tx > (PAUSE)) || button.isSingleClick())
|
||||
{
|
||||
// In case of button click, tell user to wait
|
||||
if (!tx_legal)
|
||||
if (button.isSingleClick())
|
||||
{
|
||||
both.printf("Legal limit, wait %i sec.\n",
|
||||
(int)((minimum_pause - (millis() - last_tx)) / 1000) + 1);
|
||||
return;
|
||||
fr = 1000;
|
||||
RADIOLIB_OR_HALT(radio.setFrequency(fr, false));
|
||||
}
|
||||
both.printf("TX [%s] ", String(counter).c_str());
|
||||
// In case of button click, tell user to wait
|
||||
display.printf("TX[%s]", String(counter).c_str());
|
||||
radio.clearDio1Action();
|
||||
heltec_led(50); // 50% brightness is plenty for this LED
|
||||
tx_time = millis();
|
||||
RADIOLIB(radio.transmit(String(counter++).c_str()));
|
||||
RADIOLIB(radio.transmit(String("Putin Huylo!!! LA-LA-LA-LA").c_str()));
|
||||
tx_time = millis() - tx_time;
|
||||
heltec_led(0);
|
||||
if (_radiolib_status == RADIOLIB_ERR_NONE)
|
||||
{
|
||||
both.printf("OK (%i ms)\n", (int)tx_time);
|
||||
display.printf("OK(%ims)/%.2fMhz\n", (int)tx_time, fr);
|
||||
}
|
||||
else
|
||||
{
|
||||
both.printf("fail (%i)\n", _radiolib_status);
|
||||
display.printf("fail (%i)\n", _radiolib_status);
|
||||
heltec_delay(100);
|
||||
}
|
||||
// Maximum 1% duty cycle
|
||||
minimum_pause = tx_time * 100;
|
||||
@@ -111,10 +118,16 @@ void loop()
|
||||
radio.readData(rxdata);
|
||||
if (_radiolib_status == RADIOLIB_ERR_NONE)
|
||||
{
|
||||
both.printf("RX [%s]\n", rxdata.c_str());
|
||||
both.printf(" RSSI: %.2f dBm\n", radio.getRSSI());
|
||||
both.printf(" SNR: %.2f dB\n", radio.getSNR());
|
||||
display.printf("RX [%s]\n", rxdata.c_str());
|
||||
display.printf(" RSSI: %.2f dBm\n", radio.getRSSI());
|
||||
display.printf(" SNR: %.2f dB\n", radio.getSNR());
|
||||
}
|
||||
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
|
||||
}
|
||||
FHSS_counter += 0.250;
|
||||
counter++;
|
||||
if (FHSS_counter > 20)
|
||||
{
|
||||
FHSS_counter = -10;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user