Monitoring an RF24/NRF52x Network & Creating a Scanner with the XIAO 52840 Sense
Monitoring the data from a scanner and other sources in real-time
I've been trying automate analysis of the RF24/NRF52 communication stack lately and decided it was time to automate things to a degree further. I've setup a system using MQTT and a failover node in the network, so at any given time, I can switch my main RF24/RF52 network to a different channel for testing purposes. The results have been a bit interesting, with interference popping up on channels that appear clean for long periods of time, going away after I switch to a new channel... for a while.
I also leverage NodeRed, MQTT, and other methods to monitor the radio network. To put things in perspective, I am using TCP/IP over the radio network, using the NRF24 radios as a standard NIC, to test connectivity etc, so the radios are very busy, transmitting many 32-byte packets for every TCP/IP packet sent. The network layer handles routing, fragmentation/reassembly, and the mesh layer ensures nodes stay connected to the network even under adverse conditions.
Here is a couple pictures showing ping statistics for a RPi5 running RF24Gateway in close proximity to a very busy master node:
This last picture shows a clean channel with minimal interference over a long period of time.
The % success rate of pinging the RPi5 from the master node, showing 2.24% packet loss on a busy network after a couple hours after changing to a better channel:
This is with 17 RF42Ethernet and/or RF24Gateway nodes running on the network and communicating large MQTT messages typically every few seconds each, so I'd say that amount of packet loss is very much acceptable. The network runs on a variety of NRF24 modules as well as the newer NRF52x modules.
The following is a graph of how long each of 10 nodes stay connected to the MQTT server over the radio network before having to re-connect. This tends to happen when there is interference and/or the mesh breaks down a bit and needs to re-converge. In some cases, in good radio conditions, nodes stay connected up to 5 hours or longer. With a recent update to RF24Ethernet, nodes should now stay connected even longer!
You see, I was thinking that 17 nodes pumping out constant traffic might be a bit too much for a the mesh network, but the results of testing indicate that this is not the case. I've had more nodes on a single network in the past, but was doubting recent changes to the network and the network itself. Testing on separate channels shows that the mesh works great with this number of nodes, its just the interference causing problems, which would occur with any number nodes.
One good example is the following screenshot, you can see the RX Packets (user): 327 line. Since none of my nodes are sending RF24Mesh or RF24Network messages, this is just one more pretty good indicator that one of my neighbors has an affinity for RF24 programming and is constantly hitting my network with spam and garbage. There is a history of DOS attacks as well, which took advantage of limited functionality of the is_valid_address() function of RF24Network at the time, and continue in very limited fashion due to the simplicity and effectiveness of RF24Mesh. Its been going on for years, almost daily, and I just can't understand the obsession with trying to hack my radio network. Its for testing purposes and contains no useful data.
For somebody who was able to study the RF24 stack and identify a quality DOS attack that required only one small radio and took down my whole network, why not do something constructive with those skills?
The following is a good example of interference on the network, with my main network experiencing massive interference, with a node about 1.5-meters from the master unable to establish connectivity for a long period of time. I'm not sure what creates this level of interference, but it seems most if not all of my nodes on the main testing network using channel 111 lost connectivity for this same period of time between 10:33pm and 12:18am Jun 28th to Jun 29th 2024.
With that, I've developed a scanner sketch for the XIAO NRF52840 Sense, since the radio has a feature to directly monitor RSSI levels, and it also has an option to monitor received packets separately. This is a little different from the scanner sketch included with the library, since it uses the RGB LEDs onboard to indicate signal strength in real time. It also outputs data for the Serial Plotter in the Arduino IDE, so you can see what is going on on a graph as well as the LED indicators.
The Scanner:
The images below show the Serial Plotter output in the Arduino IDE. Since I have a very busy master node, it is normal to see a fair number of 'RX 'packets. The 'Values' indicator shows the count of how often the radio received a signal with an RSSI better than -65dBm.
The scan in the pics are during normal mesh operation, with not much happening. When there is a large amount of interference the RSSI (green) line goes way up, communication is hindered, but the mesh nature of the system ensures that nodes re-converge around the master node in a short period of time after the interference stops. Testing shows the mesh is very stable and reliable at this point, with interference measured showing a direct correlation to the communication abilities of the mesh.
The Sketch:The sketch is fairly simple, but very useful for pulling in data regarding noise and received packets on a given channel over time.
The sketch is also available on GitHub here.
/*
* Single channel scanner for XIAO 52840 Sense
*
* Monitoring of a single channel using the built-in radio of the NRF52840
* Scans a single channel for noise using the RSSI measurement feature
* Can also listen for received packets
*
* Used for monitoring level of traffic and noise separately for a given RF24Mesh master node
* Default is RSSI level monitoring, uncomment USE_RX to enable monitoring of received packets also
*
* ** LED Indicators: **
* Blue: Minimal traffic,
* Green: Medium traffic, 15/100 signals > -65dBm
* Red: Heavy traffic, 25/100 signals > -65dBm
* Orange: Very heavy traffic, 35/100 signals > -65dBm
*
*/
#include "nrf_to_nrf.h"
#include <RF24Network.h>
#include <RF24Mesh.h>
/**********************************************************/
#define CHANNEL 3 // What channel to scan on
#define MIN_DBM 65 // The minimum RSSI (dBm) for counting signals. Higher value == greater sensitivity.
#define USE_RX // Also listen for and count received packets
/**********************************************************/
// Set up nRF24L01 radio on SPI bus plus pins 7 & 8
nrf_to_nrf radio;
RF52Network network(radio);
RF52Mesh mesh(radio, network);
const uint8_t num_channels = 1;
uint8_t values = 0;
uint8_t valuesR = 0;
// LED Setup
void red() {
digitalWrite(LED_BLUE, HIGH);
digitalWrite(LED_RED, LOW);
digitalWrite(LED_GREEN, HIGH);
}
void green() {
digitalWrite(LED_BLUE, HIGH);
digitalWrite(LED_RED, HIGH);
digitalWrite(LED_GREEN, LOW);
}
void blue() {
digitalWrite(LED_BLUE, LOW);
digitalWrite(LED_RED, HIGH);
digitalWrite(LED_GREEN, HIGH);
}
void orange() {
digitalWrite(LED_BLUE, HIGH);
digitalWrite(LED_RED, LOW);
digitalWrite(LED_GREEN, LOW);
}
void setup(void) {
Serial.begin(115200);
Serial.println(F("\n\rRF24/examples/scanner/"));
// Setup and configure rf radio
mesh.setNodeID(0);
mesh.begin(CHANNEL);
radio.setAutoAck(false);
radio.setCRCLength(NRF_CRC_DISABLED);
radio.setAddressWidth(2);
radio.setChannel(CHANNEL);
radio.startListening();
}
const int num_reps = 100; // Max 255
void loop(void) {
// Clear measurement values
values = 0;
valuesR = 0;
// Scan channel num_reps times
int rep_counter = num_reps;
while (rep_counter--) {
if (radio.testCarrier(MIN_DBM)) {
++values;
}
delayMicroseconds(256);
#if defined USE_RX
if (radio.available()) {
radio.read(0, 0);
valuesR++;
}
#endif
delayMicroseconds(256);
}
// Print out channel measurements
Serial.print("Low:");
Serial.println(15);
Serial.print("Med:");
Serial.println(25);
Serial.print("High:");
Serial.println(35);
Serial.print("Values:");
Serial.println(values);
if (values >= 15 && values < 25) {
green();
} else if (values >= 25 && values < 35) {
red();
} else if (values >= 35) {
orange();
} else {
blue();
}
#if defined USE_RX
Serial.print("RX:");
if (valuesR) {
Serial.println(valuesR);
} else {
Serial.println(0);
}
#endif
}