Merge remote-tracking branch 'ofw/dev' into mntm-dev
18
CHANGELOG.md
@@ -3,6 +3,11 @@
|
||||
- Games: Pinball0 (by @rdefeo)
|
||||
- NFC: Metroflip (by @luu176)
|
||||
- UL: Sub-GHz: Jolly Motors support with add manually (by @pkooiman & @xMasterX)
|
||||
- OFW: Desktop: Add winter animations (by @Astrrra)
|
||||
- OFW: Furi: Pipe support (by @portasynthinca3)
|
||||
- OFW: Furi: Thread stdin support (by @portasynthinca3)
|
||||
- OFW: RPC: Command to send a signal once (by @Astrrra)
|
||||
- OFW: API: Added `flipper_format_write_empty_line()` (by @janwiesemann)
|
||||
- OFW: Add VCP break support (by @gsurkov)
|
||||
|
||||
### Updated:
|
||||
@@ -32,10 +37,19 @@
|
||||
|
||||
### Fixed:
|
||||
- Desktop: Fixed Wardriving animation design (by @Davim09)
|
||||
- OFW: Fix lost BadBLE keystrokes (by @Astrrra)
|
||||
- OFW: GPIO: Fix USB UART Bridge Crash by increasing system stack size (by @Astrrra)
|
||||
- OFW: Loader: Fix BusFault in handling of OOM (by @Willy-JL)
|
||||
- NFC:
|
||||
- OFW: Plantain parser Last payment amount fix (by @mxcdoam)
|
||||
- OFW: Fix typo for mf_classic_key_cahce_get_next_key() function (by @luu176)
|
||||
- OFW: Fix skylander ID reading (by @bettse)
|
||||
- OFW: Fix ISO15693 stuck in wrong mode (by @RebornedBrain)
|
||||
- OFW: Fix MFUL PWD_AUTH command creation when 0x00 in password (by @GMMan)
|
||||
- OFW: Fix typo for `mf_classic_key_cahce_get_next_key()` function (by @luu176)
|
||||
- OFW: U2F: Fix message digest memory leak (by @GMMan)
|
||||
- OFW: JS: SDK workaround incorrect serial port handling by OS (by @portasynthinca3)
|
||||
- OFW: FBT: Fix invalid path errors on Windows with UTF8 paths (by @Alex4386)
|
||||
|
||||
### Removed:
|
||||
- Nothing
|
||||
- NFC: Previous fix for ISO15693 stuck in wrong mode (#225)
|
||||
- Removes APIs `nfc_iso15693_detect_mode()`, `nfc_iso15693_force_1outof4()`, `nfc_iso15693_force_1outof256()`
|
||||
|
||||
@@ -236,3 +236,11 @@ App(
|
||||
entry_point="get_api",
|
||||
requires=["unit_tests"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="test_pipe",
|
||||
sources=["tests/common/*.c", "tests/pipe/*.c"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="get_api",
|
||||
requires=["unit_tests"],
|
||||
)
|
||||
|
||||
@@ -265,6 +265,7 @@ static bool test_write(const char* file_name) {
|
||||
if(!flipper_format_file_open_always(file, file_name)) break;
|
||||
if(!flipper_format_write_header_cstr(file, test_filetype, test_version)) break;
|
||||
if(!flipper_format_write_comment_cstr(file, "This is comment")) break;
|
||||
if(!flipper_format_write_empty_line(file)) break;
|
||||
if(!flipper_format_write_string_cstr(file, test_string_key, test_string_data)) break;
|
||||
if(!flipper_format_write_int32(file, test_int_key, test_int_data, COUNT_OF(test_int_data)))
|
||||
break;
|
||||
|
||||
108
applications/debug/unit_tests/tests/furi/furi_stdio_test.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#include <furi.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include "../test.h" // IWYU pragma: keep
|
||||
|
||||
#define TAG "StdioTest"
|
||||
|
||||
#define CONTEXT_MAGIC ((void*)0xDEADBEEF)
|
||||
|
||||
// stdin
|
||||
|
||||
static char mock_in[256];
|
||||
static size_t mock_in_len, mock_in_pos;
|
||||
|
||||
static void set_mock_in(const char* str) {
|
||||
size_t len = strlen(str);
|
||||
strcpy(mock_in, str);
|
||||
mock_in_len = len;
|
||||
mock_in_pos = 0;
|
||||
}
|
||||
|
||||
static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context) {
|
||||
UNUSED(wait);
|
||||
furi_check(context == CONTEXT_MAGIC);
|
||||
size_t remaining = mock_in_len - mock_in_pos;
|
||||
size = MIN(remaining, size);
|
||||
memcpy(buffer, mock_in + mock_in_pos, size);
|
||||
mock_in_pos += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
void test_stdin(void) {
|
||||
FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback();
|
||||
furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC);
|
||||
char buf[256];
|
||||
|
||||
// plain in
|
||||
set_mock_in("Hello, World!\n");
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
mu_assert_string_eq("Hello, World!\n", buf);
|
||||
mu_assert_int_eq(EOF, getchar());
|
||||
|
||||
// ungetc
|
||||
ungetc('i', stdin);
|
||||
ungetc('H', stdin);
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
mu_assert_string_eq("Hi", buf);
|
||||
mu_assert_int_eq(EOF, getchar());
|
||||
|
||||
// ungetc + plain in
|
||||
set_mock_in(" World");
|
||||
ungetc('i', stdin);
|
||||
ungetc('H', stdin);
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
mu_assert_string_eq("Hi World", buf);
|
||||
mu_assert_int_eq(EOF, getchar());
|
||||
|
||||
// partial plain in
|
||||
set_mock_in("Hello, World!\n");
|
||||
fgets(buf, strlen("Hello") + 1, stdin);
|
||||
mu_assert_string_eq("Hello", buf);
|
||||
mu_assert_int_eq(',', getchar());
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
mu_assert_string_eq(" World!\n", buf);
|
||||
|
||||
furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC);
|
||||
}
|
||||
|
||||
// stdout
|
||||
|
||||
static FuriString* mock_out;
|
||||
FuriThreadStdoutWriteCallback original_out_cb;
|
||||
|
||||
static void mock_out_cb(const char* data, size_t size, void* context) {
|
||||
furi_check(context == CONTEXT_MAGIC);
|
||||
// there's no furi_string_cat_strn :(
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
furi_string_push_back(mock_out, data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void assert_and_clear_mock_out(const char* expected) {
|
||||
// return the original stdout callback for the duration of the check
|
||||
// if the check fails, we don't want the error to end up in our buffer,
|
||||
// we want to be able to see it!
|
||||
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
|
||||
mu_assert_string_eq(expected, furi_string_get_cstr(mock_out));
|
||||
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);
|
||||
|
||||
furi_string_reset(mock_out);
|
||||
}
|
||||
|
||||
void test_stdout(void) {
|
||||
original_out_cb = furi_thread_get_stdout_callback();
|
||||
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);
|
||||
mock_out = furi_string_alloc();
|
||||
|
||||
puts("Hello, World!");
|
||||
assert_and_clear_mock_out("Hello, World!\n");
|
||||
|
||||
printf("He");
|
||||
printf("llo!");
|
||||
fflush(stdout);
|
||||
assert_and_clear_mock_out("Hello!");
|
||||
|
||||
furi_string_free(mock_out);
|
||||
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
|
||||
}
|
||||
@@ -10,6 +10,8 @@ void test_furi_memmgr(void);
|
||||
void test_furi_event_loop(void);
|
||||
void test_errno_saving(void);
|
||||
void test_furi_primitives(void);
|
||||
void test_stdin(void);
|
||||
void test_stdout(void);
|
||||
|
||||
static int foo = 0;
|
||||
|
||||
@@ -52,6 +54,11 @@ MU_TEST(mu_test_furi_primitives) {
|
||||
test_furi_primitives();
|
||||
}
|
||||
|
||||
MU_TEST(mu_test_stdio) {
|
||||
test_stdin();
|
||||
test_stdout();
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_suite) {
|
||||
MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
|
||||
MU_RUN_TEST(test_check);
|
||||
@@ -61,6 +68,7 @@ MU_TEST_SUITE(test_suite) {
|
||||
MU_RUN_TEST(mu_test_furi_pubsub);
|
||||
MU_RUN_TEST(mu_test_furi_memmgr);
|
||||
MU_RUN_TEST(mu_test_furi_event_loop);
|
||||
MU_RUN_TEST(mu_test_stdio);
|
||||
MU_RUN_TEST(mu_test_errno_saving);
|
||||
MU_RUN_TEST(mu_test_furi_primitives);
|
||||
}
|
||||
|
||||
153
applications/debug/unit_tests/tests/pipe/pipe_test.c
Normal file
@@ -0,0 +1,153 @@
|
||||
#include "../test.h" // IWYU pragma: keep
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/toolbox/pipe.h>
|
||||
|
||||
#define PIPE_SIZE 128U
|
||||
#define PIPE_TRG_LEVEL 1U
|
||||
|
||||
MU_TEST(pipe_test_trivial) {
|
||||
PipeSideBundle bundle = pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL);
|
||||
PipeSide* alice = bundle.alices_side;
|
||||
PipeSide* bob = bundle.bobs_side;
|
||||
|
||||
mu_assert_int_eq(PipeRoleAlice, pipe_role(alice));
|
||||
mu_assert_int_eq(PipeRoleBob, pipe_role(bob));
|
||||
mu_assert_int_eq(PipeStateOpen, pipe_state(alice));
|
||||
mu_assert_int_eq(PipeStateOpen, pipe_state(bob));
|
||||
|
||||
mu_assert_int_eq(PIPE_SIZE, pipe_spaces_available(alice));
|
||||
mu_assert_int_eq(PIPE_SIZE, pipe_spaces_available(bob));
|
||||
mu_assert_int_eq(0, pipe_bytes_available(alice));
|
||||
mu_assert_int_eq(0, pipe_bytes_available(bob));
|
||||
|
||||
for(uint8_t i = 0;; ++i) {
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(alice));
|
||||
mu_assert_int_eq(i, pipe_bytes_available(bob));
|
||||
|
||||
if(pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(bob));
|
||||
mu_assert_int_eq(i, pipe_bytes_available(alice));
|
||||
|
||||
if(pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pipe_free(alice);
|
||||
mu_assert_int_eq(PipeStateBroken, pipe_state(bob));
|
||||
|
||||
for(uint8_t i = 0;; ++i) {
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_bytes_available(bob));
|
||||
|
||||
uint8_t value;
|
||||
if(pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
|
||||
mu_assert_int_eq(i, value);
|
||||
}
|
||||
|
||||
pipe_free(bob);
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
TestFlagDataArrived = 1 << 0,
|
||||
TestFlagSpaceFreed = 1 << 1,
|
||||
TestFlagBecameBroken = 1 << 2,
|
||||
} TestFlag;
|
||||
|
||||
typedef struct {
|
||||
TestFlag flag;
|
||||
FuriEventLoop* event_loop;
|
||||
} AncillaryThreadContext;
|
||||
|
||||
static void on_data_arrived(PipeSide* pipe, void* context) {
|
||||
AncillaryThreadContext* ctx = context;
|
||||
ctx->flag |= TestFlagDataArrived;
|
||||
uint8_t buffer[PIPE_SIZE];
|
||||
size_t size = pipe_receive(pipe, buffer, sizeof(buffer), 0);
|
||||
pipe_send(pipe, buffer, size, 0);
|
||||
}
|
||||
|
||||
static void on_space_freed(PipeSide* pipe, void* context) {
|
||||
AncillaryThreadContext* ctx = context;
|
||||
ctx->flag |= TestFlagSpaceFreed;
|
||||
const char* message = "Hi!";
|
||||
pipe_send(pipe, message, strlen(message), 0);
|
||||
}
|
||||
|
||||
static void on_became_broken(PipeSide* pipe, void* context) {
|
||||
UNUSED(pipe);
|
||||
AncillaryThreadContext* ctx = context;
|
||||
ctx->flag |= TestFlagBecameBroken;
|
||||
furi_event_loop_stop(ctx->event_loop);
|
||||
}
|
||||
|
||||
static int32_t ancillary_thread(void* context) {
|
||||
PipeSide* pipe = context;
|
||||
AncillaryThreadContext thread_ctx = {
|
||||
.flag = 0,
|
||||
.event_loop = furi_event_loop_alloc(),
|
||||
};
|
||||
|
||||
pipe_attach_to_event_loop(pipe, thread_ctx.event_loop);
|
||||
pipe_set_callback_context(pipe, &thread_ctx);
|
||||
pipe_set_data_arrived_callback(pipe, on_data_arrived, 0);
|
||||
pipe_set_space_freed_callback(pipe, on_space_freed, FuriEventLoopEventFlagEdge);
|
||||
pipe_set_broken_callback(pipe, on_became_broken, 0);
|
||||
|
||||
furi_event_loop_run(thread_ctx.event_loop);
|
||||
|
||||
pipe_detach_from_event_loop(pipe);
|
||||
pipe_free(pipe);
|
||||
furi_event_loop_free(thread_ctx.event_loop);
|
||||
return thread_ctx.flag;
|
||||
}
|
||||
|
||||
MU_TEST(pipe_test_event_loop) {
|
||||
PipeSideBundle bundle = pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL);
|
||||
PipeSide* alice = bundle.alices_side;
|
||||
PipeSide* bob = bundle.bobs_side;
|
||||
|
||||
FuriThread* thread = furi_thread_alloc_ex("PipeTestAnc", 2048, ancillary_thread, bob);
|
||||
furi_thread_start(thread);
|
||||
|
||||
const char* message = "Hello!";
|
||||
pipe_send(alice, message, strlen(message), FuriWaitForever);
|
||||
|
||||
char buffer_1[16];
|
||||
size_t size = pipe_receive(alice, buffer_1, sizeof(buffer_1), FuriWaitForever);
|
||||
buffer_1[size] = 0;
|
||||
|
||||
char buffer_2[16];
|
||||
const char* expected_reply = "Hi!";
|
||||
size = pipe_receive(alice, buffer_2, sizeof(buffer_2), FuriWaitForever);
|
||||
buffer_2[size] = 0;
|
||||
|
||||
pipe_free(alice);
|
||||
furi_thread_join(thread);
|
||||
|
||||
mu_assert_string_eq(message, buffer_1);
|
||||
mu_assert_string_eq(expected_reply, buffer_2);
|
||||
mu_assert_int_eq(
|
||||
TestFlagDataArrived | TestFlagSpaceFreed | TestFlagBecameBroken,
|
||||
furi_thread_get_return_code(thread));
|
||||
|
||||
furi_thread_free(thread);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_pipe) {
|
||||
MU_RUN_TEST(pipe_test_trivial);
|
||||
MU_RUN_TEST(pipe_test_event_loop);
|
||||
}
|
||||
|
||||
int run_minunit_test_pipe(void) {
|
||||
MU_RUN_SUITE(test_pipe);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
|
||||
TEST_API_DEFINE(run_minunit_test_pipe)
|
||||
@@ -157,6 +157,7 @@ static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) {
|
||||
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data);
|
||||
evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data;
|
||||
// aci_gatt_attribute_modified_event_rp0* attribute_modified;
|
||||
|
||||
if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) {
|
||||
if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) {
|
||||
// Process modification events
|
||||
@@ -274,6 +275,7 @@ bool ble_svc_hid_update_input_report(
|
||||
.data_ptr = data,
|
||||
.data_len = len,
|
||||
};
|
||||
|
||||
return ble_gatt_characteristic_update(
|
||||
hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,19 @@ static void infrared_rpc_command_callback(const RpcAppSystemEvent* event, void*
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressIndex);
|
||||
}
|
||||
} else if(event->type == RpcAppEventTypeButtonPressRelease) {
|
||||
furi_assert(
|
||||
event->data.type == RpcAppSystemEventDataTypeString ||
|
||||
event->data.type == RpcAppSystemEventDataTypeInt32);
|
||||
if(event->data.type == RpcAppSystemEventDataTypeString) {
|
||||
furi_string_set(infrared->button_name, event->data.string);
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressReleaseName);
|
||||
} else {
|
||||
infrared->app_state.current_button_index = event->data.i32;
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressReleaseIndex);
|
||||
}
|
||||
} else if(event->type == RpcAppEventTypeButtonRelease) {
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonRelease);
|
||||
@@ -402,6 +415,26 @@ void infrared_tx_stop(InfraredApp* infrared) {
|
||||
infrared->app_state.last_transmit_time = furi_get_tick();
|
||||
}
|
||||
|
||||
void infrared_tx_send_once(InfraredApp* infrared) {
|
||||
if(infrared->app_state.is_transmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
dolphin_deed(DolphinDeedIrSend);
|
||||
infrared_signal_transmit(infrared->current_signal);
|
||||
}
|
||||
|
||||
InfraredErrorCode infrared_tx_send_once_button_index(InfraredApp* infrared, size_t button_index) {
|
||||
furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote));
|
||||
|
||||
InfraredErrorCode error = infrared_remote_load_signal(
|
||||
infrared->remote, infrared->current_signal, infrared->app_state.current_button_index);
|
||||
if(!INFRARED_ERROR_PRESENT(error)) {
|
||||
infrared_tx_send_once(infrared);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) {
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewLoading);
|
||||
furi_thread_set_callback(infrared->task_thread, callback);
|
||||
|
||||
@@ -218,6 +218,20 @@ InfraredErrorCode infrared_tx_start_button_index(InfraredApp* infrared, size_t b
|
||||
*/
|
||||
void infrared_tx_stop(InfraredApp* infrared);
|
||||
|
||||
/**
|
||||
* @brief Transmit the currently loaded signal once.
|
||||
*
|
||||
* @param[in,out] infrared pointer to the application instance.
|
||||
*/
|
||||
void infrared_tx_send_once(InfraredApp* infrared);
|
||||
|
||||
/**
|
||||
* @brief Load the signal under the given index and transmit it once.
|
||||
*
|
||||
* @param[in,out] infrared pointer to the application instance.
|
||||
*/
|
||||
InfraredErrorCode infrared_tx_send_once_button_index(InfraredApp* infrared, size_t button_index);
|
||||
|
||||
/**
|
||||
* @brief Start a blocking task in a separate thread.
|
||||
*
|
||||
|
||||
@@ -21,6 +21,8 @@ enum InfraredCustomEventType {
|
||||
InfraredCustomEventTypeRpcButtonPressName,
|
||||
InfraredCustomEventTypeRpcButtonPressIndex,
|
||||
InfraredCustomEventTypeRpcButtonRelease,
|
||||
InfraredCustomEventTypeRpcButtonPressReleaseName,
|
||||
InfraredCustomEventTypeRpcButtonPressReleaseIndex,
|
||||
InfraredCustomEventTypeRpcSessionClose,
|
||||
|
||||
InfraredCustomEventTypeGpioTxPinChanged,
|
||||
|
||||
@@ -124,6 +124,49 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
rpc_system_app_confirm(infrared->rpc_ctx, result);
|
||||
|
||||
} else if(
|
||||
event.event == InfraredCustomEventTypeRpcButtonPressReleaseName ||
|
||||
event.event == InfraredCustomEventTypeRpcButtonPressReleaseIndex) {
|
||||
bool result = false;
|
||||
|
||||
// Send the signal once and stop
|
||||
if(rpc_state == InfraredRpcStateLoaded) {
|
||||
if(event.event == InfraredCustomEventTypeRpcButtonPressReleaseName) {
|
||||
const char* button_name = furi_string_get_cstr(infrared->button_name);
|
||||
size_t index;
|
||||
const bool index_found =
|
||||
infrared_remote_get_signal_index(infrared->remote, button_name, &index);
|
||||
app_state->current_button_index = index_found ? (signed)index :
|
||||
InfraredButtonIndexNone;
|
||||
FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name);
|
||||
} else {
|
||||
FURI_LOG_D(
|
||||
TAG, "Sending signal with index \"%ld\"", app_state->current_button_index);
|
||||
}
|
||||
if(infrared->app_state.current_button_index != InfraredButtonIndexNone) {
|
||||
InfraredErrorCode error = infrared_tx_send_once_button_index(
|
||||
infrared, app_state->current_button_index);
|
||||
if(!INFRARED_ERROR_PRESENT(error)) {
|
||||
const char* remote_name = infrared_remote_get_name(infrared->remote);
|
||||
infrared_text_store_set(infrared, 0, "emulating\n%s", remote_name);
|
||||
|
||||
infrared_scene_rpc_show(infrared);
|
||||
result = true;
|
||||
} else {
|
||||
rpc_system_app_set_error_code(
|
||||
infrared->rpc_ctx, RpcAppSystemErrorCodeInternalParse);
|
||||
rpc_system_app_set_error_text(
|
||||
infrared->rpc_ctx, "Cannot load button data");
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(result) {
|
||||
scene_manager_set_scene_state(
|
||||
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
|
||||
}
|
||||
rpc_system_app_confirm(infrared->rpc_ctx, result);
|
||||
} else if(
|
||||
event.event == InfraredCustomEventTypeRpcExit ||
|
||||
event.event == InfraredCustomEventTypeRpcSessionClose ||
|
||||
|
||||
@@ -7,13 +7,36 @@
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define TAG "Skylanders"
|
||||
#define TAG "Skylanders"
|
||||
#define POLY UINT64_C(0x42f0e1eba9ea3693)
|
||||
#define TOP UINT64_C(0x800000000000)
|
||||
#define UID_LEN 4
|
||||
#define KEY_MASK 0xFFFFFFFFFFFF
|
||||
|
||||
static const uint64_t skylanders_key = 0x4b0b20107ccb;
|
||||
|
||||
static const char* nfc_resources_header = "Flipper NFC resources";
|
||||
static const uint32_t nfc_resources_file_version = 1;
|
||||
|
||||
uint64_t crc64_like(uint64_t result, uint8_t sector) {
|
||||
result ^= (uint64_t)sector << 40;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
result = (result & TOP) ? (result << 1) ^ POLY : result << 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t taghash(uint32_t uid) {
|
||||
uint64_t result = 0x9AE903260CC4;
|
||||
uint8_t uidBytes[UID_LEN] = {0};
|
||||
memcpy(uidBytes, &uid, UID_LEN);
|
||||
|
||||
for(int i = 0; i < UID_LEN; i++) {
|
||||
result = crc64_like(result, uidBytes[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool skylanders_search_data(
|
||||
Storage* storage,
|
||||
const char* file_name,
|
||||
@@ -88,6 +111,12 @@ static bool skylanders_read(Nfc* nfc, NfcDevice* device) {
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
size_t* uid_len = 0;
|
||||
const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len);
|
||||
uint32_t uid = 0;
|
||||
memcpy(&uid, uid_bytes, sizeof(uid));
|
||||
uint64_t hash = taghash(uid);
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicType1k;
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
@@ -96,10 +125,18 @@ static bool skylanders_read(Nfc* nfc, NfcDevice* device) {
|
||||
data->type = type;
|
||||
MfClassicDeviceKeys keys = {};
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
if(i == 0) {
|
||||
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
} else {
|
||||
uint64_t sectorhash = crc64_like(hash, i);
|
||||
uint64_t key = sectorhash & KEY_MASK;
|
||||
uint8_t* keyBytes = (uint8_t*)&key;
|
||||
memcpy(keys.key_a[i].data, keyBytes, sizeof(MfClassicKey));
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
memset(keys.key_b[i].data, 0, sizeof(MfClassicKey));
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
@@ -134,7 +171,7 @@ static bool skylanders_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
|
||||
if(key != skylanders_key) break;
|
||||
|
||||
const uint16_t id = (uint16_t)*data->block[1].data;
|
||||
const uint16_t id = data->block[1].data[1] << 8 | data->block[1].data[0];
|
||||
if(id == 0) break;
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
@@ -35,6 +35,7 @@ typedef enum {
|
||||
SubGhzCustomEventSceneRpcLoad,
|
||||
SubGhzCustomEventSceneRpcButtonPress,
|
||||
SubGhzCustomEventSceneRpcButtonRelease,
|
||||
SubGhzCustomEventSceneRpcButtonPressRelease,
|
||||
SubGhzCustomEventSceneRpcSessionClose,
|
||||
|
||||
SubGhzCustomEventViewReceiverOK,
|
||||
|
||||
@@ -87,6 +87,43 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
|
||||
rpc_system_app_confirm(subghz->rpc_ctx, result);
|
||||
} else if(event.event == SubGhzCustomEventSceneRpcButtonPressRelease) {
|
||||
bool result = false;
|
||||
if(state == SubGhzRpcStateLoaded) {
|
||||
switch(
|
||||
subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) {
|
||||
case SubGhzTxRxStartTxStateErrorOnlyRx:
|
||||
rpc_system_app_set_error_code(
|
||||
subghz->rpc_ctx, RpcAppSystemErrorCodeRegionLock);
|
||||
rpc_system_app_set_error_text(
|
||||
subghz->rpc_ctx,
|
||||
"Transmission on this frequency is restricted in your region");
|
||||
break;
|
||||
case SubGhzTxRxStartTxStateErrorParserOthers:
|
||||
rpc_system_app_set_error_code(
|
||||
subghz->rpc_ctx, RpcAppSystemErrorCodeInternalParse);
|
||||
rpc_system_app_set_error_text(
|
||||
subghz->rpc_ctx, "Error in protocol parameters description");
|
||||
break;
|
||||
|
||||
default: //if(SubGhzTxRxStartTxStateOk)
|
||||
result = true;
|
||||
subghz_blink_start(subghz);
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateTx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop transmission
|
||||
if(state == SubGhzRpcStateTx) {
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz_blink_stop(subghz);
|
||||
result = true;
|
||||
}
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
|
||||
rpc_system_app_confirm(subghz->rpc_ctx, result);
|
||||
} else if(event.event == SubGhzCustomEventSceneRpcLoad) {
|
||||
bool result = false;
|
||||
if(state == SubGhzRpcStateIdle) {
|
||||
|
||||
@@ -53,6 +53,9 @@ static void subghz_rpc_command_callback(const RpcAppSystemEvent* event, void* co
|
||||
} else if(event->type == RpcAppEventTypeButtonRelease) {
|
||||
view_dispatcher_send_custom_event(
|
||||
subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonRelease);
|
||||
} else if(event->type == RpcAppEventTypeButtonPressRelease) {
|
||||
view_dispatcher_send_custom_event(
|
||||
subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonPressRelease);
|
||||
} else {
|
||||
rpc_system_app_confirm(subghz->rpc_ctx, false);
|
||||
}
|
||||
|
||||
@@ -280,6 +280,8 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
||||
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, private, sizeof(private)));
|
||||
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id)));
|
||||
MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, handle.hash));
|
||||
|
||||
mbedtls_md_free(&hmac_ctx);
|
||||
}
|
||||
|
||||
// Generate public key
|
||||
@@ -387,6 +389,8 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, priv_key, sizeof(priv_key)));
|
||||
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id)));
|
||||
MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, mac_control));
|
||||
|
||||
mbedtls_md_free(&hmac_ctx);
|
||||
}
|
||||
|
||||
if(memcmp(req->key_handle.hash, mac_control, sizeof(mac_control)) != 0) {
|
||||
|
||||
@@ -533,9 +533,9 @@ void cli_session_open(Cli* cli, void* session) {
|
||||
cli->session = session;
|
||||
if(cli->session != NULL) {
|
||||
cli->session->init();
|
||||
furi_thread_set_stdout_callback(cli->session->tx_stdout);
|
||||
furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL);
|
||||
} else {
|
||||
furi_thread_set_stdout_callback(NULL);
|
||||
furi_thread_set_stdout_callback(NULL, NULL);
|
||||
}
|
||||
furi_semaphore_release(cli->idle_sem);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
@@ -549,7 +549,7 @@ void cli_session_close(Cli* cli) {
|
||||
cli->session->deinit();
|
||||
}
|
||||
cli->session = NULL;
|
||||
furi_thread_set_stdout_callback(NULL);
|
||||
furi_thread_set_stdout_callback(NULL, NULL);
|
||||
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
@@ -563,9 +563,9 @@ int32_t cli_srv(void* p) {
|
||||
furi_record_create(RECORD_CLI, cli);
|
||||
|
||||
if(cli->session != NULL) {
|
||||
furi_thread_set_stdout_callback(cli->session->tx_stdout);
|
||||
furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL);
|
||||
} else {
|
||||
furi_thread_set_stdout_callback(NULL);
|
||||
furi_thread_set_stdout_callback(NULL, NULL);
|
||||
}
|
||||
|
||||
if(furi_hal_is_normal_boot()) {
|
||||
|
||||
@@ -28,8 +28,9 @@ struct CliSession {
|
||||
void (*init)(void);
|
||||
void (*deinit)(void);
|
||||
size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout);
|
||||
size_t (*rx_stdin)(uint8_t* buffer, size_t size, uint32_t timeout, void* context);
|
||||
void (*tx)(const uint8_t* buffer, size_t size);
|
||||
void (*tx_stdout)(const char* data, size_t size);
|
||||
void (*tx_stdout)(const char* data, size_t size, void* context);
|
||||
bool (*is_connected)(void);
|
||||
};
|
||||
|
||||
|
||||
@@ -243,6 +243,11 @@ static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) {
|
||||
return rx_cnt;
|
||||
}
|
||||
|
||||
static size_t cli_vcp_rx_stdin(uint8_t* data, size_t size, uint32_t timeout, void* context) {
|
||||
UNUSED(context);
|
||||
return cli_vcp_rx(data, size, timeout);
|
||||
}
|
||||
|
||||
static void cli_vcp_tx(const uint8_t* buffer, size_t size) {
|
||||
furi_assert(vcp);
|
||||
furi_assert(buffer);
|
||||
@@ -268,7 +273,8 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) {
|
||||
VCP_DEBUG("tx %u end", size);
|
||||
}
|
||||
|
||||
static void cli_vcp_tx_stdout(const char* data, size_t size) {
|
||||
static void cli_vcp_tx_stdout(const char* data, size_t size, void* context) {
|
||||
UNUSED(context);
|
||||
cli_vcp_tx((const uint8_t*)data, size);
|
||||
}
|
||||
|
||||
@@ -311,6 +317,7 @@ CliSession cli_vcp = {
|
||||
cli_vcp_init,
|
||||
cli_vcp_deinit,
|
||||
cli_vcp_rx,
|
||||
cli_vcp_rx_stdin,
|
||||
cli_vcp_tx,
|
||||
cli_vcp_tx_stdout,
|
||||
cli_vcp_is_connected,
|
||||
|
||||
@@ -258,6 +258,41 @@ static void rpc_system_app_button_release(const PB_Main* request, void* context)
|
||||
}
|
||||
}
|
||||
|
||||
static void rpc_system_app_button_press_release(const PB_Main* request, void* context) {
|
||||
furi_assert(request);
|
||||
furi_assert(request->which_content == PB_Main_app_button_press_release_request_tag);
|
||||
|
||||
RpcAppSystem* rpc_app = context;
|
||||
furi_assert(rpc_app);
|
||||
|
||||
if(rpc_app->callback) {
|
||||
FURI_LOG_D(TAG, "ButtonPressRelease");
|
||||
|
||||
RpcAppSystemEvent event;
|
||||
event.type = RpcAppEventTypeButtonPressRelease;
|
||||
|
||||
if(strlen(request->content.app_button_press_release_request.args) != 0) {
|
||||
event.data.type = RpcAppSystemEventDataTypeString;
|
||||
event.data.string = request->content.app_button_press_release_request.args;
|
||||
} else {
|
||||
event.data.type = RpcAppSystemEventDataTypeInt32;
|
||||
event.data.i32 = request->content.app_button_press_release_request.index;
|
||||
}
|
||||
|
||||
rpc_system_app_error_reset(rpc_app);
|
||||
rpc_system_app_set_last_command(rpc_app, request->command_id, &event);
|
||||
|
||||
rpc_app->callback(&event, rpc_app->callback_context);
|
||||
|
||||
} else {
|
||||
rpc_system_app_send_error_response(
|
||||
rpc_app,
|
||||
request->command_id,
|
||||
PB_CommandStatus_ERROR_APP_NOT_RUNNING,
|
||||
"ButtonPressRelease");
|
||||
}
|
||||
}
|
||||
|
||||
static void rpc_system_app_get_error_process(const PB_Main* request, void* context) {
|
||||
furi_assert(request);
|
||||
furi_assert(request->which_content == PB_Main_app_get_error_request_tag);
|
||||
@@ -332,6 +367,7 @@ void rpc_system_app_confirm(RpcAppSystem* rpc_app, bool result) {
|
||||
rpc_app->last_event_type == RpcAppEventTypeLoadFile ||
|
||||
rpc_app->last_event_type == RpcAppEventTypeButtonPress ||
|
||||
rpc_app->last_event_type == RpcAppEventTypeButtonRelease ||
|
||||
rpc_app->last_event_type == RpcAppEventTypeButtonPressRelease ||
|
||||
rpc_app->last_event_type == RpcAppEventTypeDataExchange);
|
||||
|
||||
const uint32_t last_command_id = rpc_app->last_command_id;
|
||||
@@ -432,6 +468,9 @@ void* rpc_system_app_alloc(RpcSession* session) {
|
||||
rpc_handler.message_handler = rpc_system_app_button_release;
|
||||
rpc_add_handler(session, PB_Main_app_button_release_request_tag, &rpc_handler);
|
||||
|
||||
rpc_handler.message_handler = rpc_system_app_button_press_release;
|
||||
rpc_add_handler(session, PB_Main_app_button_press_release_request_tag, &rpc_handler);
|
||||
|
||||
rpc_handler.message_handler = rpc_system_app_get_error_process;
|
||||
rpc_add_handler(session, PB_Main_app_get_error_request_tag, &rpc_handler);
|
||||
|
||||
|
||||
@@ -90,6 +90,13 @@ typedef enum {
|
||||
* all activities to be conducted while a button is being pressed.
|
||||
*/
|
||||
RpcAppEventTypeButtonRelease,
|
||||
/**
|
||||
* @brief The client has informed the application that a button has been pressed and released.
|
||||
*
|
||||
* This command's meaning is application-specific, e.g. to perform an action
|
||||
* once without repeating it.
|
||||
*/
|
||||
RpcAppEventTypeButtonPressRelease,
|
||||
/**
|
||||
* @brief The client has sent a byte array of arbitrary size.
|
||||
*
|
||||
@@ -162,6 +169,7 @@ void rpc_system_app_send_exited(RpcAppSystem* rpc_app);
|
||||
* - RpcAppEventTypeLoadFile
|
||||
* - RpcAppEventTypeButtonPress
|
||||
* - RpcAppEventTypeButtonRelease
|
||||
* - RpcAppEventTypeButtonPressRelease
|
||||
* - RpcAppEventTypeDataExchange
|
||||
*
|
||||
* Not confirming these events will result in a client-side timeout.
|
||||
|
||||
@@ -62,8 +62,8 @@ packages:
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
cross-spawn@7.0.3:
|
||||
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
@@ -240,7 +240,7 @@ snapshots:
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
cross-spawn@7.0.3:
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
shebang-command: 2.0.0
|
||||
@@ -256,7 +256,7 @@ snapshots:
|
||||
|
||||
foreground-child@3.3.0:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-flip/fz-sdk-mntm",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"description": "Type declarations and documentation for native JS modules available on Momentum Custom Firmware for Flipper Zero",
|
||||
"keywords": [
|
||||
"momentum",
|
||||
|
||||
@@ -8,13 +8,6 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
prompts:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
serialport:
|
||||
specifier: ^12.0.0
|
||||
version: 12.0.0
|
||||
devDependencies:
|
||||
esbuild:
|
||||
specifier: ^0.24.0
|
||||
version: 0.24.0
|
||||
@@ -24,6 +17,12 @@ importers:
|
||||
json5:
|
||||
specifier: ^2.2.3
|
||||
version: 2.2.3
|
||||
prompts:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
serialport:
|
||||
specifier: ^12.0.0
|
||||
version: 12.0.0
|
||||
typedoc:
|
||||
specifier: ^0.26.10
|
||||
version: 0.26.10(typescript@5.6.3)
|
||||
|
||||
@@ -91,9 +91,21 @@ async function build(config) {
|
||||
|
||||
async function upload(config) {
|
||||
const appFile = fs.readFileSync(config.input, "utf8");
|
||||
const flippers = (await SerialPort.list()).filter(x => x.serialNumber?.startsWith("flip_"));
|
||||
const serialPorts = await SerialPort.list();
|
||||
|
||||
if (!flippers) {
|
||||
let flippers = serialPorts
|
||||
.filter(x => x.serialNumber?.startsWith("flip_"))
|
||||
.map(x => ({ path: x.path, name: x.serialNumber.replace("flip_", "") }));
|
||||
|
||||
if (!flippers.length) {
|
||||
// some Windows installations don't report the serial number correctly;
|
||||
// filter by STM VCP VID:PID instead
|
||||
flippers = serialPorts
|
||||
.filter(x => x?.vendorId === "0483" && x?.productId === "5740")
|
||||
.map(x => ({ path: x.path, name: x.path }));
|
||||
}
|
||||
|
||||
if (!flippers.length) {
|
||||
console.error("No Flippers found");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png
vendored
Executable file
|
After Width: | Height: | Size: 858 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png
vendored
Executable file
|
After Width: | Height: | Size: 855 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png
vendored
Executable file
|
After Width: | Height: | Size: 872 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png
vendored
Executable file
|
After Width: | Height: | Size: 861 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png
vendored
Executable file
|
After Width: | Height: | Size: 853 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png
vendored
Executable file
|
After Width: | Height: | Size: 851 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png
vendored
Executable file
|
After Width: | Height: | Size: 852 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png
vendored
Executable file
|
After Width: | Height: | Size: 856 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png
vendored
Executable file
|
After Width: | Height: | Size: 850 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png
vendored
Executable file
|
After Width: | Height: | Size: 851 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png
vendored
Executable file
|
After Width: | Height: | Size: 860 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png
vendored
Executable file
|
After Width: | Height: | Size: 857 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png
vendored
Executable file
|
After Width: | Height: | Size: 863 B |
23
assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
Filetype: Flipper Animation
|
||||
Version: 1
|
||||
|
||||
Width: 128
|
||||
Height: 64
|
||||
Passive frames: 10
|
||||
Active frames: 18
|
||||
Frames order: 0 1 2 1 0 1 2 1 0 1 2 3 4 5 6 5 4 7 2 8 9 10 11 10 9 10 11 12
|
||||
Active cycles: 1
|
||||
Frame rate: 2
|
||||
Duration: 3600
|
||||
Active cooldown: 7
|
||||
|
||||
Bubble slots: 1
|
||||
|
||||
Slot: 0
|
||||
X: 11
|
||||
Y: 19
|
||||
Text: HAPPY\nHOLIDAYS!
|
||||
AlignH: Right
|
||||
AlignV: Center
|
||||
StartFrame: 22
|
||||
EndFrame: 27
|
||||
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png
vendored
Executable file
|
After Width: | Height: | Size: 820 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png
vendored
Executable file
|
After Width: | Height: | Size: 881 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png
vendored
Executable file
|
After Width: | Height: | Size: 788 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png
vendored
Executable file
|
After Width: | Height: | Size: 816 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png
vendored
Executable file
|
After Width: | Height: | Size: 864 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png
vendored
Executable file
|
After Width: | Height: | Size: 798 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png
vendored
Executable file
|
After Width: | Height: | Size: 813 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png
vendored
Executable file
|
After Width: | Height: | Size: 879 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png
vendored
Executable file
|
After Width: | Height: | Size: 855 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png
vendored
Executable file
|
After Width: | Height: | Size: 772 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png
vendored
Executable file
|
After Width: | Height: | Size: 817 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png
vendored
Executable file
|
After Width: | Height: | Size: 867 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png
vendored
Executable file
|
After Width: | Height: | Size: 866 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png
vendored
Executable file
|
After Width: | Height: | Size: 809 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png
vendored
Executable file
|
After Width: | Height: | Size: 795 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png
vendored
Executable file
|
After Width: | Height: | Size: 870 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png
vendored
Executable file
|
After Width: | Height: | Size: 852 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png
vendored
Executable file
|
After Width: | Height: | Size: 805 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png
vendored
Executable file
|
After Width: | Height: | Size: 858 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png
vendored
Executable file
|
After Width: | Height: | Size: 830 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png
vendored
Executable file
|
After Width: | Height: | Size: 828 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png
vendored
Executable file
|
After Width: | Height: | Size: 585 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png
vendored
Executable file
|
After Width: | Height: | Size: 431 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png
vendored
Executable file
|
After Width: | Height: | Size: 812 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png
vendored
Executable file
|
After Width: | Height: | Size: 281 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png
vendored
Executable file
|
After Width: | Height: | Size: 270 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png
vendored
Executable file
|
After Width: | Height: | Size: 236 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png
vendored
Executable file
|
After Width: | Height: | Size: 485 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png
vendored
Executable file
|
After Width: | Height: | Size: 771 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png
vendored
Executable file
|
After Width: | Height: | Size: 887 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png
vendored
Executable file
|
After Width: | Height: | Size: 809 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png
vendored
Executable file
|
After Width: | Height: | Size: 890 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png
vendored
Executable file
|
After Width: | Height: | Size: 819 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png
vendored
Executable file
|
After Width: | Height: | Size: 799 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png
vendored
Executable file
|
After Width: | Height: | Size: 817 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png
vendored
Executable file
|
After Width: | Height: | Size: 875 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png
vendored
Executable file
|
After Width: | Height: | Size: 823 B |
23
assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
Filetype: Flipper Animation
|
||||
Version: 1
|
||||
|
||||
Width: 128
|
||||
Height: 64
|
||||
Passive frames: 18
|
||||
Active frames: 19
|
||||
Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
||||
Active cycles: 1
|
||||
Frame rate: 2
|
||||
Duration: 3600
|
||||
Active cooldown: 7
|
||||
|
||||
Bubble slots: 1
|
||||
|
||||
Slot: 0
|
||||
X: 21
|
||||
Y: 25
|
||||
Text: AAAAaAAAAHHh!!
|
||||
AlignH: Right
|
||||
AlignV: Bottom
|
||||
StartFrame: 30
|
||||
EndFrame: 32
|
||||
@@ -8,7 +8,7 @@ Active frames: 0
|
||||
Frames order: 0 1 2 3 4 5 6 6
|
||||
Active cycles: 0
|
||||
Frame rate: 1
|
||||
Duration: 3600
|
||||
Duration: 360
|
||||
Active cooldown: 0
|
||||
|
||||
Bubble slots: 1
|
||||
|
||||
14
assets/dolphin/external/manifest.txt
vendored
@@ -231,3 +231,17 @@ Max butthurt: 7
|
||||
Min level: 11
|
||||
Max level: 30
|
||||
Weight: 4
|
||||
|
||||
Name: L1_Happy_holidays_128x64
|
||||
Min butthurt: 0
|
||||
Max butthurt: 14
|
||||
Min level: 16
|
||||
Max level: 30
|
||||
Weight: 4
|
||||
|
||||
Name: L1_Sleigh_ride_128x64
|
||||
Min butthurt: 0
|
||||
Max butthurt: 14
|
||||
Min level: 9
|
||||
Max level: 30
|
||||
Weight: 4
|
||||
|
||||
@@ -43,7 +43,7 @@ To add unit tests for your protocol, follow these steps:
|
||||
|
||||
1. Create a file named `test_<your_protocol_name>.irtest` in the [assets](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests/infrared) directory.
|
||||
2. Fill it with the test data (more on it below).
|
||||
3. Add the test code to [infrared_test.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/infrared/infrared_test.c).
|
||||
3. Add the test code to [infrared_test.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/tests/infrared/infrared_test.c).
|
||||
4. Build and install firmware with resources, install it on your Flipper and run the tests to see if they pass.
|
||||
|
||||
##### Test data format
|
||||
|
||||
@@ -106,5 +106,7 @@ void* aligned_malloc(size_t size, size_t alignment) {
|
||||
}
|
||||
|
||||
void aligned_free(void* p) {
|
||||
free(((void**)p)[-1]);
|
||||
if(p) {
|
||||
free(((void**)p)[-1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,11 @@ bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigg
|
||||
pdTRUE;
|
||||
}
|
||||
|
||||
size_t furi_stream_get_trigger_level(FuriStreamBuffer* stream_buffer) {
|
||||
furi_check(stream_buffer);
|
||||
return ((StaticStreamBuffer_t*)stream_buffer)->xTriggerLevelBytes;
|
||||
}
|
||||
|
||||
size_t furi_stream_buffer_send(
|
||||
FuriStreamBuffer* stream_buffer,
|
||||
const void* data,
|
||||
|
||||
@@ -54,6 +54,17 @@ void furi_stream_buffer_free(FuriStreamBuffer* stream_buffer);
|
||||
*/
|
||||
bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigger_level);
|
||||
|
||||
/**
|
||||
* @brief Get trigger level for stream buffer.
|
||||
* A stream buffer's trigger level is the number of bytes that must be in the
|
||||
* stream buffer before a task that is blocked on the stream buffer to
|
||||
* wait for data is moved out of the blocked state.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance
|
||||
* @return The trigger level for the stream buffer
|
||||
*/
|
||||
size_t furi_stream_get_trigger_level(FuriStreamBuffer* stream_buffer);
|
||||
|
||||
/**
|
||||
* @brief Sends bytes to a stream buffer. The bytes are copied into the stream buffer.
|
||||
* Wakes up task waiting for data to become available if called from ISR.
|
||||
|
||||
@@ -129,12 +129,12 @@ void furi_string_swap(FuriString* string_1, FuriString* string_2);
|
||||
|
||||
/** Move string_2 content to string_1.
|
||||
*
|
||||
* Set the string to the other one, and destroy the other one.
|
||||
* Copy data from one string to another and destroy the source.
|
||||
*
|
||||
* @param string_1 The FuriString instance 1
|
||||
* @param string_2 The FuriString instance 2
|
||||
* @param destination The destination FuriString
|
||||
* @param source The source FuriString
|
||||
*/
|
||||
void furi_string_move(FuriString* string_1, FuriString* string_2);
|
||||
void furi_string_move(FuriString* destination, FuriString* source);
|
||||
|
||||
/** Compute a hash for the string.
|
||||
*
|
||||
|
||||
@@ -25,12 +25,17 @@
|
||||
|
||||
#define THREAD_MAX_STACK_SIZE (UINT16_MAX * sizeof(StackType_t))
|
||||
|
||||
typedef struct FuriThreadStdout FuriThreadStdout;
|
||||
|
||||
struct FuriThreadStdout {
|
||||
typedef struct {
|
||||
FuriThreadStdoutWriteCallback write_callback;
|
||||
FuriString* buffer;
|
||||
};
|
||||
void* context;
|
||||
} FuriThreadStdout;
|
||||
|
||||
typedef struct {
|
||||
FuriThreadStdinReadCallback read_callback;
|
||||
FuriString* unread_buffer; // <! stores data from `ungetc` and friends
|
||||
void* context;
|
||||
} FuriThreadStdin;
|
||||
|
||||
struct FuriThread {
|
||||
StaticTask_t container;
|
||||
@@ -57,6 +62,7 @@ struct FuriThread {
|
||||
size_t heap_size;
|
||||
|
||||
FuriThreadStdout output;
|
||||
FuriThreadStdin input;
|
||||
|
||||
// Keep all non-alignable byte types in one place,
|
||||
// this ensures that the size of this structure is minimal
|
||||
@@ -138,6 +144,7 @@ static void furi_thread_body(void* context) {
|
||||
|
||||
static void furi_thread_init_common(FuriThread* thread) {
|
||||
thread->output.buffer = furi_string_alloc();
|
||||
thread->input.unread_buffer = furi_string_alloc();
|
||||
|
||||
FuriThread* parent = NULL;
|
||||
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
|
||||
@@ -247,6 +254,7 @@ void furi_thread_free(FuriThread* thread) {
|
||||
}
|
||||
|
||||
furi_string_free(thread->output.buffer);
|
||||
furi_string_free(thread->input.unread_buffer);
|
||||
free(thread);
|
||||
}
|
||||
|
||||
@@ -717,13 +725,22 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) {
|
||||
|
||||
static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size) {
|
||||
if(thread->output.write_callback != NULL) {
|
||||
thread->output.write_callback(data, size);
|
||||
thread->output.write_callback(data, size, thread->output.context);
|
||||
} else {
|
||||
furi_log_tx((const uint8_t*)data, size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static size_t
|
||||
__furi_thread_stdin_read(FuriThread* thread, char* data, size_t size, FuriWait timeout) {
|
||||
if(thread->input.read_callback != NULL) {
|
||||
return thread->input.read_callback(data, size, timeout, thread->input.context);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __furi_thread_stdout_flush(FuriThread* thread) {
|
||||
FuriString* buffer = thread->output.buffer;
|
||||
size_t size = furi_string_size(buffer);
|
||||
@@ -734,19 +751,33 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_check(thread);
|
||||
__furi_thread_stdout_flush(thread);
|
||||
thread->output.write_callback = callback;
|
||||
}
|
||||
|
||||
FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_check(thread);
|
||||
return thread->output.write_callback;
|
||||
}
|
||||
|
||||
FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void) {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_check(thread);
|
||||
return thread->input.read_callback;
|
||||
}
|
||||
|
||||
void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context) {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_check(thread);
|
||||
__furi_thread_stdout_flush(thread);
|
||||
thread->output.write_callback = callback;
|
||||
thread->output.context = context;
|
||||
}
|
||||
|
||||
void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context) {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_check(thread);
|
||||
thread->input.read_callback = callback;
|
||||
thread->input.context = context;
|
||||
}
|
||||
|
||||
size_t furi_thread_stdout_write(const char* data, size_t size) {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_check(thread);
|
||||
@@ -779,6 +810,31 @@ int32_t furi_thread_stdout_flush(void) {
|
||||
return __furi_thread_stdout_flush(thread);
|
||||
}
|
||||
|
||||
size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout) {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_check(thread);
|
||||
|
||||
size_t from_buffer = MIN(furi_string_size(thread->input.unread_buffer), size);
|
||||
size_t from_input = size - from_buffer;
|
||||
size_t from_input_actual =
|
||||
__furi_thread_stdin_read(thread, buffer + from_buffer, from_input, timeout);
|
||||
memcpy(buffer, furi_string_get_cstr(thread->input.unread_buffer), from_buffer);
|
||||
furi_string_right(thread->input.unread_buffer, from_buffer);
|
||||
|
||||
return from_buffer + from_input_actual;
|
||||
}
|
||||
|
||||
void furi_thread_stdin_unread(char* buffer, size_t size) {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_check(thread);
|
||||
|
||||
FuriString* new_buf = furi_string_alloc(); // there's no furi_string_alloc_set_strn :(
|
||||
furi_string_set_strn(new_buf, buffer, size);
|
||||
furi_string_cat(new_buf, thread->input.unread_buffer);
|
||||
furi_string_free(thread->input.unread_buffer);
|
||||
thread->input.unread_buffer = new_buf;
|
||||
}
|
||||
|
||||
void furi_thread_suspend(FuriThreadId thread_id) {
|
||||
furi_check(thread_id);
|
||||
|
||||
|
||||
@@ -74,8 +74,23 @@ typedef int32_t (*FuriThreadCallback)(void* context);
|
||||
*
|
||||
* @param[in] data pointer to the data to be written to the standard out
|
||||
* @param[in] size size of the data in bytes
|
||||
* @param[in] context optional context
|
||||
*/
|
||||
typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size);
|
||||
typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size, void* context);
|
||||
|
||||
/**
|
||||
* @brief Standard input callback function pointer type
|
||||
*
|
||||
* The function to be used as a standard input callback MUST follow this signature.
|
||||
*
|
||||
* @param[out] buffer buffer to read data into
|
||||
* @param[in] size maximum number of bytes to read into the buffer
|
||||
* @param[in] timeout how long to wait for (in ticks) before giving up
|
||||
* @param[in] context optional context
|
||||
* @returns number of bytes that was actually read into the buffer
|
||||
*/
|
||||
typedef size_t (
|
||||
*FuriThreadStdinReadCallback)(char* buffer, size_t size, FuriWait timeout, void* context);
|
||||
|
||||
/**
|
||||
* @brief State change callback function pointer type.
|
||||
@@ -468,13 +483,30 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id);
|
||||
*/
|
||||
FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void);
|
||||
|
||||
/**
|
||||
* @brief Get the standard input callback for the current thead.
|
||||
*
|
||||
* @return pointer to the standard in callback function
|
||||
*/
|
||||
FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void);
|
||||
|
||||
/** Set standard output callback for the current thread.
|
||||
*
|
||||
* @param[in] callback pointer to the callback function or NULL to clear
|
||||
* @param[in] context context to be passed to the callback
|
||||
*/
|
||||
void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback);
|
||||
void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context);
|
||||
|
||||
/** Set standard input callback for the current thread.
|
||||
*
|
||||
* @param[in] callback pointer to the callback function or NULL to clear
|
||||
* @param[in] context context to be passed to the callback
|
||||
*/
|
||||
void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context);
|
||||
|
||||
/** Write data to buffered standard output.
|
||||
*
|
||||
* @note You can also use the standard C `putc`, `puts`, `printf` and friends.
|
||||
*
|
||||
* @param[in] data pointer to the data to be written
|
||||
* @param[in] size data size in bytes
|
||||
@@ -489,6 +521,29 @@ size_t furi_thread_stdout_write(const char* data, size_t size);
|
||||
*/
|
||||
int32_t furi_thread_stdout_flush(void);
|
||||
|
||||
/** Read data from the standard input
|
||||
*
|
||||
* @note You can also use the standard C `getc`, `gets` and friends.
|
||||
*
|
||||
* @param[in] buffer pointer to the buffer to read data into
|
||||
* @param[in] size how many bytes to read into the buffer
|
||||
* @param[in] timeout how long to wait for (in ticks) before giving up
|
||||
* @return number of bytes that was actually read
|
||||
*/
|
||||
size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout);
|
||||
|
||||
/** Puts data back into the standard input buffer
|
||||
*
|
||||
* `furi_thread_stdin_read` will return the bytes in the same order that they
|
||||
* were supplied to this function.
|
||||
*
|
||||
* @note You can also use the standard C `ungetc`.
|
||||
*
|
||||
* @param[in] buffer pointer to the buffer to get data from
|
||||
* @param[in] size how many bytes to read from the buffer
|
||||
*/
|
||||
void furi_thread_stdin_unread(char* buffer, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Suspend a thread.
|
||||
*
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
#define TAG "BleHid"
|
||||
|
||||
#define BLE_SVC_HID_REPORT_MAP_MAX_LEN (255)
|
||||
#define BLE_SVC_HID_REPORT_MAX_LEN (255)
|
||||
#define BLE_SVC_HID_REPORT_REF_LEN (2)
|
||||
#define BLE_SVC_HID_INFO_LEN (4)
|
||||
#define BLE_SVC_HID_CONTROL_POINT_LEN (1)
|
||||
#define BLE_SVC_HID_REPORT_MAX_LEN (255)
|
||||
#define BLE_SVC_HID_REPORT_REF_LEN (2)
|
||||
#define BLE_SVC_HID_INFO_LEN (4)
|
||||
#define BLE_SVC_HID_CONTROL_POINT_LEN (1)
|
||||
|
||||
#define BLE_SVC_HID_INPUT_REPORT_COUNT (3)
|
||||
#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0)
|
||||
#define BLE_SVC_HID_INPUT_REPORT_COUNT (3)
|
||||
#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0)
|
||||
#define BLE_SVC_HID_FEATURE_REPORT_COUNT (0)
|
||||
#define BLE_SVC_HID_REPORT_COUNT \
|
||||
(BLE_SVC_HID_INPUT_REPORT_COUNT + BLE_SVC_HID_OUTPUT_REPORT_COUNT + \
|
||||
@@ -157,6 +157,7 @@ static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) {
|
||||
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data);
|
||||
evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data;
|
||||
// aci_gatt_attribute_modified_event_rp0* attribute_modified;
|
||||
|
||||
if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) {
|
||||
if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) {
|
||||
// Process modification events
|
||||
@@ -274,6 +275,7 @@ bool ble_svc_hid_update_input_report(
|
||||
.data_ptr = data,
|
||||
.data_len = len,
|
||||
};
|
||||
|
||||
return ble_gatt_characteristic_update(
|
||||
hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data);
|
||||
}
|
||||
|
||||
@@ -830,9 +830,7 @@ void elf_file_free(ELFFile* elf) {
|
||||
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it);
|
||||
ELFSectionDict_next(it)) {
|
||||
const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it);
|
||||
if(itref->value.data) {
|
||||
aligned_free(itref->value.data);
|
||||
}
|
||||
aligned_free(itref->value.data);
|
||||
if(itref->value.fast_rel) {
|
||||
if(itref->value.fast_rel->data) {
|
||||
aligned_free(itref->value.fast_rel->data);
|
||||
|
||||
@@ -403,6 +403,11 @@ bool flipper_format_write_comment_cstr(FlipperFormat* flipper_format, const char
|
||||
return flipper_format_stream_write_comment_cstr(flipper_format->stream, data);
|
||||
}
|
||||
|
||||
bool flipper_format_write_empty_line(FlipperFormat* flipper_format) {
|
||||
furi_check(flipper_format);
|
||||
return flipper_format_stream_write_eol(flipper_format->stream);
|
||||
}
|
||||
|
||||
bool flipper_format_delete_key(FlipperFormat* flipper_format, const char* key) {
|
||||
furi_check(flipper_format);
|
||||
FlipperStreamWriteData write_data = {
|
||||
|
||||
@@ -518,6 +518,14 @@ bool flipper_format_write_comment(FlipperFormat* flipper_format, FuriString* dat
|
||||
*/
|
||||
bool flipper_format_write_comment_cstr(FlipperFormat* flipper_format, const char* data);
|
||||
|
||||
/** Write empty line (Improves readability for human based parsing)
|
||||
*
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
*
|
||||
* @return True on success
|
||||
*/
|
||||
bool flipper_format_write_empty_line(FlipperFormat* flipper_format);
|
||||
|
||||
/** Removes the first matching key and its value. Sets the RW pointer to a
|
||||
* position of deleted data.
|
||||
*
|
||||
|
||||
@@ -146,7 +146,7 @@ static void mf_plus_poller_set_callback(
|
||||
|
||||
static NfcCommand mf_plus_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol = NfcProtocolIso14443_4a);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_4a);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
MfPlusPoller* instance = context;
|
||||
@@ -178,7 +178,7 @@ void mf_plus_poller_free(MfPlusPoller* instance) {
|
||||
|
||||
static bool mf_plus_poller_detect(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol = NfcProtocolIso14443_4a);
|
||||
furi_assert(event.protocol == NfcProtocolIso14443_4a);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
MfPlusPoller* instance = context;
|
||||
|
||||
@@ -37,7 +37,7 @@ MfUltralightError mf_ultralight_poller_auth_pwd(
|
||||
furi_check(data);
|
||||
|
||||
uint8_t auth_cmd[5] = {MF_ULTRALIGHT_CMD_PWD_AUTH}; //-V1009
|
||||
memccpy(&auth_cmd[1], data->password.data, 0, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE);
|
||||
memcpy(&auth_cmd[1], data->password.data, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE);
|
||||
bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd));
|
||||
|
||||
MfUltralightError ret = MfUltralightErrorNone;
|
||||
|
||||
@@ -44,18 +44,24 @@ wrapped_fn_list = [
|
||||
"vsiprintf",
|
||||
"vsniprintf",
|
||||
#
|
||||
# Scanf is not implemented 4 now
|
||||
# standard input
|
||||
#
|
||||
"fgetc",
|
||||
"getc",
|
||||
"getchar",
|
||||
"fgets",
|
||||
"ungetc",
|
||||
#
|
||||
# standard input, but unimplemented
|
||||
#
|
||||
"gets",
|
||||
#
|
||||
# scanf, not implemented for now
|
||||
#
|
||||
# "fscanf",
|
||||
# "scanf",
|
||||
# "sscanf",
|
||||
# "vsprintf",
|
||||
# "fgetc",
|
||||
# "fgets",
|
||||
# "getc",
|
||||
# "getchar",
|
||||
# "gets",
|
||||
# "ungetc",
|
||||
# "vfscanf",
|
||||
# "vscanf",
|
||||
# "vsscanf",
|
||||
|
||||
@@ -51,11 +51,54 @@ int __wrap_snprintf(char* str, size_t size, const char* format, ...) {
|
||||
}
|
||||
|
||||
int __wrap_fflush(FILE* stream) {
|
||||
UNUSED(stream);
|
||||
furi_thread_stdout_flush();
|
||||
if(stream == stdout) furi_thread_stdout_flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __wrap_fgetc(FILE* stream) {
|
||||
if(stream != stdin) return EOF;
|
||||
char c;
|
||||
if(furi_thread_stdin_read(&c, 1, FuriWaitForever) == 0) return EOF;
|
||||
return c;
|
||||
}
|
||||
|
||||
int __wrap_getc(FILE* stream) {
|
||||
return __wrap_fgetc(stream);
|
||||
}
|
||||
|
||||
int __wrap_getchar(void) {
|
||||
return __wrap_fgetc(stdin);
|
||||
}
|
||||
|
||||
char* __wrap_fgets(char* str, size_t n, FILE* stream) {
|
||||
// leave space for the zero terminator
|
||||
furi_check(n >= 1);
|
||||
n--;
|
||||
|
||||
if(stream != stdin) {
|
||||
*str = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
// read characters
|
||||
int c;
|
||||
do {
|
||||
c = __wrap_fgetc(stdin);
|
||||
if(c > 0) *(str++) = c;
|
||||
} while(c != EOF && c != '\n' && --n);
|
||||
|
||||
// place zero terminator
|
||||
*str = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
int __wrap_ungetc(int ch, FILE* stream) {
|
||||
char c = ch;
|
||||
if(stream != stdin) return EOF;
|
||||
furi_thread_stdin_unread(&c, 1);
|
||||
return ch;
|
||||
}
|
||||
|
||||
__attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e) {
|
||||
UNUSED(file);
|
||||
UNUSED(line);
|
||||
|
||||
@@ -16,6 +16,12 @@ int __wrap_putc(int ch, FILE* stream);
|
||||
int __wrap_snprintf(char* str, size_t size, const char* format, ...);
|
||||
int __wrap_fflush(FILE* stream);
|
||||
|
||||
int __wrap_fgetc(FILE* stream);
|
||||
int __wrap_getc(FILE* stream);
|
||||
int __wrap_getchar(void);
|
||||
char* __wrap_fgets(char* str, size_t n, FILE* stream);
|
||||
int __wrap_ungetc(int ch, FILE* stream);
|
||||
|
||||
__attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e);
|
||||
|
||||
__attribute__((__noreturn__)) void
|
||||
|
||||
@@ -243,6 +243,8 @@ static Iso15693ParserCommand iso15693_parser_parse_1_out_of_256(Iso15693Parser*
|
||||
instance->parsed_frame, instance->next_byte_part * 4 + j / 2);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
instance->zero_found = true;
|
||||
}
|
||||
}
|
||||
instance->next_byte_part = (instance->next_byte_part + 1) % 64;
|
||||
|
||||
@@ -316,8 +316,8 @@ SubGhzProtocolStatus
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
if(!subghz_protocol_encoder_bin_raw_get_upload(instance)) {
|
||||
break;
|
||||
res = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ env.Append(
|
||||
File("stream/string_stream.h"),
|
||||
File("stream/buffered_file_stream.h"),
|
||||
File("strint.h"),
|
||||
File("pipe.h"),
|
||||
File("protocols/protocol_dict.h"),
|
||||
File("pretty_format.h"),
|
||||
File("hex.h"),
|
||||
|
||||