unleashed-firmware/applications/services/rpc/rpc_app.c
Georgii Surkov 49dcf81743
[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>
2023-11-10 16:22:34 +09:00

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