RF24
RF24 copied to clipboard
A new scanner example
@Avamander If I were to write a new scanner example,
- I would require that
startConstCarrier()
take no arguments because the PA Level ~and RF channel~ can/should be set independently ofstartConstCarrier()
. - Furthermore, Any example that involves other peripheral hardware that isn't a nRF24 module (like a TFT display) should be saved to the "examples/recipes/" folder because such an example requires external libraries/hardware that isn't directly supported by RF24 lib.
- Unfortunately, I don't own a TFT display to test on ☹️ , rather I would probably just use the Arduino IDE's Serial plotter option (which would output slower than an I2C based TFT display).
- The github action that runs CI with the Arduino CLI compiler could be told to install an external library as well.
-
testRpd()
(&testCarrier()
) only detect signals "louder" than -64 dBm. The video you linked to is doing some averaging and manipulation on falling peaks. This is what makes your request interesting (to me). Furthermore, this data is only reset when entering RX mode (specifically during FALLING transitions of the CE pin when the CONFIG register's PRIM_RX bit is asserted), so add the confusion of manually manipulating the CE pin (considering thatce()
is a private function).
Nonetheless, I shy away from no challenge...
Originally posted by @2bndy5 in https://github.com/nRF24/RF24/issues/713#issuecomment-753412267
I think this is worthy of separate discussion so I split it into a separate issue.
I've been giving it more thought... Instead of a TFT display, we could use a python program (using the cross-platform freindly pyserial lib for input from the MCU) to output a more visually appealing and/or understandable chart/graph data to a window (probably using pygame or the builtin tkinter libs for drawing). Of course this idea would be even harder for people (like me) ssh-ing to RPi and running examples from the terminal.
It seems to me that this isn't a trivial example to build. There's Linux machines and Arduino boards that would both need a separate approach.
On Linux it would be possible to build something appealing with C++/ncurses or Python/rich-python without a lot of effort.
On an Arduino board it would theoretically be possible to use Adafruit-GFX API and it should work on a lot of displays.
I would require that startConstCarrier() take no arguments because the ~RF data rate~ PA Level and RF channel can/should be set independently of startConstCarrier().
After a little testing with existing scanner example, I noticed that if I changed the channel (on a separate nRF24 module) while the Carrier Wave was transmitting, then scanning nRF24 module would show the carrier wave detected on the wrong channel. This needs more investigation, but I have a feeling the module TX-ing the carrier wave isn't modulating to the right frequency while the PLL is locked (because its transmitting)...
Also I haven't tried changing the PA level while transmitting a carrier wave, but that would be harder to verify if the new PA level is achieved correctly. I'm curious if @waltbar did extensive testing before submitting #609 (which is why startConstCarrier()
takes args to begin with).
Just for reference, it was the comment from @Avamander that started this "new scanner" idea:
@2bndy5 I'm not saying you should do it, but maybe a newer Scanner sketch would still be a nice addition amongst the examples? E.g. with optional TFT support like on this video: https://www.youtube.com/watch?v=kapd3wM9hsg
The youtube video's description has a link to the scanner source code
but I have a feeling the module TX-ing the carrier wave isn't modulating to the right frequency while the PLL is locked (because its transmitting)...
This sounds like an useful thing to figure out. I wonder if anyone here has an SDR that could answer what piece here is incorrect.
On Linux it would be possible to build something appealing with C++/ncurses or Python/rich-python without a lot of effort.
A lot of python people swear by the colorama
(3rd-party) lib for changing colors in the terminal. I've used it before as a dependency to a logger lib for python flask (It works rather well).
A lot of python people swear by the
colorama
(3rd-party) lib for changing colors in the terminal. I've used it before as a dependency to a logger lib for python flask (It works rather well).
Rich is not just for setting colors, has quite a few nice features/UI-elements built-in that are useful when building a nice quick CLI, I can recommend it.
interesting lib (it also requires colorama as a dep), but how do you evision the scanner output?
A quick mock-up from using GIMP
I was just thinking a progress bar per-channel and just update those with signal strength. (So basically yours but rotated right 90deg)
glad I asked. Now I just have to explore the ncursers and rich-python libs A LOT more...
I'm currently tied up in doing a wireless HID hub using circuitpython (something that should mimic Logitech's Unifying dongles)...
We need to consider improving the "frame rate" for the scanner output.
I made an attempt at the Arduino+OLED (using I2C) sketch, and the "refresh rate" (using algorithm from original scanner.ino) is drastically slow (about 5 seconds).
The timing etc of the scanner example is dictated by the hardware mostly, as the device needs to listen for a bit. Providing the user with a channel range to scan could speed it up instead of scanning the entire range. Or do you mean to display each channel as it is scanned?
On Jan 15, 2021, at 7:37 AM, Brendan [email protected] wrote:
We need to consider improving the "frame rate" for the scanner output.
I made an attempt at the Arduino+OLED (using I2C) sketch, and the "refresh rate" (using algorithm from original scanner.ino) is drastically slow (about 5 seconds).
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.
I was thinking of displaying each channel as it is scanned. Although, providing user with input options (at program start up) isn't a bad idea. Say they want to change the data rate
I just tried using \r
instead of the default \n
in python's print()
, and it seems to have a adequate refresh rate (the cursor also looks like a really fast sweeping animated status for the current channel 😃 ) using only the python REPL (no graphic display). Example (using my circuitpython library):
for curr_channel in range(126): # for each channel
nrf.channel = curr_channel
time.sleep(0.00013) # let the radio finish modulating to the new channel
nrf.listen = 1 # start a RX session
time.sleep(0.00013) # wait 130 microseconds
signals[curr_channel] += nrf.rpd # if interference is present
nrf.listen = 0 # end the RX session
# output the signal counts per channel
print(
hex(min(0x0F, signals[curr_channel]))[2:] if signals[curr_channel] else "-",
sep="",
end="" if curr_channel < 125 else "\r",
)
# print `signals` array 1 last time with a LF at EoL for prettiness
This technique could also be used in the Linux C++ version of the scanner example (using cout
instead of printf()
).
Last time I tried doing a Serial.print(F("some data \r"));
, it didn't work in the Arduino IDE's serial monitor. ☹️ IIRC, it just output \r
like a \n
.
EDIT: Apparently the "sweeping animated cursor" for the status of the current channel being scanned is specific to using putty. 🤷🏻♂️
Question
How should we calculate the decay of the peak for a channel? Should it be time based or the last x number of scans on that channel?
@Avamander printing 125 horizontal progress bars may be problematic because each progress bar in rich python is essentially 1 line and the scrolling of terminal windows. Were you thinking of averaging 2 or 3 channels per line?
we can do 3 columns of 41 progress bars or something like that to utilize screen realty. 25 lines seems like a safe bet to avoid automatic scrolling.
we can do 3 columns of 41 progress bars or something like that to utilize screen realty. 25 lines seems like a safe bet to avoid automatic scrolling.
It'd be possible to read the amount of available lines and divide the channels between those?
Python rich has a way of finding the dimensions of the console that we could divy up accordingly.
I still haven't looked at ncurses yet... I imagine they must have an equivalent option.
int x1 = xx1;
int y1 = yy1;
int Sc;
int x2, y2;
int BlobHeight = 4; // Blobs are 4x4 pixels
char NB[12]; // Number Buffer
char NB1[12];
char NB2[12];
char NB3[12];
char NB4[12];
char CB[100]; // COMMAND BUFFER
char fyll[] = "fill ";
char IELLOW[] = "YELLOW";
char NA[1] = ""; // blank one
for (Sc = ScanStart; Sc <= ScanEnd; Sc++) {
Radio1.setChannel(Sc);
Radio1.startListening();
x2 = x1 + (Sc * 5);
y2 = y1 + 255;
y2 = y2 - BlobHeight;
y2 = y2 - AllChannels[Sc];
delayMicroseconds(120); // Minimum!?
Radio1.stopListening();
if (Radio1.testCarrier()) {
if (AllChannels[Sc] < (250)) {
AllChannels[Sc] += BlobHeight;
/* send all channel data to the display */ // EDITED FROM OG SRC
}
}
else {
NoCarrier[Sc]++;
if (NoCarrier[Sc] > 15) { // must see no carrier >15 times before reducing the trace
if (AllChannels[Sc] >= (BlobHeight)) {
/* send all channel data to the display */ // EDITED FROM OG SRC
AllChannels[Sc] -= (BlobHeight);
NoCarrier[Sc] = 0;
}
}
}
}
I noticed there wasn't any python equivalent to scanner.cpp in the examples_linux, so I did some preliminary UI design and rudimentary radio integration with python's rich
module.
I have yet to compensate for peak decays, but its working. There seems to be a lot of graphical "page tearing", though that might be due to ssh tunneling.
scanner.py
import time
try:
from rich.table import Table
from rich.console import Console
from rich.progress import BarColumn, Progress, TextColumn
from rich.live import Live
from rich.prompt import Prompt, IntPrompt
except ImportError:
raise OSError(
"This example requires the python `rich` module installed."
"\ninstall it using 'python3 -m pip install rich'"
)
from RF24 import RF24, RF24_1MBPS, RF24_2MBPS, RF24_250KBPS
CSN_PIN = 0 # connected to GPIO8
CE_PIN = 22 # connected to GPIO22
radio = RF24(CE_PIN, CSN_PIN)
if not radio.begin():
raise RuntimeError("Radio hardware not responding!")
radio.setAutoAck(False)
console = Console()
console.print("1. 1Mbps\n2. 2Mbps\n3. 250kbps")
# create table of progress bars (labeled by frewquency channel in MHz)
table = Table.grid(padding=(0,1))
signals = [0] * 126 # for tracking the signal count on each channel
progress_bars = [Progress()] * 126
for i in range(21): # 21 rows
row = []
for j in range(i, i + (21 * 6), 21): # 6 columns
progress_bars[j] = Progress(TextColumn("{task.description}"), BarColumn())
progress_bars[j].add_task(f"{2400 + (j)}") # only 1 task for each
row.append(progress_bars[j])
table.add_row(*row)
data_rate = int(Prompt.ask("Choose the data rate", choices=["1", "2", "3"], default="1"))
if data_rate == 1:
radio.setDataRate(RF24_1MBPS)
elif data_rate == 2:
radio.setDataRate(RF24_2MBPS)
elif data_rate == 3:
radio.setDataRate(RF24_250KBPS)
timeout = IntPrompt.ask("Enter the scan duration (in whole seconds)")
console.print(
"Beginning scan using {} for {} seconds. Channel labels are in MHz.".format(
"1Mbps" if data_rate == 1 else ("2Mbps" if data_rate == 2 else "250kbps"),
timeout
)
)
def scan_channel(channel) -> bool:
"""Scan a specified channel and report if a signal was detected."""
radio.channel = channel
radio.startListening()
time.sleep(0.00013)
result = radio.testRPD()
radio.stopListening()
return result
# perform scan
timeout = time.monotonic() + timeout
with Live(table, refresh_per_second=3000) as live:
try:
scan_count = 1 # count cycle per entire spectrum
while time.monotonic() < timeout:
for i, bar in enumerate(progress_bars):
signals[i] += scan_channel(i) # does not handle peak decay
bar.update(bar.task_ids[0], completed=signals[i], total=scan_count)
scan_count += 1
except KeyboardInterrupt:
radio.powerDown()
console.print("keyboard interrupt detected; powering down radio.")
I just found out that the python std libs ship with a ncurses wrapper (for Linux platforms only). This might be a better alternative to the rich
lib (which is written in pure python and a bit sluggish over ssh). Furthermore, the differences between a ncurses C++ scanner example and a python equivalent example would be more manageable.
So I put together a couple examples that use the curses lib (1 in python and 1 in C++) on the promiscuous-scanners branch. The python example runs very well, but I have no idea why the C++ example doesn't pick up as many signals (practically nothing). Everything is set up almost identically for both examples...
As for the Arduino example, I have an idea for using macro defines to allow using either an I2C driven display or an SPI driven display. By default, I think I'll demo the I2C driven display since getting 2 SPI devices on 1 bus can be tricky depending on the display being used.
I'm getting pretty consistent results between the C++ and Python scanner on my RPI4. My RPi Model B provides much more active output, and I'm not sure the reason, whether it is the radio connected or the RPi version. At least the RPi4 is consistently picking up signals from my 2 active networks on channels 30 and 50.
One thing to note, if the SSH window is too small, I'm getting an error with the python example:
File "/home/pi/rf24libs/RF24/examples_linux/RF24/examples_linux/scanner.py", line 152, in main
scanner_output_window = std_scr.subpad(26, curses.COLS, 0, 0)
_curses.error: curses function returned NULL
It may be worth trying to detect the size of the window and giving a non-cryptic error message instead, or at least adding the info to the example.
Thanks for testing the C++ curses example, I was starting to think it was my setup somehow (using RPi4 w/ adapter board) I haven't tried the C++ example on my RPi2 (yet).
Both examples don't compensate for dynamically resizing the terminal window; that was something I wanted to add later after proof of concept was established... I'll get to work on that next. TBH, the python example doesn't need to use a separate window (aka "pad"), so I can remove that call completely.
I've been prototyping an Arduino scanner with my I2C display, but the detected noise hasn't been ideal. I thought that using my ATSAMD21 board might help with the display refresh rate, but I think there's a bottleneck at the display throughput (w/ or w/o debugging in Serial monitor). Here's a sample pic with display updating once per cycle of spectrum (caching the last 6 scans of the spectrum for peak math):
So far the Arduino code is local only. I wanted to try out my SPI display and see if that refresh rate was any better.
I retract my refresh rate complaints. Turns out I had a rogue Serial.println()
still executing. And, I keep forgetting that scanning for noise while everyone is still sleeping is pretty much just my own EM footprint (& possibly from neighbors or passing cars). 🤣
Refresh rate is rather good when I toggle a const carrier wave from another device.. Its kinda fun to watch the peak go up and down.
Hey, I just realized that testRPD(); will only catch signals >64dBm, but radio.available(); will catch signals <64dBm, so a call to available could be added as well to increase responsiveness even more! See https://github.com/nRF24/RF24/blob/promiscuous-scanners/examples_linux/scanner.cpp#L150 - modify to
if (foundSignal || radio.testRPD() || radio.available()) {
I thought any payload received had to be >64dbm. Its an interesting idea though because I think the RX FIFO may be getting filled while the RPD flag isn't asserted, so the RX FIFO doesn't seem to be getting flushed when it should be (thus blocking noise detection). In fact, I called flush_rx()
before startListening()
during my dev tests, and I got a better response from noise. Using available()
might help translate that condition into what should be RPD assertion.
@TMRh20 Are you using putty for ssh? I've switched to Windows Terminal (I think it ships with Win11 as a default app) for my SSH needs.
Putty's terminal window is typically very small for every new session (using default config). The curses grid needs a 48-character width just to display the channel's labels and signal counts (that's not even including the actual progress bar which uses a dynamically calculated width).
Per the datasheet: Receiver X Fast AGC for improved dynamic range X Integrated channel filters X 13.5mA at 2Mbps X -82dBm sensitivity at 2Mbps X -85dBm sensitivity at 1Mbps X -94dBm sensitivity at 250kbps
I'll have to check out windows terminal, I am using putty.
I'll have to check out windows terminal, I am using putty.
You won't be disappointed. It now comes with builtin WSL and VS Dev prompt support, so now I can have a native Ubuntu bash in 1 tab and another tab to compile using VS compiler... And colors 🏳️🌈 !