[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:
9
applications/services/rpc/application.fam
Normal file
9
applications/services/rpc/application.fam
Normal 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"],
|
||||
)
|
463
applications/services/rpc/rpc.c
Normal file
463
applications/services/rpc/rpc.c
Normal 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);
|
||||
}
|
124
applications/services/rpc/rpc.h
Normal file
124
applications/services/rpc/rpc.h
Normal 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
|
318
applications/services/rpc/rpc_app.c
Normal file
318
applications/services/rpc/rpc_app.c
Normal 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);
|
||||
}
|
32
applications/services/rpc/rpc_app.h
Normal file
32
applications/services/rpc/rpc_app.h
Normal 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
|
89
applications/services/rpc/rpc_cli.c
Normal file
89
applications/services/rpc/rpc_cli.c
Normal 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();
|
||||
}
|
260
applications/services/rpc/rpc_debug.c
Normal file
260
applications/services/rpc/rpc_debug.c
Normal 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);
|
||||
}
|
216
applications/services/rpc/rpc_gpio.c
Normal file
216
applications/services/rpc/rpc_gpio.c
Normal 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;
|
||||
}
|
379
applications/services/rpc/rpc_gui.c
Normal file
379
applications/services/rpc/rpc_gui.c
Normal 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);
|
||||
}
|
43
applications/services/rpc/rpc_i.h
Normal file
43
applications/services/rpc/rpc_i.h
Normal 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);
|
712
applications/services/rpc/rpc_storage.c
Normal file
712
applications/services/rpc/rpc_storage.c
Normal 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);
|
||||
}
|
343
applications/services/rpc/rpc_system.c
Normal file
343
applications/services/rpc/rpc_system.c
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user