diff --git a/applications/external/nrf24channelscanner/application.fam b/applications/external/nrf24channelscanner/application.fam new file mode 100644 index 000000000..af773a1f0 --- /dev/null +++ b/applications/external/nrf24channelscanner/application.fam @@ -0,0 +1,21 @@ +App( + appid="nrf24channelscanner", + name="[NRF24] Channel scanner", + apptype=FlipperAppType.EXTERNAL, + entry_point="nrf24channelscanner_main", + stack_size=2 * 1024, + requires=["gui"], + fap_category="GPIO", + fap_version=(1, 1), + fap_icon_assets="images", + fap_icon="fapicon.png", + fap_description="Scans 2.4Ghz frequency for usage data.", + fap_private_libs=[ + Lib( + name="nrf24", + sources=[ + "nrf24.c", + ], + ), + ], +) diff --git a/applications/external/nrf24channelscanner/fapicon.png b/applications/external/nrf24channelscanner/fapicon.png new file mode 100644 index 000000000..a77435427 Binary files /dev/null and b/applications/external/nrf24channelscanner/fapicon.png differ diff --git a/applications/external/nrf24channelscanner/images/Ok_btn_9x9.png b/applications/external/nrf24channelscanner/images/Ok_btn_9x9.png new file mode 100644 index 000000000..978ec1e83 Binary files /dev/null and b/applications/external/nrf24channelscanner/images/Ok_btn_9x9.png differ diff --git a/applications/external/nrf24channelscanner/images/Pin_back_arrow_10x8.png b/applications/external/nrf24channelscanner/images/Pin_back_arrow_10x8.png new file mode 100644 index 000000000..e46cc5834 Binary files /dev/null and b/applications/external/nrf24channelscanner/images/Pin_back_arrow_10x8.png differ diff --git a/applications/external/nrf24channelscanner/lib/nrf24/nrf24.c b/applications/external/nrf24channelscanner/lib/nrf24/nrf24.c new file mode 100644 index 000000000..611a5dbda --- /dev/null +++ b/applications/external/nrf24channelscanner/lib/nrf24/nrf24.c @@ -0,0 +1,117 @@ +#include "nrf24.h" +#include +#include +#include +#include +#include + +void nrf24_init() { + // this is needed if multiple SPI devices are connected to the same bus but with different CS pins + if(XTREME_SETTINGS()->spi_nrf24_handle == SpiDefault) { + furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull); + furi_hal_gpio_write(&gpio_ext_pc3, true); + } else if(XTREME_SETTINGS()->spi_nrf24_handle == SpiExtra) { + furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull); + furi_hal_gpio_write(&gpio_ext_pa4, true); + } + + furi_hal_spi_bus_handle_init(nrf24_HANDLE); + furi_hal_spi_acquire(nrf24_HANDLE); + furi_hal_gpio_init(nrf24_CE_PIN, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh); + furi_hal_gpio_write(nrf24_CE_PIN, false); +} + +void nrf24_deinit() { + furi_hal_spi_release(nrf24_HANDLE); + furi_hal_spi_bus_handle_deinit(nrf24_HANDLE); + furi_hal_gpio_write(nrf24_CE_PIN, false); + furi_hal_gpio_init(nrf24_CE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + // resetting the CS pins to floating + if(XTREME_SETTINGS()->spi_nrf24_handle == SpiDefault) { + furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeAnalog); + } else if(XTREME_SETTINGS()->spi_nrf24_handle == SpiExtra) { + furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog); + } +} + +void nrf24_spi_trx( + FuriHalSpiBusHandle* handle, + uint8_t* tx, + uint8_t* rx, + uint8_t size, + uint32_t timeout) { + UNUSED(timeout); + furi_hal_gpio_write(handle->cs, false); + furi_hal_spi_bus_trx(handle, tx, rx, size, nrf24_TIMEOUT); + furi_hal_gpio_write(handle->cs, true); +} + +uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) { + uint8_t tx[2] = {W_REGISTER | (REGISTER_MASK & reg), data}; + uint8_t rx[2] = {0}; + nrf24_spi_trx(handle, tx, rx, 2, nrf24_TIMEOUT); + return rx[0]; +} + +uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) { + uint8_t tx[size + 1]; + uint8_t rx[size + 1]; + memset(rx, 0, size + 1); + tx[0] = R_REGISTER | (REGISTER_MASK & reg); + memset(&tx[1], 0, size); + nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT); + memcpy(data, &rx[1], size); + return rx[0]; +} + +uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle) { + uint8_t tx[] = {FLUSH_RX}; + uint8_t rx[] = {0}; + nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT); + return rx[0]; +} + +uint8_t nrf24_get_rdp(FuriHalSpiBusHandle* handle) { + uint8_t rdp; + nrf24_read_reg(handle, REG_RDP, &rdp, 1); + return rdp; +} + +uint8_t nrf24_status(FuriHalSpiBusHandle* handle) { + uint8_t status; + uint8_t tx[] = {R_REGISTER | (REGISTER_MASK & REG_STATUS)}; + nrf24_spi_trx(handle, tx, &status, 1, nrf24_TIMEOUT); + return status; +} + +uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) { + uint8_t status = 0; + uint8_t cfg = 0; + nrf24_read_reg(handle, REG_CONFIG, &cfg, 1); + cfg &= 0xfc; // clear bottom two bits to power down the radio + status = nrf24_write_reg(handle, REG_CONFIG, cfg); + furi_hal_gpio_write(nrf24_CE_PIN, false); + return status; +} + +uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle, bool nodelay) { + uint8_t status = 0; + uint8_t cfg = 0; + nrf24_read_reg(handle, REG_CONFIG, &cfg, 1); + cfg |= 0x03; // PWR_UP, and PRIM_RX + status = nrf24_write_reg(handle, REG_CONFIG, cfg); + furi_hal_gpio_write(nrf24_CE_PIN, true); + if(!nodelay) furi_delay_ms(2000); + return status; +} + +bool nrf24_check_connected(FuriHalSpiBusHandle* handle) { + uint8_t status = nrf24_status(handle); + + if(status != 0x00) { + return true; + } else { + return false; + } +} \ No newline at end of file diff --git a/applications/external/nrf24channelscanner/lib/nrf24/nrf24.h b/applications/external/nrf24channelscanner/lib/nrf24/nrf24.h new file mode 100644 index 000000000..cd1bab2f1 --- /dev/null +++ b/applications/external/nrf24channelscanner/lib/nrf24/nrf24.h @@ -0,0 +1,129 @@ +#pragma once +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define R_REGISTER 0x00 +#define W_REGISTER 0x20 +#define REGISTER_MASK 0x1F +#define ACTIVATE 0x50 +#define R_RX_PL_WID 0x60 +#define R_RX_PAYLOAD 0x61 +#define W_TX_PAYLOAD 0xA0 +#define W_TX_PAYLOAD_NOACK 0xB0 +#define W_ACK_PAYLOAD 0xA8 +#define FLUSH_TX 0xE1 +#define FLUSH_RX 0xE2 +#define REUSE_TX_PL 0xE3 +#define RF24_NOP 0xFF + +#define REG_CONFIG 0x00 +#define REG_EN_AA 0x01 +#define REG_EN_RXADDR 0x02 +#define REG_SETUP_AW 0x03 +#define REG_SETUP_RETR 0x04 +#define REG_RDP 0x09 +#define REG_DYNPD 0x1C +#define REG_FEATURE 0x1D +#define REG_RF_SETUP 0x06 +#define REG_STATUS 0x07 +#define REG_RX_ADDR_P0 0x0A +#define REG_RF_CH 0x05 +#define REG_TX_ADDR 0x10 + +#define RX_PW_P0 0x11 +#define TX_DS 0x20 +#define MAX_RT 0x10 + +#define nrf24_TIMEOUT 500 +#define nrf24_CE_PIN &gpio_ext_pb2 +#define nrf24_HANDLE \ + (XTREME_SETTINGS()->spi_nrf24_handle == SpiDefault ? &furi_hal_spi_bus_handle_external : \ + &furi_hal_spi_bus_handle_external_extra) + +/* Low level API */ + +/** Write device register + * + * @param handle - pointer to FuriHalSpiHandle + * @param reg - register + * @param data - data to write + * + * @return device status + */ +uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data); + +/** Read device register + * + * @param handle - pointer to FuriHalSpiHandle + * @param reg - register + * @param[out] data - pointer to data + * + * @return device status + */ +uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size); + +/** Power down the radio + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle); + +/** Sets the radio to RX mode + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle, bool nodelay); + +/*=============================================================================================================*/ + +/* High level API */ + +/** Must call this before using any other nrf24 API + * + */ +void nrf24_init(); + +/** Must call this when we end using nrf24 device + * + */ +void nrf24_deinit(); + +/** Send flush rx command + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return device status + */ +uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle); + +/** Gets RDP from register 0x09 + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return RDP from register 0x09 + */ +uint8_t nrf24_get_rdp(FuriHalSpiBusHandle* handle); + +/** Gets the current status flags from the STATUS register + * + * @param handle - pointer to FuriHalSpiHandle + * + * @return status flags + */ +uint8_t nrf24_status(FuriHalSpiBusHandle* handle); + +bool nrf24_check_connected(FuriHalSpiBusHandle* handle); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/external/nrf24channelscanner/nrf24channelscanner.c b/applications/external/nrf24channelscanner/nrf24channelscanner.c new file mode 100644 index 000000000..b44fe6ea5 --- /dev/null +++ b/applications/external/nrf24channelscanner/nrf24channelscanner.c @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include +#include +#include +#include "nrf24channelscanner_icons.h" + +const uint8_t num_channels = 128; +static uint8_t nrf24values[128] = {0}; //to store channel data + +bool ifNotFoundNrf = false; //to show error message +bool szuz = true; //to show welcome screen +static bool isScanning = false; //to track the progress +static bool stopNrfScan = false; //to exit thread + +static bool threadStoppedsoFree = false; //indicate if I can free the thread from ram. +static uint8_t currCh = 0; //for the progress bar or the channel selector + +static int delayPerChan = 5; //can set via up / down. + +bool showFreq = true; + +FuriThread* thread; + +typedef enum { + EventTypeKey, + EventTypeTick, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} Event; + +static void draw_callback(Canvas* canvas, void* ctx) { + UNUSED(ctx); + + canvas_clear(canvas); + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 100, 0, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 112, 8, "Exit"); + canvas_draw_icon(canvas, 1, 0, &I_Ok_btn_9x9); + canvas_set_font(canvas, FontSecondary); + if(isScanning) { + canvas_draw_str(canvas, 12, 8, "Stop"); + } else { + canvas_draw_str(canvas, 12, 8, "Scan"); + } + canvas_draw_line(canvas, 0, 11, 127, 11); + + if(ifNotFoundNrf) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 23, 35, "NRF24 not found!"); + return; + } + + canvas_draw_line(canvas, currCh, 12, currCh, 13); //draw the current channel + + //draw hello mesage + if(szuz) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 1, 22, "Up / Down to change channel time."); + canvas_draw_str(canvas, 1, 36, "Left / Right to select channel,"); + canvas_draw_str(canvas, 1, 48, "to get it's frequency"); + } + + //draw freq ir the progress + canvas_set_font(canvas, FontSecondary); + if(isScanning) { + canvas_draw_str(canvas, 37, 8, "scanning"); + } else { + if(showFreq) { + int freq = 2400 + currCh; + char strfreq[10] = {32}; + itoa(freq, strfreq, 10); + strfreq[4] = ' '; + strfreq[5] = 'M'; + strfreq[6] = 'H'; + strfreq[7] = 'Z'; + strfreq[8] = 0; + canvas_draw_str(canvas, 40, 8, strfreq); + } else { + //show delay + int dly = delayPerChan; + char strdel[10] = {32}; + itoa(dly, strdel, 10); + if(dly < 10) strdel[1] = ' '; + if(dly < 100) strdel[2] = ' '; + if(dly < 1000) strdel[3] = ' '; + strdel[4] = ' '; + strdel[5] = 'm'; + strdel[6] = 's'; + strdel[7] = 0; + canvas_draw_str(canvas, 40, 8, strdel); + } + } + + //draw the chart + for(int i = 0; i < num_channels; ++i) { + int h = 64 - nrf24values[i]; + if(h < 11) h = 12; + canvas_draw_line(canvas, i, h, i, 64); + } +} + +static void input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + Event event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static int32_t scanner(void* context) { + UNUSED(context); + isScanning = true; + stopNrfScan = false; + threadStoppedsoFree = false; + uint8_t tmp = 0; + currCh = 0; + nrf24_set_rx_mode(nrf24_HANDLE, false); + nrf24_write_reg(nrf24_HANDLE, REG_EN_AA, 0x0); + nrf24_write_reg(nrf24_HANDLE, REG_RF_SETUP, 0x0f); + for(uint8_t j = 0; j < 15;) { //scan until stopped! + if(stopNrfScan) break; + for(uint8_t i = 0; i < num_channels; i++) { + if(stopNrfScan) break; + currCh = i; + nrf24_write_reg(nrf24_HANDLE, REG_RF_CH, i); + nrf24_set_rx_mode(nrf24_HANDLE, true); + for(uint8_t ii = 0; ii < 3; ++ii) { + nrf24_flush_rx(nrf24_HANDLE); + furi_delay_ms(delayPerChan); + tmp = nrf24_get_rdp(nrf24_HANDLE); + if(tmp > 0) nrf24values[i]++; + if(nrf24values[i] > 50) j = 254; //stop, bc maxed + } + } + } + nrf24_set_idle(nrf24_HANDLE); + isScanning = false; + threadStoppedsoFree = true; + currCh = 0; + return 0; +} + +void ChangeFreq(int delta) { + currCh += delta; + if(currCh > num_channels) currCh = 0; + showFreq = true; +} + +void ChangeDelay(int delta) { + delayPerChan += delta; + if(delayPerChan > 100) delayPerChan = 100; + if(delayPerChan < 1) delayPerChan = 1; + if(delayPerChan == 11) delayPerChan = 10; //to get it rounded :) + if(delayPerChan == 6) delayPerChan = 5; //to get it rounded :) + showFreq = false; +} + +// Main entry of the application +int32_t nrf24channelscanner_main(void* p) { + UNUSED(p); + + Event event; + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(Event)); + + nrf24_init(); + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, draw_callback, NULL); + view_port_input_callback_set(view_port, input_callback, event_queue); + + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + + while(true) { + furi_check(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk); + + if(event.type == EventTypeKey) { + szuz = false; //hit any button, so hide welcome screen + if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) { + if(isScanning) { + stopNrfScan = true; //if running, stop it. + notification_message(notification, &sequence_blink_yellow_100); + furi_thread_join(thread); + furi_thread_free(thread); + } + break; + } + if(event.input.type == InputTypeShort && event.input.key == InputKeyOk) { + if(isScanning) { + notification_message(notification, &sequence_blink_yellow_100); + stopNrfScan = true; + furi_thread_join(thread); + furi_thread_free(thread); + threadStoppedsoFree = false; //to prevent double free + continue; + } + memset(nrf24values, 0, sizeof(nrf24values)); + if(nrf24_check_connected(nrf24_HANDLE)) { + threadStoppedsoFree = false; + ifNotFoundNrf = false; + notification_message(notification, &sequence_blink_green_100); + thread = furi_thread_alloc(); + furi_thread_set_name(thread, "nrfscannerth"); + furi_thread_set_stack_size(thread, 1024); + furi_thread_set_callback(thread, scanner); + furi_thread_start(thread); + } else { + ifNotFoundNrf = true; + notification_message(notification, &sequence_error); + } + } + //change the delay + if(event.input.type == InputTypeShort && event.input.key == InputKeyUp) { + ChangeDelay(5); + } + if(event.input.type == InputTypeShort && event.input.key == InputKeyDown) { + ChangeDelay(-5); + } + + if(!isScanning) { + if(event.input.type == InputTypeLong && event.input.key == InputKeyLeft) + ChangeFreq(-10); + if(event.input.type == InputTypeShort && event.input.key == InputKeyLeft) + ChangeFreq(-1); + if(event.input.type == InputTypeLong && event.input.key == InputKeyRight) + ChangeFreq(10); + if(event.input.type == InputTypeShort && event.input.key == InputKeyRight) + ChangeFreq(1); + } + } + if(threadStoppedsoFree) { + threadStoppedsoFree = false; + furi_thread_join(thread); + furi_thread_free(thread); + } + } + nrf24_deinit(); + furi_message_queue_free(event_queue); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_record_close(RECORD_GUI); + return 0; +} \ No newline at end of file