ソースを参照

[FL-2589] RPC App control commands (#1350)

* RPC App control commands
* Button release timeout
* SubGhz tx fix

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
Nikolay Minaylov 3 年 前
コミット
4a1695ba1c

+ 0 - 2
applications/gpio/usb_uart_bridge.c

@@ -85,7 +85,6 @@ static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
 
 static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
     furi_hal_usb_unlock();
-    FURI_LOG_I("", "Init %d", vcp_ch);
     if(vcp_ch == 0) {
         Cli* cli = furi_record_open("cli");
         cli_session_close(cli);
@@ -103,7 +102,6 @@ static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
 static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
     UNUSED(usb_uart);
     furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL);
-    FURI_LOG_I("", "Deinit %d", vcp_ch);
     if(vcp_ch != 0) {
         Cli* cli = furi_record_open("cli");
         cli_session_close(cli);

+ 52 - 8
applications/ibutton/ibutton.c

@@ -5,6 +5,9 @@
 #include "m-string.h"
 #include <toolbox/path.h>
 #include <flipper_format/flipper_format.h>
+#include "rpc/rpc_app.h"
+
+#define TAG "iButtonApp"
 
 static const NotificationSequence sequence_blink_start_cyan = {
     &message_blink_start_10,
@@ -55,7 +58,7 @@ static void ibutton_make_app_folder(iButton* ibutton) {
     }
 }
 
-static bool ibutton_load_key_data(iButton* ibutton, string_t key_path) {
+static bool ibutton_load_key_data(iButton* ibutton, string_t key_path, bool show_dialog) {
     FlipperFormat* file = flipper_format_file_alloc(ibutton->storage);
     bool result = false;
     string_t data;
@@ -89,13 +92,40 @@ static bool ibutton_load_key_data(iButton* ibutton, string_t key_path) {
     flipper_format_free(file);
     string_clear(data);
 
-    if(!result) {
+    if((!result) && (show_dialog)) {
         dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
     }
 
     return result;
 }
 
+static bool ibutton_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
+    furi_assert(context);
+    iButton* ibutton = context;
+
+    bool result = false;
+
+    if(event == RpcAppEventSessionClose) {
+        rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
+        ibutton->rpc_ctx = NULL;
+        view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
+        result = true;
+    } else if(event == RpcAppEventAppExit) {
+        view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
+        result = true;
+    } else if(event == RpcAppEventLoadFile) {
+        if(arg) {
+            string_set_str(ibutton->file_path, arg);
+            if(ibutton_load_key_data(ibutton, ibutton->file_path, false)) {
+                ibutton_worker_emulate_start(ibutton->key_worker, ibutton->key);
+                result = true;
+            }
+        }
+    }
+
+    return result;
+}
+
 bool ibutton_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);
     iButton* ibutton = context;
@@ -226,7 +256,7 @@ bool ibutton_file_select(iButton* ibutton) {
         true);
 
     if(success) {
-        success = ibutton_load_key_data(ibutton, ibutton->file_path);
+        success = ibutton_load_key_data(ibutton, ibutton->file_path, true);
     }
 
     return success;
@@ -334,16 +364,27 @@ int32_t ibutton_app(void* p) {
     ibutton_make_app_folder(ibutton);
 
     bool key_loaded = false;
+    bool rpc_mode = false;
 
     if(p) {
-        string_set_str(ibutton->file_path, (const char*)p);
-        if(ibutton_load_key_data(ibutton, ibutton->file_path)) {
-            key_loaded = true;
-            // TODO: Display an error if the key from p could not be loaded
+        uint32_t rpc_ctx = 0;
+        if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
+            FURI_LOG_D(TAG, "Running in RPC mode");
+            ibutton->rpc_ctx = (void*)rpc_ctx;
+            rpc_mode = true;
+            rpc_system_app_set_callback(ibutton->rpc_ctx, ibutton_rpc_command_callback, ibutton);
+        } else {
+            string_set_str(ibutton->file_path, (const char*)p);
+            if(ibutton_load_key_data(ibutton, ibutton->file_path, true)) {
+                key_loaded = true;
+                // TODO: Display an error if the key from p could not be loaded
+            }
         }
     }
 
-    if(key_loaded) {
+    if(rpc_mode) {
+        scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRpc);
+    } else if(key_loaded) {
         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
     } else {
         scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart);
@@ -351,6 +392,9 @@ int32_t ibutton_app(void* p) {
 
     view_dispatcher_run(ibutton->view_dispatcher);
 
+    if(ibutton->rpc_ctx) {
+        rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
+    }
     ibutton_free(ibutton);
     return 0;
 }

+ 2 - 0
applications/ibutton/ibutton_custom_event.h

@@ -9,4 +9,6 @@ enum iButtonCustomEvent {
     iButtonCustomEventByteEditResult,
     iButtonCustomEventWorkerEmulated,
     iButtonCustomEventWorkerRead,
+
+    iButtonCustomEventRpcExit,
 };

+ 2 - 0
applications/ibutton/ibutton_i.h

@@ -50,6 +50,8 @@ struct iButton {
     Popup* popup;
     Widget* widget;
     DialogEx* dialog_ex;
+
+    void* rpc_ctx;
 };
 
 typedef enum {

+ 1 - 0
applications/ibutton/scenes/ibutton_scene_config.h

@@ -18,3 +18,4 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm)
 ADD_SCENE(ibutton, delete_success, DeleteSuccess)
 ADD_SCENE(ibutton, retry_confirm, RetryConfirm)
 ADD_SCENE(ibutton, exit_confirm, ExitConfirm)
+ADD_SCENE(ibutton, rpc, Rpc)

+ 36 - 0
applications/ibutton/scenes/ibutton_scene_rpc.c

@@ -0,0 +1,36 @@
+#include "../ibutton_i.h"
+#include <toolbox/path.h>
+
+void ibutton_scene_rpc_on_enter(void* context) {
+    iButton* ibutton = context;
+    Widget* widget = ibutton->widget;
+
+    widget_add_text_box_element(
+        widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false);
+
+    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
+
+    notification_message(ibutton->notifications, &sequence_display_backlight_on);
+}
+
+bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    iButton* ibutton = context;
+
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        if(event.event == iButtonCustomEventRpcExit) {
+            view_dispatcher_stop(ibutton->view_dispatcher);
+        }
+    }
+
+    return consumed;
+}
+
+void ibutton_scene_rpc_on_exit(void* context) {
+    iButton* ibutton = context;
+    widget_reset(ibutton->widget);
+}

+ 69 - 7
applications/infrared/infrared.c

@@ -36,6 +36,52 @@ static void infrared_tick_event_callback(void* context) {
     scene_manager_handle_tick_event(infrared->scene_manager);
 }
 
+static bool
+    infrared_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
+    furi_assert(context);
+    Infrared* infrared = context;
+
+    if(!infrared->rpc_ctx) {
+        return false;
+    }
+
+    bool result = false;
+
+    if(event == RpcAppEventSessionClose) {
+        rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL);
+        infrared->rpc_ctx = NULL;
+        view_dispatcher_send_custom_event(
+            infrared->view_dispatcher, InfraredCustomEventTypeBackPressed);
+        result = true;
+    } else if(event == RpcAppEventAppExit) {
+        view_dispatcher_send_custom_event(
+            infrared->view_dispatcher, InfraredCustomEventTypeBackPressed);
+        result = true;
+    } else if(event == RpcAppEventLoadFile) {
+        if(arg) {
+            string_set_str(infrared->file_path, arg);
+            result = infrared_remote_load(infrared->remote, infrared->file_path);
+            infrared_worker_tx_set_get_signal_callback(
+                infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared);
+            infrared_worker_tx_set_signal_sent_callback(
+                infrared->worker, infrared_signal_sent_callback, infrared);
+        }
+    } else if(event == RpcAppEventButtonPress) {
+        if(arg) {
+            size_t button_index = 0;
+            if(infrared_remote_find_button_by_name(infrared->remote, arg, &button_index)) {
+                infrared_tx_start_button_index(infrared, button_index);
+                result = true;
+            }
+        }
+    } else if(event == RpcAppEventButtonRelease) {
+        infrared_tx_stop(infrared);
+        result = true;
+    }
+
+    return result;
+}
+
 static void infrared_find_vacant_remote_name(string_t name, const char* path) {
     Storage* storage = furi_record_open("storage");
 
@@ -154,6 +200,11 @@ static void infrared_free(Infrared* infrared) {
     ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
     InfraredAppState* app_state = &infrared->app_state;
 
+    if(infrared->rpc_ctx) {
+        rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL);
+        infrared->rpc_ctx = NULL;
+    }
+
     view_dispatcher_remove_view(view_dispatcher, InfraredViewSubmenu);
     submenu_free(infrared->submenu);
 
@@ -375,18 +426,29 @@ int32_t infrared_app(void* p) {
     infrared_make_app_folder(infrared);
 
     bool is_remote_loaded = false;
+    bool is_rpc_mode = false;
 
     if(p) {
-        string_set_str(infrared->file_path, (const char*)p);
-        is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path);
-        if(!is_remote_loaded) {
-            dialog_message_show_storage_error(
-                infrared->dialogs, "Failed to load\nselected remote");
-            return -1;
+        uint32_t rpc_ctx = 0;
+        if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
+            infrared->rpc_ctx = (void*)rpc_ctx;
+            rpc_system_app_set_callback(
+                infrared->rpc_ctx, infrared_rpc_command_callback, infrared);
+            is_rpc_mode = true;
+        } else {
+            string_set_str(infrared->file_path, (const char*)p);
+            is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path);
+            if(!is_remote_loaded) {
+                dialog_message_show_storage_error(
+                    infrared->dialogs, "Failed to load\nselected remote");
+                return -1;
+            }
         }
     }
 
-    if(is_remote_loaded) {
+    if(is_rpc_mode) {
+        scene_manager_next_scene(infrared->scene_manager, InfraredSceneRpc);
+    } else if(is_remote_loaded) {
         scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
     } else {
         scene_manager_next_scene(infrared->scene_manager, InfraredSceneStart);

+ 4 - 0
applications/infrared/infrared_i.h

@@ -30,6 +30,8 @@
 #include "views/infrared_progress_view.h"
 #include "views/infrared_debug_view.h"
 
+#include "rpc/rpc_app.h"
+
 #define INFRARED_FILE_NAME_SIZE 100
 #define INFRARED_TEXT_STORE_NUM 2
 #define INFRARED_TEXT_STORE_SIZE 128
@@ -95,6 +97,8 @@ struct Infrared {
     string_t file_path;
     char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
     InfraredAppState app_state;
+
+    void* rpc_ctx;
 };
 
 typedef enum {

+ 13 - 0
applications/infrared/infrared_remote.c

@@ -1,5 +1,7 @@
 #include "infrared_remote.h"
 
+#include <stdbool.h>
+#include <stddef.h>
 #include <stdlib.h>
 #include <m-string.h>
 #include <m-array.h>
@@ -73,6 +75,17 @@ InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t
     return *InfraredButtonArray_get(remote->buttons, index);
 }
 
+bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index) {
+    for(size_t i = 0; i < InfraredButtonArray_size(remote->buttons); i++) {
+        InfraredRemoteButton* button = *InfraredButtonArray_get(remote->buttons, i);
+        if(!strcmp(infrared_remote_button_get_name(button), name)) {
+            *index = i;
+            return true;
+        }
+    }
+    return false;
+}
+
 bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) {
     InfraredRemoteButton* button = infrared_remote_button_alloc();
     infrared_remote_button_set_name(button, name);

+ 1 - 0
applications/infrared/infrared_remote.h

@@ -18,6 +18,7 @@ const char* infrared_remote_get_path(InfraredRemote* remote);
 
 size_t infrared_remote_get_button_count(InfraredRemote* remote);
 InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index);
+bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* name, size_t* index);
 
 bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal);
 bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index);

+ 1 - 0
applications/infrared/scenes/infrared_scene_config.h

@@ -16,3 +16,4 @@ ADD_SCENE(infrared, universal, Universal)
 ADD_SCENE(infrared, universal_tv, UniversalTV)
 ADD_SCENE(infrared, debug, Debug)
 ADD_SCENE(infrared, error_databases, ErrorDatabases)
+ADD_SCENE(infrared, rpc, Rpc)

+ 37 - 0
applications/infrared/scenes/infrared_scene_rpc.c

@@ -0,0 +1,37 @@
+#include "../infrared_i.h"
+#include "gui/canvas.h"
+
+void infrared_scene_rpc_on_enter(void* context) {
+    Infrared* infrared = context;
+    Popup* popup = infrared->popup;
+
+    popup_set_text(popup, "Rpc mode", 64, 28, AlignCenter, AlignCenter);
+
+    popup_set_context(popup, context);
+    popup_set_callback(popup, infrared_popup_closed_callback);
+
+    infrared_play_notification_message(infrared, InfraredNotificationMessageYellowOn);
+    view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
+
+    notification_message(infrared->notifications, &sequence_display_backlight_on);
+}
+
+bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
+    Infrared* infrared = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        if(event.event == InfraredCustomEventTypeBackPressed) {
+            view_dispatcher_stop(infrared->view_dispatcher);
+        } else if(event.event == InfraredCustomEventTypePopupClosed) {
+            view_dispatcher_stop(infrared->view_dispatcher);
+        }
+    }
+    return consumed;
+}
+
+void infrared_scene_rpc_on_exit(void* context) {
+    Infrared* infrared = context;
+    popup_reset(infrared->popup);
+}

+ 56 - 7
applications/lfrfid/lfrfid_app.cpp

@@ -20,10 +20,13 @@
 #include "scene/lfrfid_app_scene_saved_info.h"
 #include "scene/lfrfid_app_scene_delete_confirm.h"
 #include "scene/lfrfid_app_scene_delete_success.h"
+#include "scene/lfrfid_app_scene_rpc.h"
 
 #include <toolbox/path.h>
 #include <flipper_format/flipper_format.h>
 
+#include "rpc/rpc_app.h"
+
 const char* LfRfidApp::app_folder = "/any/lfrfid";
 const char* LfRfidApp::app_extension = ".rfid";
 const char* LfRfidApp::app_filetype = "Flipper RFID key";
@@ -39,6 +42,43 @@ LfRfidApp::LfRfidApp()
 
 LfRfidApp::~LfRfidApp() {
     string_clear(file_path);
+    if(rpc_ctx) {
+        rpc_system_app_set_callback(rpc_ctx, NULL, NULL);
+    }
+}
+
+static bool rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
+    furi_assert(context);
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+
+    bool result = false;
+
+    if(event == RpcAppEventSessionClose) {
+        rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL);
+        app->rpc_ctx = NULL;
+        LfRfidApp::Event event;
+        event.type = LfRfidApp::EventType::Exit;
+        app->view_controller.send_event(&event);
+        result = true;
+    } else if(event == RpcAppEventAppExit) {
+        LfRfidApp::Event event;
+        event.type = LfRfidApp::EventType::Exit;
+        app->view_controller.send_event(&event);
+        result = true;
+    } else if(event == RpcAppEventLoadFile) {
+        if(arg) {
+            string_set_str(app->file_path, arg);
+            if(app->load_key_data(app->file_path, &(app->worker.key), false)) {
+                LfRfidApp::Event event;
+                event.type = LfRfidApp::EventType::EmulateStart;
+                app->view_controller.send_event(&event);
+                app->worker.start_emulate();
+                result = true;
+            }
+        }
+    }
+
+    return result;
 }
 
 void LfRfidApp::run(void* _args) {
@@ -47,10 +87,19 @@ void LfRfidApp::run(void* _args) {
     make_app_folder();
 
     if(strlen(args)) {
-        string_set_str(file_path, args);
-        load_key_data(file_path, &worker.key);
-        scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate());
-        scene_controller.process(100, SceneType::Emulate);
+        uint32_t rpc_ctx_ptr = 0;
+        if(sscanf(args, "RPC %lX", &rpc_ctx_ptr) == 1) {
+            rpc_ctx = (RpcAppSystem*)rpc_ctx_ptr;
+            rpc_system_app_set_callback(rpc_ctx, rpc_command_callback, this);
+            scene_controller.add_scene(SceneType::Rpc, new LfRfidAppSceneRpc());
+            scene_controller.process(100, SceneType::Rpc);
+        } else {
+            string_set_str(file_path, args);
+            load_key_data(file_path, &worker.key, true);
+            scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate());
+            scene_controller.process(100, SceneType::Emulate);
+        }
+
     } else {
         scene_controller.add_scene(SceneType::Start, new LfRfidAppSceneStart());
         scene_controller.add_scene(SceneType::Read, new LfRfidAppSceneRead());
@@ -99,7 +148,7 @@ bool LfRfidApp::load_key_from_file_select(bool need_restore) {
         dialogs, file_path, file_path, app_extension, true, &I_125_10px, true);
 
     if(result) {
-        result = load_key_data(file_path, &worker.key);
+        result = load_key_data(file_path, &worker.key, true);
     }
 
     return result;
@@ -110,7 +159,7 @@ bool LfRfidApp::delete_key(RfidKey* key) {
     return storage_simply_remove(storage, string_get_cstr(file_path));
 }
 
-bool LfRfidApp::load_key_data(string_t path, RfidKey* key) {
+bool LfRfidApp::load_key_data(string_t path, RfidKey* key, bool show_dialog) {
     FlipperFormat* file = flipper_format_file_alloc(storage);
     bool result = false;
     string_t str_result;
@@ -149,7 +198,7 @@ bool LfRfidApp::load_key_data(string_t path, RfidKey* key) {
     flipper_format_free(file);
     string_clear(str_result);
 
-    if(!result) {
+    if((!result) && (show_dialog)) {
         dialog_message_show_storage_error(dialogs, "Cannot load\nkey file");
     }
 

+ 8 - 1
applications/lfrfid/lfrfid_app.h

@@ -21,6 +21,7 @@
 #include <dialogs/dialogs.h>
 
 #include "helpers/rfid_worker.h"
+#include "rpc/rpc_app.h"
 
 class LfRfidApp {
 public:
@@ -30,6 +31,8 @@ public:
         MenuSelected,
         Stay,
         Retry,
+        Exit,
+        EmulateStart,
     };
 
     enum class SceneType : uint8_t {
@@ -51,6 +54,7 @@ public:
         SavedInfo,
         DeleteConfirm,
         DeleteSuccess,
+        Rpc,
     };
 
     class Event {
@@ -79,6 +83,8 @@ public:
 
     string_t file_path;
 
+    RpcAppSystem* rpc_ctx;
+
     void run(void* args);
 
     static const char* app_folder;
@@ -89,8 +95,9 @@ public:
     bool load_key_from_file_select(bool need_restore);
     bool delete_key(RfidKey* key);
 
-    bool load_key_data(string_t path, RfidKey* key);
+    bool load_key_data(string_t path, RfidKey* key, bool show_dialog);
     bool save_key_data(string_t path, RfidKey* key);
 
     void make_app_folder();
+    //bool rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context);
 };

+ 37 - 0
applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp

@@ -0,0 +1,37 @@
+#include "lfrfid_app_scene_rpc.h"
+#include "furi/common_defines.h"
+#include <dolphin/dolphin.h>
+
+void LfRfidAppSceneRpc::on_enter(LfRfidApp* app, bool /* need_restore */) {
+    auto popup = app->view_controller.get<PopupVM>();
+
+    popup->set_header("RPC Mode", 64, 30, AlignCenter, AlignTop);
+
+    app->view_controller.switch_to<PopupVM>();
+
+    notification_message(app->notification, &sequence_display_backlight_on);
+}
+
+bool LfRfidAppSceneRpc::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    UNUSED(app);
+    UNUSED(event);
+    bool consumed = false;
+
+    if(event->type == LfRfidApp::EventType::Exit) {
+        consumed = true;
+        LfRfidApp::Event view_event;
+        view_event.type = LfRfidApp::EventType::Back;
+        app->view_controller.send_event(&view_event);
+    } else if(event->type == LfRfidApp::EventType::EmulateStart) {
+        consumed = true;
+        emulating = true;
+    }
+    return consumed;
+}
+
+void LfRfidAppSceneRpc::on_exit(LfRfidApp* app) {
+    if(emulating) {
+        app->worker.stop_emulate();
+    }
+    app->view_controller.get<PopupVM>()->clean();
+}

+ 12 - 0
applications/lfrfid/scene/lfrfid_app_scene_rpc.h

@@ -0,0 +1,12 @@
+#pragma once
+#include "../lfrfid_app.h"
+
+class LfRfidAppSceneRpc : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    bool emulating = false;
+};

+ 77 - 1
applications/nfc/nfc.c

@@ -19,6 +19,77 @@ void nfc_tick_event_callback(void* context) {
     scene_manager_handle_tick_event(nfc->scene_manager);
 }
 
+void nfc_rpc_exit_callback(Nfc* nfc) {
+    if(nfc->rpc_state == NfcRpcStateEmulating) {
+        // Stop worker
+        nfc_worker_stop(nfc->worker);
+    } else if(nfc->rpc_state == NfcRpcStateEmulated) {
+        // Stop worker
+        nfc_worker_stop(nfc->worker);
+        // Save data in shadow file
+        nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name);
+    }
+    if(nfc->rpc_ctx) {
+        rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL);
+        nfc->rpc_ctx = NULL;
+    }
+}
+
+static void nfc_rpc_emulate_callback(NfcWorkerEvent event, void* context) {
+    UNUSED(event);
+    Nfc* nfc = context;
+
+    nfc->rpc_state = NfcRpcStateEmulated;
+}
+
+static bool nfc_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
+    furi_assert(context);
+    Nfc* nfc = context;
+
+    if(!nfc->rpc_ctx) {
+        return false;
+    }
+
+    bool result = false;
+
+    if(event == RpcAppEventSessionClose) {
+        rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL);
+        nfc->rpc_ctx = NULL;
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
+        result = true;
+    } else if(event == RpcAppEventAppExit) {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
+        result = true;
+    } else if(event == RpcAppEventLoadFile) {
+        if((arg) && (nfc->rpc_state == NfcRpcStateIdle)) {
+            if(nfc_device_load(nfc->dev, arg, false)) {
+                if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
+                    nfc_worker_start(
+                        nfc->worker,
+                        NfcWorkerStateEmulateMifareUltralight,
+                        &nfc->dev->dev_data,
+                        nfc_rpc_emulate_callback,
+                        nfc);
+                } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
+                    nfc_worker_start(
+                        nfc->worker,
+                        NfcWorkerStateEmulateMifareClassic,
+                        &nfc->dev->dev_data,
+                        nfc_rpc_emulate_callback,
+                        nfc);
+                } else {
+                    nfc_worker_start(
+                        nfc->worker, NfcWorkerStateEmulate, &nfc->dev->dev_data, NULL, nfc);
+                }
+                nfc->rpc_state = NfcRpcStateEmulating;
+                result = true;
+            }
+        }
+    }
+
+    return result;
+}
+
 Nfc* nfc_alloc() {
     Nfc* nfc = malloc(sizeof(Nfc));
 
@@ -193,7 +264,12 @@ int32_t nfc_app(void* p) {
 
     // Check argument and run corresponding scene
     if((*args != '\0')) {
-        if(nfc_device_load(nfc->dev, p)) {
+        uint32_t rpc_ctx = 0;
+        if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
+            nfc->rpc_ctx = (void*)rpc_ctx;
+            rpc_system_app_set_callback(nfc->rpc_ctx, nfc_rpc_command_callback, nfc);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneRpc);
+        } else if(nfc_device_load(nfc->dev, p, true)) {
             if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
             } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {

+ 6 - 6
applications/nfc/nfc_device.c

@@ -837,7 +837,7 @@ bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) {
     return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true);
 }
 
-static bool nfc_device_load_data(NfcDevice* dev, string_t path) {
+static bool nfc_device_load_data(NfcDevice* dev, string_t path, bool show_dialog) {
     bool parsed = false;
     FlipperFormat* file = flipper_format_file_alloc(dev->storage);
     FuriHalNfcDevData* data = &dev->dev_data.nfc_data;
@@ -887,7 +887,7 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) {
         parsed = true;
     } while(false);
 
-    if(!parsed) {
+    if((!parsed) && (show_dialog)) {
         if(deprecated_version) {
             dialog_message_show_storage_error(dev->dialogs, "File format deprecated");
         } else {
@@ -900,13 +900,13 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) {
     return parsed;
 }
 
-bool nfc_device_load(NfcDevice* dev, const char* file_path) {
+bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog) {
     furi_assert(dev);
     furi_assert(file_path);
 
     // Load device data
     string_set_str(dev->load_path, file_path);
-    bool dev_load = nfc_device_load_data(dev, dev->load_path);
+    bool dev_load = nfc_device_load_data(dev, dev->load_path, show_dialog);
     if(dev_load) {
         // Set device name
         string_t filename;
@@ -933,7 +933,7 @@ bool nfc_file_select(NfcDevice* dev) {
         string_init(filename);
         path_extract_filename(dev->load_path, filename, true);
         strncpy(dev->dev_name, string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN);
-        res = nfc_device_load_data(dev, dev->load_path);
+        res = nfc_device_load_data(dev, dev->load_path, true);
         if(res) {
             nfc_device_set_name(dev, dev->dev_name);
         }
@@ -1017,7 +1017,7 @@ bool nfc_device_restore(NfcDevice* dev, bool use_load_path) {
         } else {
             string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION);
         }
-        if(!nfc_device_load_data(dev, path)) break;
+        if(!nfc_device_load_data(dev, path, true)) break;
         restored = true;
     } while(0);
 

+ 1 - 1
applications/nfc/nfc_device.h

@@ -71,7 +71,7 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name);
 
 bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name);
 
-bool nfc_device_load(NfcDevice* dev, const char* file_path);
+bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog);
 
 bool nfc_file_select(NfcDevice* dev);
 

+ 13 - 0
applications/nfc/nfc_i.h

@@ -29,10 +29,18 @@
 #include <nfc/scenes/nfc_scene.h>
 #include <nfc/helpers/nfc_custom_event.h>
 
+#include "rpc/rpc_app.h"
+
 #define NFC_SEND_NOTIFICATION_FALSE (0UL)
 #define NFC_SEND_NOTIFICATION_TRUE (1UL)
 #define NFC_TEXT_STORE_SIZE 128
 
+typedef enum {
+    NfcRpcStateIdle,
+    NfcRpcStateEmulating,
+    NfcRpcStateEmulated,
+} NfcRpcState;
+
 // Forward declaration due to circular dependency
 typedef struct NfcGenerator NfcGenerator;
 
@@ -48,6 +56,9 @@ struct Nfc {
     char text_store[NFC_TEXT_STORE_SIZE + 1];
     string_t text_box_store;
 
+    void* rpc_ctx;
+    NfcRpcState rpc_state;
+
     // Common Views
     Submenu* submenu;
     DialogEx* dialog_ex;
@@ -85,3 +96,5 @@ void nfc_text_store_clear(Nfc* nfc);
 void nfc_blink_start(Nfc* nfc);
 
 void nfc_blink_stop(Nfc* nfc);
+
+void nfc_rpc_exit_callback(Nfc* nfc);

+ 1 - 0
applications/nfc/scenes/nfc_scene_config.h

@@ -37,4 +37,5 @@ ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic)
 ADD_SCENE(nfc, emulate_mifare_classic, EmulateMifareClassic)
 ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu)
 ADD_SCENE(nfc, dict_not_found, DictNotFound)
+ADD_SCENE(nfc, rpc, Rpc)
 ADD_SCENE(nfc, generate_info, GenerateInfo)

+ 34 - 0
applications/nfc/scenes/nfc_scene_rpc.c

@@ -0,0 +1,34 @@
+#include "../nfc_i.h"
+
+void nfc_scene_rpc_on_enter(void* context) {
+    Nfc* nfc = context;
+    Widget* widget = nfc->widget;
+
+    widget_add_text_box_element(
+        widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false);
+
+    notification_message(nfc->notifications, &sequence_display_backlight_on);
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
+}
+
+bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        if(event.event == NfcCustomEventViewExit) {
+            view_dispatcher_stop(nfc->view_dispatcher);
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_rpc_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    nfc_rpc_exit_callback(nfc);
+
+    widget_reset(nfc->widget);
+}

+ 1 - 1
applications/rpc/rpc.c

@@ -45,7 +45,7 @@ static RpcSystemCallbacks rpc_systems[] = {
     },
     {
         .alloc = rpc_system_app_alloc,
-        .free = NULL,
+        .free = rpc_system_app_free,
     },
     {
         .alloc = rpc_system_gui_alloc,

+ 171 - 4
applications/rpc/rpc_app.c

@@ -1,16 +1,39 @@
+#include "cmsis_os2.h"
 #include "flipper.pb.h"
 #include "furi/record.h"
 #include "rpc_i.h"
 #include <furi.h>
 #include <loader/loader.h>
+#include "rpc_app.h"
 
 #define TAG "RpcSystemApp"
+#define APP_BUTTON_TIMEOUT 1000
+
+struct RpcAppSystem {
+    RpcSession* session;
+    RpcAppSystemCallback app_callback;
+    void* app_context;
+    osTimerId_t timer;
+};
+
+static void rpc_system_app_timer_callback(void* context) {
+    furi_assert(context);
+    RpcAppSystem* rpc_app = context;
+
+    if(rpc_app->app_callback) {
+        rpc_app->app_callback(RpcAppEventButtonRelease, NULL, rpc_app->app_context);
+    }
+}
 
 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);
-    RpcSession* session = (RpcSession*)context;
+    RpcAppSystem* rpc_app = context;
+    RpcSession* session = rpc_app->session;
     furi_assert(session);
+    char args_temp[16];
 
     FURI_LOG_D(TAG, "Start");
 
@@ -20,6 +43,11 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context)
     const char* app_name = request->content.app_start_request.name;
     if(app_name) {
         const char* app_args = request->content.app_start_request.args;
+        if(strcmp(app_args, "RPC") == 0) {
+            // If app is being started in RPC mode - pass RPC context via args string
+            snprintf(args_temp, 16, "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;
@@ -43,8 +71,11 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context)
 
 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);
-    RpcSession* session = (RpcSession*)context;
+    RpcAppSystem* rpc_app = context;
+    RpcSession* session = rpc_app->session;
     furi_assert(session);
 
     FURI_LOG_D(TAG, "LockStatus");
@@ -66,13 +97,123 @@ static void rpc_system_app_lock_status_process(const PB_Main* request, void* con
     pb_release(&PB_Main_msg, &response);
 }
 
+static void rpc_system_app_exit(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) {
+        if(rpc_app->app_callback(RpcAppEventAppExit, NULL, rpc_app->app_context)) {
+            status = PB_CommandStatus_OK;
+            osTimerStop(rpc_app->timer);
+        } else {
+            status = PB_CommandStatus_ERROR_APP_CMD_ERROR;
+        }
+    } else {
+        status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
+    }
+
+    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) {
+        const char* file_path = request->content.app_load_file_request.path;
+        if(rpc_app->app_callback(RpcAppEventLoadFile, file_path, rpc_app->app_context)) {
+            status = PB_CommandStatus_OK;
+        } else {
+            status = PB_CommandStatus_ERROR_APP_CMD_ERROR;
+        }
+    } else {
+        status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
+    }
+
+    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) {
+        const char* args = request->content.app_button_press_request.args;
+        if(rpc_app->app_callback(RpcAppEventButtonPress, args, rpc_app->app_context)) {
+            status = PB_CommandStatus_OK;
+            osTimerStart(rpc_app->timer, APP_BUTTON_TIMEOUT);
+        } else {
+            status = PB_CommandStatus_ERROR_APP_CMD_ERROR;
+        }
+    } else {
+        status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
+    }
+
+    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) {
+        if(rpc_app->app_callback(RpcAppEventButtonRelease, NULL, rpc_app->app_context)) {
+            status = PB_CommandStatus_OK;
+            osTimerStop(rpc_app->timer);
+        } else {
+            status = PB_CommandStatus_ERROR_APP_CMD_ERROR;
+        }
+    } else {
+        status = PB_CommandStatus_ERROR_APP_NOT_RUNNING;
+    }
+
+    rpc_send_and_release_empty(session, request->command_id, status);
+}
+
+void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx) {
+    furi_assert(rpc_app);
+
+    rpc_app->app_callback = callback;
+    rpc_app->app_context = ctx;
+}
+
 void* rpc_system_app_alloc(RpcSession* session) {
     furi_assert(session);
 
+    RpcAppSystem* rpc_app = malloc(sizeof(RpcAppSystem));
+    rpc_app->session = session;
+
+    rpc_app->timer = osTimerNew(rpc_system_app_timer_callback, osTimerOnce, rpc_app, NULL);
+
     RpcHandler rpc_handler = {
         .message_handler = NULL,
         .decode_submessage = NULL,
-        .context = session,
+        .context = rpc_app,
     };
 
     rpc_handler.message_handler = rpc_system_app_start_process;
@@ -81,5 +222,31 @@ void* rpc_system_app_alloc(RpcSession* session) {
     rpc_handler.message_handler = rpc_system_app_lock_status_process;
     rpc_add_handler(session, PB_Main_app_lock_status_request_tag, &rpc_handler);
 
-    return NULL;
+    rpc_handler.message_handler = rpc_system_app_exit;
+    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;
+    RpcSession* session = rpc_app->session;
+    furi_assert(session);
+
+    osTimerDelete(rpc_app->timer);
+
+    if(rpc_app->app_callback) {
+        rpc_app->app_callback(RpcAppEventSessionClose, NULL, rpc_app->app_context);
+    }
+
+    free(rpc_app);
 }

+ 24 - 0
applications/rpc/rpc_app.h

@@ -0,0 +1,24 @@
+#pragma once
+#include "rpc.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    RpcAppEventSessionClose,
+    RpcAppEventAppExit,
+    RpcAppEventLoadFile,
+    RpcAppEventButtonPress,
+    RpcAppEventButtonRelease,
+} RpcAppSystemEvent;
+
+typedef bool (*RpcAppSystemCallback)(RpcAppSystemEvent event, const char* arg, void* context);
+
+typedef struct RpcAppSystem RpcAppSystem;
+
+void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx);
+
+#ifdef __cplusplus
+}
+#endif

+ 1 - 0
applications/rpc/rpc_i.h

@@ -29,6 +29,7 @@ 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);
 

+ 1 - 0
applications/subghz/scenes/subghz_scene_config.h

@@ -22,3 +22,4 @@ ADD_SCENE(subghz, read_raw, ReadRAW)
 ADD_SCENE(subghz, more_raw, MoreRAW)
 ADD_SCENE(subghz, delete_raw, DeleteRAW)
 ADD_SCENE(subghz, need_saving, NeedSaving)
+ADD_SCENE(subghz, rpc, Rpc)

+ 34 - 0
applications/subghz/scenes/subghz_scene_rpc.c

@@ -0,0 +1,34 @@
+#include "../subghz_i.h"
+
+void subghz_scene_rpc_on_enter(void* context) {
+    SubGhz* subghz = context;
+    Widget* widget = subghz->widget;
+
+    widget_add_text_box_element(
+        widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false);
+
+    notification_message(subghz->notifications, &sequence_display_backlight_on);
+
+    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
+}
+
+bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
+    SubGhz* subghz = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        if(event.event == SubGhzCustomEventSceneExit) {
+            view_dispatcher_stop(subghz->view_dispatcher);
+        }
+    }
+    return consumed;
+}
+
+void subghz_scene_rpc_on_exit(void* context) {
+    SubGhz* subghz = context;
+
+    //subghz_rpc_exit_callback(subghz);
+
+    widget_reset(subghz->widget);
+}

+ 59 - 1
applications/subghz/subghz.c

@@ -23,6 +23,54 @@ void subghz_tick_event_callback(void* context) {
     scene_manager_handle_tick_event(subghz->scene_manager);
 }
 
+static bool subghz_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) {
+    furi_assert(context);
+    SubGhz* subghz = context;
+
+    if(!subghz->rpc_ctx) {
+        return false;
+    }
+
+    bool result = false;
+
+    if(event == RpcAppEventSessionClose) {
+        rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL);
+        subghz->rpc_ctx = NULL;
+        view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit);
+        if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
+            subghz_tx_stop(subghz);
+            subghz_sleep(subghz);
+        }
+        result = true;
+    } else if(event == RpcAppEventAppExit) {
+        view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit);
+        if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
+            subghz_tx_stop(subghz);
+            subghz_sleep(subghz);
+        }
+        result = true;
+    } else if(event == RpcAppEventLoadFile) {
+        if(arg) {
+            if(subghz_key_load(subghz, arg, false)) {
+                string_set_str(subghz->file_path, arg);
+                result = true;
+            }
+        }
+    } else if(event == RpcAppEventButtonPress) {
+        if(subghz->txrx->txrx_state == SubGhzTxRxStateSleep) {
+            result = subghz_tx_start(subghz, subghz->txrx->fff_data);
+        }
+    } else if(event == RpcAppEventButtonRelease) {
+        if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) {
+            subghz_tx_stop(subghz);
+            subghz_sleep(subghz);
+            result = true;
+        }
+    }
+
+    return result;
+}
+
 SubGhz* subghz_alloc() {
     SubGhz* subghz = malloc(sizeof(SubGhz));
 
@@ -168,6 +216,11 @@ SubGhz* subghz_alloc() {
 void subghz_free(SubGhz* subghz) {
     furi_assert(subghz);
 
+    if(subghz->rpc_ctx) {
+        rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL);
+        subghz->rpc_ctx = NULL;
+    }
+
     // Packet Test
     view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket);
     subghz_test_packet_free(subghz->subghz_test_packet);
@@ -265,7 +318,12 @@ int32_t subghz_app(void* p) {
         subghz->txrx->environment, "/ext/subghz/assets/keeloq_mfcodes_user");
     // Check argument and run corresponding scene
     if(p) {
-        if(subghz_key_load(subghz, p)) {
+        uint32_t rpc_ctx = 0;
+        if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
+            subghz->rpc_ctx = (void*)rpc_ctx;
+            rpc_system_app_set_callback(subghz->rpc_ctx, subghz_rpc_command_callback, subghz);
+            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneRpc);
+        } else if(subghz_key_load(subghz, p, true)) {
             string_set_str(subghz->file_path, p);
 
             if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) {

+ 8 - 4
applications/subghz/subghz_i.c

@@ -218,7 +218,7 @@ void subghz_dialog_message_show_only_rx(SubGhz* subghz) {
     dialog_message_free(message);
 }
 
-bool subghz_key_load(SubGhz* subghz, const char* file_path) {
+bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
     furi_assert(subghz);
     furi_assert(file_path);
 
@@ -308,11 +308,15 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) {
 
     switch(load_key_state) {
     case SubGhzLoadKeyStateParseErr:
-        dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile");
+        if(show_dialog) {
+            dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile");
+        }
         return false;
 
     case SubGhzLoadKeyStateOnlyRx:
-        subghz_dialog_message_show_only_rx(subghz);
+        if(show_dialog) {
+            subghz_dialog_message_show_only_rx(subghz);
+        }
         return false;
 
     case SubGhzLoadKeyStateOK:
@@ -427,7 +431,7 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) {
         true);
 
     if(res) {
-        res = subghz_key_load(subghz, string_get_cstr(subghz->file_path));
+        res = subghz_key_load(subghz, string_get_cstr(subghz->file_path), true);
     }
 
     string_clear(file_path);

+ 5 - 1
applications/subghz/subghz_i.h

@@ -36,6 +36,8 @@
 #include <gui/modules/variable_item_list.h>
 #include <lib/toolbox/path.h>
 
+#include "rpc/rpc_app.h"
+
 #define SUBGHZ_MAX_LEN_NAME 64
 
 struct SubGhzTxRx {
@@ -91,6 +93,8 @@ struct SubGhz {
     string_t error_str;
     SubGhzSetting* setting;
     SubGhzLock lock;
+
+    void* rpc_ctx;
 };
 
 bool subghz_set_preset(SubGhz* subghz, const char* preset);
@@ -102,7 +106,7 @@ void subghz_sleep(SubGhz* subghz);
 bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format);
 void subghz_tx_stop(SubGhz* subghz);
 void subghz_dialog_message_show_only_rx(SubGhz* subghz);
-bool subghz_key_load(SubGhz* subghz, const char* file_path);
+bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog);
 bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len);
 bool subghz_save_protocol_to_file(
     SubGhz* subghz,

+ 1 - 1
assets/protobuf

@@ -1 +1 @@
-Subproject commit ffa62429f3c678537e0e883a3a8c3ae5f1398ed4
+Subproject commit e3d9cdb66ce789f84f6f8e0bdd6d022187964425