mirror of
https://protopirate.net/ProtoPirate/ProtoPirate.git
synced 2026-03-29 11:00:40 +00:00
528 lines
18 KiB
C
528 lines
18 KiB
C
// protopirate_app.c
|
|
#include "protopirate_app_i.h"
|
|
|
|
#include <furi.h>
|
|
#include <furi_hal.h>
|
|
#include "protocols/protocol_items.h"
|
|
#include "helpers/protopirate_settings.h"
|
|
#include "helpers/protopirate_storage.h"
|
|
#include "protocols/keys.h"
|
|
#include <string.h>
|
|
|
|
#define TAG "ProtoPirateApp"
|
|
#define LOG_HEAP(msg) FURI_LOG_I(TAG, "%s - Free heap: %zu", msg, memmgr_get_free_heap())
|
|
|
|
static bool protopirate_app_custom_event_callback(void* context, uint32_t event) {
|
|
furi_check(context);
|
|
ProtoPirateApp* app = context;
|
|
return scene_manager_handle_custom_event(app->scene_manager, event);
|
|
}
|
|
|
|
static bool protopirate_app_back_event_callback(void* context) {
|
|
furi_check(context);
|
|
ProtoPirateApp* app = context;
|
|
return scene_manager_handle_back_event(app->scene_manager);
|
|
}
|
|
|
|
static void protopirate_app_tick_event_callback(void* context) {
|
|
furi_check(context);
|
|
ProtoPirateApp* app = context;
|
|
scene_manager_handle_tick_event(app->scene_manager);
|
|
}
|
|
|
|
ProtoPirateApp* protopirate_app_alloc() {
|
|
LOG_HEAP("Pre alloc");
|
|
ProtoPirateApp* app = malloc(sizeof(ProtoPirateApp));
|
|
if(!app) {
|
|
FURI_LOG_E(TAG, "Failed to allocate ProtoPirateApp app !");
|
|
return NULL;
|
|
}
|
|
|
|
LOG_HEAP("Start alloc");
|
|
FURI_LOG_I(TAG, "Allocating ProtoPirate Decoder App");
|
|
|
|
// GUI
|
|
app->gui = furi_record_open(RECORD_GUI);
|
|
LOG_HEAP("After GUI");
|
|
|
|
// View Dispatcher
|
|
app->view_dispatcher = view_dispatcher_alloc();
|
|
#if defined(FW_ORIGIN_RM)
|
|
view_dispatcher_enable_queue(app->view_dispatcher);
|
|
#endif
|
|
app->scene_manager = scene_manager_alloc(&protopirate_scene_handlers, app);
|
|
|
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
|
view_dispatcher_set_custom_event_callback(
|
|
app->view_dispatcher, protopirate_app_custom_event_callback);
|
|
view_dispatcher_set_navigation_event_callback(
|
|
app->view_dispatcher, protopirate_app_back_event_callback);
|
|
view_dispatcher_set_tick_event_callback(
|
|
app->view_dispatcher, protopirate_app_tick_event_callback, 100);
|
|
|
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
|
LOG_HEAP("After ViewDispatcher");
|
|
|
|
// Open Notification record
|
|
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
|
|
|
// Open Dialogs record
|
|
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
|
|
|
// Variable Item List
|
|
app->variable_item_list = variable_item_list_alloc();
|
|
view_dispatcher_add_view(
|
|
app->view_dispatcher,
|
|
ProtoPirateViewVariableItemList,
|
|
variable_item_list_get_view(app->variable_item_list));
|
|
|
|
// SubMenu
|
|
app->submenu = submenu_alloc();
|
|
view_dispatcher_add_view(
|
|
app->view_dispatcher, ProtoPirateViewSubmenu, submenu_get_view(app->submenu));
|
|
|
|
// Widget
|
|
app->widget = widget_alloc();
|
|
view_dispatcher_add_view(
|
|
app->view_dispatcher, ProtoPirateViewWidget, widget_get_view(app->widget));
|
|
|
|
// File Browser path
|
|
app->file_path = furi_string_alloc();
|
|
furi_string_set(app->file_path, PROTOPIRATE_APP_FOLDER);
|
|
|
|
// About View
|
|
app->view_about = view_alloc();
|
|
view_dispatcher_add_view(app->view_dispatcher, ProtoPirateViewAbout, app->view_about);
|
|
|
|
LOG_HEAP("After basic views");
|
|
|
|
// Load saved settings
|
|
ProtoPirateSettings settings;
|
|
protopirate_settings_load(&settings);
|
|
|
|
// Apply auto-save setting
|
|
app->auto_save = settings.auto_save;
|
|
app->tx_power = settings.tx_power;
|
|
|
|
// Receiver Views
|
|
app->protopirate_receiver = protopirate_view_receiver_alloc(app->auto_save);
|
|
view_dispatcher_add_view(
|
|
app->view_dispatcher,
|
|
ProtoPirateViewReceiver,
|
|
protopirate_view_receiver_get_view(app->protopirate_receiver));
|
|
|
|
app->protopirate_receiver_info = protopirate_view_receiver_info_alloc();
|
|
view_dispatcher_add_view(
|
|
app->view_dispatcher,
|
|
ProtoPirateViewReceiverInfo,
|
|
protopirate_view_receiver_info_get_view(app->protopirate_receiver_info));
|
|
|
|
LOG_HEAP("After receiver views");
|
|
|
|
// Init setting - KEEP THIS, it's small
|
|
app->setting = subghz_setting_alloc();
|
|
app->loaded_file_path = NULL;
|
|
app->start_tx_time = 0;
|
|
subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user"));
|
|
|
|
LOG_HEAP("After subghz_setting");
|
|
|
|
// Apply loaded frequency and preset, with validation
|
|
uint32_t frequency = settings.frequency;
|
|
uint8_t preset_index = settings.preset_index;
|
|
|
|
// Validate frequency
|
|
bool frequency_valid = false;
|
|
for(size_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) {
|
|
if(subghz_setting_get_frequency(app->setting, i) == frequency) {
|
|
frequency_valid = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!frequency_valid) {
|
|
frequency = subghz_setting_get_default_frequency(app->setting);
|
|
FURI_LOG_W(TAG, "Saved frequency invalid, using default: %lu", frequency);
|
|
}
|
|
|
|
// Validate preset index
|
|
if(preset_index >= subghz_setting_get_preset_count(app->setting)) {
|
|
preset_index = 0;
|
|
FURI_LOG_W(TAG, "Saved preset index invalid, using default");
|
|
}
|
|
|
|
// Initialize TxRx structure with minimal setup
|
|
app->lock = ProtoPirateLockOff;
|
|
app->txrx = malloc(sizeof(ProtoPirateTxRx));
|
|
memset(app->txrx, 0, sizeof(ProtoPirateTxRx));
|
|
|
|
app->txrx->preset = malloc(sizeof(SubGhzRadioPreset));
|
|
app->txrx->preset->name = furi_string_alloc();
|
|
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
|
|
app->txrx->rx_key_state = ProtoPirateRxKeyStateIDLE;
|
|
|
|
// Get preset name and data
|
|
const char* preset_name = subghz_setting_get_preset_name(app->setting, preset_index);
|
|
uint8_t* preset_data = subghz_setting_get_preset_data(app->setting, preset_index);
|
|
size_t preset_data_size = subghz_setting_get_preset_data_size(app->setting, preset_index);
|
|
|
|
FURI_LOG_I(
|
|
TAG,
|
|
"Settings: freq=%lu, preset=%s, auto_save=%d, hopping=%d",
|
|
frequency,
|
|
preset_name,
|
|
settings.auto_save,
|
|
settings.hopping_enabled);
|
|
|
|
protopirate_preset_init(app, preset_name, frequency, preset_data, preset_data_size);
|
|
|
|
// Apply hopping state from settings
|
|
app->txrx->hopper_state = settings.hopping_enabled ? ProtoPirateHopperStateRunning :
|
|
ProtoPirateHopperStateOFF;
|
|
app->txrx->hopper_idx_frequency = 0;
|
|
app->txrx->hopper_timeout = 0;
|
|
app->txrx->idx_menu_chosen = 0;
|
|
|
|
LOG_HEAP("After txrx basic setup");
|
|
|
|
// Mark as not initialized
|
|
app->radio_initialized = false;
|
|
|
|
if(!protopirate_radio_init(app)) {
|
|
FURI_LOG_E(TAG, "Failed to initialize radio!");
|
|
notification_message(app->notifications, &sequence_error);
|
|
protopirate_app_free(app);
|
|
return NULL;
|
|
}
|
|
|
|
FURI_LOG_D(TAG, "Initial state: radio_initialized=%d", app->radio_initialized);
|
|
|
|
LOG_HEAP("App alloc complete (radio deferred)");
|
|
|
|
return app;
|
|
}
|
|
|
|
// Initialize radio subsystem - call from receiver scene
|
|
bool protopirate_radio_init(ProtoPirateApp* app) {
|
|
FURI_LOG_I(TAG, "=== protopirate_radio_init called ===");
|
|
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
|
|
|
|
if(app->radio_initialized) {
|
|
FURI_LOG_D(TAG, "Radio already initialized, returning true");
|
|
return true;
|
|
}
|
|
|
|
// Fresh radio init - nothing was initialized before
|
|
FURI_LOG_I(TAG, "Fresh radio init - allocating all components");
|
|
LOG_HEAP("Radio init start");
|
|
|
|
// Create environment with our custom protocols
|
|
app->txrx->environment = subghz_environment_alloc();
|
|
if(!app->txrx->environment) {
|
|
FURI_LOG_E(TAG, "Failed to allocate environment!");
|
|
return false;
|
|
}
|
|
LOG_HEAP("After environment alloc");
|
|
|
|
FURI_LOG_I(TAG, "Registering %zu ProtoPirate protocols", protopirate_protocol_registry.size);
|
|
subghz_environment_set_protocol_registry(
|
|
app->txrx->environment, (void*)&protopirate_protocol_registry);
|
|
|
|
// Load keystores
|
|
subghz_environment_load_keystore(app->txrx->environment, PROTOPIRATE_KEYSTORE_DIR_NAME);
|
|
LOG_HEAP("After keystore load");
|
|
|
|
// Load ProtoPirate specific keys
|
|
protopirate_keys_load(app->txrx->environment);
|
|
FURI_LOG_I(TAG, "Loaded ProtoPirate secure keys");
|
|
LOG_HEAP("After keys load");
|
|
|
|
// Create receiver
|
|
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
|
|
if(!app->txrx->receiver) {
|
|
FURI_LOG_E(TAG, "Failed to allocate receiver!");
|
|
subghz_environment_free(app->txrx->environment);
|
|
app->txrx->environment = NULL;
|
|
return false;
|
|
}
|
|
LOG_HEAP("After receiver alloc");
|
|
|
|
// Initialize SubGhz devices
|
|
subghz_devices_init();
|
|
FURI_LOG_D(TAG, "SubGhz devices initialized");
|
|
|
|
// Try external CC1101 first
|
|
app->txrx->radio_device = radio_device_loader_set(NULL, SubGhzRadioDeviceTypeExternalCC1101);
|
|
|
|
// if not loading, fallback to internal
|
|
if(!app->txrx->radio_device) {
|
|
FURI_LOG_W(TAG, "External CC1101 not found, trying internal radio");
|
|
app->txrx->radio_device = radio_device_loader_set(NULL, SubGhzRadioDeviceTypeInternal);
|
|
}
|
|
|
|
if(!app->txrx->radio_device) {
|
|
FURI_LOG_E(TAG, "Failed to initialize any radio device!");
|
|
subghz_receiver_free(app->txrx->receiver);
|
|
app->txrx->receiver = NULL;
|
|
subghz_environment_free(app->txrx->environment);
|
|
app->txrx->environment = NULL;
|
|
subghz_devices_deinit();
|
|
return false;
|
|
}
|
|
#ifndef REMOVE_LOGS
|
|
const char* device_name = subghz_devices_get_name(app->txrx->radio_device);
|
|
bool is_external = device_name && strstr(device_name, "ext");
|
|
FURI_LOG_I(
|
|
TAG,
|
|
"Radio device initialized: %s (%s)",
|
|
device_name ? device_name : "unknown",
|
|
is_external ? "external" : "internal");
|
|
#endif
|
|
subghz_devices_reset(app->txrx->radio_device);
|
|
subghz_devices_idle(app->txrx->radio_device);
|
|
|
|
// Set filter to accept decodable protocols
|
|
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
|
|
|
|
app->radio_initialized = true;
|
|
|
|
FURI_LOG_D(TAG, "Final state: radio_initialized=%d", app->radio_initialized);
|
|
|
|
LOG_HEAP("Radio init complete");
|
|
|
|
return true;
|
|
}
|
|
|
|
// Deinitialize radio subsystem
|
|
void protopirate_radio_deinit(ProtoPirateApp* app) {
|
|
FURI_LOG_I(TAG, "=== protopirate_radio_deinit called ===");
|
|
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
|
|
FURI_LOG_D(
|
|
TAG,
|
|
"Pointers: worker=%p, environment=%p, receiver=%p, history=%p, radio_device=%p",
|
|
app->txrx->worker,
|
|
app->txrx->environment,
|
|
app->txrx->receiver,
|
|
app->txrx->history,
|
|
app->txrx->radio_device);
|
|
|
|
if(!app->radio_initialized) {
|
|
FURI_LOG_D(TAG, "Radio was not initialized, returning");
|
|
return;
|
|
}
|
|
|
|
LOG_HEAP("Radio deinit start");
|
|
|
|
// Make sure we're not receiving
|
|
if(app->txrx->worker && app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
|
|
FURI_LOG_D(TAG, "Stopping active RX, state=%d", app->txrx->txrx_state);
|
|
subghz_worker_stop(app->txrx->worker);
|
|
if(app->txrx->radio_device) {
|
|
subghz_devices_stop_async_rx(app->txrx->radio_device);
|
|
}
|
|
}
|
|
|
|
if(app->txrx->radio_device) {
|
|
FURI_LOG_D(TAG, "Putting radio device to sleep and ending: %p", app->txrx->radio_device);
|
|
subghz_devices_sleep(app->txrx->radio_device);
|
|
radio_device_loader_end(app->txrx->radio_device);
|
|
app->txrx->radio_device = NULL;
|
|
} else {
|
|
FURI_LOG_D(TAG, "Radio device was NULL, skipping sleep/end");
|
|
}
|
|
|
|
FURI_LOG_D(TAG, "Calling subghz_devices_deinit");
|
|
subghz_devices_deinit();
|
|
|
|
if(app->txrx->receiver) {
|
|
FURI_LOG_D(TAG, "Freeing receiver %p", app->txrx->receiver);
|
|
subghz_receiver_free(app->txrx->receiver);
|
|
app->txrx->receiver = NULL;
|
|
} else {
|
|
FURI_LOG_D(TAG, "Receiver was NULL, skipping free");
|
|
}
|
|
|
|
if(app->txrx->environment) {
|
|
FURI_LOG_D(TAG, "Freeing environment %p", app->txrx->environment);
|
|
subghz_environment_free(app->txrx->environment);
|
|
app->txrx->environment = NULL;
|
|
} else {
|
|
FURI_LOG_D(TAG, "Environment was NULL, skipping free");
|
|
}
|
|
|
|
if(app->txrx->history) {
|
|
FURI_LOG_D(TAG, "Freeing history %p", app->txrx->history);
|
|
protopirate_history_free(app->txrx->history);
|
|
app->txrx->history = NULL;
|
|
} else {
|
|
FURI_LOG_D(TAG, "History was NULL, skipping free");
|
|
}
|
|
|
|
if(app->txrx->worker) {
|
|
FURI_LOG_D(TAG, "Freeing worker %p", app->txrx->worker);
|
|
subghz_worker_free(app->txrx->worker);
|
|
app->txrx->worker = NULL;
|
|
} else {
|
|
FURI_LOG_D(TAG, "Worker was NULL, skipping free");
|
|
}
|
|
|
|
app->radio_initialized = false;
|
|
|
|
FURI_LOG_D(TAG, "Final state: radio_initialized=%d", app->radio_initialized);
|
|
LOG_HEAP("Radio deinit complete");
|
|
}
|
|
|
|
void protopirate_app_free(ProtoPirateApp* app) {
|
|
furi_check(app);
|
|
|
|
FURI_LOG_I(TAG, "=== protopirate_app_free called ===");
|
|
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
|
|
|
|
// Save settings before exiting
|
|
ProtoPirateSettings settings;
|
|
settings.frequency = app->txrx->preset->frequency;
|
|
settings.auto_save = app->auto_save;
|
|
settings.tx_power = app->tx_power;
|
|
settings.hopping_enabled = (app->txrx->hopper_state != ProtoPirateHopperStateOFF);
|
|
|
|
// Find current preset index
|
|
settings.preset_index = 0;
|
|
const char* current_preset = furi_string_get_cstr(app->txrx->preset->name);
|
|
for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) {
|
|
if(strcmp(subghz_setting_get_preset_name(app->setting, i), current_preset) == 0) {
|
|
settings.preset_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
FURI_LOG_I(
|
|
TAG,
|
|
"Saving settings: freq=%lu, preset=%u, auto_save=%d, hopping=%d",
|
|
settings.frequency,
|
|
settings.preset_index,
|
|
settings.auto_save,
|
|
settings.hopping_enabled);
|
|
|
|
protopirate_settings_save(&settings);
|
|
|
|
// Deinitialize whichever is active - NULL checks inside handle all cases
|
|
FURI_LOG_D(TAG, "Calling radio_deinit");
|
|
protopirate_radio_deinit(app);
|
|
|
|
if(app->loaded_file_path) {
|
|
FURI_LOG_D(TAG, "Freeing loaded_file_path");
|
|
furi_string_free(app->loaded_file_path);
|
|
app->loaded_file_path = NULL;
|
|
}
|
|
|
|
// Submenu
|
|
FURI_LOG_D(TAG, "Removing submenu view");
|
|
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewSubmenu);
|
|
submenu_free(app->submenu);
|
|
|
|
// Variable Item List
|
|
FURI_LOG_D(TAG, "Removing variable_item_list view");
|
|
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList);
|
|
variable_item_list_free(app->variable_item_list);
|
|
|
|
// About View
|
|
FURI_LOG_D(TAG, "Removing about view");
|
|
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewAbout);
|
|
view_free(app->view_about);
|
|
|
|
// File path
|
|
if(app->file_path) {
|
|
FURI_LOG_D(TAG, "Freeing file_path");
|
|
furi_string_free(app->file_path);
|
|
}
|
|
|
|
// Widget
|
|
FURI_LOG_D(TAG, "Removing widget view");
|
|
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewWidget);
|
|
widget_free(app->widget);
|
|
|
|
// Receiver
|
|
FURI_LOG_D(TAG, "Removing receiver view");
|
|
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewReceiver);
|
|
protopirate_view_receiver_free(app->protopirate_receiver);
|
|
|
|
// Receiver Info
|
|
FURI_LOG_D(TAG, "Removing receiver_info view");
|
|
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewReceiverInfo);
|
|
protopirate_view_receiver_info_free(app->protopirate_receiver_info);
|
|
|
|
// Setting
|
|
FURI_LOG_D(TAG, "Freeing subghz_setting");
|
|
subghz_setting_free(app->setting);
|
|
|
|
// Free preset
|
|
FURI_LOG_D(TAG, "Freeing preset");
|
|
furi_string_free(app->txrx->preset->name);
|
|
free(app->txrx->preset);
|
|
free(app->txrx);
|
|
|
|
// View dispatcher
|
|
FURI_LOG_D(TAG, "Freeing view_dispatcher and scene_manager");
|
|
view_dispatcher_free(app->view_dispatcher);
|
|
scene_manager_free(app->scene_manager);
|
|
|
|
// Close Dialogs
|
|
FURI_LOG_D(TAG, "Closing dialogs record");
|
|
furi_record_close(RECORD_DIALOGS);
|
|
app->dialogs = NULL;
|
|
|
|
// Notifications
|
|
FURI_LOG_D(TAG, "Closing notifications record");
|
|
furi_record_close(RECORD_NOTIFICATION);
|
|
app->notifications = NULL;
|
|
|
|
// Close records
|
|
FURI_LOG_D(TAG, "Closing GUI record");
|
|
furi_record_close(RECORD_GUI);
|
|
|
|
FURI_LOG_I(TAG, "App free complete");
|
|
free(app);
|
|
}
|
|
|
|
int32_t protopirate_app(char* p) {
|
|
furi_hal_power_suppress_charge_enter();
|
|
|
|
ProtoPirateApp* protopirate_app = protopirate_app_alloc();
|
|
if(!protopirate_app) {
|
|
// logging is already done in protopirate_app_alloc()
|
|
furi_hal_power_suppress_charge_exit();
|
|
return -1;
|
|
}
|
|
|
|
// Handle Command line PSF that may have been passed to us
|
|
bool load_saved = (p && strlen(p));
|
|
if(load_saved) protopirate_app->loaded_file_path = furi_string_alloc_set(p);
|
|
scene_manager_next_scene(
|
|
protopirate_app->scene_manager,
|
|
(load_saved) ? ProtoPirateSceneSavedInfo : ProtoPirateSceneStart);
|
|
|
|
#ifdef ENABLE_EMULATE_FEATURE
|
|
//We now jump straight to emulate scene from Browser. If the user wanted the key to look at, just click back.
|
|
//Makes it faster in my use case
|
|
if(load_saved) {
|
|
view_dispatcher_send_custom_event(
|
|
protopirate_app->view_dispatcher, ProtoPirateCustomEventSavedInfoEmulate);
|
|
notification_message(protopirate_app->notifications, &sequence_success);
|
|
}
|
|
#else
|
|
//We now jump straight to emulate scene from Browser. If the user wanted the key to look at, just click back.
|
|
//Makes it faster in my use case
|
|
if(load_saved) {
|
|
view_dispatcher_send_custom_event(
|
|
protopirate_app->view_dispatcher, ProtoPirateCustomEventReceiverInfoSave);
|
|
}
|
|
#endif
|
|
|
|
view_dispatcher_run(protopirate_app->view_dispatcher);
|
|
|
|
protopirate_app_free(protopirate_app);
|
|
|
|
furi_hal_power_suppress_charge_exit();
|
|
|
|
return 0;
|
|
}
|