[FL-3618] Infrared remote button index support (#3180)

* Do not load all signals at once (Draft)
* Minor cleanup
* Refactor remote renaming
* Improve function signatures
* Rename infrared_remote functions
* Optimise signal loading
* Implement adding signals to remote
* Add read_name() method
* Deprecate a function
* Partially implement deleting signals (draft)
* Use m-array instead of m-list for signal name directory
* Use plain C strings instead of furi_string
* Implement deleting signals
* Implement deleting signals via generalised callback
* Implement renaming signals
* Rename some types
* Some more renaming
* Remove unused type
* Implement inserting signals (internal use)
* Improve InfraredMoveView
* Send an event to move a signal
* Remove unused type
* Implement moving signals
* Implement creating new remotes with one signal
* Un-deprecate and rename a function
* Add InfraredRemote API docs
* Add InfraredSignal API docs
* Better error messages
* Show progress pop-up when moving buttons in a remote
* Copy labels to the InfraredMoveView to avoid pointer invalidation
* Improve file selection scene
* Show progress pop-up when renaming buttons in a remote
* Refactor a scene
* Show progress when deleting a button from remote
* Use a random name for temp files
* Add docs to infrared_brute_force.h
* Rename Infrared type to InfraredApp
* Add docs to infrared_app_i.h
* Deliver event data via a callback
* Bundle event data together with event type
* Change DataExchange behaviour
* Adapt RPC debug app to new API
* Remove rogue output
* Add Doxygen comments to rpc_app.h
* Simplify rpc_app.c code
* Remove superflous parameter
* Do not allocate protobuf messages on the stack
* Fix GetError response
* Support for button indices
* Comment out shallow submodules
* Fix F18 api
* Fix logical error and add more debug output
* fbt: testing unshallow for protobuf
* github: lint&checks: unshallow prior to checks
* Fix a TODO
* github: do not unshallow the unshallowed
* fbt: assets: only attempt to unshallow if cannot describe
* Do not use the name when loading a signal by index (duh)
* Simplify loading infrared signals by name
* Sync with protobuf release
* Infrared: use compact furi_crash macros

Co-authored-by: hedger <hedger@nanode.su>
Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
Georgii Surkov
2023-11-10 16:22:34 +09:00
committed by GitHub
co-authored by hedger hedger Aleksandr Kutuzov
parent 16ffa2bf75
commit 49dcf81743
29 changed files with 668 additions and 413 deletions
+24 -10
View File
@@ -44,30 +44,42 @@ static void infrared_tick_event_callback(void* context) {
scene_manager_handle_tick_event(infrared->scene_manager);
}
static void infrared_rpc_command_callback(RpcAppSystemEvent event, void* context) {
static void infrared_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
furi_assert(context);
InfraredApp* infrared = context;
furi_assert(infrared->rpc_ctx);
if(event == RpcAppEventSessionClose) {
if(event->type == RpcAppEventTypeSessionClose) {
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcSessionClose);
rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL);
infrared->rpc_ctx = NULL;
} else if(event == RpcAppEventAppExit) {
} else if(event->type == RpcAppEventTypeAppExit) {
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcExit);
} else if(event == RpcAppEventLoadFile) {
} else if(event->type == RpcAppEventTypeLoadFile) {
furi_assert(event->data.type == RpcAppSystemEventDataTypeString);
furi_string_set(infrared->file_path, event->data.string);
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcLoad);
} else if(event == RpcAppEventButtonPress) {
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPress);
} else if(event == RpcAppEventButtonRelease) {
infrared->view_dispatcher, InfraredCustomEventTypeRpcLoadFile);
} else if(event->type == RpcAppEventTypeButtonPress) {
furi_assert(
event->data.type == RpcAppSystemEventDataTypeString ||
event->data.type == RpcAppSystemEventDataTypeInt32);
if(event->data.type == RpcAppSystemEventDataTypeString) {
furi_string_set(infrared->button_name, event->data.string);
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressName);
} else {
infrared->app_state.current_button_index = event->data.i32;
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressIndex);
}
} else if(event->type == RpcAppEventTypeButtonRelease) {
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonRelease);
} else {
rpc_system_app_confirm(infrared->rpc_ctx, event, false);
rpc_system_app_confirm(infrared->rpc_ctx, false);
}
}
@@ -117,6 +129,7 @@ static InfraredApp* infrared_alloc() {
InfraredApp* infrared = malloc(sizeof(InfraredApp));
infrared->file_path = furi_string_alloc();
infrared->button_name = furi_string_alloc();
InfraredAppState* app_state = &infrared->app_state;
app_state->is_learning_new_remote = false;
@@ -247,6 +260,7 @@ static void infrared_free(InfraredApp* infrared) {
infrared->gui = NULL;
furi_string_free(infrared->file_path);
furi_string_free(infrared->button_name);
free(infrared);
}
@@ -121,6 +121,7 @@ struct InfraredApp {
InfraredProgressView* progress; /**< Custom view for showing brute force progress. */
FuriString* file_path; /**< Full path to the currently loaded file. */
FuriString* button_name; /** Name of the button requested in RPC mode. */
/** Arbitrary text storage for various inputs. */
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
InfraredAppState app_state; /**< Application state. */
@@ -128,7 +128,7 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
furi_assert(brute_force->is_started);
const bool success = infrared_signal_search_and_read(
const bool success = infrared_signal_search_by_name_and_read(
brute_force->current_signal,
brute_force->ff,
furi_string_get_cstr(brute_force->current_record_name));
@@ -15,9 +15,10 @@ enum InfraredCustomEventType {
InfraredCustomEventTypeButtonSelected,
InfraredCustomEventTypeBackPressed,
InfraredCustomEventTypeRpcLoad,
InfraredCustomEventTypeRpcLoadFile,
InfraredCustomEventTypeRpcExit,
InfraredCustomEventTypeRpcButtonPress,
InfraredCustomEventTypeRpcButtonPressName,
InfraredCustomEventTypeRpcButtonPressIndex,
InfraredCustomEventTypeRpcButtonRelease,
InfraredCustomEventTypeRpcSessionClose,
};
+3 -4
View File
@@ -95,10 +95,9 @@ bool infrared_remote_load_signal(
const char* path = furi_string_get_cstr(remote->path);
if(!flipper_format_buffered_file_open_existing(ff, path)) break;
const char* name = infrared_remote_get_signal_name(remote, index);
if(!infrared_signal_search_and_read(signal, ff, name)) {
FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", name, path);
if(!infrared_signal_search_by_index_and_read(signal, ff, index)) {
const char* signal_name = infrared_remote_get_signal_name(remote, index);
FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", signal_name, path);
break;
}
+27 -9
View File
@@ -266,19 +266,37 @@ bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name) {
return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name);
}
bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name) {
bool infrared_signal_search_by_name_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
const char* name) {
bool success = false;
FuriString* tmp = furi_string_alloc();
do {
bool is_name_found = false;
while(!is_name_found && infrared_signal_read_name(ff, tmp)) { //-V560
is_name_found = furi_string_equal(tmp, name);
while(infrared_signal_read_name(ff, tmp)) {
if(furi_string_equal(tmp, name)) {
success = infrared_signal_read_body(signal, ff);
break;
}
if(!is_name_found) break; //-V547
if(!infrared_signal_read_body(signal, ff)) break; //-V779
success = true;
} while(false);
}
furi_string_free(tmp);
return success;
}
bool infrared_signal_search_by_index_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
size_t index) {
bool success = false;
FuriString* tmp = furi_string_alloc();
for(uint32_t i = 0; infrared_signal_read_name(ff, tmp); ++i) {
if(i == index) {
success = infrared_signal_read_body(signal, ff);
break;
}
}
furi_string_free(tmp);
return success;
+20 -1
View File
@@ -162,7 +162,26 @@ bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name);
* @param[in] name pointer to a zero-terminated string containing the requested signal name.
* @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found).
*/
bool infrared_signal_search_and_read(InfraredSignal* signal, FlipperFormat* ff, const char* name);
bool infrared_signal_search_by_name_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
const char* name);
/**
* @brief Read a signal with a particular index from a FlipperFormat file into an InfraredSignal instance.
*
* This function will look for a signal with the given index and if found, attempt to read it.
* Same considerations apply as to infrared_signal_read().
*
* @param[in,out] signal pointer to the instance to be read into.
* @param[in,out] ff pointer to the FlipperFormat file instance to read from.
* @param[in] index the requested signal index.
* @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found).
*/
bool infrared_signal_search_by_index_and_read(
InfraredSignal* signal,
FlipperFormat* ff,
size_t index);
/**
* @brief Save a signal contained in an InfraredSignal instance to a FlipperFormat file.
@@ -1,7 +1,7 @@
#include "../infrared_app_i.h"
typedef enum {
ButtonIndexPlus = -2,
ButtonIndexLearn = -2,
ButtonIndexEdit = -1,
ButtonIndexNA = 0,
} ButtonIndex;
@@ -44,7 +44,7 @@ void infrared_scene_remote_on_enter(void* context) {
button_menu_add_item(
button_menu,
"+",
ButtonIndexPlus,
ButtonIndexLearn,
infrared_scene_remote_button_menu_callback,
ButtonMenuItemTypeControl,
context);
@@ -95,7 +95,7 @@ bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) {
if(is_transmitter_idle) {
scene_manager_set_scene_state(
scene_manager, InfraredSceneRemote, (unsigned)button_index);
if(button_index == ButtonIndexPlus) {
if(button_index == ButtonIndexLearn) {
infrared->app_state.is_learning_new_remote = false;
scene_manager_next_scene(scene_manager, InfraredSceneLearn);
consumed = true;
@@ -1,6 +1,8 @@
#include "../infrared_app_i.h"
#include <gui/canvas.h>
#define TAG "InfraredApp"
typedef enum {
InfraredRpcStateIdle,
InfraredRpcStateLoaded,
@@ -38,11 +40,9 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
view_dispatcher_stop(infrared->view_dispatcher);
} else if(event.event == InfraredCustomEventTypePopupClosed) {
view_dispatcher_stop(infrared->view_dispatcher);
} else if(event.event == InfraredCustomEventTypeRpcLoad) {
} else if(event.event == InfraredCustomEventTypeRpcLoadFile) {
bool result = false;
const char* arg = rpc_system_app_get_data(infrared->rpc_ctx);
if(arg && (state == InfraredRpcStateIdle)) {
furi_string_set(infrared->file_path, arg);
if(state == InfraredRpcStateIdle) {
result = infrared_remote_load(
infrared->remote, furi_string_get_cstr(infrared->file_path));
if(result) {
@@ -56,20 +56,35 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
popup_set_text(
infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop);
rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventLoadFile, result);
} else if(event.event == InfraredCustomEventTypeRpcButtonPress) {
rpc_system_app_confirm(infrared->rpc_ctx, result);
} else if(
event.event == InfraredCustomEventTypeRpcButtonPressName ||
event.event == InfraredCustomEventTypeRpcButtonPressIndex) {
bool result = false;
const char* arg = rpc_system_app_get_data(infrared->rpc_ctx);
if(arg && (state == InfraredRpcStateLoaded)) {
size_t button_index = 0;
if(infrared_remote_get_signal_index(infrared->remote, arg, &button_index)) {
infrared_tx_start_button_index(infrared, button_index);
result = true;
if(state == InfraredRpcStateLoaded) {
if(event.event == InfraredCustomEventTypeRpcButtonPressName) {
const char* button_name = furi_string_get_cstr(infrared->button_name);
size_t index;
const bool index_found =
infrared_remote_get_signal_index(infrared->remote, button_name, &index);
infrared->app_state.current_button_index =
index_found ? (signed)index : InfraredButtonIndexNone;
FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name);
} else {
FURI_LOG_D(
TAG,
"Sending signal with index \"%ld\"",
infrared->app_state.current_button_index);
}
if(infrared->app_state.current_button_index != InfraredButtonIndexNone) {
infrared_tx_start_button_index(
infrared, infrared->app_state.current_button_index);
scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending);
result = true;
}
}
rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventButtonRelease, result);
rpc_system_app_confirm(infrared->rpc_ctx, result);
} else if(event.event == InfraredCustomEventTypeRpcButtonRelease) {
bool result = false;
if(state == InfraredRpcStateSending) {
@@ -78,11 +93,11 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
}
rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventButtonRelease, result);
rpc_system_app_confirm(infrared->rpc_ctx, result);
} else if(event.event == InfraredCustomEventTypeRpcExit) {
scene_manager_stop(infrared->scene_manager);
view_dispatcher_stop(infrared->view_dispatcher);
rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventAppExit, true);
rpc_system_app_confirm(infrared->rpc_ctx, true);
} else if(event.event == InfraredCustomEventTypeRpcSessionClose) {
scene_manager_stop(infrared->scene_manager);
view_dispatcher_stop(infrared->view_dispatcher);