Files
LoraSA/SpectrumScan.py
2024-09-15 00:54:47 +02:00

237 lines
7.0 KiB
Python

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
import argparse
import serial
import sys
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from datetime import datetime
from argparse import RawTextHelpFormatter
from datetime import datetime
# number of samples in each scanline
SCAN_WIDTH = 4
SCAN_MIN_FREQ = "850"
SCAN_TIME_POINTS = 40
# 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_BAUDRATE = 115200
DEFAULT_COLOR_MAP = "viridis"
DEFAULT_SCAN_LEN = 20
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",
):
"""
Call in a loop to create terminal progress bar
@params:
iteration - Required : current iteration (Int)
total - Required : total iterations (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
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)
"""
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)
if iteration == total:
print()
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
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.
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"
)
args = parser.parse_args()
freq_mode = False
scan_len = args.len
if args.freq != -1:
freq_mode = True
scan_len = 1000
# create the color map and the result array
arr = np.zeros((SCAN_WIDTH, scan_len, SCAN_TIME_POINTS))
# scanline counter
row = 0
# list of frequencies in frequency mode
freq_list = []
time_list = []
start=True
current_time_point=0
# open the COM port
with serial.Serial(args.port, args.speed, timeout=None) as com:
while True:
# read a single line
try:
line = com.readline().decode("utf-8")
#print(line)
except:
continue
if "LOOP:" in line:
continue
if start:
if SCAN_MIN_FREQ not in line:
continue
else:
start=False
start_time = datetime.now()
# update the progress bar
if not freq_mode:
printProgressBar(current_time_point*scan_len+row, scan_len*SCAN_TIME_POINTS)
if SCAN_MARK_FREQ in line:
new_freq = float(line.split(" ")[1])
if (len(freq_list) > 1) and (new_freq < freq_list[-1]):
continue
freq_list.append(new_freq)
print("{:.3f}".format(new_freq), end="\r")
continue
# 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][current_time_point] = int(scanline[col])
# increment the row counter
row = row + 1
# check if we're done
if (not freq_mode) and (row >= scan_len):
current_time_point+=1
row=0
current_time=datetime.now()
time_list.append(round((current_time-start_time).total_seconds(),1))
if current_time_point==SCAN_TIME_POINTS:
break
# scale to the number of scans (sum of any given scanline)
#num_samples = arr.sum(axis=0)[0]
#arr *= num_samples / arr.max()
#print("NUM SAMPLES:",num_samples)
#print("ARR.MAX:",arr.max())
print("ARR.SHAPE:",arr.shape)
print("LEN_FREQS:",len(freq_list))
arr=arr.mean(axis=0)
print("ARR.SHAPE:",arr.shape)
#arr=arr.reshape(-1,arr.shape[2])
if freq_mode:
scan_len = len(freq_list)
# create the figure
fig, ax = plt.subplots()
#print(arr)
print(freq_list)
print(time_list)
# display the result as heatmap
extent = [0, scan_len, -4 * (SCAN_WIDTH + 1), args.offset]
#extent[1] = time_list[-1]
extent[3] = freq_list[0]
extent[2] = freq_list[-1]
im = ax.imshow(arr, cmap=args.map, extent=extent)
fig.colorbar(im)
# set some properites and show
timestamp = datetime.now().strftime("%y-%m-%d %H-%M-%S")
title = f"Spectrogram with values as mean dBm of 4 samples for 40 loops {timestamp}"
plt.xlabel("Time (N of loop at the moment)")
plt.ylabel("Frequency [MHz]")
#plt.xticks(time_list[::5],time_list[::5])
plt.yticks(freq_list.reverse(),freq_list.reverse())
ax.set_aspect("auto")
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()