Merge pull request #67 from Genaker/main

Merge
This commit is contained in:
Yegor Shytikov
2024-11-26 09:45:58 -08:00
committed by GitHub
38 changed files with 3281 additions and 813 deletions

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
out/

View File

@@ -12,4 +12,7 @@
},
"files.insertFinalNewline": true,
"files.autoSave": "onFocusChange",
"files.associations": {
"cstdint": "cpp"
},
}

View File

@@ -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
![image](https://github.com/user-attachments/assets/971b6592-3b71-414c-971c-2ecd20f0f0b7)
@@ -174,6 +180,15 @@ If less, ESP32 will turn off. Fast pressing(less than 0.5 second) P button chang
7. Select Proper Environment
![image](https://github.com/user-attachments/assets/a9c6557b-a387-4457-b59b-b3d7242d2826)
---
>**Important note:** If using a Heltec V3 board, make sure your ESP32 Expressif catalog is up to date before selecting environment, otherwise might get a build time error such as: `Error: Unknown board ID 'heltec_wifi_lora_32_V3'` when trying to select the environment.
>
>Open a PlatformIO CLI: https://docs.platformio.org/en/latest/integration/ide/vscode.html#platformio-core-cli
>
>Run: `pio pkg update -g -p espressif32`
8. Select ESP32 USB Device to program
![image](https://github.com/user-attachments/assets/af76c4b1-7122-45e1-b26b-08b59e03ca3b)
Note: It is theoretically possible to program via WiFi and BTH.
@@ -222,6 +237,24 @@ or buy :
![image](https://github.com/user-attachments/assets/a1e00b51-5566-4ff5-98fe-67eaeb5bc81f)
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
```

View File

@@ -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
View File

@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>ESP Wi-Fi Manager</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="topnav">
<h1>LORA SA ESP32 CONFIG</h1>
</div>
<div class="content">
<div class="card-grid">
<div class="card">
<form action="/" method="POST">
<p>
<label for="ssid">SSID</label>
<input type="text" id="ssid" name="ssid"><br>
<label for="pass">Password</label>
<input type="text" id="pass" name="pass"><br>
<label for="ip">IP Address</label>
<input type="text" id="ip" name="ip" value="192.168.1.200"><br>
<label for="gateway">Gateway Address</label>
<input type="text" id="gateway" name="gateway" value="192.168.1.1"><br>
<label for="fstart">FREQ START</label>
<input type="number" id="fstart" name="fstart" value="800"><br>
<label for="fend">FREQ END</label>
<input type="number" id="fend" name="fend" value="960"><br>
<label for="samples">SCAN SAMPLES</label>
<input type="number" id="samples" name="samples" value="10"><br>
<input type="submit" value="Submit">
</p>
</form>
</div>
</div>
</div>
</body>
</html>

118
data/style.css Normal file
View File

@@ -0,0 +1,118 @@
html {
font-family: Arial, Helvetica, sans-serif;
display: inline-block;
text-align: center;
}
h1 {
font-size: 1.8rem;
color: white;
}
p {
font-size: 1.4rem;
}
.topnav {
overflow: hidden;
background-color: #0A1128;
}
body {
margin: 0;
}
.content {
padding: 5%;
}
.card-grid {
max-width: 800px;
margin: 0 auto;
display: grid;
grid-gap: 2rem;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.card {
background-color: white;
box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, .5);
}
.card-title {
font-size: 1.2rem;
font-weight: bold;
color: #034078
}
input[type=submit] {
border: none;
color: #FEFCFB;
background-color: #034078;
padding: 15px 15px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
width: 100px;
margin-right: 10px;
border-radius: 4px;
transition-duration: 0.4s;
}
input[type=submit]:hover {
background-color: #1282A2;
}
input[type=text],
input[type=number],
select {
width: 50%;
padding: 12px 20px;
margin: 18px;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
label {
font-size: 1.2rem;
}
.value {
font-size: 1.2rem;
color: #1282A2;
}
.state {
font-size: 1.2rem;
color: #1282A2;
}
button {
border: none;
color: #FEFCFB;
padding: 15px 32px;
text-align: center;
font-size: 16px;
width: 100px;
border-radius: 4px;
transition-duration: 0.4s;
}
.button-on {
background-color: #034078;
}
.button-on:hover {
background-color: #1282A2;
}
.button-off {
background-color: #858585;
}
.button-off:hover {
background-color: #252524;
}

0
data/text.txt Normal file
View File

View 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
View File

@@ -0,0 +1,126 @@
#include <TinyGPS++.h>
#include <time.h>
// Objects for GNSS parsing and serial communication
TinyGPSPlus gps;
HardwareSerial GNSSSerial(2);
// Pin definitions for GNSS module communication
const int GNSS_RXPin = 34;
const int GNSS_TXPin = 33;
const int GNSS_RSTPin = 35; // There is a function built for this in the example below-
// currently it isn't used
const int GNSS_PPS_Pin = 36;
// Flags for PPS handling and synchronization status
volatile bool ppsFlag = false;
volatile bool initialSyncDone = false;
// Timestamp for the last valid GNSS data received
unsigned long lastGNSSDataMillis = 0;
void setup()
{
// Initialize serial communication for debugging
USBSerial.begin(115200);
while (!USBSerial)
;
// Start GNSS module communication
GNSSSerial.begin(115200, SERIAL_8N1, GNSS_TXPin, GNSS_RXPin);
while (!GNSSSerial)
;
// Configure GNSS reset pin
pinMode(GNSS_RSTPin, OUTPUT);
digitalWrite(GNSS_RSTPin, HIGH);
// Set up PPS pin and attach an interrupt handler
pinMode(GNSS_PPS_Pin, INPUT);
attachInterrupt(digitalPinToInterrupt(GNSS_PPS_Pin), ppsInterrupt, RISING);
// Short delay for GNSS module initialization
delay(1000);
}
void loop()
{
// Process incoming GNSS data
while (GNSSSerial.available())
{
if (gps.encode(GNSSSerial.read()))
{
// Update the timestamp when valid GNSS data is received
lastGNSSDataMillis = millis();
displayGNSSData(); // Display GNSS data for debugging
}
}
// Perform initial synchronization using NMEA time data
if (!initialSyncDone && gps.date.isValid() && gps.time.isValid())
{
setSystemTime();
initialSyncDone = true;
USBSerial.println("Initial time synchronization done using NMEA data.");
}
// Disable interrupts to safely check and reset the PPS flag
noInterrupts();
if (ppsFlag)
{
fineTuneSystemTime(); // Adjust system time based on the PPS pulse
ppsFlag = false;
}
// Re-enable interrupts
interrupts();
// Check if GNSS data has been absent for more than a minute
if (millis() - lastGNSSDataMillis > 60000)
{
USBSerial.println("Warning: Haven't received GNSS data for more than 1 minute!");
// Additional actions can be added here, like alerts or module resets.
}
}
// Interrupt handler for the PPS signal
void ppsInterrupt() { ppsFlag = true; }
// Function to set system time using GNSS data
void setSystemTime()
{
struct tm timeinfo;
timeinfo.tm_year = gps.date.year() - 1900;
timeinfo.tm_mon = gps.date.month() - 1;
timeinfo.tm_mday = gps.date.day();
timeinfo.tm_hour = gps.time.hour();
timeinfo.tm_min = gps.time.minute();
timeinfo.tm_sec = gps.time.second();
time_t t = mktime(&timeinfo);
timeval tv = {t, 0};
settimeofday(&tv, NULL); // Update system time
}
// Function to fine-tune system time using the PPS pulse
void fineTuneSystemTime()
{
timeval tv;
gettimeofday(&tv, NULL);
tv.tv_usec = 0; // Reset microseconds to zero
settimeofday(&tv, NULL); // Update system time
USBSerial.println("System time fine-tuned using PPS signal.");
}
// Debugging function to display GNSS data
void displayGNSSData()
{
USBSerial.print("Latitude: ");
USBSerial.println(gps.location.lat(), 6);
USBSerial.print("Longitude: ");
USBSerial.println(gps.location.lng(), 6);
USBSerial.print("Altitude: ");
USBSerial.println(gps.altitude.meters());
USBSerial.print("Speed: ");
USBSerial.println(gps.speed.kmph());
USBSerial.println("-----------------------------");
}

View File

@@ -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
View File

@@ -0,0 +1,55 @@
#include "FS.h"
#include <LittleFS.h>
// Initialize LittleFS
void initLittleFS()
{
if (!LittleFS.begin(true))
{
Serial.println("An error has occurred while mounting LittleFS");
}
Serial.println("LittleFS mounted successfully");
}
String readFile(fs::FS &fs, const char *path)
{
Serial.printf("Reading file: %s\r\n", path);
File file = fs.open(path);
if (!file || file.isDirectory())
{
Serial.println("- failed to open file for reading");
return String("");
}
String content;
Serial.println("- read from file:");
while (file.available())
{
content = file.readStringUntil('\n');
}
file.close();
return content;
}
void writeFile(fs::FS &fs, const char *path, const char *message)
{
Serial.printf("Writing file: %s\r\n", path);
Serial.printf("Content: %s\r\n", message);
File file = fs.open(path, FILE_WRITE);
if (!file)
{
Serial.println("- failed to open file for writing");
return;
}
if (file.print(message))
{
Serial.println("- file written");
delay(500);
}
else
{
Serial.println("- write failed");
}
file.close();
}

View File

@@ -1,32 +1,4 @@
#define UNUSED_PIN (0)
// LilyGo defined
#define I2C_SDA 18
#define I2C_SCL 17
#define OLED_RST UNUSED_PIN
#define RADIO_SCLK_PIN 5
#define RADIO_MISO_PIN 3
#define RADIO_MOSI_PIN 6
#define RADIO_CS_PIN 7
#define SDCARD_MOSI 11
#define SDCARD_MISO 2
#define SDCARD_SCLK 14
#define SDCARD_CS 13
#define BOARD_LED 37
#define LED_ON HIGH
#define BUTTON_PIN 0
#define ADC_PIN 1
#define RADIO_RST_PIN 8
#define RADIO_DIO1_PIN 33
#define RADIO_BUSY_PIN 34
// Define for our code
#define 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
View File

@@ -0,0 +1,205 @@
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <WiFi.h>
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
// Search for parameter in HTTP POST request
const String SSID = "ssid";
const String PASS = "pass";
const String IP = "ip";
const String GATEWAY = "gateway";
const String FSTART = "fstart";
const String FEND = "fend";
// File paths to save input values permanently
// const char *ssidPath = "/ssid.txt";
// Variables to save values from HTML form
String ssid = "LoraSA", pass = "1234567890", ip = "192.168.1.100",
gateway = "192.168.1.1", fstart = "", fend = "", smpls = "";
IPAddress localIP;
// Set your Gateway IP address
IPAddress localGateway;
IPAddress subnet(255, 255, 0, 0);
// Timer variables
unsigned long previousMillis = 0;
const long interval = 10000; // interval to wait for Wi-Fi connection (milliseconds)
// Initialize WiFi
bool initWiFi()
{
Serial.println("SSID:" + ssid);
Serial.println("PSWD:" + pass);
Serial.println("IP:" + ip);
Serial.println("SUB:" + subnet);
Serial.println("GATAWAY:" + gateway);
if (ssid == "" || ip == "")
{
Serial.println("Undefined SSID or IP address.");
return false;
}
WiFi.mode(WIFI_STA);
localIP.fromString(ip.c_str());
localGateway.fromString(gateway.c_str());
if (!WiFi.config(localIP, localGateway, subnet))
{
Serial.println("STA Failed to configure");
return false;
}
WiFi.begin(ssid.c_str(), pass.c_str());
Serial.println("Connecting to WiFi...");
unsigned long currentMillis = millis();
previousMillis = currentMillis;
while (WiFi.status() != WL_CONNECTED)
{
currentMillis = millis();
if (currentMillis - previousMillis >= interval)
{
Serial.println("Failed to connect.");
return false;
}
}
Serial.println(WiFi.localIP());
return true;
}
void writeParameterToFile(String value, String file)
{
// Write file to save value
writeFile(LittleFS, file.c_str(), value.c_str());
}
void writeParameterToParameterFile(String param, String value)
{
String file = String("/" + param + ".txt");
// Write file to save value
writeParameterToFile(value, file.c_str());
}
String readParameterFromParameterFile(String param)
{
String file = String("/" + param + ".txt");
return readFile(LittleFS, file.c_str());
}
void serverServer()
{
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->send(LittleFS, "/index.html", "text/html"); });
server.serveStatic("/", LittleFS, "/");
server.on("/", HTTP_POST,
[](AsyncWebServerRequest *request)
{
int params = request->params();
for (int i = 0; i < params; i++)
{
Serial.println("Parameter " + String(i) + ": " +
request->getParam(i)->value());
}
Serial.println(request->params());
String p;
if (request->hasParam(IP, true))
{
p = request->getParam(IP, true)->value();
writeParameterToParameterFile(IP, p);
}
if (request->hasParam(IP, true))
{
p = request->getParam(IP, true)->value();
writeParameterToParameterFile(IP, p);
}
if (request->hasParam(IP, true))
{
p = request->getParam(IP, true)->value();
writeParameterToParameterFile(IP, p);
}
if (request->hasParam(GATEWAY, true))
{
p = request->getParam(GATEWAY, true)->value();
writeParameterToParameterFile(GATEWAY, p);
}
if (request->hasParam(FSTART, true))
{
p = request->getParam(FSTART, true)->value();
writeParameterToParameterFile(FSTART, p);
}
if (request->hasParam(FEND, true))
{
p = request->getParam(FEND, true)->value();
writeParameterToParameterFile(FEND, p);
}
if (request->hasParam("samples", true))
{
p = request->getParam("samples", true)->value();
writeParameterToParameterFile("samples", p);
}
request->send(200, "text/plain",
"Done. ESP will restart, connect to your router and "
"go to IP address: " +
ip);
delay(3000);
ESP.restart();
});
/* // Route to set GPIO state to HIGH
server.on("/on", HTTP_GET,
[](AsyncWebServerRequest *request)
{
digitalWrite(ledPin, HIGH);
request->send(LittleFS, "/index.html", "text/html", false,
processor);
});
// Route to set GPIO state to LOW
server.on("/off", HTTP_GET,
[](AsyncWebServerRequest *request)
{
digitalWrite(ledPin, LOW);
request->send(LittleFS, "/index.html", "text/html", false,
processor);
});*/
server.begin();
}
void serverStart()
{
if (initWiFi())
{
Serial.println("Setting Secure WIFI (Access Point)");
serverServer();
}
else
{
// Connect to Wi-Fi network with default SSID and password
Serial.println("Setting AP (Access Point)");
// NULL sets an open Access Point
WiFi.softAP("LoraSA", NULL);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
serverServer();
}
}

View File

@@ -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

View File

@@ -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
View File

@@ -0,0 +1,194 @@
#include "charts.h"
void BarChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
if (w != width)
{
delete[] ys;
delete[] changed;
ys = new float[w];
changed = new bool[w];
}
memset(ys, 0, w * sizeof(float));
memset(changed, false, w * sizeof(bool));
redraw_all = true;
Chart::reset(x, y, w, h);
}
int BarChart::updatePoint(float x, float y)
{
if (x < min_x || x >= max_x)
{
return -1;
}
size_t idx = width * (x - min_x) / (max_x - min_x);
if (idx >= width)
{
idx = width - 1;
}
if (!changed[idx] || ys[idx] < y)
{
ys[idx] = y;
changed[idx] = true;
}
return idx;
}
void BarChart::draw()
{
for (int x = 0; x < width; x++)
{
if (!changed[x] && !redraw_all)
continue;
drawOne(x);
}
redraw_all = false;
}
void BarChart::drawOne(int x)
{
if (x < 0)
return;
int y = y2pos(ys[x]);
if (y < height)
{
display.setColor(BLACK);
display.drawVerticalLine(pos_x + x, pos_y, y);
display.setColor(WHITE);
display.drawVerticalLine(pos_x + x, pos_y + y, height - y);
}
else
{
display.setColor(BLACK);
display.drawVerticalLine(pos_x + x, pos_y, height);
}
if (x % 2 == 0)
{
display.setColor(INVERSE);
display.setPixel(pos_x + x, pos_y + y2pos(level_y));
}
changed[x] = false;
}
int BarChart::x2pos(float x)
{
if (x < min_x)
x = min_x;
if (x > max_x)
x = max_x;
return width * (x - min_x) / (max_x - min_x);
}
int BarChart::y2pos(float y)
{
if (y < min_y)
y = min_y;
if (y > max_y)
y = max_y;
return height - height * (y - min_y) / (max_y - min_y);
}
void BarChart::onEvent(Event &e)
{
if (e.type != DETECTED)
{
return;
}
level_y = e.emitter.trigger_level;
int u = updatePoint(e.emitter.current_frequency, e.detected.rssi);
if (e.emitter.animated)
{
drawOne(u);
}
}
void DecoratedBarChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
Chart::reset(x, y, w, h);
bar.reset(x, y + LABEL_HEIGHT, w, h - LABEL_HEIGHT - AXIS_HEIGHT);
}
void DecoratedBarChart::draw()
{
bool draw_axis = bar.redraw_all;
bar.draw();
display.setColor(BLACK);
display.fillRect(pos_x, pos_y, width, bar.pos_y - pos_y);
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_LEFT);
uint16_t first_untouched = 0;
for (uint16_t x = 0; x < width; x++)
{
float y = bar.ys[x];
if (y >= bar.level_y)
{
String s = String(bar.ys[x], 0);
uint16_t w = display.getStringWidth(s);
uint16_t x1 = x;
for (; x < x1 + w && x < width; x++)
{
if (bar.ys[x] > y)
{
y = bar.ys[x];
s = String(y, 0);
w = max(w, display.getStringWidth(s));
}
}
if (x > width && first_untouched <= width - w)
{
x1 = width - w;
}
first_untouched = x;
if (x1 + w <= width)
display.drawString(pos_x + x1, pos_y, s);
}
}
if (draw_axis)
{
display.setColor(WHITE);
uint16_t y = pos_y + height - AXIS_HEIGHT + 2;
display.fillRect(pos_x, y - 1, width, X_AXIS_WEIGHT);
// Start and end ticks
display.fillRect(pos_x, y - 1, 2, AXIS_HEIGHT);
display.fillRect(pos_x + width - 2, y - 1, 2, AXIS_HEIGHT);
for (float step = 0; bar.min_x + step * MAJOR_TICKS < bar.max_x; step += 1)
{
int tick_pos = bar.x2pos(bar.min_x + step * MAJOR_TICKS);
display.drawVerticalLine(pos_x + tick_pos, y, MAJOR_TICK_LENGTH);
}
for (float step = 0; bar.min_x + step * MINOR_TICKS < bar.max_x; step += 1)
{
int tick_pos = bar.x2pos(bar.min_x + step * MINOR_TICKS);
display.drawVerticalLine(pos_x + tick_pos, y, MINOR_TICK_LENGTH);
}
}
}

View File

@@ -0,0 +1,95 @@
#include "charts.h"
uint16_t trim_w(uint16_t pos, uint16_t width, uint16_t w)
{
return min(width, (uint16_t)(max(w, pos) - pos));
}
size_t StackedChart::addChart(Chart *c)
{
Chart **cc = new Chart *[charts_sz + 1];
memcpy(cc, charts, charts_sz * sizeof(Chart *));
cc[charts_sz] = c;
free(charts);
c->reset(pos_x + c->pos_x, pos_y + c->pos_y, trim_w(c->pos_x, c->width, width),
c->height);
charts = cc;
return charts_sz++;
}
uint16_t StackedChart::setHeight(size_t c, uint16_t h)
{
if (h < height)
{
charts[c]->reset(charts[c]->pos_x, charts[c]->pos_y, charts[c]->width, h);
uint16_t used_space = 0;
for (int i = 0; i < charts_sz; i++)
{
used_space += charts[i]->height;
}
return used_space;
}
// this chart gets special treatment - pack all other charts,
// and make this one as big as possible
uint16_t used_space = 0;
for (int i = 0; i < c; i++)
{
charts[i]->reset(charts[i]->pos_x, pos_y + used_space, charts[i]->width,
charts[i]->height);
used_space += charts[i]->height;
}
uint16_t more_used_space = used_space;
for (int i = c + 1; i < charts_sz; i++)
{
more_used_space += charts[i]->height;
}
if (more_used_space < height)
{
charts[c]->reset(charts[c]->pos_x, pos_y + used_space, charts[c]->width,
height - more_used_space);
used_space += charts[c]->height;
}
for (int i = c + 1; i < charts_sz; i++)
{
charts[i]->reset(charts[i]->pos_x, pos_y + used_space, charts[i]->width,
charts[i]->height);
used_space += charts[i]->height;
}
return used_space;
}
void StackedChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
for (int i = 0; i < charts_sz; i++)
{
uint16_t rel_x = charts[i]->pos_x - pos_x;
uint16_t rel_y = charts[i]->pos_y - pos_y;
charts[i]->reset(x + rel_x, y + rel_y, trim_w(rel_x, charts[i]->width, w),
charts[i]->height);
}
Chart::reset(x, y, w, h);
}
void StackedChart::draw()
{
for (int i = 0; i < charts_sz; i++)
charts[i]->draw();
}
void StackedChart::onEvent(Event &e)
{
if (e.type != SCAN_TASK_COMPLETE)
{
return;
}
draw();
}

View File

@@ -0,0 +1,28 @@
#include "charts.h"
void UptimeClock::draw(uint64_t t)
{
t1 = t;
draw();
}
void UptimeClock::draw()
{
uint64_t uptime = t1 - t0;
int mils = uptime % 1000;
int seconds = (uptime / 1000) % 60;
int minutes = (uptime / 60000) % 60;
int hours = uptime / 3600000;
String s = String(hours) + (minutes < 10 ? ":0" : ":") + String(minutes) +
(seconds < 10 ? ":0" : ":") + String(seconds) +
(mils < 10 ? ".00"
: mils < 100 ? ".0"
: ".") +
String(mils);
int w = display.getStringWidth(s);
display.setColor(BLACK);
display.fillRect((display.width() - w) / 2, display.height() / 2 - 3, w, 7);
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
display.drawString(display.width() / 2, display.height() / 2, s);
}

View File

@@ -0,0 +1,69 @@
#include "charts.h"
#include <cstdint>
void WaterfallChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
Chart::reset(x, y, w, h);
model->reset(model->times[0], w);
update_to = model->buckets;
}
void WaterfallChart::updatePoint(uint64_t t, float x, float y)
{
if (x < min_x || x >= max_x)
{
return;
}
update_to = max(update_to, model->updateModel(t, x2pos(x), y >= level_y));
}
void WaterfallChart::draw()
{
size_t h = min(update_to, (size_t)height);
for (int y = 0; y < h; y++)
{
for (int x = 0; x < width; x++)
{
bool b = model->counts[y][x] > 0 &&
(model->events[y][x] >= model->counts[y][x] * threshold);
if (b)
{
display.setColor(WHITE);
}
else
{
display.setColor(BLACK);
}
display.setPixel(pos_x + x, pos_y + y);
}
}
update_to = 0;
}
int WaterfallChart::x2pos(float x)
{
if (x < min_x)
x = min_x;
if (x > max_x)
x = max_x;
return width * (x - min_x) / (max_x - min_x);
}
void WaterfallChart::onEvent(Event &e)
{
if (e.type != DETECTED)
{
return;
}
level_y = e.emitter.trigger_level;
updatePoint(e.time_ms, e.detected.freq, e.detected.rssi);
}

178
lib/charts/charts.h Normal file
View File

@@ -0,0 +1,178 @@
#ifndef CHARTS_H
#define CHARTS_H
#ifdef Vision_Master_E290
#include "HT_DEPG0290BxS800FxX_BW.h"
typedef DEPG0290BxS800FxX_BW Display_t;
#else
#include <OLEDDisplay.h>
typedef OLEDDisplay Display_t;
#endif
#include <cstdint>
#include <events.h>
#include <models.h>
#include <stdlib.h>
struct Chart
{
uint16_t pos_x, pos_y;
uint16_t width, height;
Display_t &display;
Chart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h)
: display(d), pos_x(x), pos_y(y), width(w), height(h) {};
/*
* This method resets the state and sets the reference time.
*/
virtual void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
pos_x = x;
pos_y = y;
width = w;
height = h;
}
/*
* Redraw everything that needs redrawing.
*/
virtual void draw() {};
};
/*
* ProgressChart supports updates with progressive redraw of just the affected area.
*/
struct ProgressChart : Chart
{
ProgressChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h)
: Chart(d, x, y, w, h) {};
/*
* Update one data point, and return what column needs redrawing.
*/
virtual int updatePoint(float x, float y) = 0;
/*
* If you fancy animated progress, then pass the output of updatePoint to here.
*/
virtual void drawOne(int x) = 0;
};
struct BarChart : ProgressChart, Listener
{
float min_x, max_x, min_y, max_y;
float level_y;
float *ys;
bool *changed;
bool redraw_all;
BarChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h, float min_x,
float max_x, float min_y, float max_y, float level_y)
: ProgressChart(d, x, y, w, h), min_x(min_x), max_x(max_x), min_y(min_y),
max_y(max_y), level_y(level_y), redraw_all(true)
{
ys = new float[w];
changed = new bool[w];
memset(ys, 0, w * sizeof(float));
memset(changed, 0, w * sizeof(bool));
};
void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override;
int updatePoint(float x, float y) override;
void drawOne(int x) override;
void draw() override;
void onEvent(Event &) override;
int x2pos(float x);
int y2pos(float y);
};
#define LABEL_HEIGHT 7
#define X_AXIS_WEIGHT 1
#define MAJOR_TICK_LENGTH 2
#define MAJOR_TICKS 10
#define MINOR_TICK_LENGTH 1
#define MINOR_TICKS 5
#define AXIS_HEIGHT (X_AXIS_WEIGHT + MAJOR_TICK_LENGTH + 2)
struct DecoratedBarChart : Chart
{
BarChart bar;
DecoratedBarChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
float min_x, float max_x, float min_y, float max_y, float level_y)
: Chart(d, x, y, w, h),
bar(d, x, y + LABEL_HEIGHT, w, h - LABEL_HEIGHT - AXIS_HEIGHT, min_x, max_x,
min_y, max_y, level_y) {};
void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override;
void draw() override;
};
struct StackedChart : Chart, Listener
{
Chart **charts;
size_t charts_sz;
StackedChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h)
: Chart(d, x, y, w, h), charts(NULL), charts_sz(0) {};
/*
* addChart adds c to the StackedChart, treats pos_x and pos_y of the chart
* as relative to this chart's origin, and trims width to fit. Adjust the
* height and pack charts using setHeight.
*/
size_t addChart(Chart *c);
/*
* Adjust the height of the chart and return the resulting required height.
* If h is >= height, the chart gets a special treatment: packs all other
* charts, and uses up the rest of space.
*/
uint16_t setHeight(size_t c, uint16_t h);
void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override;
void draw() override;
void onEvent(Event &e) override;
};
struct WaterfallChart : Chart, Listener
{
float min_x, max_x;
float level_y, threshold;
size_t update_to;
WaterfallModel *model;
WaterfallChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
float min_x, float max_x, float level_y, float threshold,
WaterfallModel *m)
: Chart(d, x, y, w, h), model(m), min_x(min_x), max_x(max_x), level_y(level_y),
threshold(threshold), update_to(m->buckets) {};
void updatePoint(uint64_t t, float x, float y);
void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override;
void draw() override;
void onEvent(Event &e) override;
int x2pos(float x);
};
struct UptimeClock : Chart
{
uint64_t t0;
uint64_t t1;
UptimeClock(Display_t &d, uint64_t t0) : Chart(d, 0, 0, 0, 0), t0(t0), t1(t0) {};
void draw(uint64_t t);
virtual void draw() override;
};
#endif

46
lib/events/events.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef LORASA_EVENTS_H
#define LORASA_EVENTS_H
struct Event;
enum EventType
{
ALL_EVENTS = 0, // used only at registration time
DETECTED,
SCAN_TASK_COMPLETE,
_MAX_EVENT_TYPE = SCAN_TASK_COMPLETE // unused as event type
};
struct Listener;
#include <cstdint>
#include <scan.h>
struct Event
{
EventType type;
uint64_t epoch;
uint64_t time_ms;
Scan &emitter;
union
{
struct
{
float rssi;
float freq;
bool trigger;
bool detected;
size_t detected_at;
} detected;
};
Event(Scan &emitter, EventType type, uint64_t time_ms)
: emitter(emitter), type(type), epoch(emitter.epoch), time_ms(time_ms) {};
};
struct Listener
{
virtual void onEvent(Event &event) = 0;
};
#endif

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,153 @@
#include "models.h"
#include <cstring>
WaterfallModel::WaterfallModel(size_t w, uint64_t base_dt, size_t m_sz,
const size_t *multiples)
{
width = w;
size_t dt_sz = 0;
for (int i = 0; i < m_sz; i++)
dt_sz += multiples[i];
buckets = dt_sz;
dt = new uint64_t[buckets];
events = new uint32_t *[buckets];
counts = new uint32_t *[buckets];
times = new uint64_t[buckets];
uint64_t m = base_dt;
for (int i = 0, j = 0; i < m_sz; i++)
{
for (int k = 0; k < multiples[i]; k++, j++)
{
dt[j] = m;
events[j] = new uint32_t[width];
counts[j] = new uint32_t[width];
}
m *= multiples[i];
}
}
void WaterfallModel::reset(uint64_t t0, size_t w)
{
if (w != width)
{
width = w;
for (int i = 0; i < buckets; i++)
{
delete[] counts[i];
delete[] events[i];
counts[i] = new uint32_t[w];
events[i] = new uint32_t[w];
}
}
for (int i = 0; i < buckets; i++)
{
memset(counts[i], 0, width * sizeof(uint32_t));
memset(events[i], 0, width * sizeof(uint32_t));
times[i] = t0 + dt[i];
}
}
/*
* The model is literally a stack of counters:
* - incomplete second
* - n complete seconds
* - incomplete minute
* - n complete minutes
* - ...
*
* updateModel updates incomplete second. When the second becomes complete, it
* gets pushed to complete seconds, and the last complete second is rotated out
* and it gets added to incomplete minute. This gets repeated for incomplete
* minutes, etc.
*/
size_t WaterfallModel::updateModel(uint16_t t, size_t x, uint16_t y)
{
size_t changed = 1;
while (t > times[0])
{
changed = push();
}
counts[0][x]++;
events[0][x] += y;
return changed;
}
size_t WaterfallModel::push()
{
size_t i = 1;
for (; i < buckets; i++)
{
if (dt[i - 1] == dt[i])
continue;
if (times[i - 1] <= times[i])
break;
times[i - 1] = times[i] + dt[i];
}
uint64_t t0 = times[0];
uint32_t *cc = counts[i - 1];
uint32_t *ee = events[i - 1];
memmove(times + 1, times, (i - 1) * sizeof(uint64_t));
memmove(counts + 1, counts, (i - 1) * sizeof(uint32_t *));
memmove(events + 1, events, (i - 1) * sizeof(uint32_t *));
if (i < buckets)
{
for (int j = 0; j < width; j++)
{
counts[i][j] += cc[j];
events[i][j] += ee[j];
}
i++;
}
memset(cc, 0, width * sizeof(uint32_t));
memset(ee, 0, width * sizeof(uint32_t));
counts[0] = cc;
events[0] = ee;
times[0] = t0 + dt[0];
return i;
}
#ifdef TO_STRING
#include <sstream>
#include <string>
#endif
char *WaterfallModel::toString()
{
#ifdef TO_STRING
std::stringstream r;
r << "w:" << width << " b:" << buckets << " [";
for (int i = 0; i < buckets; i++)
{
r << "dt:" << dt[i] << " t:" << times[i] << " [";
for (int j = 0; j < width; j++)
r << " c:" << counts[i][j] << " e:" << events[i][j];
r << " ]";
}
r << " ]";
char *ret = new char[r.str().length() + 1];
strncpy(ret, r.str().c_str(), r.str().length());
#else
char *ret = NULL;
#endif
return ret;
}

27
lib/models/models.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef CHARTS_MODELS_H
#define CHARTS_MODELS_H
#include <cstdint>
#include <cstring>
#include <stdlib.h>
struct WaterfallModel
{
uint32_t **events;
uint32_t **counts;
uint64_t *times;
uint64_t *dt;
size_t buckets;
size_t width;
WaterfallModel(size_t w, uint64_t base_dt, size_t m_sz, const size_t *multiples);
void reset(uint64_t t0, size_t width);
size_t updateModel(uint16_t t, size_t x, uint16_t y);
size_t push();
char *toString();
};
#endif

189
lib/scan/scan.cpp Normal file
View File

@@ -0,0 +1,189 @@
#ifndef LORASA_CORE_CPP
#define LORASA_CORE_CPP
#include "scan.h"
#include <cstdint>
#include <cstring>
#include <stdlib.h>
uint16_t Scan::rssiMethod(size_t samples, uint16_t *result, size_t res_size)
{
float scale((float)res_size / (HI_RSSI_THRESHOLD - LO_RSSI_THRESHOLD + 0.1));
memset(result, 0, res_size * sizeof(uint16_t));
int result_index = 0;
//
uint16_t max_signal = 65535;
// N of samples
for (int r = 0; r < samples; r++)
{
float rssi = getRSSI();
if (rssi < -65535)
rssi = -65535;
uint16_t abs_rssi = abs(rssi);
if (abs_rssi < max_signal)
{
max_signal = abs_rssi;
}
// ToDO: check if 4 is correct value for 33 power bins
// Now we have more space because we are ignoring low dB values
// we can / 3 default 4
if (RSSI_OUTPUT_FORMULA == 1)
{
result_index =
/// still not clear formula but it works
uint8_t(abs(rssi) / 4);
}
else if (RSSI_OUTPUT_FORMULA == 2)
{
if (rssi > HI_RSSI_THRESHOLD)
{
rssi = HI_RSSI_THRESHOLD;
}
else if (rssi < LO_RSSI_THRESHOLD)
{
rssi = LO_RSSI_THRESHOLD;
}
result_index = uint8_t((HI_RSSI_THRESHOLD - rssi) * scale);
}
if (result_index >= res_size)
{
// Maximum index possible
result_index = res_size - 1;
}
LOG("RSSI: %f IDX: %d\n", rssi, result_index);
if (result[result_index] == 0 || result[result_index] > abs_rssi)
{
result[result_index] = abs_rssi;
}
}
return max_signal;
}
Event Scan::detect(uint16_t *result, bool *filtered_result, size_t result_size,
int samples)
{
size_t max_rssi_x = result_size;
for (int y = 0; y < result_size; y++)
{
LOG("%i:%i,", y, result[y]);
#if !defined(FILTER_SPECTRUM_RESULTS) || FILTER_SPECTRUM_RESULTS == false
if (result[y] && result[y] != 0)
{
filtered_result[y] = 1;
}
else
{
filtered_result[y] = 0;
}
#endif
// if samples low ~1 filter removes all values
#if FILTER_SPECTRUM_RESULTS
filtered_result[y] = 0;
// Filter Elements without neighbors
// if RSSI method actual value is -xxx dB
if (result[y] > 0 && samples > 1)
{
// do not process 'first' and 'last' row to avoid out of index
// access.
if ((y > 0) && (y < (result_size - 2)))
{
if (((result[y + 1] != 0) && (result[y + 2] != 0)) ||
(result[y - 1] != 0))
{
filtered_result[y] = 1;
// Fill empty pixel
result[y + 1] = 1;
}
else
{
LOG("Filtered::%i,", y);
}
}
} // not filtering if samples == 1 because it will be filtered
else if (result[y] > 0 && samples == 1)
{
filtered_result[y] = 1;
}
#endif
if (filtered_result[y] && max_rssi_x > y)
{
max_rssi_x = y;
}
}
Event event(*this, EventType::DETECTED, 0);
event.epoch = epoch;
event.detected.detected = max_rssi_x < result_size;
event.detected.freq = current_frequency;
event.detected.rssi =
event.detected.detected ? -(float)result[max_rssi_x] : LO_RSSI_THRESHOLD;
event.detected.detected_at = max_rssi_x;
event.detected.trigger =
event.detected.detected && event.detected.rssi >= trigger_level;
detection_count++;
return event;
}
size_t Scan::addEventListener(EventType t, Listener &l)
{
size_t c = listener_count[(size_t)t];
Listener **new_list = new Listener *[c + 1];
new_list[c] = &l;
listener_count[(size_t)t] = c + 1;
if (c > 0)
{
Listener **old_list = eventListeners[(size_t)t];
memcpy(new_list, old_list, c * sizeof(Listener *));
delete[] old_list;
}
eventListeners[(size_t)t] = new_list;
return c;
}
struct CallbackFunction : Listener
{
void (*cb)(void *arg, Event &e);
void *arg;
CallbackFunction(void cb(void *arg, Event &e), void *arg) : cb(cb), arg(arg) {}
void onEvent(Event &e) { cb(arg, e); }
};
size_t Scan::addEventListener(EventType t, void cb(void *arg, Event &e), void *arg)
{
return addEventListener(t, *(new CallbackFunction(cb, arg)));
}
void Scan::fireEvent(Event &event)
{
Listener **list = eventListeners[(size_t)event.type];
size_t c = listener_count[(size_t)event.type];
for (int i = 0; i < c; i++)
{
list[i]->onEvent(event);
}
list = eventListeners[(size_t)EventType::ALL_EVENTS];
c = listener_count[(size_t)EventType::ALL_EVENTS];
for (int i = 0; i < c; i++)
{
list[i]->onEvent(event);
}
}
#endif

81
lib/scan/scan.h Normal file
View File

@@ -0,0 +1,81 @@
#include <cstdint>
#include <events.h>
#include <stdlib.h>
#ifndef LORASA_CORE_H
#define LORASA_CORE_H
#ifdef PRINT_DEBUG
#define LOG(args...) Serial.printf(args...)
#define LOG_IF(cond, args...) \
if (cond) \
LOG(args...)
#elif !defined(LOG)
#define LOG(args...)
#define LOG_IF(cond, args...)
#endif
// Output Pixel Formula
// 1 = rssi / 4, 2 = (rssi / 2) - 22 or 20
constexpr int RSSI_OUTPUT_FORMULA = 2;
// based on the formula for RSSI_OUTPUT_FORMULA == 2
// -2 * (22 + RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE) < rssi =< -44
// practice may require a better pair of thresholds
constexpr float HI_RSSI_THRESHOLD = -44.0;
constexpr float LO_RSSI_THRESHOLD = HI_RSSI_THRESHOLD - 66;
// number of samples for RSSI method
#ifndef SAMPLES_RSSI
#define SAMPLES_RSSI 13 // 21 //
#endif
#ifdef USING_SX1280PA
#define SAMPLES_RSSI 20
#endif
struct Scan
{
uint64_t epoch;
float current_frequency;
uint64_t fr_begin;
uint64_t fr_end;
uint64_t drone_detection_level;
bool sound_on;
bool led_flag;
uint64_t detection_count;
bool animated;
float trigger_level;
Listener **eventListeners[(size_t)EventType::_MAX_EVENT_TYPE + 1];
size_t listener_count[(size_t)EventType::_MAX_EVENT_TYPE + 1];
Scan()
: epoch(0), current_frequency(0), fr_begin(0), fr_end(0),
drone_detection_level(0), sound_on(false), led_flag(false), detection_count(0),
animated(false), trigger_level(0), listener_count{
0,
} {};
virtual float getRSSI() = 0;
// rssiMethod gets the data similar to the scan method,
// but uses getRSSI directly.
uint16_t rssiMethod(size_t samples, uint16_t *result, size_t res_size);
// detect method analyses result, and produces filtered_result, marking
// those values that represent a detection event.
// It returns index that represents strongest signal at which a detection event
// occurred.
Event detect(uint16_t *result, bool *filtered_result, size_t result_size,
int samples);
size_t addEventListener(EventType t, Listener &l);
size_t addEventListener(EventType t, void cb(void *, Event &), void *arg);
void fireEvent(Event &e);
};
// Remove reading without neighbors
#define FILTER_SPECTRUM_RESULTS true
#endif

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

13
ml_research/datasets.md Normal file
View File

@@ -0,0 +1,13 @@
## Overview of ML Dataets
| dataset name | data content | data format | frequencies / Sampling rate | other infos | source paper/website | source data | open questions |
|----------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
| Noisy Drone RF Signal Classification (Glüge et al.) | Drones: DJI, FutabaT14, FutabaT7, Graupner, Taranis, Turnigy | I/Q Data as well as generated Spectrograms available | The device can scan a range of 6Ghz. But the newest plot is very weird. +/- 7 Mhz, can this really be? "non-overlapping signal vectors of length of 1048576 samples, which corresponds to approx. 74.9ms at 14MHz" | mixed with either Labnoise (50%) or Gaussian noise (50%). The noise class was created by mixing Labnoise and Gaussian noise in all possible combinations. Several levels of SNR were used over the entire dataset. | https://www.scitepress.org/Link.aspx?doi=10.5220/0012176800003595 https://github.com/sgluege/Noisy-Drone-RF-Signal-Classification https://github.com/sgluege/Noisy-Drone-RF-Signal-Classification-v2/tree/main | https://www.kaggle.com/datasets/sgluege/noisy-drone-rf-signal-classification-v2/data https://www.kaggle.com/datasets/sgluege/noisy-drone-rf-signal-classification | Not sure about the frequencies. |
| DroneDataset (Swinney and Woods) | Drones: new DJI Mavic 2 Air S, DJI Mavic Pro, DJI Mavic Pro 2, DJI Inspire 2, DJI Mavic Mini, DJI Phantom 4 and the Parrot Disco. | Raw I/Q Data | TODO; Recordings were collected using a Nuand BladeRF SDR and using open source software GNURadio | There are 4 subsets of data included in this dataset, the UAS signals in the presence of Bluetooth interference, in the presence of Wi-Fi signals, in the presence of both and with no interference. 3 flight modes are captured - switched on, hovering and flying. | No paper seen | https://ieee-dataport.org/open-access/dronedetect-dataset-radio-frequency-dataset-unmanned-aerial-system-uas-signals-machine | Sampling rate? |
| DroneRF (Allahham et al.) | Drones: Bepop; AR; Phantom | "the dataset contains only time series data, and not the complex IQ signals" (From Glüge and not from the authors) - but what does this exactly mean? | capture the whole 2.4GHz bandwidth, we have used 2 RF receivers. Each RF receiver has a maximum instantaneous bandwidth of 40 MHz, so both receivers must be operating simultaneously to at least capture a technology spectrum such as WiFi (i.e. 80 MHz). Recorded using universal soft- ware radio peripheral (USRP) software-defined radio (SDR) transceivers. Signals that could be considered noise in the 2.4 GHz band (Bluetooth, Wi-Fi) were not recorded. | modes, including off, on and connected, hovering, flying, and video recording. | https://www.sciencedirect.com/science/article/pii/S2352340919306675?ref=pdf_download&fr=RR-2&rr=8bf5de727fa35d7f | https://data.mendeley.com/datasets/f4c2b4n755/1 | Apparently time versus db? Not very clear what is in the data |
| Radio-Frequency Control and Video Signal Recordings of Drones (Vuorenmaa et al.) | Drones: DJI Inspire 2 (2.44 and 5.8 GHz), DJI Matrice 100 (2.44 GHz), DJI Matrice 210 (2.44 and 5.8 GHz), DJI Mavic Mini (2.44 GHz), DJI Mavic Pro (2.44 GHz), DJI Phantom 4 (2.44 GHz), DJI Phantom 4 Pro Plus (2.44 and 5.8 GHz), Parrot Disco (2.44 GHz), Parrot Mambo (2.44 GHz), Yuneec Typhoon H (2.44 and 5.7 GHz) | I/Q Data (And Video data) | TODO: not clear yet | | https://zenodo.org/records/4264467 | | |
| Spectrogram Dataset (Wicht et al.) | Wi-Fi and Bluetooth signals (NOT DRONES) | Sepctrograms | | | https://www.mdpi.com/2306-5729/7/12/168 | | |
## Current Proposal for ML
![Diagram ML](./Diagram_ML.svg)

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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
View File

@@ -0,0 +1,67 @@
#include <stdio.h>
#define LOG(args...) printf(args)
#include "../lib/scan/scan.cpp"
#include <unity.h>
struct TestScan : Scan
{
TestScan(float *ctx, int sz) : ctx(ctx), sz(sz), idx(0) {}
float getRSSI() override;
float *ctx;
int sz;
int idx;
};
float TestScan::getRSSI()
{
if (idx >= sz)
{
return -1000000;
}
return ctx[idx++];
}
constexpr int test_sz = 13;
constexpr int inputs_sz = 13;
void test_rssi(void)
{
uint16_t samples[test_sz];
float inputs[inputs_sz] = {-40.0, -100.0, -200.0, -50.0, -400.0, -60.0, -20,
-75.5, -70, -80, -90, -55.9, -110};
TestScan t = TestScan(inputs, inputs_sz);
uint16_t r = t.rssiMethod(inputs_sz, samples, test_sz);
uint16_t expect[test_sz] = {20, 50, 55, 60, 0, 70, 75, 80, 0, 90, 0, 100, 110};
TEST_ASSERT_EQUAL_INT16(20, r);
TEST_ASSERT_EQUAL_INT16_ARRAY(expect, samples, test_sz);
}
void test_detect()
{
uint16_t samples[test_sz] = {20, 50, 55, 60, 0, 70, 75, 80, 0, 90, 0, 100, 110};
bool result[test_sz];
TestScan test_scan({}, 0);
Event e = test_scan.detect(samples, result, test_sz, 1);
size_t r = e.detected.detected_at;
bool expect[test_sz] = {1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1};
TEST_ASSERT_EQUAL_INT16(0, r);
TEST_ASSERT_EQUAL_INT8_ARRAY(expect, result, test_sz);
Event e2 = test_scan.detect(samples, result, test_sz, 2);
r = e2.detected.detected_at;
bool expect2[test_sz] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0};
TEST_ASSERT_EQUAL_INT16(1, r);
TEST_ASSERT_EQUAL_INT8_ARRAY(expect2, result, test_sz);
}

220
test/test_waterfall.cpp Normal file
View File

@@ -0,0 +1,220 @@
#define TO_STRING
#include "../lib/models/WaterfallModel.cpp"
#include <stdio.h>
#include <unity.h>
void test_push()
{
size_t *ms = new size_t[6]{5, 3, 4, 15, 4, 3};
WaterfallModel m(1, 1, 6, ms);
delete ms;
char *r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
"[dt:1 t:0 [ c:0 e:0 ]"
"dt:1 t:0 [ c:0 e:0 ]"
"dt:1 t:0 [ c:0 e:0 ]"
"dt:1 t:0 [ c:0 e:0 ]"
"dt:1 t:0 [ c:0 e:0 ]"
"dt:5 t:0 [ c:0 e:0 ]"
"dt:5 t:0 [ c:0 e:0 ]"
"dt:5 t:0 [ c:0 e:0 ]"
"dt:15 t:0 [ c:0 e:0 ]"
"dt:15 t:0 [ c:0 e:0 ]"
"dt:15 t:0 [ c:0 e:0 ]"
"dt:15 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:900 t:0 [ c:0 e:0 ]"
"dt:900 t:0 [ c:0 e:0 ]"
"dt:900 t:0 [ c:0 e:0 ]"
"dt:900 t:0 [ c:0 e:0 ]"
"dt:3600 t:0 [ c:0 e:0 ]"
"dt:3600 t:0 [ c:0 e:0 ]"
"dt:3600 t:0 [ c:0 e:0 ] ]",
r);
delete r;
m.reset(0, 1);
uint64_t i = 0;
for (; i < 10; i++)
m.updateModel(i, 0, 1);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
"[dt:1 t:9 [ c:1 e:1 ]"
"dt:1 t:8 [ c:1 e:1 ]"
"dt:1 t:7 [ c:1 e:1 ]"
"dt:1 t:6 [ c:1 e:1 ]"
"dt:1 t:5 [ c:1 e:1 ]"
"dt:5 t:5 [ c:5 e:5 ]"
"dt:5 t:5 [ c:0 e:0 ]"
"dt:5 t:5 [ c:0 e:0 ]"
"dt:15 t:15 [ c:0 e:0 ]"
"dt:15 t:15 [ c:0 e:0 ]"
"dt:15 t:15 [ c:0 e:0 ]"
"dt:15 t:15 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ] ]",
r);
delete r;
for (; i < 100; i += 10)
m.updateModel(i, 0, 1);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
"[dt:1 t:90 [ c:1 e:1 ]"
"dt:1 t:89 [ c:0 e:0 ]"
"dt:1 t:88 [ c:0 e:0 ]"
"dt:1 t:87 [ c:0 e:0 ]"
"dt:1 t:86 [ c:0 e:0 ]"
"dt:5 t:85 [ c:0 e:0 ]"
"dt:5 t:80 [ c:1 e:1 ]"
"dt:5 t:75 [ c:0 e:0 ]"
"dt:15 t:75 [ c:1 e:1 ]"
"dt:15 t:60 [ c:2 e:2 ]"
"dt:15 t:45 [ c:1 e:1 ]"
"dt:15 t:30 [ c:2 e:2 ]"
"dt:60 t:60 [ c:11 e:11 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ] ]",
r);
delete r;
for (; i < 10000; i++)
m.updateModel(i, 0, 1);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
"[dt:1 t:9999 [ c:1 e:1 ]"
"dt:1 t:9998 [ c:1 e:1 ]"
"dt:1 t:9997 [ c:1 e:1 ]"
"dt:1 t:9996 [ c:1 e:1 ]"
"dt:1 t:9995 [ c:1 e:1 ]"
"dt:5 t:9995 [ c:4 e:4 ]"
"dt:5 t:9990 [ c:5 e:5 ]"
"dt:5 t:9985 [ c:5 e:5 ]"
"dt:15 t:9990 [ c:5 e:5 ]"
"dt:15 t:9975 [ c:15 e:15 ]"
"dt:15 t:9960 [ c:15 e:15 ]"
"dt:15 t:9945 [ c:15 e:15 ]"
"dt:60 t:9960 [ c:30 e:30 ]"
"dt:60 t:9900 [ c:60 e:60 ]"
"dt:60 t:9840 [ c:60 e:60 ]"
"dt:60 t:9780 [ c:60 e:60 ]"
"dt:60 t:9720 [ c:60 e:60 ]"
"dt:60 t:9660 [ c:60 e:60 ]"
"dt:60 t:9600 [ c:60 e:60 ]"
"dt:60 t:9540 [ c:60 e:60 ]"
"dt:60 t:9480 [ c:60 e:60 ]"
"dt:60 t:9420 [ c:60 e:60 ]"
"dt:60 t:9360 [ c:60 e:60 ]"
"dt:60 t:9300 [ c:60 e:60 ]"
"dt:60 t:9240 [ c:60 e:60 ]"
"dt:60 t:9180 [ c:60 e:60 ]"
"dt:60 t:9120 [ c:60 e:60 ]"
"dt:900 t:9900 [ c:60 e:60 ]"
"dt:900 t:9000 [ c:900 e:900 ]"
"dt:900 t:8100 [ c:900 e:900 ]"
"dt:900 t:7200 [ c:900 e:900 ]"
"dt:3600 t:7200 [ c:2700 e:2700 ]"
"dt:3600 t:3600 [ c:3520 e:3520 ]"
"dt:3600 t:3600 [ c:0 e:0 ] ]",
r);
delete r;
for (; i < 5000; i++)
m.updateModel(i, 0, 1);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
"[dt:1 t:9999 [ c:1 e:1 ]"
"dt:1 t:9998 [ c:1 e:1 ]"
"dt:1 t:9997 [ c:1 e:1 ]"
"dt:1 t:9996 [ c:1 e:1 ]"
"dt:1 t:9995 [ c:1 e:1 ]"
"dt:5 t:9995 [ c:4 e:4 ]"
"dt:5 t:9990 [ c:5 e:5 ]"
"dt:5 t:9985 [ c:5 e:5 ]"
"dt:15 t:9990 [ c:5 e:5 ]"
"dt:15 t:9975 [ c:15 e:15 ]"
"dt:15 t:9960 [ c:15 e:15 ]"
"dt:15 t:9945 [ c:15 e:15 ]"
"dt:60 t:9960 [ c:30 e:30 ]"
"dt:60 t:9900 [ c:60 e:60 ]"
"dt:60 t:9840 [ c:60 e:60 ]"
"dt:60 t:9780 [ c:60 e:60 ]"
"dt:60 t:9720 [ c:60 e:60 ]"
"dt:60 t:9660 [ c:60 e:60 ]"
"dt:60 t:9600 [ c:60 e:60 ]"
"dt:60 t:9540 [ c:60 e:60 ]"
"dt:60 t:9480 [ c:60 e:60 ]"
"dt:60 t:9420 [ c:60 e:60 ]"
"dt:60 t:9360 [ c:60 e:60 ]"
"dt:60 t:9300 [ c:60 e:60 ]"
"dt:60 t:9240 [ c:60 e:60 ]"
"dt:60 t:9180 [ c:60 e:60 ]"
"dt:60 t:9120 [ c:60 e:60 ]"
"dt:900 t:9900 [ c:60 e:60 ]"
"dt:900 t:9000 [ c:900 e:900 ]"
"dt:900 t:8100 [ c:900 e:900 ]"
"dt:900 t:7200 [ c:900 e:900 ]"
"dt:3600 t:7200 [ c:2700 e:2700 ]"
"dt:3600 t:3600 [ c:3520 e:3520 ]"
"dt:3600 t:3600 [ c:0 e:0 ] ]",
r);
delete r;
}

21
test/tests.cpp Normal file
View File

@@ -0,0 +1,21 @@
#include <unity.h>
void test_rssi();
void test_detect();
void test_push();
void setUp(void) {}
void tearDown(void) {}
int main(int argc, char **argv)
{
UNITY_BEGIN();
RUN_TEST(test_rssi);
RUN_TEST(test_detect);
RUN_TEST(test_push);
UNITY_END();
}

View File

@@ -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();

View File

@@ -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;
}
}