[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 10:22:34 +03:00 committed by GitHub
parent 16ffa2bf75
commit 49dcf81743
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 668 additions and 413 deletions

View File

@ -23,7 +23,7 @@ jobs:
- name: 'Check protobuf branch'
run: |
git submodule update --init
git submodule update --init;
SUB_PATH="assets/protobuf";
SUB_BRANCH="dev";
SUB_COMMITS_MIN=40;

View File

@ -21,22 +21,51 @@ static void rpc_debug_app_tick_event_callback(void* context) {
scene_manager_handle_tick_event(app->scene_manager);
}
static void rpc_debug_app_rpc_command_callback(RpcAppSystemEvent event, void* context) {
static void
rpc_debug_app_format_hex(const uint8_t* data, size_t data_size, char* buf, size_t buf_size) {
if(data == NULL || data_size == 0) {
strncpy(buf, "<Data empty>", buf_size);
return;
}
const size_t byte_width = 3;
const size_t line_width = 7;
data_size = MIN(data_size, buf_size / (byte_width + 1));
for(size_t i = 0; i < data_size; ++i) {
char* p = buf + (i * byte_width);
char sep = !((i + 1) % line_width) ? '\n' : ' ';
snprintf(p, byte_width + 1, "%02X%c", data[i], sep);
}
buf[buf_size - 1] = '\0';
}
static void rpc_debug_app_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
furi_assert(context);
RpcDebugApp* app = context;
furi_assert(app->rpc);
if(event == RpcAppEventSessionClose) {
if(event->type == RpcAppEventTypeSessionClose) {
scene_manager_stop(app->scene_manager);
view_dispatcher_stop(app->view_dispatcher);
rpc_system_app_set_callback(app->rpc, NULL, NULL);
app->rpc = NULL;
} else if(event == RpcAppEventAppExit) {
} else if(event->type == RpcAppEventTypeAppExit) {
scene_manager_stop(app->scene_manager);
view_dispatcher_stop(app->view_dispatcher);
rpc_system_app_confirm(app->rpc, RpcAppEventAppExit, true);
rpc_system_app_confirm(app->rpc, true);
} else if(event->type == RpcAppEventTypeDataExchange) {
furi_assert(event->data.type == RpcAppSystemEventDataTypeBytes);
rpc_debug_app_format_hex(
event->data.bytes.ptr, event->data.bytes.size, app->text_store, TEXT_STORE_SIZE);
view_dispatcher_send_custom_event(
app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange);
} else {
rpc_system_app_confirm(app->rpc, event, false);
rpc_system_app_confirm(app->rpc, false);
}
}

View File

@ -1,40 +1,5 @@
#include "../rpc_debug_app.h"
static void rpc_debug_app_scene_start_format_hex(
const uint8_t* data,
size_t data_size,
char* buf,
size_t buf_size) {
furi_assert(data);
furi_assert(buf);
const size_t byte_width = 3;
const size_t line_width = 7;
data_size = MIN(data_size, buf_size / (byte_width + 1));
for(size_t i = 0; i < data_size; ++i) {
char* p = buf + (i * byte_width);
char sep = !((i + 1) % line_width) ? '\n' : ' ';
snprintf(p, byte_width + 1, "%02X%c", data[i], sep);
}
buf[buf_size - 1] = '\0';
}
static void rpc_debug_app_scene_receive_data_exchange_callback(
const uint8_t* data,
size_t data_size,
void* context) {
RpcDebugApp* app = context;
if(data) {
rpc_debug_app_scene_start_format_hex(data, data_size, app->text_store, TEXT_STORE_SIZE);
} else {
strncpy(app->text_store, "<Data empty>", TEXT_STORE_SIZE);
}
view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange);
}
void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) {
RpcDebugApp* app = context;
strncpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE);
@ -42,8 +7,6 @@ void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) {
text_box_set_text(app->text_box, app->text_store);
text_box_set_font(app->text_box, TextBoxFontHex);
rpc_system_app_set_data_exchange_callback(
app->rpc, rpc_debug_app_scene_receive_data_exchange_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextBox);
}
@ -53,6 +16,7 @@ bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneMana
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RpcDebugAppCustomEventRpcDataExchange) {
rpc_system_app_confirm(app->rpc, true);
notification_message(app->notifications, &sequence_blink_cyan_100);
notification_message(app->notifications, &sequence_display_backlight_on);
text_box_set_text(app->text_box, app->text_store);
@ -66,5 +30,4 @@ bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneMana
void rpc_debug_app_scene_receive_data_exchange_on_exit(void* context) {
RpcDebugApp* app = context;
text_box_reset(app->text_box);
rpc_system_app_set_data_exchange_callback(app->rpc, NULL, NULL);
}

View File

@ -39,21 +39,23 @@ static void ibutton_make_app_folder(iButton* ibutton) {
furi_record_close(RECORD_STORAGE);
}
static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) {
static void ibutton_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
furi_assert(context);
iButton* ibutton = context;
if(event == RpcAppEventSessionClose) {
if(event->type == RpcAppEventTypeSessionClose) {
view_dispatcher_send_custom_event(
ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose);
rpc_system_app_set_callback(ibutton->rpc, NULL, NULL);
ibutton->rpc = NULL;
} else if(event == RpcAppEventAppExit) {
} else if(event->type == RpcAppEventTypeAppExit) {
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
} else if(event == RpcAppEventLoadFile) {
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoad);
} else if(event->type == RpcAppEventTypeLoadFile) {
furi_assert(event->data.type == RpcAppSystemEventDataTypeString);
furi_string_set(ibutton->file_path, event->data.string);
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoadFile);
} else {
rpc_system_app_confirm(ibutton->rpc, event, false);
rpc_system_app_confirm(ibutton->rpc, false);
}
}

View File

@ -15,7 +15,7 @@ typedef enum {
iButtonCustomEventWorkerWriteNoDetect,
iButtonCustomEventWorkerWriteCannotWrite,
iButtonCustomEventRpcLoad,
iButtonCustomEventRpcLoadFile,
iButtonCustomEventRpcExit,
iButtonCustomEventRpcSessionClose,
} iButtonCustomEvent;

View File

@ -23,28 +23,23 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == iButtonCustomEventRpcLoad) {
if(event.event == iButtonCustomEventRpcLoadFile) {
bool result = false;
const char* file_path = rpc_system_app_get_data(ibutton->rpc);
if(file_path && (furi_string_empty(ibutton->file_path))) {
furi_string_set(ibutton->file_path, file_path);
if(ibutton_load_key(ibutton)) {
popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
if(ibutton_load_key(ibutton)) {
popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
ibutton_worker_emulate_start(ibutton->worker, ibutton->key);
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
ibutton_worker_emulate_start(ibutton->worker, ibutton->key);
result = true;
}
result = true;
}
rpc_system_app_confirm(ibutton->rpc, RpcAppEventLoadFile, result);
rpc_system_app_confirm(ibutton->rpc, result);
} else if(event.event == iButtonCustomEventRpcExit) {
rpc_system_app_confirm(ibutton->rpc, RpcAppEventAppExit, true);
rpc_system_app_confirm(ibutton->rpc, true);
scene_manager_stop(ibutton->scene_manager);
view_dispatcher_stop(ibutton->view_dispatcher);

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);
}

View File

@ -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. */

View File

@ -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));

View File

@ -15,9 +15,10 @@ enum InfraredCustomEventType {
InfraredCustomEventTypeButtonSelected,
InfraredCustomEventTypeBackPressed,
InfraredCustomEventTypeRpcLoad,
InfraredCustomEventTypeRpcLoadFile,
InfraredCustomEventTypeRpcExit,
InfraredCustomEventTypeRpcButtonPress,
InfraredCustomEventTypeRpcButtonPressName,
InfraredCustomEventTypeRpcButtonPressIndex,
InfraredCustomEventTypeRpcButtonRelease,
InfraredCustomEventTypeRpcSessionClose,
};

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;
}

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;

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.

View 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;

View File

@ -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);

View File

@ -13,21 +13,23 @@ static bool lfrfid_debug_back_event_callback(void* context) {
return scene_manager_handle_back_event(app->scene_manager);
}
static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) {
static void rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
furi_assert(context);
LfRfid* app = (LfRfid*)context;
if(rpc_event == RpcAppEventSessionClose) {
if(event->type == RpcAppEventTypeSessionClose) {
view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventRpcSessionClose);
// Detach RPC
rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL);
app->rpc_ctx = NULL;
} else if(rpc_event == RpcAppEventAppExit) {
} else if(event->type == RpcAppEventTypeAppExit) {
view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventExit);
} else if(rpc_event == RpcAppEventLoadFile) {
} else if(event->type == RpcAppEventTypeLoadFile) {
furi_assert(event->data.type == RpcAppSystemEventDataTypeString);
furi_string_set(app->file_path, event->data.string);
view_dispatcher_send_custom_event(app->view_dispatcher, LfRfidEventRpcLoadFile);
} else {
rpc_system_app_confirm(app->rpc_ctx, rpc_event, false);
rpc_system_app_confirm(app->rpc_ctx, false);
}
}

View File

@ -24,17 +24,15 @@ bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == LfRfidEventExit) {
rpc_system_app_confirm(app->rpc_ctx, RpcAppEventAppExit, true);
rpc_system_app_confirm(app->rpc_ctx, true);
scene_manager_stop(app->scene_manager);
view_dispatcher_stop(app->view_dispatcher);
} else if(event.event == LfRfidEventRpcSessionClose) {
scene_manager_stop(app->scene_manager);
view_dispatcher_stop(app->view_dispatcher);
} else if(event.event == LfRfidEventRpcLoadFile) {
const char* arg = rpc_system_app_get_data(app->rpc_ctx);
bool result = false;
if(arg && (app->rpc_state == LfRfidRpcStateIdle)) {
furi_string_set(app->file_path, arg);
if(app->rpc_state == LfRfidRpcStateIdle) {
if(lfrfid_load_key_data(app, app->file_path, false)) {
lfrfid_worker_start_thread(app->lfworker);
lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id);
@ -48,7 +46,7 @@ bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) {
result = true;
}
}
rpc_system_app_confirm(app->rpc_ctx, RpcAppEventLoadFile, result);
rpc_system_app_confirm(app->rpc_ctx, result);
}
}
return consumed;

View File

@ -21,7 +21,7 @@ typedef enum {
NfcCustomEventTextInputDone,
NfcCustomEventDictAttackDone,
NfcCustomEventRpcLoad,
NfcCustomEventRpcLoadFile,
NfcCustomEventRpcExit,
NfcCustomEventRpcSessionClose,

View File

@ -694,15 +694,17 @@ static bool nfc_protocol_support_scene_rpc_on_event(NfcApp* instance, SceneManag
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventRpcLoad && instance->rpc_state == NfcRpcStateIdle) {
furi_string_set(instance->file_path, rpc_system_app_get_data(instance->rpc_ctx));
const bool load_success = nfc_load_file(instance, instance->file_path, false);
if(load_success) {
nfc_protocol_support_scene_rpc_setup_ui_and_emulate(instance);
if(event.event == NfcCustomEventRpcLoadFile) {
bool success = false;
if(instance->rpc_state == NfcRpcStateIdle) {
if(nfc_load_file(instance, instance->file_path, false)) {
nfc_protocol_support_scene_rpc_setup_ui_and_emulate(instance);
success = true;
}
}
rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventLoadFile, load_success);
rpc_system_app_confirm(instance->rpc_ctx, success);
} else if(event.event == NfcCustomEventRpcExit) {
rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventAppExit, true);
rpc_system_app_confirm(instance->rpc_ctx, true);
scene_manager_stop(instance->scene_manager);
view_dispatcher_stop(instance->view_dispatcher);
} else if(event.event == NfcCustomEventRpcSessionClose) {

View File

@ -14,22 +14,24 @@ bool nfc_back_event_callback(void* context) {
return scene_manager_handle_back_event(nfc->scene_manager);
}
static void nfc_app_rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) {
static void nfc_app_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
furi_assert(context);
NfcApp* nfc = (NfcApp*)context;
furi_assert(nfc->rpc_ctx);
if(rpc_event == RpcAppEventSessionClose) {
if(event->type == RpcAppEventTypeSessionClose) {
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcSessionClose);
rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL);
nfc->rpc_ctx = NULL;
} else if(rpc_event == RpcAppEventAppExit) {
} else if(event->type == RpcAppEventTypeAppExit) {
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcExit);
} else if(rpc_event == RpcAppEventLoadFile) {
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad);
} else if(event->type == RpcAppEventTypeLoadFile) {
furi_assert(event->data.type == RpcAppSystemEventDataTypeString);
furi_string_set(nfc->file_path, event->data.string);
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoadFile);
} else {
rpc_system_app_confirm(nfc->rpc_ctx, rpc_event, false);
rpc_system_app_confirm(nfc->rpc_ctx, false);
}
}

View File

@ -33,13 +33,13 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
if(event.event == SubGhzCustomEventSceneExit) {
scene_manager_stop(subghz->scene_manager);
view_dispatcher_stop(subghz->view_dispatcher);
rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventAppExit, true);
rpc_system_app_confirm(subghz->rpc_ctx, true);
} else if(event.event == SubGhzCustomEventSceneRpcSessionClose) {
scene_manager_stop(subghz->scene_manager);
view_dispatcher_stop(subghz->view_dispatcher);
} else if(event.event == SubGhzCustomEventSceneRpcButtonPress) {
bool result = false;
if((state == SubGhzRpcStateLoaded)) {
if(state == SubGhzRpcStateLoaded) {
switch(
subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) {
case SubGhzTxRxStartTxStateErrorOnlyRx:
@ -61,7 +61,7 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
break;
}
}
rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result);
rpc_system_app_confirm(subghz->rpc_ctx, result);
} else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) {
bool result = false;
if(state == SubGhzRpcStateTx) {
@ -70,15 +70,13 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
result = true;
}
state = SubGhzRpcStateIdle;
rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonRelease, result);
rpc_system_app_confirm(subghz->rpc_ctx, result);
} else if(event.event == SubGhzCustomEventSceneRpcLoad) {
bool result = false;
const char* arg = rpc_system_app_get_data(subghz->rpc_ctx);
if(arg && (state == SubGhzRpcStateIdle)) {
if(subghz_key_load(subghz, arg, false)) {
if(state == SubGhzRpcStateIdle) {
if(subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false)) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateLoaded);
furi_string_set(subghz->file_path, arg);
result = true;
FuriString* file_name;
file_name = furi_string_alloc();
@ -97,7 +95,7 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
rpc_system_app_set_error_text(subghz->rpc_ctx, "Cannot parse file");
}
}
rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventLoadFile, result);
rpc_system_app_confirm(subghz->rpc_ctx, result);
}
}
return consumed;

View File

@ -20,29 +20,31 @@ void subghz_tick_event_callback(void* context) {
scene_manager_handle_tick_event(subghz->scene_manager);
}
static void subghz_rpc_command_callback(RpcAppSystemEvent event, void* context) {
static void subghz_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
furi_assert(context);
SubGhz* subghz = context;
furi_assert(subghz->rpc_ctx);
if(event == RpcAppEventSessionClose) {
if(event->type == RpcAppEventTypeSessionClose) {
view_dispatcher_send_custom_event(
subghz->view_dispatcher, SubGhzCustomEventSceneRpcSessionClose);
rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL);
subghz->rpc_ctx = NULL;
} else if(event == RpcAppEventAppExit) {
} else if(event->type == RpcAppEventTypeAppExit) {
view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit);
} else if(event == RpcAppEventLoadFile) {
} else if(event->type == RpcAppEventTypeLoadFile) {
furi_assert(event->data.type == RpcAppSystemEventDataTypeString);
furi_string_set(subghz->file_path, event->data.string);
view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneRpcLoad);
} else if(event == RpcAppEventButtonPress) {
} else if(event->type == RpcAppEventTypeButtonPress) {
view_dispatcher_send_custom_event(
subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonPress);
} else if(event == RpcAppEventButtonRelease) {
} else if(event->type == RpcAppEventTypeButtonRelease) {
view_dispatcher_send_custom_event(
subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonRelease);
} else {
rpc_system_app_confirm(subghz->rpc_ctx, event, false);
rpc_system_app_confirm(subghz->rpc_ctx, false);
}
}

View File

@ -477,12 +477,15 @@ void rpc_send_and_release(RpcSession* session, PB_Main* message) {
}
void rpc_send_and_release_empty(RpcSession* session, uint32_t command_id, PB_CommandStatus status) {
furi_assert(session);
PB_Main message = {
.command_id = command_id,
.command_status = status,
.has_next = false,
.which_content = PB_Main_empty_tag,
};
rpc_send_and_release(session, &message);
pb_release(&PB_Main_msg, &message);
}

View File

@ -10,49 +10,85 @@
struct RpcAppSystem {
RpcSession* session;
RpcAppSystemCallback app_callback;
void* app_context;
RpcAppSystemCallback callback;
void* callback_context;
RpcAppSystemDataExchangeCallback data_exchange_callback;
void* data_exchange_context;
uint32_t error_code;
char* error_text;
PB_Main* state_msg;
PB_Main* error_msg;
uint32_t last_id;
char* last_data;
uint32_t last_command_id;
RpcAppSystemEventType last_event_type;
};
#define RPC_SYSTEM_APP_TEMP_ARGS_SIZE 16
static void rpc_system_app_send_state_response(
RpcAppSystem* rpc_app,
PB_App_AppState state,
const char* name) {
PB_Main* response = malloc(sizeof(PB_Main));
response->which_content = PB_Main_app_state_response_tag;
response->content.app_state_response.state = state;
FURI_LOG_D(TAG, "%s", name);
rpc_send(rpc_app->session, response);
free(response);
}
static void rpc_system_app_send_error_response(
RpcAppSystem* rpc_app,
uint32_t command_id,
PB_CommandStatus status,
const char* name) {
// Not describing all possible errors as only APP_NOT_RUNNING is used
const char* status_str = status == PB_CommandStatus_ERROR_APP_NOT_RUNNING ? "APP_NOT_RUNNING" :
"UNKNOWN";
FURI_LOG_E(TAG, "%s: %s, id %lu, status: %d", name, status_str, command_id, status);
rpc_send_and_release_empty(rpc_app->session, command_id, status);
}
static void rpc_system_app_set_last_command(
RpcAppSystem* rpc_app,
uint32_t command_id,
const RpcAppSystemEvent* event) {
furi_assert(rpc_app->last_command_id == 0);
furi_assert(rpc_app->last_event_type == RpcAppEventTypeInvalid);
rpc_app->last_command_id = command_id;
rpc_app->last_event_type = event->type;
}
static void rpc_system_app_start_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_start_request_tag);
RpcAppSystem* rpc_app = context;
RpcSession* session = rpc_app->session;
rpc_system_app_error_reset(rpc_app);
furi_assert(session);
char args_temp[RPC_SYSTEM_APP_TEMP_ARGS_SIZE];
furi_assert(!rpc_app->last_id);
furi_assert(!rpc_app->last_data);
RpcAppSystem* rpc_app = context;
furi_assert(rpc_app);
furi_assert(rpc_app->last_command_id == 0);
furi_assert(rpc_app->last_event_type == RpcAppEventTypeInvalid);
FURI_LOG_D(TAG, "StartProcess: id %lu", request->command_id);
PB_CommandStatus result;
Loader* loader = furi_record_open(RECORD_LOADER);
const char* app_name = request->content.app_start_request.name;
PB_CommandStatus result;
if(app_name) {
rpc_system_app_error_reset(rpc_app);
char app_args_temp[RPC_SYSTEM_APP_TEMP_ARGS_SIZE];
const char* app_args = request->content.app_start_request.args;
if(app_args && strcmp(app_args, "RPC") == 0) {
// If app is being started in RPC mode - pass RPC context via args string
snprintf(args_temp, RPC_SYSTEM_APP_TEMP_ARGS_SIZE, "RPC %08lX", (uint32_t)rpc_app);
app_args = args_temp;
snprintf(app_args_temp, RPC_SYSTEM_APP_TEMP_ARGS_SIZE, "RPC %08lX", (uint32_t)rpc_app);
app_args = app_args_temp;
}
LoaderStatus status = loader_start(loader, app_name, app_args, NULL);
const LoaderStatus status = loader_start(loader, app_name, app_args, NULL);
if(status == LoaderStatusErrorAppStarted) {
result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED;
} else if(status == LoaderStatusErrorInternal) {
@ -71,266 +107,271 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context)
furi_record_close(RECORD_LOADER);
FURI_LOG_D(TAG, "StartProcess: response id %lu, result %d", request->command_id, result);
rpc_send_and_release_empty(session, request->command_id, result);
rpc_send_and_release_empty(rpc_app->session, request->command_id, result);
}
static void rpc_system_app_lock_status_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_lock_status_request_tag);
RpcAppSystem* rpc_app = context;
furi_assert(rpc_app);
rpc_system_app_error_reset(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
FURI_LOG_D(TAG, "LockStatus");
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->which_content = PB_Main_app_lock_status_response_tag;
Loader* loader = furi_record_open(RECORD_LOADER);
PB_Main response = {
.has_next = false,
.command_status = PB_CommandStatus_OK,
.command_id = request->command_id,
.which_content = PB_Main_app_lock_status_response_tag,
};
response.content.app_lock_status_response.locked = loader_is_locked(loader);
response->content.app_lock_status_response.locked = loader_is_locked(loader);
furi_record_close(RECORD_LOADER);
FURI_LOG_D(TAG, "LockStatus: response");
rpc_send_and_release(session, &response);
pb_release(&PB_Main_msg, &response);
rpc_send_and_release(rpc_app->session, response);
free(response);
}
static void rpc_system_app_exit_request(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_exit_request_tag);
RpcAppSystem* rpc_app = context;
rpc_system_app_error_reset(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
furi_assert(rpc_app);
PB_CommandStatus status;
if(rpc_app->app_callback) {
if(rpc_app->callback) {
FURI_LOG_D(TAG, "ExitRequest: id %lu", request->command_id);
furi_assert(!rpc_app->last_id);
furi_assert(!rpc_app->last_data);
rpc_app->last_id = request->command_id;
rpc_app->app_callback(RpcAppEventAppExit, rpc_app->app_context);
const RpcAppSystemEvent event = {
.type = RpcAppEventTypeAppExit,
.data =
{
.type = RpcAppSystemEventDataTypeNone,
{0},
},
};
rpc_system_app_error_reset(rpc_app);
rpc_system_app_set_last_command(rpc_app, request->command_id, &event);
rpc_app->callback(&event, rpc_app->callback_context);
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
FURI_LOG_E(
TAG, "ExitRequest: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status);
rpc_send_and_release_empty(session, request->command_id, status);
rpc_system_app_send_error_response(
rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "ExitRequest");
}
}
static void rpc_system_app_load_file(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_load_file_request_tag);
RpcAppSystem* rpc_app = context;
rpc_system_app_error_reset(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
PB_CommandStatus status;
if(rpc_app->app_callback) {
RpcAppSystem* rpc_app = context;
furi_assert(rpc_app);
if(rpc_app->callback) {
FURI_LOG_D(TAG, "LoadFile: id %lu", request->command_id);
furi_assert(!rpc_app->last_id);
furi_assert(!rpc_app->last_data);
rpc_app->last_id = request->command_id;
rpc_app->last_data = strdup(request->content.app_load_file_request.path);
rpc_app->app_callback(RpcAppEventLoadFile, rpc_app->app_context);
const RpcAppSystemEvent event = {
.type = RpcAppEventTypeLoadFile,
.data =
{
.type = RpcAppSystemEventDataTypeString,
.string = request->content.app_load_file_request.path,
},
};
rpc_system_app_error_reset(rpc_app);
rpc_system_app_set_last_command(rpc_app, request->command_id, &event);
rpc_app->callback(&event, rpc_app->callback_context);
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
FURI_LOG_E(
TAG, "LoadFile: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status);
rpc_send_and_release_empty(session, request->command_id, status);
rpc_system_app_send_error_response(
rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "LoadFile");
}
}
static void rpc_system_app_button_press(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_button_press_request_tag);
RpcAppSystem* rpc_app = context;
rpc_system_app_error_reset(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
PB_CommandStatus status;
if(rpc_app->app_callback) {
RpcAppSystem* rpc_app = context;
furi_assert(rpc_app);
if(rpc_app->callback) {
FURI_LOG_D(TAG, "ButtonPress");
furi_assert(!rpc_app->last_id);
furi_assert(!rpc_app->last_data);
rpc_app->last_id = request->command_id;
rpc_app->last_data = strdup(request->content.app_button_press_request.args);
rpc_app->app_callback(RpcAppEventButtonPress, rpc_app->app_context);
RpcAppSystemEvent event;
event.type = RpcAppEventTypeButtonPress;
if(strlen(request->content.app_button_press_request.args) != 0) {
event.data.type = RpcAppSystemEventDataTypeString;
event.data.string = request->content.app_button_press_request.args;
} else {
event.data.type = RpcAppSystemEventDataTypeInt32;
event.data.i32 = request->content.app_button_press_request.index;
}
rpc_system_app_error_reset(rpc_app);
rpc_system_app_set_last_command(rpc_app, request->command_id, &event);
rpc_app->callback(&event, rpc_app->callback_context);
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
FURI_LOG_E(
TAG, "ButtonPress: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status);
rpc_send_and_release_empty(session, request->command_id, status);
rpc_system_app_send_error_response(
rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "ButtonPress");
}
}
static void rpc_system_app_button_release(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_app_button_release_request_tag);
furi_assert(context);
RpcAppSystem* rpc_app = context;
rpc_system_app_error_reset(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
furi_assert(rpc_app);
PB_CommandStatus status;
if(rpc_app->app_callback) {
if(rpc_app->callback) {
FURI_LOG_D(TAG, "ButtonRelease");
furi_assert(!rpc_app->last_id);
furi_assert(!rpc_app->last_data);
rpc_app->last_id = request->command_id;
rpc_app->app_callback(RpcAppEventButtonRelease, rpc_app->app_context);
const RpcAppSystemEvent event = {
.type = RpcAppEventTypeButtonRelease,
.data =
{
.type = RpcAppSystemEventDataTypeNone,
{0},
},
};
rpc_system_app_error_reset(rpc_app);
rpc_system_app_set_last_command(rpc_app, request->command_id, &event);
rpc_app->callback(&event, rpc_app->callback_context);
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
FURI_LOG_E(
TAG, "ButtonRelease: APP_NOT_RUNNING, id %lu, status: %d", request->command_id, status);
rpc_send_and_release_empty(session, request->command_id, status);
rpc_system_app_send_error_response(
rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "ButtonRelease");
}
}
static void rpc_system_app_get_error_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_app_get_error_request_tag);
furi_assert(context);
RpcAppSystem* rpc_app = context;
RpcSession* session = rpc_app->session;
furi_assert(session);
furi_assert(rpc_app);
rpc_app->error_msg->command_id = request->command_id;
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->which_content = PB_Main_app_get_error_response_tag;
response->content.app_get_error_response.code = rpc_app->error_code;
response->content.app_get_error_response.text = rpc_app->error_text;
FURI_LOG_D(TAG, "GetError");
rpc_send(session, rpc_app->error_msg);
rpc_send(rpc_app->session, response);
free(response);
}
static void rpc_system_app_data_exchange_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_app_data_exchange_request_tag);
furi_assert(context);
RpcAppSystem* rpc_app = context;
rpc_system_app_error_reset(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
furi_assert(rpc_app);
PB_CommandStatus command_status;
pb_bytes_array_t* data = request->content.app_data_exchange_request.data;
if(rpc_app->callback) {
FURI_LOG_D(TAG, "DataExchange");
if(rpc_app->data_exchange_callback) {
uint8_t* data_bytes = NULL;
size_t data_size = 0;
if(data) {
data_bytes = data->bytes;
data_size = data->size;
}
rpc_app->data_exchange_callback(data_bytes, data_size, rpc_app->data_exchange_context);
command_status = PB_CommandStatus_OK;
const pb_bytes_array_t* data = request->content.app_data_exchange_request.data;
const RpcAppSystemEvent event = {
.type = RpcAppEventTypeDataExchange,
.data =
{
.type = RpcAppSystemEventDataTypeBytes,
.bytes =
{
.ptr = data ? data->bytes : NULL,
.size = data ? data->size : 0,
},
},
};
rpc_system_app_error_reset(rpc_app);
rpc_system_app_set_last_command(rpc_app, request->command_id, &event);
rpc_app->callback(&event, rpc_app->callback_context);
} else {
command_status = PB_CommandStatus_ERROR_APP_CMD_ERROR;
rpc_system_app_send_error_response(
rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "DataExchange");
}
FURI_LOG_D(TAG, "DataExchange");
rpc_send_and_release_empty(session, request->command_id, command_status);
}
void rpc_system_app_send_started(RpcAppSystem* rpc_app) {
furi_assert(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
rpc_app->state_msg->content.app_state_response.state = PB_App_AppState_APP_STARTED;
FURI_LOG_D(TAG, "SendStarted");
rpc_send(session, rpc_app->state_msg);
rpc_system_app_send_state_response(rpc_app, PB_App_AppState_APP_STARTED, "SendStarted");
}
void rpc_system_app_send_exited(RpcAppSystem* rpc_app) {
furi_assert(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
rpc_app->state_msg->content.app_state_response.state = PB_App_AppState_APP_CLOSED;
FURI_LOG_D(TAG, "SendExit");
rpc_send(session, rpc_app->state_msg);
rpc_system_app_send_state_response(rpc_app, PB_App_AppState_APP_CLOSED, "SendExit");
}
const char* rpc_system_app_get_data(RpcAppSystem* rpc_app) {
void rpc_system_app_confirm(RpcAppSystem* rpc_app, bool result) {
furi_assert(rpc_app);
furi_assert(rpc_app->last_data);
return rpc_app->last_data;
}
furi_assert(rpc_app->last_command_id != 0);
/* Ensure that only commands of these types can be confirmed */
furi_assert(
rpc_app->last_event_type == RpcAppEventTypeAppExit ||
rpc_app->last_event_type == RpcAppEventTypeLoadFile ||
rpc_app->last_event_type == RpcAppEventTypeButtonPress ||
rpc_app->last_event_type == RpcAppEventTypeButtonRelease ||
rpc_app->last_event_type == RpcAppEventTypeDataExchange);
void rpc_system_app_confirm(RpcAppSystem* rpc_app, RpcAppSystemEvent event, bool result) {
furi_assert(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
furi_assert(rpc_app->last_id);
const uint32_t last_command_id = rpc_app->last_command_id;
const RpcAppSystemEventType last_event_type = rpc_app->last_event_type;
PB_CommandStatus status = result ? PB_CommandStatus_OK : PB_CommandStatus_ERROR_APP_CMD_ERROR;
rpc_app->last_command_id = 0;
rpc_app->last_event_type = RpcAppEventTypeInvalid;
uint32_t last_id = 0;
switch(event) {
case RpcAppEventAppExit:
case RpcAppEventLoadFile:
case RpcAppEventButtonPress:
case RpcAppEventButtonRelease:
last_id = rpc_app->last_id;
rpc_app->last_id = 0;
if(rpc_app->last_data) {
free(rpc_app->last_data);
rpc_app->last_data = NULL;
}
FURI_LOG_D(TAG, "AppConfirm: event %d last_id %lu status %d", event, last_id, status);
rpc_send_and_release_empty(session, last_id, status);
break;
default:
furi_crash("RPC App state programming Error");
break;
}
const PB_CommandStatus status = result ? PB_CommandStatus_OK :
PB_CommandStatus_ERROR_APP_CMD_ERROR;
FURI_LOG_D(
TAG,
"AppConfirm: event %d last_id %lu status %d",
last_event_type,
last_command_id,
status);
rpc_send_and_release_empty(rpc_app->session, last_command_id, status);
}
void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx) {
furi_assert(rpc_app);
rpc_app->app_callback = callback;
rpc_app->app_context = ctx;
rpc_app->callback = callback;
rpc_app->callback_context = ctx;
}
void rpc_system_app_set_error_code(RpcAppSystem* rpc_app, uint32_t error_code) {
furi_assert(rpc_app);
PB_App_GetErrorResponse* content = &rpc_app->error_msg->content.app_get_error_response;
content->code = error_code;
rpc_app->error_code = error_code;
}
void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text) {
furi_assert(rpc_app);
PB_App_GetErrorResponse* content = &rpc_app->error_msg->content.app_get_error_response;
if(content->text) {
free(content->text);
if(rpc_app->error_text) {
free(rpc_app->error_text);
}
content->text = error_text ? strdup(error_text) : NULL;
rpc_app->error_text = error_text ? strdup(error_text) : NULL;
}
void rpc_system_app_error_reset(RpcAppSystem* rpc_app) {
@ -340,29 +381,13 @@ void rpc_system_app_error_reset(RpcAppSystem* rpc_app) {
rpc_system_app_set_error_text(rpc_app, NULL);
}
void rpc_system_app_set_data_exchange_callback(
RpcAppSystem* rpc_app,
RpcAppSystemDataExchangeCallback callback,
void* ctx) {
furi_assert(rpc_app);
rpc_app->data_exchange_callback = callback;
rpc_app->data_exchange_context = ctx;
}
void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, size_t data_size) {
furi_assert(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
PB_Main message = {
.command_id = 0,
.command_status = PB_CommandStatus_OK,
.has_next = false,
.which_content = PB_Main_app_data_exchange_request_tag,
};
PB_Main* request = malloc(sizeof(PB_Main));
PB_App_DataExchangeRequest* content = &message.content.app_data_exchange_request;
request->which_content = PB_Main_app_data_exchange_request_tag;
PB_App_DataExchangeRequest* content = &request->content.app_data_exchange_request;
if(data && data_size) {
content->data = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(data_size));
@ -372,7 +397,9 @@ void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, si
content->data = NULL;
}
rpc_send_and_release(session, &message);
rpc_send_and_release(rpc_app->session, request);
free(request);
}
void* rpc_system_app_alloc(RpcSession* session) {
@ -381,18 +408,6 @@ void* rpc_system_app_alloc(RpcSession* session) {
RpcAppSystem* rpc_app = malloc(sizeof(RpcAppSystem));
rpc_app->session = session;
// App exit message
rpc_app->state_msg = malloc(sizeof(PB_Main));
rpc_app->state_msg->which_content = PB_Main_app_state_response_tag;
rpc_app->state_msg->command_status = PB_CommandStatus_OK;
// App error message
rpc_app->error_msg = malloc(sizeof(PB_Main));
rpc_app->error_msg->which_content = PB_Main_app_get_error_response_tag;
rpc_app->error_msg->command_status = PB_CommandStatus_OK;
rpc_app->error_msg->content.app_get_error_response.code = 0;
rpc_app->error_msg->content.app_get_error_response.text = NULL;
RpcHandler rpc_handler = {
.message_handler = NULL,
.decode_submessage = NULL,
@ -429,24 +444,24 @@ void* rpc_system_app_alloc(RpcSession* session) {
void rpc_system_app_free(void* context) {
RpcAppSystem* rpc_app = context;
furi_assert(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
furi_assert(rpc_app->session);
if(rpc_app->app_callback) {
rpc_app->app_callback(RpcAppEventSessionClose, rpc_app->app_context);
if(rpc_app->callback) {
const RpcAppSystemEvent event = {
.type = RpcAppEventTypeSessionClose,
.data =
{
.type = RpcAppSystemEventDataTypeNone,
{0},
},
};
rpc_app->callback(&event, rpc_app->callback_context);
}
while(rpc_app->app_callback) {
while(rpc_app->callback) {
furi_delay_tick(1);
}
furi_assert(!rpc_app->data_exchange_callback);
if(rpc_app->last_data) free(rpc_app->last_data);
pb_release(&PB_Main_msg, rpc_app->error_msg);
free(rpc_app->error_msg);
free(rpc_app->state_msg);
free(rpc_app);
}

View File

@ -1,45 +1,213 @@
/**
* @file rpc_app.h
* @brief Application RPC subsystem interface.
*
* The application RPC subsystem provides facilities for interacting with applications,
* such as starting/stopping, passing parameters, sending commands and exchanging arbitrary data.
*
* All commands are handled asynchronously via a user-settable callback.
*
* For a complete description of message types handled in this subsystem,
* see https://github.com/flipperdevices/flipperzero-protobuf/blob/dev/application.proto
*/
#pragma once
#include "rpc.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Enumeration of possible event data types.
*/
typedef enum {
RpcAppEventSessionClose,
RpcAppEventAppExit,
RpcAppEventLoadFile,
RpcAppEventButtonPress,
RpcAppEventButtonRelease,
RpcAppSystemEventDataTypeNone, /**< No data is provided by the event. */
RpcAppSystemEventDataTypeString, /**< Event data contains a zero-terminated string. */
RpcAppSystemEventDataTypeInt32, /**< Event data contains a signed 32-bit integer. */
RpcAppSystemEventDataTypeBytes, /**< Event data contains zero or more bytes. */
} RpcAppSystemEventDataType;
/**
* @brief Event data structure, containing the type and associated data.
*
* All below fields except for type are valid only if the respective type is set.
*/
typedef struct {
RpcAppSystemEventDataType
type; /**< Type of the data. The meaning of other fields depends on this one. */
union {
const char* string; /**< Pointer to a zero-terminated character string. */
int32_t i32; /**< Signed 32-bit integer value. */
struct {
const uint8_t* ptr; /**< Pointer to the byte array data. */
size_t size; /**< Size of the byte array, in bytes. */
} bytes; /**< Byte array of arbitrary length. */
};
} RpcAppSystemEventData;
/**
* @brief Enumeration of possible event types.
*/
typedef enum {
/**
* @brief Denotes an invalid state.
*
* An event of this type shall never be passed into the callback.
*/
RpcAppEventTypeInvalid,
/**
* @brief The client side has closed the session.
*
* After receiving this event, the RPC context is no more valid.
*/
RpcAppEventTypeSessionClose,
/**
* @brief The client has requested the application to exit.
*
* The application must exit after receiving this command.
*/
RpcAppEventTypeAppExit,
/**
* @brief The client has requested the application to load a file.
*
* This command's meaning is application-specific, i.e. the application might or
* might not require additional commands after loading a file to do anything useful.
*/
RpcAppEventTypeLoadFile,
/**
* @brief The client has informed the application that a button has been pressed.
*
* This command's meaning is application-specific, e.g. to select a part of the
* previously loaded file or to invoke a particular function within the application.
*/
RpcAppEventTypeButtonPress,
/**
* @brief The client has informed the application that a button has been released.
*
* This command's meaning is application-specific, e.g. to cease
* all activities to be conducted while a button is being pressed.
*/
RpcAppEventTypeButtonRelease,
/**
* @brief The client has sent a byte array of arbitrary size.
*
* This command's purpose is bi-directional exchange of arbitrary raw data.
* Useful for implementing higher-level protocols while using the RPC as a transport layer.
*/
RpcAppEventTypeDataExchange,
} RpcAppSystemEventType;
/**
* @brief RPC application subsystem event structure.
*/
typedef struct {
RpcAppSystemEventType type; /**< Type of the event. */
RpcAppSystemEventData data; /**< Data associated with the event. */
} RpcAppSystemEvent;
typedef void (*RpcAppSystemCallback)(RpcAppSystemEvent event, void* context);
typedef void (
*RpcAppSystemDataExchangeCallback)(const uint8_t* data, size_t data_size, void* context);
/**
* @brief Callback function type.
*
* A function of this type must be passed to rpc_system_app_set_callback() by the user code.
*
* @warning The event pointer is valid ONLY inside the callback function.
*
* @param[in] event pointer to the event object. Valid only inside the callback function.
* @param[in,out] context pointer to the user-defined context object.
*/
typedef void (*RpcAppSystemCallback)(const RpcAppSystemEvent* event, void* context);
/**
* @brief RPC application subsystem opaque type declaration.
*/
typedef struct RpcAppSystem RpcAppSystem;
void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx);
/**
* @brief Set the callback function for use by an RpcAppSystem instance.
*
* @param[in,out] rpc_app pointer to the instance to be configured.
* @param[in] callback pointer to the function to be called upon message reception.
* @param[in,out] context pointer to the user-defined context object. Will be passed to the callback.
*/
void rpc_system_app_set_callback(
RpcAppSystem* rpc_app,
RpcAppSystemCallback callback,
void* context);
/**
* @brief Send a notification that an RpcAppSystem instance has been started and is ready.
*
* Call this function once right after acquiring an RPC context and setting the callback.
*
* @param[in,out] rpc_app pointer to the instance to be used.
*/
void rpc_system_app_send_started(RpcAppSystem* rpc_app);
/**
* @brief Send a notification that the application using an RpcAppSystem instance is about to exit.
*
* Call this function when the application is about to exit (usually in the *_free() function).
*
* @param[in,out] rpc_app pointer to the instance to be used.
*/
void rpc_system_app_send_exited(RpcAppSystem* rpc_app);
const char* rpc_system_app_get_data(RpcAppSystem* rpc_app);
void rpc_system_app_confirm(RpcAppSystem* rpc_app, RpcAppSystemEvent event, bool result);
/**
* @brief Send a confirmation that the application using an RpcAppSystem instance has handled the event.
*
* An explicit confirmation is required for the following event types:
* - RpcAppEventTypeAppExit
* - RpcAppEventTypeLoadFile
* - RpcAppEventTypeButtonPress
* - RpcAppEventTypeButtonRelease
* - RpcAppEventTypeDataExchange
*
* Not confirming these events will result in a client-side timeout.
*
* @param[in,out] rpc_app pointer to the instance to be used.
* @param[in] result whether the command was successfully handled or not (true for success).
*/
void rpc_system_app_confirm(RpcAppSystem* rpc_app, bool result);
/**
* @brief Set the error code stored in an RpcAppSystem instance.
*
* The error code can be retrieved by the client at any time by using the GetError request.
* The error code value has no meaning within the subsystem, i.e. it is only passed through to the client.
*
* @param[in,out] rpc_app pointer to the instance to be modified.
* @param[in] error_code arbitrary error code to be set.
*/
void rpc_system_app_set_error_code(RpcAppSystem* rpc_app, uint32_t error_code);
/**
* @brief Set the error text stored in an RpcAppSystem instance.
*
* The error text can be retrieved by the client at any time by using the GetError request.
* The text has no meaning within the subsystem, i.e. it is only passed through to the client.
*
* @param[in,out] rpc_app pointer to the instance to be modified.
* @param[in] error_text Pointer to a zero-terminated string containing the error text.
*/
void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text);
/**
* @brief Reset the error code and text stored in an RpcAppSystem instance.
*
* Resets the error code to 0 and error text to "" (empty string).
*
* @param[in,out] rpc_app pointer to the instance to be reset.
*/
void rpc_system_app_error_reset(RpcAppSystem* rpc_app);
void rpc_system_app_set_data_exchange_callback(
RpcAppSystem* rpc_app,
RpcAppSystemDataExchangeCallback callback,
void* ctx);
/**
* @brief Send a byte array of arbitrary data to the client using an RpcAppSystem instance.
*
* @param[in,out] rpc_app pointer to the instance to be used.
* @param[in] data pointer to the data buffer to be sent.
* @param[in] data_size size of the data buffer, in bytes.
*/
void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, size_t data_size);
#ifdef __cplusplus

@ -1 +1 @@
Subproject commit 327163d5867c7aa3051334c93ced718d15bfe4da
Subproject commit 23ad19a756649ed9f6677b598e5361c5cce6847b

View File

@ -80,22 +80,35 @@ def __invoke_git(args, source_dir):
def _proto_ver_generator(target, source, env):
target_file = target[0]
src_dir = source[0].dir.abspath
try:
__invoke_git(
["fetch", "--tags"],
source_dir=src_dir,
)
except (subprocess.CalledProcessError, EnvironmentError):
# Not great, not terrible
print(fg.boldred("Git: fetch failed"))
try:
git_describe = __invoke_git(
["describe", "--tags", "--abbrev=0"],
source_dir=src_dir,
)
except (subprocess.CalledProcessError, EnvironmentError):
raise StopError("Git: describe failed")
def fetch(unshallow=False):
git_args = ["fetch", "--tags"]
if unshallow:
git_args.append("--unshallow")
try:
__invoke_git(git_args, source_dir=src_dir)
except (subprocess.CalledProcessError, EnvironmentError):
# Not great, not terrible
print(fg.boldred("Git: fetch failed"))
def describe():
try:
return __invoke_git(
["describe", "--tags", "--abbrev=0"],
source_dir=src_dir,
)
except (subprocess.CalledProcessError, EnvironmentError):
return None
fetch()
git_describe = describe()
if not git_describe:
fetch(unshallow=True)
git_describe = describe()
if not git_describe:
raise StopError("Failed to process git tags for protobuf versioning")
git_major, git_minor = git_describe.split(".")
version_file_data = (

View File

@ -1950,14 +1950,12 @@ Function,+,rpc_session_set_close_callback,void,"RpcSession*, RpcSessionClosedCal
Function,+,rpc_session_set_context,void,"RpcSession*, void*"
Function,+,rpc_session_set_send_bytes_callback,void,"RpcSession*, RpcSendBytesCallback"
Function,+,rpc_session_set_terminated_callback,void,"RpcSession*, RpcSessionTerminatedCallback"
Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, RpcAppSystemEvent, _Bool"
Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, _Bool"
Function,+,rpc_system_app_error_reset,void,RpcAppSystem*
Function,+,rpc_system_app_exchange_data,void,"RpcAppSystem*, const uint8_t*, size_t"
Function,+,rpc_system_app_get_data,const char*,RpcAppSystem*
Function,+,rpc_system_app_send_exited,void,RpcAppSystem*
Function,+,rpc_system_app_send_started,void,RpcAppSystem*
Function,+,rpc_system_app_set_callback,void,"RpcAppSystem*, RpcAppSystemCallback, void*"
Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcAppSystemDataExchangeCallback, void*"
Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t"
Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*"
Function,-,rpmatch,int,const char*

1 entry status name type params
1950 Function + rpc_session_set_context void RpcSession*, void*
1951 Function + rpc_session_set_send_bytes_callback void RpcSession*, RpcSendBytesCallback
1952 Function + rpc_session_set_terminated_callback void RpcSession*, RpcSessionTerminatedCallback
1953 Function + rpc_system_app_confirm void RpcAppSystem*, RpcAppSystemEvent, _Bool RpcAppSystem*, _Bool
1954 Function + rpc_system_app_error_reset void RpcAppSystem*
1955 Function + rpc_system_app_exchange_data void RpcAppSystem*, const uint8_t*, size_t
Function + rpc_system_app_get_data const char* RpcAppSystem*
1956 Function + rpc_system_app_send_exited void RpcAppSystem*
1957 Function + rpc_system_app_send_started void RpcAppSystem*
1958 Function + rpc_system_app_set_callback void RpcAppSystem*, RpcAppSystemCallback, void*
Function + rpc_system_app_set_data_exchange_callback void RpcAppSystem*, RpcAppSystemDataExchangeCallback, void*
1959 Function + rpc_system_app_set_error_code void RpcAppSystem*, uint32_t
1960 Function + rpc_system_app_set_error_text void RpcAppSystem*, const char*
1961 Function - rpmatch int const char*

View File

@ -2493,14 +2493,12 @@ Function,+,rpc_session_set_close_callback,void,"RpcSession*, RpcSessionClosedCal
Function,+,rpc_session_set_context,void,"RpcSession*, void*"
Function,+,rpc_session_set_send_bytes_callback,void,"RpcSession*, RpcSendBytesCallback"
Function,+,rpc_session_set_terminated_callback,void,"RpcSession*, RpcSessionTerminatedCallback"
Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, RpcAppSystemEvent, _Bool"
Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, _Bool"
Function,+,rpc_system_app_error_reset,void,RpcAppSystem*
Function,+,rpc_system_app_exchange_data,void,"RpcAppSystem*, const uint8_t*, size_t"
Function,+,rpc_system_app_get_data,const char*,RpcAppSystem*
Function,+,rpc_system_app_send_exited,void,RpcAppSystem*
Function,+,rpc_system_app_send_started,void,RpcAppSystem*
Function,+,rpc_system_app_set_callback,void,"RpcAppSystem*, RpcAppSystemCallback, void*"
Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcAppSystemDataExchangeCallback, void*"
Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t"
Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*"
Function,-,rpmatch,int,const char*

1 entry status name type params
2493 Function + rpc_session_set_context void RpcSession*, void*
2494 Function + rpc_session_set_send_bytes_callback void RpcSession*, RpcSendBytesCallback
2495 Function + rpc_session_set_terminated_callback void RpcSession*, RpcSessionTerminatedCallback
2496 Function + rpc_system_app_confirm void RpcAppSystem*, RpcAppSystemEvent, _Bool RpcAppSystem*, _Bool
2497 Function + rpc_system_app_error_reset void RpcAppSystem*
2498 Function + rpc_system_app_exchange_data void RpcAppSystem*, const uint8_t*, size_t
Function + rpc_system_app_get_data const char* RpcAppSystem*
2499 Function + rpc_system_app_send_exited void RpcAppSystem*
2500 Function + rpc_system_app_send_started void RpcAppSystem*
2501 Function + rpc_system_app_set_callback void RpcAppSystem*, RpcAppSystemCallback, void*
Function + rpc_system_app_set_data_exchange_callback void RpcAppSystem*, RpcAppSystemDataExchangeCallback, void*
2502 Function + rpc_system_app_set_error_code void RpcAppSystem*, uint32_t
2503 Function + rpc_system_app_set_error_text void RpcAppSystem*, const char*
2504 Function - rpmatch int const char*