[FL-2627] Flipper applications: SDK, build and debug system (#1387)

* Added support for running applications from SD card (FAPs - Flipper Application Packages)
* Added plugin_dist target for fbt to build FAPs
* All apps of type FlipperAppType.EXTERNAL and FlipperAppType.PLUGIN are built as FAPs by default
* Updated VSCode configuration for new fbt features - re-deploy stock configuration to use them
* Added debugging support for FAPs with fbt debug & VSCode
* Added public firmware API with automated versioning

Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: SG <who.just.the.doctor@gmail.com>
Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
SG
2022-09-15 02:11:38 +10:00
committed by Aleksandr Kutuzov
parent 0f6f9ad52e
commit b9a766d909
895 changed files with 8862 additions and 1465 deletions

View File

@@ -0,0 +1,9 @@
App(
appid="rpc_start",
apptype=FlipperAppType.STARTUP,
entry_point="rpc_on_system_start",
cdefines=["SRV_RPC"],
requires=["cli"],
order=10,
sdk_headers=["rpc_app.h"],
)

View File

@@ -0,0 +1,463 @@
#include "rpc_i.h"
#include <pb.h>
#include <pb_decode.h>
#include <pb_encode.h>
#include <storage.pb.h>
#include <flipper.pb.h>
#include <portmacro.h>
#include <furi.h>
#include <cli/cli.h>
#include <stdint.h>
#include <stdio.h>
#include <stream_buffer.h>
#include <m-string.h>
#include <m-dict.h>
#define TAG "RpcSrv"
typedef enum {
RpcEvtNewData = (1 << 0),
RpcEvtDisconnect = (1 << 1),
} RpcEvtFlags;
#define RPC_ALL_EVENTS (RpcEvtNewData | RpcEvtDisconnect)
DICT_DEF2(RpcHandlerDict, pb_size_t, M_DEFAULT_OPLIST, RpcHandler, M_POD_OPLIST)
typedef struct {
RpcSystemAlloc alloc;
RpcSystemFree free;
void* context;
} RpcSystemCallbacks;
static RpcSystemCallbacks rpc_systems[] = {
{
.alloc = rpc_system_system_alloc,
.free = NULL,
},
{
.alloc = rpc_system_storage_alloc,
.free = rpc_system_storage_free,
},
{
.alloc = rpc_system_app_alloc,
.free = rpc_system_app_free,
},
{
.alloc = rpc_system_gui_alloc,
.free = rpc_system_gui_free,
},
{
.alloc = rpc_system_gpio_alloc,
.free = NULL,
}};
struct RpcSession {
Rpc* rpc;
FuriThread* thread;
RpcHandlerDict_t handlers;
StreamBufferHandle_t stream;
PB_Main* decoded_message;
bool terminate;
void** system_contexts;
bool decode_error;
FuriMutex* callbacks_mutex;
RpcSendBytesCallback send_bytes_callback;
RpcBufferIsEmptyCallback buffer_is_empty_callback;
RpcSessionClosedCallback closed_callback;
RpcSessionTerminatedCallback terminated_callback;
void* context;
};
struct Rpc {
FuriMutex* busy_mutex;
};
static void rpc_close_session_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
RpcSession* session = (RpcSession*)context;
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever);
if(session->closed_callback) {
session->closed_callback(session->context);
} else {
FURI_LOG_W(TAG, "Session stop isn't processed by transport layer");
}
furi_mutex_release(session->callbacks_mutex);
}
void rpc_session_set_context(RpcSession* session, void* context) {
furi_assert(session);
furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever);
session->context = context;
furi_mutex_release(session->callbacks_mutex);
}
void rpc_session_set_close_callback(RpcSession* session, RpcSessionClosedCallback callback) {
furi_assert(session);
furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever);
session->closed_callback = callback;
furi_mutex_release(session->callbacks_mutex);
}
void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback) {
furi_assert(session);
furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever);
session->send_bytes_callback = callback;
furi_mutex_release(session->callbacks_mutex);
}
void rpc_session_set_buffer_is_empty_callback(
RpcSession* session,
RpcBufferIsEmptyCallback callback) {
furi_assert(session);
furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever);
session->buffer_is_empty_callback = callback;
furi_mutex_release(session->callbacks_mutex);
}
void rpc_session_set_terminated_callback(
RpcSession* session,
RpcSessionTerminatedCallback callback) {
furi_assert(session);
furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever);
session->terminated_callback = callback;
furi_mutex_release(session->callbacks_mutex);
}
/* Doesn't forbid using rpc_feed_bytes() after session close - it's safe.
* Because any bytes received in buffer will be flushed before next session.
* If bytes get into stream buffer before it's get epmtified and this
* command is gets processed - it's safe either. But case of it is quite
* odd: client sends close request and sends command after.
*/
size_t
rpc_session_feed(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout) {
furi_assert(session);
furi_assert(encoded_bytes);
furi_assert(size > 0);
size_t bytes_sent = xStreamBufferSend(session->stream, encoded_bytes, size, timeout);
furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtNewData);
return bytes_sent;
}
size_t rpc_session_get_available_size(RpcSession* session) {
furi_assert(session);
return xStreamBufferSpacesAvailable(session->stream);
}
bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
furi_assert(istream);
furi_assert(buf);
RpcSession* session = istream->state;
furi_assert(session);
furi_assert(istream->bytes_left);
uint32_t flags = 0;
size_t bytes_received = 0;
while(1) {
bytes_received +=
xStreamBufferReceive(session->stream, buf + bytes_received, count - bytes_received, 0);
if(xStreamBufferIsEmpty(session->stream)) {
if(session->buffer_is_empty_callback) {
session->buffer_is_empty_callback(session->context);
}
}
if(session->decode_error) {
/* never go out till RPC_EVENT_DISCONNECT come */
bytes_received = 0;
}
if(count == bytes_received) {
break;
} else {
flags = furi_thread_flags_wait(RPC_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever);
if(flags & RpcEvtDisconnect) {
if(xStreamBufferIsEmpty(session->stream)) {
session->terminate = true;
istream->bytes_left = 0;
bytes_received = 0;
break;
} else {
/* Save disconnect flag and continue reading buffer */
furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtDisconnect);
}
} else if(flags & RpcEvtNewData) {
// Just wake thread up
}
}
}
#if SRV_RPC_DEBUG
rpc_debug_print_data("INPUT", buf, bytes_received);
#endif
return (count == bytes_received);
}
static bool rpc_pb_content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg) {
furi_assert(stream);
RpcSession* session = stream->state;
furi_assert(session);
furi_assert(field);
RpcHandler* handler = RpcHandlerDict_get(session->handlers, field->tag);
if(handler && handler->decode_submessage) {
handler->decode_submessage(stream, field, arg);
}
return true;
}
static int32_t rpc_session_worker(void* context) {
furi_assert(context);
RpcSession* session = (RpcSession*)context;
Rpc* rpc = session->rpc;
FURI_LOG_D(TAG, "Session started");
while(1) {
pb_istream_t istream = {
.callback = rpc_pb_stream_read,
.state = session,
.errmsg = NULL,
.bytes_left = RPC_MAX_MESSAGE_SIZE, /* max incoming message size */
};
bool message_decode_failed = false;
if(pb_decode_ex(&istream, &PB_Main_msg, session->decoded_message, PB_DECODE_DELIMITED)) {
#if SRV_RPC_DEBUG
FURI_LOG_I(TAG, "INPUT:");
rpc_debug_print_message(session->decoded_message);
#endif
RpcHandler* handler =
RpcHandlerDict_get(session->handlers, session->decoded_message->which_content);
if(handler && handler->message_handler) {
furi_check(furi_mutex_acquire(rpc->busy_mutex, FuriWaitForever) == FuriStatusOk);
handler->message_handler(session->decoded_message, handler->context);
furi_check(furi_mutex_release(rpc->busy_mutex) == FuriStatusOk);
} else if(session->decoded_message->which_content == 0) {
/* Receiving zeroes means message is 0-length, which
* is valid for proto3: all fields are filled with default values.
* 0 - is default value for which_content field.
* Mark it as decode error, because there is no content message
* in Main message with tag 0.
*/
message_decode_failed = true;
} else if(!handler && !session->terminate) {
FURI_LOG_E(
TAG,
"Message(%d) decoded, but not implemented",
session->decoded_message->which_content);
rpc_send_and_release_empty(
session,
session->decoded_message->command_id,
PB_CommandStatus_ERROR_NOT_IMPLEMENTED);
}
} else {
message_decode_failed = true;
}
if(message_decode_failed) {
xStreamBufferReset(session->stream);
if(!session->terminate) {
/* Protobuf can't determine start and end of message.
* Handle this by adding varint at beginning
* of a message (PB_ENCODE_DELIMITED). But decoding fail
* means we can't be sure next bytes are varint for next
* message, so the only way to close session.
* RPC itself can't make decision to close session. It has
* to notify:
* 1) down layer (transport)
* 2) other side (companion app)
* Who are responsible to handle RPC session lifecycle.
* Companion receives 2 messages: ERROR_DECODE and session_closed.
*/
FURI_LOG_E(TAG, "Decode failed, error: \'%.128s\'", PB_GET_ERROR(&istream));
session->decode_error = true;
rpc_send_and_release_empty(session, 0, PB_CommandStatus_ERROR_DECODE);
furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever);
if(session->closed_callback) {
session->closed_callback(session->context);
}
furi_mutex_release(session->callbacks_mutex);
}
}
pb_release(&PB_Main_msg, session->decoded_message);
if(session->terminate) {
FURI_LOG_D(TAG, "Session terminated");
break;
}
}
return 0;
}
static void rpc_session_free_callback(FuriThreadState thread_state, void* context) {
furi_assert(context);
RpcSession* session = (RpcSession*)context;
if(thread_state == FuriThreadStateStopped) {
for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) {
if(rpc_systems[i].free) {
rpc_systems[i].free(session->system_contexts[i]);
}
}
free(session->system_contexts);
free(session->decoded_message);
RpcHandlerDict_clear(session->handlers);
vStreamBufferDelete(session->stream);
furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever);
if(session->terminated_callback) {
session->terminated_callback(session->context);
}
furi_mutex_release(session->callbacks_mutex);
furi_mutex_free(session->callbacks_mutex);
furi_thread_free(session->thread);
free(session);
}
}
RpcSession* rpc_session_open(Rpc* rpc) {
furi_assert(rpc);
RpcSession* session = malloc(sizeof(RpcSession));
session->callbacks_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
session->stream = xStreamBufferCreate(RPC_BUFFER_SIZE, 1);
session->rpc = rpc;
session->terminate = false;
session->decode_error = false;
RpcHandlerDict_init(session->handlers);
session->decoded_message = malloc(sizeof(PB_Main));
session->decoded_message->cb_content.funcs.decode = rpc_pb_content_callback;
session->decoded_message->cb_content.arg = session;
session->system_contexts = malloc(COUNT_OF(rpc_systems) * sizeof(void*));
for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) {
session->system_contexts[i] = rpc_systems[i].alloc(session);
}
RpcHandler rpc_handler = {
.message_handler = rpc_close_session_process,
.decode_submessage = NULL,
.context = session,
};
rpc_add_handler(session, PB_Main_stop_session_tag, &rpc_handler);
session->thread = furi_thread_alloc();
furi_thread_set_name(session->thread, "RpcSessionWorker");
furi_thread_set_stack_size(session->thread, 2048);
furi_thread_set_context(session->thread, session);
furi_thread_set_callback(session->thread, rpc_session_worker);
furi_thread_set_state_context(session->thread, session);
furi_thread_set_state_callback(session->thread, rpc_session_free_callback);
furi_thread_start(session->thread);
return session;
}
void rpc_session_close(RpcSession* session) {
furi_assert(session);
furi_assert(session->rpc);
rpc_session_set_send_bytes_callback(session, NULL);
rpc_session_set_close_callback(session, NULL);
rpc_session_set_buffer_is_empty_callback(session, NULL);
furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtDisconnect);
}
void rpc_on_system_start(void* p) {
UNUSED(p);
Rpc* rpc = malloc(sizeof(Rpc));
rpc->busy_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(
cli, "start_rpc_session", CliCommandFlagParallelSafe, rpc_cli_command_start_session, rpc);
furi_record_create(RECORD_RPC, rpc);
}
void rpc_add_handler(RpcSession* session, pb_size_t message_tag, RpcHandler* handler) {
furi_assert(RpcHandlerDict_get(session->handlers, message_tag) == NULL);
RpcHandlerDict_set_at(session->handlers, message_tag, *handler);
}
void rpc_send(RpcSession* session, PB_Main* message) {
furi_assert(session);
furi_assert(message);
pb_ostream_t ostream = PB_OSTREAM_SIZING;
#if SRV_RPC_DEBUG
FURI_LOG_I(TAG, "OUTPUT:");
rpc_debug_print_message(message);
#endif
bool result = pb_encode_ex(&ostream, &PB_Main_msg, message, PB_ENCODE_DELIMITED);
furi_check(result && ostream.bytes_written);
uint8_t* buffer = malloc(ostream.bytes_written);
ostream = pb_ostream_from_buffer(buffer, ostream.bytes_written);
pb_encode_ex(&ostream, &PB_Main_msg, message, PB_ENCODE_DELIMITED);
#if SRV_RPC_DEBUG
rpc_debug_print_data("OUTPUT", buffer, ostream.bytes_written);
#endif
furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever);
if(session->send_bytes_callback) {
session->send_bytes_callback(session->context, buffer, ostream.bytes_written);
}
furi_mutex_release(session->callbacks_mutex);
free(buffer);
}
void rpc_send_and_release(RpcSession* session, PB_Main* message) {
rpc_send(session, message);
pb_release(&PB_Main_msg, message);
}
void rpc_send_and_release_empty(RpcSession* session, uint32_t command_id, PB_CommandStatus status) {
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

@@ -0,0 +1,124 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <furi.h>
#ifdef __cplusplus
extern "C" {
#endif
#define RPC_BUFFER_SIZE (1024)
#define RPC_MAX_MESSAGE_SIZE (1536)
#define RECORD_RPC "rpc"
/** Rpc interface. Used for opening session only. */
typedef struct Rpc Rpc;
/** Rpc session interface */
typedef struct RpcSession RpcSession;
/** Callback to send to client any data (e.g. response to command) */
typedef void (*RpcSendBytesCallback)(void* context, uint8_t* bytes, size_t bytes_len);
/** Callback to notify client that buffer is empty */
typedef void (*RpcBufferIsEmptyCallback)(void* context);
/** Callback to notify transport layer that close_session command
* is received. Any other actions lays on transport layer.
* No destruction or session close preformed. */
typedef void (*RpcSessionClosedCallback)(void* context);
/** Callback to notify transport layer that session was closed
* and all operations were finished */
typedef void (*RpcSessionTerminatedCallback)(void* context);
/** Open RPC session
*
* USAGE:
* 1) rpc_session_open();
* 2) rpc_session_set_context();
* 3) rpc_session_set_send_bytes_callback();
* 4) rpc_session_set_close_callback();
* 5) while(1) {
* rpc_session_feed();
* }
* 6) rpc_session_close();
*
*
* @param rpc instance
* @return pointer to RpcSession descriptor, or
* NULL if RPC is busy and can't open session now
*/
RpcSession* rpc_session_open(Rpc* rpc);
/** Close RPC session
* It is guaranteed that no callbacks will be called
* as soon as session is closed. So no need in setting
* callbacks to NULL after session close.
*
* @param session pointer to RpcSession descriptor
*/
void rpc_session_close(RpcSession* session);
/** Set session context for callbacks to pass
*
* @param session pointer to RpcSession descriptor
* @param context context to pass to callbacks
*/
void rpc_session_set_context(RpcSession* session, void* context);
/** Set callback to send bytes to client
* WARN: It's forbidden to call RPC API within RpcSendBytesCallback
*
* @param session pointer to RpcSession descriptor
* @param callback callback to send bytes to client (can be NULL)
*/
void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback);
/** Set callback to notify that buffer is empty
*
* @param session pointer to RpcSession descriptor
* @param callback callback to notify client that buffer is empty (can be NULL)
*/
void rpc_session_set_buffer_is_empty_callback(
RpcSession* session,
RpcBufferIsEmptyCallback callback);
/** Set callback to be called when RPC command to close session is received
* WARN: It's forbidden to call RPC API within RpcSessionClosedCallback
*
* @param session pointer to RpcSession descriptor
* @param callback callback to inform about RPC close session command (can be NULL)
*/
void rpc_session_set_close_callback(RpcSession* session, RpcSessionClosedCallback callback);
/** Set callback to be called when RPC session is closed
*
* @param session pointer to RpcSession descriptor
* @param callback callback to inform about RPC session state
*/
void rpc_session_set_terminated_callback(
RpcSession* session,
RpcSessionTerminatedCallback callback);
/** Give bytes to RPC service to decode them and perform command
*
* @param session pointer to RpcSession descriptor
* @param buffer buffer to provide to RPC service
* @param size size of buffer
* @param timeout max timeout to wait till all buffer will be consumed
*
* @return actually consumed bytes
*/
size_t rpc_session_feed(RpcSession* session, uint8_t* buffer, size_t size, TickType_t timeout);
/** Get available size of RPC buffer
*
* @param session pointer to RpcSession descriptor
*
* @return bytes available in buffer
*/
size_t rpc_session_get_available_size(RpcSession* session);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,318 @@
#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 app_callback;
void* app_context;
PB_Main* state_msg;
uint32_t last_id;
char* last_data;
};
#define RPC_SYSTEM_APP_TEMP_ARGS_SIZE 16
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;
furi_assert(session);
char args_temp[RPC_SYSTEM_APP_TEMP_ARGS_SIZE];
furi_assert(!rpc_app->last_id);
furi_assert(!rpc_app->last_data);
FURI_LOG_D(TAG, "StartProcess: id %d", request->command_id);
PB_CommandStatus result = PB_CommandStatus_ERROR_APP_CANT_START;
Loader* loader = furi_record_open(RECORD_LOADER);
const char* app_name = request->content.app_start_request.name;
if(app_name) {
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;
}
LoaderStatus status = loader_start(loader, app_name, app_args);
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("Programming Error");
}
} else {
result = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
}
furi_record_close(RECORD_LOADER);
FURI_LOG_D(TAG, "StartProcess: response id %d, result %d", request->command_id, result);
rpc_send_and_release_empty(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;
RpcSession* session = rpc_app->session;
furi_assert(session);
FURI_LOG_D(TAG, "LockStatus");
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);
furi_record_close(RECORD_LOADER);
FURI_LOG_D(TAG, "LockStatus: response");
rpc_send_and_release(session, &response);
pb_release(&PB_Main_msg, &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;
RpcSession* session = rpc_app->session;
furi_assert(session);
PB_CommandStatus status;
if(rpc_app->app_callback) {
FURI_LOG_D(TAG, "ExitRequest: id %d", 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);
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
FURI_LOG_E(
TAG, "ExitRequest: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status);
rpc_send_and_release_empty(session, request->command_id, status);
}
}
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;
RpcSession* session = rpc_app->session;
furi_assert(session);
PB_CommandStatus status;
if(rpc_app->app_callback) {
FURI_LOG_D(TAG, "LoadFile: id %d", 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);
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
FURI_LOG_E(
TAG, "LoadFile: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status);
rpc_send_and_release_empty(session, request->command_id, status);
}
}
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;
RpcSession* session = rpc_app->session;
furi_assert(session);
PB_CommandStatus status;
if(rpc_app->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);
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
FURI_LOG_E(
TAG, "ButtonPress: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status);
rpc_send_and_release_empty(session, request->command_id, status);
}
}
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;
RpcSession* session = rpc_app->session;
furi_assert(session);
PB_CommandStatus status;
if(rpc_app->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);
} else {
status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
FURI_LOG_E(
TAG, "ButtonRelease: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status);
rpc_send_and_release_empty(session, request->command_id, 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);
}
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);
}
const char* rpc_system_app_get_data(RpcAppSystem* rpc_app) {
furi_assert(rpc_app);
furi_assert(rpc_app->last_data);
return rpc_app->last_data;
}
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);
PB_CommandStatus status = result ? PB_CommandStatus_OK : PB_CommandStatus_ERROR_APP_CMD_ERROR;
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 %d 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;
}
}
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;
}
void* rpc_system_app_alloc(RpcSession* session) {
furi_assert(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;
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);
return rpc_app;
}
void rpc_system_app_free(void* context) {
RpcAppSystem* rpc_app = context;
furi_assert(rpc_app);
RpcSession* session = rpc_app->session;
furi_assert(session);
if(rpc_app->app_callback) {
rpc_app->app_callback(RpcAppEventSessionClose, rpc_app->app_context);
}
while(rpc_app->app_callback) {
furi_delay_tick(1);
}
if(rpc_app->last_data) free(rpc_app->last_data);
free(rpc_app->state_msg);
free(rpc_app);
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include "rpc.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
RpcAppEventSessionClose,
RpcAppEventAppExit,
RpcAppEventLoadFile,
RpcAppEventButtonPress,
RpcAppEventButtonRelease,
} RpcAppSystemEvent;
typedef void (*RpcAppSystemCallback)(RpcAppSystemEvent event, void* context);
typedef struct RpcAppSystem RpcAppSystem;
void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx);
void rpc_system_app_send_started(RpcAppSystem* rpc_app);
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);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,89 @@
#include <cli/cli.h>
#include <furi.h>
#include <rpc/rpc.h>
#include <furi_hal.h>
#include <semphr.h>
#define TAG "RpcCli"
typedef struct {
Cli* cli;
bool session_close_request;
FuriSemaphore* terminate_semaphore;
} CliRpc;
#define CLI_READ_BUFFER_SIZE 64
static void rpc_cli_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) {
furi_assert(context);
furi_assert(bytes);
furi_assert(bytes_len > 0);
CliRpc* cli_rpc = context;
cli_write(cli_rpc->cli, bytes, bytes_len);
}
static void rpc_cli_session_close_callback(void* context) {
furi_assert(context);
CliRpc* cli_rpc = context;
cli_rpc->session_close_request = true;
}
static void rpc_cli_session_terminated_callback(void* context) {
furi_check(context);
CliRpc* cli_rpc = context;
furi_semaphore_release(cli_rpc->terminate_semaphore);
}
void rpc_cli_command_start_session(Cli* cli, string_t args, void* context) {
UNUSED(args);
furi_assert(cli);
furi_assert(context);
Rpc* rpc = context;
uint32_t mem_before = memmgr_get_free_heap();
FURI_LOG_D(TAG, "Free memory %d", mem_before);
furi_hal_usb_lock();
RpcSession* rpc_session = rpc_session_open(rpc);
if(rpc_session == NULL) {
printf("Session start error\r\n");
furi_hal_usb_unlock();
return;
}
CliRpc cli_rpc = {.cli = cli, .session_close_request = false};
cli_rpc.terminate_semaphore = furi_semaphore_alloc(1, 0);
rpc_session_set_context(rpc_session, &cli_rpc);
rpc_session_set_send_bytes_callback(rpc_session, rpc_cli_send_bytes_callback);
rpc_session_set_close_callback(rpc_session, rpc_cli_session_close_callback);
rpc_session_set_terminated_callback(rpc_session, rpc_cli_session_terminated_callback);
uint8_t* buffer = malloc(CLI_READ_BUFFER_SIZE);
size_t size_received = 0;
while(1) {
size_received = cli_read_timeout(cli_rpc.cli, buffer, CLI_READ_BUFFER_SIZE, 50);
if(!cli_is_connected(cli_rpc.cli) || cli_rpc.session_close_request) {
break;
}
if(size_received) {
size_t fed_bytes = rpc_session_feed(rpc_session, buffer, size_received, 3000);
(void)fed_bytes;
furi_assert(fed_bytes == size_received);
}
}
rpc_session_close(rpc_session);
furi_check(
furi_semaphore_acquire(cli_rpc.terminate_semaphore, FuriWaitForever) == FuriStatusOk);
furi_semaphore_free(cli_rpc.terminate_semaphore);
free(buffer);
furi_hal_usb_unlock();
}

View File

@@ -0,0 +1,260 @@
#include "rpc_i.h"
#include <m-string.h>
static size_t rpc_debug_print_file_msg(
string_t str,
const char* prefix,
const PB_Storage_File* msg_file,
size_t msg_files_size) {
size_t cnt = 0;
for(size_t i = 0; i < msg_files_size; ++i, ++msg_file) {
string_cat_printf(
str,
"%s[%c] size: %5ld",
prefix,
msg_file->type == PB_Storage_File_FileType_DIR ? 'd' : 'f',
msg_file->size);
if(msg_file->name) {
string_cat_printf(str, " \'%s\'", msg_file->name);
}
if(msg_file->data && msg_file->data->size) {
string_cat_printf(
str,
" (%d):\'%.*s%s\'",
msg_file->data->size,
MIN(msg_file->data->size, 30),
msg_file->data->bytes,
msg_file->data->size > 30 ? "..." : "");
}
string_cat_printf(str, "\r\n");
}
return cnt;
}
void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size) {
string_t str;
string_init(str);
string_reserve(str, 100 + size * 5);
string_cat_printf(str, "\r\n%s DEC(%d): {", prefix, size);
for(size_t i = 0; i < size; ++i) {
string_cat_printf(str, "%d, ", buffer[i]);
}
string_cat_printf(str, "}\r\n");
printf("%s", string_get_cstr(str));
string_reset(str);
string_reserve(str, 100 + size * 3);
string_cat_printf(str, "%s HEX(%d): {", prefix, size);
for(size_t i = 0; i < size; ++i) {
string_cat_printf(str, "%02X", buffer[i]);
}
string_cat_printf(str, "}\r\n\r\n");
printf("%s", string_get_cstr(str));
string_clear(str);
}
void rpc_debug_print_message(const PB_Main* message) {
string_t str;
string_init(str);
string_cat_printf(
str,
"PB_Main: {\r\n\tresult: %d cmd_id: %ld (%s)\r\n",
message->command_status,
message->command_id,
message->has_next ? "has_next" : "last");
switch(message->which_content) {
default:
/* not implemented yet */
string_cat_printf(str, "\tNOT_IMPLEMENTED (%d) {\r\n", message->which_content);
break;
case PB_Main_stop_session_tag:
string_cat_printf(str, "\tstop_session {\r\n");
break;
case PB_Main_app_start_request_tag: {
string_cat_printf(str, "\tapp_start {\r\n");
const char* name = message->content.app_start_request.name;
const char* args = message->content.app_start_request.args;
if(name) {
string_cat_printf(str, "\t\tname: %s\r\n", name);
}
if(args) {
string_cat_printf(str, "\t\targs: %s\r\n", args);
}
break;
}
case PB_Main_app_lock_status_request_tag: {
string_cat_printf(str, "\tapp_lock_status_request {\r\n");
break;
}
case PB_Main_app_lock_status_response_tag: {
string_cat_printf(str, "\tapp_lock_status_response {\r\n");
bool lock_status = message->content.app_lock_status_response.locked;
string_cat_printf(str, "\t\tlocked: %s\r\n", lock_status ? "true" : "false");
break;
}
case PB_Main_storage_md5sum_request_tag: {
string_cat_printf(str, "\tmd5sum_request {\r\n");
const char* path = message->content.storage_md5sum_request.path;
if(path) {
string_cat_printf(str, "\t\tpath: %s\r\n", path);
}
break;
}
case PB_Main_storage_md5sum_response_tag: {
string_cat_printf(str, "\tmd5sum_response {\r\n");
const char* path = message->content.storage_md5sum_response.md5sum;
if(path) {
string_cat_printf(str, "\t\tmd5sum: %s\r\n", path);
}
break;
}
case PB_Main_system_ping_request_tag:
string_cat_printf(str, "\tping_request {\r\n");
break;
case PB_Main_system_ping_response_tag:
string_cat_printf(str, "\tping_response {\r\n");
break;
case PB_Main_system_device_info_request_tag:
string_cat_printf(str, "\tdevice_info_request {\r\n");
break;
case PB_Main_system_device_info_response_tag:
string_cat_printf(str, "\tdevice_info_response {\r\n");
string_cat_printf(
str,
"\t\t%s: %s\r\n",
message->content.system_device_info_response.key,
message->content.system_device_info_response.value);
break;
case PB_Main_storage_mkdir_request_tag:
string_cat_printf(str, "\tmkdir {\r\n");
break;
case PB_Main_storage_delete_request_tag: {
string_cat_printf(str, "\tdelete {\r\n");
const char* path = message->content.storage_delete_request.path;
if(path) {
string_cat_printf(str, "\t\tpath: %s\r\n", path);
}
break;
}
case PB_Main_empty_tag:
string_cat_printf(str, "\tempty {\r\n");
break;
case PB_Main_storage_info_request_tag: {
string_cat_printf(str, "\tinfo_request {\r\n");
const char* path = message->content.storage_info_request.path;
if(path) {
string_cat_printf(str, "\t\tpath: %s\r\n", path);
}
break;
}
case PB_Main_storage_info_response_tag: {
string_cat_printf(str, "\tinfo_response {\r\n");
string_cat_printf(
str, "\t\ttotal_space: %lu\r\n", message->content.storage_info_response.total_space);
string_cat_printf(
str, "\t\tfree_space: %lu\r\n", message->content.storage_info_response.free_space);
break;
}
case PB_Main_storage_stat_request_tag: {
string_cat_printf(str, "\tstat_request {\r\n");
const char* path = message->content.storage_stat_request.path;
if(path) {
string_cat_printf(str, "\t\tpath: %s\r\n", path);
}
break;
}
case PB_Main_storage_stat_response_tag: {
string_cat_printf(str, "\tstat_response {\r\n");
if(message->content.storage_stat_response.has_file) {
const PB_Storage_File* msg_file = &message->content.storage_stat_response.file;
rpc_debug_print_file_msg(str, "\t\t\t", msg_file, 1);
}
break;
}
case PB_Main_storage_list_request_tag: {
string_cat_printf(str, "\tlist_request {\r\n");
const char* path = message->content.storage_list_request.path;
if(path) {
string_cat_printf(str, "\t\tpath: %s\r\n", path);
}
break;
}
case PB_Main_storage_read_request_tag: {
string_cat_printf(str, "\tread_request {\r\n");
const char* path = message->content.storage_read_request.path;
if(path) {
string_cat_printf(str, "\t\tpath: %s\r\n", path);
}
break;
}
case PB_Main_storage_write_request_tag: {
string_cat_printf(str, "\twrite_request {\r\n");
const char* path = message->content.storage_write_request.path;
if(path) {
string_cat_printf(str, "\t\tpath: %s\r\n", path);
}
if(message->content.storage_write_request.has_file) {
const PB_Storage_File* msg_file = &message->content.storage_write_request.file;
rpc_debug_print_file_msg(str, "\t\t\t", msg_file, 1);
}
break;
}
case PB_Main_storage_read_response_tag:
string_cat_printf(str, "\tread_response {\r\n");
if(message->content.storage_read_response.has_file) {
const PB_Storage_File* msg_file = &message->content.storage_read_response.file;
rpc_debug_print_file_msg(str, "\t\t\t", msg_file, 1);
}
break;
case PB_Main_storage_list_response_tag: {
const PB_Storage_File* msg_file = message->content.storage_list_response.file;
size_t msg_file_count = message->content.storage_list_response.file_count;
string_cat_printf(str, "\tlist_response {\r\n");
rpc_debug_print_file_msg(str, "\t\t", msg_file, msg_file_count);
break;
}
case PB_Main_storage_rename_request_tag: {
string_cat_printf(str, "\trename_request {\r\n");
string_cat_printf(
str, "\t\told_path: %s\r\n", message->content.storage_rename_request.old_path);
string_cat_printf(
str, "\t\tnew_path: %s\r\n", message->content.storage_rename_request.new_path);
break;
}
case PB_Main_gui_start_screen_stream_request_tag:
string_cat_printf(str, "\tstart_screen_stream {\r\n");
break;
case PB_Main_gui_stop_screen_stream_request_tag:
string_cat_printf(str, "\tstop_screen_stream {\r\n");
break;
case PB_Main_gui_screen_frame_tag:
string_cat_printf(str, "\tscreen_frame {\r\n");
break;
case PB_Main_gui_send_input_event_request_tag:
string_cat_printf(str, "\tsend_input_event {\r\n");
string_cat_printf(
str, "\t\tkey: %d\r\n", message->content.gui_send_input_event_request.key);
string_cat_printf(
str, "\t\type: %d\r\n", message->content.gui_send_input_event_request.type);
break;
case PB_Main_gui_start_virtual_display_request_tag:
string_cat_printf(str, "\tstart_virtual_display {\r\n");
break;
case PB_Main_gui_stop_virtual_display_request_tag:
string_cat_printf(str, "\tstop_virtual_display {\r\n");
break;
}
string_cat_printf(str, "\t}\r\n}\r\n");
printf("%s", string_get_cstr(str));
string_clear(str);
}

View File

@@ -0,0 +1,216 @@
#include "flipper.pb.h"
#include "rpc_i.h"
#include "gpio.pb.h"
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) {
switch(rpc_pin) {
case PB_Gpio_GpioPin_PC0:
return &gpio_ext_pc0;
case PB_Gpio_GpioPin_PC1:
return &gpio_ext_pc1;
case PB_Gpio_GpioPin_PC3:
return &gpio_ext_pc3;
case PB_Gpio_GpioPin_PB2:
return &gpio_ext_pb2;
case PB_Gpio_GpioPin_PB3:
return &gpio_ext_pb3;
case PB_Gpio_GpioPin_PA4:
return &gpio_ext_pa4;
case PB_Gpio_GpioPin_PA6:
return &gpio_ext_pa6;
case PB_Gpio_GpioPin_PA7:
return &gpio_ext_pa7;
}
__builtin_unreachable();
}
static GpioMode rpc_mode_to_hal_mode(PB_Gpio_GpioPinMode rpc_mode) {
switch(rpc_mode) {
case PB_Gpio_GpioPinMode_OUTPUT:
return GpioModeOutputPushPull;
case PB_Gpio_GpioPinMode_INPUT:
return GpioModeInput;
}
__builtin_unreachable();
}
static GpioPull rpc_pull_mode_to_hall_pull_mode(PB_Gpio_GpioInputPull pull_mode) {
switch(pull_mode) {
case PB_Gpio_GpioInputPull_UP:
return GpioPullUp;
case PB_Gpio_GpioInputPull_DOWN:
return GpioPullDown;
case PB_Gpio_GpioInputPull_NO:
return GpioPullNo;
}
__builtin_unreachable();
}
static void rpc_system_gpio_set_pin_mode(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_set_pin_mode_tag);
RpcSession* session = context;
PB_Gpio_SetPinMode cmd = request->content.gpio_set_pin_mode;
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
GpioMode mode = rpc_mode_to_hal_mode(cmd.mode);
furi_hal_gpio_init_simple(pin, mode);
if(mode == GpioModeOutputPushPull) {
furi_hal_gpio_write(pin, false);
}
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}
static void rpc_system_gpio_write_pin(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_write_pin_tag);
RpcSession* session = context;
PB_Gpio_WritePin cmd = request->content.gpio_write_pin;
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
uint8_t value = !!(cmd.value);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->has_next = false;
if(LL_GPIO_MODE_OUTPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) {
response->command_status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT;
} else {
response->command_status = PB_CommandStatus_OK;
furi_hal_gpio_write(pin, value);
}
rpc_send_and_release(session, response);
free(response);
}
static void rpc_system_gpio_read_pin(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_read_pin_tag);
RpcSession* session = context;
PB_Gpio_ReadPin cmd = request->content.gpio_read_pin;
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->has_next = false;
if(LL_GPIO_MODE_INPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) {
response->command_status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT;
} else {
response->command_status = PB_CommandStatus_OK;
response->which_content = PB_Main_gpio_read_pin_response_tag;
response->content.gpio_read_pin_response.value = !!furi_hal_gpio_read(pin);
}
rpc_send_and_release(session, response);
free(response);
}
void rpc_system_gpio_get_pin_mode(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_get_pin_mode_tag);
RpcSession* session = context;
PB_Gpio_GetPinMode cmd = request->content.gpio_get_pin_mode;
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->has_next = false;
uint32_t raw_pin_mode = LL_GPIO_GetPinMode(pin->port, pin->pin);
PB_Gpio_GpioPinMode pin_mode;
if(LL_GPIO_MODE_INPUT == raw_pin_mode) {
pin_mode = PB_Gpio_GpioPinMode_INPUT;
response->command_status = PB_CommandStatus_OK;
} else if(LL_GPIO_MODE_OUTPUT == raw_pin_mode) {
pin_mode = PB_Gpio_GpioPinMode_OUTPUT;
response->command_status = PB_CommandStatus_OK;
} else {
pin_mode = PB_Gpio_GpioPinMode_INPUT;
response->command_status = PB_CommandStatus_ERROR_GPIO_UNKNOWN_PIN_MODE;
}
response->which_content = PB_Main_gpio_get_pin_mode_response_tag;
response->content.gpio_get_pin_mode_response.mode = pin_mode;
rpc_send_and_release(session, response);
free(response);
}
void rpc_system_gpio_set_input_pull(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_gpio_set_input_pull_tag);
RpcSession* session = context;
PB_Gpio_SetInputPull cmd = request->content.gpio_set_input_pull;
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
const GpioPull pull_mode = rpc_pull_mode_to_hall_pull_mode(cmd.pull_mode);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->has_next = false;
PB_CommandStatus status;
if(LL_GPIO_MODE_INPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) {
status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT;
} else {
status = PB_CommandStatus_OK;
furi_hal_gpio_init(pin, GpioModeInput, pull_mode, GpioSpeedLow);
}
rpc_send_and_release_empty(session, request->command_id, status);
free(response);
}
void* rpc_system_gpio_alloc(RpcSession* session) {
furi_assert(session);
RpcHandler rpc_handler = {
.message_handler = NULL,
.decode_submessage = NULL,
.context = session,
};
rpc_handler.message_handler = rpc_system_gpio_set_pin_mode;
rpc_add_handler(session, PB_Main_gpio_set_pin_mode_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_write_pin;
rpc_add_handler(session, PB_Main_gpio_write_pin_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_read_pin;
rpc_add_handler(session, PB_Main_gpio_read_pin_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_get_pin_mode;
rpc_add_handler(session, PB_Main_gpio_get_pin_mode_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_set_input_pull;
rpc_add_handler(session, PB_Main_gpio_set_input_pull_tag, &rpc_handler);
return NULL;
}

View File

@@ -0,0 +1,379 @@
#include "flipper.pb.h"
#include "rpc_i.h"
#include "gui.pb.h"
#include <gui/gui_i.h>
#define TAG "RpcGui"
typedef enum {
RpcGuiWorkerFlagTransmit = (1 << 0),
RpcGuiWorkerFlagExit = (1 << 1),
} RpcGuiWorkerFlag;
#define RpcGuiWorkerFlagAny (RpcGuiWorkerFlagTransmit | RpcGuiWorkerFlagExit)
typedef struct {
RpcSession* session;
Gui* gui;
// Receive part
ViewPort* virtual_display_view_port;
uint8_t* virtual_display_buffer;
// Transmit
PB_Main* transmit_frame;
FuriThread* transmit_thread;
bool virtual_display_not_empty;
bool is_streaming;
} RpcGuiSystem;
static void
rpc_system_gui_screen_stream_frame_callback(uint8_t* data, size_t size, void* context) {
furi_assert(data);
furi_assert(context);
RpcGuiSystem* rpc_gui = (RpcGuiSystem*)context;
uint8_t* buffer = rpc_gui->transmit_frame->content.gui_screen_frame.data->bytes;
furi_assert(size == rpc_gui->transmit_frame->content.gui_screen_frame.data->size);
memcpy(buffer, data, size);
furi_thread_flags_set(furi_thread_get_id(rpc_gui->transmit_thread), RpcGuiWorkerFlagTransmit);
}
static int32_t rpc_system_gui_screen_stream_frame_transmit_thread(void* context) {
furi_assert(context);
RpcGuiSystem* rpc_gui = (RpcGuiSystem*)context;
while(true) {
uint32_t flags =
furi_thread_flags_wait(RpcGuiWorkerFlagAny, FuriFlagWaitAny, FuriWaitForever);
if(flags & RpcGuiWorkerFlagTransmit) {
rpc_send(rpc_gui->session, rpc_gui->transmit_frame);
}
if(flags & RpcGuiWorkerFlagExit) {
break;
}
}
return 0;
}
static void rpc_system_gui_start_screen_stream_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
FURI_LOG_D(TAG, "StartScreenStream");
RpcGuiSystem* rpc_gui = context;
RpcSession* session = rpc_gui->session;
furi_assert(session);
if(rpc_gui->is_streaming) {
rpc_send_and_release_empty(
session, request->command_id, PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_ALREADY_STARTED);
} else {
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
rpc_gui->is_streaming = true;
size_t framebuffer_size = gui_get_framebuffer_size(rpc_gui->gui);
// Reusable Frame
rpc_gui->transmit_frame = malloc(sizeof(PB_Main));
rpc_gui->transmit_frame->which_content = PB_Main_gui_screen_frame_tag;
rpc_gui->transmit_frame->command_status = PB_CommandStatus_OK;
rpc_gui->transmit_frame->content.gui_screen_frame.data =
malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(framebuffer_size));
rpc_gui->transmit_frame->content.gui_screen_frame.data->size = framebuffer_size;
// Transmission thread for async TX
rpc_gui->transmit_thread = furi_thread_alloc();
furi_thread_set_name(rpc_gui->transmit_thread, "GuiRpcWorker");
furi_thread_set_callback(
rpc_gui->transmit_thread, rpc_system_gui_screen_stream_frame_transmit_thread);
furi_thread_set_context(rpc_gui->transmit_thread, rpc_gui);
furi_thread_set_stack_size(rpc_gui->transmit_thread, 1024);
furi_thread_start(rpc_gui->transmit_thread);
// GUI framebuffer callback
gui_add_framebuffer_callback(
rpc_gui->gui, rpc_system_gui_screen_stream_frame_callback, context);
}
}
static void rpc_system_gui_stop_screen_stream_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
FURI_LOG_D(TAG, "StopScreenStream");
RpcGuiSystem* rpc_gui = context;
RpcSession* session = rpc_gui->session;
furi_assert(session);
if(rpc_gui->is_streaming) {
rpc_gui->is_streaming = false;
// Remove GUI framebuffer callback
gui_remove_framebuffer_callback(
rpc_gui->gui, rpc_system_gui_screen_stream_frame_callback, context);
// Stop and release worker thread
furi_thread_flags_set(furi_thread_get_id(rpc_gui->transmit_thread), RpcGuiWorkerFlagExit);
furi_thread_join(rpc_gui->transmit_thread);
furi_thread_free(rpc_gui->transmit_thread);
// Release frame
pb_release(&PB_Main_msg, rpc_gui->transmit_frame);
free(rpc_gui->transmit_frame);
rpc_gui->transmit_frame = NULL;
}
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}
static void
rpc_system_gui_send_input_event_request_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_gui_send_input_event_request_tag);
furi_assert(context);
FURI_LOG_D(TAG, "SendInputEvent");
RpcGuiSystem* rpc_gui = context;
RpcSession* session = rpc_gui->session;
furi_assert(session);
InputEvent event;
bool invalid = false;
switch(request->content.gui_send_input_event_request.key) {
case PB_Gui_InputKey_UP:
event.key = InputKeyUp;
break;
case PB_Gui_InputKey_DOWN:
event.key = InputKeyDown;
break;
case PB_Gui_InputKey_RIGHT:
event.key = InputKeyRight;
break;
case PB_Gui_InputKey_LEFT:
event.key = InputKeyLeft;
break;
case PB_Gui_InputKey_OK:
event.key = InputKeyOk;
break;
case PB_Gui_InputKey_BACK:
event.key = InputKeyBack;
break;
default:
// Invalid key
invalid = true;
break;
}
switch(request->content.gui_send_input_event_request.type) {
case PB_Gui_InputType_PRESS:
event.type = InputTypePress;
break;
case PB_Gui_InputType_RELEASE:
event.type = InputTypeRelease;
break;
case PB_Gui_InputType_SHORT:
event.type = InputTypeShort;
break;
case PB_Gui_InputType_LONG:
event.type = InputTypeLong;
break;
case PB_Gui_InputType_REPEAT:
event.type = InputTypeRepeat;
break;
default:
// Invalid type
invalid = true;
break;
}
if(invalid) {
rpc_send_and_release_empty(
session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
return;
}
FuriPubSub* input_events = furi_record_open(RECORD_INPUT_EVENTS);
furi_check(input_events);
furi_pubsub_publish(input_events, &event);
furi_record_close(RECORD_INPUT_EVENTS);
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}
static void rpc_system_gui_virtual_display_render_callback(Canvas* canvas, void* context) {
furi_assert(canvas);
furi_assert(context);
RpcGuiSystem* rpc_gui = context;
if(!rpc_gui->virtual_display_not_empty) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, "Virtual Display");
canvas_draw_str_aligned(canvas, 64, 36, AlignCenter, AlignCenter, "Waiting for frames...");
return;
}
canvas_draw_xbm(canvas, 0, 0, canvas->width, canvas->height, rpc_gui->virtual_display_buffer);
}
static void rpc_system_gui_start_virtual_display_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
FURI_LOG_D(TAG, "StartVirtualDisplay");
RpcGuiSystem* rpc_gui = context;
RpcSession* session = rpc_gui->session;
furi_assert(session);
if(rpc_gui->virtual_display_view_port) {
rpc_send_and_release_empty(
session, request->command_id, PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_ALREADY_STARTED);
return;
}
// TODO: consider refactoring
// Using display framebuffer size as an XBM buffer size is like comparing apples and oranges
// Glad they both are 1024 for now
size_t buffer_size = canvas_get_buffer_size(rpc_gui->gui->canvas);
rpc_gui->virtual_display_buffer = malloc(buffer_size);
if(request->content.gui_start_virtual_display_request.has_first_frame) {
size_t buffer_size = canvas_get_buffer_size(rpc_gui->gui->canvas);
memcpy(
rpc_gui->virtual_display_buffer,
request->content.gui_start_virtual_display_request.first_frame.data->bytes,
buffer_size);
rpc_gui->virtual_display_not_empty = true;
}
rpc_gui->virtual_display_view_port = view_port_alloc();
view_port_draw_callback_set(
rpc_gui->virtual_display_view_port,
rpc_system_gui_virtual_display_render_callback,
rpc_gui);
gui_add_view_port(rpc_gui->gui, rpc_gui->virtual_display_view_port, GuiLayerFullscreen);
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}
static void rpc_system_gui_stop_virtual_display_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
FURI_LOG_D(TAG, "StopVirtualDisplay");
RpcGuiSystem* rpc_gui = context;
RpcSession* session = rpc_gui->session;
furi_assert(session);
if(!rpc_gui->virtual_display_view_port) {
rpc_send_and_release_empty(
session, request->command_id, PB_CommandStatus_ERROR_VIRTUAL_DISPLAY_NOT_STARTED);
return;
}
gui_remove_view_port(rpc_gui->gui, rpc_gui->virtual_display_view_port);
view_port_free(rpc_gui->virtual_display_view_port);
free(rpc_gui->virtual_display_buffer);
rpc_gui->virtual_display_view_port = NULL;
rpc_gui->virtual_display_not_empty = false;
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}
static void rpc_system_gui_virtual_display_frame_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
FURI_LOG_D(TAG, "VirtualDisplayFrame");
RpcGuiSystem* rpc_gui = context;
RpcSession* session = rpc_gui->session;
furi_assert(session);
if(!rpc_gui->virtual_display_view_port) {
FURI_LOG_W(TAG, "Virtual display is not started, ignoring incoming frame packet");
return;
}
size_t buffer_size = canvas_get_buffer_size(rpc_gui->gui->canvas);
memcpy(
rpc_gui->virtual_display_buffer,
request->content.gui_screen_frame.data->bytes,
buffer_size);
rpc_gui->virtual_display_not_empty = true;
view_port_update(rpc_gui->virtual_display_view_port);
(void)session;
}
void* rpc_system_gui_alloc(RpcSession* session) {
furi_assert(session);
RpcGuiSystem* rpc_gui = malloc(sizeof(RpcGuiSystem));
rpc_gui->gui = furi_record_open(RECORD_GUI);
rpc_gui->session = session;
RpcHandler rpc_handler = {
.message_handler = NULL,
.decode_submessage = NULL,
.context = rpc_gui,
};
rpc_handler.message_handler = rpc_system_gui_start_screen_stream_process;
rpc_add_handler(session, PB_Main_gui_start_screen_stream_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gui_stop_screen_stream_process;
rpc_add_handler(session, PB_Main_gui_stop_screen_stream_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gui_send_input_event_request_process;
rpc_add_handler(session, PB_Main_gui_send_input_event_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gui_start_virtual_display_process;
rpc_add_handler(session, PB_Main_gui_start_virtual_display_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gui_stop_virtual_display_process;
rpc_add_handler(session, PB_Main_gui_stop_virtual_display_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gui_virtual_display_frame_process;
rpc_add_handler(session, PB_Main_gui_screen_frame_tag, &rpc_handler);
return rpc_gui;
}
void rpc_system_gui_free(void* context) {
furi_assert(context);
RpcGuiSystem* rpc_gui = context;
furi_assert(rpc_gui->gui);
if(rpc_gui->virtual_display_view_port) {
gui_remove_view_port(rpc_gui->gui, rpc_gui->virtual_display_view_port);
view_port_free(rpc_gui->virtual_display_view_port);
free(rpc_gui->virtual_display_buffer);
rpc_gui->virtual_display_view_port = NULL;
rpc_gui->virtual_display_not_empty = false;
}
if(rpc_gui->is_streaming) {
rpc_gui->is_streaming = false;
// Remove GUI framebuffer callback
gui_remove_framebuffer_callback(
rpc_gui->gui, rpc_system_gui_screen_stream_frame_callback, context);
// Stop and release worker thread
furi_thread_flags_set(furi_thread_get_id(rpc_gui->transmit_thread), RpcGuiWorkerFlagExit);
furi_thread_join(rpc_gui->transmit_thread);
furi_thread_free(rpc_gui->transmit_thread);
// Release frame
pb_release(&PB_Main_msg, rpc_gui->transmit_frame);
free(rpc_gui->transmit_frame);
rpc_gui->transmit_frame = NULL;
}
furi_record_close(RECORD_GUI);
free(rpc_gui);
}

View File

@@ -0,0 +1,43 @@
#pragma once
#include "rpc.h"
#include "storage/filesystem_api_defines.h"
#include <pb.h>
#include <pb_decode.h>
#include <pb_encode.h>
#include <flipper.pb.h>
#include <cli/cli.h>
typedef void* (*RpcSystemAlloc)(RpcSession* session);
typedef void (*RpcSystemFree)(void* context);
typedef void (*PBMessageHandler)(const PB_Main* msg_request, void* context);
typedef struct {
bool (*decode_submessage)(pb_istream_t* stream, const pb_field_t* field, void** arg);
PBMessageHandler message_handler;
void* context;
} RpcHandler;
void rpc_send(RpcSession* session, PB_Main* main_message);
void rpc_send_and_release(RpcSession* session, PB_Main* main_message);
void rpc_send_and_release_empty(RpcSession* session, uint32_t command_id, PB_CommandStatus status);
void rpc_add_handler(RpcSession* session, pb_size_t message_tag, RpcHandler* handler);
void* rpc_system_system_alloc(RpcSession* session);
void* rpc_system_storage_alloc(RpcSession* session);
void rpc_system_storage_free(void* ctx);
void* rpc_system_app_alloc(RpcSession* session);
void rpc_system_app_free(void* ctx);
void* rpc_system_gui_alloc(RpcSession* session);
void rpc_system_gui_free(void* ctx);
void* rpc_system_gpio_alloc(RpcSession* session);
void rpc_system_gpio_free(void* ctx);
void rpc_debug_print_message(const PB_Main* message);
void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size);
void rpc_cli_command_start_session(Cli* cli, string_t args, void* context);
PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error);

View File

@@ -0,0 +1,712 @@
#include "flipper.pb.h"
#include <core/common_defines.h>
#include <core/memmgr.h>
#include <core/record.h>
#include "pb_decode.h"
#include "rpc/rpc.h"
#include "rpc_i.h"
#include "storage.pb.h"
#include "storage/filesystem_api_defines.h"
#include "storage/storage.h"
#include <stdint.h>
#include <lib/toolbox/md5.h>
#include <lib/toolbox/path.h>
#include <update_util/lfs_backup.h>
#define TAG "RpcStorage"
#define MAX_NAME_LENGTH 255
static const size_t MAX_DATA_SIZE = 512;
typedef enum {
RpcStorageStateIdle = 0,
RpcStorageStateWriting,
} RpcStorageState;
typedef struct {
RpcSession* session;
Storage* api;
File* file;
RpcStorageState state;
uint32_t current_command_id;
} RpcStorageSystem;
static void rpc_system_storage_reset_state(
RpcStorageSystem* rpc_storage,
RpcSession* session,
bool send_error) {
furi_assert(rpc_storage);
furi_assert(session);
if(rpc_storage->state != RpcStorageStateIdle) {
if(send_error) {
rpc_send_and_release_empty(
session,
rpc_storage->current_command_id,
PB_CommandStatus_ERROR_CONTINUOUS_COMMAND_INTERRUPTED);
}
if(rpc_storage->state == RpcStorageStateWriting) {
storage_file_close(rpc_storage->file);
storage_file_free(rpc_storage->file);
furi_record_close(RECORD_STORAGE);
}
rpc_storage->state = RpcStorageStateIdle;
}
}
PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error) {
PB_CommandStatus pb_error;
switch(fs_error) {
case FSE_OK:
pb_error = PB_CommandStatus_OK;
break;
case FSE_INVALID_NAME:
pb_error = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME;
break;
case FSE_INVALID_PARAMETER:
pb_error = PB_CommandStatus_ERROR_STORAGE_INVALID_PARAMETER;
break;
case FSE_INTERNAL:
pb_error = PB_CommandStatus_ERROR_STORAGE_INTERNAL;
break;
case FSE_ALREADY_OPEN:
pb_error = PB_CommandStatus_ERROR_STORAGE_ALREADY_OPEN;
break;
case FSE_DENIED:
pb_error = PB_CommandStatus_ERROR_STORAGE_DENIED;
break;
case FSE_EXIST:
pb_error = PB_CommandStatus_ERROR_STORAGE_EXIST;
break;
case FSE_NOT_EXIST:
pb_error = PB_CommandStatus_ERROR_STORAGE_NOT_EXIST;
break;
case FSE_NOT_READY:
pb_error = PB_CommandStatus_ERROR_STORAGE_NOT_READY;
break;
case FSE_NOT_IMPLEMENTED:
pb_error = PB_CommandStatus_ERROR_STORAGE_NOT_IMPLEMENTED;
break;
default:
pb_error = PB_CommandStatus_ERROR;
break;
}
return pb_error;
}
static PB_CommandStatus rpc_system_storage_get_file_error(File* file) {
return rpc_system_storage_get_error(storage_file_get_error(file));
}
static void rpc_system_storage_info_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_storage_info_request_tag);
FURI_LOG_D(TAG, "Info");
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
rpc_system_storage_reset_state(rpc_storage, session, true);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
Storage* fs_api = furi_record_open(RECORD_STORAGE);
FS_Error error = storage_common_fs_info(
fs_api,
request->content.storage_info_request.path,
&response->content.storage_info_response.total_space,
&response->content.storage_info_response.free_space);
response->command_status = rpc_system_storage_get_error(error);
if(error == FSE_OK) {
response->which_content = PB_Main_storage_info_response_tag;
} else {
response->which_content = PB_Main_empty_tag;
}
rpc_send_and_release(session, response);
free(response);
furi_record_close(RECORD_STORAGE);
}
static void rpc_system_storage_stat_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_storage_stat_request_tag);
FURI_LOG_D(TAG, "Stat");
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
rpc_system_storage_reset_state(rpc_storage, session, true);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
Storage* fs_api = furi_record_open(RECORD_STORAGE);
const char* path = request->content.storage_stat_request.path;
FileInfo fileinfo;
FS_Error error = storage_common_stat(fs_api, path, &fileinfo);
response->command_status = rpc_system_storage_get_error(error);
response->which_content = PB_Main_empty_tag;
if(error == FSE_OK) {
response->which_content = PB_Main_storage_stat_response_tag;
response->content.storage_stat_response.has_file = true;
response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ?
PB_Storage_File_FileType_DIR :
PB_Storage_File_FileType_FILE;
response->content.storage_stat_response.file.size = fileinfo.size;
}
rpc_send_and_release(session, response);
free(response);
furi_record_close(RECORD_STORAGE);
}
static void rpc_system_storage_list_root(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
const char* hard_coded_dirs[] = {"any", "int", "ext"};
PB_Main response = {
.has_next = false,
.command_id = request->command_id,
.command_status = PB_CommandStatus_OK,
.which_content = PB_Main_storage_list_response_tag,
};
furi_assert(COUNT_OF(hard_coded_dirs) < COUNT_OF(response.content.storage_list_response.file));
for(uint32_t i = 0; i < COUNT_OF(hard_coded_dirs); ++i) {
++response.content.storage_list_response.file_count;
response.content.storage_list_response.file[i].data = NULL;
response.content.storage_list_response.file[i].size = 0;
response.content.storage_list_response.file[i].type = PB_Storage_File_FileType_DIR;
char* str = malloc(strlen(hard_coded_dirs[i]) + 1);
strcpy(str, hard_coded_dirs[i]);
response.content.storage_list_response.file[i].name = str;
}
rpc_send_and_release(session, &response);
}
static void rpc_system_storage_list_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_storage_list_request_tag);
FURI_LOG_D(TAG, "List");
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
rpc_system_storage_reset_state(rpc_storage, session, true);
if(!strcmp(request->content.storage_list_request.path, "/")) {
rpc_system_storage_list_root(request, context);
return;
}
Storage* fs_api = furi_record_open(RECORD_STORAGE);
File* dir = storage_file_alloc(fs_api);
PB_Main response = {
.command_id = request->command_id,
.has_next = false,
.which_content = PB_Main_storage_list_response_tag,
.command_status = PB_CommandStatus_OK,
};
PB_Storage_ListResponse* list = &response.content.storage_list_response;
bool finish = false;
int i = 0;
if(!storage_dir_open(dir, request->content.storage_list_request.path)) {
response.command_status = rpc_system_storage_get_file_error(dir);
response.which_content = PB_Main_empty_tag;
finish = true;
}
while(!finish) {
FileInfo fileinfo;
char* name = malloc(MAX_NAME_LENGTH + 1);
if(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
if(path_contains_only_ascii(name)) {
if(i == COUNT_OF(list->file)) {
list->file_count = i;
response.has_next = true;
rpc_send_and_release(session, &response);
i = 0;
}
list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ?
PB_Storage_File_FileType_DIR :
PB_Storage_File_FileType_FILE;
list->file[i].size = fileinfo.size;
list->file[i].data = NULL;
list->file[i].name = name;
++i;
} else {
free(name);
}
} else {
list->file_count = i;
finish = true;
free(name);
}
}
response.has_next = false;
rpc_send_and_release(session, &response);
storage_dir_close(dir);
storage_file_free(dir);
furi_record_close(RECORD_STORAGE);
}
static void rpc_system_storage_read_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_storage_read_request_tag);
FURI_LOG_D(TAG, "Read");
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
rpc_system_storage_reset_state(rpc_storage, session, true);
/* use same message memory to send reponse */
PB_Main* response = malloc(sizeof(PB_Main));
const char* path = request->content.storage_read_request.path;
Storage* fs_api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(fs_api);
bool fs_operation_success = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING);
if(fs_operation_success) {
size_t size_left = storage_file_size(file);
do {
response->command_id = request->command_id;
response->which_content = PB_Main_storage_read_response_tag;
response->command_status = PB_CommandStatus_OK;
size_t read_size = MIN(size_left, MAX_DATA_SIZE);
if(read_size) {
response->content.storage_read_response.has_file = true;
response->content.storage_read_response.file.data =
malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(read_size));
uint8_t* buffer = &response->content.storage_read_response.file.data->bytes[0];
uint16_t* read_size_msg = &response->content.storage_read_response.file.data->size;
*read_size_msg = storage_file_read(file, buffer, read_size);
size_left -= *read_size_msg;
fs_operation_success = (*read_size_msg == read_size);
response->has_next = fs_operation_success && (size_left > 0);
} else {
response->content.storage_read_response.file.data =
malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(0));
response->content.storage_read_response.file.data->size = 0;
response->content.storage_read_response.has_file = true;
response->has_next = false;
fs_operation_success = true;
}
if(fs_operation_success) {
rpc_send_and_release(session, response);
}
} while((size_left != 0) && fs_operation_success);
}
if(!fs_operation_success) {
rpc_send_and_release_empty(
session, request->command_id, rpc_system_storage_get_file_error(file));
}
free(response);
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
static void rpc_system_storage_write_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_storage_write_request_tag);
FURI_LOG_D(TAG, "Write");
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
bool fs_operation_success = true;
if(!path_contains_only_ascii(request->content.storage_write_request.path)) {
rpc_storage->current_command_id = request->command_id;
rpc_send_and_release_empty(
session, rpc_storage->current_command_id, PB_CommandStatus_ERROR_STORAGE_INVALID_NAME);
rpc_system_storage_reset_state(rpc_storage, session, false);
return;
}
if((request->command_id != rpc_storage->current_command_id) &&
(rpc_storage->state == RpcStorageStateWriting)) {
rpc_system_storage_reset_state(rpc_storage, session, true);
}
if(rpc_storage->state != RpcStorageStateWriting) {
rpc_storage->api = furi_record_open(RECORD_STORAGE);
rpc_storage->file = storage_file_alloc(rpc_storage->api);
rpc_storage->current_command_id = request->command_id;
rpc_storage->state = RpcStorageStateWriting;
const char* path = request->content.storage_write_request.path;
fs_operation_success =
storage_file_open(rpc_storage->file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS);
}
File* file = rpc_storage->file;
bool send_response = false;
if(fs_operation_success) {
if(request->content.storage_write_request.has_file &&
request->content.storage_write_request.file.data &&
request->content.storage_write_request.file.data->size) {
uint8_t* buffer = request->content.storage_write_request.file.data->bytes;
size_t buffer_size = request->content.storage_write_request.file.data->size;
uint16_t written_size = storage_file_write(file, buffer, buffer_size);
fs_operation_success = (written_size == buffer_size);
}
send_response = !request->has_next;
}
PB_CommandStatus command_status = PB_CommandStatus_OK;
if(!fs_operation_success) {
send_response = true;
command_status = rpc_system_storage_get_file_error(file);
}
if(send_response) {
rpc_send_and_release_empty(session, rpc_storage->current_command_id, command_status);
rpc_system_storage_reset_state(rpc_storage, session, false);
}
}
static bool rpc_system_storage_is_dir_is_empty(Storage* fs_api, const char* path) {
furi_assert(fs_api);
furi_assert(path);
FileInfo fileinfo;
bool is_dir_is_empty = true;
FS_Error error = storage_common_stat(fs_api, path, &fileinfo);
if((error == FSE_OK) && (fileinfo.flags & FSF_DIRECTORY)) {
File* dir = storage_file_alloc(fs_api);
if(storage_dir_open(dir, path)) {
char* name = malloc(MAX_NAME_LENGTH);
while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
if(path_contains_only_ascii(name)) {
is_dir_is_empty = false;
break;
}
}
free(name);
}
storage_dir_close(dir);
storage_file_free(dir);
}
return is_dir_is_empty;
}
static void rpc_system_storage_delete_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_storage_delete_request_tag);
furi_assert(context);
FURI_LOG_D(TAG, "Delete");
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
PB_CommandStatus status = PB_CommandStatus_ERROR;
rpc_system_storage_reset_state(rpc_storage, session, true);
Storage* fs_api = furi_record_open(RECORD_STORAGE);
char* path = request->content.storage_delete_request.path;
if(!path) {
status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
} else {
FS_Error error_remove = storage_common_remove(fs_api, path);
// FSE_DENIED is for empty directory, but not only for this
// that's why we have to check it
if((error_remove == FSE_DENIED) && !rpc_system_storage_is_dir_is_empty(fs_api, path)) {
if(request->content.storage_delete_request.recursive) {
bool deleted = storage_simply_remove_recursive(fs_api, path);
status = deleted ? PB_CommandStatus_OK : PB_CommandStatus_ERROR;
} else {
status = PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY;
}
} else if(error_remove == FSE_NOT_EXIST) {
status = PB_CommandStatus_OK;
} else {
status = rpc_system_storage_get_error(error_remove);
}
}
furi_record_close(RECORD_STORAGE);
rpc_send_and_release_empty(session, request->command_id, status);
}
static void rpc_system_storage_mkdir_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_storage_mkdir_request_tag);
furi_assert(context);
FURI_LOG_D(TAG, "Mkdir");
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
PB_CommandStatus status;
rpc_system_storage_reset_state(rpc_storage, session, true);
Storage* fs_api = furi_record_open(RECORD_STORAGE);
char* path = request->content.storage_mkdir_request.path;
if(path) {
if(path_contains_only_ascii(path)) {
FS_Error error = storage_common_mkdir(fs_api, path);
status = rpc_system_storage_get_error(error);
} else {
status = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME;
}
} else {
status = PB_CommandStatus_ERROR_INVALID_PARAMETERS;
}
furi_record_close(RECORD_STORAGE);
rpc_send_and_release_empty(session, request->command_id, status);
}
static void rpc_system_storage_md5sum_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_storage_md5sum_request_tag);
furi_assert(context);
FURI_LOG_D(TAG, "Md5sum");
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
rpc_system_storage_reset_state(rpc_storage, session, true);
const char* filename = request->content.storage_md5sum_request.path;
if(!filename) {
rpc_send_and_release_empty(
session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
return;
}
Storage* fs_api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(fs_api);
if(storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING)) {
const uint16_t size_to_read = 512;
const uint8_t hash_size = 16;
uint8_t* data = malloc(size_to_read);
uint8_t* hash = malloc(sizeof(uint8_t) * hash_size);
md5_context* md5_ctx = malloc(sizeof(md5_context));
md5_starts(md5_ctx);
while(true) {
uint16_t read_size = storage_file_read(file, data, size_to_read);
if(read_size == 0) break;
md5_update(md5_ctx, data, read_size);
}
md5_finish(md5_ctx, hash);
free(md5_ctx);
PB_Main response = {
.command_id = request->command_id,
.command_status = PB_CommandStatus_OK,
.which_content = PB_Main_storage_md5sum_response_tag,
.has_next = false,
};
char* md5sum = response.content.storage_md5sum_response.md5sum;
size_t md5sum_size = sizeof(response.content.storage_md5sum_response.md5sum);
(void)md5sum_size;
furi_assert(hash_size <= ((md5sum_size - 1) / 2));
for(uint8_t i = 0; i < hash_size; i++) {
md5sum += snprintf(md5sum, md5sum_size, "%02x", hash[i]);
}
free(hash);
free(data);
storage_file_close(file);
rpc_send_and_release(session, &response);
} else {
rpc_send_and_release_empty(
session, request->command_id, rpc_system_storage_get_file_error(file));
}
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
static void rpc_system_storage_rename_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_storage_rename_request_tag);
furi_assert(context);
FURI_LOG_D(TAG, "Rename");
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
PB_CommandStatus status;
rpc_system_storage_reset_state(rpc_storage, session, true);
Storage* fs_api = furi_record_open(RECORD_STORAGE);
if(path_contains_only_ascii(request->content.storage_rename_request.new_path)) {
FS_Error error = storage_common_rename(
fs_api,
request->content.storage_rename_request.old_path,
request->content.storage_rename_request.new_path);
status = rpc_system_storage_get_error(error);
} else {
status = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME;
}
furi_record_close(RECORD_STORAGE);
rpc_send_and_release_empty(session, request->command_id, status);
}
static void rpc_system_storage_backup_create_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_storage_backup_create_request_tag);
furi_assert(context);
FURI_LOG_D(TAG, "BackupCreate");
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
Storage* fs_api = furi_record_open(RECORD_STORAGE);
bool backup_ok =
lfs_backup_create(fs_api, request->content.storage_backup_create_request.archive_path);
furi_record_close(RECORD_STORAGE);
rpc_send_and_release_empty(
session, request->command_id, backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR);
}
static void rpc_system_storage_backup_restore_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_storage_backup_restore_request_tag);
furi_assert(context);
FURI_LOG_D(TAG, "BackupRestore");
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
Storage* fs_api = furi_record_open(RECORD_STORAGE);
bool backup_ok =
lfs_backup_unpack(fs_api, request->content.storage_backup_restore_request.archive_path);
furi_record_close(RECORD_STORAGE);
rpc_send_and_release_empty(
session, request->command_id, backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR);
}
void* rpc_system_storage_alloc(RpcSession* session) {
furi_assert(session);
RpcStorageSystem* rpc_storage = malloc(sizeof(RpcStorageSystem));
rpc_storage->api = furi_record_open(RECORD_STORAGE);
rpc_storage->session = session;
rpc_storage->state = RpcStorageStateIdle;
RpcHandler rpc_handler = {
.message_handler = NULL,
.decode_submessage = NULL,
.context = rpc_storage,
};
rpc_handler.message_handler = rpc_system_storage_info_process;
rpc_add_handler(session, PB_Main_storage_info_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_storage_stat_process;
rpc_add_handler(session, PB_Main_storage_stat_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_storage_list_process;
rpc_add_handler(session, PB_Main_storage_list_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_storage_read_process;
rpc_add_handler(session, PB_Main_storage_read_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_storage_write_process;
rpc_add_handler(session, PB_Main_storage_write_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_storage_delete_process;
rpc_add_handler(session, PB_Main_storage_delete_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_storage_mkdir_process;
rpc_add_handler(session, PB_Main_storage_mkdir_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_storage_md5sum_process;
rpc_add_handler(session, PB_Main_storage_md5sum_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_storage_rename_process;
rpc_add_handler(session, PB_Main_storage_rename_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_storage_backup_create_process;
rpc_add_handler(session, PB_Main_storage_backup_create_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_storage_backup_restore_process;
rpc_add_handler(session, PB_Main_storage_backup_restore_request_tag, &rpc_handler);
return rpc_storage;
}
void rpc_system_storage_free(void* context) {
furi_assert(context);
RpcStorageSystem* rpc_storage = context;
RpcSession* session = rpc_storage->session;
furi_assert(session);
rpc_system_storage_reset_state(rpc_storage, session, false);
free(rpc_storage);
}

View File

@@ -0,0 +1,343 @@
#include <flipper.pb.h>
#include <furi_hal.h>
#include <power/power_service/power.h>
#include <notification/notification_messages.h>
#include <protobuf_version.h>
#include <update_util/update_operation.h>
#include "rpc_i.h"
#define TAG "RpcSystem"
typedef struct {
RpcSession* session;
PB_Main* response;
} RpcSystemContext;
static void rpc_system_system_ping_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_system_ping_request_tag);
FURI_LOG_D(TAG, "Ping");
RpcSession* session = (RpcSession*)context;
furi_assert(session);
if(request->has_next) {
rpc_send_and_release_empty(
session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
return;
}
PB_Main response = PB_Main_init_default;
response.has_next = false;
response.command_status = PB_CommandStatus_OK;
response.command_id = request->command_id;
response.which_content = PB_Main_system_ping_response_tag;
const PB_System_PingRequest* ping_request = &request->content.system_ping_request;
PB_System_PingResponse* ping_response = &response.content.system_ping_response;
if(ping_request->data && (ping_request->data->size > 0)) {
ping_response->data = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(ping_request->data->size));
memcpy(ping_response->data->bytes, ping_request->data->bytes, ping_request->data->size);
ping_response->data->size = ping_request->data->size;
}
rpc_send_and_release(session, &response);
}
static void rpc_system_system_reboot_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_system_reboot_request_tag);
FURI_LOG_D(TAG, "Reboot");
RpcSession* session = (RpcSession*)context;
furi_assert(session);
const int mode = request->content.system_reboot_request.mode;
if(mode == PB_System_RebootRequest_RebootMode_OS) {
power_reboot(PowerBootModeNormal);
} else if(mode == PB_System_RebootRequest_RebootMode_DFU) {
power_reboot(PowerBootModeDfu);
} else if(mode == PB_System_RebootRequest_RebootMode_UPDATE) {
power_reboot(PowerBootModeUpdateStart);
} else {
rpc_send_and_release_empty(
session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
}
}
static void rpc_system_system_device_info_callback(
const char* key,
const char* value,
bool last,
void* context) {
furi_assert(key);
furi_assert(value);
RpcSystemContext* ctx = context;
furi_assert(ctx);
furi_assert(key);
furi_assert(value);
char* str_key = strdup(key);
char* str_value = strdup(value);
ctx->response->has_next = !last;
ctx->response->content.system_device_info_response.key = str_key;
ctx->response->content.system_device_info_response.value = str_value;
rpc_send_and_release(ctx->session, ctx->response);
}
static void rpc_system_system_device_info_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_system_device_info_request_tag);
FURI_LOG_D(TAG, "DeviceInfo");
RpcSession* session = (RpcSession*)context;
furi_assert(session);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->which_content = PB_Main_system_device_info_response_tag;
response->command_status = PB_CommandStatus_OK;
RpcSystemContext device_info_context = {
.session = session,
.response = response,
};
furi_hal_info_get(rpc_system_system_device_info_callback, &device_info_context);
free(response);
}
static void rpc_system_system_get_datetime_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_system_get_datetime_request_tag);
FURI_LOG_D(TAG, "GetDatetime");
RpcSession* session = (RpcSession*)context;
furi_assert(session);
FuriHalRtcDateTime datetime;
furi_hal_rtc_get_datetime(&datetime);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->which_content = PB_Main_system_get_datetime_response_tag;
response->command_status = PB_CommandStatus_OK;
response->content.system_get_datetime_response.has_datetime = true;
response->content.system_get_datetime_response.datetime.hour = datetime.hour;
response->content.system_get_datetime_response.datetime.minute = datetime.minute;
response->content.system_get_datetime_response.datetime.second = datetime.second;
response->content.system_get_datetime_response.datetime.day = datetime.day;
response->content.system_get_datetime_response.datetime.month = datetime.month;
response->content.system_get_datetime_response.datetime.year = datetime.year;
response->content.system_get_datetime_response.datetime.weekday = datetime.weekday;
rpc_send_and_release(session, response);
free(response);
}
static void rpc_system_system_set_datetime_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_system_set_datetime_request_tag);
FURI_LOG_D(TAG, "SetDatetime");
RpcSession* session = (RpcSession*)context;
furi_assert(session);
if(!request->content.system_set_datetime_request.has_datetime) {
rpc_send_and_release_empty(
session, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS);
return;
}
FuriHalRtcDateTime datetime;
datetime.hour = request->content.system_set_datetime_request.datetime.hour;
datetime.minute = request->content.system_set_datetime_request.datetime.minute;
datetime.second = request->content.system_set_datetime_request.datetime.second;
datetime.day = request->content.system_set_datetime_request.datetime.day;
datetime.month = request->content.system_set_datetime_request.datetime.month;
datetime.year = request->content.system_set_datetime_request.datetime.year;
datetime.weekday = request->content.system_set_datetime_request.datetime.weekday;
furi_hal_rtc_set_datetime(&datetime);
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}
static void rpc_system_system_factory_reset_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_system_factory_reset_request_tag);
FURI_LOG_D(TAG, "Reset");
RpcSession* session = (RpcSession*)context;
furi_assert(session);
furi_hal_rtc_set_flag(FuriHalRtcFlagFactoryReset);
power_reboot(PowerBootModeNormal);
(void)session;
}
static void
rpc_system_system_play_audiovisual_alert_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_system_play_audiovisual_alert_request_tag);
FURI_LOG_D(TAG, "Alert");
RpcSession* session = (RpcSession*)context;
furi_assert(session);
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message(notification, &sequence_audiovisual_alert);
furi_record_close(RECORD_NOTIFICATION);
rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK);
}
static void rpc_system_system_protobuf_version_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_system_protobuf_version_request_tag);
FURI_LOG_D(TAG, "ProtobufVersion");
RpcSession* session = (RpcSession*)context;
furi_assert(session);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->has_next = false;
response->command_status = PB_CommandStatus_OK;
response->which_content = PB_Main_system_protobuf_version_response_tag;
/* build error here means something wrong with tags in
* local repo https://github.com/flipperdevices/flipperzero-protobuf */
response->content.system_protobuf_version_response.major = PROTOBUF_MAJOR_VERSION;
response->content.system_protobuf_version_response.minor = PROTOBUF_MINOR_VERSION;
rpc_send_and_release(session, response);
free(response);
}
static void rpc_system_system_power_info_callback(
const char* key,
const char* value,
bool last,
void* context) {
furi_assert(key);
furi_assert(value);
RpcSystemContext* ctx = context;
furi_assert(ctx);
furi_assert(key);
furi_assert(value);
char* str_key = strdup(key);
char* str_value = strdup(value);
ctx->response->has_next = !last;
ctx->response->content.system_device_info_response.key = str_key;
ctx->response->content.system_device_info_response.value = str_value;
rpc_send_and_release(ctx->session, ctx->response);
}
static void rpc_system_system_get_power_info_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_system_power_info_request_tag);
FURI_LOG_D(TAG, "GetPowerInfo");
RpcSession* session = (RpcSession*)context;
furi_assert(session);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->which_content = PB_Main_system_power_info_response_tag;
response->command_status = PB_CommandStatus_OK;
RpcSystemContext power_info_context = {
.session = session,
.response = response,
};
furi_hal_power_info_get(rpc_system_system_power_info_callback, &power_info_context);
free(response);
}
#ifdef APP_UPDATER
static void rpc_system_system_update_request_process(const PB_Main* request, void* context) {
furi_assert(request);
furi_assert(request->which_content == PB_Main_system_update_request_tag);
FURI_LOG_D(TAG, "SystemUpdate");
RpcSession* session = (RpcSession*)context;
furi_assert(session);
UpdatePrepareResult update_prepare_result =
update_operation_prepare(request->content.system_update_request.update_manifest);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->has_next = false;
response->command_status = (update_prepare_result == UpdatePrepareResultOK) ?
PB_CommandStatus_OK :
PB_CommandStatus_ERROR_INVALID_PARAMETERS;
response->which_content = PB_Main_system_update_response_tag;
response->content.system_update_response.code =
(PB_System_UpdateResponse_UpdateResultCode)update_prepare_result;
rpc_send_and_release(session, response);
free(response);
}
#endif
void* rpc_system_system_alloc(RpcSession* session) {
furi_assert(session);
RpcHandler rpc_handler = {
.message_handler = NULL,
.decode_submessage = NULL,
.context = session,
};
rpc_handler.message_handler = rpc_system_system_ping_process;
rpc_add_handler(session, PB_Main_system_ping_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_system_reboot_process;
rpc_add_handler(session, PB_Main_system_reboot_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_system_device_info_process;
rpc_add_handler(session, PB_Main_system_device_info_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_system_factory_reset_process;
rpc_add_handler(session, PB_Main_system_factory_reset_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_system_get_datetime_process;
rpc_add_handler(session, PB_Main_system_get_datetime_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_system_set_datetime_process;
rpc_add_handler(session, PB_Main_system_set_datetime_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_system_play_audiovisual_alert_process;
rpc_add_handler(session, PB_Main_system_play_audiovisual_alert_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_system_protobuf_version_process;
rpc_add_handler(session, PB_Main_system_protobuf_version_request_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_system_get_power_info_process;
rpc_add_handler(session, PB_Main_system_power_info_request_tag, &rpc_handler);
#ifdef APP_UPDATER
rpc_handler.message_handler = rpc_system_system_update_request_process;
rpc_add_handler(session, PB_Main_system_update_request_tag, &rpc_handler);
#endif
return NULL;
}