1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 12:42:30 +04:00

Merge branch 'fz-dev' into dev

This commit is contained in:
Eng1n33r
2022-07-04 21:57:40 +03:00
62 changed files with 1405 additions and 221 deletions

View File

@@ -28,15 +28,41 @@ SConscript("site_scons/cc.scons", exports={"ENV": coreenv})
# Store root dir in environment for certain tools # Store root dir in environment for certain tools
coreenv["ROOT_DIR"] = Dir(".") coreenv["ROOT_DIR"] = Dir(".")
# Create a separate "dist" environment and add construction envs to it # Create a separate "dist" environment and add construction envs to it
distenv = coreenv.Clone( distenv = coreenv.Clone(
tools=["fbt_dist", "openocd"], tools=["fbt_dist", "openocd", "blackmagic"],
GDBOPTS="-ex 'target extended-remote | ${OPENOCD} -c \"gdb_port pipe\" ${OPENOCD_OPTS}' " OPENOCD_GDB_PIPE=["|openocd -c 'gdb_port pipe' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}"],
'-ex "set confirm off" ', GDBOPTS_BASE=[
"-ex",
"target extended-remote ${GDBREMOTE}",
"-ex",
"set confirm off",
],
GDBOPTS_BLACKMAGIC=[
"-ex",
"monitor swdp_scan",
"-ex",
"monitor debug_bmp enable",
"-ex",
"attach 1",
"-ex",
"set mem inaccessible-by-default off",
],
GDBPYOPTS=[
"-ex",
"source debug/FreeRTOS/FreeRTOS.py",
"-ex",
"source debug/PyCortexMDebug/PyCortexMDebug.py",
"-ex",
"svd_load ${SVD_FILE}",
"-ex",
"compare-sections",
],
ENV=os.environ, ENV=os.environ,
) )
firmware_out = distenv.AddFwProject( firmware_env = distenv.AddFwProject(
base_env=coreenv, base_env=coreenv,
fw_type="firmware", fw_type="firmware",
fw_env_key="FW_ENV", fw_env_key="FW_ENV",
@@ -44,7 +70,7 @@ firmware_out = distenv.AddFwProject(
# If enabled, initialize updater-related targets # If enabled, initialize updater-related targets
if GetOption("fullenv"): if GetOption("fullenv"):
updater_out = distenv.AddFwProject( updater_env = distenv.AddFwProject(
base_env=coreenv, base_env=coreenv,
fw_type="updater", fw_type="updater",
fw_env_key="UPD_ENV", fw_env_key="UPD_ENV",
@@ -72,19 +98,32 @@ if GetOption("fullenv"):
selfupdate_dist = distenv.DistCommand( selfupdate_dist = distenv.DistCommand(
"updater_package", "updater_package",
(distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"]), (distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES"]),
DIST_EXTRA=dist_arguments, DIST_EXTRA=dist_arguments,
) )
# Updater debug # Updater debug
distenv.AddDebugTarget("updater_debug", updater_out, False) distenv.PhonyTarget(
"updater_debug",
"${GDBPYCOM}",
source=updater_env["FW_ELF"],
GDBREMOTE="${OPENOCD_GDB_PIPE}",
)
distenv.PhonyTarget(
"updater_blackmagic",
"${GDBPYCOM}",
source=updater_env["FW_ELF"],
GDBOPTS=distenv.subst("$GDBOPTS_BLACKMAGIC"),
GDBREMOTE="${BLACKMAGIC_ADDR}",
)
# Installation over USB & CLI # Installation over USB & CLI
usb_update_package = distenv.UsbInstall( usb_update_package = distenv.UsbInstall(
"#build/usbinstall.flag", "#build/usbinstall.flag",
( (
distenv["DIST_DEPENDS"], distenv["DIST_DEPENDS"],
firmware_out["FW_RESOURCES"], firmware_env["FW_RESOURCES"],
selfupdate_dist, selfupdate_dist,
), ),
) )
@@ -104,15 +143,47 @@ copro_dist = distenv.CoproBuilder(
) )
distenv.Alias("copro_dist", copro_dist) distenv.Alias("copro_dist", copro_dist)
firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env)
distenv.Alias("flash", firmware_flash)
firmware_bm_flash = distenv.PhonyTarget(
"flash_blackmagic",
"$GDB $GDBOPTS $SOURCES $GDBFLASH",
source=firmware_env["FW_ELF"],
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
GDBREMOTE="${BLACKMAGIC_ADDR}",
GDBFLASH=[
"-ex",
"load",
"-ex",
"quit",
],
)
# Debugging firmware # Debugging firmware
distenv.AddDebugTarget("debug", firmware_out) firmware_debug = distenv.PhonyTarget(
"debug",
"${GDBPYCOM}",
source=firmware_env["FW_ELF"],
GDBOPTS="${GDBOPTS_BASE}",
GDBREMOTE="${OPENOCD_GDB_PIPE}",
)
distenv.Depends(firmware_debug, firmware_flash)
distenv.PhonyTarget(
"blackmagic",
"${GDBPYCOM}",
source=firmware_env["FW_ELF"],
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
GDBREMOTE="${BLACKMAGIC_ADDR}",
)
# Debug alien elf # Debug alien elf
distenv.PhonyTarget( distenv.PhonyTarget(
"debug_other", "debug_other",
"$GDBPYCOM", "${GDBPYCOM}",
GDBPYOPTS= GDBPYOPTS='-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ',
# '-ex "source ${ROOT_DIR.abspath}/debug/FreeRTOS/FreeRTOS.py" ' GDBREMOTE="${OPENOCD_GDB_PIPE}",
'-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ',
) )
# Just start OpenOCD # Just start OpenOCD
@@ -125,11 +196,19 @@ distenv.PhonyTarget(
distenv.PhonyTarget( distenv.PhonyTarget(
"lint", "lint",
"${PYTHON3} scripts/lint.py check ${LINT_SOURCES}", "${PYTHON3} scripts/lint.py check ${LINT_SOURCES}",
LINT_SOURCES=firmware_out["LINT_SOURCES"], LINT_SOURCES=firmware_env["LINT_SOURCES"],
) )
distenv.PhonyTarget( distenv.PhonyTarget(
"format", "format",
"${PYTHON3} scripts/lint.py format ${LINT_SOURCES}", "${PYTHON3} scripts/lint.py format ${LINT_SOURCES}",
LINT_SOURCES=firmware_out["LINT_SOURCES"], LINT_SOURCES=firmware_env["LINT_SOURCES"],
)
# Find blackmagic probe
distenv.PhonyTarget(
"get_blackmagic",
"@echo $( ${BLACKMAGIC_ADDR} $)",
) )

View File

@@ -12,6 +12,7 @@
struct Slideshow { struct Slideshow {
Icon icon; Icon icon;
uint32_t current_frame; uint32_t current_frame;
bool loaded;
}; };
#pragma pack(push, 1) #pragma pack(push, 1)
@@ -34,6 +35,7 @@ _Static_assert(sizeof(SlideshowFrameHeader) == 2, "Incorrect SlideshowFrameHeade
Slideshow* slideshow_alloc() { Slideshow* slideshow_alloc() {
Slideshow* ret = malloc(sizeof(Slideshow)); Slideshow* ret = malloc(sizeof(Slideshow));
ret->loaded = false;
return ret; return ret;
} }
@@ -52,7 +54,7 @@ void slideshow_free(Slideshow* slideshow) {
bool slideshow_load(Slideshow* slideshow, const char* fspath) { bool slideshow_load(Slideshow* slideshow, const char* fspath) {
Storage* storage = furi_record_open("storage"); Storage* storage = furi_record_open("storage");
File* slideshow_file = storage_file_alloc(storage); File* slideshow_file = storage_file_alloc(storage);
bool load_success = false; slideshow->loaded = false;
do { do {
if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) { if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) {
break; break;
@@ -80,12 +82,16 @@ bool slideshow_load(Slideshow* slideshow, const char* fspath) {
frame_header.size) { frame_header.size) {
break; break;
} }
load_success = (frame_idx + 1) == header.frame_count; slideshow->loaded = (frame_idx + 1) == header.frame_count;
} }
} while(false); } while(false);
storage_file_free(slideshow_file); storage_file_free(slideshow_file);
furi_record_close("storage"); furi_record_close("storage");
return load_success; return slideshow->loaded;
}
bool slideshow_is_loaded(Slideshow* slideshow) {
return slideshow->loaded;
} }
bool slideshow_advance(Slideshow* slideshow) { bool slideshow_advance(Slideshow* slideshow) {

View File

@@ -8,6 +8,7 @@ Slideshow* slideshow_alloc();
void slideshow_free(Slideshow* slideshow); void slideshow_free(Slideshow* slideshow);
bool slideshow_load(Slideshow* slideshow, const char* fspath); bool slideshow_load(Slideshow* slideshow, const char* fspath);
bool slideshow_is_loaded(Slideshow* slideshow);
void slideshow_goback(Slideshow* slideshow); void slideshow_goback(Slideshow* slideshow);
bool slideshow_advance(Slideshow* slideshow); bool slideshow_advance(Slideshow* slideshow);
void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y); void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y);

View File

@@ -21,8 +21,10 @@ static void desktop_view_slideshow_draw(Canvas* canvas, void* model) {
DesktopSlideshowViewModel* m = model; DesktopSlideshowViewModel* m = model;
canvas_clear(canvas); canvas_clear(canvas);
if(slideshow_is_loaded(m->slideshow)) {
slideshow_draw(m->slideshow, canvas, 0, 0); slideshow_draw(m->slideshow, canvas, 0, 0);
} }
}
static bool desktop_view_slideshow_input(InputEvent* event, void* context) { static bool desktop_view_slideshow_input(InputEvent* event, void* context) {
furi_assert(event); furi_assert(event);

View File

@@ -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) { static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
furi_hal_usb_unlock(); furi_hal_usb_unlock();
FURI_LOG_I("", "Init %d", vcp_ch);
if(vcp_ch == 0) { if(vcp_ch == 0) {
Cli* cli = furi_record_open("cli"); Cli* cli = furi_record_open("cli");
cli_session_close(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) { static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
UNUSED(usb_uart); UNUSED(usb_uart);
furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL); furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL);
FURI_LOG_I("", "Deinit %d", vcp_ch);
if(vcp_ch != 0) { if(vcp_ch != 0) {
Cli* cli = furi_record_open("cli"); Cli* cli = furi_record_open("cli");
cli_session_close(cli); cli_session_close(cli);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,6 +36,52 @@ static void infrared_tick_event_callback(void* context) {
scene_manager_handle_tick_event(infrared->scene_manager); 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) { static void infrared_find_vacant_remote_name(string_t name, const char* path) {
Storage* storage = furi_record_open("storage"); Storage* storage = furi_record_open("storage");
@@ -154,6 +200,11 @@ static void infrared_free(Infrared* infrared) {
ViewDispatcher* view_dispatcher = infrared->view_dispatcher; ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
InfraredAppState* app_state = &infrared->app_state; 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); view_dispatcher_remove_view(view_dispatcher, InfraredViewSubmenu);
submenu_free(infrared->submenu); submenu_free(infrared->submenu);
@@ -375,8 +426,16 @@ int32_t infrared_app(void* p) {
infrared_make_app_folder(infrared); infrared_make_app_folder(infrared);
bool is_remote_loaded = false; bool is_remote_loaded = false;
bool is_rpc_mode = false;
if(p) { if(p) {
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); string_set_str(infrared->file_path, (const char*)p);
is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path); is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path);
if(!is_remote_loaded) { if(!is_remote_loaded) {
@@ -385,8 +444,11 @@ int32_t infrared_app(void* p) {
return -1; 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); scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
} else { } else {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneStart); scene_manager_next_scene(infrared->scene_manager, InfraredSceneStart);

View File

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

View File

@@ -1,5 +1,7 @@
#include "infrared_remote.h" #include "infrared_remote.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
#include <m-string.h> #include <m-string.h>
#include <m-array.h> #include <m-array.h>
@@ -73,6 +75,17 @@ InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t
return *InfraredButtonArray_get(remote->buttons, index); 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) { bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal) {
InfraredRemoteButton* button = infrared_remote_button_alloc(); InfraredRemoteButton* button = infrared_remote_button_alloc();
infrared_remote_button_set_name(button, name); infrared_remote_button_set_name(button, name);

View File

@@ -18,6 +18,7 @@ const char* infrared_remote_get_path(InfraredRemote* remote);
size_t infrared_remote_get_button_count(InfraredRemote* remote); size_t infrared_remote_get_button_count(InfraredRemote* remote);
InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t index); 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_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal);
bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index); bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index);

View File

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

View File

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

View File

@@ -20,10 +20,13 @@
#include "scene/lfrfid_app_scene_saved_info.h" #include "scene/lfrfid_app_scene_saved_info.h"
#include "scene/lfrfid_app_scene_delete_confirm.h" #include "scene/lfrfid_app_scene_delete_confirm.h"
#include "scene/lfrfid_app_scene_delete_success.h" #include "scene/lfrfid_app_scene_delete_success.h"
#include "scene/lfrfid_app_scene_rpc.h"
#include <toolbox/path.h> #include <toolbox/path.h>
#include <flipper_format/flipper_format.h> #include <flipper_format/flipper_format.h>
#include "rpc/rpc_app.h"
const char* LfRfidApp::app_folder = "/any/lfrfid"; const char* LfRfidApp::app_folder = "/any/lfrfid";
const char* LfRfidApp::app_extension = ".rfid"; const char* LfRfidApp::app_extension = ".rfid";
const char* LfRfidApp::app_filetype = "Flipper RFID key"; const char* LfRfidApp::app_filetype = "Flipper RFID key";
@@ -39,6 +42,43 @@ LfRfidApp::LfRfidApp()
LfRfidApp::~LfRfidApp() { LfRfidApp::~LfRfidApp() {
string_clear(file_path); 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) { void LfRfidApp::run(void* _args) {
@@ -47,10 +87,19 @@ void LfRfidApp::run(void* _args) {
make_app_folder(); make_app_folder();
if(strlen(args)) { if(strlen(args)) {
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); string_set_str(file_path, args);
load_key_data(file_path, &worker.key); load_key_data(file_path, &worker.key, true);
scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate());
scene_controller.process(100, SceneType::Emulate); scene_controller.process(100, SceneType::Emulate);
}
} else { } else {
scene_controller.add_scene(SceneType::Start, new LfRfidAppSceneStart()); scene_controller.add_scene(SceneType::Start, new LfRfidAppSceneStart());
scene_controller.add_scene(SceneType::Read, new LfRfidAppSceneRead()); 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); dialogs, file_path, file_path, app_extension, true, &I_125_10px, true);
if(result) { if(result) {
result = load_key_data(file_path, &worker.key); result = load_key_data(file_path, &worker.key, true);
} }
return result; return result;
@@ -110,7 +159,7 @@ bool LfRfidApp::delete_key(RfidKey* key) {
return storage_simply_remove(storage, string_get_cstr(file_path)); 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); FlipperFormat* file = flipper_format_file_alloc(storage);
bool result = false; bool result = false;
string_t str_result; string_t str_result;
@@ -149,7 +198,7 @@ bool LfRfidApp::load_key_data(string_t path, RfidKey* key) {
flipper_format_free(file); flipper_format_free(file);
string_clear(str_result); string_clear(str_result);
if(!result) { if((!result) && (show_dialog)) {
dialog_message_show_storage_error(dialogs, "Cannot load\nkey file"); dialog_message_show_storage_error(dialogs, "Cannot load\nkey file");
} }

View File

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

View File

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

View File

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

78
applications/nfc/nfc.c Executable file → Normal file
View File

@@ -19,6 +19,77 @@ void nfc_tick_event_callback(void* context) {
scene_manager_handle_tick_event(nfc->scene_manager); 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_alloc() {
Nfc* nfc = malloc(sizeof(Nfc)); Nfc* nfc = malloc(sizeof(Nfc));
@@ -193,7 +264,12 @@ int32_t nfc_app(void* p) {
// Check argument and run corresponding scene // Check argument and run corresponding scene
if((*args != '\0')) { 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) { if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl); scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
} else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {

View File

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

View File

@@ -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_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); bool nfc_file_select(NfcDevice* dev);

View File

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

View File

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

View File

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

View File

@@ -45,13 +45,16 @@ static RpcSystemCallbacks rpc_systems[] = {
}, },
{ {
.alloc = rpc_system_app_alloc, .alloc = rpc_system_app_alloc,
.free = NULL, .free = rpc_system_app_free,
}, },
{ {
.alloc = rpc_system_gui_alloc, .alloc = rpc_system_gui_alloc,
.free = rpc_system_gui_free, .free = rpc_system_gui_free,
}, },
}; {
.alloc = rpc_system_gpio_alloc,
.free = NULL,
}};
struct RpcSession { struct RpcSession {
Rpc* rpc; Rpc* rpc;

View File

@@ -1,16 +1,39 @@
#include "cmsis_os2.h"
#include "flipper.pb.h" #include "flipper.pb.h"
#include "furi/record.h" #include "furi/record.h"
#include "rpc_i.h" #include "rpc_i.h"
#include <furi.h> #include <furi.h>
#include <loader/loader.h> #include <loader/loader.h>
#include "rpc_app.h"
#define TAG "RpcSystemApp" #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) { static void rpc_system_app_start_process(const PB_Main* request, void* context) {
furi_assert(request); furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_start_request_tag); 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); furi_assert(session);
char args_temp[16];
FURI_LOG_D(TAG, "Start"); 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; const char* app_name = request->content.app_start_request.name;
if(app_name) { if(app_name) {
const char* app_args = request->content.app_start_request.args; 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); LoaderStatus status = loader_start(loader, app_name, app_args);
if(status == LoaderStatusErrorAppStarted) { if(status == LoaderStatusErrorAppStarted) {
result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED; 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) { static void rpc_system_app_lock_status_process(const PB_Main* request, void* context) {
furi_assert(request); furi_assert(request);
furi_assert(context);
furi_assert(request->which_content == PB_Main_app_lock_status_request_tag); 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_assert(session);
FURI_LOG_D(TAG, "LockStatus"); 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); 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) { void* rpc_system_app_alloc(RpcSession* session) {
furi_assert(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 = { RpcHandler rpc_handler = {
.message_handler = NULL, .message_handler = NULL,
.decode_submessage = NULL, .decode_submessage = NULL,
.context = session, .context = rpc_app,
}; };
rpc_handler.message_handler = rpc_system_app_start_process; 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_handler.message_handler = rpc_system_app_lock_status_process;
rpc_add_handler(session, PB_Main_app_lock_status_request_tag, &rpc_handler); 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);
} }

View File

@@ -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

221
applications/rpc/rpc_gpio.c Normal file
View File

@@ -0,0 +1,221 @@
#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;
furi_assert(session);
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;
furi_assert(session);
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;
furi_assert(session);
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;
furi_assert(session);
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;
furi_assert(session);
PB_Gpio_SetInputPull cmd = request->content.gpio_set_input_pull;
const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin);
const GpioPull pull_mode = rpc_pull_mode_to_hall_pull_mode(cmd.pull_mode);
PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id;
response->has_next = false;
PB_CommandStatus status;
if(LL_GPIO_MODE_INPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) {
status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT;
} else {
status = PB_CommandStatus_OK;
furi_hal_gpio_init(pin, GpioModeInput, pull_mode, GpioSpeedLow);
}
rpc_send_and_release_empty(session, request->command_id, status);
free(response);
}
void* rpc_system_gpio_alloc(RpcSession* session) {
furi_assert(session);
RpcHandler rpc_handler = {
.message_handler = NULL,
.decode_submessage = NULL,
.context = session,
};
rpc_handler.message_handler = rpc_system_gpio_set_pin_mode;
rpc_add_handler(session, PB_Main_gpio_set_pin_mode_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_write_pin;
rpc_add_handler(session, PB_Main_gpio_write_pin_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_read_pin;
rpc_add_handler(session, PB_Main_gpio_read_pin_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_get_pin_mode;
rpc_add_handler(session, PB_Main_gpio_get_pin_mode_tag, &rpc_handler);
rpc_handler.message_handler = rpc_system_gpio_set_input_pull;
rpc_add_handler(session, PB_Main_gpio_set_input_pull_tag, &rpc_handler);
return NULL;
}

View File

@@ -29,8 +29,11 @@ void* rpc_system_system_alloc(RpcSession* session);
void* rpc_system_storage_alloc(RpcSession* session); void* rpc_system_storage_alloc(RpcSession* session);
void rpc_system_storage_free(void* ctx); void rpc_system_storage_free(void* ctx);
void* rpc_system_app_alloc(RpcSession* session); 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_alloc(RpcSession* session);
void rpc_system_gui_free(void* ctx); void rpc_system_gui_free(void* ctx);
void* rpc_system_gpio_alloc(RpcSession* session);
void rpc_system_gpio_free(void* ctx);
void rpc_print_message(const PB_Main* message); void rpc_print_message(const PB_Main* message);
void rpc_cli_command_start_session(Cli* cli, string_t args, void* context); void rpc_cli_command_start_session(Cli* cli, string_t args, void* context);

View File

@@ -277,10 +277,6 @@ static void rpc_system_system_update_request_process(const PB_Main* request, voi
UpdatePrepareResult update_prepare_result = UpdatePrepareResult update_prepare_result =
update_operation_prepare(request->content.system_update_request.update_manifest); update_operation_prepare(request->content.system_update_request.update_manifest);
/* RPC enum does not have such entry; setting to closest one */
if(update_prepare_result == UpdatePrepareResultOutdatedManifestVersion) {
update_prepare_result = UpdatePrepareResultManifestInvalid;
}
PB_Main* response = malloc(sizeof(PB_Main)); PB_Main* response = malloc(sizeof(PB_Main));
response->command_id = request->command_id; response->command_id = request->command_id;

View File

@@ -31,6 +31,8 @@ typedef enum {
GameStateGameOver, GameStateGameOver,
} GameState; } GameState;
// Note: do not change without purpose. Current values are used in smart
// orthogonality calculation in `snake_game_get_turn_snake`.
typedef enum { typedef enum {
DirectionUp, DirectionUp,
DirectionRight, DirectionRight,
@@ -203,44 +205,9 @@ static bool
} }
static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) { static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) {
switch(snake_state->currentMovement) { // Sum of two `Direction` lies between 0 and 6, odd values indicate orthogonality.
case DirectionUp: bool is_orthogonal = (snake_state->currentMovement + snake_state->nextMovement) % 2 == 1;
switch(snake_state->nextMovement) { return is_orthogonal ? snake_state->nextMovement : snake_state->currentMovement;
case DirectionRight:
return DirectionRight;
case DirectionLeft:
return DirectionLeft;
default:
return snake_state->currentMovement;
}
case DirectionRight:
switch(snake_state->nextMovement) {
case DirectionUp:
return DirectionUp;
case DirectionDown:
return DirectionDown;
default:
return snake_state->currentMovement;
}
case DirectionDown:
switch(snake_state->nextMovement) {
case DirectionRight:
return DirectionRight;
case DirectionLeft:
return DirectionLeft;
default:
return snake_state->currentMovement;
}
default: // case DirectionLeft:
switch(snake_state->nextMovement) {
case DirectionUp:
return DirectionUp;
case DirectionDown:
return DirectionDown;
default:
return snake_state->currentMovement;
}
}
} }
static Point snake_game_get_next_step(SnakeState const* const snake_state) { static Point snake_game_get_next_step(SnakeState const* const snake_state) {

View File

@@ -655,11 +655,13 @@ static FS_Error storage_int_common_fs_info(
lfs_t* lfs = lfs_get_from_storage(storage); lfs_t* lfs = lfs_get_from_storage(storage);
LFSData* lfs_data = lfs_data_get_from_storage(storage); LFSData* lfs_data = lfs_data_get_from_storage(storage);
if(total_space) {
*total_space = lfs_data->config.block_size * lfs_data->config.block_count; *total_space = lfs_data->config.block_size * lfs_data->config.block_count;
}
lfs_ssize_t result = lfs_fs_size(lfs); lfs_ssize_t result = lfs_fs_size(lfs);
if(result >= 0) { if(free_space && (result >= 0)) {
*free_space = *total_space - (result * lfs_data->config.block_size); *free_space = (lfs_data->config.block_count - result) * lfs_data->config.block_size;
} }
return storage_int_parse_error(result); return storage_int_parse_error(result);

View File

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

View File

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

View File

@@ -23,6 +23,54 @@ void subghz_tick_event_callback(void* context) {
scene_manager_handle_tick_event(subghz->scene_manager); 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_alloc() {
SubGhz* subghz = malloc(sizeof(SubGhz)); SubGhz* subghz = malloc(sizeof(SubGhz));
@@ -174,6 +222,11 @@ SubGhz* subghz_alloc() {
void subghz_free(SubGhz* subghz) { void subghz_free(SubGhz* subghz) {
furi_assert(subghz); furi_assert(subghz);
if(subghz->rpc_ctx) {
rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL);
subghz->rpc_ctx = NULL;
}
// Packet Test // Packet Test
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket);
subghz_test_packet_free(subghz->subghz_test_packet); subghz_test_packet_free(subghz->subghz_test_packet);
@@ -276,7 +329,12 @@ int32_t subghz_app(void* p) {
subghz->txrx->environment, "/ext/subghz/assets/keeloq_mfcodes_user"); subghz->txrx->environment, "/ext/subghz/assets/keeloq_mfcodes_user");
// Check argument and run corresponding scene // Check argument and run corresponding scene
if(p) { 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); string_set_str(subghz->file_path, p);
if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) {

View File

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

View File

@@ -37,6 +37,8 @@
#include <gui/modules/variable_item_list.h> #include <gui/modules/variable_item_list.h>
#include <lib/toolbox/path.h> #include <lib/toolbox/path.h>
#include "rpc/rpc_app.h"
#define SUBGHZ_MAX_LEN_NAME 64 #define SUBGHZ_MAX_LEN_NAME 64
typedef struct { typedef struct {
@@ -100,6 +102,8 @@ struct SubGhz {
string_t error_str; string_t error_str;
SubGhzSetting* setting; SubGhzSetting* setting;
SubGhzLock lock; SubGhzLock lock;
void* rpc_ctx;
}; };
bool subghz_set_preset(SubGhz* subghz, const char* preset); bool subghz_set_preset(SubGhz* subghz, const char* preset);
@@ -111,7 +115,7 @@ void subghz_sleep(SubGhz* subghz);
bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format); bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format);
void subghz_tx_stop(SubGhz* subghz); void subghz_tx_stop(SubGhz* subghz);
void subghz_dialog_message_show_only_rx(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_get_next_name_file(SubGhz* subghz, uint8_t max_len);
bool subghz_save_protocol_to_file( bool subghz_save_protocol_to_file(
SubGhz* subghz, SubGhz* subghz,

View File

@@ -44,17 +44,17 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = {
[UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5),
[UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15), [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15),
[UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 10), [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15),
[UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 50), [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 60),
[UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 90), [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 80),
[UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 60),
[UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 80),
[UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10), [UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10),
[UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 100), [UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50),
[UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 200), [UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 200),
[UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50), [UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 30),
[UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30), [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30),
@@ -214,6 +214,7 @@ UpdateTask* update_task_alloc() {
update_task->storage = furi_record_open("storage"); update_task->storage = furi_record_open("storage");
update_task->file = storage_file_alloc(update_task->storage); update_task->file = storage_file_alloc(update_task->storage);
update_task->status_change_cb = NULL; update_task->status_change_cb = NULL;
update_task->boot_mode = furi_hal_rtc_get_boot_mode();
string_init(update_task->update_path); string_init(update_task->update_path);
FuriThread* thread = update_task->thread = furi_thread_alloc(); FuriThread* thread = update_task->thread = furi_thread_alloc();

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <storage/storage.h> #include <storage/storage.h>
#include <furi_hal.h>
#define UPDATE_TASK_NOERR 0 #define UPDATE_TASK_NOERR 0
#define UPDATE_TASK_FAILED -1 #define UPDATE_TASK_FAILED -1
@@ -14,6 +15,7 @@ typedef struct UpdateTask {
File* file; File* file;
updateProgressCb status_change_cb; updateProgressCb status_change_cb;
void* status_change_cb_state; void* status_change_cb_state;
FuriHalRtcBootMode boot_mode;
} UpdateTask; } UpdateTask;
void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress); void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress);

View File

@@ -67,7 +67,6 @@ static bool update_task_post_update(UpdateTask* update_task) {
string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, file_path); string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, file_path);
update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0); update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0);
update_operation_disarm();
CHECK_RESULT(lfs_backup_unpack(update_task->storage, string_get_cstr(file_path))); CHECK_RESULT(lfs_backup_unpack(update_task->storage, string_get_cstr(file_path)));
@@ -117,17 +116,17 @@ static bool update_task_post_update(UpdateTask* update_task) {
int32_t update_task_worker_backup_restore(void* context) { int32_t update_task_worker_backup_restore(void* context) {
furi_assert(context); furi_assert(context);
UpdateTask* update_task = context; UpdateTask* update_task = context;
bool success = false;
FuriHalRtcBootMode boot_mode = furi_hal_rtc_get_boot_mode(); FuriHalRtcBootMode boot_mode = update_task->boot_mode;
if((boot_mode != FuriHalRtcBootModePreUpdate) && (boot_mode != FuriHalRtcBootModePostUpdate)) { if((boot_mode != FuriHalRtcBootModePreUpdate) && (boot_mode != FuriHalRtcBootModePostUpdate)) {
/* no idea how we got here. Clear to normal boot */ /* no idea how we got here. Do nothing */
update_operation_disarm();
return UPDATE_TASK_NOERR; return UPDATE_TASK_NOERR;
} }
bool success = false;
do {
if(!update_task_parse_manifest(update_task)) { if(!update_task_parse_manifest(update_task)) {
return UPDATE_TASK_FAILED; break;
} }
/* Waiting for BT service to 'start', so we don't race for boot mode flag */ /* Waiting for BT service to 'start', so we don't race for boot mode flag */
@@ -138,7 +137,11 @@ int32_t update_task_worker_backup_restore(void* context) {
success = update_task_pre_update(update_task); success = update_task_pre_update(update_task);
} else if(boot_mode == FuriHalRtcBootModePostUpdate) { } else if(boot_mode == FuriHalRtcBootModePostUpdate) {
success = update_task_post_update(update_task); success = update_task_post_update(update_task);
if(success) {
update_operation_disarm();
} }
}
} while(false);
if(!success) { if(!success) {
update_task_set_progress(update_task, UpdateTaskStageError, 0); update_task_set_progress(update_task, UpdateTaskStageError, 0);

View File

@@ -340,6 +340,8 @@ int32_t update_task_worker_flash_writer(void* context) {
} }
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate);
// Format LFS before restoring backup on next boot
furi_hal_rtc_set_flag(FuriHalRtcFlagFactoryReset);
update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); update_task_set_progress(update_task, UpdateTaskStageCompleted, 100);
success = true; success = true;

View File

@@ -10,7 +10,10 @@ Make sure that `gcc-arm-none-eabi` toolchain & OpenOCD executables are in system
## NB ## NB
FBT constructs all referenced environments & their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding construction of certain targets behind command-line options. * `fbt` constructs all referenced environments & their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding construction of certain targets behind command-line options.
* `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in environment:
* On Windows, that's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from
* On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...`
## Invoking FBT ## Invoking FBT
@@ -34,7 +37,9 @@ FBT keeps track of internal dependencies, so you only need to build the highest-
- `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded - `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded
- `debug_updater` - attach gdb with updater's .elf loaded. _Requires `--with-updater` option_ - `debug_updater` - attach gdb with updater's .elf loaded. _Requires `--with-updater` option_
- `debug_other` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb. - `debug_other` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb.
- `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board)
- `openocd` - just start OpenOCD - `openocd` - just start OpenOCD
- `get_blackmagic` - output blackmagic address in gdb remote format. Useful for IDE integration
### Firmware targets ### Firmware targets
@@ -43,6 +48,7 @@ FBT keeps track of internal dependencies, so you only need to build the highest-
- Check out `--extra-ext-apps` for force adding extra apps to external build - Check out `--extra-ext-apps` for force adding extra apps to external build
- `firmware_snake_game_list`, etc - generate source + assembler listing for app's .elf - `firmware_snake_game_list`, etc - generate source + assembler listing for app's .elf
- `flash`, `firmware_flash` - flash current version to attached device with OpenOCD over ST-Link - `flash`, `firmware_flash` - flash current version to attached device with OpenOCD over ST-Link
- `flash_blackmagic` - flash current version to attached device with Blackmagic probe
- `firmware_cdb` - generate compilation database - `firmware_cdb` - generate compilation database
- `firmware_all`, `updater_all` - build basic set of binaries - `firmware_all`, `updater_all` - build basic set of binaries
- `firmware_list`, `updater_list` - generate source + assembler listing - `firmware_list`, `updater_list` - generate source + assembler listing

6
fbt
View File

@@ -5,14 +5,14 @@ set -e
SCRIPTDIR="$( dirname -- "$0"; )"; SCRIPTDIR="$( dirname -- "$0"; )";
SCONS_EP=${SCRIPTDIR}/lib/scons/scripts/scons.py SCONS_EP=${SCRIPTDIR}/lib/scons/scripts/scons.py
if [[ -z "${FBT_NO_SYNC:-}" ]] ; then
if [[ -d .git ]]; then if [[ -d .git ]]; then
echo Updating git submodules
git submodule update --init git submodule update --init
else # Not in a git repo else
echo Not in a git repo, please clone with git clone --recursive echo Not in a git repo, please clone with git clone --recursive
# Return error code 1 to indicate failure
exit 1 exit 1
fi fi
fi
SCONS_DEFAULT_FLAGS="-Q --warn=target-not-built" SCONS_DEFAULT_FLAGS="-Q --warn=target-not-built"
python3 ${SCONS_EP} ${SCONS_DEFAULT_FLAGS} "$@" python3 ${SCONS_EP} ${SCONS_DEFAULT_FLAGS} "$@"

17
fbt.cmd
View File

@@ -1,12 +1,19 @@
@echo off @echo off
set SCONS_EP=%~dp0\lib\scons\scripts\scons.py
if [%FBT_NO_SYNC%] == [] (
if exist ".git" (
git submodule update --init
) else (
echo Not in a git repo, please clone with git clone --recursive
exit /b 1
)
)
set "flipper_toolchain_version=2" set "flipper_toolchain_version=2"
set "toolchainRoot=%~dp0toolchain\i686-windows" set "toolchainRoot=%~dp0toolchain\i686-windows"
set "SCONS_DEFAULT_FLAGS=-Q --warn=target-not-built" set "SCONS_DEFAULT_FLAGS=-Q --warn=target-not-built"
if not exist "%~dp0.git" (
echo ".git" directory not found, please clone repo via "git clone --recursive"
exit /B 1
)
git submodule update --init
if not exist "%toolchainRoot%" ( if not exist "%toolchainRoot%" (
powershell -ExecutionPolicy Bypass -File %~dp0scripts\toolchain\windows-toolchain-download.ps1 "%flipper_toolchain_version%" powershell -ExecutionPolicy Bypass -File %~dp0scripts\toolchain\windows-toolchain-download.ps1 "%flipper_toolchain_version%"
) )

View File

@@ -41,10 +41,24 @@ COPRO_STACK_BIN_DIR = posixpath.join(
# Supported toolchain versions # Supported toolchain versions
FBT_TOOLCHAIN_VERSIONS = (" 10.3.",) FBT_TOOLCHAIN_VERSIONS = (" 10.3.",)
OPENOCD_OPTS = '-f interface/stlink.cfg -c "transport select hla_swd" -f debug/stm32wbx.cfg -c "stm32wbx.cpu configure -rtos auto" -c "init"' OPENOCD_OPTS = [
"-f",
"interface/stlink.cfg",
"-c",
"transport select hla_swd",
"-f",
"debug/stm32wbx.cfg",
"-c",
"stm32wbx.cpu configure -rtos auto",
"-c",
"init",
]
SVD_FILE = "debug/STM32WB55_CM4.svd" SVD_FILE = "debug/STM32WB55_CM4.svd"
# Look for blackmagic probe on serial ports
BLACKMAGIC = "auto"
FIRMWARE_APPS = { FIRMWARE_APPS = {
"default": [ "default": [
"crypto_start", "crypto_start",

View File

@@ -6,7 +6,7 @@ from fbt.util import link_dir
# Building initial C environment for libs # Building initial C environment for libs
env = ENV.Clone( env = ENV.Clone(
tools=["compilation_db", "fwbin", "openocd", "fbt_apps"], tools=["compilation_db", "fwbin", "fbt_apps"],
COMPILATIONDB_USE_ABSPATH=True, COMPILATIONDB_USE_ABSPATH=True,
BUILD_DIR=fw_build_meta["build_dir"], BUILD_DIR=fw_build_meta["build_dir"],
IS_BASE_FIRMWARE=fw_build_meta["type"] == "firmware", IS_BASE_FIRMWARE=fw_build_meta["type"] == "firmware",
@@ -139,6 +139,8 @@ apps_c = fwenv.ApplicationsC(
"applications/applications.c", "applications/applications.c",
Value(fwenv["APPS"]), Value(fwenv["APPS"]),
) )
# Adding dependency on manifest files so apps.c is rebuilt when any manifest is changed
fwenv.Depends(apps_c, fwenv.GlobRecursive("*.fam", "applications"))
sources = [apps_c] sources = [apps_c]
# Gather sources only from app folders from current configuration # Gather sources only from app folders from current configuration
@@ -235,7 +237,7 @@ AddPostAction(fwelf, Action("@$SIZECOM"))
AddPostAction(fwelf, Action(link_latest_dir, None)) AddPostAction(fwelf, Action(link_latest_dir, None))
link_dir_command = fwenv["LINK_DIR_CMD"] = fwenv.PhonyTarget( link_dir_command = fwenv["LINK_DIR_CMD"] = fwenv.PhonyTarget(
"${FIRMWARE_BUILD_CFG}" + "_latest", fwenv.subst("${FIRMWARE_BUILD_CFG}_latest"),
Action(lambda target, source, env: link_elf_dir_as_latest(env, source[0]), None), Action(lambda target, source, env: link_elf_dir_as_latest(env, source[0]), None),
source=fwelf, source=fwelf,
) )
@@ -249,18 +251,6 @@ Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_dfu", fwdfu)
fwdump = fwenv.ObjDump("${FIRMWARE_BUILD_CFG}") fwdump = fwenv.ObjDump("${FIRMWARE_BUILD_CFG}")
Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_list", fwdump) Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_list", fwdump)
# Additional FW-related pseudotargets
flash = fwenv["FW_FLASH"] = fwenv.OpenOCDFlash(
"#build/oocd-${FIRMWARE_BUILD_CFG}-flash.flag",
"${FIRMWARE_BUILD_CFG}",
OPENOCD_COMMAND='-c "program ${SOURCE.posix} reset exit ${IMAGE_BASE_ADDRESS}"',
)
if fwenv["FORCE"]:
fwenv.AlwaysBuild(flash)
fwenv.Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_flash", flash)
if fwenv["IS_BASE_FIRMWARE"]:
fwenv.Alias("flash", flash)
# Compile DB generation # Compile DB generation
fwcdb = fwenv["FW_CDB"] = fwenv.CompilationDatabase("compile_commands.json") fwcdb = fwenv["FW_CDB"] = fwenv.CompilationDatabase("compile_commands.json")

View File

@@ -1,5 +1,6 @@
#include <furi_hal_power.h> #include <furi_hal_power.h>
#include <furi_hal_clock.h> #include <furi_hal_clock.h>
#include <furi_hal_delay.h>
#include <furi_hal_bt.h> #include <furi_hal_bt.h>
#include <furi_hal_resources.h> #include <furi_hal_resources.h>
#include <furi_hal_uart.h> #include <furi_hal_uart.h>
@@ -299,6 +300,10 @@ void furi_hal_power_shutdown() {
} }
void furi_hal_power_off() { void furi_hal_power_off() {
// Crutch: shutting down with ext 3V3 off is causing LSE to stop
furi_hal_power_enable_external_3_3v();
furi_hal_delay_us(1000);
// Send poweroff to charger
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
bq25896_poweroff(&furi_hal_i2c_handle_power); bq25896_poweroff(&furi_hal_i2c_handle_power);
furi_hal_i2c_release(&furi_hal_i2c_handle_power); furi_hal_i2c_release(&furi_hal_i2c_handle_power);
@@ -482,11 +487,11 @@ void furi_hal_power_dump_state() {
} }
void furi_hal_power_enable_external_3_3v() { void furi_hal_power_enable_external_3_3v() {
LL_GPIO_SetOutputPin(PERIPH_POWER_GPIO_Port, PERIPH_POWER_Pin); furi_hal_gpio_write(&periph_power, 1);
} }
void furi_hal_power_disable_external_3_3v() { void furi_hal_power_disable_external_3_3v() {
LL_GPIO_ResetOutputPin(PERIPH_POWER_GPIO_Port, PERIPH_POWER_Pin); furi_hal_gpio_write(&periph_power, 0);
} }
void furi_hal_power_suppress_charge_enter() { void furi_hal_power_suppress_charge_enter() {

View File

@@ -58,7 +58,7 @@ const GpioPin gpio_i2c_power_scl = {.port = GPIOA, .pin = LL_GPIO_PIN_9};
const GpioPin gpio_speaker = {.port = GPIOB, .pin = LL_GPIO_PIN_8}; const GpioPin gpio_speaker = {.port = GPIOB, .pin = LL_GPIO_PIN_8};
const GpioPin periph_power = {.port = PERIPH_POWER_GPIO_Port, .pin = PERIPH_POWER_Pin}; const GpioPin periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3};
const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11}; const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11};
const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12};
@@ -77,6 +77,10 @@ const size_t input_pins_count = sizeof(input_pins) / sizeof(InputPin);
void furi_hal_resources_init_early() { void furi_hal_resources_init_early() {
furi_hal_gpio_init(&gpio_button_left, GpioModeInput, GpioPullUp, GpioSpeedLow); furi_hal_gpio_init(&gpio_button_left, GpioModeInput, GpioPullUp, GpioSpeedLow);
// SD Card stepdown control
furi_hal_gpio_write(&periph_power, 1);
furi_hal_gpio_init(&periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
// Display pins // Display pins
furi_hal_gpio_write(&gpio_display_rst_n, 1); furi_hal_gpio_write(&gpio_display_rst_n, 1);
furi_hal_gpio_init_simple(&gpio_display_rst_n, GpioModeOutputPushPull); furi_hal_gpio_init_simple(&gpio_display_rst_n, GpioModeOutputPushPull);
@@ -142,8 +146,6 @@ void furi_hal_resources_init() {
furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(&periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
NVIC_EnableIRQ(EXTI0_IRQn); NVIC_EnableIRQ(EXTI0_IRQn);

View File

@@ -147,9 +147,6 @@ extern const GpioPin gpio_usb_dp;
#define PC3_GPIO_Port GPIOC #define PC3_GPIO_Port GPIOC
#define PC3_Pin LL_GPIO_PIN_3 #define PC3_Pin LL_GPIO_PIN_3
#define PERIPH_POWER_GPIO_Port GPIOA
#define PERIPH_POWER_Pin LL_GPIO_PIN_3
#define QUARTZ_32MHZ_IN_GPIO_Port GPIOC #define QUARTZ_32MHZ_IN_GPIO_Port GPIOC
#define QUARTZ_32MHZ_IN_Pin LL_GPIO_PIN_14 #define QUARTZ_32MHZ_IN_Pin LL_GPIO_PIN_14
#define QUARTZ_32MHZ_OUT_GPIO_Port GPIOC #define QUARTZ_32MHZ_OUT_GPIO_Port GPIOC

View File

@@ -12,7 +12,9 @@
#define TAG "FuriHalRtc" #define TAG "FuriHalRtc"
#define RTC_CLOCK_IS_READY() (LL_RCC_LSE_IsReady() && LL_RCC_LSI1_IsReady()) #define FURI_HAL_RTC_LSE_STARTUP_TIME 300
#define FURI_HAL_RTC_CLOCK_IS_READY() (LL_RCC_LSE_IsReady() && LL_RCC_LSI1_IsReady())
#define FURI_HAL_RTC_HEADER_MAGIC 0x10F1 #define FURI_HAL_RTC_HEADER_MAGIC 0x10F1
#define FURI_HAL_RTC_HEADER_VERSION 0 #define FURI_HAL_RTC_HEADER_VERSION 0
@@ -47,33 +49,83 @@ static const uint8_t furi_hal_rtc_days_per_month[][FURI_HAL_RTC_MONTHS_COUNT] =
static const uint16_t furi_hal_rtc_days_per_year[] = {365, 366}; static const uint16_t furi_hal_rtc_days_per_year[] = {365, 366};
void furi_hal_rtc_init_early() { static void furi_hal_rtc_reset() {
// LSE and RTC
LL_PWR_EnableBkUpAccess();
if(!RTC_CLOCK_IS_READY()) {
LL_RCC_LSI1_Enable();
// Try to start LSE normal way
LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_HIGH);
LL_RCC_LSE_Enable();
uint32_t c = 0;
while(!RTC_CLOCK_IS_READY() && c < 200) {
LL_mDelay(10);
c++;
}
// Plan B: reset backup domain
if(!RTC_CLOCK_IS_READY()) {
furi_hal_light_sequence("rgb R.r.R.r.R");
LL_RCC_ForceBackupDomainReset(); LL_RCC_ForceBackupDomainReset();
LL_RCC_ReleaseBackupDomainReset(); LL_RCC_ReleaseBackupDomainReset();
}
static bool furi_hal_rtc_start_clock_and_switch() {
// Clock operation require access to Backup Domain
LL_PWR_EnableBkUpAccess();
// Enable LSI and LSE
LL_RCC_LSI1_Enable();
LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_HIGH);
LL_RCC_LSE_Enable();
// Wait for LSI and LSE startup
uint32_t c = 0;
while(!FURI_HAL_RTC_CLOCK_IS_READY() && c < FURI_HAL_RTC_LSE_STARTUP_TIME) {
LL_mDelay(1);
c++;
}
if(FURI_HAL_RTC_CLOCK_IS_READY()) {
LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE);
LL_RCC_EnableRTC();
return LL_RCC_GetRTCClockSource() == LL_RCC_RTC_CLKSOURCE_LSE;
} else {
return false;
}
}
static void furi_hal_rtc_recover() {
FuriHalRtcDateTime datetime = {0};
// Handle fixable LSE failure
if(LL_RCC_LSE_IsCSSDetected()) {
furi_hal_light_sequence("rgb B");
// Shutdown LSE and LSECSS
LL_RCC_LSE_DisableCSS();
LL_RCC_LSE_Disable();
} else {
furi_hal_light_sequence("rgb R");
}
// Temporary switch to LSI
LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSI);
if(LL_RCC_GetRTCClockSource() == LL_RCC_RTC_CLKSOURCE_LSI) {
// Get datetime before RTC Domain reset
furi_hal_rtc_get_datetime(&datetime);
}
// Reset RTC Domain
furi_hal_rtc_reset();
// Start Clock
if(!furi_hal_rtc_start_clock_and_switch()) {
// Plan C: reset RTC and restart
furi_hal_light_sequence("rgb R.r.R.r.R.r");
furi_hal_rtc_reset();
NVIC_SystemReset(); NVIC_SystemReset();
} }
// Set RTC domain clock to LSE
LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE); // Set date if it valid
if(datetime.year != 0) {
furi_hal_rtc_set_datetime(&datetime);
} }
// Enable clocking }
LL_RCC_EnableRTC();
void furi_hal_rtc_init_early() {
// Enable RTCAPB clock
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_RTCAPB); LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_RTCAPB);
// Prepare clock
if(!furi_hal_rtc_start_clock_and_switch()) {
// Plan B: try to recover
furi_hal_rtc_recover();
}
// Verify header register // Verify header register
uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterHeader); uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterHeader);
FuriHalRtcHeader* data = (FuriHalRtcHeader*)&data_reg; FuriHalRtcHeader* data = (FuriHalRtcHeader*)&data_reg;
@@ -98,14 +150,6 @@ void furi_hal_rtc_deinit_early() {
} }
void furi_hal_rtc_init() { void furi_hal_rtc_init() {
if(LL_RCC_GetRTCClockSource() != LL_RCC_RTC_CLKSOURCE_LSE) {
LL_RCC_ForceBackupDomainReset();
LL_RCC_ReleaseBackupDomainReset();
LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE);
}
LL_RCC_EnableRTC();
LL_RTC_InitTypeDef RTC_InitStruct = {0}; LL_RTC_InitTypeDef RTC_InitStruct = {0};
RTC_InitStruct.HourFormat = LL_RTC_HOURFORMAT_24HOUR; RTC_InitStruct.HourFormat = LL_RTC_HOURFORMAT_24HOUR;
RTC_InitStruct.AsynchPrescaler = 127; RTC_InitStruct.AsynchPrescaler = 127;

View File

@@ -12,6 +12,8 @@
#define UPDATE_ROOT_DIR "/ext" UPDATE_DIR_DEFAULT_REL_PATH #define UPDATE_ROOT_DIR "/ext" UPDATE_DIR_DEFAULT_REL_PATH
#define UPDATE_PREFIX "/ext" UPDATE_DIR_DEFAULT_REL_PATH "/" #define UPDATE_PREFIX "/ext" UPDATE_DIR_DEFAULT_REL_PATH "/"
#define UPDATE_SUFFIX "/" UPDATE_MANIFEST_DEFAULT_NAME #define UPDATE_SUFFIX "/" UPDATE_MANIFEST_DEFAULT_NAME
/* Need at least 4 free LFS pages before update */
#define UPDATE_MIN_INT_FREE_SPACE 4 * 4 * 1024
static const char* update_prepare_result_descr[] = { static const char* update_prepare_result_descr[] = {
[UpdatePrepareResultOK] = "OK", [UpdatePrepareResultOK] = "OK",
@@ -22,6 +24,7 @@ static const char* update_prepare_result_descr[] = {
[UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader", [UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader",
[UpdatePrepareResultManifestPointerError] = "Failed to create update pointer file", [UpdatePrepareResultManifestPointerError] = "Failed to create update pointer file",
[UpdatePrepareResultOutdatedManifestVersion] = "Update package is too old", [UpdatePrepareResultOutdatedManifestVersion] = "Update package is too old",
[UpdatePrepareResultIntFull] = "Need more free space in internal storage",
}; };
const char* update_operation_describe_preparation_result(const UpdatePrepareResult value) { const char* update_operation_describe_preparation_result(const UpdatePrepareResult value) {
@@ -133,15 +136,22 @@ static bool update_operation_persist_manifest_path(Storage* storage, const char*
} }
UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) {
UpdatePrepareResult result = UpdatePrepareResultManifestFolderNotFound; UpdatePrepareResult result = UpdatePrepareResultIntFull;
Storage* storage = furi_record_open("storage"); Storage* storage = furi_record_open("storage");
UpdateManifest* manifest = update_manifest_alloc(); UpdateManifest* manifest = update_manifest_alloc();
File* file = storage_file_alloc(storage); File* file = storage_file_alloc(storage);
uint64_t free_int_space;
string_t stage_path; string_t stage_path;
string_init(stage_path); string_init(stage_path);
do { do {
if((storage_common_fs_info(storage, "/int", NULL, &free_int_space) != FSE_OK) ||
(free_int_space < UPDATE_MIN_INT_FREE_SPACE)) {
break;
}
if(storage_common_stat(storage, manifest_file_path, NULL) != FSE_OK) { if(storage_common_stat(storage, manifest_file_path, NULL) != FSE_OK) {
result = UpdatePrepareResultManifestFolderNotFound;
break; break;
} }

View File

@@ -32,6 +32,8 @@ typedef enum {
UpdatePrepareResultManifestPointerError, UpdatePrepareResultManifestPointerError,
UpdatePrepareResultTargetMismatch, UpdatePrepareResultTargetMismatch,
UpdatePrepareResultOutdatedManifestVersion, UpdatePrepareResultOutdatedManifestVersion,
UpdatePrepareResultIntFull,
UpdatePrepareResultUnspecifiedError,
} UpdatePrepareResult; } UpdatePrepareResult;
const char* update_operation_describe_preparation_result(const UpdatePrepareResult value); const char* update_operation_describe_preparation_result(const UpdatePrepareResult value);

View File

@@ -76,12 +76,15 @@ class Main(App):
manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2]
pkg_dir_name = self.args.pkg_dir_name or pkg_name pkg_dir_name = self.args.pkg_dir_name or pkg_name
flipper_update_path = f"/ext/update/{pkg_dir_name}" update_root = "/ext/update"
flipper_update_path = f"{update_root}/{pkg_dir_name}"
self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}')
# if not os.path.exists(self.args.manifest_path): # if not os.path.exists(self.args.manifest_path):
# self.logger.error("Error: package not found") # self.logger.error("Error: package not found")
if not self.mkdir_on_storage(storage, flipper_update_path): if not self.mkdir_on_storage(
storage, update_root
) or not self.mkdir_on_storage(storage, flipper_update_path):
self.logger.error(f"Error: cannot create {storage.last_error}") self.logger.error(f"Error: cannot create {storage.last_error}")
return -2 return -2
@@ -99,6 +102,14 @@ class Main(App):
storage.send_and_wait_eol( storage.send_and_wait_eol(
f"update install {flipper_update_path}/{manifest_name}\r" f"update install {flipper_update_path}/{manifest_name}\r"
) )
result = storage.read.until(storage.CLI_EOL)
if not b"Verifying" in result:
self.logger.error(f"Unexpected response: {result.decode('ascii')}")
return -4
result = storage.read.until(storage.CLI_EOL)
if not result.startswith(b"OK"):
self.logger.error(result.decode("ascii"))
return -5
break break
return 0 return 0
finally: finally:

View File

@@ -162,6 +162,12 @@ vars.Add(
default="", default="",
) )
vars.Add(
"BLACKMAGIC",
help="Blackmagic probe location",
default="auto",
)
vars.Add( vars.Add(
"UPDATE_SPLASH", "UPDATE_SPLASH",
help="Directory name with slideshow frames to render after installing update package", help="Directory name with slideshow frames to render after installing update package",

View File

@@ -78,5 +78,6 @@ coreenv["TEMPFILEARGESCFUNC"] = util.tempfile_arg_esc_func
util.wrap_tempfile(coreenv, "LINKCOM") util.wrap_tempfile(coreenv, "LINKCOM")
util.wrap_tempfile(coreenv, "ARCOM") util.wrap_tempfile(coreenv, "ARCOM")
coreenv["SINGLEQUOTEFUNC"] = util.single_quote
Return("coreenv") Return("coreenv")

View File

@@ -42,3 +42,7 @@ def random_alnum(length):
return "".join( return "".join(
random.choice(string.ascii_letters + string.digits) for _ in range(length) random.choice(string.ascii_letters + string.digits) for _ in range(length)
) )
def single_quote(arg_list):
return " ".join(f"'{arg}'" if " " in arg else str(arg) for arg in arg_list)

View File

@@ -0,0 +1,74 @@
from SCons.Errors import StopError
class BlackmagicResolver:
BLACKMAGIC_HOSTNAME = "blackmagic.local"
def __init__(self, env):
self.env = env
# On Win:
# 'location': '1-5:x.0', 'name': 'COM4',
# 'location': '1-5:x.2', 'name': 'COM13',
# On Linux:
# 'location': '1-1.2:1.0', 'name': 'ttyACM0',
# 'location': '1-1.2:1.2', 'name': 'ttyACM1',
# On MacOS:
# 'location': '0-1.3', 'name': 'cu.usbmodemblackmagic1',
# 'location': '0-1.3', 'name': 'cu.usbmodemblackmagic3',
def _find_probe(self):
import serial.tools.list_ports as list_ports
ports = list(list_ports.grep("blackmagic"))
if len(ports) == 0:
# Blackmagic probe serial port not found, will be handled later
pass
elif len(ports) > 2:
raise StopError("More than one Blackmagic probe found")
else:
# If you're getting any issues with auto lookup, uncomment this
# print("\n".join([f"{p.device} {vars(p)}" for p in ports]))
return sorted(ports, key=lambda p: f"{p.location}_{p.name}")[0]
# Look up blackmagic probe hostname with dns
def _resolve_hostname(self):
import socket
try:
return socket.gethostbyname(self.BLACKMAGIC_HOSTNAME)
except socket.gaierror:
print("Failed to resolve Blackmagic hostname")
return None
def get_serial(self):
if not (probe := self._find_probe()):
return None
# print(f"Found Blackmagic probe on {probe.device}")
if self.env.subst("$PLATFORM") == "win32":
return f"\\\\.\\{probe.device}"
return probe.device
def get_networked(self):
if not (probe := self._resolve_hostname()):
return None
return f"tcp:{probe}:2345"
def __str__(self):
# print("distenv blackmagic", self.env.subst("$BLACKMAGIC"))
if (blackmagic := self.env.subst("$BLACKMAGIC")) != "auto":
return blackmagic
# print("Looking for Blackmagic...")
if probe := self.get_serial() or self.get_networked():
return probe
raise Exception("Please specify BLACKMAGIC=...")
def generate(env):
env.SetDefault(BLACKMAGIC_ADDR=BlackmagicResolver(env))
def exists(env):
return True

View File

@@ -54,20 +54,20 @@ def AddFwProject(env, base_env, fw_type, fw_env_key):
return project_env return project_env
def AddDebugTarget(env, alias, targetenv, force_flash=True): def AddOpenOCDFlashTarget(env, targetenv, **kw):
debug_target = env.PhonyTarget( openocd_target = env.OpenOCDFlash(
alias, "#build/oocd-${BUILD_CFG}-flash.flag",
"$GDBPYCOM", targetenv["FW_BIN"],
source=targetenv["FW_ELF"], OPENOCD_COMMAND=[
GDBPYOPTS='-ex "source debug/FreeRTOS/FreeRTOS.py" ' "-c",
'-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ' "program ${SOURCE.posix} reset exit ${BASE_ADDRESS}",
'-ex "svd_load ${SVD_FILE}" ' ],
'-ex "compare-sections"', BUILD_CFG=targetenv.subst("$FIRMWARE_BUILD_CFG"),
BASE_ADDRESS=targetenv.subst("$IMAGE_BASE_ADDRESS"),
**kw,
) )
if force_flash: env.Alias(targetenv.subst("${FIRMWARE_BUILD_CFG}_flash"), openocd_target)
env.Depends(debug_target, targetenv["FW_FLASH"]) return openocd_target
return debug_target
def DistCommand(env, name, source, **kw): def DistCommand(env, name, source, **kw):
@@ -85,8 +85,9 @@ def DistCommand(env, name, source, **kw):
def generate(env): def generate(env):
env.AddMethod(AddFwProject) env.AddMethod(AddFwProject)
env.AddMethod(AddDebugTarget)
env.AddMethod(DistCommand) env.AddMethod(DistCommand)
env.AddMethod(AddOpenOCDFlashTarget)
env.SetDefault( env.SetDefault(
COPRO_MCU_FAMILY="STM32WB5x", COPRO_MCU_FAMILY="STM32WB5x",
) )

View File

@@ -34,9 +34,9 @@ def PhonyTarget(env, name, action, source=None, **kw):
source = [] source = []
phony_name = "phony_" + name phony_name = "phony_" + name
env.Pseudo(phony_name) env.Pseudo(phony_name)
return env.AlwaysBuild( command = env.Command(phony_name, source, action, **kw)
env.Alias(name, env.Command(phony_name, source, action, **kw)) env.AlwaysBuild(env.Alias(name, command))
) return command
def generate(env): def generate(env):