diff --git a/helpers/radio_scanner_states.h b/helpers/radio_scanner_states.h new file mode 100644 index 0000000..fc3afab --- /dev/null +++ b/helpers/radio_scanner_states.h @@ -0,0 +1,7 @@ +// Enumeration of the possible states of sound, ending with the number of sound states. +typedef enum { + SoundStateOFF, + SoundStateON, + SoundStateSquelch, + SoundStateNum +} SoundState; diff --git a/helpers/scanner_event.h b/helpers/scanner_event.h new file mode 100644 index 0000000..44abc64 --- /dev/null +++ b/helpers/scanner_event.h @@ -0,0 +1,13 @@ +#pragma once + +/** + * Enumeration of scanner events. + */ +typedef enum { + // Scanning + ScannerEventScanDirectionDown, + ScannerEventScanDirectionUp, + ScannerEventToggleScanning, + // Views + ScannerEventViewConfig, +} ScannerEvent; diff --git a/radio_scanner_app.c b/radio_scanner_app.c index 97f36ac..6bd085c 100644 --- a/radio_scanner_app.c +++ b/radio_scanner_app.c @@ -1,4 +1,5 @@ -#include "radio_scanner_app.h" +#include "radio_scanner_app_i.h" + #include #include #include @@ -6,234 +7,40 @@ #include #include -#define TAG "RadioScannerApp" - -#define RADIO_SCANNER_DEFAULT_FREQ 433920000 -#define RADIO_SCANNER_DEFAULT_RSSI (-100.0f) -#define RADIO_SCANNER_DEFAULT_SENSITIVITY (-85.0f) -#define RADIO_SCANNER_BUFFER_SZ 32 - -#define SUBGHZ_FREQUENCY_MIN 300000000 -#define SUBGHZ_FREQUENCY_MAX 928000000 -#define SUBGHZ_FREQUENCY_STEP 10000 -#define SUBGHZ_DEVICE_NAME "cc1101_int" - -static void radio_scanner_draw_callback(Canvas* canvas, void* context) { - furi_assert(canvas); +/** + * Custom event callback for the radio scanner app. + * Passes custom events to the scene manager for handling. + */ +static bool radio_scanner_app_custom_event_callback(void* context, uint32_t event) { furi_assert(context); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Enter radio_scanner_draw_callback"); -#endif - RadioScannerApp* app = (RadioScannerApp*)context; - canvas_clear(canvas); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Radio Scanner"); - - canvas_set_font(canvas, FontSecondary); - char freq_str[RADIO_SCANNER_BUFFER_SZ + 1] = {0}; - snprintf(freq_str, RADIO_SCANNER_BUFFER_SZ, "Freq: %.2f MHz", (double)app->frequency / 1000000); - canvas_draw_str_aligned(canvas, 64, 18, AlignCenter, AlignTop, freq_str); - - char rssi_str[RADIO_SCANNER_BUFFER_SZ + 1] = {0}; - snprintf(rssi_str, RADIO_SCANNER_BUFFER_SZ, "RSSI: %.2f", (double)app->rssi); - canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignTop, rssi_str); - - char sensitivity_str[RADIO_SCANNER_BUFFER_SZ + 1] = {0}; - snprintf(sensitivity_str, RADIO_SCANNER_BUFFER_SZ, "Sens: %.2f", (double)app->sensitivity); - canvas_draw_str_aligned(canvas, 64, 42, AlignCenter, AlignTop, sensitivity_str); - - canvas_draw_str_aligned( - canvas, 64, 54, AlignCenter, AlignTop, app->scanning ? "Scanning..." : "Locked"); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Exit radio_scanner_draw_callback"); -#endif + RadioScannerApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); } -static void radio_scanner_input_callback(InputEvent* input_event, void* context) { +/** + * Back event callback for the radio scanner app. + * Processes back navigation events via the scene manager. + */ +static bool radio_scanner_app_back_event_callback(void* context) { furi_assert(context); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Enter radio_scanner_input_callback"); - FURI_LOG_D(TAG, "Input event: type=%d, key=%d", input_event->type, input_event->key); -#endif - FuriMessageQueue* event_queue = context; - furi_message_queue_put(event_queue, input_event, FuriWaitForever); - FURI_LOG_D(TAG, "Exit radio_scanner_input_callback"); -} - -static void radio_scanner_rx_callback(const void* data, size_t size, void* context) { - UNUSED(data); - UNUSED(context); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "radio_scanner_rx_callback called with size: %zu", size); -#else - UNUSED(size); -#endif + RadioScannerApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); } -static void radio_scanner_update_rssi(RadioScannerApp* app) { - furi_assert(app); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Enter radio_scanner_update_rssi"); -#endif - if(app->radio_device) { - app->rssi = subghz_devices_get_rssi(app->radio_device); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Updated RSSI: %f", (double)app->rssi); -#endif - } else { - FURI_LOG_E(TAG, "Radio device is NULL"); - app->rssi = RADIO_SCANNER_DEFAULT_RSSI; - } -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Exit radio_scanner_update_rssi"); -#endif -} - -static bool radio_scanner_init_subghz(RadioScannerApp* app) { - furi_assert(app); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Enter radio_scanner_init_subghz"); -#endif - subghz_devices_init(); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "SubGHz devices initialized"); -#endif - - const SubGhzDevice* device = subghz_devices_get_by_name(SUBGHZ_DEVICE_NAME); - if(!device) { - FURI_LOG_E(TAG, "Failed to get SubGhzDevice"); - return false; - } - FURI_LOG_I(TAG, "SubGhzDevice obtained: %s", subghz_devices_get_name(device)); - - app->radio_device = device; - - subghz_devices_begin(device); - subghz_devices_reset(device); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "SubGhzDevice begun"); -#endif - if(!subghz_devices_is_frequency_valid(device, app->frequency)) { - FURI_LOG_E(TAG, "Invalid frequency: %lu", app->frequency); - return false; - } -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Frequency is valid: %lu", app->frequency); -#endif - subghz_devices_load_preset(device, FuriHalSubGhzPreset2FSKDev238Async, NULL); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Preset loaded"); -#endif - subghz_devices_set_frequency(device, app->frequency); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Frequency set to %lu", app->frequency); -#endif - subghz_devices_start_async_rx(device, radio_scanner_rx_callback, app); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Asynchronous RX started"); -#endif - if(furi_hal_speaker_acquire(30)) { - app->speaker_acquired = true; - subghz_devices_set_async_mirror_pin(device, &gpio_speaker); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Speaker acquired and async mirror pin set"); -#endif - } else { - app->speaker_acquired = false; - FURI_LOG_E(TAG, "Failed to acquire speaker"); - } -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Exit radio_scanner_init_subghz"); -#endif - return true; -} - -static void radio_scanner_process_scanning(RadioScannerApp* app) { - furi_assert(app); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Enter radio_scanner_process_scanning"); -#endif - radio_scanner_update_rssi(app); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "RSSI after update: %f", (double)app->rssi); -#endif - bool signal_detected = (app->rssi > app->sensitivity); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Signal detected: %d", signal_detected); -#endif - - if(signal_detected) { - if(app->scanning) { - app->scanning = false; -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Scanning stopped"); -#endif - } - } else { - if(!app->scanning) { - app->scanning = true; -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Scanning started"); -#endif - } - } - - if(!app->scanning) { -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Exit radio_scanner_process_scanning"); - return; -#endif - } - uint32_t new_frequency = (app->scan_direction == ScanDirectionUp) ? - app->frequency + SUBGHZ_FREQUENCY_STEP : - app->frequency - SUBGHZ_FREQUENCY_STEP; - - if(!subghz_devices_is_frequency_valid(app->radio_device, new_frequency)) { - if(app->scan_direction == ScanDirectionUp) { - if(new_frequency < 387000000) { - new_frequency = 387000000; - } else if(new_frequency < 779000000) { - new_frequency = 779000000; - } else if(new_frequency > SUBGHZ_FREQUENCY_MAX) { - new_frequency = SUBGHZ_FREQUENCY_MIN; - } - } else { - if(new_frequency > 464000000) { - new_frequency = 464000000; - } else if(new_frequency > 348000000) { - new_frequency = 348000000; - } else if(new_frequency < SUBGHZ_FREQUENCY_MIN) { - new_frequency = SUBGHZ_FREQUENCY_MAX; - } - } -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Adjusted frequency to next valid range: %lu", new_frequency); -#endif - } - - subghz_devices_flush_rx(app->radio_device); - subghz_devices_stop_async_rx(app->radio_device); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Asynchronous RX stopped"); -#endif - - subghz_devices_idle(app->radio_device); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Device set to idle"); -#endif - app->frequency = new_frequency; - subghz_devices_set_frequency(app->radio_device, app->frequency); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Frequency set to %lu", app->frequency); -#endif - - subghz_devices_start_async_rx(app->radio_device, radio_scanner_rx_callback, app); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Asynchronous RX restarted"); - FURI_LOG_D(TAG, "Exit radio_scanner_process_scanning"); -#endif +/** + * Tick event callback for the radio scanner app. + * Invokes the scene manager to handle periodic tick events. + */ +static void radio_scanner_app_tick_event_callback(void* context) { + furi_assert(context); + RadioScannerApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); } +/** + * Allocates and initializes a new instance of the RadioScannerApp. + * Sets up GUI components, state variables, and input handlers. + */ RadioScannerApp* radio_scanner_app_alloc() { #ifdef FURI_DEBUG FURI_LOG_D(TAG, "Enter radio_scanner_app_alloc"); @@ -243,53 +50,53 @@ RadioScannerApp* radio_scanner_app_alloc() { FURI_LOG_E(TAG, "Failed to allocate RadioScannerApp"); return NULL; } -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "RadioScannerApp allocated"); -#endif - app->view_port = view_port_alloc(); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "ViewPort allocated"); -#endif + // GUI + app->gui = furi_record_open(RECORD_GUI); - app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Event queue allocated"); -#endif + // SceneManager + app->scene_manager = scene_manager_alloc(&radio_scanner_scene_handlers, app); + + // ViewDispatcher + app->view_dispatcher = view_dispatcher_alloc(); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback(app->view_dispatcher, radio_scanner_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback(app->view_dispatcher, radio_scanner_app_back_event_callback); + view_dispatcher_set_tick_event_callback(app->view_dispatcher, radio_scanner_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - app->running = true; + // Config + app->config = variable_item_list_alloc(); + view_dispatcher_add_view(app->view_dispatcher, RadioScannerViewConfig, variable_item_list_get_view(app->config)); + + // Scanner + app->scanner = scanner_view_alloc(); + view_dispatcher_add_view(app->view_dispatcher, RadioScannerViewScanner, scanner_view_get_view(app->scanner)); + + // Init app state app->frequency = RADIO_SCANNER_DEFAULT_FREQ; app->rssi = RADIO_SCANNER_DEFAULT_RSSI; app->sensitivity = RADIO_SCANNER_DEFAULT_SENSITIVITY; app->scanning = true; app->scan_direction = ScanDirectionUp; + app->sound_state = SoundStateSquelch; app->speaker_acquired = false; app->radio_device = NULL; - view_port_draw_callback_set(app->view_port, radio_scanner_draw_callback, app); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Draw callback set"); -#endif + scene_manager_next_scene(app->scene_manager, RadioScannerSceneScanner); - view_port_input_callback_set(app->view_port, radio_scanner_input_callback, app->event_queue); #ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Input callback set"); FURI_LOG_D(TAG, "Exit radio_scanner_app_alloc"); #endif - - app->gui = furi_record_open(RECORD_GUI); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "GUI record opened"); -#endif - - gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "ViewPort added to GUI"); -#endif - return app; } +/** + * Frees all resources allocated by the RadioScannerApp instance. + * Cleans up GUI, radio device, event queue, and memory. + */ void radio_scanner_app_free(RadioScannerApp* app) { furi_assert(app); #ifdef FURI_DEBUG @@ -319,27 +126,22 @@ void radio_scanner_app_free(RadioScannerApp* app) { } subghz_devices_deinit(); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "SubGHz devices de-initialized"); -#endif - gui_remove_view_port(app->gui, app->view_port); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "ViewPort removed from GUI"); -#endif - view_port_free(app->view_port); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "ViewPort freed"); -#endif - furi_message_queue_free(app->event_queue); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Event queue freed"); -#endif + // Config + view_dispatcher_remove_view(app->view_dispatcher, RadioScannerViewConfig); + variable_item_list_free(app->config); + + // Scanner + view_dispatcher_remove_view(app->view_dispatcher, RadioScannerViewScanner); + scanner_view_free(app->scanner); + + // ViewDispatcher + view_dispatcher_free(app->view_dispatcher); + + // SceneManager + scene_manager_free(app->scene_manager); furi_record_close(RECORD_GUI); -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "GUI record closed"); -#endif free(app); #ifdef FURI_DEBUG @@ -347,6 +149,10 @@ void radio_scanner_app_free(RadioScannerApp* app) { #endif } +/** + * Main entry point for the radio scanner app. + * Handles main loop, input processing, scanning logic, and cleanup. + */ int32_t radio_scanner_app(void* p) { UNUSED(p); FURI_LOG_I(TAG, "Enter radio_scanner_app"); @@ -366,56 +172,7 @@ int32_t radio_scanner_app(void* p) { FURI_LOG_D(TAG, "SubGHz initialized successfully"); #endif - InputEvent event; - while(app->running) { -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Main loop iteration"); -#endif - if(app->scanning) { -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Scanning is active"); -#endif - radio_scanner_process_scanning(app); - } else { -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Scanning is inactive, updating RSSI"); -#endif - radio_scanner_update_rssi(app); - } - -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Checking for input events"); -#endif - if(furi_message_queue_get(app->event_queue, &event, 10) == FuriStatusOk) { -#ifdef FURI_DEBUG - FURI_LOG_D(TAG, "Input event received: type=%d, key=%d", event.type, event.key); -#endif - if(event.type == InputTypeShort) { - if(event.key == InputKeyOk) { - app->scanning = !app->scanning; - FURI_LOG_I(TAG, "Toggled scanning: %d", app->scanning); - } else if(event.key == InputKeyUp) { - app->sensitivity += 1.0f; - FURI_LOG_I(TAG, "Increased sensitivity: %f", (double)app->sensitivity); - } else if(event.key == InputKeyDown) { - app->sensitivity -= 1.0f; - FURI_LOG_I(TAG, "Decreased sensitivity: %f", (double)app->sensitivity); - } else if(event.key == InputKeyLeft) { - app->scan_direction = ScanDirectionDown; - FURI_LOG_I(TAG, "Scan direction set to down"); - } else if(event.key == InputKeyRight) { - app->scan_direction = ScanDirectionUp; - FURI_LOG_I(TAG, "Scan direction set to up"); - } else if(event.key == InputKeyBack) { - app->running = false; - FURI_LOG_I(TAG, "Exiting app"); - } - } - } - - view_port_update(app->view_port); - furi_delay_ms(10); - } + view_dispatcher_run(app->view_dispatcher); radio_scanner_app_free(app); #ifdef FURI_DEBUG diff --git a/radio_scanner_app.h b/radio_scanner_app.h deleted file mode 100644 index 70b03a3..0000000 --- a/radio_scanner_app.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include -#include - -typedef enum { - ScanDirectionUp, - ScanDirectionDown, -} ScanDirection; - -typedef struct { - Gui* gui; - ViewPort* view_port; - FuriMessageQueue* event_queue; - bool running; - uint32_t frequency; - float rssi; - float sensitivity; - bool scanning; - ScanDirection scan_direction; - const SubGhzDevice* radio_device; - bool speaker_acquired; -} RadioScannerApp; - -RadioScannerApp* radio_scanner_app_alloc(void); -void radio_scanner_app_free(RadioScannerApp* app); -int32_t radio_scanner_app(void* p); diff --git a/radio_scanner_app_i.c b/radio_scanner_app_i.c new file mode 100644 index 0000000..d8a7d97 --- /dev/null +++ b/radio_scanner_app_i.c @@ -0,0 +1,270 @@ +#include "radio_scanner_app_i.h" + +/** + * RX callback triggered on radio packet reception. + * Currently unused beyond debug logging. + */ +void radio_scanner_rx_callback(const void* data, size_t size, void* context) { + UNUSED(data); + UNUSED(context); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "radio_scanner_rx_callback called with size: %zu", size); +#else + UNUSED(size); +#endif +} + +/** + * Updates the RSSI (signal strength) value from the radio device. + */ +void radio_scanner_update_rssi(RadioScannerApp* app) { + furi_assert(app); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Enter radio_scanner_update_rssi"); +#endif + if(app->radio_device) { + app->rssi = subghz_devices_get_rssi(app->radio_device); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Updated RSSI: %f", (double)app->rssi); +#endif + } else { + FURI_LOG_E(TAG, "Radio device is NULL"); + app->rssi = RADIO_SCANNER_DEFAULT_RSSI; + } +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Exit radio_scanner_update_rssi"); +#endif +} + +/** + * Initializes the SubGHz radio device with appropriate settings. + * Sets frequency, loads preset, and begins asynchronous reception. + */ +bool radio_scanner_init_subghz(RadioScannerApp* app) { + furi_assert(app); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Enter radio_scanner_init_subghz"); +#endif + subghz_devices_init(); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "SubGHz devices initialized"); +#endif + + const SubGhzDevice* device = subghz_devices_get_by_name(SUBGHZ_DEVICE_NAME); + if(!device) { + FURI_LOG_E(TAG, "Failed to get SubGhzDevice"); + return false; + } + FURI_LOG_I(TAG, "SubGhzDevice obtained: %s", subghz_devices_get_name(device)); + + app->radio_device = device; + + subghz_devices_begin(device); + subghz_devices_reset(device); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "SubGhzDevice begun"); +#endif + if(!subghz_devices_is_frequency_valid(device, app->frequency)) { + FURI_LOG_E(TAG, "Invalid frequency: %lu", app->frequency); + return false; + } +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Frequency is valid: %lu", app->frequency); +#endif + subghz_devices_load_preset(device, FuriHalSubGhzPreset2FSKDev238Async, NULL); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Preset loaded"); +#endif + subghz_devices_set_frequency(device, app->frequency); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Frequency set to %lu", app->frequency); +#endif + subghz_devices_start_async_rx(device, radio_scanner_rx_callback, app); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Asynchronous RX started"); +#endif + if(furi_hal_speaker_acquire(30)) { + app->speaker_acquired = true; + if(app->sound_state == SoundStateON) { + subghz_devices_set_async_mirror_pin(device, &gpio_speaker); + } else { + subghz_devices_set_async_mirror_pin(device, NULL); + } +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Speaker acquired and async mirror pin set"); +#endif + } else { + app->speaker_acquired = false; + FURI_LOG_E(TAG, "Failed to acquire speaker"); + } +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Exit radio_scanner_init_subghz"); +#endif + return true; +} + +/** + * Core logic for scanning radio frequencies. + * Adjusts frequency up/down and checks for valid signal above sensitivity threshold. + */ +void radio_scanner_process_scanning(RadioScannerApp* app) { + furi_assert(app); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Enter radio_scanner_process_scanning"); +#endif + radio_scanner_update_rssi(app); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "RSSI after update: %f", (double)app->rssi); +#endif + bool signal_detected = (app->rssi > app->sensitivity); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Signal detected: %d", signal_detected); +#endif + + if(signal_detected) { + if(app->scanning) { + app->scanning = false; +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Scanning stopped"); +#endif + } + } else { + if(!app->scanning) { + app->scanning = true; +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Scanning started"); +#endif + } + } + + if(!app->scanning) { +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Exit radio_scanner_process_scanning"); + return; +#endif + } + uint32_t new_frequency = (app->scan_direction == ScanDirectionUp) ? + app->frequency + SUBGHZ_FREQUENCY_STEP : + app->frequency - SUBGHZ_FREQUENCY_STEP; + + if(!subghz_devices_is_frequency_valid(app->radio_device, new_frequency)) { + if(app->scan_direction == ScanDirectionUp) { + if(new_frequency < 387000000) { + new_frequency = 387000000; + } else if(new_frequency < 779000000) { + new_frequency = 779000000; + } else if(new_frequency > SUBGHZ_FREQUENCY_MAX) { + new_frequency = SUBGHZ_FREQUENCY_MIN; + } + } else { + if(new_frequency > 464000000) { + new_frequency = 464000000; + } else if(new_frequency > 348000000) { + new_frequency = 348000000; + } else if(new_frequency < SUBGHZ_FREQUENCY_MIN) { + new_frequency = SUBGHZ_FREQUENCY_MAX; + } + } +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Adjusted frequency to next valid range: %lu", new_frequency); +#endif + } + + subghz_devices_flush_rx(app->radio_device); + subghz_devices_stop_async_rx(app->radio_device); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Asynchronous RX stopped"); +#endif + + subghz_devices_idle(app->radio_device); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Device set to idle"); +#endif + app->frequency = new_frequency; + subghz_devices_set_frequency(app->radio_device, app->frequency); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Frequency set to %lu", app->frequency); +#endif + + subghz_devices_start_async_rx(app->radio_device, radio_scanner_rx_callback, app); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Asynchronous RX restarted"); + FURI_LOG_D(TAG, "Exit radio_scanner_process_scanning"); +#endif +} + +/** + * Updates the squelch state by checking the current RSSI against the configured sensitivity threshold. + */ +void radio_scanner_update_squelch(RadioScannerApp* app) { + if(app->speaker_acquired && app->sound_state == SoundStateSquelch) { + bool signal_detected = (app->rssi > app->sensitivity); + + if(signal_detected) { + subghz_devices_stop_async_rx(app->radio_device); + subghz_devices_set_async_mirror_pin(app->radio_device, &gpio_speaker); + subghz_devices_start_async_rx(app->radio_device, radio_scanner_rx_callback, app); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Squelch activated: enabling audio output"); +#endif + } + else { + subghz_devices_stop_async_rx(app->radio_device); + subghz_devices_set_async_mirror_pin(app->radio_device, NULL); + subghz_devices_start_async_rx(app->radio_device, radio_scanner_rx_callback, app); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Squelch deactivated: disabling audio output"); +#endif + } + } +} + +/** + * Retrieves the current frequency value as a string. + */ +void radio_scanner_get_frequency_str(RadioScannerApp* app, FuriString* frequency_str) { + furi_assert(app); + if(frequency_str != NULL) { + furi_string_printf(frequency_str, "%.2f", (double)app->frequency / 1000000); + } +} + +/** + * Retrieves the current RSSI value as a string. + */ +void radio_scanner_get_rssi_str(RadioScannerApp* app, FuriString* rssi_str) { + furi_assert(app); + if(rssi_str != NULL) { + furi_string_printf(rssi_str, "%.1f", (double)app->rssi); + } +} + +/** + * Retrieves the current sensitivity value as a string. + */ +void radio_scanner_get_sensitivity_str(RadioScannerApp* app, FuriString* sensitivity_str) { + furi_assert(app); + if(sensitivity_str != NULL) { + furi_string_printf(sensitivity_str, "%.1f", (double)app->sensitivity); + } +} + +/** + * Retrieves the scan direction as a string. + */ +void radio_scanner_get_scan_direction_str(RadioScannerApp* app, FuriString* scan_direction_str) { + furi_assert(app); + if(scan_direction_str != NULL) { + furi_string_printf(scan_direction_str, app->scan_direction == ScanDirectionUp ? "+" : "-"); + } +} + +/** + * Retrieves the scanning status as a string. + */ +void radio_scanner_get_scanning_str(RadioScannerApp* app, FuriString* scanning_str) { + furi_assert(app); + if(scanning_str != NULL) { + furi_string_printf(scanning_str, app->scanning ? "Scanning..." : "Locked"); + } +} diff --git a/radio_scanner_app_i.h b/radio_scanner_app_i.h new file mode 100644 index 0000000..03d8cdc --- /dev/null +++ b/radio_scanner_app_i.h @@ -0,0 +1,72 @@ +#pragma once + +#include "helpers/radio_scanner_states.h" +#include "scenes/radio_scanner_scene.h" +#include "views/scanner.h" + +#include +#include +#include +#include +#include +#include +#include + +#define TAG "RadioScannerApp" + +#define RADIO_SCANNER_DEFAULT_FREQ 433920000 +#define RADIO_SCANNER_DEFAULT_RSSI (-100.0f) +#define RADIO_SCANNER_DEFAULT_SENSITIVITY (-85.0f) +#define RADIO_SCANNER_BUFFER_SZ 32 + +#define SUBGHZ_FREQUENCY_MIN 300000000 +#define SUBGHZ_FREQUENCY_MAX 928000000 +#define SUBGHZ_FREQUENCY_STEP 10000 +#define SUBGHZ_DEVICE_NAME "cc1101_int" + +/** + * Enumeration of view types used in the radio scanner app. + */ +typedef enum { + RadioScannerViewConfig, + RadioScannerViewScanner, +} RadioScannerView; + +/** + * Enumeration representing the scanning direction. + */ +typedef enum { + ScanDirectionUp, + ScanDirectionDown, +} ScanDirection; + +/** + * Main structure for the radio scanner app. + */ +typedef struct { + VariableItemList* config; + Gui* gui; + uint32_t frequency; + float rssi; + SceneManager* scene_manager; + float sensitivity; + bool scanning; + ScanDirection scan_direction; + Scanner* scanner; + const SubGhzDevice* radio_device; + SoundState sound_state; + bool speaker_acquired; + ViewDispatcher* view_dispatcher; +} RadioScannerApp; + +void radio_scanner_rx_callback(const void* data, size_t size, void* context); +void radio_scanner_update_rssi(RadioScannerApp* app); +bool radio_scanner_init_subghz(RadioScannerApp* app); +void radio_scanner_process_scanning(RadioScannerApp* app); +void radio_scanner_update_squelch(RadioScannerApp* app); + +void radio_scanner_get_frequency_str(RadioScannerApp* app, FuriString* frequency_str); +void radio_scanner_get_rssi_str(RadioScannerApp* app, FuriString* rssi_str); +void radio_scanner_get_sensitivity_str(RadioScannerApp* app, FuriString* sensitivity_str); +void radio_scanner_get_scan_direction_str(RadioScannerApp* app, FuriString* scan_direction_str); +void radio_scanner_get_scanning_str(RadioScannerApp* app, FuriString* scanning_str); diff --git a/scenes/config.c b/scenes/config.c new file mode 100644 index 0000000..27e9e02 --- /dev/null +++ b/scenes/config.c @@ -0,0 +1,157 @@ +#include "../radio_scanner_app_i.h" + +/** + * Enumeration of configuration indices used for accessing specific settings. + */ +enum ConfigIndex { + ConfigIndexSound, + ConfigIndexSensitivity, +}; + +// Text labels for the sound state values. +const char* const sound_state_text[SoundStateNum] = { + "OFF", + "SQLCH", + "ON", +}; + +// Corresponding enum/int values for sound states. +const uint32_t sound_state_value[SoundStateNum] = { + SoundStateOFF, + SoundStateSquelch, + SoundStateON, +}; + +/** + * Callback to update the sound state when changed in the config UI. + */ +static void config_scene_set_sound_state(VariableItem* item) { + RadioScannerApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, sound_state_text[index]); + + app->sound_state = sound_state_value[index]; + + if(app->speaker_acquired) { + // Stop RX before changing the mirror pin + subghz_devices_stop_async_rx(app->radio_device); + // Update the mirror pin based on the sound state + if(app->sound_state == SoundStateON) { + subghz_devices_set_async_mirror_pin(app->radio_device, &gpio_speaker); + } else { + subghz_devices_set_async_mirror_pin(app->radio_device, NULL); + } + // Restart RX + subghz_devices_start_async_rx(app->radio_device, radio_scanner_rx_callback, app); + } +} + +/** + * Utility function to get the index of a sound state value. + */ +uint8_t config_scene_sound_value_index(const uint32_t value, const uint32_t values[], uint8_t values_count, void* context) { + furi_assert(context); + UNUSED(context); + + for(uint8_t i = 0; i < values_count; i++){ + if(values[i] == value) { + return i; + } + } + + return 0; +} + +/** + * Helper function to update the sensitivity display text. + */ +static void config_scene_set_sensitivity_display_text(VariableItem* item, float sensitivity) { + char buf[8]; + snprintf(buf, sizeof(buf), "%.1f", (double)sensitivity); + variable_item_set_current_value_text(item, buf); +} + +/** + * Helper function to compute the configuration index based on the current sensitivity value. + * + * Sensitivity is a float where 0.0 corresponds to index 0 and -127.0 corresponds to index 127. + * This function clamps the value to the allowed range and returns the proper index. + */ +static uint8_t config_scene_sensitivity_value_index(float sensitivity) { + if(sensitivity > 0.0f) { + sensitivity = 0.0f; + } else if(sensitivity < -127.0f) { + sensitivity = -127.0f; + } + return (uint8_t)(sensitivity + 127.0f); +} + +/** + * Callback to update the sensitivity value when changed in the config UI. + */ +static void config_scene_set_sensitivity(VariableItem* item) { + RadioScannerApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + // Calculate sensitivity: index 0 = -127.0, index 1 = -126.0, ..., index 127 = 0.0 + float sensitivity = -127.0f + (float)index; + app->sensitivity = sensitivity; + + config_scene_set_sensitivity_display_text(item, sensitivity); +} + +/** + * Handler called when entering the config scene. + * Sets the config view callback and switches the view to the config view. + */ +void config_scene_on_enter(void* context) { + RadioScannerApp* app = context; + VariableItem* item; + uint8_t value_index; + + // Sound + item = variable_item_list_add( + app->config, + "Sound:", + SoundStateNum, + config_scene_set_sound_state, + app + ); + value_index = config_scene_sound_value_index(app->sound_state, sound_state_value, SoundStateNum, app); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, sound_state_text[value_index]); + + // Sensitivity + item = variable_item_list_add( + app->config, + "Sensitivity:", + 128, + config_scene_set_sensitivity, + app + ); + uint8_t sensitivity_index = config_scene_sensitivity_value_index(app->sensitivity); + variable_item_set_current_value_index(item, sensitivity_index); + config_scene_set_sensitivity_display_text(item, app->sensitivity); + + view_dispatcher_switch_to_view(app->view_dispatcher, RadioScannerViewConfig); +} + +/** + * Handles events for the config scene. + * Processes custom events. + */ +bool config_scene_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +/** + * Handler called when exiting the config scene. + */ +void config_scene_on_exit(void* context) { + RadioScannerApp* app = context; + variable_item_list_set_selected_item(app->config, 0); + variable_item_list_reset(app->config); +} diff --git a/scenes/radio_scanner_scene.c b/scenes/radio_scanner_scene.c new file mode 100644 index 0000000..1bd3e43 --- /dev/null +++ b/scenes/radio_scanner_scene.c @@ -0,0 +1,38 @@ +#include "../radio_scanner_app_i.h" + +/** + * Generate array of on_enter handler functions for each scene. + */ +#define ADD_SCENE(name, id) name##_scene_on_enter, +void (*const radio_scanner_scene_on_enter_handlers[])(void*) = { +#include "radio_scanner_scene_config.h" +}; +#undef ADD_SCENE + +/** + * Generate array of on_event handler functions for each scene. + */ +#define ADD_SCENE(name, id) name##_scene_on_event, +bool (*const radio_scanner_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "radio_scanner_scene_config.h" +}; +#undef ADD_SCENE + +/** + * Generate array of on_exit handler functions for each scene. + */ +#define ADD_SCENE(name, id) name##_scene_on_exit, +void (*const radio_scanner_scene_on_exit_handlers[])(void* context) = { +#include "radio_scanner_scene_config.h" +}; +#undef ADD_SCENE + +/** + * Scene manager handlers configuration structure for the radio scanner app. + */ +const SceneManagerHandlers radio_scanner_scene_handlers = { + .on_enter_handlers = radio_scanner_scene_on_enter_handlers, + .on_event_handlers = radio_scanner_scene_on_event_handlers, + .on_exit_handlers = radio_scanner_scene_on_exit_handlers, + .scene_num = RadioScannerSceneNum, +}; diff --git a/scenes/radio_scanner_scene.h b/scenes/radio_scanner_scene.h new file mode 100644 index 0000000..95d4a66 --- /dev/null +++ b/scenes/radio_scanner_scene.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +/** + * Generate enumeration of radio scanner scenes and total number of scenes. + */ +#define ADD_SCENE(name, id) RadioScannerScene##id, +typedef enum { +#include "radio_scanner_scene_config.h" + RadioScannerSceneNum, +} RadioScannerScene; +#undef ADD_SCENE + +/** + * Scene manager handlers for the radio scanner app. + */ +extern const SceneManagerHandlers radio_scanner_scene_handlers; + +/** + * Generate declarations for scene on_enter handler functions. + */ +#define ADD_SCENE(name, id) void name##_scene_on_enter(void*); +#include "radio_scanner_scene_config.h" +#undef ADD_SCENE + +/** + * Generate declarations for scene on_event handler functions. + */ +#define ADD_SCENE(name, id) \ + bool name##_scene_on_event(void* context, SceneManagerEvent event); +#include "radio_scanner_scene_config.h" +#undef ADD_SCENE + +/** + * Generate declarations for scene on_exit handler functions. + */ +#define ADD_SCENE(name, id) void name##_scene_on_exit(void* context); +#include "radio_scanner_scene_config.h" +#undef ADD_SCENE diff --git a/scenes/radio_scanner_scene_config.h b/scenes/radio_scanner_scene_config.h new file mode 100644 index 0000000..deab509 --- /dev/null +++ b/scenes/radio_scanner_scene_config.h @@ -0,0 +1,6 @@ +/** + * Configuration for radio scanner scene handlers. + * Defines the available scenes and their corresponding handler identifiers. + */ +ADD_SCENE(config, Config) +ADD_SCENE(scanner, Scanner) diff --git a/scenes/scanner.c b/scenes/scanner.c new file mode 100644 index 0000000..81a8ee8 --- /dev/null +++ b/scenes/scanner.c @@ -0,0 +1,137 @@ +#include "../radio_scanner_app_i.h" +#include "../views/scanner.h" + +/** + * Receiver callback for scanner scene events. + * Sends the received scanner event to the view dispatcher. + */ +void scanner_scene_receiver_callback(ScannerEvent event, void* context) { + furi_assert(context); + RadioScannerApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +/** + * Updates the scanner scene by fetching the latest frequency, RSSI, sensitivity, + * and scanning status, then refreshing the scanner view. + */ +static void scanner_scene_update(void* context) { + RadioScannerApp* app = context; + + FuriString* frequency_str; + FuriString* rssi_str; + FuriString* sensitivity_str; + FuriString* scan_direction_str; + FuriString* scanning_str; + + frequency_str = furi_string_alloc(); + rssi_str = furi_string_alloc(); + sensitivity_str = furi_string_alloc(); + scan_direction_str = furi_string_alloc(); + scanning_str = furi_string_alloc(); + + radio_scanner_get_frequency_str(app, frequency_str); + radio_scanner_get_rssi_str(app, rssi_str); + radio_scanner_get_sensitivity_str(app, sensitivity_str); + radio_scanner_get_scan_direction_str(app, scan_direction_str); + radio_scanner_get_scanning_str(app, scanning_str); + + scanner_view_update( + app->scanner, + furi_string_get_cstr(frequency_str), + furi_string_get_cstr(rssi_str), + furi_string_get_cstr(sensitivity_str), + furi_string_get_cstr(scan_direction_str), + furi_string_get_cstr(scanning_str) + ); + + furi_string_free(frequency_str); + furi_string_free(rssi_str); + furi_string_free(sensitivity_str); + furi_string_free(scan_direction_str); + furi_string_free(scanning_str); +} + +/** + * Handler called when entering the scanner scene. + * Sets the scanner view callback and switches the view to the scanner view. + */ +void scanner_scene_on_enter(void* context) { + RadioScannerApp* app = context; + + scanner_view_set_callback(app->scanner, scanner_scene_receiver_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, RadioScannerViewScanner); +} + +/** + * Handles events for the scanner scene. + * Processes custom events and tick events. + */ +bool scanner_scene_on_event(void* context, SceneManagerEvent event) { + UNUSED(event); + RadioScannerApp* app = context; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + // Scanning + case ScannerEventScanDirectionDown: + app->scan_direction = ScanDirectionDown; + FURI_LOG_I(TAG, "Scan direction set to down"); + consumed = true; + break; + case ScannerEventScanDirectionUp: + app->scan_direction = ScanDirectionUp; + FURI_LOG_I(TAG, "Scan direction set to up"); + consumed = true; + break; + case ScannerEventToggleScanning: + app->scanning = !app->scanning; + FURI_LOG_I(TAG, "Toggled scanning: %d", app->scanning); + consumed = true; + break; + // Views + case ScannerEventViewConfig: + scene_manager_next_scene(app->scene_manager, RadioScannerSceneConfig); + consumed = true; + break; + default: + FURI_LOG_I(TAG, "Unknown event"); + break; + } + } else if(event.type == SceneManagerEventTypeTick) { + if(app->scanning) { + #ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Scanning is active"); + #endif + radio_scanner_process_scanning(app); + } else { + #ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Scanning is inactive, updating RSSI"); + #endif + radio_scanner_update_rssi(app); + } + + if(app->sound_state == SoundStateSquelch) { + #ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Squelch is active, updating sound output"); + #endif + radio_scanner_update_squelch(app); + } + + scanner_scene_update(app); + + consumed = true; + } + + return consumed; +} + +/** + * Handler called when exiting the scanner scene. + */ +void scanner_scene_on_exit(void* context) { + UNUSED(context); +} diff --git a/views/scanner.c b/views/scanner.c new file mode 100644 index 0000000..b45bc4b --- /dev/null +++ b/views/scanner.c @@ -0,0 +1,186 @@ +#include "scanner.h" +#include "../radio_scanner_app_i.h" + +#include + +/** + * Sets the callback function for scanner view events. + */ +void scanner_view_set_callback(Scanner* scanner, ScannerCallback callback, void* context) { + furi_assert(scanner); + furi_assert(callback); + scanner->callback = callback; + scanner->context = context; +} + +/** + * Retrieves the view associated with the scanner. + */ +View* scanner_view_get_view(Scanner* scanner) { + furi_assert(scanner); + return scanner->view; +} + +/** + * Updates the scanner view with new frequency, RSSI, sensitivity, and scanning status strings. + */ +void scanner_view_update(Scanner* scanner, const char* frequency_str, const char* rssi_str, const char* sensitivity_str, const char* scan_direction_str, const char* scanning_str) { + furi_assert(scanner); + with_view_model( + scanner->view, + ScannerModel* model, + { + furi_string_set_str(model->frequency_str, frequency_str); + furi_string_set_str(model->rssi_str, rssi_str); + furi_string_set_str(model->sensitivity_str, sensitivity_str); + furi_string_set_str(model->scan_direction_str, scan_direction_str); + furi_string_set_str(model->scanning_str, scanning_str); + }, + true); +} + +/** + * Draw callback for updating the canvas UI. + * Displays the current frequency, RSSI, sensitivity, and scanning status. + */ +void scanner_view_draw(Canvas* canvas, ScannerModel* model) { + furi_assert(canvas); + furi_assert(model); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Enter scanner_view_draw"); +#endif + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + + elements_button_left(canvas, "Config"); + + canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Radio Scanner"); + + canvas_set_font(canvas, FontSecondary); + char freq_str[RADIO_SCANNER_BUFFER_SZ + 1] = {0}; + snprintf(freq_str, RADIO_SCANNER_BUFFER_SZ, "Freq. (%s):", furi_string_get_cstr(model->scan_direction_str)); + canvas_draw_str_aligned(canvas, 8, 18, AlignLeft, AlignTop, freq_str); + char freq_value_str[RADIO_SCANNER_BUFFER_SZ + 1] = {0}; + snprintf(freq_value_str, RADIO_SCANNER_BUFFER_SZ, "%s MHz", furi_string_get_cstr(model->frequency_str)); + canvas_draw_str_aligned(canvas, 120, 18, AlignRight, AlignTop, freq_value_str); + + char rssi_str[RADIO_SCANNER_BUFFER_SZ + 1] = {0}; + snprintf(rssi_str, RADIO_SCANNER_BUFFER_SZ, "RSSI (%s):", furi_string_get_cstr(model->sensitivity_str)); + canvas_draw_str_aligned(canvas, 8, 30, AlignLeft, AlignTop, rssi_str); + char rssi_value_str[RADIO_SCANNER_BUFFER_SZ + 1] = {0}; + snprintf(rssi_value_str, RADIO_SCANNER_BUFFER_SZ, "%s dBm", furi_string_get_cstr(model->rssi_str)); + canvas_draw_str_aligned(canvas, 120, 30, AlignRight, AlignTop, rssi_value_str); + + canvas_draw_str_aligned(canvas, 64, 42, AlignCenter, AlignTop, furi_string_get_cstr(model->scanning_str)); +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Exit scanner_view_draw"); +#endif +} + +/** + * Input callback for handling button events. + * Passes input events into the event queue for processing. + */ +bool scanner_view_input(InputEvent* event, void* context) { + furi_assert(context); + Scanner* scanner = context; +#ifdef FURI_DEBUG + FURI_LOG_D(TAG, "Enter scanner_view_input"); + FURI_LOG_D(TAG, "Input event: type=%d, key=%d", event->type, event->key); +#endif + bool consumed = false; + if(event->type == InputTypeShort) { + switch(event->key) { + case InputKeyOk: + scanner->callback(ScannerEventToggleScanning, scanner->context); + consumed = true; + break; + case InputKeyLeft: + scanner->callback(ScannerEventViewConfig, scanner->context); + consumed = true; + break; + case InputKeyUp: + scanner->callback(ScannerEventScanDirectionUp, scanner->context); + consumed = true; + break; + case InputKeyDown: + scanner->callback(ScannerEventScanDirectionDown, scanner->context); + consumed = true; + break; + default: + FURI_LOG_I(TAG, "Unknown input"); + break; + } + } + FURI_LOG_D(TAG, "Exit scanner_view_input"); + return consumed; +} + +/** + * Called when entering the scanner view. + */ +void scanner_view_enter(void* context) { + furi_assert(context); +} + +/** + * Called when exiting the scanner view. + */ +void scanner_view_exit(void* context) { + furi_assert(context); +} + +/** + * Allocates and initializes a new Scanner instance. + * Sets up the view, model, and callbacks. + */ +Scanner* scanner_view_alloc() { + Scanner* scanner = malloc(sizeof(Scanner)); + + scanner->view = view_alloc(); + + view_allocate_model(scanner->view, ViewModelTypeLocking, sizeof(ScannerModel)); + view_set_context(scanner->view, scanner); + view_set_draw_callback(scanner->view, (ViewDrawCallback)scanner_view_draw); + view_set_input_callback(scanner->view, scanner_view_input); + view_set_enter_callback(scanner->view, scanner_view_enter); + view_set_exit_callback(scanner->view, scanner_view_exit); + + with_view_model( + scanner->view, + ScannerModel* model, + { + model->frequency_str = furi_string_alloc(); + model->rssi_str = furi_string_alloc(); + model->sensitivity_str = furi_string_alloc(); + model->scan_direction_str = furi_string_alloc(); + model->scanning_str = furi_string_alloc(); + }, + true + ); + + return scanner; +} + +/** + * Frees the resources associated with the Scanner instance. + */ +void scanner_view_free(Scanner* scanner) { + furi_assert(scanner); + + with_view_model( + scanner->view, + ScannerModel * model, + { + furi_string_free(model->frequency_str); + furi_string_free(model->rssi_str); + furi_string_free(model->sensitivity_str); + furi_string_free(model->scan_direction_str); + furi_string_free(model->scanning_str); + }, + false); + + view_free(scanner->view); + + free(scanner); +} diff --git a/views/scanner.h b/views/scanner.h new file mode 100644 index 0000000..b31cd27 --- /dev/null +++ b/views/scanner.h @@ -0,0 +1,45 @@ +#pragma once + +#include "../helpers/scanner_event.h" + +#include + +/** + * Forward declaration for the Scanner structure. + * Represents a scanner view with its associated callbacks and context. + */ +typedef struct Scanner Scanner; + +/** + * Function pointer type for scanner event callbacks. + */ +typedef void (*ScannerCallback)(ScannerEvent event, void* context); + +/** + * Structure representing the scanner view. + */ +struct Scanner { + View* view; + ScannerCallback callback; + void* context; +}; + +/** + * Data model for the scanner view UI. + */ +typedef struct { + FuriString* frequency_str; + FuriString* rssi_str; + FuriString* sensitivity_str; + FuriString* scan_direction_str; + FuriString* scanning_str; +} ScannerModel; + +void scanner_view_set_callback(Scanner* scanner, ScannerCallback callback, void* context); + +View* scanner_view_get_view(Scanner* scanner); + +void scanner_view_update(Scanner* scanner, const char* frequency_str, const char* rssi_str, const char* sensitivity_str, const char* scan_direction_str, const char* scanning_str); + +Scanner* scanner_view_alloc(); +void scanner_view_free(Scanner* scanner);