MX 2 лет назад
Родитель
Сommit
422791f303

+ 7 - 6
ReadMe.md

@@ -13,7 +13,7 @@ Apps contains changes needed to compile them on latest firmware, fixes has been
 
 The Flipper and its community wouldn't be as rich as it is without your contributions and support. Thank you for all you have done.
 
-### Apps checked & updated at `19 Aug 02:18 GMT +3`
+### Apps checked & updated at `20 Aug 04:37 GMT +3`
 
 
 # Default pack
@@ -49,9 +49,9 @@ The Flipper and its community wouldn't be as rich as it is without your contribu
 - **ProtoView** [(by antirez)](https://github.com/antirez/protoview)
 - **SWD Probe** [(by g3gg0)](https://github.com/g3gg0/flipper-swd_probe)
 - IR Scope [(by kallanreed)](https://github.com/DarkFlippers/unleashed-firmware/pull/407)
-- **BadBT** plugin (BT version of BadKB) [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/main/bad_kb) (See in Applications->Tools) - (aka BadUSB via Bluetooth)
+- **BadBT** plugin (BT version of BadKB) [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/Flipper-XFW/Xtreme-Firmware/tree/dev/applications/main/bad_kb) (See in Applications->Tools) - (aka BadUSB via Bluetooth)
 - **Mifare Nested** [(by AloneLiberty)](https://github.com/AloneLiberty/FlipperNested) - Works with PC and python app `FlipperNested`
-- **NFC Maker** plugin (make tags with URLs, Wifi and other things) [(by Willy-JL)](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/applications/external/nfc_maker) 
+- **NFC Maker** plugin (make tags with URLs, Wifi and other things) [(by Willy-JL)](https://github.com/Flipper-XFW/Xtreme-Firmware/tree/dev/applications/external/nfc_maker) 
 - ESP32-CAM -> Camera Suite [(by CodyTolene)](https://github.com/CodyTolene/Flipper-Zero-Camera-Suite)
 - USB Mass Storage [(by hedger & nminaylov)](https://github.com/flipperdevices/flipperzero-good-faps)
 
@@ -119,7 +119,7 @@ Games:
 - [RC2014 ColecoVision (by ezod)](https://github.com/ezod/flipperzero-rc2014-coleco)
 - [ESP Flasher (by 0xchocolate)](https://github.com/0xchocolate/flipperzero-esp-flasher)
 - [ESP32-C6 Gravity terminal (by chris-bc)](https://github.com/chris-bc/Flipper-Gravity)
-- [IFTTT Virtual Button for ESP8266 (by Ferrazzi)](https://github.com/Ferrazzi/FlipperZero_IFTTT_Virtual_Button) - Fixes [(by Willy-JL)](https://github.com/ClaraCrazy/Flipper-Xtreme/commit/ae321fb5f4c616d3965546926b1b4b446eef8d86)
+- [IFTTT Virtual Button for ESP8266 (by Ferrazzi)](https://github.com/Ferrazzi/FlipperZero_IFTTT_Virtual_Button) - Fixes [(by Willy-JL)](https://github.com/Flipper-XFW/Xtreme-Firmware/commit/ae321fb5f4c616d3965546926b1b4b446eef8d86)
 - [Wifi Deauth v2 for ESP8266 (by Timmotools)](https://github.com/Timmotools/flipperzero_esp8266_deautherv2)
 - [Evil captive portal (by bigbrodude6119)](https://github.com/bigbrodude6119/flipper-zero-evil-portal) - WIP -> +2 new features [by leedave](https://github.com/leedave/flipper-zero-evil-portal/tree/leedave/ap_rename)
 - [Flashlight (by xMasterX)](https://github.com/xMasterX/flipper-flashlight)
@@ -151,6 +151,7 @@ Games:
 - [Wii EC Analyser (by csBlueChip)](https://github.com/csBlueChip/FlipperZero_WiiEC)
 - [Wire Tester (by unixispower)](https://gitlab.com/unixispower/flipper-wire-tester)
 - [Atomic Dice Roller (by nmrr)](https://github.com/nmrr/flipperzero-atomicdiceroller)
+- [NRF24 Channel Scanner (by Sil333033)](https://github.com/Flipper-XFW/Xtreme-Firmware/commit/8015ea29a2d4e788b6dee5f7f967dd001534214c)
 
 ## Infrared
 - [IR Remote (by Hong5489)](https://github.com/Hong5489/ir_remote) - improvements [(by friebel)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/535) - Hold Option, RAW support [(by d4ve10)](https://github.com/d4ve10/ir_remote/tree/infrared_hold_option)
@@ -162,8 +163,8 @@ Games:
 - [Flizzer Tracker (by LTVA1)](https://github.com/LTVA1/flizzer_tracker)
 - [Music Beeper (by qqMajiKpp / Haseo)](https://github.com/qqmajikpp/)
 - [Ocarina (by invalidna-me)](https://github.com/invalidna-me/flipperzero-ocarina)
-- [Text 2 SAM (by Round-Pi)](https://github.com/Round-Pi/flipperzero-text2sam) - Fixes [(by Willy-JL)](https://github.com/ClaraCrazy/Flipper-Xtreme/commit/e688f81b53b0138d80de4b609daf1f9fca5be647)
-- [Tuning Fork (by besya)](https://github.com/besya/flipperzero-tuning-fork) - Fixes [(by Willy-JL)](https://github.com/ClaraCrazy/Flipper-Xtreme/commit/44023851f7349b6ae9ca9f9bd9228d795a7e04c0)
+- [Text 2 SAM (by Round-Pi)](https://github.com/Round-Pi/flipperzero-text2sam) - Fixes [(by Willy-JL)](https://github.com/Flipper-XFW/Xtreme-Firmware/commit/e688f81b53b0138d80de4b609daf1f9fca5be647)
+- [Tuning Fork (by besya)](https://github.com/besya/flipperzero-tuning-fork) - Fixes [(by Willy-JL)](https://github.com/Flipper-XFW/Xtreme-Firmware/commit/44023851f7349b6ae9ca9f9bd9228d795a7e04c0)
 - [USB Midi (by DrZlo13)](https://github.com/DrZlo13/flipper-zero-usb-midi)
 - [Video Player (by LTVA1)](https://github.com/LTVA1/flipper-zero-video-player) <- Follow link to download examples and learn how to convert videos
 - [Music Tracker (by DrZlo13)](https://github.com/DrZlo13/flipper-zero-music-tracker)

+ 21 - 0
non_catalog_apps/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",
+            ],
+        ),
+    ],
+)

BIN
non_catalog_apps/nrf24channelscanner/fapicon.png


BIN
non_catalog_apps/nrf24channelscanner/images/Ok_btn_9x9.png


BIN
non_catalog_apps/nrf24channelscanner/images/Pin_back_arrow_10x8.png


+ 117 - 0
non_catalog_apps/nrf24channelscanner/lib/nrf24/nrf24.c

@@ -0,0 +1,117 @@
+#include "nrf24.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <furi_hal_resources.h>
+#include <assert.h>
+#include <string.h>
+
+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;
+    }
+}

+ 129 - 0
non_catalog_apps/nrf24channelscanner/lib/nrf24/nrf24.h

@@ -0,0 +1,129 @@
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+#include <furi_hal_spi.h>
+#include <xtreme.h>
+
+#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

+ 251 - 0
non_catalog_apps/nrf24channelscanner/nrf24channelscanner.c

@@ -0,0 +1,251 @@
+#include <stdio.h>
+#include <furi.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <notification/notification_messages.h>
+#include <nrf24.h>
+#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;
+}

+ 11 - 0
non_catalog_apps/sudoku/README.md

@@ -1 +1,12 @@
 # fz-sudoku
+
+- ok - increment number
+- back - clear number
+- long back - pause game
+- cursor keys - move cursor
+
+<div style="text-align:center"><img src="screenshots/menu.png"/></div>
+
+----------
+
+<div style="text-align:center"><img src="screenshots/main.png"/></div>

+ 22 - 12
non_catalog_apps/sudoku/sudoku.c

@@ -40,6 +40,7 @@ typedef struct {
     uint16_t vertivalFlags;
     GameState state;
     int8_t menuCursor;
+    int8_t lastGameMode;
 } SudokuState;
 
 #define MENU_ITEMS_COUNT 5
@@ -82,10 +83,20 @@ const uint8_t u8g2_font_tom_thumb_4x6_tr[725] =
     "y\11\227\307$\225dJ\0z\7\223\310\254\221\6{\10\227\310\251\32D\1|\6\265\310(\1}\11"
     "\227\310\310\14RR\0~\6\213\313\215\4\0\0\0\4\377\377\0";
 
+static int get_mode_gaps(int index) {
+    if(index <= 0) {
+        return EASY_GAPS;
+    }
+    if(index == 1) {
+        return NORMAL_GAPS;
+    }
+    return HARD_GAPS;
+}
+
 #define SAVE_VERSION 1
 #define SAVE_FILE APP_DATA_PATH("save.dat")
 
-bool load_game(SudokuState* state) {
+static bool load_game(SudokuState* state) {
     Storage* storage = furi_record_open(RECORD_STORAGE);
     File* file = storage_file_alloc(storage);
     bool res = false;
@@ -109,7 +120,7 @@ bool load_game(SudokuState* state) {
     return res;
 }
 
-void save_game(SudokuState* app) {
+static void save_game(SudokuState* app) {
     Storage* storage = furi_record_open(RECORD_STORAGE);
     File* file = storage_file_alloc(storage);
 
@@ -323,7 +334,7 @@ static void shuffle_board(SudokuState* state, int times) {
 }
 
 static void add_gaps(SudokuState* state, int inputCells) {
-    for(int i = 0; i < inputCells; ++i) {
+    for(int i = 0; i <= inputCells; ++i) {
         int x, y;
         do {
             x = furi_hal_random_get() % BOARD_SIZE;
@@ -399,12 +410,13 @@ static bool validate_board(SudokuState* state) {
     return true;
 }
 
-static bool start_game(SudokuState* state, int inputCells) {
+static bool start_game(SudokuState* state) {
+    state->state = GameStateRunning;
     state->cursorX = 0;
     state->cursorY = 0;
     init_board(state);
     shuffle_board(state, 10);
-    add_gaps(state, inputCells);
+    add_gaps(state, get_mode_gaps(state->lastGameMode));
     return validate_board(state);
 }
 
@@ -416,9 +428,10 @@ int32_t sudoku_main(void* p) {
 
     SudokuState* state = malloc(sizeof(SudokuState));
     if(!load_game(state) || state->state == GameStateRestart) {
-        state->state = GameStateRunning;
         state->menuCursor = 0;
-        start_game(state, NORMAL_GAPS);
+        if(state->state != GameStateRestart)
+            state->lastGameMode = 1; // set normal game mode by default, except restart
+        start_game(state);
     }
     state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
     furi_check(state->mutex, "mutex alloc failed");
@@ -455,12 +468,9 @@ int32_t sudoku_main(void* p) {
                     if(state->state == GameStatePaused && state->menuCursor == 0) {
                         state->state = GameStateRunning;
                     } else if(state->menuCursor >= 1 && state->menuCursor <= 3) {
-                        state->state = GameStateRunning;
-                        int gaps = state->menuCursor == 1 ? EASY_GAPS :
-                                   state->menuCursor == 2 ? NORMAL_GAPS :
-                                                            HARD_GAPS;
-                        start_game(state, gaps);
+                        state->lastGameMode = state->menuCursor - 1;
                         state->menuCursor = 0;
+                        start_game(state);
                     } else if(state->menuCursor == 4) {
                         exit = true;
                         break;