Store history as cached files instead of in memory

Stop RX during emulate and defer restarting RX when we get back to the receiver

Sub Decode off by default
This commit is contained in:
0mega
2026-04-18 12:57:45 +02:00
parent bca43b8912
commit f9791b0a2b
16 changed files with 641 additions and 264 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
//#define ENABLE_TIMING_TUNER_SCENE
#define ENABLE_SUB_DECODE_SCENE
//#define ENABLE_SUB_DECODE_SCENE
// #define ENABLE_EMULATE_FEATURE
+51
View File
@@ -10,6 +10,57 @@ bool protopirate_storage_init(void) {
return result;
}
void protopirate_storage_wipe_history_cache(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) {
storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER);
FURI_LOG_I(TAG, "Wiped history cache");
}
furi_record_close(RECORD_STORAGE);
}
void protopirate_storage_purge_temp_history_at_startup(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) {
storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER);
}
furi_record_close(RECORD_STORAGE);
}
bool protopirate_storage_ensure_history_folder(void) {
if(!protopirate_storage_init()) {
return false;
}
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage, PROTOPIRATE_CACHE_FOLDER);
bool ok = storage_simply_mkdir(storage, PROTOPIRATE_HISTORY_FOLDER);
furi_record_close(RECORD_STORAGE);
return ok;
}
bool protopirate_storage_save_history_capture(
FlipperFormat* flipper_format,
uint32_t seq,
FuriString* out_path) {
furi_check(flipper_format);
furi_check(out_path);
if(!protopirate_storage_ensure_history_folder()) {
FURI_LOG_E(TAG, "History folder missing");
return false;
}
furi_string_printf(
out_path,
"%s/hist_%08lu%s",
PROTOPIRATE_HISTORY_FOLDER,
(unsigned long)seq,
PROTOPIRATE_APP_EXTENSION);
return protopirate_storage_save_capture_to_path(
flipper_format, furi_string_get_cstr(out_path));
}
static void sanitize_filename(const char* input, char* output, size_t output_size) {
if(!output || output_size == 0) return;
if(!input) {
+15 -1
View File
@@ -9,6 +9,8 @@
#define PROTOPIRATE_APP_EXTENSION ".psf"
#define PROTOPIRATE_APP_FILE_VERSION 1
#define PROTOPIRATE_TEMP_FILE APP_DATA_PATH("saved/.temp.psf")
#define PROTOPIRATE_CACHE_FOLDER APP_DATA_PATH("cache")
#define PROTOPIRATE_HISTORY_FOLDER APP_DATA_PATH("cache/history")
// Helper: failed read
#define PROTOPIRATE_FAIL_READ(_k) \
@@ -107,4 +109,16 @@ FlipperFormat* protopirate_storage_load_file(const char* file_path);
void protopirate_storage_close_file(FlipperFormat* flipper_format);
// Check if file exists
bool protopirate_storage_file_exists(const char* file_path);
bool protopirate_storage_file_exists(const char* file_path);
bool protopirate_storage_ensure_history_folder(void);
void protopirate_storage_purge_temp_history_at_startup(void);
void protopirate_storage_wipe_history_cache(void);
bool protopirate_storage_save_history_capture(
FlipperFormat* flipper_format,
uint32_t seq,
FuriString* out_path);
+1
View File
@@ -23,6 +23,7 @@ typedef enum {
ProtoPirateCustomEventViewReceiverUnlock,
// Custom events for scenes
ProtoPirateCustomEventSceneReceiverUpdate,
ProtoPirateCustomEventReceiverDeferredRxStart,
ProtoPirateCustomEventSceneSettingLock,
// File management
ProtoPirateCustomEventReceiverInfoSave,
+1
View File
@@ -32,6 +32,7 @@ static void protopirate_app_tick_event_callback(void* context) {
ProtoPirateApp* protopirate_app_alloc() {
LOG_HEAP("Pre alloc");
protopirate_storage_purge_temp_history_at_startup();
ProtoPirateApp* app = malloc(sizeof(ProtoPirateApp));
if(!app) {
FURI_LOG_E(TAG, "Failed to allocate ProtoPirateApp app !");
+43 -1
View File
@@ -114,6 +114,48 @@ void protopirate_sleep(ProtoPirateApp* app) {
app->txrx->txrx_state = ProtoPirateTxRxStateSleep;
}
void protopirate_rx_stack_suspend_for_tx(ProtoPirateApp* app) {
if(!app || !app->radio_initialized) {
return;
}
if(app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
protopirate_rx_end(app);
}
if(app->txrx->receiver) {
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, NULL);
subghz_receiver_free(app->txrx->receiver);
app->txrx->receiver = NULL;
}
if(app->txrx->worker) {
subghz_worker_free(app->txrx->worker);
app->txrx->worker = NULL;
}
if(app->txrx->radio_device && app->txrx->txrx_state != ProtoPirateTxRxStateTx) {
subghz_devices_idle(app->txrx->radio_device);
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
}
}
void protopirate_rx_stack_resume_after_tx(ProtoPirateApp* app) {
if(!app || !app->radio_initialized || !app->txrx->environment) {
return;
}
if(app->txrx->receiver) {
return;
}
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
if(!app->txrx->receiver) {
FURI_LOG_E(TAG, "rx_stack_resume: subghz_receiver_alloc_init failed");
return;
}
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
}
void protopirate_hopper_update(ProtoPirateApp* app) {
furi_check(app);
@@ -153,7 +195,7 @@ void protopirate_hopper_update(ProtoPirateApp* app) {
if(app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
protopirate_rx_end(app);
}
if(app->txrx->txrx_state == ProtoPirateTxRxStateIDLE) {
if(app->txrx->txrx_state == ProtoPirateTxRxStateIDLE && app->txrx->receiver) {
subghz_receiver_reset(app->txrx->receiver);
app->txrx->preset->frequency =
subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency);
+3
View File
@@ -107,6 +107,9 @@ void protopirate_tx_stop(ProtoPirateApp* app);
bool protopirate_radio_init(ProtoPirateApp* app);
void protopirate_radio_deinit(ProtoPirateApp* app);
void protopirate_rx_stack_suspend_for_tx(ProtoPirateApp* app);
void protopirate_rx_stack_resume_after_tx(ProtoPirateApp* app);
void protopirate_app_free(ProtoPirateApp* app);
static const NotificationSequence sequence_tx = {
+235 -93
View File
@@ -1,15 +1,49 @@
// protopirate_history.c
#include "protopirate_history.h"
#include "defines.h"
#include "helpers/protopirate_storage.h"
#include "protocols/protocol_items.h"
#include <lib/subghz/environment.h>
#include <lib/subghz/receiver.h>
#include <flipper_format/flipper_format_i.h>
#include <storage/storage.h>
#include <string.h>
#define TAG "ProtoPirateHistory"
static void protopirate_history_shrink_to_first_line(FuriString* s) {
const char* c = furi_string_get_cstr(s);
size_t n = 0;
while(c[n] && c[n] != '\r' && c[n] != '\n') {
n++;
}
furi_string_left(s, n);
}
static const SubGhzProtocol* protopirate_history_find_protocol_by_name(const char* pname) {
if(!pname) return NULL;
const char* lookup = pname;
if(strcmp(pname, "Kia V3") == 0 || strcmp(pname, "Kia V4") == 0) {
lookup = "Kia V3/V4";
}
for(size_t i = 0; i < protopirate_protocol_registry.size; i++) {
if(strcmp(protopirate_protocol_registry.items[i]->name, lookup) == 0) {
return protopirate_protocol_registry.items[i];
}
}
if(lookup != pname) {
for(size_t i = 0; i < protopirate_protocol_registry.size; i++) {
if(strcmp(protopirate_protocol_registry.items[i]->name, pname) == 0) {
return protopirate_protocol_registry.items[i];
}
}
}
return NULL;
}
typedef struct {
FuriString* item_str;
FlipperFormat* flipper_format;
FuriString* capture_path;
uint8_t type;
SubGhzRadioPreset* preset;
} ProtoPirateHistoryItem;
ARRAY_DEF(ProtoPirateHistoryItemArray, ProtoPirateHistoryItem, M_POD_OPLIST)
@@ -19,8 +53,35 @@ struct ProtoPirateHistory {
uint16_t last_index;
uint32_t last_update_timestamp;
uint8_t code_last_hash_data;
uint32_t next_capture_seq;
Storage* storage;
FlipperFormat* loaded_ff;
int16_t loaded_idx;
};
void protopirate_history_release_scratch(ProtoPirateHistory* instance) {
furi_check(instance);
if(instance->loaded_ff) {
flipper_format_free(instance->loaded_ff);
instance->loaded_ff = NULL;
}
instance->loaded_idx = -1;
}
static void protopirate_history_item_free(ProtoPirateHistoryItem* item, bool delete_file) {
if(item->item_str) {
furi_string_free(item->item_str);
item->item_str = NULL;
}
if(item->capture_path) {
if(delete_file) {
protopirate_storage_delete_file(furi_string_get_cstr(item->capture_path));
}
furi_string_free(item->capture_path);
item->capture_path = NULL;
}
}
ProtoPirateHistory* protopirate_history_alloc(void) {
ProtoPirateHistory* instance = malloc(sizeof(ProtoPirateHistory));
furi_check(instance);
@@ -28,41 +89,42 @@ ProtoPirateHistory* protopirate_history_alloc(void) {
instance->last_index = 0;
instance->last_update_timestamp = 0;
instance->code_last_hash_data = 0;
instance->next_capture_seq = (uint32_t)(furi_get_tick() & 0x0FFFFFFF);
if(instance->next_capture_seq == 0) {
instance->next_capture_seq = 1;
}
instance->storage = furi_record_open(RECORD_STORAGE);
instance->loaded_ff = NULL;
instance->loaded_idx = -1;
return instance;
}
void protopirate_history_free(ProtoPirateHistory* instance) {
furi_check(instance);
protopirate_history_release_scratch(instance);
for(size_t i = 0; i < ProtoPirateHistoryItemArray_size(instance->data); i++) {
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, i);
furi_string_free(item->item_str);
flipper_format_free(item->flipper_format);
if(item->preset) {
if(item->preset->name) {
furi_string_free(item->preset->name);
}
free(item->preset);
}
protopirate_history_item_free(item, false);
}
ProtoPirateHistoryItemArray_clear(instance->data);
protopirate_storage_wipe_history_cache();
if(instance->storage) {
furi_record_close(RECORD_STORAGE);
instance->storage = NULL;
}
free(instance);
}
void protopirate_history_reset(ProtoPirateHistory* instance) {
furi_check(instance);
protopirate_history_release_scratch(instance);
for(size_t i = 0; i < ProtoPirateHistoryItemArray_size(instance->data); i++) {
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, i);
furi_string_free(item->item_str);
flipper_format_free(item->flipper_format);
if(item->preset) {
if(item->preset->name) {
furi_string_free(item->preset->name);
}
free(item->preset);
}
protopirate_history_item_free(item, false);
}
ProtoPirateHistoryItemArray_reset(instance->data);
instance->last_index = 0;
protopirate_storage_wipe_history_cache();
}
uint16_t protopirate_history_get_item(ProtoPirateHistory* instance) {
@@ -75,23 +137,22 @@ uint16_t protopirate_history_get_last_index(ProtoPirateHistory* instance) {
return instance->last_index;
}
// Helper function to free a single history item's resources
static void protopirate_history_item_free(ProtoPirateHistoryItem* item) {
if(item->item_str) {
furi_string_free(item->item_str);
item->item_str = NULL;
bool protopirate_history_get_capture_path(
ProtoPirateHistory* instance,
uint16_t idx,
FuriString* out_path) {
furi_check(instance);
furi_check(out_path);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
return false;
}
if(item->flipper_format) {
flipper_format_free(item->flipper_format);
item->flipper_format = NULL;
}
if(item->preset) {
if(item->preset->name) {
furi_string_free(item->preset->name);
}
free(item->preset);
item->preset = NULL;
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
if(!item->capture_path || furi_string_size(item->capture_path) == 0) {
return false;
}
furi_string_set(out_path, item->capture_path);
return true;
}
bool protopirate_history_add_to_history(
@@ -103,7 +164,6 @@ bool protopirate_history_add_to_history(
SubGhzProtocolDecoderBase* decoder_base = context;
// Check for duplicate (same hash within 500ms)
if((instance->code_last_hash_data ==
subghz_protocol_decoder_base_get_hash_data(decoder_base)) &&
((furi_get_tick() - instance->last_update_timestamp) < 500)) {
@@ -111,11 +171,12 @@ bool protopirate_history_add_to_history(
return false;
}
// If history is full, remove the oldest entry
protopirate_history_release_scratch(instance);
if(ProtoPirateHistoryItemArray_size(instance->data) >= PROTOPIRATE_HISTORY_MAX) {
ProtoPirateHistoryItem* oldest = ProtoPirateHistoryItemArray_get(instance->data, 0);
if(oldest) {
protopirate_history_item_free(oldest);
protopirate_history_item_free(oldest, true);
}
ProtoPirateHistoryItemArray_pop_at(NULL, instance->data, 0);
FURI_LOG_D(TAG, "History full, removed oldest entry");
@@ -124,54 +185,64 @@ bool protopirate_history_add_to_history(
instance->code_last_hash_data = subghz_protocol_decoder_base_get_hash_data(decoder_base);
instance->last_update_timestamp = furi_get_tick();
// Create a new history item
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_push_raw(instance->data);
item->item_str = furi_string_alloc();
item->flipper_format = flipper_format_string_alloc();
item->capture_path = furi_string_alloc();
item->type = 0;
// Copy preset
item->preset = malloc(sizeof(SubGhzRadioPreset));
item->preset->frequency = preset->frequency;
item->preset->name = furi_string_alloc();
if(preset->name) {
furi_string_set(item->preset->name, preset->name);
} else {
furi_string_set(item->preset->name, "UNKNOWN");
}
item->preset->data = preset->data;
item->preset->data_size = preset->data_size;
// Get string representation
FuriString* text = furi_string_alloc();
subghz_protocol_decoder_base_get_string(decoder_base, text);
furi_string_set(item->item_str, text);
// Serialize to flipper format
subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_format, preset);
// Debug: Log what we're adding to history
flipper_format_rewind(item->flipper_format);
uint32_t debug_bit_count;
FuriString* debug_protocol = furi_string_alloc();
if(flipper_format_read_string(item->flipper_format, "Protocol", debug_protocol)) {
FURI_LOG_I(TAG, "History add - Protocol: %s", furi_string_get_cstr(debug_protocol));
}
flipper_format_rewind(item->flipper_format);
if(flipper_format_read_uint32(item->flipper_format, "Bit", &debug_bit_count, 1)) {
FURI_LOG_I(TAG, "History add - Bit count: %lu", debug_bit_count);
}
furi_string_free(debug_protocol);
protopirate_history_shrink_to_first_line(item->item_str);
furi_string_free(text);
FlipperFormat* temp_ff = flipper_format_string_alloc();
furi_check(temp_ff);
SubGhzProtocolStatus ser =
subghz_protocol_decoder_base_serialize(decoder_base, temp_ff, preset);
if(ser != SubGhzProtocolStatusOk) {
FURI_LOG_E(TAG, "Serialize failed");
flipper_format_free(temp_ff);
furi_string_free(item->item_str);
furi_string_free(item->capture_path);
ProtoPirateHistoryItemArray_pop_at(NULL, instance->data,
ProtoPirateHistoryItemArray_size(instance->data) - 1);
return false;
}
uint32_t seq = instance->next_capture_seq++;
if(!protopirate_storage_save_history_capture(temp_ff, seq, item->capture_path)) {
FURI_LOG_E(TAG, "Failed to save history file");
flipper_format_free(temp_ff);
furi_string_free(item->item_str);
furi_string_free(item->capture_path);
ProtoPirateHistoryItemArray_pop_at(NULL, instance->data,
ProtoPirateHistoryItemArray_size(instance->data) - 1);
return false;
}
flipper_format_rewind(temp_ff);
{
uint32_t debug_bit_count;
FuriString* debug_protocol = furi_string_alloc();
if(flipper_format_read_string(temp_ff, "Protocol", debug_protocol)) {
FURI_LOG_I(TAG, "History add - Protocol: %s", furi_string_get_cstr(debug_protocol));
}
flipper_format_rewind(temp_ff);
if(flipper_format_read_uint32(temp_ff, "Bit", &debug_bit_count, 1)) {
FURI_LOG_I(TAG, "History add - Bit count: %lu", (unsigned long)debug_bit_count);
}
furi_string_free(debug_protocol);
}
flipper_format_free(temp_ff);
instance->last_index++;
FURI_LOG_I(
TAG,
"Added item %u to history (size: %zu)",
"Added item %u to history (size: %zu) path %s",
instance->last_index,
ProtoPirateHistoryItemArray_size(instance->data));
ProtoPirateHistoryItemArray_size(instance->data),
furi_string_get_cstr(item->capture_path));
return true;
}
@@ -189,31 +260,16 @@ void protopirate_history_get_text_item_menu(
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
// Get just the first line for the menu
const char* str = furi_string_get_cstr(item->item_str);
const char* newline = strchr(str, '\r');
size_t len = 0;
if(newline) {
len = newline - str;
} else {
newline = strchr(str, '\n');
if(newline) {
len = newline - str;
} else {
len = furi_string_size(item->item_str);
}
}
// Add index prefix
uint16_t display_idx = idx + 1;
furi_string_printf(output, "%u. %.*s", display_idx, (int)len, str);
furi_string_printf(
output, "%u. %s", display_idx, furi_string_get_cstr(item->item_str));
}
void protopirate_history_get_text_item(
void protopirate_history_get_text_item_detail(
ProtoPirateHistory* instance,
uint16_t idx,
FuriString* output,
uint16_t idx) {
SubGhzEnvironment* environment) {
furi_check(instance);
furi_check(output);
@@ -223,7 +279,53 @@ void protopirate_history_get_text_item(
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
furi_string_set(output, item->item_str);
if(!environment) {
furi_string_set(output, item->item_str);
return;
}
FlipperFormat* ff = protopirate_history_get_raw_data(instance, idx);
if(!ff) {
furi_string_set(output, item->item_str);
return;
}
FuriString* proto = furi_string_alloc();
flipper_format_rewind(ff);
if(!flipper_format_read_string(ff, "Protocol", proto)) {
furi_string_free(proto);
furi_string_set(output, item->item_str);
return;
}
const SubGhzProtocol* protocol =
protopirate_history_find_protocol_by_name(furi_string_get_cstr(proto));
furi_string_free(proto);
if(!protocol || !protocol->decoder || !protocol->decoder->alloc || !protocol->decoder->free) {
furi_string_set(output, item->item_str);
return;
}
void* dec = protocol->decoder->alloc(environment);
if(!dec) {
furi_string_set(output, item->item_str);
return;
}
SubGhzProtocolDecoderBase* base = dec;
flipper_format_rewind(ff);
SubGhzProtocolStatus st = subghz_protocol_decoder_base_deserialize(base, ff);
if(st != SubGhzProtocolStatusOk) {
protocol->decoder->free(dec);
furi_string_set(output, item->item_str);
return;
}
furi_string_reset(output);
subghz_protocol_decoder_base_get_string(base, output);
protocol->decoder->free(dec);
}
SubGhzProtocolDecoderBase*
@@ -240,8 +342,47 @@ FlipperFormat* protopirate_history_get_raw_data(ProtoPirateHistory* instance, ui
return NULL;
}
if(instance->loaded_idx == (int16_t)idx && instance->loaded_ff) {
return instance->loaded_ff;
}
protopirate_history_release_scratch(instance);
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
return item->flipper_format;
if(!item->capture_path || furi_string_size(item->capture_path) == 0) {
return NULL;
}
instance->loaded_ff = flipper_format_file_alloc(instance->storage);
furi_check(instance->loaded_ff);
if(!flipper_format_file_open_existing(
instance->loaded_ff, furi_string_get_cstr(item->capture_path))) {
FURI_LOG_E(TAG, "Failed open history capture %s", furi_string_get_cstr(item->capture_path));
flipper_format_free(instance->loaded_ff);
instance->loaded_ff = NULL;
return NULL;
}
instance->loaded_idx = (int16_t)idx;
return instance->loaded_ff;
}
void protopirate_history_commit_loaded(ProtoPirateHistory* instance) {
furi_check(instance);
if(instance->loaded_idx < 0 || !instance->loaded_ff) {
return;
}
if((size_t)instance->loaded_idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
return;
}
ProtoPirateHistoryItem* item =
ProtoPirateHistoryItemArray_get(instance->data, (size_t)instance->loaded_idx);
if(!item->capture_path) {
return;
}
if(!protopirate_storage_save_capture_to_path(
instance->loaded_ff, furi_string_get_cstr(item->capture_path))) {
FURI_LOG_E(TAG, "commit_loaded failed for %s", furi_string_get_cstr(item->capture_path));
}
}
void protopirate_history_set_item_str(
@@ -257,4 +398,5 @@ void protopirate_history_set_item_str(
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
furi_string_set(item->item_str, str);
protopirate_history_shrink_to_first_line(item->item_str);
}
+15 -2
View File
@@ -6,6 +6,7 @@
#define PROTOPIRATE_HISTORY_MAX 20
typedef struct SubGhzEnvironment SubGhzEnvironment;
typedef struct ProtoPirateHistory ProtoPirateHistory;
ProtoPirateHistory* protopirate_history_alloc(void);
@@ -13,6 +14,12 @@ void protopirate_history_free(ProtoPirateHistory* instance);
void protopirate_history_reset(ProtoPirateHistory* instance);
uint16_t protopirate_history_get_item(ProtoPirateHistory* instance);
uint16_t protopirate_history_get_last_index(ProtoPirateHistory* instance);
bool protopirate_history_get_capture_path(
ProtoPirateHistory* instance,
uint16_t idx,
FuriString* out_path);
bool protopirate_history_add_to_history(
ProtoPirateHistory* instance,
void* context,
@@ -21,14 +28,20 @@ void protopirate_history_get_text_item_menu(
ProtoPirateHistory* instance,
FuriString* output,
uint16_t idx);
void protopirate_history_get_text_item(
void protopirate_history_get_text_item_detail(
ProtoPirateHistory* instance,
uint16_t idx,
FuriString* output,
uint16_t idx);
SubGhzEnvironment* environment);
SubGhzProtocolDecoderBase*
protopirate_history_get_decoder_base(ProtoPirateHistory* instance, uint16_t idx);
FlipperFormat* protopirate_history_get_raw_data(ProtoPirateHistory* instance, uint16_t idx);
void protopirate_history_commit_loaded(ProtoPirateHistory* instance);
void protopirate_history_release_scratch(ProtoPirateHistory* instance);
void protopirate_history_set_item_str(
ProtoPirateHistory* instance,
uint16_t idx,
+65 -59
View File
@@ -100,6 +100,58 @@ static void emulate_context_free(void) {
emulate_context = NULL;
}
static bool emulate_context_try_init_transmitter(ProtoPirateApp* app, EmulateContext* ctx) {
if(ctx->transmitter) {
return true;
}
if(!ctx->flipper_format || !ctx->protocol_name) {
return false;
}
const char* proto_name = furi_string_get_cstr(ctx->protocol_name);
const char* registry_name = proto_name;
if(strcmp(proto_name, "Kia V3") == 0) {
registry_name = "Kia V3/V4";
FURI_LOG_I(TAG, "Protocol name KiaV3 fixed to Kia V3/V4 for registry");
} else if(strcmp(proto_name, "Kia V4") == 0) {
registry_name = "Kia V3/V4";
FURI_LOG_I(TAG, "Protocol name KiaV4 fixed to Kia V3/V4 for registry");
}
const SubGhzProtocol* protocol = NULL;
for(size_t i = 0; i < protopirate_protocol_registry.size; i++) {
if(strcmp(protopirate_protocol_registry.items[i]->name, registry_name) == 0) {
protocol = protopirate_protocol_registry.items[i];
FURI_LOG_I(TAG, "Found protocol %s in registry at index %zu", registry_name, i);
break;
}
}
if(!protocol || !protocol->encoder || !protocol->encoder->alloc) {
FURI_LOG_E(TAG, "Protocol %s has no encoder or not in registry", registry_name);
return false;
}
ctx->transmitter = subghz_transmitter_alloc_init(app->txrx->environment, registry_name);
if(!ctx->transmitter) {
FURI_LOG_E(TAG, "Failed to allocate transmitter for %s", registry_name);
return false;
}
flipper_format_rewind(ctx->flipper_format);
SubGhzProtocolStatus status =
subghz_transmitter_deserialize(ctx->transmitter, ctx->flipper_format);
if(status != SubGhzProtocolStatusOk) {
FURI_LOG_E(TAG, "Failed to deserialize transmitter, status: %d", status);
subghz_transmitter_free(ctx->transmitter);
ctx->transmitter = NULL;
return false;
}
FURI_LOG_I(TAG, "Transmitter ready (lazy init)");
return true;
}
static uint8_t protopirate_get_button_for_protocol(
const char* protocol,
InputKey key,
@@ -456,6 +508,12 @@ void protopirate_scene_emulate_on_enter(void* context) {
emulate_context_free();
}
if(app->txrx && app->txrx->history) {
protopirate_history_release_scratch(app->txrx->history);
}
protopirate_rx_stack_suspend_for_tx(app);
// Create emulate context
emulate_context = malloc(sizeof(EmulateContext));
if(!emulate_context) {
@@ -572,60 +630,6 @@ void protopirate_scene_emulate_on_enter(void* context) {
emulate_context->current_counter = emulate_context->original_counter;
}
// Set up transmitter based on protocol
const char* proto_name = furi_string_get_cstr(emulate_context->protocol_name);
FURI_LOG_I(TAG, "Setting up transmitter for protocol: %s", proto_name);
if(strcmp(proto_name, "Kia V3") == 0) {
proto_name = "Kia V3/V4";
FURI_LOG_I(TAG, "Protocol name KiaV3 fixed to Kia V3/V4 for registry");
} else if(strcmp(proto_name, "Kia V4") == 0) {
proto_name = "Kia V3/V4";
FURI_LOG_I(TAG, "Protocol name KiaV4 fixed to Kia V3/V4 for registry");
}
// Find the protocol in the registry
const SubGhzProtocol* protocol = NULL;
for(size_t i = 0; i < protopirate_protocol_registry.size; i++) {
if(strcmp(protopirate_protocol_registry.items[i]->name, proto_name) == 0) {
protocol = protopirate_protocol_registry.items[i];
FURI_LOG_I(TAG, "Found protocol %s in registry at index %zu", proto_name, i);
break;
}
}
if(protocol) {
if(protocol->encoder && protocol->encoder->alloc) {
FURI_LOG_I(TAG, "Protocol has encoder support");
// Try to create transmitter
emulate_context->transmitter =
subghz_transmitter_alloc_init(app->txrx->environment, proto_name);
if(emulate_context->transmitter) {
FURI_LOG_I(TAG, "Transmitter allocated successfully");
// Deserialize for transmission
flipper_format_rewind(emulate_context->flipper_format);
SubGhzProtocolStatus status = subghz_transmitter_deserialize(
emulate_context->transmitter, emulate_context->flipper_format);
if(status != SubGhzProtocolStatusOk) {
FURI_LOG_E(TAG, "Failed to deserialize transmitter, status: %d", status);
subghz_transmitter_free(emulate_context->transmitter);
emulate_context->transmitter = NULL;
} else {
FURI_LOG_I(TAG, "Transmitter deserialized successfully");
}
} else {
FURI_LOG_E(TAG, "Failed to allocate transmitter for %s", proto_name);
}
} else {
FURI_LOG_E(TAG, "Protocol %s has no encoder", proto_name);
}
} else {
FURI_LOG_E(TAG, "Protocol %s not found in registry", proto_name);
}
} else {
FURI_LOG_E(TAG, "No file path set");
emulate_context_free();
@@ -661,8 +665,13 @@ bool protopirate_scene_emulate_on_event(void* context, SceneManagerEvent event)
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case ProtoPirateCustomEventEmulateTransmit:
if(emulate_context && emulate_context->transmitter &&
emulate_context->flipper_format) {
if(emulate_context && emulate_context->flipper_format) {
if(!emulate_context_try_init_transmitter(app, emulate_context)) {
FURI_LOG_E(TAG, "No transmitter available");
notification_message(app->notifications, &sequence_error);
consumed = true;
break;
}
// Stop any ongoing transmission FIRST
if(app->txrx->txrx_state == ProtoPirateTxRxStateTx) {
FURI_LOG_W(TAG, "Previous transmission still active, stopping it");
@@ -792,9 +801,6 @@ bool protopirate_scene_emulate_on_event(void* context, SceneManagerEvent event)
if(free_custom_data)
free(preset_data); //We have used the preset, I alloced it I have to free.
} else {
FURI_LOG_E(TAG, "No transmitter available");
notification_message(app->notifications, &sequence_error);
}
consumed = true;
break;
+91 -66
View File
@@ -5,8 +5,7 @@
#include <notification/notification_messages.h>
#include "proto_pirate_icons.h"
#define TAG "ProtoPirateSceneRx"
#define PROTOPIRATE_DISPLAY_HISTORY_MAX 20 // Reduced from 50 to save memory
#define TAG "ProtoPirateSceneRx"
// Forward declaration
void protopirate_scene_receiver_view_callback(ProtoPirateCustomEvent event, void* context);
@@ -46,7 +45,7 @@ static void protopirate_scene_receiver_update_statusbar(void* context) {
history_stat_str,
"%u/%u",
protopirate_history_get_item(app->txrx->history),
PROTOPIRATE_DISPLAY_HISTORY_MAX);
PROTOPIRATE_HISTORY_MAX);
// Pass actual external radio status
protopirate_view_receiver_add_data_statusbar(
app->protopirate_receiver,
@@ -81,6 +80,7 @@ static void protopirate_scene_receiver_callback(
FURI_LOG_I(TAG, "%s", furi_string_get_cstr(str_buff));
// Add to history
uint16_t count_before = protopirate_history_get_item(app->txrx->history);
if(protopirate_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset)) {
notification_message(app->notifications, &sequence_semi_success);
@@ -89,20 +89,16 @@ static void protopirate_scene_receiver_callback(
"Added to history, total items: %u",
protopirate_history_get_item(app->txrx->history));
FuriString* item_name = furi_string_alloc();
if(!item_name) {
FURI_LOG_E(TAG, "item_name allocation failed");
return;
uint16_t count_after = protopirate_history_get_item(app->txrx->history);
if(count_after == PROTOPIRATE_HISTORY_MAX) {
protopirate_view_receiver_sync_menu_from_history(
app->protopirate_receiver, app->txrx->history);
} else if(count_after > count_before) {
protopirate_view_receiver_append_menu_row_from_history(
app->protopirate_receiver, app->txrx->history, count_after - 1);
}
protopirate_history_get_text_item_menu(
app->txrx->history, item_name, protopirate_history_get_item(app->txrx->history) - 1);
protopirate_view_receiver_add_item_to_menu(
app->protopirate_receiver, furi_string_get_cstr(item_name), 0);
furi_string_free(item_name);
// Auto-scroll to the last detected signal
uint16_t last_index = protopirate_history_get_item(app->txrx->history) - 1;
protopirate_view_receiver_set_idx_menu(app->protopirate_receiver, last_index);
@@ -165,42 +161,26 @@ static void protopirate_scene_receiver_callback(
}
}
void protopirate_scene_receiver_on_enter(void* context) {
furi_check(context);
ProtoPirateApp* app = context;
FURI_LOG_I(TAG, "=== ENTERING RECEIVER SCENE ===");
// Now safe to access radio device
#ifndef REMOVE_LOGS
bool is_external =
app->txrx->radio_device ? radio_device_loader_is_external(app->txrx->radio_device) : false;
const char* device_name =
app->txrx->radio_device ? subghz_devices_get_name(app->txrx->radio_device) : NULL;
FURI_LOG_I(TAG, "Radio device: %s", device_name ? device_name : "NULL");
FURI_LOG_I(TAG, "Is External: %s", is_external ? "YES" : "NO");
FURI_LOG_I(TAG, "Frequency: %lu Hz", app->txrx->preset->frequency);
FURI_LOG_I(TAG, "Modulation: %s", furi_string_get_cstr(app->txrx->preset->name));
FURI_LOG_I(TAG, "Auto-save: %s", app->auto_save ? "ON" : "OFF");
#endif
// Allocate history
if(!app->txrx->history) {
app->txrx->history = protopirate_history_alloc();
if(!app->txrx->history) {
FURI_LOG_E(TAG, "Failed to allocate history!");
return;
}
static void protopirate_scene_receiver_start_rx_stack(ProtoPirateApp* app) {
furi_check(app);
if(!app->radio_initialized) {
return;
}
protopirate_rx_stack_resume_after_tx(app);
if(!app->txrx->receiver) {
FURI_LOG_E(TAG, "SubGhz receiver unavailable — cannot start RX");
notification_message(app->notifications, &sequence_error);
scene_manager_previous_scene(app->scene_manager);
return;
}
// Allocate worker
if(!app->txrx->worker) {
app->txrx->worker = subghz_worker_alloc();
if(!app->txrx->worker) {
FURI_LOG_E(TAG, "Failed to allocate worker!");
return;
}
// Set up worker callbacks
subghz_worker_set_overrun_callback(
app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
subghz_worker_set_pair_callback(
@@ -208,22 +188,12 @@ void protopirate_scene_receiver_on_enter(void* context) {
subghz_worker_set_context(app->txrx->worker, app->txrx->receiver);
}
// Set up the receiver callback
subghz_receiver_set_rx_callback(app->txrx->receiver, protopirate_scene_receiver_callback, app);
// Set up view callback
protopirate_view_receiver_set_callback(
app->protopirate_receiver, protopirate_scene_receiver_view_callback, app);
// Update status bar
protopirate_scene_receiver_update_statusbar(app);
// Start hopper if enabled
if(app->txrx->hopper_state != ProtoPirateHopperStateOFF) {
app->txrx->hopper_state = ProtoPirateHopperStateRunning;
}
// Get preset data
const char* preset_name = furi_string_get_cstr(app->txrx->preset->name);
uint8_t* preset_data = subghz_setting_get_preset_data_by_name(app->setting, preset_name);
@@ -232,7 +202,6 @@ void protopirate_scene_receiver_on_enter(void* context) {
preset_data = subghz_setting_get_preset_data_by_name(app->setting, "AM650");
}
// Begin receiving
protopirate_begin(app, preset_data);
uint32_t frequency = app->txrx->preset->frequency;
@@ -244,17 +213,57 @@ void protopirate_scene_receiver_on_enter(void* context) {
FURI_LOG_I(TAG, "Starting RX on %lu Hz", frequency);
protopirate_rx(app, frequency);
FURI_LOG_I(TAG, "RX started, state: %d", app->txrx->txrx_state);
}
void protopirate_scene_receiver_on_enter(void* context) {
furi_check(context);
ProtoPirateApp* app = context;
if(app->txrx->history) {
protopirate_history_release_scratch(app->txrx->history);
}
if(!app->txrx->history) {
app->txrx->history = protopirate_history_alloc();
if(!app->txrx->history) {
FURI_LOG_E(TAG, "Failed to allocate history!");
return;
}
}
protopirate_view_receiver_set_callback(
app->protopirate_receiver, protopirate_scene_receiver_view_callback, app);
protopirate_scene_receiver_update_statusbar(app);
// Update lock state in view
protopirate_view_receiver_set_lock(app->protopirate_receiver, app->lock);
// Update auto-save state in view
protopirate_view_receiver_set_autosave(app->protopirate_receiver, app->auto_save);
//Not in Sub Decode Mode
protopirate_view_receiver_set_sub_decode_mode(app->protopirate_receiver, false);
// Switch to receiver view
if(app->radio_initialized && !app->txrx->receiver) {
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewReceiver);
view_dispatcher_send_custom_event(
app->view_dispatcher, ProtoPirateCustomEventReceiverDeferredRxStart);
return;
}
#ifndef REMOVE_LOGS
bool is_external =
app->txrx->radio_device ? radio_device_loader_is_external(app->txrx->radio_device) : false;
const char* device_name =
app->txrx->radio_device ? subghz_devices_get_name(app->txrx->radio_device) : NULL;
FURI_LOG_I(TAG, "=== ENTERING RECEIVER SCENE ===");
FURI_LOG_I(TAG, "Radio device: %s", device_name ? device_name : "NULL");
FURI_LOG_I(TAG, "Is External: %s", is_external ? "YES" : "NO");
FURI_LOG_I(TAG, "Frequency: %lu Hz", app->txrx->preset->frequency);
FURI_LOG_I(TAG, "Modulation: %s", furi_string_get_cstr(app->txrx->preset->name));
FURI_LOG_I(TAG, "Auto-save: %s", app->auto_save ? "ON" : "OFF");
#endif
if(app->radio_initialized) {
protopirate_scene_receiver_start_rx_stack(app);
}
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewReceiver);
}
@@ -277,6 +286,14 @@ bool protopirate_scene_receiver_on_event(void* context, SceneManagerEvent event)
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case ProtoPirateCustomEventReceiverDeferredRxStart:
#ifndef REMOVE_LOGS
FURI_LOG_I(TAG, "Deferred RX start (post-emulate path)");
#endif
protopirate_scene_receiver_start_rx_stack(app);
consumed = true;
break;
case ProtoPirateCustomEventSceneReceiverUpdate:
protopirate_scene_receiver_update_statusbar(app);
consumed = true;
@@ -315,7 +332,11 @@ bool protopirate_scene_receiver_on_event(void* context, SceneManagerEvent event)
// Update hopper
if(app->txrx->hopper_state != ProtoPirateHopperStateOFF) {
protopirate_hopper_update(app);
protopirate_scene_receiver_update_statusbar(app);
static uint8_t hopper_statusbar_tick = 0;
if(++hopper_statusbar_tick >= 8) {
hopper_statusbar_tick = 0;
protopirate_scene_receiver_update_statusbar(app);
}
}
// Update RSSI from the correct radio device (only if initialized)
@@ -352,10 +373,19 @@ void protopirate_scene_receiver_on_exit(void* context) {
FURI_LOG_I(TAG, "=== EXITING RECEIVER SCENE ===");
const bool leaving_for_subscene =
(scene_manager_get_scene_state(app->scene_manager, ProtoPirateSceneReceiver) == 1);
// Only try to stop RX if radio is initialized
if(app->radio_initialized && app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
protopirate_rx_end(app);
}
if(leaving_for_subscene) {
scene_manager_set_scene_state(app->scene_manager, ProtoPirateSceneReceiver, 0);
return;
}
if(app->txrx->worker) {
FURI_LOG_D(TAG, "Freeing worker %p", app->txrx->worker);
subghz_worker_free(app->txrx->worker);
@@ -364,11 +394,6 @@ void protopirate_scene_receiver_on_exit(void* context) {
FURI_LOG_D(TAG, "Worker was NULL, skipping free");
}
if(scene_manager_get_scene_state(app->scene_manager, ProtoPirateSceneReceiver) == 1) {
scene_manager_set_scene_state(app->scene_manager, ProtoPirateSceneReceiver, 0);
return;
}
// Full teardown: put radio to sleep, free worker and history
protopirate_sleep(app);
+14 -13
View File
@@ -97,7 +97,8 @@ static void protopirate_receiver_info_build_normal_widget(ProtoPirateApp* app) {
app->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(text));
furi_string_reset(text);
protopirate_history_get_text_item(app->txrx->history, text, app->txrx->idx_menu_chosen);
protopirate_history_get_text_item_detail(
app->txrx->history, app->txrx->idx_menu_chosen, text, app->txrx->environment);
bool is_psa = false;
FlipperFormat* ff =
@@ -309,6 +310,7 @@ static void psa_bf_finish_and_show_result(ProtoPirateApp* app) {
protopirate_history_set_item_str(
app->txrx->history, app->txrx->idx_menu_chosen, furi_string_get_cstr(new_str));
furi_string_free(new_str);
protopirate_history_commit_loaded(app->txrx->history);
}
if(status == PSA_BF_STATUS_FOUND) {
@@ -514,19 +516,18 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e
#ifdef ENABLE_EMULATE_FEATURE
if(event.event == ProtoPirateCustomEventReceiverInfoEmulate && !is_emu_off) {
FlipperFormat* ff =
protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen);
if(ff) {
if(protopirate_storage_save_temp(ff)) {
FURI_LOG_I(TAG, "Saved temp for emulate");
if(app->loaded_file_path) furi_string_free(app->loaded_file_path);
app->loaded_file_path = furi_string_alloc_set_str(PROTOPIRATE_TEMP_FILE);
scene_manager_next_scene(app->scene_manager, ProtoPirateSceneEmulate);
} else {
notification_message(app->notifications, &sequence_error);
}
FuriString* hist_path = furi_string_alloc();
if(protopirate_history_get_capture_path(
app->txrx->history, app->txrx->idx_menu_chosen, hist_path)) {
protopirate_history_release_scratch(app->txrx->history);
if(app->loaded_file_path) furi_string_free(app->loaded_file_path);
app->loaded_file_path = furi_string_alloc_set(hist_path);
furi_string_free(hist_path);
FURI_LOG_I(TAG, "Emulate from history file: %s", furi_string_get_cstr(app->loaded_file_path));
scene_manager_next_scene(app->scene_manager, ProtoPirateSceneEmulate);
} else {
FURI_LOG_E(TAG, "No flipper format data for index %d", app->txrx->idx_menu_chosen);
furi_string_free(hist_path);
FURI_LOG_E(TAG, "No capture path for index %d", app->txrx->idx_menu_chosen);
notification_message(app->notifications, &sequence_error);
}
consumed = true;
+14 -20
View File
@@ -481,6 +481,10 @@ static void protopirate_scene_sub_decode_widget_callback(
void protopirate_scene_sub_decode_on_enter(void* context) {
ProtoPirateApp* app = context;
if(app->radio_initialized) {
protopirate_rx_stack_resume_after_tx(app);
}
FURI_LOG_I(TAG, "Sub decode scene enter - Free heap: %zu", memmgr_get_free_heap());
g_decode_ctx = malloc(sizeof(SubDecodeContext));
@@ -567,15 +571,8 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even
// 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);
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_sync_menu_from_history(
app->protopirate_receiver, ctx->history);
protopirate_view_receiver_set_idx_menu(
app->protopirate_receiver, ctx->selected_history_index);
@@ -732,6 +729,7 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even
ctx->selected_history_index,
furi_string_get_cstr(new_str));
furi_string_free(new_str);
protopirate_history_commit_loaded(ctx->history);
notification_message(app->notifications, &sequence_success);
}
free(app->psa_bf_state);
@@ -1147,16 +1145,8 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even
// 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);
protopirate_view_receiver_sync_menu_from_history(
app->protopirate_receiver, ctx->history);
// Set initial selection
protopirate_view_receiver_set_idx_menu(
@@ -1221,7 +1211,11 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even
// Get full text for body
furi_string_reset(text);
protopirate_history_get_text_item(ctx->history, text, ctx->selected_history_index);
protopirate_history_get_text_item_detail(
ctx->history,
ctx->selected_history_index,
text,
app->txrx->environment);
widget_add_text_scroll_element(
app->widget, 0, 0, 128, 50, furi_string_get_cstr(text));
+4
View File
@@ -631,6 +631,10 @@ void protopirate_scene_timing_tuner_on_enter(void* context) {
g_timing_ctx->buffer_wrapped = false;
g_timing_ctx->app = app;
if(app->radio_initialized) {
protopirate_rx_stack_resume_after_tx(app);
}
view_set_draw_callback(app->view_about, timing_tuner_draw_callback);
view_set_input_callback(app->view_about, timing_tuner_input_callback);
view_set_context(app->view_about, app);
+76 -8
View File
@@ -1,5 +1,6 @@
// views/protopirate_receiver.c
#include "protopirate_receiver.h"
#include "../protopirate_history.h"
#include "../protopirate_app_i.h"
#include <input/input.h>
#include <gui/elements.h>
@@ -35,6 +36,7 @@ typedef struct {
FuriString* frequency_str;
FuriString* preset_str;
FuriString* history_stat_str;
FuriString* draw_scratch;
bool external_radio;
ProtoPirateLock lock;
uint8_t lock_count;
@@ -199,9 +201,6 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo
size_t item_count = ProtoPirateReceiverMenuItemArray_size(model->history_item_arr);
bool scrollbar = item_count > MENU_ITEMS;
FuriString* str_buff;
str_buff = furi_string_alloc();
if(!model->sub_decode_mode) {
//Config button. (Do it at the top so we dont get Inversion problems from the list view part.)
elements_button_left(canvas, "Config");
@@ -244,8 +243,9 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo
ProtoPirateReceiverMenuItem* item =
ProtoPirateReceiverMenuItemArray_get(model->history_item_arr, idx);
furi_string_set(str_buff, item->item_str);
elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX);
furi_string_set(model->draw_scratch, item->item_str);
elements_string_fit_width(
canvas, model->draw_scratch, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX);
if(model->history_item == idx) {
protopirate_view_receiver_draw_frame(canvas, i, scrollbar);
@@ -253,7 +253,8 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo
canvas_set_color(canvas, ColorBlack);
}
canvas_draw_str(canvas, 4, 9 + (i * FRAME_HEIGHT), furi_string_get_cstr(str_buff));
canvas_draw_str(
canvas, 4, 9 + (i * FRAME_HEIGHT), furi_string_get_cstr(model->draw_scratch));
}
//Draw scrollbar if needed
@@ -395,8 +396,6 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo
canvas, 110 - canvas_string_width(canvas, auto_save_text), 7, auto_save_text);
}
}
furi_string_free(str_buff);
}
bool protopirate_view_receiver_input(InputEvent* event, void* context) {
@@ -555,6 +554,8 @@ ProtoPirateReceiver* protopirate_view_receiver_alloc(bool auto_save) {
model->frequency_str = furi_string_alloc();
model->preset_str = furi_string_alloc();
model->history_stat_str = furi_string_alloc();
model->draw_scratch = furi_string_alloc();
furi_check(model->draw_scratch);
model->list_offset = 0;
model->history_item = 0;
model->rssi = -127.0f;
@@ -588,6 +589,7 @@ void protopirate_view_receiver_free(ProtoPirateReceiver* receiver) {
furi_string_free(model->frequency_str);
furi_string_free(model->preset_str);
furi_string_free(model->history_stat_str);
furi_string_free(model->draw_scratch);
},
false);
@@ -614,6 +616,72 @@ void protopirate_view_receiver_reset_menu(ProtoPirateReceiver* receiver) {
false);
}
void protopirate_view_receiver_sync_menu_from_history(
ProtoPirateReceiver* receiver,
ProtoPirateHistory* history) {
furi_check(receiver);
furi_check(history);
protopirate_view_receiver_reset_menu(receiver);
uint16_t count = protopirate_history_get_item(history);
if(count == 0) {
return;
}
FuriString* line = furi_string_alloc();
furi_check(line);
for(uint16_t i = 0; i < count; i++) {
protopirate_history_get_text_item_menu(history, line, i);
protopirate_view_receiver_add_item_to_menu(
receiver, furi_string_get_cstr(line), 0);
}
furi_string_free(line);
}
void protopirate_view_receiver_pop_first_menu_item(ProtoPirateReceiver* receiver) {
furi_check(receiver);
with_view_model(
receiver->view,
ProtoPirateReceiverModel * model,
{
if(ProtoPirateReceiverMenuItemArray_size(model->history_item_arr) > 0) {
ProtoPirateReceiverMenuItem* first =
ProtoPirateReceiverMenuItemArray_get(model->history_item_arr, 0);
furi_string_free(first->item_str);
ProtoPirateReceiverMenuItemArray_pop_at(NULL, model->history_item_arr, 0);
if(model->history_item > 0) {
model->history_item--;
}
size_t item_count =
ProtoPirateReceiverMenuItemArray_size(model->history_item_arr);
if(model->list_offset > 0 && model->list_offset >= item_count) {
model->list_offset = item_count > 0 ? item_count - 1 : 0;
}
}
},
true);
protopirate_view_receiver_update_offset(receiver);
}
void protopirate_view_receiver_append_menu_row_from_history(
ProtoPirateReceiver* receiver,
ProtoPirateHistory* history,
uint16_t idx) {
furi_check(receiver);
furi_check(history);
FuriString* line = furi_string_alloc();
if(!line) {
protopirate_view_receiver_sync_menu_from_history(receiver, history);
return;
}
protopirate_history_get_text_item_menu(history, line, idx);
protopirate_view_receiver_add_item_to_menu(
receiver, furi_string_get_cstr(line), 0);
furi_string_free(line);
}
View* protopirate_view_receiver_get_view(ProtoPirateReceiver* receiver) {
furi_check(receiver);
return receiver->view;
+12
View File
@@ -5,6 +5,7 @@
#include "../helpers/protopirate_types.h"
typedef struct ProtoPirateReceiver ProtoPirateReceiver;
typedef struct ProtoPirateHistory ProtoPirateHistory;
typedef void (*ProtoPirateReceiverCallback)(ProtoPirateCustomEvent event, void* context);
@@ -38,3 +39,14 @@ void protopirate_view_receiver_set_sub_decode_mode(
ProtoPirateReceiver* receiver,
bool sub_decode_mode);
void protopirate_view_receiver_reset_menu(ProtoPirateReceiver* receiver);
void protopirate_view_receiver_sync_menu_from_history(
ProtoPirateReceiver* receiver,
ProtoPirateHistory* history);
void protopirate_view_receiver_pop_first_menu_item(ProtoPirateReceiver* receiver);
void protopirate_view_receiver_append_menu_row_from_history(
ProtoPirateReceiver* receiver,
ProtoPirateHistory* history,
uint16_t idx);