 49dcf81743
			
		
	
	
		49dcf81743
		
			
		
	
	
	
	
		
			
			* 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>
		
			
				
	
	
		
			468 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			468 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "flipper.pb.h"
 | |
| #include <core/record.h>
 | |
| #include "rpc_i.h"
 | |
| #include <furi.h>
 | |
| #include <loader/loader.h>
 | |
| #include "rpc_app.h"
 | |
| 
 | |
| #define TAG "RpcSystemApp"
 | |
| 
 | |
| struct RpcAppSystem {
 | |
|     RpcSession* session;
 | |
| 
 | |
|     RpcAppSystemCallback callback;
 | |
|     void* callback_context;
 | |
| 
 | |
|     uint32_t error_code;
 | |
|     char* error_text;
 | |
| 
 | |
|     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(request->which_content == PB_Main_app_start_request_tag);
 | |
| 
 | |
|     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);
 | |
| 
 | |
|     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(app_args_temp, RPC_SYSTEM_APP_TEMP_ARGS_SIZE, "RPC %08lX", (uint32_t)rpc_app);
 | |
|             app_args = app_args_temp;
 | |
|         }
 | |
| 
 | |
|         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) {
 | |
|             result = PB_CommandStatus_ERROR_APP_CANT_START;
 | |
|         } else if(status == LoaderStatusErrorUnknownApp) {
 | |
|             result = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
 | |
|         } else if(status == LoaderStatusOk) {
 | |
|             result = PB_CommandStatus_OK;
 | |
|         } else {
 | |
|             furi_crash();
 | |
|         }
 | |
|     } else {
 | |
|         result = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
 | |
|     }
 | |
| 
 | |
|     furi_record_close(RECORD_LOADER);
 | |
| 
 | |
|     FURI_LOG_D(TAG, "StartProcess: response id %lu, result %d", 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(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);
 | |
| 
 | |
|     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);
 | |
|     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(rpc_app->session, response);
 | |
| 
 | |
|     free(response);
 | |
| }
 | |
| 
 | |
| static void rpc_system_app_exit_request(const PB_Main* request, void* context) {
 | |
|     furi_assert(request);
 | |
|     furi_assert(request->which_content == PB_Main_app_exit_request_tag);
 | |
| 
 | |
|     RpcAppSystem* rpc_app = context;
 | |
|     furi_assert(rpc_app);
 | |
| 
 | |
|     if(rpc_app->callback) {
 | |
|         FURI_LOG_D(TAG, "ExitRequest: id %lu", request->command_id);
 | |
| 
 | |
|         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 {
 | |
|         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(request->which_content == PB_Main_app_load_file_request_tag);
 | |
| 
 | |
|     RpcAppSystem* rpc_app = context;
 | |
|     furi_assert(rpc_app);
 | |
| 
 | |
|     if(rpc_app->callback) {
 | |
|         FURI_LOG_D(TAG, "LoadFile: id %lu", request->command_id);
 | |
| 
 | |
|         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 {
 | |
|         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(request->which_content == PB_Main_app_button_press_request_tag);
 | |
| 
 | |
|     RpcAppSystem* rpc_app = context;
 | |
|     furi_assert(rpc_app);
 | |
| 
 | |
|     if(rpc_app->callback) {
 | |
|         FURI_LOG_D(TAG, "ButtonPress");
 | |
| 
 | |
|         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 {
 | |
|         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);
 | |
| 
 | |
|     RpcAppSystem* rpc_app = context;
 | |
|     furi_assert(rpc_app);
 | |
| 
 | |
|     if(rpc_app->callback) {
 | |
|         FURI_LOG_D(TAG, "ButtonRelease");
 | |
| 
 | |
|         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 {
 | |
|         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);
 | |
| 
 | |
|     RpcAppSystem* rpc_app = context;
 | |
|     furi_assert(rpc_app);
 | |
| 
 | |
|     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(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);
 | |
| 
 | |
|     RpcAppSystem* rpc_app = context;
 | |
|     furi_assert(rpc_app);
 | |
| 
 | |
|     if(rpc_app->callback) {
 | |
|         FURI_LOG_D(TAG, "DataExchange");
 | |
| 
 | |
|         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 {
 | |
|         rpc_system_app_send_error_response(
 | |
|             rpc_app, request->command_id, PB_CommandStatus_ERROR_APP_NOT_RUNNING, "DataExchange");
 | |
|     }
 | |
| }
 | |
| 
 | |
| void rpc_system_app_send_started(RpcAppSystem* rpc_app) {
 | |
|     furi_assert(rpc_app);
 | |
|     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);
 | |
|     rpc_system_app_send_state_response(rpc_app, PB_App_AppState_APP_CLOSED, "SendExit");
 | |
| }
 | |
| 
 | |
| void rpc_system_app_confirm(RpcAppSystem* rpc_app, bool result) {
 | |
|     furi_assert(rpc_app);
 | |
|     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);
 | |
| 
 | |
|     const uint32_t last_command_id = rpc_app->last_command_id;
 | |
|     const RpcAppSystemEventType last_event_type = rpc_app->last_event_type;
 | |
| 
 | |
|     rpc_app->last_command_id = 0;
 | |
|     rpc_app->last_event_type = RpcAppEventTypeInvalid;
 | |
| 
 | |
|     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->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);
 | |
|     rpc_app->error_code = error_code;
 | |
| }
 | |
| 
 | |
| void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text) {
 | |
|     furi_assert(rpc_app);
 | |
| 
 | |
|     if(rpc_app->error_text) {
 | |
|         free(rpc_app->error_text);
 | |
|     }
 | |
| 
 | |
|     rpc_app->error_text = error_text ? strdup(error_text) : NULL;
 | |
| }
 | |
| 
 | |
| void rpc_system_app_error_reset(RpcAppSystem* rpc_app) {
 | |
|     furi_assert(rpc_app);
 | |
| 
 | |
|     rpc_system_app_set_error_code(rpc_app, 0);
 | |
|     rpc_system_app_set_error_text(rpc_app, NULL);
 | |
| }
 | |
| 
 | |
| void rpc_system_app_exchange_data(RpcAppSystem* rpc_app, const uint8_t* data, size_t data_size) {
 | |
|     furi_assert(rpc_app);
 | |
| 
 | |
|     PB_Main* request = malloc(sizeof(PB_Main));
 | |
| 
 | |
|     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));
 | |
|         content->data->size = data_size;
 | |
|         memcpy(content->data->bytes, data, data_size);
 | |
|     } else {
 | |
|         content->data = NULL;
 | |
|     }
 | |
| 
 | |
|     rpc_send_and_release(rpc_app->session, request);
 | |
| 
 | |
|     free(request);
 | |
| }
 | |
| 
 | |
| void* rpc_system_app_alloc(RpcSession* session) {
 | |
|     furi_assert(session);
 | |
| 
 | |
|     RpcAppSystem* rpc_app = malloc(sizeof(RpcAppSystem));
 | |
|     rpc_app->session = session;
 | |
| 
 | |
|     RpcHandler rpc_handler = {
 | |
|         .message_handler = NULL,
 | |
|         .decode_submessage = NULL,
 | |
|         .context = rpc_app,
 | |
|     };
 | |
| 
 | |
|     rpc_handler.message_handler = rpc_system_app_start_process;
 | |
|     rpc_add_handler(session, PB_Main_app_start_request_tag, &rpc_handler);
 | |
| 
 | |
|     rpc_handler.message_handler = rpc_system_app_lock_status_process;
 | |
|     rpc_add_handler(session, PB_Main_app_lock_status_request_tag, &rpc_handler);
 | |
| 
 | |
|     rpc_handler.message_handler = rpc_system_app_exit_request;
 | |
|     rpc_add_handler(session, PB_Main_app_exit_request_tag, &rpc_handler);
 | |
| 
 | |
|     rpc_handler.message_handler = rpc_system_app_load_file;
 | |
|     rpc_add_handler(session, PB_Main_app_load_file_request_tag, &rpc_handler);
 | |
| 
 | |
|     rpc_handler.message_handler = rpc_system_app_button_press;
 | |
|     rpc_add_handler(session, PB_Main_app_button_press_request_tag, &rpc_handler);
 | |
| 
 | |
|     rpc_handler.message_handler = rpc_system_app_button_release;
 | |
|     rpc_add_handler(session, PB_Main_app_button_release_request_tag, &rpc_handler);
 | |
| 
 | |
|     rpc_handler.message_handler = rpc_system_app_get_error_process;
 | |
|     rpc_add_handler(session, PB_Main_app_get_error_request_tag, &rpc_handler);
 | |
| 
 | |
|     rpc_handler.message_handler = rpc_system_app_data_exchange_process;
 | |
|     rpc_add_handler(session, PB_Main_app_data_exchange_request_tag, &rpc_handler);
 | |
| 
 | |
|     return rpc_app;
 | |
| }
 | |
| 
 | |
| void rpc_system_app_free(void* context) {
 | |
|     RpcAppSystem* rpc_app = context;
 | |
|     furi_assert(rpc_app);
 | |
|     furi_assert(rpc_app->session);
 | |
| 
 | |
|     if(rpc_app->callback) {
 | |
|         const RpcAppSystemEvent event = {
 | |
|             .type = RpcAppEventTypeSessionClose,
 | |
|             .data =
 | |
|                 {
 | |
|                     .type = RpcAppSystemEventDataTypeNone,
 | |
|                     {0},
 | |
|                 },
 | |
|         };
 | |
| 
 | |
|         rpc_app->callback(&event, rpc_app->callback_context);
 | |
|     }
 | |
| 
 | |
|     while(rpc_app->callback) {
 | |
|         furi_delay_tick(1);
 | |
|     }
 | |
| 
 | |
|     free(rpc_app);
 | |
| }
 |