Crash fix, lower stack size

Replace sub decode implementation with @zero-mega code, apply UI fixes
This commit is contained in:
MX
2026-01-14 22:58:48 +03:00
parent f78c153383
commit e5cc9c3e85
4 changed files with 533 additions and 237 deletions
+2 -2
View File
@@ -5,9 +5,9 @@ App(
targets=["f7"],
entry_point="protopirate_app",
requires=["gui"],
stack_size=6 * 1024,
stack_size=2 * 1024,
fap_description="Decode car key fob signals from Sub-GHz",
fap_version="1.5",
fap_version="1.6",
fap_icon="images/protopirate_10px.png",
fap_category="Sub-GHz",
fap_icon_assets="images",
+5 -4
View File
@@ -207,8 +207,6 @@ ProtoPirateApp* protopirate_app_alloc() {
app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
subghz_worker_set_context(app->txrx->worker, app->txrx->receiver);
furi_hal_power_suppress_charge_enter();
scene_manager_next_scene(app->scene_manager, ProtoPirateSceneStart);
return app;
@@ -310,18 +308,21 @@ void protopirate_app_free(ProtoPirateApp* app) {
// Close records
furi_record_close(RECORD_GUI);
furi_hal_power_suppress_charge_exit();
free(app);
}
int32_t protopirate_app(void* p) {
UNUSED(p);
furi_hal_power_suppress_charge_enter();
ProtoPirateApp* protopirate_app = protopirate_app_alloc();
view_dispatcher_run(protopirate_app->view_dispatcher);
protopirate_app_free(protopirate_app);
furi_hal_power_suppress_charge_exit();
return 0;
}
+2
View File
@@ -21,6 +21,7 @@
#include <lib/subghz/receiver.h>
#include <lib/subghz/transmitter.h>
#include <lib/subghz/devices/devices.h>
#include <lib/subghz/subghz_file_encoder_worker.h>
#include <dialogs/dialogs.h>
#define PROTOPIRATE_KEYSTORE_DIR_NAME APP_ASSETS_PATH("encrypted")
@@ -61,6 +62,7 @@ struct ProtoPirateApp {
FuriString* loaded_file_path;
bool auto_save;
ProtoPirateSettings settings;
SubGhzFileEncoderWorker* decode_raw_file_worker_encoder;
};
void protopirate_preset_init(
+524 -231
View File
@@ -2,26 +2,36 @@
#include "../protopirate_app_i.h"
#include "../protocols/protocol_items.h"
#include "../helpers/protopirate_storage.h"
#include "../helpers/radio_device_loader.h"
#include "../protopirate_history.h"
#include "core/record.h"
#include "storage/storage.h"
#include <dialogs/dialogs.h>
#include <ctype.h>
#include <math.h>
#include <lib/subghz/types.h>
#include <lib/subghz/subghz_file_encoder_worker.h>
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
#define TAG "ProtoPirateSubDecode"
#define SUBGHZ_APP_FOLDER EXT_PATH("subghz")
#define SAMPLES_PER_TICK 128
#define MAX_RAW_SAMPLES 2048
#define SUCCESS_DISPLAY_TICKS 18
#define FAILURE_DISPLAY_TICKS 18
#define SUBGHZ_APP_FOLDER EXT_PATH("subghz")
#define SAMPLES_TO_READ_PER_TICK 400
#define SUCCESS_DISPLAY_TICKS 18
#define FAILURE_DISPLAY_TICKS 18
#define KIA_HISTORY_MAX 50
// Decode state machine
typedef enum {
DecodeStateIdle,
DecodeStateOpenFile,
DecodeStateReadHeader,
DecodeStateLoadRawSamples,
DecodeStateStartingWorker,
DecodeStateDecodingRaw,
DecodeStateDecodingProtocol,
DecodeStateShowHistory,
DecodeStateShowSignalInfo,
DecodeStateShowSuccess,
DecodeStateShowFailure,
DecodeStateDone,
@@ -44,22 +54,23 @@ typedef struct {
Storage* storage;
FlipperFormat* ff;
// RAW decode state
int32_t* raw_samples;
size_t total_samples;
size_t current_sample;
size_t current_protocol_idx;
void* current_decoder;
const SubGhzProtocol* current_protocol;
// RAW decode state - using receiver system
bool decode_success;
// Callback context
bool callback_fired;
SubGhzProtocolDecoderBase* decoded_decoder;
FuriString* decoded_string;
// For saving - keep a copy of the flipper format data
FlipperFormat* save_data;
bool can_save;
// Worker startup delay counter
uint8_t worker_startup_delay;
// History for decoded signals
ProtoPirateHistory* history;
uint16_t match_count;
uint16_t selected_history_index;
bool showing_signal_info; // Track if we're showing signal info (true) or history (false)
} SubDecodeContext;
static SubDecodeContext* g_decode_ctx = NULL;
@@ -70,17 +81,38 @@ static void protopirate_scene_sub_decode_widget_callback(
InputType type,
void* context);
// Callback when decoder successfully decodes
static void protopirate_decode_callback(SubGhzProtocolDecoderBase* decoder_base, void* context) {
SubDecodeContext* ctx = context;
ctx->callback_fired = true;
// Receiver view callback for history navigation
static void
protopirate_scene_sub_decode_receiver_callback(ProtoPirateCustomEvent event, void* context);
if(ctx->current_protocol && ctx->current_protocol->decoder &&
ctx->current_protocol->decoder->get_string) {
ctx->current_protocol->decoder->get_string(decoder_base, ctx->decoded_string);
// Callback when receiver successfully decodes a signal
static void protopirate_sub_decode_receiver_callback(
SubGhzReceiver* receiver,
SubGhzProtocolDecoderBase* decoder_base,
void* context) {
UNUSED(receiver);
furi_assert(context);
ProtoPirateApp* app = context;
SubDecodeContext* ctx = g_decode_ctx;
if(!ctx || ctx->state != DecodeStateDecodingRaw) {
return;
}
FURI_LOG_I(TAG, "Decode callback fired for %s!", ctx->current_protocol->name);
FURI_LOG_I(TAG, "=== SIGNAL DECODED FROM FILE ===");
// Add to history
if(protopirate_history_add_to_history(ctx->history, decoder_base, app->txrx->preset)) {
ctx->match_count++;
FURI_LOG_I(TAG, "Added signal %u to history", ctx->match_count);
// Send update event to refresh animation
view_dispatcher_send_custom_event(
app->view_dispatcher, ProtoPirateCustomEventSubDecodeUpdate);
}
// Reset receiver to continue looking for more signals
subghz_receiver_reset(receiver);
}
// Case-insensitive string search
@@ -362,12 +394,11 @@ static void protopirate_decode_draw_callback(Canvas* canvas, void* context) {
// Calculate progress
int progress = 0;
if(ctx->state == DecodeStateLoadRawSamples && ctx->total_samples > 0) {
progress = 10 + (ctx->total_samples * 20) / MAX_RAW_SAMPLES;
} else if(ctx->state == DecodeStateDecodingRaw && ctx->total_samples > 0) {
int sample_pct = (ctx->current_sample * 100) / ctx->total_samples;
int proto_pct = (ctx->current_protocol_idx * 100) / protopirate_protocol_registry.size;
progress = 30 + (sample_pct * 35 + proto_pct * 35) / 100;
if(ctx->state == DecodeStateStartingWorker) {
progress = 20 + (frame % 10);
} else if(ctx->state == DecodeStateDecodingRaw) {
// Show animated progress while decoding - gradually increase
progress = 30 + (frame % 50);
} else if(ctx->state == DecodeStateOpenFile || ctx->state == DecodeStateReadHeader) {
progress = 5 + (frame % 10);
} else if(ctx->state == DecodeStateDecodingProtocol) {
@@ -396,12 +427,24 @@ static void protopirate_decode_draw_callback(Canvas* canvas, void* context) {
case DecodeStateReadHeader:
status_text = "Reading header...";
break;
case DecodeStateLoadRawSamples:
status_text = "Loading samples...";
case DecodeStateStartingWorker:
status_text = "Starting decoder...";
break;
case DecodeStateDecodingRaw:
status_text = ctx->current_protocol ? ctx->current_protocol->name : "Analyzing...";
case DecodeStateDecodingRaw: {
static char match_text[32];
if(ctx->match_count > 0) {
snprintf(
match_text,
sizeof(match_text),
"%u match%s",
ctx->match_count,
ctx->match_count > 1 ? "es" : "");
status_text = match_text;
} else {
status_text = "Decoding signal...";
}
break;
}
case DecodeStateDecodingProtocol:
status_text = "Parsing protocol...";
break;
@@ -432,11 +475,23 @@ static void protopirate_decode_draw_callback(Canvas* canvas, void* context) {
}
static bool protopirate_decode_input_callback(InputEvent* event, void* context) {
UNUSED(context);
ProtoPirateApp* app = context;
if(event->type == InputTypeShort && event->key == InputKeyBack) {
if(g_decode_ctx && g_decode_ctx->state != DecodeStateIdle &&
g_decode_ctx->state != DecodeStateDone) {
// Stop worker if running
if(app->decode_raw_file_worker_encoder) {
if(subghz_file_encoder_worker_is_running(app->decode_raw_file_worker_encoder)) {
subghz_file_encoder_worker_stop(app->decode_raw_file_worker_encoder);
}
subghz_file_encoder_worker_free(app->decode_raw_file_worker_encoder);
app->decode_raw_file_worker_encoder = NULL;
}
// Clear receiver callback
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, NULL);
furi_string_set(g_decode_ctx->error_info, "Cancelled");
g_decode_ctx->state = DecodeStateShowFailure;
g_decode_ctx->result_display_counter = 0;
@@ -448,115 +503,37 @@ static bool protopirate_decode_input_callback(InputEvent* event, void* context)
return false;
}
// Process one chunk of RAW samples
static bool protopirate_process_raw_chunk(ProtoPirateApp* app, SubDecodeContext* ctx) {
if(!ctx->current_decoder) {
while(ctx->current_protocol_idx < protopirate_protocol_registry.size) {
const SubGhzProtocol* protocol =
protopirate_protocol_registry.items[ctx->current_protocol_idx];
// Process samples from file encoder worker
static bool protopirate_process_raw_file_samples(ProtoPirateApp* app, SubDecodeContext* ctx) {
if(!app->decode_raw_file_worker_encoder ||
!subghz_file_encoder_worker_is_running(app->decode_raw_file_worker_encoder)) {
return true; // File finished or not started
}
if(protocol->decoder && protocol->decoder->alloc) {
ctx->current_decoder = protocol->decoder->alloc(app->txrx->environment);
ctx->current_protocol = protocol;
ctx->current_sample = 0;
ctx->callback_fired = false;
furi_string_reset(ctx->decoded_string);
LevelDuration level_duration;
if(ctx->current_decoder) {
SubGhzProtocolDecoderBase* decoder_base = ctx->current_decoder;
decoder_base->callback = protopirate_decode_callback;
decoder_base->context = ctx;
for(uint32_t read = SAMPLES_TO_READ_PER_TICK; read > 0; --read) {
level_duration =
subghz_file_encoder_worker_get_level_duration(app->decode_raw_file_worker_encoder);
if(protocol->decoder->reset) {
protocol->decoder->reset(ctx->current_decoder);
}
if(!level_duration_is_reset(level_duration)) {
bool level = level_duration_get_level(level_duration);
uint32_t duration = level_duration_get_duration(level_duration);
subghz_receiver_decode(app->txrx->receiver, level, duration);
FURI_LOG_D(TAG, "Trying protocol: %s", protocol->name);
break;
}
// Check if we got a decode
if(ctx->decode_success) {
// Stop processing and show result
return true;
}
ctx->current_protocol_idx++;
}
if(!ctx->current_decoder) {
return true;
}
}
size_t end_sample = ctx->current_sample + SAMPLES_PER_TICK;
if(end_sample > ctx->total_samples) {
end_sample = ctx->total_samples;
}
for(size_t i = ctx->current_sample; i < end_sample && !ctx->callback_fired; i++) {
int32_t duration = ctx->raw_samples[i];
bool level = (duration >= 0);
if(duration < 0) duration = -duration;
ctx->current_protocol->decoder->feed(ctx->current_decoder, level, (uint32_t)duration);
}
ctx->current_sample = end_sample;
if(ctx->callback_fired && furi_string_size(ctx->decoded_string) > 0) {
furi_string_printf(
ctx->result,
"RAW Decoded!\nFreq: %lu.%02lu MHz\n\n%s",
ctx->frequency / 1000000,
(ctx->frequency % 1000000) / 10000,
furi_string_get_cstr(ctx->decoded_string));
ctx->decode_success = true;
ctx->can_save = true;
// Serialize the decoded data BEFORE freeing the decoder
ctx->save_data = flipper_format_string_alloc();
if(ctx->current_protocol->decoder->serialize) {
// Create a temporary preset for serialization
SubGhzRadioPreset temp_preset;
temp_preset.frequency = ctx->frequency;
temp_preset.name = furi_string_alloc_set("AM650");
temp_preset.data = NULL;
temp_preset.data_size = 0;
SubGhzProtocolStatus status = ctx->current_protocol->decoder->serialize(
ctx->current_decoder, ctx->save_data, &temp_preset);
if(status != SubGhzProtocolStatusOk) {
FURI_LOG_W(TAG, "RAW serialize failed: %d", status);
flipper_format_free(ctx->save_data);
ctx->save_data = NULL;
ctx->can_save = false;
} else {
FURI_LOG_I(TAG, "RAW serialize success for %s", ctx->current_protocol->name);
}
furi_string_free(temp_preset.name);
} else {
FURI_LOG_W(TAG, "Protocol %s has no serialize function", ctx->current_protocol->name);
flipper_format_free(ctx->save_data);
ctx->save_data = NULL;
ctx->can_save = false;
}
ctx->current_protocol->decoder->free(ctx->current_decoder);
ctx->current_decoder = NULL;
return true;
}
if(ctx->current_sample >= ctx->total_samples) {
if(ctx->current_decoder) {
ctx->current_protocol->decoder->free(ctx->current_decoder);
ctx->current_decoder = NULL;
}
ctx->current_protocol_idx++;
ctx->current_sample = 0;
if(ctx->current_protocol_idx >= protopirate_protocol_registry.size) {
// File finished
FURI_LOG_I(TAG, "File decode complete");
return true;
}
}
return false;
return false; // Continue processing
}
static void close_file_handles(SubDecodeContext* ctx) {
@@ -564,20 +541,27 @@ static void close_file_handles(SubDecodeContext* ctx) {
flipper_format_free(ctx->ff);
ctx->ff = NULL;
}
if(ctx->storage) {
furi_record_close(RECORD_STORAGE);
ctx->storage = NULL;
}
furi_record_close(RECORD_STORAGE);
//ctx->storage = NULL;
}
// Widget callback for save button
// Receiver view callback for history navigation
static void
protopirate_scene_sub_decode_receiver_callback(ProtoPirateCustomEvent event, void* context) {
ProtoPirateApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, event);
}
// Widget callback for buttons (used in signal info view)
static void protopirate_scene_sub_decode_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
ProtoPirateApp* app = context;
if(type == InputTypeShort) {
if(result == GuiButtonTypeRight) {
// Save button in signal info view
view_dispatcher_send_custom_event(
app->view_dispatcher, ProtoPirateCustomEventSubDecodeSave);
}
@@ -597,6 +581,10 @@ void protopirate_scene_sub_decode_on_enter(void* context) {
g_decode_ctx->state = DecodeStateIdle;
g_decode_ctx->can_save = false;
g_decode_ctx->save_data = NULL;
g_decode_ctx->worker_startup_delay = 0;
g_decode_ctx->history = protopirate_history_alloc();
g_decode_ctx->match_count = 0;
g_decode_ctx->selected_history_index = 0;
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, ".sub", NULL);
@@ -631,42 +619,103 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even
if(!ctx) return false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == ProtoPirateCustomEventSubDecodeSave) {
// Save the file
if(ctx->save_data) {
FuriString* protocol = furi_string_alloc();
flipper_format_rewind(ctx->save_data);
if(event.event == ProtoPirateCustomEventSubDecodeUpdate) {
// Update receiver view with new history items (when signals are detected during decoding)
if(ctx->state == DecodeStateDecodingRaw) {
// History is updated in callback, just refresh animation
consumed = true;
} else if(
ctx->state == DecodeStateShowHistory ||
(ctx->state == DecodeStateDone && !ctx->showing_signal_info)) {
// Rebuild history view
uint16_t history_count = protopirate_history_get_item(ctx->history);
if(history_count > 0) {
protopirate_view_receiver_reset_menu(app->protopirate_receiver);
if(!flipper_format_read_string(ctx->save_data, "Protocol", protocol)) {
FuriString* item_text = furi_string_alloc();
for(uint16_t i = 0; i < history_count; i++) {
protopirate_history_get_text_item_menu(ctx->history, item_text, i);
protopirate_view_receiver_add_item_to_menu(
app->protopirate_receiver, furi_string_get_cstr(item_text), 0);
}
furi_string_free(item_text);
protopirate_view_receiver_set_idx_menu(
app->protopirate_receiver, ctx->selected_history_index);
// Update status bar
FuriString* frequency_str = furi_string_alloc();
FuriString* modulation_str = furi_string_alloc();
FuriString* history_stat_str = furi_string_alloc();
protopirate_get_frequency_modulation(app, frequency_str, modulation_str);
furi_string_printf(history_stat_str, "%u/%u", history_count, KIA_HISTORY_MAX);
protopirate_view_receiver_add_data_statusbar(
app->protopirate_receiver,
furi_string_get_cstr(frequency_str),
furi_string_get_cstr(modulation_str),
furi_string_get_cstr(history_stat_str),
radio_device_loader_is_external(app->txrx->radio_device));
furi_string_free(frequency_str);
furi_string_free(modulation_str);
furi_string_free(history_stat_str);
}
}
consumed = true;
} else if(event.event == ProtoPirateCustomEventSubDecodeSave) {
// Save the file (same as receiver_info)
FlipperFormat* ff =
protopirate_history_get_raw_data(ctx->history, ctx->selected_history_index);
if(ff) {
// Extract protocol name
FuriString* protocol = furi_string_alloc();
flipper_format_rewind(ff);
if(!flipper_format_read_string(ff, "Protocol", protocol)) {
furi_string_set_str(protocol, "Unknown");
FURI_LOG_W(TAG, "Could not read Protocol from save_data");
}
// Clean protocol name for filename
furi_string_replace_all(protocol, "/", "_");
furi_string_replace_all(protocol, " ", "_");
FURI_LOG_I(TAG, "Saving as protocol: %s", furi_string_get_cstr(protocol));
FuriString* saved_path = furi_string_alloc();
if(protopirate_storage_save_capture(
ctx->save_data, furi_string_get_cstr(protocol), saved_path)) {
FURI_LOG_I(TAG, "Saved to: %s", furi_string_get_cstr(saved_path));
ff, furi_string_get_cstr(protocol), saved_path)) {
notification_message(app->notifications, &sequence_success);
} else {
FURI_LOG_E(TAG, "Save failed!");
notification_message(app->notifications, &sequence_error);
}
furi_string_free(protocol);
furi_string_free(saved_path);
} else {
FURI_LOG_E(TAG, "save_data is NULL, cannot save");
FURI_LOG_E(TAG, "No flipper format data available");
notification_message(app->notifications, &sequence_error);
}
consumed = true;
} else if(event.event == ProtoPirateCustomEventViewReceiverOK) {
// User selected a signal from history - show signal info
uint16_t idx = protopirate_view_receiver_get_idx_menu(app->protopirate_receiver);
uint16_t history_count = protopirate_history_get_item(ctx->history);
if(idx < history_count) {
ctx->selected_history_index = idx;
ctx->state = DecodeStateShowSignalInfo;
ctx->showing_signal_info = true;
// Trigger state handler
view_dispatcher_send_custom_event(
app->view_dispatcher, ProtoPirateCustomEventSubDecodeUpdate);
}
consumed = true;
} else if(event.event == ProtoPirateCustomEventViewReceiverBack) {
// User pressed back from history - reset and go to main menu
protopirate_history_reset(ctx->history);
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, ProtoPirateSceneStart);
consumed = true;
}
return consumed;
}
if(event.type == SceneManagerEventTypeTick) {
@@ -739,91 +788,202 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even
ctx->result_display_counter = 0;
notification_message(app->notifications, &sequence_error);
} else if(furi_string_cmp_str(ctx->protocol_name, "RAW") == 0) {
ctx->raw_samples = malloc(sizeof(int32_t) * MAX_RAW_SAMPLES);
if(!ctx->raw_samples) {
furi_string_set(ctx->result, "Memory error");
furi_string_set(ctx->error_info, "Out of memory");
close_file_handles(ctx);
ctx->state = DecodeStateShowFailure;
ctx->result_display_counter = 0;
notification_message(app->notifications, &sequence_error);
} else {
ctx->total_samples = 0;
flipper_format_rewind(ctx->ff);
ctx->state = DecodeStateLoadRawSamples;
}
// Close the file handle as we'll use SubGhzFileEncoderWorker
close_file_handles(ctx);
// Set up receiver callback
subghz_receiver_set_rx_callback(
app->txrx->receiver, protopirate_sub_decode_receiver_callback, app);
// Move to starting worker state - this will happen on next tick
ctx->state = DecodeStateStartingWorker;
} else {
ctx->state = DecodeStateDecodingProtocol;
}
break;
}
case DecodeStateLoadRawSamples: {
size_t samples_this_tick = 0;
while(ctx->total_samples < MAX_RAW_SAMPLES && samples_this_tick < 512) {
uint32_t count = 0;
if(!flipper_format_get_value_count(ctx->ff, "RAW_Data", &count) || count == 0) {
break;
}
size_t to_read = count;
if(ctx->total_samples + to_read > MAX_RAW_SAMPLES) {
to_read = MAX_RAW_SAMPLES - ctx->total_samples;
}
if(!flipper_format_read_int32(
ctx->ff, "RAW_Data", &ctx->raw_samples[ctx->total_samples], to_read)) {
break;
}
ctx->total_samples += to_read;
samples_this_tick += to_read;
case DecodeStateStartingWorker: {
// Wait a couple ticks to show the animation before starting worker
if(ctx->worker_startup_delay < 3) {
ctx->worker_startup_delay++;
break;
}
ctx->worker_startup_delay = 0;
uint32_t count = 0;
bool more_data = flipper_format_get_value_count(ctx->ff, "RAW_Data", &count) &&
count > 0;
// Load file metadata (frequency and preset) for the worker
FlipperFormat* fff_data_file = flipper_format_file_alloc(ctx->storage);
FuriString* temp_str = furi_string_alloc();
bool worker_started = false;
if(!more_data || ctx->total_samples >= MAX_RAW_SAMPLES) {
close_file_handles(ctx);
FURI_LOG_I(TAG, "Loaded %zu RAW samples", ctx->total_samples);
if(ctx->total_samples < 10) {
furi_string_set(ctx->result, "Not enough samples");
furi_string_set(ctx->error_info, "Too few samples");
ctx->state = DecodeStateShowFailure;
ctx->result_display_counter = 0;
notification_message(app->notifications, &sequence_error);
} else {
ctx->current_protocol_idx = 0;
ctx->current_sample = 0;
ctx->state = DecodeStateDecodingRaw;
do {
if(!flipper_format_file_open_existing(
fff_data_file, furi_string_get_cstr(ctx->file_path))) {
FURI_LOG_E(TAG, "Error opening file for worker");
break;
}
uint32_t version = 0;
if(!flipper_format_read_header(fff_data_file, temp_str, &version)) {
FURI_LOG_E(TAG, "Missing or incorrect header");
break;
}
// Check if it's a RAW file
if(strcmp(furi_string_get_cstr(temp_str), "Flipper SubGhz RAW File") != 0 ||
version != 1) {
FURI_LOG_E(TAG, "Not a valid RAW file");
break;
}
// Load frequency
if(!flipper_format_read_uint32(fff_data_file, "Frequency", &ctx->frequency, 1)) {
FURI_LOG_E(TAG, "Missing Frequency");
break;
}
// Load preset
if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) {
FURI_LOG_E(TAG, "Missing Preset");
break;
}
// Convert preset name from long format to short format
const char* preset_name_long = furi_string_get_cstr(temp_str);
const char* preset_name_short = preset_name_long;
if(!strcmp(preset_name_long, "FuriHalSubGhzPresetOok270Async")) {
preset_name_short = "AM270";
} else if(!strcmp(preset_name_long, "FuriHalSubGhzPresetOok650Async")) {
preset_name_short = "AM650";
} else if(!strcmp(preset_name_long, "FuriHalSubGhzPreset2FSKDev238Async")) {
preset_name_short = "FM238";
} else if(!strcmp(preset_name_long, "FuriHalSubGhzPreset2FSKDev12KAsync")) {
preset_name_short = "FM12K";
} else if(!strcmp(preset_name_long, "FuriHalSubGhzPreset2FSKDev476Async")) {
preset_name_short = "FM476";
} else if(!strcmp(preset_name_long, "FuriHalSubGhzPresetCustom")) {
preset_name_short = "CUSTOM";
}
// Get preset index by searching
size_t preset_index = subghz_setting_get_preset_count(app->setting);
for(size_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) {
if(!strcmp(
subghz_setting_get_preset_name(app->setting, i), preset_name_short)) {
preset_index = i;
break;
}
}
if(preset_index >= subghz_setting_get_preset_count(app->setting)) {
FURI_LOG_E(
TAG, "Failed to get preset index for %s, using AM650", preset_name_short);
preset_name_short = "AM650";
for(size_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) {
if(!strcmp(
subghz_setting_get_preset_name(app->setting, i),
preset_name_short)) {
preset_index = i;
break;
}
}
if(preset_index >= subghz_setting_get_preset_count(app->setting)) {
FURI_LOG_E(TAG, "Failed to get AM650 preset index!");
break;
}
}
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);
if(preset_data == NULL) {
FURI_LOG_E(TAG, "Failed to get preset data!");
break;
}
// Set the preset
protopirate_preset_init(
app, preset_name_short, ctx->frequency, preset_data, preset_data_size);
// Start file encoder worker
app->decode_raw_file_worker_encoder = subghz_file_encoder_worker_alloc();
const char* device_name = SUBGHZ_DEVICE_CC1101_INT_NAME;
if(radio_device_loader_is_external(app->txrx->radio_device)) {
device_name = SUBGHZ_DEVICE_CC1101_EXT_NAME;
}
if(subghz_file_encoder_worker_start(
app->decode_raw_file_worker_encoder,
furi_string_get_cstr(ctx->file_path),
device_name)) {
worker_started = true;
ctx->state = DecodeStateDecodingRaw;
FURI_LOG_I(TAG, "Started file encoder worker");
} else {
FURI_LOG_E(TAG, "Failed to start file encoder worker");
subghz_file_encoder_worker_free(app->decode_raw_file_worker_encoder);
app->decode_raw_file_worker_encoder = NULL;
}
} while(false);
flipper_format_free(fff_data_file);
furi_string_free(temp_str);
if(!worker_started) {
furi_string_set(ctx->result, "Failed to start decode");
furi_string_set(ctx->error_info, "Worker start failed");
ctx->state = DecodeStateShowFailure;
ctx->result_display_counter = 0;
notification_message(app->notifications, &sequence_error);
}
break;
}
case DecodeStateDecodingRaw: {
bool done = protopirate_process_raw_chunk(app, ctx);
// Check if worker is ready (give it a tick to initialize)
if(app->decode_raw_file_worker_encoder &&
!subghz_file_encoder_worker_is_running(app->decode_raw_file_worker_encoder)) {
// Worker not ready yet, wait
break;
}
bool done = protopirate_process_raw_file_samples(app, ctx);
if(done) {
if(ctx->decode_success) {
// Stop the worker
if(app->decode_raw_file_worker_encoder) {
if(subghz_file_encoder_worker_is_running(
app->decode_raw_file_worker_encoder)) {
subghz_file_encoder_worker_stop(app->decode_raw_file_worker_encoder);
}
subghz_file_encoder_worker_free(app->decode_raw_file_worker_encoder);
app->decode_raw_file_worker_encoder = NULL;
}
// Clear receiver callback
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, NULL);
// Check if we found any signals
uint16_t history_count = protopirate_history_get_item(ctx->history);
if(history_count > 0) {
// First show success animation, then history list
ctx->state = DecodeStateShowSuccess;
ctx->selected_history_index = 0;
ctx->showing_signal_info = false;
ctx->result_display_counter = 0;
notification_message(app->notifications, &sequence_success);
} else {
// No signals found
furi_string_printf(
ctx->result,
"RAW Signal\n\n"
"Freq: %lu.%02lu MHz\n"
"Samples: %zu\n\n"
"Freq: %lu.%02lu MHz\n\n"
"No ProtoPirate protocol\n"
"detected in signal.",
ctx->frequency / 1000000,
(ctx->frequency % 1000000) / 10000,
ctx->total_samples);
(ctx->frequency % 1000000) / 10000);
furi_string_set(ctx->error_info, "No protocol match");
ctx->state = DecodeStateShowFailure;
ctx->result_display_counter = 0;
@@ -1058,22 +1218,30 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even
case DecodeStateShowSuccess: {
ctx->result_display_counter++;
if(ctx->result_display_counter >= SUCCESS_DISPLAY_TICKS) {
widget_reset(app->widget);
widget_add_text_scroll_element(
app->widget, 0, 0, 128, 54, furi_string_get_cstr(ctx->result));
// Check if we have history items (from RAW decoding) - show history list instead of widget
uint16_t history_count = protopirate_history_get_item(ctx->history);
if(history_count > 0) {
// Transition to showing history list
ctx->state = DecodeStateShowHistory;
} else {
// No history items, show widget with result (for protocol decoding)
widget_reset(app->widget);
widget_add_text_scroll_element(
app->widget, 0, 0, 128, 50, furi_string_get_cstr(ctx->result));
// Add save button if we can save
if(ctx->can_save) {
widget_add_button_element(
app->widget,
GuiButtonTypeRight,
"Save",
protopirate_scene_sub_decode_widget_callback,
app);
// Add save button if we can save
if(ctx->can_save) {
widget_add_button_element(
app->widget,
GuiButtonTypeRight,
"Save",
protopirate_scene_sub_decode_widget_callback,
app);
}
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget);
ctx->state = DecodeStateDone;
}
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget);
ctx->state = DecodeStateDone;
}
break;
}
@@ -1090,11 +1258,127 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even
break;
}
case DecodeStateShowHistory: {
// Show history list using receiver view (same as receive mode)
uint16_t history_count = protopirate_history_get_item(ctx->history);
if(history_count > 0) {
// Reset and populate receiver view menu
protopirate_view_receiver_reset_menu(app->protopirate_receiver);
FuriString* item_text = furi_string_alloc();
for(uint16_t i = 0; i < history_count; i++) {
protopirate_history_get_text_item_menu(ctx->history, item_text, i);
protopirate_view_receiver_add_item_to_menu(
app->protopirate_receiver, furi_string_get_cstr(item_text), 0);
}
furi_string_free(item_text);
// Set initial selection
protopirate_view_receiver_set_idx_menu(
app->protopirate_receiver, ctx->selected_history_index);
// Set up callback
protopirate_view_receiver_set_callback(
app->protopirate_receiver,
protopirate_scene_sub_decode_receiver_callback,
app);
// Update status bar
FuriString* frequency_str = furi_string_alloc();
FuriString* modulation_str = furi_string_alloc();
FuriString* history_stat_str = furi_string_alloc();
protopirate_get_frequency_modulation(app, frequency_str, modulation_str);
furi_string_printf(history_stat_str, "%u/%u", history_count, KIA_HISTORY_MAX);
protopirate_view_receiver_add_data_statusbar(
app->protopirate_receiver,
furi_string_get_cstr(frequency_str),
furi_string_get_cstr(modulation_str),
furi_string_get_cstr(history_stat_str),
radio_device_loader_is_external(app->txrx->radio_device));
furi_string_free(frequency_str);
furi_string_free(modulation_str);
furi_string_free(history_stat_str);
// Switch to receiver view
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewReceiver);
ctx->state = DecodeStateDone;
ctx->showing_signal_info = false;
}
break;
}
case DecodeStateShowSignalInfo: {
// Show signal info in widget (same layout as receiver_info)
widget_reset(app->widget);
uint16_t history_count = protopirate_history_get_item(ctx->history);
if(ctx->selected_history_index < history_count) {
FuriString* text = furi_string_alloc();
// Get menu text (first line) for header
/*protopirate_history_get_text_item_menu(
ctx->history, text, ctx->selected_history_index);
widget_add_string_element(
app->widget,
64,
0,
AlignCenter,
AlignTop,
FontPrimary,
furi_string_get_cstr(text));*/
// Get full text for body
furi_string_reset(text);
protopirate_history_get_text_item(ctx->history, text, ctx->selected_history_index);
widget_add_text_scroll_element(
app->widget, 0, 0, 128, 50, furi_string_get_cstr(text));
// Add save button
widget_add_button_element(
app->widget,
GuiButtonTypeRight,
"Save",
protopirate_scene_sub_decode_widget_callback,
app);
// Store reference to history item's flipper format for saving
FlipperFormat* ff =
protopirate_history_get_raw_data(ctx->history, ctx->selected_history_index);
if(ff) {
// We'll use the history's flipper format directly when saving
ctx->can_save = true;
}
furi_string_free(text);
}
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget);
ctx->state = DecodeStateDone;
ctx->showing_signal_info = true;
break;
}
default:
break;
}
// Force view update to show animation
view_commit_model(app->view_about, true);
} else if(event.type == SceneManagerEventTypeBack) {
// Handle back button navigation
if(ctx->showing_signal_info) {
// In signal info - go back to history
ctx->showing_signal_info = false;
//ctx->selected_history_index = 0;
ctx->state = DecodeStateShowHistory;
view_dispatcher_send_custom_event(
app->view_dispatcher, ProtoPirateCustomEventSubDecodeUpdate);
consumed = true;
}
// If in history view, back is handled by ViewReceiverBack event
}
return consumed;
@@ -1103,18 +1387,27 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even
void protopirate_scene_sub_decode_on_exit(void* context) {
ProtoPirateApp* app = context;
// Stop worker if still running
if(app->decode_raw_file_worker_encoder) {
if(subghz_file_encoder_worker_is_running(app->decode_raw_file_worker_encoder)) {
subghz_file_encoder_worker_stop(app->decode_raw_file_worker_encoder);
}
subghz_file_encoder_worker_free(app->decode_raw_file_worker_encoder);
app->decode_raw_file_worker_encoder = NULL;
}
// Clear receiver callback
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, NULL);
if(g_decode_ctx) {
close_file_handles(g_decode_ctx);
if(g_decode_ctx->current_decoder && g_decode_ctx->current_protocol) {
g_decode_ctx->current_protocol->decoder->free(g_decode_ctx->current_decoder);
}
if(g_decode_ctx->raw_samples) {
free(g_decode_ctx->raw_samples);
}
if(g_decode_ctx->save_data) {
flipper_format_free(g_decode_ctx->save_data);
}
if(g_decode_ctx->history) {
protopirate_history_free(g_decode_ctx->history);
}
furi_string_free(g_decode_ctx->file_path);
furi_string_free(g_decode_ctx->protocol_name);
furi_string_free(g_decode_ctx->result);