From 2b1f4e5a23d8f5e5a69fc1b2adbf98e1d69d600d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Tue, 12 Jul 2022 06:35:12 -0700 Subject: [PATCH 01/66] adjust scanner examples as discussed in #852 --- examples/scanner/scanner.ino | 7 +++++++ examples_linux/scanner.cpp | 7 +++++++ examples_linux/scanner.py | 9 +++++++++ 3 files changed, 23 insertions(+) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index 119a81f65..93d007d28 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -40,6 +40,7 @@ RF24 radio(CE_PIN, CSN_PIN); const uint8_t num_channels = 126; uint8_t values[num_channels]; +const uint8_t noiseAddress[][2] = { {0x55, 0x55}, {0xAA, 0xAA} }; // // Setup @@ -60,10 +61,15 @@ void setup(void) { radio.begin(); radio.setAutoAck(false); + radio.disableCRC(); + radio.setAddressWidth(2); + radio.openReadingPipe(0, noiseAddress[0]); + radio.openReadingPipe(1, noiseAddress[1]); // Get into standby mode radio.startListening(); radio.stopListening(); + radio.flush_rx(); radio.printDetails(); //delay(1000); @@ -130,6 +136,7 @@ void loop(void) { // Did we get a carrier? if (radio.testCarrier()) { ++values[i]; + radio.flush_rx(); } } } diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index b02b323ff..86dc2b235 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -50,6 +50,7 @@ RF24 radio(CE_PIN, CSN_PIN); // Channel info const uint8_t num_channels = 126; uint8_t values[num_channels]; +const uint8_t noiseAddress[][2] = { {0x55, 0x55}, {0xAA, 0xAA} }; const int num_reps = 100; int reset_array = 0; @@ -67,10 +68,15 @@ int main(int argc, char** argv) radio.begin(); radio.setAutoAck(false); + radio.disableCRC(); + radio.setAddressWidth(2); + radio.openReadingPipe(0, noiseAddress[0]); + radio.openReadingPipe(1, noiseAddress[1]); // Get into standby mode radio.startListening(); radio.stopListening(); + radio.flush_rx(); radio.printDetails(); @@ -113,6 +119,7 @@ int main(int argc, char** argv) // Did we get a carrier? if (radio.testCarrier()) { ++values[i]; + radio.flush_rx(); } } } diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 014bca636..503f9a064 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -24,6 +24,13 @@ if not radio.begin(): raise RuntimeError("Radio hardware not responding!") radio.setAutoAck(False) +radio.disableCRC() +radio.setAddressWidth(2) +radio.openReadingPipe(0, b"\x55\x55") +radio.openReadingPipe(1, b"\xAA\xAA") +radio.startListening() +radio.stopListening() +radio.flush_rx() offered_rates = ["1 Mbps", "2 Mbps", "250 kbps"] available_rates = [RF24_1MBPS, RF24_2MBPS, RF24_250KBPS] @@ -68,6 +75,8 @@ def scan_channel(channel: int) -> bool: time.sleep(0.00013) result = radio.testRPD() radio.stopListening() + if result: + radio.flush_rx() return result From 28979ede4b1d50cfc0c0977fce701156e23ce9a1 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Tue, 12 Jul 2022 06:38:11 -0700 Subject: [PATCH 02/66] ran clang-format on scanner changes --- examples/scanner/scanner.ino | 2 +- examples_linux/scanner.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index 93d007d28..74c2a01d7 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -40,7 +40,7 @@ RF24 radio(CE_PIN, CSN_PIN); const uint8_t num_channels = 126; uint8_t values[num_channels]; -const uint8_t noiseAddress[][2] = { {0x55, 0x55}, {0xAA, 0xAA} }; +const uint8_t noiseAddress[][2] = { { 0x55, 0x55 }, { 0xAA, 0xAA } }; // // Setup diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index 86dc2b235..693b7de2c 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -50,7 +50,7 @@ RF24 radio(CE_PIN, CSN_PIN); // Channel info const uint8_t num_channels = 126; uint8_t values[num_channels]; -const uint8_t noiseAddress[][2] = { {0x55, 0x55}, {0xAA, 0xAA} }; +const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; const int num_reps = 100; int reset_array = 0; From 7fe365d171e29e62c855340421e21a4aab7a550d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Tue, 12 Jul 2022 12:58:34 -0700 Subject: [PATCH 03/66] overhaul C++ scanners --- examples/scanner/scanner.ino | 174 +++++++++++++++++++++++------------ examples_linux/scanner.cpp | 152 ++++++++++++++++++++---------- 2 files changed, 217 insertions(+), 109 deletions(-) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index 74c2a01d7..b08b6b026 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -22,6 +22,26 @@ * See http://arduino.cc/forum/index.php/topic,54795.0.html */ +/* + * How to read the output: + * - The header is a list of supported channels in decimal written vertically. + * - Each column corresponding to the vertical header is a hexadecimal count of + * detected signals (max is 15 or 'f'). + * + * The following example + * 000 + * 111 + * 789 + * ~~~ <- just a divider between the channel's vertical labels and signal counts + * 1-2 + * can be interpreted as + * - 1 signal detected on channel 17 + * - 0 signals (denoted as '-') detected on channel 18 + * - 2 signals detected on channel 19 + * + * Each line of signal counts represent 100 passes of the supported spectrum. + */ + #include "RF24.h" #include "printf.h" @@ -38,80 +58,95 @@ RF24 radio(CE_PIN, CSN_PIN); // Channel info // -const uint8_t num_channels = 126; -uint8_t values[num_channels]; +const uint8_t num_channels = 126; // 0-125 are supported +uint8_t values[num_channels]; // the array to store summary of signal counts per channel + +// To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). +// These addresses are designed to confuse the radio into thinking +// that the RF signal's preamble is part of the packet/payload. const uint8_t noiseAddress[][2] = { { 0x55, 0x55 }, { 0xAA, 0xAA } }; -// -// Setup -// +const int num_reps = 100; // number of passes for each scan of the entire spectrum +bool constCarrierMode = 0; // this flag controls example behavior (scan mode is default) + +void printHeader(); // prototype function for printing the channels' header + void setup(void) { - // - // Print preamble - // + // Print preamble Serial.begin(115200); - printf_begin(); - Serial.println(F("\n\rRF24/examples/scanner/")); + while (!Serial) { + // some boards need this to wait for Serial connection + } + Serial.println(F("RF24/examples/scanner/")); + Serial.println(F("!!! Please do not send line endings in the serial monitor !!!")); - // // Setup and configure rf radio - // - - radio.begin(); - radio.setAutoAck(false); - radio.disableCRC(); - radio.setAddressWidth(2); + if (!radio.begin()) { + Serial.println(F("radio hardware not responding!")); + while (true) { + // hold in an infinite loop + } + } + radio.setAutoAck(false); // Don't acknowledge arbitrary signals + radio.disableCRC(); // Accept any signal we find + radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) radio.openReadingPipe(0, noiseAddress[0]); radio.openReadingPipe(1, noiseAddress[1]); + // set the data rate + Serial.print(F("Select your Data Rate. ")); + Serial.println(F("Enter '1' for 1Mbps, 2 for 2Mbps, 3 for 250kbps. Defaults to 1Mbps.")); + while (!Serial.available()) { + // wait for user input + } + uint8_t dataRate = Serial.read(); + if (dataRate == 50) { + Serial.println(F("Using 2 Mbps.")); + radio.setDataRate(RF24_2MBPS); + } else if (dataRate == 51) { + Serial.println(F("Using 250 kbps.")); + radio.setDataRate(RF24_250KBPS); + } else { + Serial.println(F("Using 1 Mbps.")); + radio.setDataRate(RF24_1MBPS); + } + Serial.println(F("***Enter a channel number to emit a constant carrier wave.")); + Serial.println(F("***Enter a negative number to switch back to scanner mode.")); + // Get into standby mode radio.startListening(); radio.stopListening(); radio.flush_rx(); - radio.printDetails(); - - //delay(1000); - // Print out header, high then low digit - int i = 0; - while (i < num_channels) { - Serial.print(i >> 4, HEX); - ++i; - } - Serial.println(); - i = 0; - while (i < num_channels) { - Serial.print(i & 0xf, HEX); - ++i; - } - Serial.println(); - //delay(1000); -} -// -// Loop -// + // printf_begin(); + // radio.printPrettyDetails(); + // delay(1000); -const int num_reps = 100; -bool constCarrierMode = 0; + // Print out vertical header + printHeader(); +} void loop(void) { /****************************************/ - // Send g over Serial to begin CCW output - // Configure the channel and power level below + // Send a number over Serial to begin Constant Carrier Wave output + // Configure the power amplitude level below if (Serial.available()) { - char c = Serial.read(); - if (c == 'g') { + int c = Serial.read(); + if (c >= 0) { + c = min(125, max(0, c)); // clamp channel to supported range constCarrierMode = 1; radio.stopListening(); delay(2); - Serial.println("Starting Carrier Out"); - radio.startConstCarrier(RF24_PA_LOW, 40); - } else if (c == 'e') { + Serial.print("\nStarting Carrier Wave Output on channel "); + Serial.println(c); + radio.startConstCarrier(RF24_PA_LOW, c); + } else { constCarrierMode = 0; radio.stopConstCarrier(); - Serial.println("Stopping Carrier Out"); + Serial.println("\nStopping Carrier Out"); + printHeader(); } } /****************************************/ @@ -133,26 +168,49 @@ void loop(void) { delayMicroseconds(128); radio.stopListening(); - // Did we get a carrier? - if (radio.testCarrier()) { + // Did we get a signal? + if (radio.testRPD()) { ++values[i]; - radio.flush_rx(); + radio.flush_rx(); // discard packets of noise } } } - // Print out channel measurements, clamped to a single hex digit - int i = 0; - while (i < num_channels) { + for (int i = 0; i < num_channels; ++i) { if (values[i]) Serial.print(min(0xf, values[i]), HEX); else Serial.print(F("-")); - - ++i; } Serial.println(); - } //If constCarrierMode == 0 -} + } // if constCarrierMode == 0 + else { + // show some output to prove that the program isn't bricked + Serial.print(F(".")); + delay(1000); // delay a second to keep output readable + } +} // end loop() + +void printHeader() { + // Print the hundreds digits + for (int i = 0; i < num_channels; ++i) + Serial.print(i / 100); + Serial.println(); + + // Print the tens digits + for (int i = 0; i < num_channels; ++i) + Serial.print((i % 100) / 10); + Serial.println(); + + // Print the singles digits + for (int i = 0; i < num_channels; ++i) + Serial.print(i % 10); + Serial.println(); + + // Print the header's divider + for (int i = 0; i < num_channels; ++i) + Serial.print(F("~")); + Serial.println(); +} \ No newline at end of file diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index 693b7de2c..1ea240085 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -23,8 +23,27 @@ * See http://arduino.cc/forum/index.php/topic,54795.0.html */ -#include -#include +/* + * How to read the output: + * - The header is a list of supported channels in decimal written vertically. + * - Each column corresponding to the vertical header is a hexadecimal count of + * detected signals (max is 15 or 'f'). + * + * The following example + * 000 + * 111 + * 789 + * ~~~ <- just a divider between the channel's vertical labels and signal counts + * 1-2 + * can be interpreted as + * - 1 signal detected on channel 17 + * - 0 signals (denoted as '-') detected on channel 18 + * - 2 signals detected on channel 19 + * + * Each line of signal counts represent 100 passes of the supported spectrum. + */ +#include // string, getline() +#include // cout, endl, flush, cin #include using namespace std; @@ -48,28 +67,72 @@ RF24 radio(CE_PIN, CSN_PIN); // See https://www.kernel.org/doc/Documentation/spi/spidev for more information on SPIDEV // Channel info -const uint8_t num_channels = 126; -uint8_t values[num_channels]; -const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; +const uint8_t num_channels = 126; // 0-125 are supported +uint8_t values[num_channels]; // the array to store summary of signal counts per channel -const int num_reps = 100; -int reset_array = 0; +// To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). +// These addresses are designed to confuse the radio into thinking +// that the RF signal's preamble is part of the packet/payload. +const uint8_t noiseAddress[][2] = {{0, 0x55}, {0, 0xAA}}; -int main(int argc, char** argv) +const int num_reps = 100; // number of passes for each scan of the entire spectrum + +void printHeader() { - // Print preamble + // print the hundreds digits + for (int i = 0; i < num_channels; ++i) + cout << (i / 100); + cout << endl; + + // print the tens digits + for (int i = 0; i < num_channels; ++i) + cout << ((i % 100) / 10); + cout << endl; + + // print the singles digits + for (int i = 0; i < num_channels; ++i) + cout << (i % 10); + cout << endl; + + // print the header's divider + for (int i = 0; i < num_channels; ++i) + cout << '~'; + cout << endl; +} +int main(int argc, char** argv) +{ // print example's name - printf("%s", argv[0]); + cout << argv[0] << endl; + + // Setup the radio + if (!radio.begin()) { + cout << "Radio hardware not responding!" << endl; + return 1; + } - // - // Setup and configure rf radio - // - radio.begin(); + // set the data rate + cout << "Select your Data Rate. "; + cout << "Enter '1' for 1Mbps, '2' for 2Mbps, '3' for 250kbps. Defaults to 1Mbps." << endl; + string dataRate = 0; + getline(cin, dataRate); + if (dataRate.length() >= 1 && static_cast(dataRate[0]) == '2') { + cout << "Using 2 Mbps." << endl; + radio.setDataRate(RF24_2MBPS); + } + else if (dataRate.length() >= 1 && static_cast(dataRate[0]) == '3') { + cout << "Using 250 kbps." << endl; + radio.setDataRate(RF24_250KBPS); + } + else { + cout << "Using 1 Mbps." << endl; + radio.setDataRate(RF24_1MBPS); + } - radio.setAutoAck(false); - radio.disableCRC(); - radio.setAddressWidth(2); + // configure the radio + radio.setAutoAck(false); // Don't acknowledge arbitrary signals + radio.disableCRC(); // Accept any signal we find + radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) radio.openReadingPipe(0, noiseAddress[0]); radio.openReadingPipe(1, noiseAddress[1]); @@ -78,23 +141,10 @@ int main(int argc, char** argv) radio.stopListening(); radio.flush_rx(); - radio.printDetails(); - - // Print out header, high then low digit - int i = 0; - - while (i < num_channels) { - printf("%x", i >> 4); - ++i; - } - printf("\n"); + radio.printPrettyDetails(); - i = 0; - while (i < num_channels) { - printf("%x", i & 0xf); - ++i; - } - printf("\n"); + // print the vertical header + printHeader(); // forever loop while (1) { @@ -105,36 +155,36 @@ int main(int argc, char** argv) int rep_counter = num_reps; while (rep_counter--) { - int i = num_channels; - while (i--) { + for (int i = 0; i < num_channels; ++i) { // Select this channel radio.setChannel(i); // Listen for a little radio.startListening(); - delayMicroseconds(128); + delayMicroseconds(130); + // for some reason, this flag is more accurate on Linux when still in RX mode. + bool foundSignal = radio.testRPD(); radio.stopListening(); - // Did we get a carrier? - if (radio.testCarrier()) { + // Did we get a signal? + if (foundSignal || radio.testRPD()) { ++values[i]; - radio.flush_rx(); + radio.flush_rx(); // discard packets of noise } - } - } - - // Print out channel measurements, clamped to a single hex digit - i = 0; - while (i < num_channels) { - if (values[i]) - printf("%x", min(0xf, (values[i] & 0xf))); - else - printf("-"); - ++i; + // output the summary/snapshot for this channel + if (values[i]) { + // Print out channel measurements, clamped to a single hex digit + cout << hex << min(0xF, static_cast(values[i])) << flush; + } + else { + cout << '-' << flush; + } + } + cout << '\r' << flush; } - printf("\n"); + cout << endl; } return 0; From 49a1a57ae4cbc7508f77c5f61ba089e375501823 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Tue, 12 Jul 2022 13:03:01 -0700 Subject: [PATCH 04/66] trim trailing whitespace and fix a string's init --- examples/scanner/scanner.ino | 6 +++--- examples_linux/scanner.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index b08b6b026..4e6746033 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -27,18 +27,18 @@ * - The header is a list of supported channels in decimal written vertically. * - Each column corresponding to the vertical header is a hexadecimal count of * detected signals (max is 15 or 'f'). - * + * * The following example * 000 * 111 * 789 * ~~~ <- just a divider between the channel's vertical labels and signal counts * 1-2 - * can be interpreted as + * can be interpreted as * - 1 signal detected on channel 17 * - 0 signals (denoted as '-') detected on channel 18 * - 2 signals detected on channel 19 - * + * * Each line of signal counts represent 100 passes of the supported spectrum. */ diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index 1ea240085..656e10799 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -28,18 +28,18 @@ * - The header is a list of supported channels in decimal written vertically. * - Each column corresponding to the vertical header is a hexadecimal count of * detected signals (max is 15 or 'f'). - * + * * The following example * 000 * 111 * 789 * ~~~ <- just a divider between the channel's vertical labels and signal counts * 1-2 - * can be interpreted as + * can be interpreted as * - 1 signal detected on channel 17 * - 0 signals (denoted as '-') detected on channel 18 * - 2 signals detected on channel 19 - * + * * Each line of signal counts represent 100 passes of the supported spectrum. */ #include // string, getline() @@ -114,7 +114,7 @@ int main(int argc, char** argv) // set the data rate cout << "Select your Data Rate. "; cout << "Enter '1' for 1Mbps, '2' for 2Mbps, '3' for 250kbps. Defaults to 1Mbps." << endl; - string dataRate = 0; + string dataRate = ""; getline(cin, dataRate); if (dataRate.length() >= 1 && static_cast(dataRate[0]) == '2') { cout << "Using 2 Mbps." << endl; From eb6258cf74acbb46328934c78f5778f8953491c4 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Tue, 12 Jul 2022 22:41:41 -0700 Subject: [PATCH 05/66] minor updates to C++ scanners --- examples/scanner/scanner.ino | 3 +- examples_linux/scanner.cpp | 61 ++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index 4e6746033..fa9efdc83 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -97,7 +97,8 @@ void setup(void) { // set the data rate Serial.print(F("Select your Data Rate. ")); - Serial.println(F("Enter '1' for 1Mbps, 2 for 2Mbps, 3 for 250kbps. Defaults to 1Mbps.")); + Serial.print(F("Enter '1' for 1 Mbps, '2' for 2 Mbps, '3' for 250 kbps. ")); + Serial.println(F("Defaults to 1Mbps.")); while (!Serial.available()) { // wait for user input } diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index 656e10799..60dc8419a 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -73,32 +73,11 @@ uint8_t values[num_channels]; // the array to store summary of signal counts // To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). // These addresses are designed to confuse the radio into thinking // that the RF signal's preamble is part of the packet/payload. -const uint8_t noiseAddress[][2] = {{0, 0x55}, {0, 0xAA}}; +const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; const int num_reps = 100; // number of passes for each scan of the entire spectrum -void printHeader() -{ - // print the hundreds digits - for (int i = 0; i < num_channels; ++i) - cout << (i / 100); - cout << endl; - - // print the tens digits - for (int i = 0; i < num_channels; ++i) - cout << ((i % 100) / 10); - cout << endl; - - // print the singles digits - for (int i = 0; i < num_channels; ++i) - cout << (i % 10); - cout << endl; - - // print the header's divider - for (int i = 0; i < num_channels; ++i) - cout << '~'; - cout << endl; -} +void printHeader(); // prototype function for printing the channels' header int main(int argc, char** argv) { @@ -112,8 +91,10 @@ int main(int argc, char** argv) } // set the data rate - cout << "Select your Data Rate. "; - cout << "Enter '1' for 1Mbps, '2' for 2Mbps, '3' for 250kbps. Defaults to 1Mbps." << endl; + cout << "Select your Data Rate. " + << "Enter '1' for 1 Mbps, '2' for 2 Mbps, '3' for 250 kbps. " + << "Defaults to 1Mbps." + << endl; string dataRate = ""; getline(cin, dataRate); if (dataRate.length() >= 1 && static_cast(dataRate[0]) == '2') { @@ -140,9 +121,12 @@ int main(int argc, char** argv) radio.startListening(); radio.stopListening(); radio.flush_rx(); + // radio.printPrettyDetails(); - radio.printPrettyDetails(); - + // print a line that should not be wrapped + cout << "\n!!! This example requires a width of at least 126 characters. " + << "If this text uses multiple lines, then the output will look bad.\n" + << endl; // print the vertical header printHeader(); @@ -190,4 +174,27 @@ int main(int argc, char** argv) return 0; } +void printHeader() +{ + // print the hundreds digits + for (int i = 0; i < num_channels; ++i) + cout << (i / 100); + cout << endl; + + // print the tens digits + for (int i = 0; i < num_channels; ++i) + cout << ((i % 100) / 10); + cout << endl; + + // print the singles digits + for (int i = 0; i < num_channels; ++i) + cout << (i % 10); + cout << endl; + + // print the header's divider + for (int i = 0; i < num_channels; ++i) + cout << '~'; + cout << endl; +} + // vim:ai:cin:sts=2 sw=2 ft=cpp From 4c14c477ddbfd66171a7aaf44cfb5d78ec9f7053 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 11:59:49 -0700 Subject: [PATCH 06/66] add new curses python scanner --- examples_linux/curses_scanner.py | 158 +++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 examples_linux/curses_scanner.py diff --git a/examples_linux/curses_scanner.py b/examples_linux/curses_scanner.py new file mode 100644 index 000000000..5b21ba7ad --- /dev/null +++ b/examples_linux/curses_scanner.py @@ -0,0 +1,158 @@ +"""A scanner example written in python using the std lib's ncurses wrapper""" +import curses +import time +from typing import List, Tuple, Any, Optional + +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) +radio.disableCRC() +radio.setAddressWidth(2) +radio.openReadingPipe(0, b"\x55\x55") +radio.openReadingPipe(1, b"\xAA\xAA") +radio.startListening() +radio.stopListening() +radio.flush_rx() + +OFFERED_DATA_RATES = ["1 Mbps", "2 Mbps", "250 kbps"] +AVAILABLE_RATES = [RF24_1MBPS, RF24_2MBPS, RF24_250KBPS] +TOTAL_CHANNELS = 126 +CACHE_MAX = 5 # the depth of history to calculate peaks +history = [[False] * CACHE_MAX] * TOTAL_CHANNELS # FIFO for tracking peak decays +totals = [0] * TOTAL_CHANNELS # for the total signal counts + + +class ProgressBar: # pylint: disable=too-few-public-methods + """This represents a progress bar using a curses window object.""" + + def __init__( # pylint: disable=too-many-arguments,invalid-name + self, + x: int, + y: int, + cols: int, + std_scr: curses.window, + label: str, + color: curses.color_pair, + ): + self.x, self.y, self.width, self.win, self.color = (x, y, cols, std_scr, color) + string = label # always labeled in MHz (4 digits) + # -9 for padding, label, & signal count + string += "─" * (self.width - 8) + string += " - " # the initial signal count + self.win.addstr(self.y, self.x, string, self.color) + + def update(self, completed: int, signal_count: int): + filled = min(CACHE_MAX, completed) / CACHE_MAX + bar = "═" * int((self.width - 8) * filled) + empty = "─" * (self.width - 8 - len(bar)) + count = "-" + if signal_count: + count = "%X" % min(0xF, signal_count) + self.win.addstr(self.y, self.x + 5, bar, curses.color_pair(5)) + self.win.addstr(self.y, self.x + 5 + len(bar), f"{empty} {count} ", self.color) + + +def init_interface(window) -> List[ProgressBar]: + """Creates a table of progress bars (1 for each channel).""" + progress_bars: List[ProgressBar] = [None] * TOTAL_CHANNELS + bar_w = int(curses.COLS / 6) + for i in range(21): # 21 rows + for j in range(i, i + (21 * 6), 21): # 6 columns + color = curses.color_pair(7) if int(j / 21) % 2 else curses.color_pair(3) + progress_bars[j] = ProgressBar( + x=bar_w * int(j / 21), + y=i + 1, + cols=bar_w, + std_scr=window, + label=f"{2400 + (j)} ", + color=color, + ) + return progress_bars + + +def init_curses(): + """init the curses interface""" + std_scr = curses.initscr() + # stdscr.keypad(True) + curses.noecho() + curses.cbreak() + curses.start_color() + curses.use_default_colors() + curses.init_pair(3, curses.COLOR_YELLOW, -1) + curses.init_pair(5, curses.COLOR_MAGENTA, -1) + curses.init_pair(7, curses.COLOR_WHITE, -1) + return std_scr + + +def deinit_curses(std_scr: curses.window, output: Optional[curses.window]): + """de-init the curses interface""" + # std_scr.keypad(False) + curses.nocbreak() + curses.echo() + cache_out = [] + if output is not None: + for line in range(1, 21): + cache_out.append(output.instr(line, 0).decode()) + curses.endwin() + print("\n".join(cache_out)) + + +def get_user_input() -> Tuple[int, int]: + """Get input parameters for the scan from the user.""" + for i, d_rate in enumerate(OFFERED_DATA_RATES): + print(f"{i + 1}. {d_rate}") + d_rate = int(input("Select your data rate [1, 2, 3] (defaults to 1 Mbps) ") or "1") + duration = input("how long (in seconds) to perform scan? ") + while duration is None or not duration.isdigit(): + print("Please enter a number.") + duration = input("how long (in seconds) to perform scan? ") + return (min(1, max(3, d_rate)) - 1, abs(int(duration))) + + +def scan_channel(channel: int) -> 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() + result = result or radio.testRPD() + if result: + radio.flush_rx() + return result + + +def main(): + data_rate, duration = get_user_input() + radio.setDataRate(AVAILABLE_RATES[data_rate]) + scanner_output_window = None + try: + std_scr = init_curses() + timer_prompt = "Scanning for {} seconds at " + OFFERED_DATA_RATES[data_rate] + scanner_output_window = std_scr.subpad(26, curses.COLS, 0, 0) + table = init_interface(scanner_output_window) + channel, val = (0, False) + end = time.monotonic() + duration + while time.monotonic() < end: + std_scr.addstr(0, 0, timer_prompt.format(int(end - time.monotonic()))) + val = scan_channel(channel) + totals[channel] += val + history[channel] = history[channel][1:] + [val] + table[channel].update(history[channel].count(True), totals[channel]) + scanner_output_window.refresh() + channel = 0 if channel + 1 == TOTAL_CHANNELS else channel + 1 + finally: + radio.powerDown() + deinit_curses(std_scr, scanner_output_window) + + +if __name__ == "__main__": + main() +else: + print("Enter 'main()' to run the program.") From 98f6edf13c54ae29b0f137becc902f41ff8324f3 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 12:09:48 -0700 Subject: [PATCH 07/66] replace scanner.py w/ curses implementation --- examples_linux/curses_scanner.py | 158 ------------------------ examples_linux/scanner.py | 202 ++++++++++++++++++------------- 2 files changed, 121 insertions(+), 239 deletions(-) delete mode 100644 examples_linux/curses_scanner.py diff --git a/examples_linux/curses_scanner.py b/examples_linux/curses_scanner.py deleted file mode 100644 index 5b21ba7ad..000000000 --- a/examples_linux/curses_scanner.py +++ /dev/null @@ -1,158 +0,0 @@ -"""A scanner example written in python using the std lib's ncurses wrapper""" -import curses -import time -from typing import List, Tuple, Any, Optional - -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) -radio.disableCRC() -radio.setAddressWidth(2) -radio.openReadingPipe(0, b"\x55\x55") -radio.openReadingPipe(1, b"\xAA\xAA") -radio.startListening() -radio.stopListening() -radio.flush_rx() - -OFFERED_DATA_RATES = ["1 Mbps", "2 Mbps", "250 kbps"] -AVAILABLE_RATES = [RF24_1MBPS, RF24_2MBPS, RF24_250KBPS] -TOTAL_CHANNELS = 126 -CACHE_MAX = 5 # the depth of history to calculate peaks -history = [[False] * CACHE_MAX] * TOTAL_CHANNELS # FIFO for tracking peak decays -totals = [0] * TOTAL_CHANNELS # for the total signal counts - - -class ProgressBar: # pylint: disable=too-few-public-methods - """This represents a progress bar using a curses window object.""" - - def __init__( # pylint: disable=too-many-arguments,invalid-name - self, - x: int, - y: int, - cols: int, - std_scr: curses.window, - label: str, - color: curses.color_pair, - ): - self.x, self.y, self.width, self.win, self.color = (x, y, cols, std_scr, color) - string = label # always labeled in MHz (4 digits) - # -9 for padding, label, & signal count - string += "─" * (self.width - 8) - string += " - " # the initial signal count - self.win.addstr(self.y, self.x, string, self.color) - - def update(self, completed: int, signal_count: int): - filled = min(CACHE_MAX, completed) / CACHE_MAX - bar = "═" * int((self.width - 8) * filled) - empty = "─" * (self.width - 8 - len(bar)) - count = "-" - if signal_count: - count = "%X" % min(0xF, signal_count) - self.win.addstr(self.y, self.x + 5, bar, curses.color_pair(5)) - self.win.addstr(self.y, self.x + 5 + len(bar), f"{empty} {count} ", self.color) - - -def init_interface(window) -> List[ProgressBar]: - """Creates a table of progress bars (1 for each channel).""" - progress_bars: List[ProgressBar] = [None] * TOTAL_CHANNELS - bar_w = int(curses.COLS / 6) - for i in range(21): # 21 rows - for j in range(i, i + (21 * 6), 21): # 6 columns - color = curses.color_pair(7) if int(j / 21) % 2 else curses.color_pair(3) - progress_bars[j] = ProgressBar( - x=bar_w * int(j / 21), - y=i + 1, - cols=bar_w, - std_scr=window, - label=f"{2400 + (j)} ", - color=color, - ) - return progress_bars - - -def init_curses(): - """init the curses interface""" - std_scr = curses.initscr() - # stdscr.keypad(True) - curses.noecho() - curses.cbreak() - curses.start_color() - curses.use_default_colors() - curses.init_pair(3, curses.COLOR_YELLOW, -1) - curses.init_pair(5, curses.COLOR_MAGENTA, -1) - curses.init_pair(7, curses.COLOR_WHITE, -1) - return std_scr - - -def deinit_curses(std_scr: curses.window, output: Optional[curses.window]): - """de-init the curses interface""" - # std_scr.keypad(False) - curses.nocbreak() - curses.echo() - cache_out = [] - if output is not None: - for line in range(1, 21): - cache_out.append(output.instr(line, 0).decode()) - curses.endwin() - print("\n".join(cache_out)) - - -def get_user_input() -> Tuple[int, int]: - """Get input parameters for the scan from the user.""" - for i, d_rate in enumerate(OFFERED_DATA_RATES): - print(f"{i + 1}. {d_rate}") - d_rate = int(input("Select your data rate [1, 2, 3] (defaults to 1 Mbps) ") or "1") - duration = input("how long (in seconds) to perform scan? ") - while duration is None or not duration.isdigit(): - print("Please enter a number.") - duration = input("how long (in seconds) to perform scan? ") - return (min(1, max(3, d_rate)) - 1, abs(int(duration))) - - -def scan_channel(channel: int) -> 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() - result = result or radio.testRPD() - if result: - radio.flush_rx() - return result - - -def main(): - data_rate, duration = get_user_input() - radio.setDataRate(AVAILABLE_RATES[data_rate]) - scanner_output_window = None - try: - std_scr = init_curses() - timer_prompt = "Scanning for {} seconds at " + OFFERED_DATA_RATES[data_rate] - scanner_output_window = std_scr.subpad(26, curses.COLS, 0, 0) - table = init_interface(scanner_output_window) - channel, val = (0, False) - end = time.monotonic() + duration - while time.monotonic() < end: - std_scr.addstr(0, 0, timer_prompt.format(int(end - time.monotonic()))) - val = scan_channel(channel) - totals[channel] += val - history[channel] = history[channel][1:] + [val] - table[channel].update(history[channel].count(True), totals[channel]) - scanner_output_window.refresh() - channel = 0 if channel + 1 == TOTAL_CHANNELS else channel + 1 - finally: - radio.powerDown() - deinit_curses(std_scr, scanner_output_window) - - -if __name__ == "__main__": - main() -else: - print("Enter 'main()' to run the program.") diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 503f9a064..5b21ba7ad 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -1,20 +1,8 @@ -"""A scanner example that uses the python `rich` module to provide -a user-friendly output.""" +"""A scanner example written in python using the std lib's ncurses wrapper""" +import curses import time -from typing import List - -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 - from rich.style import Style -except ImportError as exc: - raise ImportError( - "This example requires the python `rich` module installed." - "\nInstall it using 'python3 -m pip install rich'" - ) from exc +from typing import List, Tuple, Any, Optional + from RF24 import RF24, RF24_1MBPS, RF24_2MBPS, RF24_250KBPS CSN_PIN = 0 # connected to GPIO8 @@ -32,40 +20,99 @@ radio.stopListening() radio.flush_rx() -offered_rates = ["1 Mbps", "2 Mbps", "250 kbps"] -available_rates = [RF24_1MBPS, RF24_2MBPS, RF24_250KBPS] -console = Console() -for i, rate in enumerate(offered_rates): - console.print(f"{i + 1}. {rate}") -DATA_RATE = ( - int(Prompt.ask("Choose the data rate", choices=["1", "2", "3"], default="1")) - 1 -) -radio.setDataRate(available_rates[DATA_RATE]) - -DURATION = IntPrompt.ask("Enter the scan duration (in whole seconds)") -SELECTED_RATE = offered_rates[DATA_RATE] - +OFFERED_DATA_RATES = ["1 Mbps", "2 Mbps", "250 kbps"] +AVAILABLE_RATES = [RF24_1MBPS, RF24_2MBPS, RF24_250KBPS] +TOTAL_CHANNELS = 126 CACHE_MAX = 5 # the depth of history to calculate peaks -history = [[False] * CACHE_MAX] * 126 # for tracking peak decay on each channel -signals = [False] * 126 # for tracking the signal count on each channel -totals = [0] * 126 # for the total signal count on each channel - -# create table of progress bars (labeled by frequency channel in MHz) -table = Table.grid(padding=(0, 1)) -progress_bars: List[Progress] = [None] * 126 -for i in range(21): # 21 rows - row = [] - for j in range(i, i + (21 * 6), 21): # 6 columns - COLOR = "white" if int(j / 21) % 2 else "yellow" - progress_bars[j] = Progress( - TextColumn("{task.description}", style=Style(color=COLOR)), - BarColumn(style=Style(color=COLOR)), - TextColumn("{task.fields[signals]}", style=Style(color=COLOR)), - ) - # add only 1 task for each progress bar - progress_bars[j].add_task(f"{2400 + (j)}", total=CACHE_MAX, signals="-") - row.append(progress_bars[j]) - table.add_row(*row) +history = [[False] * CACHE_MAX] * TOTAL_CHANNELS # FIFO for tracking peak decays +totals = [0] * TOTAL_CHANNELS # for the total signal counts + + +class ProgressBar: # pylint: disable=too-few-public-methods + """This represents a progress bar using a curses window object.""" + + def __init__( # pylint: disable=too-many-arguments,invalid-name + self, + x: int, + y: int, + cols: int, + std_scr: curses.window, + label: str, + color: curses.color_pair, + ): + self.x, self.y, self.width, self.win, self.color = (x, y, cols, std_scr, color) + string = label # always labeled in MHz (4 digits) + # -9 for padding, label, & signal count + string += "─" * (self.width - 8) + string += " - " # the initial signal count + self.win.addstr(self.y, self.x, string, self.color) + + def update(self, completed: int, signal_count: int): + filled = min(CACHE_MAX, completed) / CACHE_MAX + bar = "═" * int((self.width - 8) * filled) + empty = "─" * (self.width - 8 - len(bar)) + count = "-" + if signal_count: + count = "%X" % min(0xF, signal_count) + self.win.addstr(self.y, self.x + 5, bar, curses.color_pair(5)) + self.win.addstr(self.y, self.x + 5 + len(bar), f"{empty} {count} ", self.color) + + +def init_interface(window) -> List[ProgressBar]: + """Creates a table of progress bars (1 for each channel).""" + progress_bars: List[ProgressBar] = [None] * TOTAL_CHANNELS + bar_w = int(curses.COLS / 6) + for i in range(21): # 21 rows + for j in range(i, i + (21 * 6), 21): # 6 columns + color = curses.color_pair(7) if int(j / 21) % 2 else curses.color_pair(3) + progress_bars[j] = ProgressBar( + x=bar_w * int(j / 21), + y=i + 1, + cols=bar_w, + std_scr=window, + label=f"{2400 + (j)} ", + color=color, + ) + return progress_bars + + +def init_curses(): + """init the curses interface""" + std_scr = curses.initscr() + # stdscr.keypad(True) + curses.noecho() + curses.cbreak() + curses.start_color() + curses.use_default_colors() + curses.init_pair(3, curses.COLOR_YELLOW, -1) + curses.init_pair(5, curses.COLOR_MAGENTA, -1) + curses.init_pair(7, curses.COLOR_WHITE, -1) + return std_scr + + +def deinit_curses(std_scr: curses.window, output: Optional[curses.window]): + """de-init the curses interface""" + # std_scr.keypad(False) + curses.nocbreak() + curses.echo() + cache_out = [] + if output is not None: + for line in range(1, 21): + cache_out.append(output.instr(line, 0).decode()) + curses.endwin() + print("\n".join(cache_out)) + + +def get_user_input() -> Tuple[int, int]: + """Get input parameters for the scan from the user.""" + for i, d_rate in enumerate(OFFERED_DATA_RATES): + print(f"{i + 1}. {d_rate}") + d_rate = int(input("Select your data rate [1, 2, 3] (defaults to 1 Mbps) ") or "1") + duration = input("how long (in seconds) to perform scan? ") + while duration is None or not duration.isdigit(): + print("Please enter a number.") + duration = input("how long (in seconds) to perform scan? ") + return (min(1, max(3, d_rate)) - 1, abs(int(duration))) def scan_channel(channel: int) -> bool: @@ -75,44 +122,37 @@ def scan_channel(channel: int) -> bool: time.sleep(0.00013) result = radio.testRPD() radio.stopListening() + result = result or radio.testRPD() if result: radio.flush_rx() return result -def scan(duration: int = DURATION): - """Perform scan.""" - timeout = time.monotonic() + duration - console.print( - f"Scanning all channels using {SELECTED_RATE} for", - f"{duration} seconds. Channel labels are in MHz.", - ) - with Live(table, refresh_per_second=1000): - try: - while time.monotonic() < timeout: - for chl, p_bar in enumerate(progress_bars): - # save the latest in history (FIFO ordering) - history[chl] = history[chl][1:] + [signals[chl]] - - # refresh the latest - signals[chl] = scan_channel(chl) - - # update total signal count for the channel - totals[chl] += int(signals[chl]) - - p_bar.update( - p_bar.task_ids[0], - completed=history[chl].count(True), - signals="-" if not totals[chl] else totals[chl], - ) - except KeyboardInterrupt: - console.print(" Keyboard interrupt detected. Powering down radio.") - radio.powerDown() +def main(): + data_rate, duration = get_user_input() + radio.setDataRate(AVAILABLE_RATES[data_rate]) + scanner_output_window = None + try: + std_scr = init_curses() + timer_prompt = "Scanning for {} seconds at " + OFFERED_DATA_RATES[data_rate] + scanner_output_window = std_scr.subpad(26, curses.COLS, 0, 0) + table = init_interface(scanner_output_window) + channel, val = (0, False) + end = time.monotonic() + duration + while time.monotonic() < end: + std_scr.addstr(0, 0, timer_prompt.format(int(end - time.monotonic()))) + val = scan_channel(channel) + totals[channel] += val + history[channel] = history[channel][1:] + [val] + table[channel].update(history[channel].count(True), totals[channel]) + scanner_output_window.refresh() + channel = 0 if channel + 1 == TOTAL_CHANNELS else channel + 1 + finally: + radio.powerDown() + deinit_curses(std_scr, scanner_output_window) if __name__ == "__main__": - scan() - radio.powerDown() + main() else: - console.print("Enter `scan()` to run a scan.") - console.print("Change data rate using `radio.setDataRate(RF24_**BPS)`") + print("Enter 'main()' to run the program.") From d9bab34b2ed3f2ff23f087315d834c6c30585787 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 12:21:09 -0700 Subject: [PATCH 08/66] add more informative output --- examples_linux/scanner.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 5b21ba7ad..624fd39dd 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -67,7 +67,7 @@ def init_interface(window) -> List[ProgressBar]: color = curses.color_pair(7) if int(j / 21) % 2 else curses.color_pair(3) progress_bars[j] = ProgressBar( x=bar_w * int(j / 21), - y=i + 1, + y=i + 3, cols=bar_w, std_scr=window, label=f"{2400 + (j)} ", @@ -97,7 +97,7 @@ def deinit_curses(std_scr: curses.window, output: Optional[curses.window]): curses.echo() cache_out = [] if output is not None: - for line in range(1, 21): + for line in range(3, 24): cache_out.append(output.instr(line, 0).decode()) curses.endwin() print("\n".join(cache_out)) @@ -112,6 +112,10 @@ def get_user_input() -> Tuple[int, int]: while duration is None or not duration.isdigit(): print("Please enter a number.") duration = input("how long (in seconds) to perform scan? ") + print( + "Channels are labeled in MHz. Signal counts are", + "clamped to a single hexadecimal digit.", + ) return (min(1, max(3, d_rate)) - 1, abs(int(duration))) @@ -135,12 +139,14 @@ def main(): try: std_scr = init_curses() timer_prompt = "Scanning for {} seconds at " + OFFERED_DATA_RATES[data_rate] + std_scr.addstr(0, 0, "Channels are labeled in MHz.") + std_scr.addstr(1, 0, "Signal counts are clamped to a single hexadecimal digit.") scanner_output_window = std_scr.subpad(26, curses.COLS, 0, 0) table = init_interface(scanner_output_window) channel, val = (0, False) end = time.monotonic() + duration while time.monotonic() < end: - std_scr.addstr(0, 0, timer_prompt.format(int(end - time.monotonic()))) + std_scr.addstr(2, 0, timer_prompt.format(int(end - time.monotonic()))) val = scan_channel(channel) totals[channel] += val history[channel] = history[channel][1:] + [val] From 9e6b879aaa4636d10f734b313cfb5cbae22489d8 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 12:35:37 -0700 Subject: [PATCH 09/66] fix scanner data rate option input --- examples_linux/scanner.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 624fd39dd..671ef4969 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -1,7 +1,7 @@ """A scanner example written in python using the std lib's ncurses wrapper""" import curses import time -from typing import List, Tuple, Any, Optional +from typing import List, Tuple, Optional from RF24 import RF24, RF24_1MBPS, RF24_2MBPS, RF24_250KBPS @@ -43,11 +43,12 @@ def __init__( # pylint: disable=too-many-arguments,invalid-name self.x, self.y, self.width, self.win, self.color = (x, y, cols, std_scr, color) string = label # always labeled in MHz (4 digits) # -9 for padding, label, & signal count - string += "─" * (self.width - 8) + string += "─" * (self.width - 8) # the empty bar string += " - " # the initial signal count self.win.addstr(self.y, self.x, string, self.color) def update(self, completed: int, signal_count: int): + """Update the progress bar.""" filled = min(CACHE_MAX, completed) / CACHE_MAX bar = "═" * int((self.width - 8) * filled) empty = "─" * (self.width - 8 - len(bar)) @@ -79,7 +80,6 @@ def init_interface(window) -> List[ProgressBar]: def init_curses(): """init the curses interface""" std_scr = curses.initscr() - # stdscr.keypad(True) curses.noecho() curses.cbreak() curses.start_color() @@ -90,9 +90,8 @@ def init_curses(): return std_scr -def deinit_curses(std_scr: curses.window, output: Optional[curses.window]): +def deinit_curses(output: Optional[curses.window]): """de-init the curses interface""" - # std_scr.keypad(False) curses.nocbreak() curses.echo() cache_out = [] @@ -107,7 +106,7 @@ def get_user_input() -> Tuple[int, int]: """Get input parameters for the scan from the user.""" for i, d_rate in enumerate(OFFERED_DATA_RATES): print(f"{i + 1}. {d_rate}") - d_rate = int(input("Select your data rate [1, 2, 3] (defaults to 1 Mbps) ") or "1") + d_rate = input("Select your data rate [1, 2, 3] (defaults to 1 Mbps) ") duration = input("how long (in seconds) to perform scan? ") while duration is None or not duration.isdigit(): print("Please enter a number.") @@ -116,7 +115,11 @@ def get_user_input() -> Tuple[int, int]: "Channels are labeled in MHz. Signal counts are", "clamped to a single hexadecimal digit.", ) - return (min(1, max(3, d_rate)) - 1, abs(int(duration))) + + return ( + min(1, max(3, int(1 if d_rate is None else d_rate))) - 1, + abs(int(duration)), + ) def scan_channel(channel: int) -> bool: @@ -155,7 +158,7 @@ def main(): channel = 0 if channel + 1 == TOTAL_CHANNELS else channel + 1 finally: radio.powerDown() - deinit_curses(std_scr, scanner_output_window) + deinit_curses(scanner_output_window) if __name__ == "__main__": From 7df0d174a7482ff1327bc66fd1446363f2d1229b Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 12:52:53 -0700 Subject: [PATCH 10/66] fix logic about normalizing user input --- examples_linux/scanner.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 671ef4969..b5516d392 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -3,22 +3,12 @@ import time from typing import List, Tuple, Optional -from RF24 import RF24, RF24_1MBPS, RF24_2MBPS, RF24_250KBPS +from pyrf24 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) -radio.disableCRC() -radio.setAddressWidth(2) -radio.openReadingPipe(0, b"\x55\x55") -radio.openReadingPipe(1, b"\xAA\xAA") -radio.startListening() -radio.stopListening() -radio.flush_rx() OFFERED_DATA_RATES = ["1 Mbps", "2 Mbps", "250 kbps"] AVAILABLE_RATES = [RF24_1MBPS, RF24_2MBPS, RF24_250KBPS] @@ -77,6 +67,20 @@ def init_interface(window) -> List[ProgressBar]: return progress_bars +def init_radio(): + """init the radio""" + if not radio.begin(): + raise RuntimeError("Radio hardware not responding!") + radio.setAutoAck(False) + radio.disableCRC() + radio.setAddressWidth(2) + radio.openReadingPipe(0, b"\x55\x55") + radio.openReadingPipe(1, b"\xAA\xAA") + radio.startListening() + radio.stopListening() + radio.flush_rx() + + def init_curses(): """init the curses interface""" std_scr = curses.initscr() @@ -107,17 +111,17 @@ def get_user_input() -> Tuple[int, int]: for i, d_rate in enumerate(OFFERED_DATA_RATES): print(f"{i + 1}. {d_rate}") d_rate = input("Select your data rate [1, 2, 3] (defaults to 1 Mbps) ") - duration = input("how long (in seconds) to perform scan? ") - while duration is None or not duration.isdigit(): - print("Please enter a number.") - duration = input("how long (in seconds) to perform scan? ") + duration = input("How long (in seconds) to perform scan? ") + while not duration.isdigit(): + print("Please enter a positive number.") + duration = input("How long (in seconds) to perform scan? ") print( "Channels are labeled in MHz. Signal counts are", "clamped to a single hexadecimal digit.", ) return ( - min(1, max(3, int(1 if d_rate is None else d_rate))) - 1, + max(1, min(3, 1 if not d_rate.isdigit() else int(d_rate))) - 1, abs(int(duration)), ) @@ -137,6 +141,7 @@ def scan_channel(channel: int) -> bool: def main(): data_rate, duration = get_user_input() + init_radio() radio.setDataRate(AVAILABLE_RATES[data_rate]) scanner_output_window = None try: From b005f9997b4d8da3ba95c8c2e7628e6e27b1bb99 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 13:07:18 -0700 Subject: [PATCH 11/66] remove typehints related to the curses wrapper --- examples_linux/scanner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index b5516d392..93677af82 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -1,7 +1,7 @@ """A scanner example written in python using the std lib's ncurses wrapper""" import curses import time -from typing import List, Tuple, Optional +from typing import List, Tuple, Any from pyrf24 import RF24, RF24_1MBPS, RF24_2MBPS, RF24_250KBPS @@ -26,9 +26,9 @@ def __init__( # pylint: disable=too-many-arguments,invalid-name x: int, y: int, cols: int, - std_scr: curses.window, + std_scr: Any, # type: curses.window, label: str, - color: curses.color_pair, + color: Any, # type: curses.color_pair, ): self.x, self.y, self.width, self.win, self.color = (x, y, cols, std_scr, color) string = label # always labeled in MHz (4 digits) @@ -94,7 +94,7 @@ def init_curses(): return std_scr -def deinit_curses(output: Optional[curses.window]): +def deinit_curses(output: Any): """de-init the curses interface""" curses.nocbreak() curses.echo() From 334a43b32bb516a659a6dea462a14c62c5605f63 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 13:21:33 -0700 Subject: [PATCH 12/66] show signal counts on scanner exit; not curses buf --- examples_linux/scanner.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 93677af82..d840e7d39 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -1,6 +1,7 @@ """A scanner example written in python using the std lib's ncurses wrapper""" import curses import time +import json from typing import List, Tuple, Any from pyrf24 import RF24, RF24_1MBPS, RF24_2MBPS, RF24_250KBPS @@ -26,9 +27,9 @@ def __init__( # pylint: disable=too-many-arguments,invalid-name x: int, y: int, cols: int, - std_scr: Any, # type: curses.window, + std_scr: Any, # type: curses.window, label: str, - color: Any, # type: curses.color_pair, + color: Any, # type: curses.color_pair, ): self.x, self.y, self.width, self.win, self.color = (x, y, cols, std_scr, color) string = label # always labeled in MHz (4 digits) @@ -94,16 +95,22 @@ def init_curses(): return std_scr -def deinit_curses(output: Any): +def deinit_curses(): """de-init the curses interface""" curses.nocbreak() curses.echo() cache_out = [] - if output is not None: - for line in range(3, 24): - cache_out.append(output.instr(line, 0).decode()) curses.endwin() - print("\n".join(cache_out)) + noisy_channels = {} + for channel, count in enumerate(totals): + if count: + noisy_channels[channel] = count + if noisy_channels: + print( + "Channels with detected signals:", + json.dumps(noisy_channels, indent=2), + sep="\n", + ) def get_user_input() -> Tuple[int, int]: @@ -163,7 +170,7 @@ def main(): channel = 0 if channel + 1 == TOTAL_CHANNELS else channel + 1 finally: radio.powerDown() - deinit_curses(scanner_output_window) + deinit_curses() if __name__ == "__main__": From f72dd3ee1c199249acbd7b7eca1c5ffe73e1b26a Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 13:33:13 -0700 Subject: [PATCH 13/66] don't serialize signal counts as json --- examples_linux/scanner.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index d840e7d39..964485298 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -1,7 +1,6 @@ """A scanner example written in python using the std lib's ncurses wrapper""" import curses import time -import json from typing import List, Tuple, Any from pyrf24 import RF24, RF24_1MBPS, RF24_2MBPS, RF24_250KBPS @@ -101,16 +100,10 @@ def deinit_curses(): curses.echo() cache_out = [] curses.endwin() - noisy_channels = {} for channel, count in enumerate(totals): if count: - noisy_channels[channel] = count - if noisy_channels: - print( - "Channels with detected signals:", - json.dumps(noisy_channels, indent=2), - sep="\n", - ) + print(f" {channel}: {count}") + print(f"{TOTAL_CHANNELS - totals.count(0)} channels detected signals.") def get_user_input() -> Tuple[int, int]: From 79101f4c0417b8acdf7f03a870becc7c36783cf1 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 13:34:44 -0700 Subject: [PATCH 14/66] remove misleading output prompt --- examples_linux/scanner.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 964485298..ac6f26ba2 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -115,11 +115,6 @@ def get_user_input() -> Tuple[int, int]: while not duration.isdigit(): print("Please enter a positive number.") duration = input("How long (in seconds) to perform scan? ") - print( - "Channels are labeled in MHz. Signal counts are", - "clamped to a single hexadecimal digit.", - ) - return ( max(1, min(3, 1 if not d_rate.isdigit() else int(d_rate))) - 1, abs(int(duration)), From debdf5dad0d7eeb292b64b634ba95deb17309b79 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 13:39:25 -0700 Subject: [PATCH 15/66] echo user input on scanner exit --- examples_linux/scanner.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index ac6f26ba2..467aa3d51 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -115,6 +115,11 @@ def get_user_input() -> Tuple[int, int]: while not duration.isdigit(): print("Please enter a positive number.") duration = input("How long (in seconds) to perform scan? ") + print( + "Channels are labeled in MHz. Signal counts are", + "clamped to a single hexadecimal digit.", + ) + return ( max(1, min(3, 1 if not d_rate.isdigit() else int(d_rate))) - 1, abs(int(duration)), @@ -136,6 +141,7 @@ def scan_channel(channel: int) -> bool: def main(): data_rate, duration = get_user_input() + print(f"Scanning for {duration} seconds at {OFFERED_DATA_RATES[data_rate]}") init_radio() radio.setDataRate(AVAILABLE_RATES[data_rate]) scanner_output_window = None From 16416faf0f9589c4fb1aa280519b7e64e8890f92 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 13:40:43 -0700 Subject: [PATCH 16/66] re-remove misleading output --- examples_linux/scanner.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 467aa3d51..91e1e5165 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -115,11 +115,6 @@ def get_user_input() -> Tuple[int, int]: while not duration.isdigit(): print("Please enter a positive number.") duration = input("How long (in seconds) to perform scan? ") - print( - "Channels are labeled in MHz. Signal counts are", - "clamped to a single hexadecimal digit.", - ) - return ( max(1, min(3, 1 if not d_rate.isdigit() else int(d_rate))) - 1, abs(int(duration)), From 08ea0a0178d3139e89842f8bb73c8adb845a0e12 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 14:55:26 -0700 Subject: [PATCH 17/66] only update channel widget if any signal found --- examples_linux/scanner.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 91e1e5165..8804018f4 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -152,10 +152,11 @@ def main(): while time.monotonic() < end: std_scr.addstr(2, 0, timer_prompt.format(int(end - time.monotonic()))) val = scan_channel(channel) - totals[channel] += val history[channel] = history[channel][1:] + [val] - table[channel].update(history[channel].count(True), totals[channel]) - scanner_output_window.refresh() + totals[channel] += val + if totals[channel]: + table[channel].update(history[channel].count(True), totals[channel]) + scanner_output_window.refresh() channel = 0 if channel + 1 == TOTAL_CHANNELS else channel + 1 finally: radio.powerDown() From 237306c225bf53803bcbfed33b40c42e0ec67b5b Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 22:52:47 -0700 Subject: [PATCH 18/66] count & output spectrum passes --- examples_linux/scanner.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 8804018f4..ab13f3636 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -16,7 +16,7 @@ CACHE_MAX = 5 # the depth of history to calculate peaks history = [[False] * CACHE_MAX] * TOTAL_CHANNELS # FIFO for tracking peak decays totals = [0] * TOTAL_CHANNELS # for the total signal counts - +spectrum_pass = [0] class ProgressBar: # pylint: disable=too-few-public-methods """This represents a progress bar using a curses window object.""" @@ -103,7 +103,10 @@ def deinit_curses(): for channel, count in enumerate(totals): if count: print(f" {channel}: {count}") - print(f"{TOTAL_CHANNELS - totals.count(0)} channels detected signals.") + print( + f"{TOTAL_CHANNELS - totals.count(0)} channels detected signals", + f"out of {spectrum_pass[0]} passes on the entire spectrum." + ) def get_user_input() -> Tuple[int, int]: @@ -157,7 +160,11 @@ def main(): if totals[channel]: table[channel].update(history[channel].count(True), totals[channel]) scanner_output_window.refresh() - channel = 0 if channel + 1 == TOTAL_CHANNELS else channel + 1 + if channel + 1 == TOTAL_CHANNELS: + channel = 0 + spectrum_pass[0] += 1 + else: + channel += 1 finally: radio.powerDown() deinit_curses() From f9e190a6216221cff73304daf891227b679a9022 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 13 Jul 2022 23:05:37 -0700 Subject: [PATCH 19/66] show an overall percentage --- examples_linux/scanner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index ab13f3636..bcb70af9f 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -102,7 +102,8 @@ def deinit_curses(): curses.endwin() for channel, count in enumerate(totals): if count: - print(f" {channel}: {count}") + percentage = round(count / spectrum_pass[0], 3) + print(f" {channel}: {count} / {spectrum_pass[0]} ({percentage} %)") print( f"{TOTAL_CHANNELS - totals.count(0)} channels detected signals", f"out of {spectrum_pass[0]} passes on the entire spectrum." From 10b879dafd709b32c98161237fa99f6674299545 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 14 Jul 2022 00:52:11 -0700 Subject: [PATCH 20/66] oops, use stand-alone wrapper (not pyrf24 pkg) --- examples_linux/scanner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index bcb70af9f..6c23a66b5 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -1,9 +1,10 @@ """A scanner example written in python using the std lib's ncurses wrapper""" +# pylint: disable=no-member import curses import time from typing import List, Tuple, Any -from pyrf24 import RF24, RF24_1MBPS, RF24_2MBPS, RF24_250KBPS +from RF24 import RF24, RF24_1MBPS, RF24_2MBPS, RF24_250KBPS CSN_PIN = 0 # connected to GPIO8 CE_PIN = 22 # connected to GPIO22 @@ -98,7 +99,6 @@ def deinit_curses(): """de-init the curses interface""" curses.nocbreak() curses.echo() - cache_out = [] curses.endwin() for channel, count in enumerate(totals): if count: From 24ac44e98a5a93c70edb574645b78006e4f65848 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 14 Jul 2022 10:23:17 -0700 Subject: [PATCH 21/66] update pico scanner example --- examples_pico/scanner.cpp | 173 ++++++++++++++++++++++++++------------ 1 file changed, 120 insertions(+), 53 deletions(-) diff --git a/examples_pico/scanner.cpp b/examples_pico/scanner.cpp index d67bd8b43..33f1e81da 100644 --- a/examples_pico/scanner.cpp +++ b/examples_pico/scanner.cpp @@ -22,6 +22,26 @@ * See http://arduino.cc/forum/index.php/topic,54795.0.html */ +/* + * How to read the output: + * - The header is a list of supported channels in decimal written vertically. + * - Each column corresponding to the vertical header is a hexadecimal count of + * detected signals (max is 15 or 'f'). + * + * The following example + * 000 + * 111 + * 789 + * ~~~ <- just a divider between the channel's vertical labels and signal counts + * 1-2 + * can be interpreted as + * - 1 signal detected on channel 17 + * - 0 signals (denoted as '-') detected on channel 18 + * - 2 signals detected on channel 19 + * + * Each line of signal counts represent 100 passes of the supported spectrum. + */ + #include "pico/stdlib.h" // printf(), sleep_ms(), getchar_timeout_us(), to_us_since_boot(), get_absolute_time() #include "pico/bootrom.h" // reset_usb_boot() #include // tud_cdc_connected() @@ -32,11 +52,19 @@ RF24 radio(CE_PIN, CSN_PIN); // Channel info -const uint8_t num_channels = 126; -uint8_t values[num_channels]; +const uint8_t num_channels = 126; // 0-125 are supported +uint8_t values[num_channels]; // the array to store summary of signal counts per channel + +// To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). +// These addresses are designed to confuse the radio into thinking +// that the RF signal's preamble is part of the packet/payload. +const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; -const int num_reps = 100; -int reset_array = 0; +const int num_reps = 100; // number of passes for each scan of the entire spectrum + +void printHeader(); +void scanChannel(uint8_t); +void initRadio(); int main() { @@ -47,37 +75,17 @@ int main() sleep_ms(10); } - // initialize the transceiver on the SPI bus - while (!radio.begin()) { - printf("radio hardware is not responding!!\n"); - } - // print example's name printf("RF24/examples_pico/scanner\n"); - radio.setAutoAck(false); - - // Get into standby mode - radio.startListening(); - radio.stopListening(); - - // radio.printDetails(); - - // Print out header, high then low digit - int i = 0; - - while (i < num_channels) { - printf("%x", i >> 4); - ++i; + // initialize the transceiver on the SPI bus + while (!radio.begin()) { + printf("radio hardware is not responding!!\n"); } - printf("\n"); + initRadio(); - i = 0; - while (i < num_channels) { - printf("%x", i & 0xf); - ++i; - } - printf("\n"); + // Print out header + printHeader(); // forever loop while (1) { @@ -88,36 +96,95 @@ int main() int rep_counter = num_reps; while (rep_counter--) { - int i = num_channels; - while (i--) { + for (uint8_t i = 0; i < num_channels; ++i) { // Select this channel - radio.setChannel(i); - - // Listen for a little - radio.startListening(); - sleep_us(128); - radio.stopListening(); + scanChannel(i); // updates values[i] accordingly - // Did we get a carrier? - if (radio.testCarrier()) { - ++values[i]; - } + // Print out channel measurements, clamped to a single hex digit + if (values[i]) + printf("%x", rf24_min(0xf, values[i])); + else + printf("-"); } - } - - // Print out channel measurements, clamped to a single hex digit - i = 0; - while (i < num_channels) { - if (values[i]) - printf("%x", rf24_min(0xf, (values[i] & 0xf))); - else - printf("-"); - - ++i; + printf("\r"); } printf("\n"); } return 0; } + +void initRadio() +{ + // configure the radio + radio.setAutoAck(false); // Don't acknowledge arbitrary signals + radio.disableCRC(); // Accept any signal we find + radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) + radio.openReadingPipe(0, noiseAddress[0]); + radio.openReadingPipe(1, noiseAddress[1]); + + // To set the radioNumber via the Serial terminal on startup + printf("Select your data rate. "); + printf("Enter '1' for 1 Mbps, '2' for 2 Mbps, or '3' for 250 kbps. "); + printf("Defaults to 1 Mbps.\n"); + char input = getchar(); + if (input == 50) { + printf("\nUsing 2 Mbps.\n"); + radio.setDataRate(RF24_2MBPS); + } + else if (input == 51) { + printf("\nUsing 250 kbps.\n"); + radio.setDataRate(RF24_250KBPS); + } + else { + printf("\nUsing 1 Mbps.\n"); + radio.setDataRate(RF24_1MBPS); + } + + // Get into standby mode + radio.startListening(); + radio.stopListening(); + radio.flush_rx(); + // radio.printPrettyDetails(); +} + +void scanChannel(uint8_t channel) +{ + radio.setChannel(channel); + + // Listen for a little + radio.startListening(); + sleep_us(130); + bool foundSignal = radio.testRPD(); + radio.stopListening(); + + // Did we get a carrier? + if (foundSignal || radio.testRPD()) { + ++values[channel]; + radio.flush_rx(); + } +} + +void printHeader() +{ + // print the hundreds digits + for (int i = 0; i < num_channels; ++i) + printf("%d", (i / 100)); + printf("\n"); + + // print the tens digits + for (int i = 0; i < num_channels; ++i) + printf("%d", ((i % 100) / 10)); + printf("\n"); + + // print the singles digits + for (int i = 0; i < num_channels; ++i) + printf("%d", (i % 10)); + printf("\n"); + + // print the header's divider + for (int i = 0; i < num_channels; ++i) + printf('~'); + printf("\n"); +} From ebfe1876c9f3bec719946d8fa7e9c2f731392cf3 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 14 Jul 2022 10:27:19 -0700 Subject: [PATCH 22/66] [pico scanner] only send char* to printf() --- examples_pico/scanner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_pico/scanner.cpp b/examples_pico/scanner.cpp index 33f1e81da..887facc99 100644 --- a/examples_pico/scanner.cpp +++ b/examples_pico/scanner.cpp @@ -185,6 +185,6 @@ void printHeader() // print the header's divider for (int i = 0; i < num_channels; ++i) - printf('~'); + printf("~"); printf("\n"); } From e75dc79d3953b6de72d0ea851406f8640cf69f32 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 14 Jul 2022 10:52:39 -0700 Subject: [PATCH 23/66] update pico & arduino scanner --- examples/scanner/scanner.ino | 3 +++ examples_pico/scanner.cpp | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index fa9efdc83..4ed5b5398 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -142,10 +142,13 @@ void loop(void) { delay(2); Serial.print("\nStarting Carrier Wave Output on channel "); Serial.println(c); + // for non-plus models, startConstCarrier() changes address on pipe 0 and sets address width to 5 radio.startConstCarrier(RF24_PA_LOW, c); } else { constCarrierMode = 0; radio.stopConstCarrier(); + radio.setAddressWidth(2); // reset address width + radio.openReadingPipe(0, noiseAddress[0]); // ensure address is looking for noise Serial.println("\nStopping Carrier Out"); printHeader(); } diff --git a/examples_pico/scanner.cpp b/examples_pico/scanner.cpp index 887facc99..b09bca6ef 100644 --- a/examples_pico/scanner.cpp +++ b/examples_pico/scanner.cpp @@ -78,6 +78,10 @@ int main() // print example's name printf("RF24/examples_pico/scanner\n"); + // print a line that should not be wrapped + printf("!!! This example requires a width of at least 126 characters. "); + printf("If this text uses multiple lines, then the output will look bad.\n"); + // initialize the transceiver on the SPI bus while (!radio.begin()) { printf("radio hardware is not responding!!\n"); @@ -110,6 +114,15 @@ int main() printf("\r"); } printf("\n"); + + char input = getchar_timeout_us(0); // get char from buffer for user input + if (input != PICO_ERROR_TIMEOUT) { + if (input == 'b' || input == 'B') { + // reset to bootloader + radio.powerDown(); + reset_usb_boot(0, 0); + } + } } return 0; From 2c9446b1dd6c1e2b2faa7d1553e29e86ce2d30af Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 15 Jul 2022 03:29:36 -0700 Subject: [PATCH 24/66] add new C++ curses scanner example --- cmake/enableNcursesExample.cmake | 15 ++ examples_linux/CMakeLists.txt | 54 ++++- examples_linux/ncurses/CMakeLists.txt | 17 ++ examples_linux/ncurses/scanner_curses.cpp | 274 ++++++++++++++++++++++ 4 files changed, 347 insertions(+), 13 deletions(-) create mode 100644 cmake/enableNcursesExample.cmake create mode 100644 examples_linux/ncurses/CMakeLists.txt create mode 100644 examples_linux/ncurses/scanner_curses.cpp diff --git a/cmake/enableNcursesExample.cmake b/cmake/enableNcursesExample.cmake new file mode 100644 index 000000000..b016787de --- /dev/null +++ b/cmake/enableNcursesExample.cmake @@ -0,0 +1,15 @@ +find_package(Curses) +if(Curses_FOUND) + include_directories(${CURSES_INCLUDE_DIR}) + option(BUILD_NCURSES_EXAMPLE + "Enable/Disable building the ncurses example (requires libncurses5-dev installed)" + ON + ) +else() + message(STATUS "libncurses5-dev not found. Skipping ncurses example") + option(BUILD_NCURSES_EXAMPLE + "Enable/Disable building the ncurses example (requires libncurses5-dev installed)" + OFF + ) +endif() +message(STATUS "BUILD_NCURSES_EXAMPLE set to ${BUILD_NCURSES_EXAMPLE}") diff --git a/examples_linux/CMakeLists.txt b/examples_linux/CMakeLists.txt index 2a46b61f1..b66512f27 100644 --- a/examples_linux/CMakeLists.txt +++ b/examples_linux/CMakeLists.txt @@ -30,25 +30,53 @@ else() # not using MRAA or wiringPi drivers (or pigpio lib was found) list(APPEND EXAMPLES_LIST interruptConfigure) endif() + +set(linked_libs + ${RF24} + pthread # Notice we specify pthread as a linked lib here +) + +# append additional libs for linking to the executable +if("${RF24_DRIVER}" STREQUAL "MRAA") + if(NOT "${LibMRAA}" STREQUAL "LibMRAA-NOTFOUND") + message(STATUS "linking to ${LibMRAA}") + list(APPEND linked_libs ${LibMRAA}) + else() + message(FATAL "Lib ${RF24_DRIVER} not found.") + endif() +elseif("${RF24_DRIVER}" STREQUAL "wiringPi") + if(NOT "${LibWiringPi}" STREQUAL "LibWiringPi-NOTFOUND") + message(STATUS "linking to ${LibWiringPi}") + # wiringPi additionally needs to link to crypt and shm_open libraries + list(APPEND linked_libs ${LibWiringPi} crypt rt) + else() + message(FATAL "Lib ${RF24_DRIVER} not found.") + endif() +elseif(NOT "${LibPIGPIO}" STREQUAL "LibPIGPIO-NOTFOUND" AND NOT DEFINED RF24_NO_INTERUPT) + if(NOT "${RF24_DRIVER}" STREQUAL "pigpio") + message(STATUS "linking to ${LibPIGPIO} for interrupt support") + else() + message(STATUS "linking to ${LibPIGPIO}") + endif() + # linking to pigpio requires pthread to be listed as last linked lib + list(APPEND linked_libs ${LibPIGPIO} pthread) +else() + message(STATUS "Disabling IRQ pin support") +endif() + foreach(example ${EXAMPLES_LIST}) #make a target add_executable(${example} ${example}.cpp) # avoid including interrupt.h when pigpio is not available - if("${LibPIGPIO}" STREQUAL "LibPIGPIO-NOTFOUND") + if("${LibPIGPIO}" STREQUAL "LibPIGPIO-NOTFOUND" OR DEFINED RF24_NO_INTERRUPT) target_compile_definitions(${example} PUBLIC RF24_NO_INTERRUPT) endif() - # link the RF24 lib to the target. Notice we specify pthread as a linked lib here - if("${RF24_DRIVER}" STREQUAL "MRAA") - target_link_libraries(${example} PUBLIC ${RF24} pthread ${LibMRAA}) - elseif("${RF24_DRIVER}" STREQUAL "wiringPi") - # wiringPi additionally needs to link to crypt and shm_open libraries - target_link_libraries(${example} PUBLIC ${RF24} pthread ${LibWiringPi} crypt rt) - elseif("${RF24_DRIVER}" STREQUAL "pigpio" OR NOT "${LibPIGPIO}" STREQUAL "LibPIGPIO-NOTFOUND") - # linking to pigpio requires pthread to be listed as last linked lib - target_link_libraries(${example} PUBLIC ${RF24} ${LibPIGPIO} pthread) - else() # not using MRAA or wiringPi drivers - target_link_libraries(${example} PUBLIC ${RF24} pthread) - endif() + target_link_libraries(${example} PUBLIC ${linked_libs}) endforeach() + +include(../cmake/enableNcursesExample.cmake) +if(BUILD_NCURSES_EXAMPLE) + add_subdirectory(ncurses) +endif() diff --git a/examples_linux/ncurses/CMakeLists.txt b/examples_linux/ncurses/CMakeLists.txt new file mode 100644 index 000000000..2541c3bc7 --- /dev/null +++ b/examples_linux/ncurses/CMakeLists.txt @@ -0,0 +1,17 @@ +# this example needs the ncurses package installed +find_package(Curses REQUIRED) +include_directories(${CURSES_INCLUDE_DIR}) + +set(example scanner_curses) + +# make a target +add_executable(${example} ${example}.cpp) + +# link the RF24 lib to the target. Notice we specify pthread as a linked lib here +target_link_libraries(${example} PUBLIC + ${linked_libs} + ${CURSES_LIBRARIES} +) +if("${LibPIGPIO}" STREQUAL "LibPIGPIO-NOTFOUND" OR DEFINED RF24_NO_INTERRUPT) + target_compile_definitions(${example} PUBLIC RF24_NO_INTERRUPT) +endif() diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp new file mode 100644 index 000000000..89f8d28e6 --- /dev/null +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -0,0 +1,274 @@ +/* + Copyright (C) 2022 Brendan Doherty <2bndy5@gmail.com> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + version 2 as published by the Free Software Foundation. + */ +#include // sprintf() +#include // string, getline() +#include // time_t, time(), difftime() +#include // cout, endl, flush, cin +#include +#include +#include + +using namespace std; + +/****************** Linux ***********************/ +// Radio CE Pin, CSN Pin, SPI Speed +// CE Pin uses GPIO number with BCM and SPIDEV drivers, other platforms use their own pin numbering +// CS Pin addresses the SPI bus number at /dev/spidev. +// ie: RF24 radio(, *10+); spidev1.0 is 10, spidev1.1 is 11 etc.. + +// Generic: +RF24 radio(22, 0); +/****************** Linux (BBB,x86,etc) ***********************/ +// See http://nRF24.github.io/RF24/pages.html for more information on usage +// See http://iotdk.intel.com/docs/master/mraa/ for more information on MRAA +// See https://www.kernel.org/doc/Documentation/spi/spidev for more information on SPIDEV + +// Channel info +const uint8_t num_channels = 126; // 0-125 are supported +uint8_t values[num_channels] = {0}; // the array to store summary of signal counts per channel +const int CACHE_MAX = 5; // maximum depth of history for calculating peaks per channel +queue history[num_channels]; // a cache of history for each channel + +// we'll need a container to act as a buffer during history init and counting historic signals +queue temp = queue({0, 0, 0, 0, 0}); + +// To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). +// These addresses are designed to confuse the radio into thinking +// that the RF signal's preamble is part of the packet/payload. +const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; + +unsigned int num_reps = 0; // count of passes for each scan of the entire spectrum + +WINDOW* win; // curses base window object +char sig_cnt_buf[] = " - "; + +// function prototypes +void init_radio(); +bool scan_channel(uint8_t); +void init_curses(); +void deinit_curses(); +void init_containers(); +int count_history(uint8_t index); + +class ProgressBar +{ + +private: + const int x, y, w, color; + +public: + ProgressBar(int cols, int rows, int width, string label, int color_index) + : x(cols), y(rows), w(width), color(color_index) + { + attron(COLOR_PAIR(color)); + mvaddstr(y, x, label.c_str()); + for (uint8_t i = 0; i < w - 8; ++i) + addch(ACS_HLINE); + addstr(sig_cnt_buf); + attroff(COLOR_PAIR(color)); + }; + + void update(int completed) + { + int filled = (w - 8) * completed / CACHE_MAX; + int offset_x = 5; + move(y, x + offset_x); + for (int i = offset_x; i < w - 3; ++i) { + int bar_color = i < (filled + offset_x) ? 3 : color; + attron(COLOR_PAIR(bar_color)); + addch(ACS_HLINE); + attroff(COLOR_PAIR(bar_color)); + } + attron(COLOR_PAIR(color)); + addstr(sig_cnt_buf); + attroff(COLOR_PAIR(color)); + }; +}; + +// our table of progress bars used to represent channels in the curses window +ProgressBar* table[num_channels]; + + +int main(int argc, char** argv) +{ + // print example's name + cout << argv[0] << endl; + + // Setup the radio + if (!radio.begin()) { + cout << "Radio hardware not responding!" << endl; + return 1; + } + init_radio(); + + string input = ""; + int duration = 0; + while (!input.length()) { + cout << endl + << "Enter the duration (in seconds) of the scan: "; + getline(cin, input); + if (input.length()) { + duration = stoi(input); + if (!duration) input.clear(); + } + } + + time_t start = time(nullptr); + while (static_cast(difftime(time(nullptr), start)) < duration) { + // Clear measurement values + memset(values, 0, sizeof(values)); + + // Scan all channels num_reps times + int rep_counter = num_reps; + while (rep_counter--) { + + for (uint8_t i = 0; i < num_channels; ++i) { + + bool foundSignal = scan_channel(i); + history[i].pop(); + history[i].push(foundSignal); + + // output the summary/snapshot for this channel + if (values[i]) { + int cached_count = count_history(i); + sprintf(sig_cnt_buf, " %X ", rf24_min(values[i], 0xF)); + + // Print changes to screen + table[i]->update(cached_count); + refresh(); + } + } + } + } + + return 0; +} + +void init_radio() +{ + // set the data rate + cout << "Select your Data Rate. " + << "Enter '1' for 1 Mbps, '2' for 2 Mbps, '3' for 250 kbps. " + << "Defaults to 1Mbps." + << endl; + string dataRate = ""; + getline(cin, dataRate); + if (dataRate.length() >= 1 && static_cast(dataRate[0]) == '2') { + cout << "Using 2 Mbps." << endl; + radio.setDataRate(RF24_2MBPS); + } + else if (dataRate.length() >= 1 && static_cast(dataRate[0]) == '3') { + cout << "Using 250 kbps." << endl; + radio.setDataRate(RF24_250KBPS); + } + else { + cout << "Using 1 Mbps." << endl; + radio.setDataRate(RF24_1MBPS); + } + + // configure the radio + radio.setAutoAck(false); // Don't acknowledge arbitrary signals + radio.disableCRC(); // Accept any signal we find + radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) + radio.openReadingPipe(0, noiseAddress[0]); + radio.openReadingPipe(1, noiseAddress[1]); + + // Get into standby mode + radio.startListening(); + radio.stopListening(); + radio.flush_rx(); + // radio.printPrettyDetails(); +} + +bool scan_channel(uint8_t channel) +{ + // Select this channel + radio.setChannel(channel); + + // Listen for a little + radio.startListening(); + delayMicroseconds(130); + // for some reason, this flag is more accurate on Linux when still in RX mode. + bool foundSignal = radio.testRPD(); + radio.stopListening(); + + // Did we get a signal? + if (foundSignal || radio.testRPD()) { + ++values[channel]; + radio.flush_rx(); // discard packets of noise + return true; + } + return false; +} + +void init_curses() +{ + win = initscr(); // Start curses mode + noecho(); + cbreak(); + start_color(); + use_default_colors(); + init_pair(3, COLOR_YELLOW, -1); + init_pair(5, COLOR_MAGENTA, -1); + init_pair(7, COLOR_WHITE, -1); +} + +void deinit_curses() +{ + nocbreak(); + echo(); + endwin(); +} + +void init_containers() +{ + // fill our history with blanks + for (uint8_t i = 0; i < num_channels; ++i) { + history[i] = queue(temp); + } + + // init our progress bars + int bar_w = COLS / 6; + for (uint8_t i = 0; i < 21; ++i) { // 21 rows + for (uint8_t j = i; j < i + (21 * 6); j += 21) { // 6 columns + + int color = (j / 21) % 2 ? 7 : 3; // 3 is yellow, 7 is white + int inMHz = 2400 + j; + + table[j] = new ProgressBar(bar_w * j / 21, // x + i + 3, // y + bar_w, // width + to_string(inMHz), // label + color); // color + } + } +} + +int count_history(uint8_t index) +{ + int sum = 0; + // clear the temp buffer + while (!temp.empty()) temp.pop(); + + // move values of history into temp buffer, and count the historic signals + while (!history[index].empty()) { + temp.push(history[index].front()); + sum += static_cast(history[index].front()); + history[index].pop(); + } + + // move values from buffer back into history + while (!temp.empty()) { + history[index].push(temp.front()); + temp.pop(); + } + + return sum; +} + +// vim:ai:cin:sts=2 sw=2 ft=cpp From c038d67ed19bc0098ee1ef7c69b5d33cf8f6a398 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 15 Jul 2022 03:32:11 -0700 Subject: [PATCH 25/66] ran clang-format on new scanner_curses.cpp --- examples_linux/ncurses/scanner_curses.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 89f8d28e6..d8f72edde 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -44,7 +44,7 @@ const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; unsigned int num_reps = 0; // count of passes for each scan of the entire spectrum -WINDOW* win; // curses base window object +WINDOW* win; // curses base window object char sig_cnt_buf[] = " - "; // function prototypes @@ -93,7 +93,6 @@ class ProgressBar // our table of progress bars used to represent channels in the curses window ProgressBar* table[num_channels]; - int main(int argc, char** argv) { // print example's name @@ -241,10 +240,10 @@ void init_containers() int inMHz = 2400 + j; table[j] = new ProgressBar(bar_w * j / 21, // x - i + 3, // y - bar_w, // width - to_string(inMHz), // label - color); // color + i + 3, // y + bar_w, // width + to_string(inMHz), // label + color); // color } } } From 6d3a5ce2d0bf69dcbf6b7b268ba2b527e1d10bc5 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 15 Jul 2022 03:34:14 -0700 Subject: [PATCH 26/66] let Linux CI run on examples nested in folders --- .github/workflows/build_linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index fd1449642..038a476ac 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -17,7 +17,7 @@ on: - "utility/MRAAA/*" - "utility/SPIDEV/*" - "utility/pigpio" - - "examples_linux/*" + - "examples_linux/**" - "!examples_linux/*.py" - "!examples_linux/*.md" - "pyRF24/setup.py" @@ -39,7 +39,7 @@ on: - "utility/MRAAA/*" - "utility/SPIDEV/*" - "utility/pigpio" - - "examples_linux/*" + - "examples_linux/**" - "!examples_linux/*.py" - "!examples_linux/*.md" - "pyRF24/setup.py" From 94f2e3bc662a5ba3a95965a619bf1dba4f74770f Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 15 Jul 2022 03:58:03 -0700 Subject: [PATCH 27/66] begin debugging scanner_curses.cpp --- examples_linux/ncurses/scanner_curses.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index d8f72edde..b21ebc393 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -49,10 +49,10 @@ char sig_cnt_buf[] = " - "; // function prototypes void init_radio(); -bool scan_channel(uint8_t); void init_curses(); -void deinit_curses(); void init_containers(); +void deinit_curses(); +bool scan_channel(uint8_t); int count_history(uint8_t index); class ProgressBar @@ -67,6 +67,7 @@ class ProgressBar { attron(COLOR_PAIR(color)); mvaddstr(y, x, label.c_str()); + addch(' '); for (uint8_t i = 0; i < w - 8; ++i) addch(ACS_HLINE); addstr(sig_cnt_buf); @@ -117,6 +118,10 @@ int main(int argc, char** argv) } } + // create out interface + init_curses(); + init_containers(); + time_t start = time(nullptr); while (static_cast(difftime(time(nullptr), start)) < duration) { // Clear measurement values @@ -145,6 +150,7 @@ int main(int argc, char** argv) } } + deinit_curses(); return 0; } From 228b98aefff41a3cfa5c252ecb9270062cf0dafd Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 15 Jul 2022 04:36:14 -0700 Subject: [PATCH 28/66] simplify the progress bars' init --- examples_linux/ncurses/scanner_curses.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index b21ebc393..559ced208 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -42,7 +42,7 @@ queue temp = queue({0, 0, 0, 0, 0}); // that the RF signal's preamble is part of the packet/payload. const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; -unsigned int num_reps = 0; // count of passes for each scan of the entire spectrum +unsigned int num_reps = 100; // count of passes for each scan of the entire spectrum WINDOW* win; // curses base window object char sig_cnt_buf[] = " - "; @@ -238,20 +238,22 @@ void init_containers() } // init our progress bars - int bar_w = COLS / 6; - for (uint8_t i = 0; i < 21; ++i) { // 21 rows - for (uint8_t j = i; j < i + (21 * 6); j += 21) { // 6 columns - - int color = (j / 21) % 2 ? 7 : 3; // 3 is yellow, 7 is white - int inMHz = 2400 + j; - - table[j] = new ProgressBar(bar_w * j / 21, // x + int bar_w = COLS / 6; // total progress bar width (including all contents) + int inMHz = 2400; + for (uint8_t i = 0; i < 21; ++i) { // 21 rows + for (uint8_t j = 0; j < 6; ++j) { // 6 columns + + int color = j % 2 ? 7 : 3; // 3 is yellow, 7 is white + + table[j] = new ProgressBar(bar_w * j, // x i + 3, // y bar_w, // width to_string(inMHz), // label color); // color + inMHz++; } } + refresh(); } int count_history(uint8_t index) From 55fb24ea7e7dc184f8600dd6d5531f331e538fac Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 15 Jul 2022 07:47:40 -0700 Subject: [PATCH 29/66] [C++ curses scanner] show sig counts on exit; display countdown timer --- examples_linux/ncurses/scanner_curses.cpp | 56 +++++++++++++++-------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 559ced208..f8fd8fed8 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -45,10 +45,9 @@ const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; unsigned int num_reps = 100; // count of passes for each scan of the entire spectrum WINDOW* win; // curses base window object -char sig_cnt_buf[] = " - "; // function prototypes -void init_radio(); +int init_radio(); void init_curses(); void init_containers(); void deinit_curses(); @@ -70,11 +69,11 @@ class ProgressBar addch(' '); for (uint8_t i = 0; i < w - 8; ++i) addch(ACS_HLINE); - addstr(sig_cnt_buf); + addstr(" - "); attroff(COLOR_PAIR(color)); }; - void update(int completed) + void update(int completed, int sig_count) { int filled = (w - 8) * completed / CACHE_MAX; int offset_x = 5; @@ -86,7 +85,7 @@ class ProgressBar attroff(COLOR_PAIR(bar_color)); } attron(COLOR_PAIR(color)); - addstr(sig_cnt_buf); + printw(" %x ", sig_count); attroff(COLOR_PAIR(color)); }; }; @@ -104,7 +103,8 @@ int main(int argc, char** argv) cout << "Radio hardware not responding!" << endl; return 1; } - init_radio(); + int d_rate = init_radio(); + char bpsUnit = d_rate > 2 ? 'k' : 'M'; string input = ""; int duration = 0; @@ -121,12 +121,18 @@ int main(int argc, char** argv) // create out interface init_curses(); init_containers(); - time_t start = time(nullptr); while (static_cast(difftime(time(nullptr), start)) < duration) { // Clear measurement values memset(values, 0, sizeof(values)); + mvprintw(0, + 0, + "Scanning for %d seconds at %d %cbps", + static_cast(difftime(time(nullptr), start)), + d_rate, + bpsUnit); + refresh(); // Scan all channels num_reps times int rep_counter = num_reps; while (rep_counter--) { @@ -140,10 +146,9 @@ int main(int argc, char** argv) // output the summary/snapshot for this channel if (values[i]) { int cached_count = count_history(i); - sprintf(sig_cnt_buf, " %X ", rf24_min(values[i], 0xF)); // Print changes to screen - table[i]->update(cached_count); + table[i]->update(cached_count, rf24_min(values[i], 0xF)); refresh(); } } @@ -154,8 +159,9 @@ int main(int argc, char** argv) return 0; } -void init_radio() +int init_radio() { + int returnVal = 0; // set the data rate cout << "Select your Data Rate. " << "Enter '1' for 1 Mbps, '2' for 2 Mbps, '3' for 250 kbps. " @@ -166,14 +172,17 @@ void init_radio() if (dataRate.length() >= 1 && static_cast(dataRate[0]) == '2') { cout << "Using 2 Mbps." << endl; radio.setDataRate(RF24_2MBPS); + returnVal = 2; } else if (dataRate.length() >= 1 && static_cast(dataRate[0]) == '3') { cout << "Using 250 kbps." << endl; radio.setDataRate(RF24_250KBPS); + returnVal = 250; } else { cout << "Using 1 Mbps." << endl; radio.setDataRate(RF24_1MBPS); + returnVal = 1; } // configure the radio @@ -188,6 +197,8 @@ void init_radio() radio.stopListening(); radio.flush_rx(); // radio.printPrettyDetails(); + + return returnVal; } bool scan_channel(uint8_t channel) @@ -228,6 +239,16 @@ void deinit_curses() nocbreak(); echo(); endwin(); + + // print out the total signal counts (if any) + uint8_t active_channels = 0; // the sum of channels with detected noise + for (uint8_t i = 0; i < num_channels; ++i) { + if (values[i]) { + active_channels++; + cout << " " << i << ": " << static_cast(values[i]) << endl; + } + } + cout << static_cast(active_channels) << " channels deteted signals." << endl; } void init_containers() @@ -238,22 +259,21 @@ void init_containers() } // init our progress bars - int bar_w = COLS / 6; // total progress bar width (including all contents) + int bar_w = COLS / 6; // total progress bar width (including all contents) int inMHz = 2400; for (uint8_t i = 0; i < 21; ++i) { // 21 rows for (uint8_t j = 0; j < 6; ++j) { // 6 columns int color = j % 2 ? 7 : 3; // 3 is yellow, 7 is white - - table[j] = new ProgressBar(bar_w * j, // x - i + 3, // y - bar_w, // width - to_string(inMHz), // label - color); // color + + table[j + (i * 6)] = new ProgressBar(bar_w * j, // x + i + 3, // y + bar_w, // width + to_string(inMHz), // label + color); // color inMHz++; } } - refresh(); } int count_history(uint8_t index) From e3568bee5427e5e8375b1023bc86da98f77b30fc Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 15 Jul 2022 08:04:35 -0700 Subject: [PATCH 30/66] reduce nested loops during scan --- examples_linux/ncurses/scanner_curses.cpp | 48 +++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index f8fd8fed8..8adf28688 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -29,10 +29,10 @@ RF24 radio(22, 0); // See https://www.kernel.org/doc/Documentation/spi/spidev for more information on SPIDEV // Channel info -const uint8_t num_channels = 126; // 0-125 are supported -uint8_t values[num_channels] = {0}; // the array to store summary of signal counts per channel -const int CACHE_MAX = 5; // maximum depth of history for calculating peaks per channel -queue history[num_channels]; // a cache of history for each channel +const uint8_t num_channels = 126; // 0-125 are supported +unsigned int values[num_channels] = {0}; // the array to store summary of signal counts per channel +const int CACHE_MAX = 5; // maximum depth of history for calculating peaks per channel +queue history[num_channels]; // a cache of history for each channel // we'll need a container to act as a buffer during history init and counting historic signals queue temp = queue({0, 0, 0, 0, 0}); @@ -42,7 +42,7 @@ queue temp = queue({0, 0, 0, 0, 0}); // that the RF signal's preamble is part of the packet/payload. const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; -unsigned int num_reps = 100; // count of passes for each scan of the entire spectrum +unsigned int passeCount = 0; // count of passes for each scan of the entire spectrum WINDOW* win; // curses base window object @@ -121,6 +121,7 @@ int main(int argc, char** argv) // create out interface init_curses(); init_containers(); + uint8_t i = 0; time_t start = time(nullptr); while (static_cast(difftime(time(nullptr), start)) < duration) { // Clear measurement values @@ -129,29 +130,29 @@ int main(int argc, char** argv) mvprintw(0, 0, "Scanning for %d seconds at %d %cbps", - static_cast(difftime(time(nullptr), start)), + static_cast(difftime(start + duration, time(nullptr))), d_rate, bpsUnit); - refresh(); - // Scan all channels num_reps times - int rep_counter = num_reps; - while (rep_counter--) { - - for (uint8_t i = 0; i < num_channels; ++i) { - bool foundSignal = scan_channel(i); - history[i].pop(); - history[i].push(foundSignal); + bool foundSignal = scan_channel(i); + values[i] += static_cast(foundSignal); + history[i].pop(); + history[i].push(foundSignal); - // output the summary/snapshot for this channel - if (values[i]) { - int cached_count = count_history(i); + // output the summary/snapshot for this channel + if (values[i]) { + int cached_count = count_history(i); - // Print changes to screen - table[i]->update(cached_count, rf24_min(values[i], 0xF)); - refresh(); - } - } + // Print changes to screen + table[i]->update(cached_count, rf24_min(values[i], 0xF)); + } + refresh(); + if (i + 1 == num_channels) { + i = 0; + passeCount++; + } + else { + ++i; } } @@ -215,7 +216,6 @@ bool scan_channel(uint8_t channel) // Did we get a signal? if (foundSignal || radio.testRPD()) { - ++values[channel]; radio.flush_rx(); // discard packets of noise return true; } From 08cacdbcb0d80daf1f7b544b6bd67285f27bb543 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 15 Jul 2022 09:04:53 -0700 Subject: [PATCH 31/66] getting closer to finishing C++ curses scanner --- examples_linux/ncurses/scanner_curses.cpp | 107 +++++++++++----------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 8adf28688..f7a37e4af 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -29,10 +29,10 @@ RF24 radio(22, 0); // See https://www.kernel.org/doc/Documentation/spi/spidev for more information on SPIDEV // Channel info -const uint8_t num_channels = 126; // 0-125 are supported -unsigned int values[num_channels] = {0}; // the array to store summary of signal counts per channel +const uint8_t MAX_CHANNELS = 126; // 0-125 are supported +unsigned int values[MAX_CHANNELS] = {0}; // the array to store summary of signal counts per channel const int CACHE_MAX = 5; // maximum depth of history for calculating peaks per channel -queue history[num_channels]; // a cache of history for each channel +queue history[MAX_CHANNELS]; // a cache of history for each channel // we'll need a container to act as a buffer during history init and counting historic signals queue temp = queue({0, 0, 0, 0, 0}); @@ -42,17 +42,17 @@ queue temp = queue({0, 0, 0, 0, 0}); // that the RF signal's preamble is part of the packet/payload. const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; -unsigned int passeCount = 0; // count of passes for each scan of the entire spectrum +unsigned int passesCount = 0; // count of passes for each scan of the entire spectrum WINDOW* win; // curses base window object // function prototypes -int init_radio(); -void init_curses(); -void init_containers(); -void deinit_curses(); -bool scan_channel(uint8_t); -int count_history(uint8_t index); +uint8_t initRadio(); +void initCurses(); +void initContainers(); +void deinitCurses(); +bool scanChannel(uint8_t); +int countHistory(uint8_t index); class ProgressBar { @@ -79,7 +79,7 @@ class ProgressBar int offset_x = 5; move(y, x + offset_x); for (int i = offset_x; i < w - 3; ++i) { - int bar_color = i < (filled + offset_x) ? 3 : color; + int bar_color = i < (filled + offset_x) ? 5 : color; attron(COLOR_PAIR(bar_color)); addch(ACS_HLINE); attroff(COLOR_PAIR(bar_color)); @@ -91,7 +91,7 @@ class ProgressBar }; // our table of progress bars used to represent channels in the curses window -ProgressBar* table[num_channels]; +ProgressBar* table[MAX_CHANNELS]; int main(int argc, char** argv) { @@ -103,14 +103,13 @@ int main(int argc, char** argv) cout << "Radio hardware not responding!" << endl; return 1; } - int d_rate = init_radio(); + uint8_t d_rate = initRadio(); char bpsUnit = d_rate > 2 ? 'k' : 'M'; string input = ""; int duration = 0; while (!input.length()) { - cout << endl - << "Enter the duration (in seconds) of the scan: "; + cout << "Enter the duration (in seconds) of the scan: "; getline(cin, input); if (input.length()) { duration = stoi(input); @@ -119,55 +118,54 @@ int main(int argc, char** argv) } // create out interface - init_curses(); - init_containers(); + initCurses(); + initContainers(); + mvaddstr(0, 0, "Channels are labeled in MHz."); + mvaddstr(1, 0, "Signal counts are clamped to a single hexadecimal digit."); + uint8_t i = 0; time_t start = time(nullptr); while (static_cast(difftime(time(nullptr), start)) < duration) { - // Clear measurement values - memset(values, 0, sizeof(values)); - - mvprintw(0, + mvprintw(2, 0, - "Scanning for %d seconds at %d %cbps", + "Scanning for %3d seconds at %d %cbps", static_cast(difftime(start + duration, time(nullptr))), - d_rate, + static_cast(d_rate), bpsUnit); - bool foundSignal = scan_channel(i); - values[i] += static_cast(foundSignal); + bool foundSignal = scanChannel(i); history[i].pop(); history[i].push(foundSignal); // output the summary/snapshot for this channel if (values[i]) { - int cached_count = count_history(i); + int cachedCount = countHistory(i); // Print changes to screen - table[i]->update(cached_count, rf24_min(values[i], 0xF)); + table[i]->update(cachedCount, rf24_min(values[i], 0xF)); } + refresh(); - if (i + 1 == num_channels) { + if (i + 1 == MAX_CHANNELS) { i = 0; - passeCount++; + ++passesCount; } else { ++i; } } - deinit_curses(); + deinitCurses(); return 0; } -int init_radio() +uint8_t initRadio() { - int returnVal = 0; + uint8_t returnVal = 0; // set the data rate cout << "Select your Data Rate. " << "Enter '1' for 1 Mbps, '2' for 2 Mbps, '3' for 250 kbps. " - << "Defaults to 1Mbps." - << endl; + << "Defaults to 1Mbps: "; string dataRate = ""; getline(cin, dataRate); if (dataRate.length() >= 1 && static_cast(dataRate[0]) == '2') { @@ -202,7 +200,7 @@ int init_radio() return returnVal; } -bool scan_channel(uint8_t channel) +bool scanChannel(uint8_t channel) { // Select this channel radio.setChannel(channel); @@ -216,13 +214,14 @@ bool scan_channel(uint8_t channel) // Did we get a signal? if (foundSignal || radio.testRPD()) { + ++values[channel]; radio.flush_rx(); // discard packets of noise return true; } return false; } -void init_curses() +void initCurses() { win = initscr(); // Start curses mode noecho(); @@ -234,7 +233,7 @@ void init_curses() init_pair(7, COLOR_WHITE, -1); } -void deinit_curses() +void deinitCurses() { nocbreak(); echo(); @@ -242,41 +241,47 @@ void deinit_curses() // print out the total signal counts (if any) uint8_t active_channels = 0; // the sum of channels with detected noise - for (uint8_t i = 0; i < num_channels; ++i) { + for (uint8_t i = 0; i < MAX_CHANNELS; ++i) { if (values[i]) { active_channels++; - cout << " " << i << ": " << static_cast(values[i]) << endl; + cout << " " + << static_cast(i) + << ": " + << static_cast(values[i]) + << endl; } } - cout << static_cast(active_channels) << " channels deteted signals." << endl; + cout << static_cast(active_channels) + << " channels deteted signals after " + << passesCount + << " passes." + << endl; } -void init_containers() +void initContainers() { // fill our history with blanks - for (uint8_t i = 0; i < num_channels; ++i) { + for (uint8_t i = 0; i < MAX_CHANNELS; ++i) { history[i] = queue(temp); } // init our progress bars int bar_w = COLS / 6; // total progress bar width (including all contents) - int inMHz = 2400; for (uint8_t i = 0; i < 21; ++i) { // 21 rows for (uint8_t j = 0; j < 6; ++j) { // 6 columns int color = j % 2 ? 7 : 3; // 3 is yellow, 7 is white - - table[j + (i * 6)] = new ProgressBar(bar_w * j, // x - i + 3, // y - bar_w, // width - to_string(inMHz), // label - color); // color - inMHz++; + uint8_t channel = j * 21 + i; + table[channel] = new ProgressBar(bar_w * j, // x + i + 3, // y + bar_w, // width + to_string(2400 + channel), // label + color); // color } } } -int count_history(uint8_t index) +int countHistory(uint8_t index) { int sum = 0; // clear the temp buffer From f23a8b9a837d682d74fcddc18be9349b0163abac Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 15 Jul 2022 10:57:02 -0700 Subject: [PATCH 32/66] something wrong with curses C++ scanner IDK what the actual problem is. The ncurses/scanner_curses.cpp example runs fine, but it isn't as promiscuous as expected. My hunch is that the example runs too fast, but I cannot verify it. --- examples_linux/ncurses/scanner_curses.cpp | 26 +++++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index f7a37e4af..8bee4853a 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -47,10 +47,11 @@ unsigned int passesCount = 0; // count of passes for each scan of the entire spe WINDOW* win; // curses base window object // function prototypes + uint8_t initRadio(); void initCurses(); -void initContainers(); void deinitCurses(); +void initContainers(); bool scanChannel(uint8_t); int countHistory(uint8_t index); @@ -159,6 +160,7 @@ int main(int argc, char** argv) return 0; } +/** init radio according to user-specified data rate */ uint8_t initRadio() { uint8_t returnVal = 0; @@ -200,6 +202,7 @@ uint8_t initRadio() return returnVal; } +/** scan the specified channel and increment signal count accordingly */ bool scanChannel(uint8_t channel) { // Select this channel @@ -221,6 +224,7 @@ bool scanChannel(uint8_t channel) return false; } +/** init the curses interface */ void initCurses() { win = initscr(); // Start curses mode @@ -233,6 +237,7 @@ void initCurses() init_pair(7, COLOR_WHITE, -1); } +/** de-init the curses interface & show total signal counts */ void deinitCurses() { nocbreak(); @@ -258,6 +263,7 @@ void deinitCurses() << endl; } +/** init the history and progress bars */ void initContainers() { // fill our history with blanks @@ -267,22 +273,28 @@ void initContainers() // init our progress bars int bar_w = COLS / 6; // total progress bar width (including all contents) + for (uint8_t i = 0; i < 21; ++i) { // 21 rows for (uint8_t j = 0; j < 6; ++j) { // 6 columns - int color = j % 2 ? 7 : 3; // 3 is yellow, 7 is white uint8_t channel = j * 21 + i; - table[channel] = new ProgressBar(bar_w * j, // x - i + 3, // y - bar_w, // width - to_string(2400 + channel), // label - color); // color + table[channel] = new ProgressBar( + bar_w * j, // x + i + 3, // y + bar_w, // width + to_string(2400 + channel), // label + j % 2 ? 7 : 3 // 3 is yellow, 7 is white + ); } } } +/** count the number of historic signals (limited to CACHE_MAX) */ int countHistory(uint8_t index) { + // TODO: there must be a better way to implement a FIFO and + // count the exact same values contained within. + int sum = 0; // clear the temp buffer while (!temp.empty()) temp.pop(); From c8cfd610dc720a4bfaeea1d637dd3c88b28f41c4 Mon Sep 17 00:00:00 2001 From: TMRh20 Date: Sun, 17 Jul 2022 06:16:34 -0600 Subject: [PATCH 33/66] Call available - Catch signals lower than 64dBm by calling available. Only works if an actual payload was received. --- examples_linux/scanner.cpp | 2 +- examples_linux/scanner.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index 60dc8419a..c5cb47b24 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -152,7 +152,7 @@ int main(int argc, char** argv) radio.stopListening(); // Did we get a signal? - if (foundSignal || radio.testRPD()) { + if (foundSignal || radio.testRPD() || radio.available()) { ++values[i]; radio.flush_rx(); // discard packets of noise } diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 6c23a66b5..e1c2ff19b 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -132,7 +132,7 @@ def scan_channel(channel: int) -> bool: time.sleep(0.00013) result = radio.testRPD() radio.stopListening() - result = result or radio.testRPD() + result = result or radio.testRPD() or radio.available() if result: radio.flush_rx() return result From 2b4de3ea166aac9853675463afdfab523670f75d Mon Sep 17 00:00:00 2001 From: TMRh20 Date: Sun, 17 Jul 2022 06:33:39 -0600 Subject: [PATCH 34/66] Add additional 'noise' addys --- examples_linux/scanner.cpp | 6 +++++- examples_linux/scanner.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index c5cb47b24..0618ff8f8 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -73,7 +73,7 @@ uint8_t values[num_channels]; // the array to store summary of signal counts // To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). // These addresses are designed to confuse the radio into thinking // that the RF signal's preamble is part of the packet/payload. -const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; +const uint8_t noiseAddress[][6] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x0A, 0xAA}, {0xA0, 0xAA}, {0x00,0xAA}, {0xAB,0xAA}}; const int num_reps = 100; // number of passes for each scan of the entire spectrum @@ -116,6 +116,10 @@ int main(int argc, char** argv) radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) radio.openReadingPipe(0, noiseAddress[0]); radio.openReadingPipe(1, noiseAddress[1]); + radio.openReadingPipe(2, noiseAddress[2]); + radio.openReadingPipe(3, noiseAddress[3]); + radio.openReadingPipe(4, noiseAddress[4]); + radio.openReadingPipe(5, noiseAddress[5]); // Get into standby mode radio.startListening(); diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index e1c2ff19b..649e42dd2 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -77,6 +77,10 @@ def init_radio(): radio.setAddressWidth(2) radio.openReadingPipe(0, b"\x55\x55") radio.openReadingPipe(1, b"\xAA\xAA") + radio.openReadingPipe(1, b"\x0A\xAA") + radio.openReadingPipe(1, b"\xA0\xAA") + radio.openReadingPipe(1, b"\x00\xAA") + radio.openReadingPipe(1, b"\xAB\xAA") radio.startListening() radio.stopListening() radio.flush_rx() From debed433f7619d45970b3d8046ab856349c77dd1 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 17 Jul 2022 09:43:08 -0700 Subject: [PATCH 35/66] use available() in arduino & pico scanners use more pipes in C++ curses scanner --- examples/scanner/scanner.ino | 2 +- examples_linux/ncurses/scanner_curses.cpp | 8 ++++++-- examples_pico/scanner.cpp | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index 4ed5b5398..80b00e19b 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -173,7 +173,7 @@ void loop(void) { radio.stopListening(); // Did we get a signal? - if (radio.testRPD()) { + if (radio.testRPD() || radio.available()) { ++values[i]; radio.flush_rx(); // discard packets of noise } diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 8bee4853a..922749371 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -40,7 +40,7 @@ queue temp = queue({0, 0, 0, 0, 0}); // To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). // These addresses are designed to confuse the radio into thinking // that the RF signal's preamble is part of the packet/payload. -const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; +const uint8_t noiseAddress[][6] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x0A, 0xAA}, {0xA0, 0xAA}, {0x00, 0xAA}, {0xAB, 0xAA}}; unsigned int passesCount = 0; // count of passes for each scan of the entire spectrum @@ -192,6 +192,10 @@ uint8_t initRadio() radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) radio.openReadingPipe(0, noiseAddress[0]); radio.openReadingPipe(1, noiseAddress[1]); + radio.openReadingPipe(2, noiseAddress[2]); + radio.openReadingPipe(3, noiseAddress[3]); + radio.openReadingPipe(4, noiseAddress[4]); + radio.openReadingPipe(5, noiseAddress[5]); // Get into standby mode radio.startListening(); @@ -216,7 +220,7 @@ bool scanChannel(uint8_t channel) radio.stopListening(); // Did we get a signal? - if (foundSignal || radio.testRPD()) { + if (foundSignal || radio.testRPD() || radio.available()) { ++values[channel]; radio.flush_rx(); // discard packets of noise return true; diff --git a/examples_pico/scanner.cpp b/examples_pico/scanner.cpp index b09bca6ef..46fb6ef57 100644 --- a/examples_pico/scanner.cpp +++ b/examples_pico/scanner.cpp @@ -58,7 +58,7 @@ uint8_t values[num_channels]; // the array to store summary of signal counts // To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). // These addresses are designed to confuse the radio into thinking // that the RF signal's preamble is part of the packet/payload. -const uint8_t noiseAddress[][2] = {{0x55, 0x55}, {0xAA, 0xAA}}; +const uint8_t noiseAddress[][6] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x0A, 0xAA}, {0xA0, 0xAA}, {0x00,0xAA}, {0xAB,0xAA}}; const int num_reps = 100; // number of passes for each scan of the entire spectrum @@ -136,6 +136,10 @@ void initRadio() radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) radio.openReadingPipe(0, noiseAddress[0]); radio.openReadingPipe(1, noiseAddress[1]); + radio.openReadingPipe(2, noiseAddress[2]); + radio.openReadingPipe(3, noiseAddress[3]); + radio.openReadingPipe(4, noiseAddress[4]); + radio.openReadingPipe(5, noiseAddress[5]); // To set the radioNumber via the Serial terminal on startup printf("Select your data rate. "); @@ -173,7 +177,7 @@ void scanChannel(uint8_t channel) radio.stopListening(); // Did we get a carrier? - if (foundSignal || radio.testRPD()) { + if (foundSignal || radio.testRPD() || radio.available()) { ++values[channel]; radio.flush_rx(); } From 3440bc96417f9cef4e5bd27f7b8bbdb04dbc4f0c Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 17 Jul 2022 09:44:09 -0700 Subject: [PATCH 36/66] remove call to curses.subpad() in python scanner --- examples_linux/scanner.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 649e42dd2..36e127615 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -147,14 +147,12 @@ def main(): print(f"Scanning for {duration} seconds at {OFFERED_DATA_RATES[data_rate]}") init_radio() radio.setDataRate(AVAILABLE_RATES[data_rate]) - scanner_output_window = None try: std_scr = init_curses() timer_prompt = "Scanning for {} seconds at " + OFFERED_DATA_RATES[data_rate] std_scr.addstr(0, 0, "Channels are labeled in MHz.") std_scr.addstr(1, 0, "Signal counts are clamped to a single hexadecimal digit.") - scanner_output_window = std_scr.subpad(26, curses.COLS, 0, 0) - table = init_interface(scanner_output_window) + table = init_interface(std_scr) channel, val = (0, False) end = time.monotonic() + duration while time.monotonic() < end: @@ -164,7 +162,7 @@ def main(): totals[channel] += val if totals[channel]: table[channel].update(history[channel].count(True), totals[channel]) - scanner_output_window.refresh() + std_scr.refresh() if channel + 1 == TOTAL_CHANNELS: channel = 0 spectrum_pass[0] += 1 From 56b2f55b0295bfcae27f3d3ba0d502df10f65ff0 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 17 Jul 2022 09:48:42 -0700 Subject: [PATCH 37/66] remove use of queue in C++ scanner - show noise ratio of channel out of total passes on spectrum - spelling fixes and rename a couple functions --- examples_linux/ncurses/scanner_curses.cpp | 73 ++++++++++------------- 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 922749371..ec1cf592a 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -8,8 +8,7 @@ #include // sprintf() #include // string, getline() #include // time_t, time(), difftime() -#include // cout, endl, flush, cin -#include +#include // cout, endl, flush, cin, setprecision() #include #include @@ -31,11 +30,8 @@ RF24 radio(22, 0); // Channel info const uint8_t MAX_CHANNELS = 126; // 0-125 are supported unsigned int values[MAX_CHANNELS] = {0}; // the array to store summary of signal counts per channel -const int CACHE_MAX = 5; // maximum depth of history for calculating peaks per channel -queue history[MAX_CHANNELS]; // a cache of history for each channel - -// we'll need a container to act as a buffer during history init and counting historic signals -queue temp = queue({0, 0, 0, 0, 0}); +const uint8_t CACHE_MAX = 5; // maximum depth of history for calculating peaks per channel +bool history[MAX_CHANNELS][CACHE_MAX]; // a cache of history for each channel // To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). // These addresses are designed to confuse the radio into thinking @@ -51,9 +47,9 @@ WINDOW* win; // curses base window object uint8_t initRadio(); void initCurses(); void deinitCurses(); -void initContainers(); +void initArrays(); bool scanChannel(uint8_t); -int countHistory(uint8_t index); +int historyPush(uint8_t index, bool value); class ProgressBar { @@ -120,7 +116,7 @@ int main(int argc, char** argv) // create out interface initCurses(); - initContainers(); + initArrays(); mvaddstr(0, 0, "Channels are labeled in MHz."); mvaddstr(1, 0, "Signal counts are clamped to a single hexadecimal digit."); @@ -135,14 +131,11 @@ int main(int argc, char** argv) bpsUnit); bool foundSignal = scanChannel(i); - history[i].pop(); - history[i].push(foundSignal); + int cachedCount = historyPush(i, foundSignal); // output the summary/snapshot for this channel if (values[i]) { - int cachedCount = countHistory(i); - - // Print changes to screen + // make changes to the screen table[i]->update(cachedCount, rf24_min(values[i], 0xF)); } @@ -253,26 +246,35 @@ void deinitCurses() for (uint8_t i = 0; i < MAX_CHANNELS; ++i) { if (values[i]) { active_channels++; + float noiseRatio = static_cast(values[i]) / passesCount; cout << " " << static_cast(i) << ": " << static_cast(values[i]) + << " / " + << passesCount + << " (" + << setprecision(3) + << noiseRatio + << " %)" << endl; } } cout << static_cast(active_channels) - << " channels deteted signals after " + << " channels detected signals after " << passesCount << " passes." << endl; } -/** init the history and progress bars */ -void initContainers() +/** init arrays for the history and progress bars */ +void initArrays() { // fill our history with blanks for (uint8_t i = 0; i < MAX_CHANNELS; ++i) { - history[i] = queue(temp); + for (uint8_t j = 0; j < CACHE_MAX; ++j) { + history[i][j] = false; + } } // init our progress bars @@ -293,30 +295,19 @@ void initContainers() } } -/** count the number of historic signals (limited to CACHE_MAX) */ -int countHistory(uint8_t index) +/** + * Push new scan result for a channel into the history. + * @returns The count of historic signals found (including pushed result) + */ +uint8_t historyPush(uint8_t index, bool value) { - // TODO: there must be a better way to implement a FIFO and - // count the exact same values contained within. - - int sum = 0; - // clear the temp buffer - while (!temp.empty()) temp.pop(); - - // move values of history into temp buffer, and count the historic signals - while (!history[index].empty()) { - temp.push(history[index].front()); - sum += static_cast(history[index].front()); - history[index].pop(); + uint8_t sum = 0; + for (uint8_t i = 0; i < CACHE_MAX - 1; ++i) { + history[index][i] = history[index][i + 1]; + sum += history[index][i]; } - - // move values from buffer back into history - while (!temp.empty()) { - history[index].push(temp.front()); - temp.pop(); - } - - return sum; + history[index][CACHE_MAX - 1] = value; + return sum + value; } // vim:ai:cin:sts=2 sw=2 ft=cpp From b14cd0c73429fcbfbbf68504b9424386ee99c7cb Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 17 Jul 2022 09:52:50 -0700 Subject: [PATCH 38/66] introducing a rough draft of Arduino scannerGraphic - supports 2 types of displays (& optional Serial debugginng) 1. SPI display (uses same bus as radio) is tested with an ST7789 (https://www.adafruit.com/product/4383) 2. I2C display is tested with SSd1306 (https://www.adafruit.com/product/326) Use defines to toggle debugging and EITHER (not both) of the displays. --- examples/scannerGraphic/scannerGraphic.ino | 299 +++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 examples/scannerGraphic/scannerGraphic.ino diff --git a/examples/scannerGraphic/scannerGraphic.ino b/examples/scannerGraphic/scannerGraphic.ino new file mode 100644 index 000000000..1f7863e28 --- /dev/null +++ b/examples/scannerGraphic/scannerGraphic.ino @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2022 Brendan Doherty <2bndy5@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ +#include "RF24.h" +#include + + +/* ******************************************************************* + * Instantiate the radio and app-specific attributes + * *******************************************************************/ + +// Set up nRF24L01 radio on SPI bus using pins 7 (for CE) & 8 (for CSN) +RF24 radio(0, 1); + +// To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). +// These addresses are designed to confuse the radio into thinking +// that the RF signal's preamble is part of the packet/payload. +const uint8_t noiseAddress[][2] = { { 0x55, 0x55 }, { 0xAA, 0xAA }, { 0xA0, 0xAA }, { 0xAB, 0xAA }, { 0xAC, 0xAA }, { 0xAD, 0xAA } }; + +// keep a cache of history for peak decay +const uint8_t CACHE_MAX = 6; +const uint8_t num_channels = 126; // 0-125 are supported +bool history[num_channels][CACHE_MAX]; + +/******************************************************************** + * Instantiate the appropriate display + ********************************************************************/ + +// uncomment either or none of the following: + +// #define I2C_DISPLAY // using the SSD1306 +#define SPI_DISPLAY // using ST7789 + +// create our `display` objects according to the above defines +#ifdef I2C_DISPLAY + +#include +#include + +#define SCREEN_WIDTH 128 // OLED display width, in pixels +#define SCREEN_HEIGHT 64 // OLED display height, in pixels + +// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) +// The pins for I2C are defined by the Wire-library. +// On an arduino UNO: A4(SDA), A5(SCL) +// On an arduino MEGA 2560: 20(SDA), 21(SCL) +// On an arduino LEONARDO: 2(SDA), 3(SCL), ... +#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) +#define SCREEN_ADDRESS 0x3D ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); + +#define BLACK SSD1306_BLACK +#define WHITE SSD1306_WHITE +#define REFRESH ({ display.display(); }) +#define CLEAR_DISPLAY ({ display.clearDisplay(); }) +#define X_OFFSET 0 + +#elif defined(SPI_DISPLAY) + +#include // Hardware-specific library for ST7789 + +#define TFT_CS 2 +#define TFT_RST -1 // Or set to -1 and connect to Arduino RESET pin +#define TFT_DC 3 + +#define SCREEN_WIDTH 240 // TFT display width, in pixels +#define SCREEN_HEIGHT 135 // TFT display height, in pixels + +// For 1.14", 1.3", 1.54", 1.69", and 2.0" TFT with ST7789: +Adafruit_ST7789 display = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); + +#define BLACK ST77XX_BLACK +#define WHITE ST77XX_WHITE +#define REFRESH +#define CLEAR_DISPLAY ({ display.fillScreen(BLACK); }) +#define X_OFFSET (SCREEN_WIDTH - 128) / 2 + +#endif // if defined(I2C_DISPLAY) || defined(SPI_DISPLAY) + +/******************************************************************** + * Configure debugging on Serial output + ********************************************************************/ + +// #define DEBUGGING // uncomment to enable Serial output + +#ifdef DEBUGGING +#include "printf.h" +#define SERIAL_DEBUG(x) ({ x; }) + +// Using "\r" is meant for console apps that connect to USB serial ports (SimplySerial on Windows) +#define RETURN_FEED "\r" // change this to "\n" if using the Serial Monitor in Arduino IDE + +#else +#define SERIAL_DEBUG(x) +#endif + +/******************************************************************** + * Setup the app + ********************************************************************/ +void setup(void) { + +#ifdef DEBUGGING + // Print preamble + Serial.begin(115200); + while (!Serial) { + // some boards need this to wait for Serial connection + } + Serial.println(F("RF24/examples/recipes/scanner/")); +#endif + +#ifdef I2C_DISPLAY + // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally + if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { + SERIAL_DEBUG(Serial.println(F("SSD1306 allocation failed"));); + while (true) { + // Don't proceed, loop forever + } + } +#elif defined(SPI_DISPLAY) + // use this initializer for a 1.14" 240x135 TFT: + display.init(SCREEN_HEIGHT, SCREEN_WIDTH); // Init ST7789 240x135 + display.setRotation(1); +#endif + + // Clear the buffer + CLEAR_DISPLAY; + displayChartAxis(); + + // Setup and configure rf radio + if (!radio.begin()) { + SERIAL_DEBUG(Serial.println(F("radio hardware not responding!"));); + while (true) { + // hold in an infinite loop + } + } + radio.setAutoAck(false); // Don't acknowledge arbitrary signals + radio.disableCRC(); // accept any signal we find + radio.setAddressWidth(2); // a reverse engineering tactic (not typically recommended) + radio.openReadingPipe(0, noiseAddress[0]); + radio.openReadingPipe(1, noiseAddress[1]); + radio.openReadingPipe(2, noiseAddress[2]); + radio.openReadingPipe(3, noiseAddress[3]); + radio.openReadingPipe(4, noiseAddress[4]); + radio.openReadingPipe(5, noiseAddress[5]); + + // set the data rate +#ifdef DEBUGGING + unsigned long inputTimeout = millis() + 7000; + Serial.print(F("Select your Data Rate. ")); + Serial.println(F("Enter '1' for 1Mbps, '2' for 2Mbps, '3' for 250kbps. Defaults to 1 Mbps.")); + while (!Serial.available() && millis() < inputTimeout) { + // Wait for user input. Timeout after 7 seconds. + } + uint8_t dataRate = !Serial.available() ? '1' : Serial.read(); +#else + uint8_t dataRate = '1'; +#endif + if (dataRate == '2') { + SERIAL_DEBUG(Serial.println(F("Using 2 Mbps."));); + radio.setDataRate(RF24_2MBPS); + } else if (dataRate == '3') { + SERIAL_DEBUG(Serial.println(F("Using 250 kbps."));); + radio.setDataRate(RF24_250KBPS); + } else { // dataRate == '1' or invalid values + SERIAL_DEBUG(Serial.println(F("Using 1 Mbps."));); + radio.setDataRate(RF24_1MBPS); + } + + // Get into standby mode + radio.startListening(); + radio.stopListening(); + radio.flush_rx(); + + // initialize our history for showing cumulative peaks + for (uint8_t i = 0; i < num_channels; ++i) { + for (uint8_t j = 0; j < CACHE_MAX; ++j) { + history[i][j] = false; + } + } +#ifdef DEBUGGING + printHeader(); +#endif +} + +/******************************************************************** + * Make the app loop forever + ********************************************************************/ +void loop(void) { + // Print out channel measurements, clamped to a single hex digit + for (int i = 0; i < num_channels; ++i) { + bool foundSignal = scanChannel(i); + // uint8_t chart + uint8_t historicCount = historyPush(i, foundSignal); + uint8_t x = i + 1 + X_OFFSET; + uint8_t chartHeight = SCREEN_HEIGHT - 10; + display.drawLine(x, 0, x, chartHeight, BLACK); + if (!historicCount) { + SERIAL_DEBUG(Serial.print("-");); + } else { + uint8_t barHeight = chartHeight * historicCount / CACHE_MAX; + display.drawLine(x, SCREEN_HEIGHT - 10, x, SCREEN_HEIGHT - 10 - barHeight, WHITE); + SERIAL_DEBUG(Serial.print(historicCount);); + } + } + REFRESH; + SERIAL_DEBUG(Serial.print(RETURN_FEED);); +} // end loop() + +/******************************************************************** + * Scan a specified channel and return the resulting flag + ********************************************************************/ +bool scanChannel(uint8_t channel) { + radio.setChannel(channel); + + // Listen for a little + radio.startListening(); + delayMicroseconds(130); + bool foundSignal = radio.testRPD(); + radio.stopListening(); + + // Did we get a signal? + if (foundSignal || radio.testRPD() || radio.available()) { + radio.flush_rx(); // discard packets of noise + return true; + } + return false; +} + +/******************************************************************** + * Push new scan result for a channel into the history. + * @returns The count of historic signals found (including pushed result) + ********************************************************************/ +uint8_t historyPush(uint8_t index, bool value) { + uint8_t sum = 0; + for (uint8_t i = 0; i < CACHE_MAX - 1; ++i) { + history[index][i] = history[index][i + 1]; + sum += history[index][i]; + } + history[index][CACHE_MAX - 1] = value; + return sum + value; +} + +/******************************************************************** + * A debugging function for printing the channels' header + ********************************************************************/ +void printHeader() { + // Print the hundreds digits + for (int i = 0; i < num_channels; ++i) + Serial.print(i / 100); + Serial.println(); + + // Print the tens digits + for (int i = 0; i < num_channels; ++i) + Serial.print((i % 100) / 10); + Serial.println(); + + // Print the singles digits + for (int i = 0; i < num_channels; ++i) + Serial.print(i % 10); + Serial.println(); + + // Print the header's divider + for (int i = 0; i < num_channels; ++i) + Serial.print(F("~")); + Serial.println(); +} + +/******************************************************************** + * Draw the chart axis and labels + ********************************************************************/ +void displayChartAxis() { + // draw base line + display.drawLine(X_OFFSET, SCREEN_HEIGHT - 8, X_OFFSET + 127, SCREEN_HEIGHT - 8, WHITE); + + // draw base line border + display.drawLine(X_OFFSET, SCREEN_HEIGHT - 1, X_OFFSET, SCREEN_HEIGHT - 12, WHITE); + display.drawLine(X_OFFSET + 127, SCREEN_HEIGHT - 1, X_OFFSET + 127, SCREEN_HEIGHT - 12, WHITE); + + // draw scalar marks + for (uint8_t i = 0; i < CACHE_MAX; ++i) { + uint8_t scalarHeight = (SCREEN_HEIGHT - 10) * i / CACHE_MAX; + display.drawLine(X_OFFSET, scalarHeight, SCREEN_WIDTH - X_OFFSET, scalarHeight, WHITE); + } + + // draw channel range labels + display.setTextSize(1); + display.setTextColor(WHITE); + display.setCursor(SCREEN_WIDTH - X_OFFSET - 21, SCREEN_HEIGHT - 7); + display.print(125); + display.setCursor(X_OFFSET + 2, SCREEN_HEIGHT - 7); + display.print(0); + + // refresh display + REFRESH; +} \ No newline at end of file From 690b4ed98c833c40324862d4749b4fa48ede90b7 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 17 Jul 2022 10:00:16 -0700 Subject: [PATCH 39/66] ran clang-format --- examples_linux/scanner.cpp | 2 +- examples_pico/scanner.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index 0618ff8f8..5560066ad 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -73,7 +73,7 @@ uint8_t values[num_channels]; // the array to store summary of signal counts // To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). // These addresses are designed to confuse the radio into thinking // that the RF signal's preamble is part of the packet/payload. -const uint8_t noiseAddress[][6] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x0A, 0xAA}, {0xA0, 0xAA}, {0x00,0xAA}, {0xAB,0xAA}}; +const uint8_t noiseAddress[][6] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x0A, 0xAA}, {0xA0, 0xAA}, {0x00, 0xAA}, {0xAB, 0xAA}}; const int num_reps = 100; // number of passes for each scan of the entire spectrum diff --git a/examples_pico/scanner.cpp b/examples_pico/scanner.cpp index 46fb6ef57..1eaf7f91a 100644 --- a/examples_pico/scanner.cpp +++ b/examples_pico/scanner.cpp @@ -58,7 +58,7 @@ uint8_t values[num_channels]; // the array to store summary of signal counts // To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). // These addresses are designed to confuse the radio into thinking // that the RF signal's preamble is part of the packet/payload. -const uint8_t noiseAddress[][6] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x0A, 0xAA}, {0xA0, 0xAA}, {0x00,0xAA}, {0xAB,0xAA}}; +const uint8_t noiseAddress[][6] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x0A, 0xAA}, {0xA0, 0xAA}, {0x00, 0xAA}, {0xAB, 0xAA}}; const int num_reps = 100; // number of passes for each scan of the entire spectrum From 9ed9011060741752f28a092dee144012e149332d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 17 Jul 2022 10:08:17 -0700 Subject: [PATCH 40/66] fix and cast datatype for recent change in C++ curses scanner --- examples_linux/ncurses/scanner_curses.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index ec1cf592a..6454c9059 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -49,7 +49,7 @@ void initCurses(); void deinitCurses(); void initArrays(); bool scanChannel(uint8_t); -int historyPush(uint8_t index, bool value); +uint8_t historyPush(uint8_t index, bool value); class ProgressBar { @@ -131,12 +131,12 @@ int main(int argc, char** argv) bpsUnit); bool foundSignal = scanChannel(i); - int cachedCount = historyPush(i, foundSignal); + uint8_t cachedCount = historyPush(i, foundSignal); // output the summary/snapshot for this channel if (values[i]) { // make changes to the screen - table[i]->update(cachedCount, rf24_min(values[i], 0xF)); + table[i]->update(static_cast(cachedCount), rf24_min(values[i], 0xF)); } refresh(); From b47d377a4953a0d4eb1396451c9e142090836fbf Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 17 Jul 2022 10:10:45 -0700 Subject: [PATCH 41/66] include iomanip for `setprecision` --- examples_linux/ncurses/scanner_curses.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 6454c9059..89c538a7a 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -8,7 +8,8 @@ #include // sprintf() #include // string, getline() #include // time_t, time(), difftime() -#include // cout, endl, flush, cin, setprecision() +#include // cout, endl, flush, cin +#include // setprecision() #include #include From 2c4aaeca0367838ca413d0ce9454fe78ca22d4b1 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 18 Sep 2022 08:57:25 -0700 Subject: [PATCH 42/66] ran clang-format on linux ncurses scanner example --- examples_linux/ncurses/scanner_curses.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 89c538a7a..772cd836c 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -111,7 +111,8 @@ int main(int argc, char** argv) getline(cin, input); if (input.length()) { duration = stoi(input); - if (!duration) input.clear(); + if (!duration) + input.clear(); } } From 5a89b9c9254f1827269228cbcc5d8cdfabcdef7d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 3 Dec 2023 08:31:50 -0800 Subject: [PATCH 43/66] change filled bar char for better visual distinction --- examples_linux/ncurses/scanner_curses.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 772cd836c..9df42ffcf 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -77,9 +77,10 @@ class ProgressBar int offset_x = 5; move(y, x + offset_x); for (int i = offset_x; i < w - 3; ++i) { - int bar_color = i < (filled + offset_x) ? 5 : color; + bool bar_filled = i < (filled + offset_x); + int bar_color = bar_filled ? 5 : color; attron(COLOR_PAIR(bar_color)); - addch(ACS_HLINE); + addch(bar_filled ? '=' : ACS_HLINE); attroff(COLOR_PAIR(bar_color)); } attron(COLOR_PAIR(color)); From 3cb8324dd67e7501d873fced75a6192cbe4e38c2 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 3 Mar 2024 05:58:01 -0800 Subject: [PATCH 44/66] reviewed examples - added intro comments - use structs for caching channel history/results in ncurses examples (py & cpp) - fixed gitignore about changes staged for sub-folders of examples_linux - moved cmake script into relevant CMakeLists.txt - fix dead link in text-based scanner examples' intro comment - use a fixed width for summary printed at ncurses example exit (& fix percent calculation) --- .gitignore | 3 +- cmake/enableNcursesExample.cmake | 15 ---- examples/scanner/scanner.ino | 21 +++--- examples/scannerGraphic/scannerGraphic.ino | 59 +++++++++++----- examples_linux/CMakeLists.txt | 5 +- examples_linux/ncurses/CMakeLists.txt | 10 ++- examples_linux/ncurses/scanner_curses.cpp | 81 ++++++++++++---------- examples_linux/scanner.cpp | 23 +++--- examples_linux/scanner.py | 68 +++++++++++------- 9 files changed, 161 insertions(+), 124 deletions(-) delete mode 100644 cmake/enableNcursesExample.cmake diff --git a/.gitignore b/.gitignore index 783d309a4..11a99decf 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,9 @@ Session.vim .DS_Store Makefile.inc utility/includes.h +!examples_linux/ examples_linux/* -examples_linux/**/* +!examples_linux/**/ !examples_linux/*.cpp !examples_linux/*.py !examples_linux/**/*.cpp diff --git a/cmake/enableNcursesExample.cmake b/cmake/enableNcursesExample.cmake deleted file mode 100644 index b016787de..000000000 --- a/cmake/enableNcursesExample.cmake +++ /dev/null @@ -1,15 +0,0 @@ -find_package(Curses) -if(Curses_FOUND) - include_directories(${CURSES_INCLUDE_DIR}) - option(BUILD_NCURSES_EXAMPLE - "Enable/Disable building the ncurses example (requires libncurses5-dev installed)" - ON - ) -else() - message(STATUS "libncurses5-dev not found. Skipping ncurses example") - option(BUILD_NCURSES_EXAMPLE - "Enable/Disable building the ncurses example (requires libncurses5-dev installed)" - OFF - ) -endif() -message(STATUS "BUILD_NCURSES_EXAMPLE set to ${BUILD_NCURSES_EXAMPLE}") diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index 80b00e19b..a69e8084e 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -14,12 +14,14 @@ * This is a good diagnostic tool to check whether you're picking a * good channel for your application. * - * Run this sketch on two devices. On one device, start CCW output by sending a 'g' - * character over Serial. The other device scanning should detect the output of the sending - * device on the given channel. Adjust channel and output power of CCW below. + * Run this sketch on two devices. On one device, start emitting a constant carrier wave + * by sending a channel number in the Serial Monitor. The other device scanning should + * detect the constant carrier wave from the sending device on the given channel. + * Send a negative number in the Serial Monitor to stop emitting a constant carrier wave + * and resume scanning. * * Inspired by cpixip. - * See http://arduino.cc/forum/index.php/topic,54795.0.html + * See https://forum.arduino.cc/t/poor-mans-2-4-ghz-scanner/54846 */ /* @@ -64,7 +66,7 @@ uint8_t values[num_channels]; // the array to store summary of signal count // To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). // These addresses are designed to confuse the radio into thinking // that the RF signal's preamble is part of the packet/payload. -const uint8_t noiseAddress[][2] = { { 0x55, 0x55 }, { 0xAA, 0xAA } }; +const uint8_t noiseAddress[][2] = { { 0x55, 0x55 }, { 0xAA, 0xAA }, { 0xA0, 0xAA }, { 0xAB, 0xAA }, { 0xAC, 0xAA }, { 0xAD, 0xAA } }; const int num_reps = 100; // number of passes for each scan of the entire spectrum bool constCarrierMode = 0; // this flag controls example behavior (scan mode is default) @@ -92,8 +94,9 @@ void setup(void) { radio.setAutoAck(false); // Don't acknowledge arbitrary signals radio.disableCRC(); // Accept any signal we find radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) - radio.openReadingPipe(0, noiseAddress[0]); - radio.openReadingPipe(1, noiseAddress[1]); + for (uint8_t i = 0; i < 6; ++i) { + radio.openReadingPipe(i, noiseAddress[i]); + } // set the data rate Serial.print(F("Select your Data Rate. ")); @@ -102,7 +105,7 @@ void setup(void) { while (!Serial.available()) { // wait for user input } - uint8_t dataRate = Serial.read(); + uint8_t dataRate = Serial.parseInt(); if (dataRate == 50) { Serial.println(F("Using 2 Mbps.")); radio.setDataRate(RF24_2MBPS); @@ -134,7 +137,7 @@ void loop(void) { // Send a number over Serial to begin Constant Carrier Wave output // Configure the power amplitude level below if (Serial.available()) { - int c = Serial.read(); + uint8_t c = Serial.parseInt(); if (c >= 0) { c = min(125, max(0, c)); // clamp channel to supported range constCarrierMode = 1; diff --git a/examples/scannerGraphic/scannerGraphic.ino b/examples/scannerGraphic/scannerGraphic.ino index 1f7863e28..c8af59d5a 100644 --- a/examples/scannerGraphic/scannerGraphic.ino +++ b/examples/scannerGraphic/scannerGraphic.ino @@ -5,13 +5,39 @@ * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. */ + +/* + * This example uses 1 of 2 different popular displays. To control which display to use, + * comment/uncomment the lines below that define + * - `SPI_DISPLAY`: This requires the "Adafruit ST7735 and ST7789 Library" installed + * - `I2C_DISPLAY`: This requires the "Adafruit SSD1306" library installed + * + * Use the Arduino Library manager to ensure the required libraries are installed. + * By default, this sketch uses the SPI_DISPLAY (ST7789). Using both displays at the same + * time is not supported by this sketch. + * + * NOTES: + * The `SCREEN_HEIGHT` and `SCREEN_WIDTH` defines may need to be adjusted according + * to your display module's capability. + * + * The SPI_DISPLAY ues its own pins defined by `TFT_CS`, `TFT_DC`, and the + * optional `TFT_RST` (see below). The SPI bus is shared between radio and display, + * so the display's CS pin must be connected as specified by `TFT_CS`. + * If your ST7789 display does not have a CS pin, then further modification must + * be made so it does not use the same SPI bus that the radio uses. + * + * `DEBUGGING` can be enabled (uncommented) to show Serial output. This is just a + * convenience for troubleshooting and advanced development. See our other + * RF24/scanner example that only uses the Serial Monitor instead of a graphic + * display. + */ +#include // dependency of Adafruit display libraries #include "RF24.h" -#include -/* ******************************************************************* +/******************************************************************** * Instantiate the radio and app-specific attributes - * *******************************************************************/ + ********************************************************************/ // Set up nRF24L01 radio on SPI bus using pins 7 (for CE) & 8 (for CSN) RF24 radio(0, 1); @@ -30,7 +56,7 @@ bool history[num_channels][CACHE_MAX]; * Instantiate the appropriate display ********************************************************************/ -// uncomment either or none of the following: +// uncomment either or none of the following to use the desired display: // #define I2C_DISPLAY // using the SSD1306 #define SPI_DISPLAY // using ST7789 @@ -50,7 +76,7 @@ bool history[num_channels][CACHE_MAX]; // On an arduino MEGA 2560: 20(SDA), 21(SCL) // On an arduino LEONARDO: 2(SDA), 3(SCL), ... #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) -#define SCREEN_ADDRESS 0x3D ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 +#define SCREEN_ADDRESS 0x3D // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); #define BLACK SSD1306_BLACK @@ -109,7 +135,7 @@ void setup(void) { while (!Serial) { // some boards need this to wait for Serial connection } - Serial.println(F("RF24/examples/recipes/scanner/")); + Serial.println(F("RF24/examples/scannerGraphic/")); #endif #ifdef I2C_DISPLAY @@ -140,12 +166,9 @@ void setup(void) { radio.setAutoAck(false); // Don't acknowledge arbitrary signals radio.disableCRC(); // accept any signal we find radio.setAddressWidth(2); // a reverse engineering tactic (not typically recommended) - radio.openReadingPipe(0, noiseAddress[0]); - radio.openReadingPipe(1, noiseAddress[1]); - radio.openReadingPipe(2, noiseAddress[2]); - radio.openReadingPipe(3, noiseAddress[3]); - radio.openReadingPipe(4, noiseAddress[4]); - radio.openReadingPipe(5, noiseAddress[5]); + for (uint8_t i = 0; i < 6; ++i) { + radio.openReadingPipe(i, noiseAddress[i]); + } // set the data rate #ifdef DEBUGGING @@ -155,9 +178,9 @@ void setup(void) { while (!Serial.available() && millis() < inputTimeout) { // Wait for user input. Timeout after 7 seconds. } - uint8_t dataRate = !Serial.available() ? '1' : Serial.read(); + char dataRate = !Serial.available() ? '1' : Serial.parseInt(); #else - uint8_t dataRate = '1'; + char dataRate = '1'; #endif if (dataRate == '2') { SERIAL_DEBUG(Serial.println(F("Using 2 Mbps."));); @@ -234,13 +257,13 @@ bool scanChannel(uint8_t channel) { * Push new scan result for a channel into the history. * @returns The count of historic signals found (including pushed result) ********************************************************************/ -uint8_t historyPush(uint8_t index, bool value) { +uint8_t historyPush(uint8_t channel, bool value) { uint8_t sum = 0; for (uint8_t i = 0; i < CACHE_MAX - 1; ++i) { - history[index][i] = history[index][i + 1]; - sum += history[index][i]; + history[channel][i] = history[channel][i + 1]; + sum += history[channel][i]; } - history[index][CACHE_MAX - 1] = value; + history[channel][CACHE_MAX - 1] = value; return sum + value; } diff --git a/examples_linux/CMakeLists.txt b/examples_linux/CMakeLists.txt index b66512f27..eb8421042 100644 --- a/examples_linux/CMakeLists.txt +++ b/examples_linux/CMakeLists.txt @@ -76,7 +76,4 @@ foreach(example ${EXAMPLES_LIST}) target_link_libraries(${example} PUBLIC ${linked_libs}) endforeach() -include(../cmake/enableNcursesExample.cmake) -if(BUILD_NCURSES_EXAMPLE) - add_subdirectory(ncurses) -endif() +add_subdirectory(ncurses) diff --git a/examples_linux/ncurses/CMakeLists.txt b/examples_linux/ncurses/CMakeLists.txt index 2541c3bc7..d7718e1b4 100644 --- a/examples_linux/ncurses/CMakeLists.txt +++ b/examples_linux/ncurses/CMakeLists.txt @@ -1,6 +1,12 @@ # this example needs the ncurses package installed -find_package(Curses REQUIRED) -include_directories(${CURSES_INCLUDE_DIR}) +find_package(Curses) +if(Curses_FOUND) + message(STATUS "Including ncurses example") + include_directories(${CURSES_INCLUDE_DIR}) +else() + message(STATUS "libncurses5-dev not found. Skipping ncurses example") + return() +endif() set(example scanner_curses) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 9df42ffcf..eac690e24 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -29,10 +29,15 @@ RF24 radio(22, 0); // See https://www.kernel.org/doc/Documentation/spi/spidev for more information on SPIDEV // Channel info -const uint8_t MAX_CHANNELS = 126; // 0-125 are supported -unsigned int values[MAX_CHANNELS] = {0}; // the array to store summary of signal counts per channel -const uint8_t CACHE_MAX = 5; // maximum depth of history for calculating peaks per channel -bool history[MAX_CHANNELS][CACHE_MAX]; // a cache of history for each channel +const uint8_t MAX_CHANNELS = 126; // 0-125 are supported +const uint8_t CACHE_MAX = 5; // maximum depth of history for calculating peaks per channel + +struct ChannelHistory +{ + unsigned int total = 0; // the summary of signal counts for the channel + bool history[CACHE_MAX]; // a cache of history for the channel +}; +ChannelHistory stored[MAX_CHANNELS]; // To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). // These addresses are designed to confuse the radio into thinking @@ -48,7 +53,7 @@ WINDOW* win; // curses base window object uint8_t initRadio(); void initCurses(); void deinitCurses(); -void initArrays(); +void initBars(); bool scanChannel(uint8_t); uint8_t historyPush(uint8_t index, bool value); @@ -119,11 +124,11 @@ int main(int argc, char** argv) // create out interface initCurses(); - initArrays(); + initBars(); mvaddstr(0, 0, "Channels are labeled in MHz."); mvaddstr(1, 0, "Signal counts are clamped to a single hexadecimal digit."); - uint8_t i = 0; + uint8_t channel = 0; time_t start = time(nullptr); while (static_cast(difftime(time(nullptr), start)) < duration) { mvprintw(2, @@ -133,22 +138,22 @@ int main(int argc, char** argv) static_cast(d_rate), bpsUnit); - bool foundSignal = scanChannel(i); - uint8_t cachedCount = historyPush(i, foundSignal); + bool foundSignal = scanChannel(channel); + uint8_t cachedCount = historyPush(channel, foundSignal); // output the summary/snapshot for this channel - if (values[i]) { + if (stored[channel].total) { // make changes to the screen - table[i]->update(static_cast(cachedCount), rf24_min(values[i], 0xF)); + table[channel]->update(static_cast(cachedCount), rf24_min(stored[channel].total, 0xF)); } refresh(); - if (i + 1 == MAX_CHANNELS) { - i = 0; + if (channel + 1 == MAX_CHANNELS) { + channel = 0; ++passesCount; } else { - ++i; + ++channel; } } @@ -186,12 +191,9 @@ uint8_t initRadio() radio.setAutoAck(false); // Don't acknowledge arbitrary signals radio.disableCRC(); // Accept any signal we find radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) - radio.openReadingPipe(0, noiseAddress[0]); - radio.openReadingPipe(1, noiseAddress[1]); - radio.openReadingPipe(2, noiseAddress[2]); - radio.openReadingPipe(3, noiseAddress[3]); - radio.openReadingPipe(4, noiseAddress[4]); - radio.openReadingPipe(5, noiseAddress[5]); + for (uint8_t i = 0; i < 6; ++i) { + radio.openReadingPipe(i, noiseAddress[i]); + } // Get into standby mode radio.startListening(); @@ -217,7 +219,7 @@ bool scanChannel(uint8_t channel) // Did we get a signal? if (foundSignal || radio.testRPD() || radio.available()) { - ++values[channel]; + ++stored[channel].total; radio.flush_rx(); // discard packets of noise return true; } @@ -246,14 +248,24 @@ void deinitCurses() // print out the total signal counts (if any) uint8_t active_channels = 0; // the sum of channels with detected noise - for (uint8_t i = 0; i < MAX_CHANNELS; ++i) { - if (values[i]) { + uint8_t digitW = 0; + unsigned int tmp = passesCount; + while (tmp) { + digitW += 1; + tmp /= 10; + } + + for (uint8_t channel = 0; channel < MAX_CHANNELS; ++channel) { + if (stored[channel].total) { active_channels++; - float noiseRatio = static_cast(values[i]) / passesCount; + float noiseRatio = static_cast(stored[channel].total) / passesCount * 100; cout << " " - << static_cast(i) + << setfill(' ') + << setw(3) + << static_cast(channel) << ": " - << static_cast(values[i]) + << setw(digitW) + << static_cast(stored[channel].total) << " / " << passesCount << " (" @@ -271,15 +283,8 @@ void deinitCurses() } /** init arrays for the history and progress bars */ -void initArrays() +void initBars() { - // fill our history with blanks - for (uint8_t i = 0; i < MAX_CHANNELS; ++i) { - for (uint8_t j = 0; j < CACHE_MAX; ++j) { - history[i][j] = false; - } - } - // init our progress bars int bar_w = COLS / 6; // total progress bar width (including all contents) @@ -302,14 +307,14 @@ void initArrays() * Push new scan result for a channel into the history. * @returns The count of historic signals found (including pushed result) */ -uint8_t historyPush(uint8_t index, bool value) +uint8_t historyPush(uint8_t channel, bool value) { uint8_t sum = 0; for (uint8_t i = 0; i < CACHE_MAX - 1; ++i) { - history[index][i] = history[index][i + 1]; - sum += history[index][i]; + stored[channel].history[i] = stored[channel].history[i + 1]; + sum += stored[channel].history[i]; } - history[index][CACHE_MAX - 1] = value; + stored[channel].history[CACHE_MAX - 1] = value; return sum + value; } diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index 5560066ad..ce68b92ea 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -9,7 +9,7 @@ 03/17/2013 : Charles-Henri Hallard (http://hallard.me) Modified to use with Arduipi board http://hallard.me/arduipi Changed to use modified bcm2835 and RF24 library - + 07/12/2022: Modified to be more promiscuous using reverse engineering tactics. */ /** @@ -20,7 +20,7 @@ * good channel for your application. * * Inspired by cpixip. - * See http://arduino.cc/forum/index.php/topic,54795.0.html + * See https://forum.arduino.cc/t/poor-mans-2-4-ghz-scanner/54846 */ /* @@ -90,6 +90,11 @@ int main(int argc, char** argv) return 1; } + // print a line that should not be wrapped + cout << "\n!!! This example requires a width of at least 126 characters. " + << "If this text uses multiple lines, then the output will look bad.\n" + << endl; + // set the data rate cout << "Select your Data Rate. " << "Enter '1' for 1 Mbps, '2' for 2 Mbps, '3' for 250 kbps. " @@ -114,23 +119,15 @@ int main(int argc, char** argv) radio.setAutoAck(false); // Don't acknowledge arbitrary signals radio.disableCRC(); // Accept any signal we find radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) - radio.openReadingPipe(0, noiseAddress[0]); - radio.openReadingPipe(1, noiseAddress[1]); - radio.openReadingPipe(2, noiseAddress[2]); - radio.openReadingPipe(3, noiseAddress[3]); - radio.openReadingPipe(4, noiseAddress[4]); - radio.openReadingPipe(5, noiseAddress[5]); + for (uint8_t i = 0; i < 6; ++i) { + radio.openReadingPipe(i, noiseAddress[i]); + } // Get into standby mode radio.startListening(); radio.stopListening(); radio.flush_rx(); // radio.printPrettyDetails(); - - // print a line that should not be wrapped - cout << "\n!!! This example requires a width of at least 126 characters. " - << "If this text uses multiple lines, then the output will look bad.\n" - << endl; // print the vertical header printHeader(); diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 36e127615..7cdacdba4 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -1,4 +1,5 @@ -"""A scanner example written in python using the std lib's ncurses wrapper""" +"""A scanner example written in python using the std lib's ncurses wrapper.""" + # pylint: disable=no-member import curses import time @@ -15,9 +16,19 @@ AVAILABLE_RATES = [RF24_1MBPS, RF24_2MBPS, RF24_250KBPS] TOTAL_CHANNELS = 126 CACHE_MAX = 5 # the depth of history to calculate peaks -history = [[False] * CACHE_MAX] * TOTAL_CHANNELS # FIFO for tracking peak decays -totals = [0] * TOTAL_CHANNELS # for the total signal counts -spectrum_pass = [0] + + +class ChannelHistory: + def __init__(self) -> None: + #: FIFO for tracking peak decays + self.history: List[bool] = [False] * CACHE_MAX + #: for the total signal counts + self.total: int = 0 + + +#: An array of histories for each channel +stored = [ChannelHistory() for _ in range(TOTAL_CHANNELS)] + class ProgressBar: # pylint: disable=too-few-public-methods """This represents a progress bar using a curses window object.""" @@ -50,7 +61,7 @@ def update(self, completed: int, signal_count: int): self.win.addstr(self.y, self.x + 5 + len(bar), f"{empty} {count} ", self.color) -def init_interface(window) -> List[ProgressBar]: +def init_display(window) -> List[ProgressBar]: """Creates a table of progress bars (1 for each channel).""" progress_bars: List[ProgressBar] = [None] * TOTAL_CHANNELS bar_w = int(curses.COLS / 6) @@ -76,11 +87,11 @@ def init_radio(): radio.disableCRC() radio.setAddressWidth(2) radio.openReadingPipe(0, b"\x55\x55") - radio.openReadingPipe(1, b"\xAA\xAA") - radio.openReadingPipe(1, b"\x0A\xAA") - radio.openReadingPipe(1, b"\xA0\xAA") - radio.openReadingPipe(1, b"\x00\xAA") - radio.openReadingPipe(1, b"\xAB\xAA") + radio.openReadingPipe(1, b"\xaa\xaa") + radio.openReadingPipe(1, b"\x0a\xaa") + radio.openReadingPipe(1, b"\xa0\xaa") + radio.openReadingPipe(1, b"\x00\xaa") + radio.openReadingPipe(1, b"\xab\xaa") radio.startListening() radio.stopListening() radio.flush_rx() @@ -99,18 +110,24 @@ def init_curses(): return std_scr -def deinit_curses(): +def deinit_curses(spectrum_passes: int): """de-init the curses interface""" curses.nocbreak() curses.echo() curses.endwin() - for channel, count in enumerate(totals): - if count: - percentage = round(count / spectrum_pass[0], 3) - print(f" {channel}: {count} / {spectrum_pass[0]} ({percentage} %)") + noisy_channels: int = 0 + digit_w = len(str(spectrum_passes)) + for channel, data in enumerate(stored): + if data.total: + count_padding = " " * (digit_w - len(str(data.total))) + percentage = round(data.total / spectrum_passes * 100, 3) + print( + f" {channel:>3}: {count_padding}{data.total} / {spectrum_passes} ({percentage} %)" + ) + noisy_channels += 1 print( - f"{TOTAL_CHANNELS - totals.count(0)} channels detected signals", - f"out of {spectrum_pass[0]} passes on the entire spectrum." + f"{noisy_channels} channels detected signals out of {spectrum_passes}", + "passes on the entire spectrum.", ) @@ -143,6 +160,7 @@ def scan_channel(channel: int) -> bool: def main(): + spectrum_passes = 0 data_rate, duration = get_user_input() print(f"Scanning for {duration} seconds at {OFFERED_DATA_RATES[data_rate]}") init_radio() @@ -152,25 +170,27 @@ def main(): timer_prompt = "Scanning for {} seconds at " + OFFERED_DATA_RATES[data_rate] std_scr.addstr(0, 0, "Channels are labeled in MHz.") std_scr.addstr(1, 0, "Signal counts are clamped to a single hexadecimal digit.") - table = init_interface(std_scr) + bars = init_display(std_scr) channel, val = (0, False) end = time.monotonic() + duration while time.monotonic() < end: std_scr.addstr(2, 0, timer_prompt.format(int(end - time.monotonic()))) val = scan_channel(channel) - history[channel] = history[channel][1:] + [val] - totals[channel] += val - if totals[channel]: - table[channel].update(history[channel].count(True), totals[channel]) + stored[channel].history = stored[channel].history[1:] + [val] + stored[channel].total += val + if stored[channel].total: + bars[channel].update( + stored[channel].history.count(True), stored[channel].total + ) std_scr.refresh() if channel + 1 == TOTAL_CHANNELS: channel = 0 - spectrum_pass[0] += 1 + spectrum_passes += 1 else: channel += 1 finally: radio.powerDown() - deinit_curses() + deinit_curses(spectrum_passes) if __name__ == "__main__": From e7798798c5fe0652998c91c60b2c2a6704c6d1a1 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 3 Mar 2024 06:28:03 -0800 Subject: [PATCH 45/66] revised the pico scanner example missed this one --- examples_pico/scanner.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/examples_pico/scanner.cpp b/examples_pico/scanner.cpp index 1eaf7f91a..4e64c8479 100644 --- a/examples_pico/scanner.cpp +++ b/examples_pico/scanner.cpp @@ -19,7 +19,7 @@ * good channel for your application. * * Inspired by cpixip. - * See http://arduino.cc/forum/index.php/topic,54795.0.html + * See https://forum.arduino.cc/t/poor-mans-2-4-ghz-scanner/54846 */ /* @@ -134,12 +134,9 @@ void initRadio() radio.setAutoAck(false); // Don't acknowledge arbitrary signals radio.disableCRC(); // Accept any signal we find radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) - radio.openReadingPipe(0, noiseAddress[0]); - radio.openReadingPipe(1, noiseAddress[1]); - radio.openReadingPipe(2, noiseAddress[2]); - radio.openReadingPipe(3, noiseAddress[3]); - radio.openReadingPipe(4, noiseAddress[4]); - radio.openReadingPipe(5, noiseAddress[5]); + for (uint8_t i = 0; i < 6; ++i) { + radio.openReadingPipe(i, noiseAddress[i]); + } // To set the radioNumber via the Serial terminal on startup printf("Select your data rate. "); From 1755503182b594fc0226e782b88859e9d46ccb29 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 3 Mar 2024 06:34:53 -0800 Subject: [PATCH 46/66] ran clang-format --- examples_linux/ncurses/scanner_curses.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index eac690e24..2e6b3c0cb 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -34,7 +34,7 @@ const uint8_t CACHE_MAX = 5; // maximum depth of history for calculating pe struct ChannelHistory { - unsigned int total = 0; // the summary of signal counts for the channel + unsigned int total = 0; // the summary of signal counts for the channel bool history[CACHE_MAX]; // a cache of history for the channel }; ChannelHistory stored[MAX_CHANNELS]; From 2ec46208b38e530b3ec504b6fce56aca9ffac191 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 3 Mar 2024 08:09:16 -0800 Subject: [PATCH 47/66] use single-byte chars for py example progress bars --- examples_linux/scanner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 7cdacdba4..4d191a791 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -45,15 +45,15 @@ def __init__( # pylint: disable=too-many-arguments,invalid-name self.x, self.y, self.width, self.win, self.color = (x, y, cols, std_scr, color) string = label # always labeled in MHz (4 digits) # -9 for padding, label, & signal count - string += "─" * (self.width - 8) # the empty bar + string += "-" * (self.width - 8) # the empty bar string += " - " # the initial signal count self.win.addstr(self.y, self.x, string, self.color) def update(self, completed: int, signal_count: int): """Update the progress bar.""" filled = min(CACHE_MAX, completed) / CACHE_MAX - bar = "═" * int((self.width - 8) * filled) - empty = "─" * (self.width - 8 - len(bar)) + bar = "=" * int((self.width - 8) * filled) + empty = "-" * (self.width - 8 - len(bar)) count = "-" if signal_count: count = "%X" % min(0xF, signal_count) From b6088f01271c3a3f5ecc613fffbb38ac626daaba Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 3 Mar 2024 09:50:37 -0800 Subject: [PATCH 48/66] tested arduino scanner and pico scanner --- examples/scanner/scanner.ino | 4 ++-- examples_pico/scanner.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index a69e8084e..cfe443f73 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -137,7 +137,7 @@ void loop(void) { // Send a number over Serial to begin Constant Carrier Wave output // Configure the power amplitude level below if (Serial.available()) { - uint8_t c = Serial.parseInt(); + int8_t c = Serial.parseInt(); if (c >= 0) { c = min(125, max(0, c)); // clamp channel to supported range constCarrierMode = 1; @@ -152,7 +152,7 @@ void loop(void) { radio.stopConstCarrier(); radio.setAddressWidth(2); // reset address width radio.openReadingPipe(0, noiseAddress[0]); // ensure address is looking for noise - Serial.println("\nStopping Carrier Out"); + Serial.println("\nStopping Carrier Wave Output"); printHeader(); } } diff --git a/examples_pico/scanner.cpp b/examples_pico/scanner.cpp index 4e64c8479..5b723f5b7 100644 --- a/examples_pico/scanner.cpp +++ b/examples_pico/scanner.cpp @@ -79,7 +79,7 @@ int main() printf("RF24/examples_pico/scanner\n"); // print a line that should not be wrapped - printf("!!! This example requires a width of at least 126 characters. "); + printf("\n!!! This example requires a width of at least 126 characters. "); printf("If this text uses multiple lines, then the output will look bad.\n"); // initialize the transceiver on the SPI bus @@ -139,7 +139,7 @@ void initRadio() } // To set the radioNumber via the Serial terminal on startup - printf("Select your data rate. "); + printf("\nSelect your data rate. "); printf("Enter '1' for 1 Mbps, '2' for 2 Mbps, or '3' for 250 kbps. "); printf("Defaults to 1 Mbps.\n"); char input = getchar(); From 49a1f1344e8fed22b19592a45f102fd3de97cc94 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 3 Mar 2024 21:13:42 -0800 Subject: [PATCH 49/66] reviewed and tested scannerGraphic with I2C display --- examples/scannerGraphic/scannerGraphic.ino | 57 ++++++---------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/examples/scannerGraphic/scannerGraphic.ino b/examples/scannerGraphic/scannerGraphic.ino index c8af59d5a..a8d7ffea5 100644 --- a/examples/scannerGraphic/scannerGraphic.ino +++ b/examples/scannerGraphic/scannerGraphic.ino @@ -39,8 +39,10 @@ * Instantiate the radio and app-specific attributes ********************************************************************/ -// Set up nRF24L01 radio on SPI bus using pins 7 (for CE) & 8 (for CSN) -RF24 radio(0, 1); +#define CE_PIN 7 +#define CSN_PIN 8 +// instantiate an object for the nRF24L01 transceiver +RF24 radio(CE_PIN, CSN_PIN); // To detect noise, we'll use the worst addresses possible (a reverse engineering tactic). // These addresses are designed to confuse the radio into thinking @@ -48,7 +50,7 @@ RF24 radio(0, 1); const uint8_t noiseAddress[][2] = { { 0x55, 0x55 }, { 0xAA, 0xAA }, { 0xA0, 0xAA }, { 0xAB, 0xAA }, { 0xAC, 0xAA }, { 0xAD, 0xAA } }; // keep a cache of history for peak decay -const uint8_t CACHE_MAX = 6; +const uint8_t CACHE_MAX = 4; const uint8_t num_channels = 126; // 0-125 are supported bool history[num_channels][CACHE_MAX]; @@ -116,10 +118,6 @@ Adafruit_ST7789 display = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); #ifdef DEBUGGING #include "printf.h" #define SERIAL_DEBUG(x) ({ x; }) - -// Using "\r" is meant for console apps that connect to USB serial ports (SimplySerial on Windows) -#define RETURN_FEED "\r" // change this to "\n" if using the Serial Monitor in Arduino IDE - #else #define SERIAL_DEBUG(x) #endif @@ -135,7 +133,7 @@ void setup(void) { while (!Serial) { // some boards need this to wait for Serial connection } - Serial.println(F("RF24/examples/scannerGraphic/")); + Serial.println(F("RF24/examples/scannerGraphic")); #endif #ifdef I2C_DISPLAY @@ -149,20 +147,25 @@ void setup(void) { #elif defined(SPI_DISPLAY) // use this initializer for a 1.14" 240x135 TFT: display.init(SCREEN_HEIGHT, SCREEN_WIDTH); // Init ST7789 240x135 - display.setRotation(1); + display.setRotation(1); // put in landscape mode #endif // Clear the buffer CLEAR_DISPLAY; - displayChartAxis(); // Setup and configure rf radio if (!radio.begin()) { SERIAL_DEBUG(Serial.println(F("radio hardware not responding!"));); + display.setCursor(1, 1); + display.setTextColor(WHITE); + display.printf("radio hardware\nnot responding!"); + REFRESH; while (true) { // hold in an infinite loop } } + displayChartAxis(); + radio.setAutoAck(false); // Don't acknowledge arbitrary signals radio.disableCRC(); // accept any signal we find radio.setAddressWidth(2); // a reverse engineering tactic (not typically recommended) @@ -204,9 +207,6 @@ void setup(void) { history[i][j] = false; } } -#ifdef DEBUGGING - printHeader(); -#endif } /******************************************************************** @@ -221,16 +221,12 @@ void loop(void) { uint8_t x = i + 1 + X_OFFSET; uint8_t chartHeight = SCREEN_HEIGHT - 10; display.drawLine(x, 0, x, chartHeight, BLACK); - if (!historicCount) { - SERIAL_DEBUG(Serial.print("-");); - } else { + if (historicCount) { uint8_t barHeight = chartHeight * historicCount / CACHE_MAX; display.drawLine(x, SCREEN_HEIGHT - 10, x, SCREEN_HEIGHT - 10 - barHeight, WHITE); - SERIAL_DEBUG(Serial.print(historicCount);); } } REFRESH; - SERIAL_DEBUG(Serial.print(RETURN_FEED);); } // end loop() /******************************************************************** @@ -267,31 +263,6 @@ uint8_t historyPush(uint8_t channel, bool value) { return sum + value; } -/******************************************************************** - * A debugging function for printing the channels' header - ********************************************************************/ -void printHeader() { - // Print the hundreds digits - for (int i = 0; i < num_channels; ++i) - Serial.print(i / 100); - Serial.println(); - - // Print the tens digits - for (int i = 0; i < num_channels; ++i) - Serial.print((i % 100) / 10); - Serial.println(); - - // Print the singles digits - for (int i = 0; i < num_channels; ++i) - Serial.print(i % 10); - Serial.println(); - - // Print the header's divider - for (int i = 0; i < num_channels; ++i) - Serial.print(F("~")); - Serial.println(); -} - /******************************************************************** * Draw the chart axis and labels ********************************************************************/ From 23ee59a3b43a2f47bd6ca7612e8c43e39307ba7e Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 4 Mar 2024 02:00:42 -0800 Subject: [PATCH 50/66] [scannerGraphic] make X_OFFSET calc agnostic of display type --- examples/scannerGraphic/scannerGraphic.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/scannerGraphic/scannerGraphic.ino b/examples/scannerGraphic/scannerGraphic.ino index a8d7ffea5..dad243a30 100644 --- a/examples/scannerGraphic/scannerGraphic.ino +++ b/examples/scannerGraphic/scannerGraphic.ino @@ -85,7 +85,6 @@ Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); #define WHITE SSD1306_WHITE #define REFRESH ({ display.display(); }) #define CLEAR_DISPLAY ({ display.clearDisplay(); }) -#define X_OFFSET 0 #elif defined(SPI_DISPLAY) @@ -105,10 +104,11 @@ Adafruit_ST7789 display = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); #define WHITE ST77XX_WHITE #define REFRESH #define CLEAR_DISPLAY ({ display.fillScreen(BLACK); }) -#define X_OFFSET (SCREEN_WIDTH - 128) / 2 #endif // if defined(I2C_DISPLAY) || defined(SPI_DISPLAY) +#define X_OFFSET (SCREEN_WIDTH - (num_channels + 2)) / 2 + /******************************************************************** * Configure debugging on Serial output ********************************************************************/ From 0791b3747f78403b344614548b887e3b708dd782 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 4 Mar 2024 10:14:46 -0800 Subject: [PATCH 51/66] [scannerGraphic] draw graph based on screen dimensions; add max peak decay --- examples/scannerGraphic/scannerGraphic.ino | 120 +++++++++++++-------- 1 file changed, 74 insertions(+), 46 deletions(-) diff --git a/examples/scannerGraphic/scannerGraphic.ino b/examples/scannerGraphic/scannerGraphic.ino index dad243a30..938495fa8 100644 --- a/examples/scannerGraphic/scannerGraphic.ino +++ b/examples/scannerGraphic/scannerGraphic.ino @@ -27,7 +27,7 @@ * be made so it does not use the same SPI bus that the radio uses. * * `DEBUGGING` can be enabled (uncommented) to show Serial output. This is just a - * convenience for troubleshooting and advanced development. See our other + * convenience set radio data rate or further development. See our other * RF24/scanner example that only uses the Serial Monitor instead of a graphic * display. */ @@ -49,10 +49,43 @@ RF24 radio(CE_PIN, CSN_PIN); // that the RF signal's preamble is part of the packet/payload. const uint8_t noiseAddress[][2] = { { 0x55, 0x55 }, { 0xAA, 0xAA }, { 0xA0, 0xAA }, { 0xAB, 0xAA }, { 0xAC, 0xAA }, { 0xAD, 0xAA } }; -// keep a cache of history for peak decay -const uint8_t CACHE_MAX = 4; -const uint8_t num_channels = 126; // 0-125 are supported -bool history[num_channels][CACHE_MAX]; +const uint8_t numChannels = 126; // 0-125 are supported + +/*********************************************************************** + * Declare caching mechanism to track history of signals for peak decay + **********************************************************************/ + +const uint8_t cacheMax = 4; + +// uncomment to disable decay of maxPeak pixels +// #define HOLD_PEAKS + +/// A data structure to organize the cache of signals for a certain channel. +struct ChannelHistory { + /// max peak value is (at most) 2 * CACHE_MAX to allow for half-step decays + uint8_t maxPeak = 0; + + /// Push a signal's value into cached history while popping + /// oldest cached value. This also sets the maxPeak value. + /// @returns The sum of signals found in the cached history + uint8_t push(bool value) { + uint8_t sum = 0; + for (uint8_t i = 0; i < cacheMax - 1; ++i) { + history[i] = history[i + 1]; + sum += history[i]; + } + history[cacheMax - 1] = value; + sum += value; + maxPeak = max(sum * 2, maxPeak); // sum * 2 to allow half-step decay + return sum; + } + +private: + bool history[cacheMax] = { 0 }; +}; + +/// An array of caches to use as channels' history +ChannelHistory stored[numChannels]; /******************************************************************** * Instantiate the appropriate display @@ -107,7 +140,10 @@ Adafruit_ST7789 display = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); #endif // if defined(I2C_DISPLAY) || defined(SPI_DISPLAY) -#define X_OFFSET (SCREEN_WIDTH - (num_channels + 2)) / 2 +// constant chart size attributes +const uint8_t margin = 1; // use 1 pixel margin for markers on each side of chart +const uint8_t barWidth = (SCREEN_WIDTH - (margin * 2)) / numChannels; +const uint8_t chartHeight = SCREEN_HEIGHT - 10; /******************************************************************** * Configure debugging on Serial output @@ -200,13 +236,6 @@ void setup(void) { radio.startListening(); radio.stopListening(); radio.flush_rx(); - - // initialize our history for showing cumulative peaks - for (uint8_t i = 0; i < num_channels; ++i) { - for (uint8_t j = 0; j < CACHE_MAX; ++j) { - history[i][j] = false; - } - } } /******************************************************************** @@ -214,16 +243,23 @@ void setup(void) { ********************************************************************/ void loop(void) { // Print out channel measurements, clamped to a single hex digit - for (int i = 0; i < num_channels; ++i) { - bool foundSignal = scanChannel(i); - // uint8_t chart - uint8_t historicCount = historyPush(i, foundSignal); - uint8_t x = i + 1 + X_OFFSET; - uint8_t chartHeight = SCREEN_HEIGHT - 10; - display.drawLine(x, 0, x, chartHeight, BLACK); - if (historicCount) { - uint8_t barHeight = chartHeight * historicCount / CACHE_MAX; - display.drawLine(x, SCREEN_HEIGHT - 10, x, SCREEN_HEIGHT - 10 - barHeight, WHITE); + for (uint8_t channel = 0; channel < numChannels; ++channel) { + bool foundSignal = scanChannel(channel); + uint8_t cacheSum = stored[channel].push(foundSignal); + uint8_t x = (barWidth * channel) + 1 + margin - (barWidth * (bool)channel); + // reset bar for current channel to 0 + display.fillRect(x, 0, barWidth, chartHeight, BLACK); + if (stored[channel].maxPeak > cacheSum * 2) { + // draw a peak line only if it is greater than current sum of cached signal counts + uint16_t y = chartHeight - (chartHeight * stored[channel].maxPeak / (cacheMax * 2)); + display.drawLine(x, y, x + barWidth, y, WHITE); +#ifndef HOLD_PEAKS + stored[channel].maxPeak -= 1; // decrement max peak +#endif + } + if (cacheSum) { // draw the cached signal count + uint8_t barHeight = chartHeight * cacheSum / cacheMax; + display.fillRect(x, chartHeight - barHeight, barWidth, barHeight, WHITE); } } REFRESH; @@ -249,43 +285,35 @@ bool scanChannel(uint8_t channel) { return false; } -/******************************************************************** - * Push new scan result for a channel into the history. - * @returns The count of historic signals found (including pushed result) - ********************************************************************/ -uint8_t historyPush(uint8_t channel, bool value) { - uint8_t sum = 0; - for (uint8_t i = 0; i < CACHE_MAX - 1; ++i) { - history[channel][i] = history[channel][i + 1]; - sum += history[channel][i]; - } - history[channel][CACHE_MAX - 1] = value; - return sum + value; -} - /******************************************************************** * Draw the chart axis and labels ********************************************************************/ void displayChartAxis() { // draw base line - display.drawLine(X_OFFSET, SCREEN_HEIGHT - 8, X_OFFSET + 127, SCREEN_HEIGHT - 8, WHITE); + display.drawLine(0, chartHeight + 1, SCREEN_WIDTH, chartHeight + 1, WHITE); // draw base line border - display.drawLine(X_OFFSET, SCREEN_HEIGHT - 1, X_OFFSET, SCREEN_HEIGHT - 12, WHITE); - display.drawLine(X_OFFSET + 127, SCREEN_HEIGHT - 1, X_OFFSET + 127, SCREEN_HEIGHT - 12, WHITE); + display.drawLine(margin, SCREEN_HEIGHT, margin, chartHeight - 2, WHITE); + display.drawLine(SCREEN_WIDTH - margin, SCREEN_HEIGHT, SCREEN_WIDTH - margin, chartHeight - 2, WHITE); // draw scalar marks - for (uint8_t i = 0; i < CACHE_MAX; ++i) { - uint8_t scalarHeight = (SCREEN_HEIGHT - 10) * i / CACHE_MAX; - display.drawLine(X_OFFSET, scalarHeight, SCREEN_WIDTH - X_OFFSET, scalarHeight, WHITE); + for (uint8_t i = 0; i < cacheMax; ++i) { + uint8_t scalarHeight = chartHeight * i / cacheMax; + display.drawLine(0, scalarHeight, SCREEN_WIDTH, scalarHeight, WHITE); } // draw channel range labels display.setTextSize(1); display.setTextColor(WHITE); - display.setCursor(SCREEN_WIDTH - X_OFFSET - 21, SCREEN_HEIGHT - 7); - display.print(125); - display.setCursor(X_OFFSET + 2, SCREEN_HEIGHT - 7); + uint8_t maxChannelDigits = 0; + uint8_t tmp = numChannels; + while (tmp) { + maxChannelDigits += 1; + tmp /= 10; + } + display.setCursor(SCREEN_WIDTH - margin - (7 * maxChannelDigits), chartHeight + 3); + display.print(numChannels - 1); + display.setCursor(margin + 2, chartHeight + 3); display.print(0); // refresh display From aa9c67075b2f5a7887da549d84255a5cf335e7c2 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Tue, 5 Mar 2024 10:01:06 -0800 Subject: [PATCH 52/66] tested scannerGraphic with ST7789 display --- examples/scannerGraphic/scannerGraphic.ino | 68 +++++++++++----------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/examples/scannerGraphic/scannerGraphic.ino b/examples/scannerGraphic/scannerGraphic.ino index 938495fa8..26005f160 100644 --- a/examples/scannerGraphic/scannerGraphic.ino +++ b/examples/scannerGraphic/scannerGraphic.ino @@ -18,7 +18,9 @@ * * NOTES: * The `SCREEN_HEIGHT` and `SCREEN_WIDTH` defines may need to be adjusted according - * to your display module's capability. + * to your display module's capability. This example expects the display to be at + * least 128 pixels wide. Otherwise, you would have to reduce the `numChannels` + * constant to fit within your display's width. * * The SPI_DISPLAY ues its own pins defined by `TFT_CS`, `TFT_DC`, and the * optional `TFT_RST` (see below). The SPI bus is shared between radio and display, @@ -34,6 +36,19 @@ #include // dependency of Adafruit display libraries #include "RF24.h" +/******************************************************************** + * CHOOSE A DISPLAY INTERFACE + * uncomment/comment only 1 of the following to use the desired display + ********************************************************************/ +// #define I2C_DISPLAY // using the SSD1306 +#define SPI_DISPLAY // using ST7789 + +/******************************************************************** + * Choose a sketch feature + * uncomment any of the following to enable a special feature + ********************************************************************/ +// #define DEBUGGING // uncomment to enable Serial output (optional) +// #define HOLD_PEAKS // uncomment to disable decay of maxPeak pixels (useful for assessing total noise) /******************************************************************** * Instantiate the radio and app-specific attributes @@ -57,9 +72,6 @@ const uint8_t numChannels = 126; // 0-125 are supported const uint8_t cacheMax = 4; -// uncomment to disable decay of maxPeak pixels -// #define HOLD_PEAKS - /// A data structure to organize the cache of signals for a certain channel. struct ChannelHistory { /// max peak value is (at most) 2 * CACHE_MAX to allow for half-step decays @@ -88,15 +100,11 @@ private: ChannelHistory stored[numChannels]; /******************************************************************** - * Instantiate the appropriate display + * Instantiate the appropriate display objects according to the + * defines (above near top of file) ********************************************************************/ -// uncomment either or none of the following to use the desired display: - -// #define I2C_DISPLAY // using the SSD1306 -#define SPI_DISPLAY // using ST7789 - -// create our `display` objects according to the above defines +// create our `display` #ifdef I2C_DISPLAY #include @@ -123,12 +131,12 @@ Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); #include // Hardware-specific library for ST7789 -#define TFT_CS 2 +#define TFT_CS 9 #define TFT_RST -1 // Or set to -1 and connect to Arduino RESET pin -#define TFT_DC 3 +#define TFT_DC 6 -#define SCREEN_WIDTH 240 // TFT display width, in pixels -#define SCREEN_HEIGHT 135 // TFT display height, in pixels +#define SCREEN_WIDTH 135 // TFT display width, in pixels +#define SCREEN_HEIGHT 240 // TFT display height, in pixels // For 1.14", 1.3", 1.54", 1.69", and 2.0" TFT with ST7789: Adafruit_ST7789 display = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); @@ -141,16 +149,15 @@ Adafruit_ST7789 display = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); #endif // if defined(I2C_DISPLAY) || defined(SPI_DISPLAY) // constant chart size attributes -const uint8_t margin = 1; // use 1 pixel margin for markers on each side of chart -const uint8_t barWidth = (SCREEN_WIDTH - (margin * 2)) / numChannels; -const uint8_t chartHeight = SCREEN_HEIGHT - 10; +const uint16_t margin = 1; // use 1 pixel margin for markers on each side of chart +const uint16_t barWidth = (SCREEN_WIDTH - (margin * 2)) / numChannels; +const uint16_t chartHeight = SCREEN_HEIGHT - 10; +const uint16_t chartWidth = margin * 2 + (numChannels * barWidth); /******************************************************************** * Configure debugging on Serial output ********************************************************************/ -// #define DEBUGGING // uncomment to enable Serial output - #ifdef DEBUGGING #include "printf.h" #define SERIAL_DEBUG(x) ({ x; }) @@ -182,8 +189,7 @@ void setup(void) { } #elif defined(SPI_DISPLAY) // use this initializer for a 1.14" 240x135 TFT: - display.init(SCREEN_HEIGHT, SCREEN_WIDTH); // Init ST7789 240x135 - display.setRotation(1); // put in landscape mode + display.init(SCREEN_WIDTH, SCREEN_HEIGHT); // Init ST7789 240x135 #endif // Clear the buffer @@ -265,9 +271,7 @@ void loop(void) { REFRESH; } // end loop() -/******************************************************************** - * Scan a specified channel and return the resulting flag - ********************************************************************/ +/// Scan a specified channel and return the resulting flag bool scanChannel(uint8_t channel) { radio.setChannel(channel); @@ -285,21 +289,19 @@ bool scanChannel(uint8_t channel) { return false; } -/******************************************************************** - * Draw the chart axis and labels - ********************************************************************/ +/// Draw the chart axis and labels void displayChartAxis() { // draw base line - display.drawLine(0, chartHeight + 1, SCREEN_WIDTH, chartHeight + 1, WHITE); + display.drawLine(0, chartHeight + 1, chartWidth - margin, chartHeight + 1, WHITE); // draw base line border display.drawLine(margin, SCREEN_HEIGHT, margin, chartHeight - 2, WHITE); - display.drawLine(SCREEN_WIDTH - margin, SCREEN_HEIGHT, SCREEN_WIDTH - margin, chartHeight - 2, WHITE); + display.drawLine(chartWidth - margin, SCREEN_HEIGHT, chartWidth - margin, chartHeight - 2, WHITE); // draw scalar marks for (uint8_t i = 0; i < cacheMax; ++i) { uint8_t scalarHeight = chartHeight * i / cacheMax; - display.drawLine(0, scalarHeight, SCREEN_WIDTH, scalarHeight, WHITE); + display.drawLine(0, scalarHeight, chartWidth, scalarHeight, WHITE); } // draw channel range labels @@ -311,11 +313,11 @@ void displayChartAxis() { maxChannelDigits += 1; tmp /= 10; } - display.setCursor(SCREEN_WIDTH - margin - (7 * maxChannelDigits), chartHeight + 3); + display.setCursor(chartWidth - (7 * maxChannelDigits), chartHeight + 3); display.print(numChannels - 1); display.setCursor(margin + 2, chartHeight + 3); display.print(0); // refresh display REFRESH; -} \ No newline at end of file +} From 42b95baf6fa0e3697919cb428babd81e167a6e9d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Tue, 5 Mar 2024 10:24:00 -0800 Subject: [PATCH 53/66] ensure radio is not stuck with CONT_WAVE flag asserted after MCU reset --- examples/scanner/scanner.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index cfe443f73..1ad2b4e9d 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -91,6 +91,7 @@ void setup(void) { // hold in an infinite loop } } + radio.stopConstCarrier(); // in case MCU was reset while radio was emitting carrier wave radio.setAutoAck(false); // Don't acknowledge arbitrary signals radio.disableCRC(); // Accept any signal we find radio.setAddressWidth(2); // A reverse engineering tactic (not typically recommended) From 91745599c18034dcc0652f8f9297df9ec25e6add Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Tue, 5 Mar 2024 10:38:14 -0800 Subject: [PATCH 54/66] reviewed files' doc comment at beginning of examples --- examples/scanner/scanner.ino | 2 ++ examples/scannerGraphic/scannerGraphic.ino | 2 ++ examples_linux/ncurses/scanner_curses.cpp | 10 ++++++++++ examples_linux/scanner.cpp | 2 ++ examples_linux/scanner.py | 8 +++++++- 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index 1ad2b4e9d..c54e3e591 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -22,6 +22,8 @@ * * Inspired by cpixip. * See https://forum.arduino.cc/t/poor-mans-2-4-ghz-scanner/54846 + * + * See documentation at https://nRF24.github.io/RF24 */ /* diff --git a/examples/scannerGraphic/scannerGraphic.ino b/examples/scannerGraphic/scannerGraphic.ino index 26005f160..566cb075a 100644 --- a/examples/scannerGraphic/scannerGraphic.ino +++ b/examples/scannerGraphic/scannerGraphic.ino @@ -32,6 +32,8 @@ * convenience set radio data rate or further development. See our other * RF24/scanner example that only uses the Serial Monitor instead of a graphic * display. + * + * See documentation at https://nRF24.github.io/RF24 */ #include // dependency of Adafruit display libraries #include "RF24.h" diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 2e6b3c0cb..54fc9cd99 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -5,6 +5,16 @@ modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. */ + +/** + * Channel scanner + * + * Example to detect interference on the various channels available. + * This is a good diagnostic tool to check whether you're picking a + * good channel for your application. + * + * See documentation at https://nRF24.github.io/RF24 + */ #include // sprintf() #include // string, getline() #include // time_t, time(), difftime() diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index ce68b92ea..7de430433 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -21,6 +21,8 @@ * * Inspired by cpixip. * See https://forum.arduino.cc/t/poor-mans-2-4-ghz-scanner/54846 + * + * See documentation at https://nRF24.github.io/RF24 */ /* diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 4d191a791..f1debb1f6 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -1,4 +1,10 @@ -"""A scanner example written in python using the std lib's ncurses wrapper.""" +"""A scanner example written in python using the std lib's ncurses wrapper. + +This is a good diagnostic tool to check whether you're picking a +good channel for your application. + +See documentation at https://nRF24.github.io/RF24 +""" # pylint: disable=no-member import curses From e0c51683175b16fdaa260ab64729efdc1527c09b Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 6 Mar 2024 08:57:51 -0800 Subject: [PATCH 55/66] arduino scanner discard CRLF from Serial input --- examples/scanner/scanner.ino | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index c54e3e591..2df42f7c7 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -84,7 +84,6 @@ void setup(void) { // some boards need this to wait for Serial connection } Serial.println(F("RF24/examples/scanner/")); - Serial.println(F("!!! Please do not send line endings in the serial monitor !!!")); // Setup and configure rf radio if (!radio.begin()) { @@ -158,7 +157,17 @@ void loop(void) { Serial.println("\nStopping Carrier Wave Output"); printHeader(); } + + // discard any CR and LF sent + while (Serial.peek() != -1) { + if (Serial.peek() == '\r' || Serial.peek() == '\n') { + Serial.read(); + } else { // got a charater that isn't a line feed + break; // handle it on nect loop() iteration + } + } } + /****************************************/ if (constCarrierMode == 0) { From 795d0fbf6c60d91801fcfe52710a80313374c8b2 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 6 Mar 2024 08:59:14 -0800 Subject: [PATCH 56/66] fix address assignment to pipes in python scanner --- examples_linux/scanner.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index f1debb1f6..2399094aa 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -23,6 +23,18 @@ TOTAL_CHANNELS = 126 CACHE_MAX = 5 # the depth of history to calculate peaks +# To detect noise, we'll use the worst addresses possible (a reverse engineering +# tactic). These addresses are designed to confuse the radio into thinking that the +# RF signal's preamble is part of the packet/payload. +noise_address = [ + b"\x55\x55", + b"\xaa\xaa", + b"\x0a\xaa", + b"\xa0\xaa", + b"\x00\xaa", + b"\xab\xaa", +] + class ChannelHistory: def __init__(self) -> None: @@ -92,12 +104,8 @@ def init_radio(): radio.setAutoAck(False) radio.disableCRC() radio.setAddressWidth(2) - radio.openReadingPipe(0, b"\x55\x55") - radio.openReadingPipe(1, b"\xaa\xaa") - radio.openReadingPipe(1, b"\x0a\xaa") - radio.openReadingPipe(1, b"\xa0\xaa") - radio.openReadingPipe(1, b"\x00\xaa") - radio.openReadingPipe(1, b"\xab\xaa") + for pipe, address in enumerate(noise_address): + radio.openReadingPipe(pipe, address) radio.startListening() radio.stopListening() radio.flush_rx() From 3fa016d0c6a2d10381f00e2f866a3278c472c362 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 6 Mar 2024 09:08:04 -0800 Subject: [PATCH 57/66] add push() to ChannelHistory structs --- examples_linux/ncurses/scanner_curses.cpp | 34 ++++++++++++----------- examples_linux/scanner.py | 15 ++++++---- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 54fc9cd99..809b60eb5 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -46,6 +46,23 @@ struct ChannelHistory { unsigned int total = 0; // the summary of signal counts for the channel bool history[CACHE_MAX]; // a cache of history for the channel + + /** + * Push new scan result for a channel into the history. + * This function also increments the total signal count accordingly. + * @returns The count of cached signals found (including pushed result) + */ + uint8_t push(bool value) + { + uint8_t sum = value; + total += value; + for (uint8_t i = 0; i < CACHE_MAX - 1; ++i) { + history[i] = history[i + 1]; + sum += history[i]; + } + history[CACHE_MAX - 1] = value; + return sum; + } }; ChannelHistory stored[MAX_CHANNELS]; @@ -149,7 +166,7 @@ int main(int argc, char** argv) bpsUnit); bool foundSignal = scanChannel(channel); - uint8_t cachedCount = historyPush(channel, foundSignal); + uint8_t cachedCount = stored[channel].push(foundSignal); // output the summary/snapshot for this channel if (stored[channel].total) { @@ -313,19 +330,4 @@ void initBars() } } -/** - * Push new scan result for a channel into the history. - * @returns The count of historic signals found (including pushed result) - */ -uint8_t historyPush(uint8_t channel, bool value) -{ - uint8_t sum = 0; - for (uint8_t i = 0; i < CACHE_MAX - 1; ++i) { - stored[channel].history[i] = stored[channel].history[i + 1]; - sum += stored[channel].history[i]; - } - stored[channel].history[CACHE_MAX - 1] = value; - return sum + value; -} - // vim:ai:cin:sts=2 sw=2 ft=cpp diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 2399094aa..dc8edfff0 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -43,6 +43,14 @@ def __init__(self) -> None: #: for the total signal counts self.total: int = 0 + def push(self, value: bool) -> int: + """Push a scan result's value into history while returning the sum of cached + signals found. This function also increments the total signal count accordingly. + """ + self.history = self.history[1:] + [value] + self.total += value + return self.history.count(True) + #: An array of histories for each channel stored = [ChannelHistory() for _ in range(TOTAL_CHANNELS)] @@ -190,12 +198,9 @@ def main(): while time.monotonic() < end: std_scr.addstr(2, 0, timer_prompt.format(int(end - time.monotonic()))) val = scan_channel(channel) - stored[channel].history = stored[channel].history[1:] + [val] - stored[channel].total += val + cache_sum = stored[channel].push(val) if stored[channel].total: - bars[channel].update( - stored[channel].history.count(True), stored[channel].total - ) + bars[channel].update(cache_sum, stored[channel].total) std_scr.refresh() if channel + 1 == TOTAL_CHANNELS: channel = 0 From 0f4c0ce105a2155b916c3bcb7131e4c90c936e9c Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 6 Mar 2024 09:35:40 -0800 Subject: [PATCH 58/66] more review changes --- examples/scanner/scanner.ino | 5 +++-- examples_linux/ncurses/scanner_curses.cpp | 4 +++- examples_linux/scanner.cpp | 1 - examples_linux/scanner.py | 14 +++++++------- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index 2df42f7c7..7e2cc580a 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -163,7 +163,7 @@ void loop(void) { if (Serial.peek() == '\r' || Serial.peek() == '\n') { Serial.read(); } else { // got a charater that isn't a line feed - break; // handle it on nect loop() iteration + break; // handle it on next loop() iteration } } } @@ -185,10 +185,11 @@ void loop(void) { // Listen for a little radio.startListening(); delayMicroseconds(128); + bool foundSignal = radio.testRPD(); radio.stopListening(); // Did we get a signal? - if (radio.testRPD() || radio.available()) { + if (foundSignal || radio.testRPD() || radio.available()) { ++values[i]; radio.flush_rx(); // discard packets of noise } diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 809b60eb5..ce7805d29 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -45,7 +45,6 @@ const uint8_t CACHE_MAX = 5; // maximum depth of history for calculating pe struct ChannelHistory { unsigned int total = 0; // the summary of signal counts for the channel - bool history[CACHE_MAX]; // a cache of history for the channel /** * Push new scan result for a channel into the history. @@ -63,6 +62,9 @@ struct ChannelHistory history[CACHE_MAX - 1] = value; return sum; } + +private: + bool history[CACHE_MAX]; // a cache of history for the channel }; ChannelHistory stored[MAX_CHANNELS]; diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index 7de430433..bed003f4a 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -150,7 +150,6 @@ int main(int argc, char** argv) // Listen for a little radio.startListening(); delayMicroseconds(130); - // for some reason, this flag is more accurate on Linux when still in RX mode. bool foundSignal = radio.testRPD(); radio.stopListening(); diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index dc8edfff0..dccbf1a7f 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -39,7 +39,7 @@ class ChannelHistory: def __init__(self) -> None: #: FIFO for tracking peak decays - self.history: List[bool] = [False] * CACHE_MAX + self._history: List[bool] = [False] * CACHE_MAX #: for the total signal counts self.total: int = 0 @@ -47,9 +47,9 @@ def push(self, value: bool) -> int: """Push a scan result's value into history while returning the sum of cached signals found. This function also increments the total signal count accordingly. """ - self.history = self.history[1:] + [value] + self._history = self._history[1:] + [value] self.total += value - return self.history.count(True) + return self._history.count(True) #: An array of histories for each channel @@ -173,12 +173,12 @@ def scan_channel(channel: int) -> bool: radio.channel = channel radio.startListening() time.sleep(0.00013) - result = radio.testRPD() + found_signal = radio.testRPD() radio.stopListening() - result = result or radio.testRPD() or radio.available() - if result: + if found_signal or radio.testRPD() or radio.available(): radio.flush_rx() - return result + return True + return False def main(): From 2d744670363b1616d07263913a00ad3d2d0a34ac Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 6 Mar 2024 09:39:18 -0800 Subject: [PATCH 59/66] format examples_linux/ncurses/scanner_curses.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- examples_linux/ncurses/scanner_curses.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index ce7805d29..8c1bfdfeb 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -44,7 +44,7 @@ const uint8_t CACHE_MAX = 5; // maximum depth of history for calculating pe struct ChannelHistory { - unsigned int total = 0; // the summary of signal counts for the channel + unsigned int total = 0; // the summary of signal counts for the channel /** * Push new scan result for a channel into the history. From 2c6d0f482bf3e7165ea74784e5feb6c98c5edded Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 6 Mar 2024 18:28:07 -0800 Subject: [PATCH 60/66] fix ncurses scanner incrementing total signal counts --- examples_linux/ncurses/scanner_curses.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 8c1bfdfeb..24b959f0f 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -248,7 +248,6 @@ bool scanChannel(uint8_t channel) // Did we get a signal? if (foundSignal || radio.testRPD() || radio.available()) { - ++stored[channel].total; radio.flush_rx(); // discard packets of noise return true; } From 936eafe8afbeb5d4ed96dcc1d8d3e162f80d18bf Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 6 Mar 2024 18:29:12 -0800 Subject: [PATCH 61/66] fix py scanner countdown timer prompt formatting --- examples_linux/scanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index dccbf1a7f..71bda626d 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -189,7 +189,7 @@ def main(): radio.setDataRate(AVAILABLE_RATES[data_rate]) try: std_scr = init_curses() - timer_prompt = "Scanning for {} seconds at " + OFFERED_DATA_RATES[data_rate] + timer_prompt = "Scanning for {:>3} seconds at " + OFFERED_DATA_RATES[data_rate] std_scr.addstr(0, 0, "Channels are labeled in MHz.") std_scr.addstr(1, 0, "Signal counts are clamped to a single hexadecimal digit.") bars = init_display(std_scr) From 056b80a58ed0071f15feedee0427db216633b7c3 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 6 Mar 2024 20:26:30 -0800 Subject: [PATCH 62/66] proofreading scannerGraphic --- examples/scannerGraphic/scannerGraphic.ino | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/scannerGraphic/scannerGraphic.ino b/examples/scannerGraphic/scannerGraphic.ino index 566cb075a..e11ef6ea3 100644 --- a/examples/scannerGraphic/scannerGraphic.ino +++ b/examples/scannerGraphic/scannerGraphic.ino @@ -22,14 +22,14 @@ * least 128 pixels wide. Otherwise, you would have to reduce the `numChannels` * constant to fit within your display's width. * - * The SPI_DISPLAY ues its own pins defined by `TFT_CS`, `TFT_DC`, and the + * The SPI_DISPLAY uses its own pins defined by `TFT_CS`, `TFT_DC`, and the * optional `TFT_RST` (see below). The SPI bus is shared between radio and display, * so the display's CS pin must be connected as specified by `TFT_CS`. * If your ST7789 display does not have a CS pin, then further modification must * be made so it does not use the same SPI bus that the radio uses. * * `DEBUGGING` can be enabled (uncommented) to show Serial output. This is just a - * convenience set radio data rate or further development. See our other + * convenience to set radio data rate or further development. See our other * RF24/scanner example that only uses the Serial Monitor instead of a graphic * display. * @@ -83,13 +83,12 @@ struct ChannelHistory { /// oldest cached value. This also sets the maxPeak value. /// @returns The sum of signals found in the cached history uint8_t push(bool value) { - uint8_t sum = 0; + uint8_t sum = value; for (uint8_t i = 0; i < cacheMax - 1; ++i) { history[i] = history[i + 1]; sum += history[i]; } history[cacheMax - 1] = value; - sum += value; maxPeak = max(sum * 2, maxPeak); // sum * 2 to allow half-step decay return sum; } @@ -106,7 +105,6 @@ ChannelHistory stored[numChannels]; * defines (above near top of file) ********************************************************************/ -// create our `display` #ifdef I2C_DISPLAY #include @@ -120,7 +118,7 @@ ChannelHistory stored[numChannels]; // On an arduino UNO: A4(SDA), A5(SCL) // On an arduino MEGA 2560: 20(SDA), 21(SCL) // On an arduino LEONARDO: 2(SDA), 3(SCL), ... -#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) +#define OLED_RESET -1 // Or set to -1 and connect to Arduino RESET pin #define SCREEN_ADDRESS 0x3D // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); @@ -202,7 +200,7 @@ void setup(void) { SERIAL_DEBUG(Serial.println(F("radio hardware not responding!"));); display.setCursor(1, 1); display.setTextColor(WHITE); - display.printf("radio hardware\nnot responding!"); + display.print(F("radio hardware\nnot responding!")); REFRESH; while (true) { // hold in an infinite loop From 5fea46fcb68247f212d8408c525a181d85fab37c Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 6 Mar 2024 22:09:11 -0800 Subject: [PATCH 63/66] use pins defined as macros in scanner_curses.cpp --- examples_linux/ncurses/scanner_curses.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples_linux/ncurses/scanner_curses.cpp b/examples_linux/ncurses/scanner_curses.cpp index 24b959f0f..1c45bf3db 100644 --- a/examples_linux/ncurses/scanner_curses.cpp +++ b/examples_linux/ncurses/scanner_curses.cpp @@ -30,9 +30,14 @@ using namespace std; // CE Pin uses GPIO number with BCM and SPIDEV drivers, other platforms use their own pin numbering // CS Pin addresses the SPI bus number at /dev/spidev. // ie: RF24 radio(, *10+); spidev1.0 is 10, spidev1.1 is 11 etc.. - +#define CSN_PIN 0 +#ifdef MRAA + #define CE_PIN 15 // GPIO22 +#else + #define CE_PIN 22 +#endif // Generic: -RF24 radio(22, 0); +RF24 radio(CE_PIN, CSN_PIN); /****************** Linux (BBB,x86,etc) ***********************/ // See http://nRF24.github.io/RF24/pages.html for more information on usage // See http://iotdk.intel.com/docs/master/mraa/ for more information on MRAA From fcad2328eeace16edce02c880a6b01bc8f6324f7 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 6 Mar 2024 23:15:10 -0800 Subject: [PATCH 64/66] adjust python curses usage to reflect C++ curses usage --- examples_linux/scanner.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index 71bda626d..f4c93b783 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -66,25 +66,34 @@ def __init__( # pylint: disable=too-many-arguments,invalid-name cols: int, std_scr: Any, # type: curses.window, label: str, - color: Any, # type: curses.color_pair, + color: int, ): self.x, self.y, self.width, self.win, self.color = (x, y, cols, std_scr, color) - string = label # always labeled in MHz (4 digits) - # -9 for padding, label, & signal count - string += "-" * (self.width - 8) # the empty bar - string += " - " # the initial signal count - self.win.addstr(self.y, self.x, string, self.color) + self.win.move(self.y, self.x) + self.win.attron(curses.color_pair(self.color)) + self.win.addstr(label) # always labeled in MHz (4 digits) + for _ in range(self.width - 8): # draw the empty bar + self.win.addch(curses.ACS_HLINE) + self.win.addstr(" - ") # draw the initial signal count + self.win.attroff(curses.color_pair(self.color)) def update(self, completed: int, signal_count: int): """Update the progress bar.""" - filled = min(CACHE_MAX, completed) / CACHE_MAX - bar = "=" * int((self.width - 8) * filled) - empty = "-" * (self.width - 8 - len(bar)) count = "-" if signal_count: count = "%X" % min(0xF, signal_count) - self.win.addstr(self.y, self.x + 5, bar, curses.color_pair(5)) - self.win.addstr(self.y, self.x + 5 + len(bar), f"{empty} {count} ", self.color) + filled = (self.width - 8) * completed / CACHE_MAX + offset_x = 5 + self.win.move(self.y, self.x + offset_x) + for i in range(offset_x, self.width - 3): + bar_filled = i < (filled + offset_x) + bar_color = 5 if bar_filled else self.color + self.win.attron(curses.color_pair(bar_color)) + self.win.addch("=" if bar_filled else curses.ACS_HLINE) + self.win.attroff(curses.color_pair(bar_color)) + self.win.attron(curses.color_pair(self.color)) + self.win.addstr(f" {count} ") + self.win.attroff(curses.color_pair(self.color)) def init_display(window) -> List[ProgressBar]: @@ -93,7 +102,7 @@ def init_display(window) -> List[ProgressBar]: bar_w = int(curses.COLS / 6) for i in range(21): # 21 rows for j in range(i, i + (21 * 6), 21): # 6 columns - color = curses.color_pair(7) if int(j / 21) % 2 else curses.color_pair(3) + color = 7 if int(j / 21) % 2 else 3 progress_bars[j] = ProgressBar( x=bar_w * int(j / 21), y=i + 3, From 0cf9bedd3cf69fef69f781cad0c9c35b640b0a0d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 7 Mar 2024 00:44:33 -0800 Subject: [PATCH 65/66] adjust scanner.py string formatting --- examples_linux/scanner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples_linux/scanner.py b/examples_linux/scanner.py index f4c93b783..1d01cd7bc 100644 --- a/examples_linux/scanner.py +++ b/examples_linux/scanner.py @@ -71,7 +71,7 @@ def __init__( # pylint: disable=too-many-arguments,invalid-name self.x, self.y, self.width, self.win, self.color = (x, y, cols, std_scr, color) self.win.move(self.y, self.x) self.win.attron(curses.color_pair(self.color)) - self.win.addstr(label) # always labeled in MHz (4 digits) + self.win.addstr(label + " ") # always labeled in MHz (4 digits) for _ in range(self.width - 8): # draw the empty bar self.win.addch(curses.ACS_HLINE) self.win.addstr(" - ") # draw the initial signal count @@ -79,9 +79,9 @@ def __init__( # pylint: disable=too-many-arguments,invalid-name def update(self, completed: int, signal_count: int): """Update the progress bar.""" - count = "-" + count = " - " if signal_count: - count = "%X" % min(0xF, signal_count) + count = " %X " % min(0xF, signal_count) filled = (self.width - 8) * completed / CACHE_MAX offset_x = 5 self.win.move(self.y, self.x + offset_x) @@ -92,7 +92,7 @@ def update(self, completed: int, signal_count: int): self.win.addch("=" if bar_filled else curses.ACS_HLINE) self.win.attroff(curses.color_pair(bar_color)) self.win.attron(curses.color_pair(self.color)) - self.win.addstr(f" {count} ") + self.win.addstr(count) self.win.attroff(curses.color_pair(self.color)) From 3fda357e299f53e682d0d6d91d446af5f24e1c9f Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 7 Mar 2024 02:30:31 -0800 Subject: [PATCH 66/66] use uint8_t iter when printing vertical header (non-curses examples) --- examples/scanner/scanner.ino | 8 ++++---- examples_linux/scanner.cpp | 16 ++++++++-------- examples_pico/scanner.cpp | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/examples/scanner/scanner.ino b/examples/scanner/scanner.ino index 7e2cc580a..c8b800931 100644 --- a/examples/scanner/scanner.ino +++ b/examples/scanner/scanner.ino @@ -215,22 +215,22 @@ void loop(void) { void printHeader() { // Print the hundreds digits - for (int i = 0; i < num_channels; ++i) + for (uint8_t i = 0; i < num_channels; ++i) Serial.print(i / 100); Serial.println(); // Print the tens digits - for (int i = 0; i < num_channels; ++i) + for (uint8_t i = 0; i < num_channels; ++i) Serial.print((i % 100) / 10); Serial.println(); // Print the singles digits - for (int i = 0; i < num_channels; ++i) + for (uint8_t i = 0; i < num_channels; ++i) Serial.print(i % 10); Serial.println(); // Print the header's divider - for (int i = 0; i < num_channels; ++i) + for (uint8_t i = 0; i < num_channels; ++i) Serial.print(F("~")); Serial.println(); } \ No newline at end of file diff --git a/examples_linux/scanner.cpp b/examples_linux/scanner.cpp index bed003f4a..e50becc50 100644 --- a/examples_linux/scanner.cpp +++ b/examples_linux/scanner.cpp @@ -94,7 +94,7 @@ int main(int argc, char** argv) // print a line that should not be wrapped cout << "\n!!! This example requires a width of at least 126 characters. " - << "If this text uses multiple lines, then the output will look bad.\n" + << "If this text uses multiple lines, then the output will look bad." << endl; // set the data rate @@ -179,22 +179,22 @@ int main(int argc, char** argv) void printHeader() { // print the hundreds digits - for (int i = 0; i < num_channels; ++i) - cout << (i / 100); + for (uint8_t i = 0; i < num_channels; ++i) + cout << static_cast(i / 100); cout << endl; // print the tens digits - for (int i = 0; i < num_channels; ++i) - cout << ((i % 100) / 10); + for (uint8_t i = 0; i < num_channels; ++i) + cout << static_cast((i % 100) / 10); cout << endl; // print the singles digits - for (int i = 0; i < num_channels; ++i) - cout << (i % 10); + for (uint8_t i = 0; i < num_channels; ++i) + cout << static_cast(i % 10); cout << endl; // print the header's divider - for (int i = 0; i < num_channels; ++i) + for (uint8_t i = 0; i < num_channels; ++i) cout << '~'; cout << endl; } diff --git a/examples_pico/scanner.cpp b/examples_pico/scanner.cpp index 5b723f5b7..5d43d4d96 100644 --- a/examples_pico/scanner.cpp +++ b/examples_pico/scanner.cpp @@ -183,22 +183,22 @@ void scanChannel(uint8_t channel) void printHeader() { // print the hundreds digits - for (int i = 0; i < num_channels; ++i) + for (uint8_t i = 0; i < num_channels; ++i) printf("%d", (i / 100)); printf("\n"); // print the tens digits - for (int i = 0; i < num_channels; ++i) + for (uint8_t i = 0; i < num_channels; ++i) printf("%d", ((i % 100) / 10)); printf("\n"); // print the singles digits - for (int i = 0; i < num_channels; ++i) + for (uint8_t i = 0; i < num_channels; ++i) printf("%d", (i % 10)); printf("\n"); // print the header's divider - for (int i = 0; i < num_channels; ++i) + for (uint8_t i = 0; i < num_channels; ++i) printf("~"); printf("\n"); }