diff --git a/CHANGELOG.md b/CHANGELOG.md index faa5a49a4..9a1028223 100644 --- a/CHANGELOG.md +++ b/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()` diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index dec3283e4..f92d7e66f 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -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"], +) diff --git a/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c b/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c index 888a66444..934634c71 100644 --- a/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c +++ b/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c @@ -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; diff --git a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c new file mode 100644 index 000000000..94e2f613b --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c @@ -0,0 +1,108 @@ +#include +#include +#include +#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); +} diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index 193a8124d..f23be37a9 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -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); } diff --git a/applications/debug/unit_tests/tests/pipe/pipe_test.c b/applications/debug/unit_tests/tests/pipe/pipe_test.c new file mode 100644 index 000000000..d440a04ee --- /dev/null +++ b/applications/debug/unit_tests/tests/pipe/pipe_test.c @@ -0,0 +1,153 @@ +#include "../test.h" // IWYU pragma: keep + +#include +#include + +#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) diff --git a/applications/external b/applications/external index 2a7953ffc..ea20cedd2 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 2a7953ffcd63e2b8dc4324379f8b0c5ddbbaaff8 +Subproject commit ea20cedd203c8324e2ccfda8f9359b85eafd8935 diff --git a/applications/main/bad_kb/helpers/ble_hid_svc.c b/applications/main/bad_kb/helpers/ble_hid_svc.c index 2f0910252..024e25a78 100644 --- a/applications/main/bad_kb/helpers/ble_hid_svc.c +++ b/applications/main/bad_kb/helpers/ble_hid_svc.c @@ -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); } diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index e42446a42..4bc4937df 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -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); diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 692cc9671..75d8502f2 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -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. * diff --git a/applications/main/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h index 02d9a276f..2efc99f4b 100644 --- a/applications/main/infrared/infrared_custom_event.h +++ b/applications/main/infrared/infrared_custom_event.h @@ -21,6 +21,8 @@ enum InfraredCustomEventType { InfraredCustomEventTypeRpcButtonPressName, InfraredCustomEventTypeRpcButtonPressIndex, InfraredCustomEventTypeRpcButtonRelease, + InfraredCustomEventTypeRpcButtonPressReleaseName, + InfraredCustomEventTypeRpcButtonPressReleaseIndex, InfraredCustomEventTypeRpcSessionClose, InfraredCustomEventTypeGpioTxPinChanged, diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 8f9dc4338..35cd971d8 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -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 || diff --git a/applications/main/nfc/plugins/supported_cards/skylanders.c b/applications/main/nfc/plugins/supported_cards/skylanders.c index 6c199f114..b5dc0ab86 100644 --- a/applications/main/nfc/plugins/supported_cards/skylanders.c +++ b/applications/main/nfc/plugins/supported_cards/skylanders.c @@ -7,13 +7,36 @@ #include #include -#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); diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 2d2fabd75..2ac74652b 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -35,6 +35,7 @@ typedef enum { SubGhzCustomEventSceneRpcLoad, SubGhzCustomEventSceneRpcButtonPress, SubGhzCustomEventSceneRpcButtonRelease, + SubGhzCustomEventSceneRpcButtonPressRelease, SubGhzCustomEventSceneRpcSessionClose, SubGhzCustomEventViewReceiverOK, diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index f058821e0..b262679a4 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -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) { diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 19ea02a95..456a3ae6e 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -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); } diff --git a/applications/main/u2f/u2f.c b/applications/main/u2f/u2f.c index 0143eb245..132baf4f9 100644 --- a/applications/main/u2f/u2f.c +++ b/applications/main/u2f/u2f.c @@ -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) { diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 9756d1eef..d4903f2c5 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -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()) { diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h index f6d2bba7e..7b742a469 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -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); }; diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 83f4f8214..315baa3a2 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -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, diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index aa2a3f64f..2b9a6542d 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -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); diff --git a/applications/services/rpc/rpc_app.h b/applications/services/rpc/rpc_app.h index aa6fd81cc..377d9ccb3 100644 --- a/applications/services/rpc/rpc_app.h +++ b/applications/services/rpc/rpc_app.h @@ -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. diff --git a/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml b/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml index 58f20a385..3f753df15 100644 --- a/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml +++ b/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml @@ -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: {} diff --git a/applications/system/js_app/packages/fz-sdk/package.json b/applications/system/js_app/packages/fz-sdk/package.json index c39c5b9f8..e0a945346 100644 --- a/applications/system/js_app/packages/fz-sdk/package.json +++ b/applications/system/js_app/packages/fz-sdk/package.json @@ -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", diff --git a/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml b/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml index 45944a854..67d3bde82 100644 --- a/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml +++ b/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml @@ -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) diff --git a/applications/system/js_app/packages/fz-sdk/sdk.js b/applications/system/js_app/packages/fz-sdk/sdk.js index 55c3c2bb0..34279270b 100644 --- a/applications/system/js_app/packages/fz-sdk/sdk.js +++ b/applications/system/js_app/packages/fz-sdk/sdk.js @@ -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); } diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png new file mode 100755 index 000000000..f1207ed14 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png new file mode 100755 index 000000000..9d9012281 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png new file mode 100755 index 000000000..cb8f173b0 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png new file mode 100755 index 000000000..0b042e3aa Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png new file mode 100755 index 000000000..5d4c7e7c5 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png new file mode 100755 index 000000000..35ee06ee0 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png new file mode 100755 index 000000000..3a47dfc17 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png new file mode 100755 index 000000000..0b3bb3250 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png new file mode 100755 index 000000000..233448547 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png new file mode 100755 index 000000000..b7164380d Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png new file mode 100755 index 000000000..da3a78f4c Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png new file mode 100755 index 000000000..adbe53159 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png new file mode 100755 index 000000000..3d81f935a Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt b/assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt new file mode 100755 index 000000000..a2c733397 --- /dev/null +++ b/assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt @@ -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 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png new file mode 100755 index 000000000..0e86e6641 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png new file mode 100755 index 000000000..219407e4b Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png new file mode 100755 index 000000000..5459ae27c Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png new file mode 100755 index 000000000..9f9d80de6 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png new file mode 100755 index 000000000..7b35e1d3c Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png new file mode 100755 index 000000000..9e560baa8 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png new file mode 100755 index 000000000..a5d6c1a7a Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png new file mode 100755 index 000000000..57b2c9bbe Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png new file mode 100755 index 000000000..4be832c64 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png new file mode 100755 index 000000000..1910cf939 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png new file mode 100755 index 000000000..0236692c6 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png new file mode 100755 index 000000000..7450c87b4 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png new file mode 100755 index 000000000..0355c510c Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png new file mode 100755 index 000000000..c371e217f Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png new file mode 100755 index 000000000..822d2230e Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png new file mode 100755 index 000000000..6d359ef86 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png new file mode 100755 index 000000000..639834612 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png new file mode 100755 index 000000000..fbafab70f Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png new file mode 100755 index 000000000..190985282 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png new file mode 100755 index 000000000..894e62d29 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png new file mode 100755 index 000000000..962349bfd Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png new file mode 100755 index 000000000..2eb4456cc Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png new file mode 100755 index 000000000..ee325d3ee Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png new file mode 100755 index 000000000..e5772a672 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png new file mode 100755 index 000000000..11fc5d0b4 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png new file mode 100755 index 000000000..ec47b899c Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png new file mode 100755 index 000000000..3f345b563 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png new file mode 100755 index 000000000..c044765eb Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png new file mode 100755 index 000000000..44c4d0d49 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png new file mode 100755 index 000000000..f70ecb3ab Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png new file mode 100755 index 000000000..9af3a8338 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png new file mode 100755 index 000000000..7d970234f Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png new file mode 100755 index 000000000..70eab1f3e Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png new file mode 100755 index 000000000..8169e0766 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png new file mode 100755 index 000000000..adee1a63d Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png new file mode 100755 index 000000000..db6e667fb Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png new file mode 100755 index 000000000..a6a37fe29 Binary files /dev/null and b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt b/assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt new file mode 100755 index 000000000..3e31e1d69 --- /dev/null +++ b/assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt @@ -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 diff --git a/assets/dolphin/external/L1_Wardriving_128x64/meta.txt b/assets/dolphin/external/L1_Wardriving_128x64/meta.txt index 93ab92696..7ad227a1a 100644 --- a/assets/dolphin/external/L1_Wardriving_128x64/meta.txt +++ b/assets/dolphin/external/L1_Wardriving_128x64/meta.txt @@ -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 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 9f3b93448..2b15e7a4e 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -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 diff --git a/assets/protobuf b/assets/protobuf index c255d71a9..ea4f185f5 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit c255d71a90af202515deb7aaa51685d45a196152 +Subproject commit ea4f185f5eaa265955c520eae2832887ee6aa5e4 diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index 9711c6ae1..5d04c8f67 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -43,7 +43,7 @@ To add unit tests for your protocol, follow these steps: 1. Create a file named `test_.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 diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index d3ff873ae..8ee0d1723 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -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]); + } } diff --git a/furi/core/stream_buffer.c b/furi/core/stream_buffer.c index 783b2d741..902ec931c 100644 --- a/furi/core/stream_buffer.c +++ b/furi/core/stream_buffer.c @@ -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, diff --git a/furi/core/stream_buffer.h b/furi/core/stream_buffer.h index eef8ee510..deca813c7 100644 --- a/furi/core/stream_buffer.h +++ b/furi/core/stream_buffer.h @@ -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. diff --git a/furi/core/string.h b/furi/core/string.h index 84b8c6a24..0d407356b 100644 --- a/furi/core/string.h +++ b/furi/core/string.h @@ -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. * diff --git a/furi/core/thread.c b/furi/core/thread.c index 73e42b4c0..5b8b59f12 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -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; // 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); diff --git a/furi/core/thread.h b/furi/core/thread.h index ed7aa4553..9abfde5cd 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -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. * diff --git a/lib/ble_profile/extra_services/hid_service.c b/lib/ble_profile/extra_services/hid_service.c index e46d2010c..9f9a0f752 100644 --- a/lib/ble_profile/extra_services/hid_service.c +++ b/lib/ble_profile/extra_services/hid_service.c @@ -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); } diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 2a9307cac..5eab0efdd 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -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); diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index 8992247d1..d07022e12 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -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 = { diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index 46f78e255..4a1bb767b 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -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. * diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.c b/lib/nfc/protocols/mf_plus/mf_plus_poller.c index 8d1cc1c99..57a26a9cf 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.c @@ -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; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c index 141ab6c46..d84377612 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -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; diff --git a/lib/print/SConscript b/lib/print/SConscript index 07be8d890..90028cf06 100644 --- a/lib/print/SConscript +++ b/lib/print/SConscript @@ -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", diff --git a/lib/print/wrappers.c b/lib/print/wrappers.c index c8d72d192..18df92def 100644 --- a/lib/print/wrappers.c +++ b/lib/print/wrappers.c @@ -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); diff --git a/lib/print/wrappers.h b/lib/print/wrappers.h index 3cec88249..8a4599b41 100644 --- a/lib/print/wrappers.h +++ b/lib/print/wrappers.h @@ -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 diff --git a/lib/signal_reader/parsers/iso15693/iso15693_parser.c b/lib/signal_reader/parsers/iso15693/iso15693_parser.c index a2c6912e6..e47c734a2 100644 --- a/lib/signal_reader/parsers/iso15693/iso15693_parser.c +++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.c @@ -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; diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 92326ed45..2b2a57b16 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -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; diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 03b8999c4..8a1c4a8c5 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -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"), diff --git a/lib/toolbox/pipe.c b/lib/toolbox/pipe.c new file mode 100644 index 000000000..9dc1d368e --- /dev/null +++ b/lib/toolbox/pipe.c @@ -0,0 +1,241 @@ +#include "pipe.h" +#include + +/** + * Data shared between both sides. + */ +typedef struct { + FuriSemaphore* instance_count; // role; +} + +PipeState pipe_state(PipeSide* pipe) { + furi_check(pipe); + uint32_t count = furi_semaphore_get_count(pipe->shared->instance_count); + return (count == 1) ? PipeStateOpen : PipeStateBroken; +} + +void pipe_free(PipeSide* pipe) { + furi_check(pipe); + furi_check(!pipe->event_loop); + + furi_mutex_acquire(pipe->shared->state_transition, FuriWaitForever); + FuriStatus status = furi_semaphore_acquire(pipe->shared->instance_count, 0); + + if(status == FuriStatusOk) { + // the other side is still intact + furi_mutex_release(pipe->shared->state_transition); + free(pipe); + } else { + // the other side is gone too + furi_stream_buffer_free(pipe->sending); + furi_stream_buffer_free(pipe->receiving); + furi_semaphore_free(pipe->shared->instance_count); + furi_mutex_free(pipe->shared->state_transition); + free(pipe->shared); + free(pipe); + } +} + +static void _pipe_stdout_cb(const char* data, size_t size, void* context) { + furi_assert(context); + PipeSide* pipe = context; + while(size) { + size_t sent = pipe_send(pipe, data, size, FuriWaitForever); + data += sent; + size -= sent; + } +} + +static size_t _pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { + furi_assert(context); + PipeSide* pipe = context; + return pipe_receive(pipe, data, size, timeout); +} + +void pipe_install_as_stdio(PipeSide* pipe) { + furi_check(pipe); + furi_thread_set_stdout_callback(_pipe_stdout_cb, pipe); + furi_thread_set_stdin_callback(_pipe_stdin_cb, pipe); +} + +size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout) { + furi_check(pipe); + return furi_stream_buffer_receive(pipe->receiving, data, length, timeout); +} + +size_t pipe_send(PipeSide* pipe, const void* data, size_t length, FuriWait timeout) { + furi_check(pipe); + return furi_stream_buffer_send(pipe->sending, data, length, timeout); +} + +size_t pipe_bytes_available(PipeSide* pipe) { + furi_check(pipe); + return furi_stream_buffer_bytes_available(pipe->receiving); +} + +size_t pipe_spaces_available(PipeSide* pipe) { + furi_check(pipe); + return furi_stream_buffer_spaces_available(pipe->sending); +} + +static void pipe_receiving_buffer_callback(FuriEventLoopObject* buffer, void* context) { + UNUSED(buffer); + PipeSide* pipe = context; + furi_assert(pipe); + if(pipe->on_space_freed) pipe->on_data_arrived(pipe, pipe->callback_context); +} + +static void pipe_sending_buffer_callback(FuriEventLoopObject* buffer, void* context) { + UNUSED(buffer); + PipeSide* pipe = context; + furi_assert(pipe); + if(pipe->on_data_arrived) pipe->on_space_freed(pipe, pipe->callback_context); +} + +static void pipe_semaphore_callback(FuriEventLoopObject* semaphore, void* context) { + UNUSED(semaphore); + PipeSide* pipe = context; + furi_assert(pipe); + if(pipe->on_pipe_broken) pipe->on_pipe_broken(pipe, pipe->callback_context); +} + +void pipe_attach_to_event_loop(PipeSide* pipe, FuriEventLoop* event_loop) { + furi_check(pipe); + furi_check(event_loop); + furi_check(!pipe->event_loop); + + pipe->event_loop = event_loop; +} + +void pipe_detach_from_event_loop(PipeSide* pipe) { + furi_check(pipe); + furi_check(pipe->event_loop); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->receiving); + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->sending); + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->shared->instance_count); + + pipe->event_loop = NULL; +} + +void pipe_set_callback_context(PipeSide* pipe, void* context) { + furi_check(pipe); + pipe->callback_context = context; +} + +void pipe_set_data_arrived_callback( + PipeSide* pipe, + PipeSideDataArrivedCallback callback, + FuriEventLoopEvent event) { + furi_check(pipe); + furi_check(pipe->event_loop); + furi_check((event & FuriEventLoopEventMask) == 0); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->receiving); + pipe->on_data_arrived = callback; + if(callback) + furi_event_loop_subscribe_stream_buffer( + pipe->event_loop, + pipe->receiving, + FuriEventLoopEventIn | event, + pipe_receiving_buffer_callback, + pipe); +} + +void pipe_set_space_freed_callback( + PipeSide* pipe, + PipeSideSpaceFreedCallback callback, + FuriEventLoopEvent event) { + furi_check(pipe); + furi_check(pipe->event_loop); + furi_check((event & FuriEventLoopEventMask) == 0); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->sending); + pipe->on_space_freed = callback; + if(callback) + furi_event_loop_subscribe_stream_buffer( + pipe->event_loop, + pipe->sending, + FuriEventLoopEventOut | event, + pipe_sending_buffer_callback, + pipe); +} + +void pipe_set_broken_callback( + PipeSide* pipe, + PipeSideBrokenCallback callback, + FuriEventLoopEvent event) { + furi_check(pipe); + furi_check(pipe->event_loop); + furi_check((event & FuriEventLoopEventMask) == 0); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->shared->instance_count); + pipe->on_pipe_broken = callback; + if(callback) + furi_event_loop_subscribe_semaphore( + pipe->event_loop, + pipe->shared->instance_count, + FuriEventLoopEventOut | event, + pipe_semaphore_callback, + pipe); +} diff --git a/lib/toolbox/pipe.h b/lib/toolbox/pipe.h new file mode 100644 index 000000000..df75f4c48 --- /dev/null +++ b/lib/toolbox/pipe.h @@ -0,0 +1,295 @@ +/** + * @file pipe.h + * Pipe convenience module + * + * Pipes are used to send bytes between two threads in both directions. The two + * threads are referred to as Alice and Bob and their abilities regarding what + * they can do with the pipe are equal. + * + * It is also possible to use both sides of the pipe within one thread. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief The role of a pipe side + * + * Both roles are equal, as they can both read and write the data. This status + * might be helpful in determining the role of a thread w.r.t. another thread in + * an application that builds on the pipe. + */ +typedef enum { + PipeRoleAlice, + PipeRoleBob, +} PipeRole; + +/** + * @brief The state of a pipe + * + * - `PipeStateOpen`: Both pipe sides are in place, meaning data that is sent + * down the pipe _might_ be read by the peer, and new data sent by the peer + * _might_ arrive. + * - `PipeStateBroken`: The other side of the pipe has been freed, meaning + * data that is written will never reach its destination, and no new data + * will appear in the buffer. + * + * A broken pipe can never become open again, because there's no way to connect + * a side of a pipe to another side of a pipe. + */ +typedef enum { + PipeStateOpen, + PipeStateBroken, +} PipeState; + +typedef struct PipeSide PipeSide; + +typedef struct { + PipeSide* alices_side; + PipeSide* bobs_side; +} PipeSideBundle; + +typedef struct { + size_t capacity; + size_t trigger_level; +} PipeSideReceiveSettings; + +/** + * @brief Allocates two connected sides of one pipe. + * + * Creating a pair of sides using this function is the only way to connect two + * pipe sides together. Two unrelated orphaned sides may never be connected back + * together. + * + * The capacity and trigger level for both directions are the same when the pipe + * is created using this function. Use `pipe_alloc_ex` if you want more + * control. + * + * @param capacity Maximum number of bytes buffered in one direction + * @param trigger_level Number of bytes that need to be available in the buffer + * in order for a blocked thread to unblock + * @returns Bundle with both sides of the pipe + */ +PipeSideBundle pipe_alloc(size_t capacity, size_t trigger_level); + +/** + * @brief Allocates two connected sides of one pipe. + * + * Creating a pair of sides using this function is the only way to connect two + * pipe sides together. Two unrelated orphaned sides may never be connected back + * together. + * + * The capacity and trigger level may be different for the two directions when + * the pipe is created using this function. Use `pipe_alloc` if you don't + * need control this fine. + * + * @param alice `capacity` and `trigger_level` settings for Alice's receiving + * buffer + * @param bob `capacity` and `trigger_level` settings for Bob's receiving buffer + * @returns Bundle with both sides of the pipe + */ +PipeSideBundle pipe_alloc_ex(PipeSideReceiveSettings alice, PipeSideReceiveSettings bob); + +/** + * @brief Gets the role of a pipe side. + * + * The roles (Alice and Bob) are equal, as both can send and receive data. This + * status might be helpful in determining the role of a thread w.r.t. another + * thread. + * + * @param [in] pipe Pipe side to query + * @returns Role of provided pipe side + */ +PipeRole pipe_role(PipeSide* pipe); + +/** + * @brief Gets the state of a pipe. + * + * When the state is `PipeStateOpen`, both sides are active and may send or + * receive data. When the state is `PipeStateBroken`, only one side is active + * (the one that this method has been called on). If you find yourself in that + * state, the data that you send will never be heard by anyone, and the data you + * receive are leftovers in the buffer. + * + * @param [in] pipe Pipe side to query + * @returns State of the pipe + */ +PipeState pipe_state(PipeSide* pipe); + +/** + * @brief Frees a side of a pipe. + * + * When only one of the sides is freed, the pipe is transitioned from the "Open" + * state into the "Broken" state. When both sides are freed, the underlying data + * structures are freed too. + * + * @param [in] pipe Pipe side to free + */ +void pipe_free(PipeSide* pipe); + +/** + * @brief Connects the pipe to the `stdin` and `stdout` of the current thread. + * + * After performing this operation, you can use `getc`, `puts`, etc. to send and + * receive data to and from the pipe. If the pipe becomes broken, C stdlib calls + * will return `EOF` wherever possible. + * + * You can disconnect the pipe by manually calling + * `furi_thread_set_stdout_callback` and `furi_thread_set_stdin_callback` with + * `NULL`. + * + * @param [in] pipe Pipe side to connect to the stdio + */ +void pipe_install_as_stdio(PipeSide* pipe); + +/** + * @brief Receives data from the pipe. + * + * @param [in] pipe The pipe side to read data out of + * @param [out] data The buffer to fill with data + * @param length Maximum length of data to read + * @param timeout The timeout (in ticks) after which the read operation is + * interrupted + * @returns The number of bytes actually written into the provided buffer + */ +size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout); + +/** + * @brief Sends data into the pipe. + * + * @param [in] pipe The pipe side to send data into + * @param [out] data The buffer to get data from + * @param length Maximum length of data to send + * @param timeout The timeout (in ticks) after which the write operation is + * interrupted + * @returns The number of bytes actually read from the provided buffer + */ +size_t pipe_send(PipeSide* pipe, const void* data, size_t length, FuriWait timeout); + +/** + * @brief Determines how many bytes there are in the pipe available to be read. + * + * @param [in] pipe Pipe side to query + * @returns Number of bytes available to be read out from that side of the pipe + */ +size_t pipe_bytes_available(PipeSide* pipe); + +/** + * @brief Determines how many space there is in the pipe for data to be written + * into. + * + * @param [in] pipe Pipe side to query + * @returns Number of bytes available to be written into that side of the pipe + */ +size_t pipe_spaces_available(PipeSide* pipe); + +/** + * @brief Attaches a `PipeSide` to a `FuriEventLoop`, allowing to attach + * callbacks to the PipeSide. + * + * @param [in] pipe Pipe side to attach to the event loop + * @param [in] event_loop Event loop to attach the pipe side to + */ +void pipe_attach_to_event_loop(PipeSide* pipe, FuriEventLoop* event_loop); + +/** + * @brief Detaches a `PipeSide` from the `FuriEventLoop` that it was previously + * attached to. + * + * @param [in] pipe Pipe side to detach to the event loop + */ +void pipe_detach_from_event_loop(PipeSide* pipe); + +/** + * @brief Callback for when data arrives to a `PipeSide`. + * + * @param [in] pipe Pipe side that called the callback + * @param [inout] context Custom context + */ +typedef void (*PipeSideDataArrivedCallback)(PipeSide* pipe, void* context); + +/** + * @brief Callback for when data is read out of the opposite `PipeSide`. + * + * @param [in] pipe Pipe side that called the callback + * @param [inout] context Custom context + */ +typedef void (*PipeSideSpaceFreedCallback)(PipeSide* pipe, void* context); + +/** + * @brief Callback for when the opposite `PipeSide` is freed, making the pipe + * broken. + * + * @param [in] pipe Pipe side that called the callback + * @param [inout] context Custom context + */ +typedef void (*PipeSideBrokenCallback)(PipeSide* pipe, void* context); + +/** + * @brief Sets the custom context for all callbacks. + * + * @param [in] pipe Pipe side to set the context of + * @param [inout] context Custom context that will be passed to callbacks + */ +void pipe_set_callback_context(PipeSide* pipe, void* context); + +/** + * @brief Sets the callback for when data arrives. + * + * @param [in] pipe Pipe side to assign the callback to + * @param [in] callback Callback to assign to the pipe side. Set to NULL to + * unsubscribe. + * @param [in] event Additional event loop flags (e.g. `Edge`, `Once`, etc.). + * Non-flag values of the enum are not allowed. + * + * @warning Attach the pipe side to an event loop first using + * `pipe_attach_to_event_loop`. + */ +void pipe_set_data_arrived_callback( + PipeSide* pipe, + PipeSideDataArrivedCallback callback, + FuriEventLoopEvent event); + +/** + * @brief Sets the callback for when data is read out of the opposite `PipeSide`. + * + * @param [in] pipe Pipe side to assign the callback to + * @param [in] callback Callback to assign to the pipe side. Set to NULL to + * unsubscribe. + * @param [in] event Additional event loop flags (e.g. `Edge`, `Once`, etc.). + * Non-flag values of the enum are not allowed. + * + * @warning Attach the pipe side to an event loop first using + * `pipe_attach_to_event_loop`. + */ +void pipe_set_space_freed_callback( + PipeSide* pipe, + PipeSideSpaceFreedCallback callback, + FuriEventLoopEvent event); + +/** + * @brief Sets the callback for when the opposite `PipeSide` is freed, making + * the pipe broken. + * + * @param [in] pipe Pipe side to assign the callback to + * @param [in] callback Callback to assign to the pipe side. Set to NULL to + * unsubscribe. + * @param [in] event Additional event loop flags (e.g. `Edge`, `Once`, etc.). + * Non-flag values of the enum are not allowed. + * + * @warning Attach the pipe side to an event loop first using + * `pipe_attach_to_event_loop`. + */ +void pipe_set_broken_callback( + PipeSide* pipe, + PipeSideBrokenCallback callback, + FuriEventLoopEvent event); + +#ifdef __cplusplus +} +#endif diff --git a/scripts/get_env.py b/scripts/get_env.py index 8d76e97fa..643418c4b 100755 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -34,7 +34,11 @@ def get_commit_json(event): commit_url = event["pull_request"]["base"]["repo"]["commits_url"].replace( "{/sha}", f"/{event['pull_request']['head']['sha']}" ) - with urllib.request.urlopen(commit_url, context=context) as commit_file: + request = urllib.request.Request(commit_url) + if "GH_TOKEN" in os.environ: + request.add_header("Authorization", "Bearer %s" % (os.environ["GH_TOKEN"])) + + with urllib.request.urlopen(request, context=context) as commit_file: commit_json = json.loads(commit_file.read().decode("utf-8")) return commit_json diff --git a/scripts/testops.py b/scripts/testops.py index 4ae10c7f4..3100a9b7f 100644 --- a/scripts/testops.py +++ b/scripts/testops.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 - import re -import sys import time +from datetime import datetime from typing import Optional from flipper.app import App @@ -11,7 +10,10 @@ from flipper.utils.cdc import resolve_port class Main(App): - # this is basic use without sub-commands, simply to reboot flipper / power it off, not meant as a full CLI wrapper + def __init__(self, no_exit=False): + super().__init__(no_exit) + self.test_results = None + def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( @@ -67,64 +69,108 @@ class Main(App): self.logger.info("Running unit tests") flipper.send("unit_tests" + "\r") self.logger.info("Waiting for unit tests to complete") - data = flipper.read.until(">: ") - self.logger.info("Parsing result") - - lines = data.decode().split("\r\n") - - tests_re = r"Failed tests: \d{0,}" - time_re = r"Consumed: \d{0,}" - leak_re = r"Leaked: \d{0,}" - status_re = r"Status: \w{3,}" - - tests_pattern = re.compile(tests_re) - time_pattern = re.compile(time_re) - leak_pattern = re.compile(leak_re) - status_pattern = re.compile(status_re) tests, elapsed_time, leak, status = None, None, None, None total = 0 + all_required_found = False - for line in lines: - self.logger.info(line) - if "()" in line: - total += 1 + full_output = [] - if not tests: - tests = re.match(tests_pattern, line) - if not elapsed_time: - elapsed_time = re.match(time_pattern, line) - if not leak: - leak = re.match(leak_pattern, line) - if not status: - status = re.match(status_pattern, line) + tests_pattern = re.compile(r"Failed tests: \d{0,}") + time_pattern = re.compile(r"Consumed: \d{0,}") + leak_pattern = re.compile(r"Leaked: \d{0,}") + status_pattern = re.compile(r"Status: \w{3,}") - if None in (tests, elapsed_time, leak, status): - self.logger.error( - f"Failed to parse output: {tests} {elapsed_time} {leak} {status}" + try: + while not all_required_found: + try: + line = flipper.read.until("\r\n", cut_eol=True).decode() + self.logger.info(line) + if "command not found," in line: + self.logger.error(f"Command not found: {line}") + return 1 + + if "()" in line: + total += 1 + self.logger.debug(f"Test completed: {line}") + + if not tests: + tests = tests_pattern.match(line) + if not elapsed_time: + elapsed_time = time_pattern.match(line) + if not leak: + leak = leak_pattern.match(line) + if not status: + status = status_pattern.match(line) + + pattern = re.compile( + r"(\[-]|\[\\]|\[\|]|\[/-]|\[[^\]]*\]|\x1b\[\d+D)" + ) + line_to_append = pattern.sub("", line) + pattern = re.compile(r"\[3D[^\]]*") + line_to_append = pattern.sub("", line_to_append) + line_to_append = f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')} {line_to_append}" + + full_output.append(line_to_append) + + if tests and elapsed_time and leak and status: + all_required_found = True + try: + remaining = flipper.read.until(">: ", cut_eol=True).decode() + if remaining.strip(): + full_output.append(remaining) + except: + pass + break + + except Exception as e: + self.logger.error(f"Error reading output: {e}") + raise + + if None in (tests, elapsed_time, leak, status): + raise RuntimeError( + f"Failed to parse output: {tests} {elapsed_time} {leak} {status}" + ) + + leak = int(re.findall(r"[- ]\d+", leak.group(0))[0]) + status = re.findall(r"\w+", status.group(0))[1] + tests = int(re.findall(r"\d+", tests.group(0))[0]) + elapsed_time = int(re.findall(r"\d+", elapsed_time.group(0))[0]) + + test_results = { + "full_output": "\n".join(full_output), + "total_tests": total, + "failed_tests": tests, + "elapsed_time_ms": elapsed_time, + "memory_leak_bytes": leak, + "status": status, + } + + self.test_results = test_results + + output_file = "unit_tests_output.txt" + with open(output_file, "w") as f: + f.write(test_results["full_output"]) + + print( + f"::notice:: Total tests: {total} Failed tests: {tests} Status: {status} Elapsed time: {elapsed_time / 1000} s Memory leak: {leak} bytes" ) - sys.exit(1) - leak = int(re.findall(r"[- ]\d+", leak.group(0))[0]) - status = re.findall(r"\w+", status.group(0))[1] - tests = int(re.findall(r"\d+", tests.group(0))[0]) - elapsed_time = int(re.findall(r"\d+", elapsed_time.group(0))[0]) + if tests > 0 or status != "PASSED": + self.logger.error(f"Got {tests} failed tests.") + self.logger.error(f"Leaked (not failing on this stat): {leak}") + self.logger.error(f"Status: {status}") + self.logger.error(f"Time: {elapsed_time / 1000} seconds") + return 1 - if tests > 0 or status != "PASSED": - self.logger.error(f"Got {tests} failed tests.") - self.logger.error(f"Leaked (not failing on this stat): {leak}") - self.logger.error(f"Status: {status}") - self.logger.error(f"Time: {elapsed_time/1000} seconds") + self.logger.info(f"Leaked (not failing on this stat): {leak}") + self.logger.info( + f"Tests ran successfully! Time elapsed {elapsed_time / 1000} seconds. Passed {total} tests." + ) + return 0 + + finally: flipper.stop() - return 1 - - self.logger.info(f"Leaked (not failing on this stat): {leak}") - self.logger.info( - f"Tests ran successfully! Time elapsed {elapsed_time/1000} seconds. Passed {total} tests." - ) - - flipper.stop() - return 0 if __name__ == "__main__": diff --git a/scripts/toolchain/windows-toolchain-download.ps1 b/scripts/toolchain/windows-toolchain-download.ps1 index 025f8341f..85a5ab724 100644 --- a/scripts/toolchain/windows-toolchain-download.ps1 +++ b/scripts/toolchain/windows-toolchain-download.ps1 @@ -16,15 +16,15 @@ $toolchain_dist_temp_path = "$download_dir\$toolchain_dist_folder" try { if (Test-Path -LiteralPath "$toolchain_target_path") { - Write-Host -NoNewline "Removing old Windows toolchain.." - Remove-Item -LiteralPath "$toolchain_target_path" -Force -Recurse - Write-Host "done!" + Write-Host -NoNewline "Removing old Windows toolchain.." + Remove-Item -LiteralPath "$toolchain_target_path" -Force -Recurse + Write-Host "done!" } if (Test-path -LiteralPath "$toolchain_target_path\..\current") { - Write-Host -NoNewline "Unlinking 'current'.." + Write-Host -NoNewline "Unlinking 'current'.." Remove-Item -LiteralPath "$toolchain_target_path\..\current" -Force - Write-Host "done!" + Write-Host "done!" } if (!(Test-Path -LiteralPath "$toolchain_zip_temp_path" -PathType Leaf)) { @@ -46,7 +46,8 @@ if (Test-Path -LiteralPath "$toolchain_dist_temp_path") { Write-Host -NoNewline "Extracting Windows toolchain.." # This is faster than Expand-Archive Add-Type -Assembly "System.IO.Compression.Filesystem" -[System.IO.Compression.ZipFile]::ExtractToDirectory("$toolchain_zip_temp_path", "$download_dir") +Add-Type -Assembly "System.Text.Encoding" +[System.IO.Compression.ZipFile]::ExtractToDirectory("$toolchain_zip_temp_path", "$download_dir", [System.Text.Encoding]::UTF8) # Expand-Archive -LiteralPath "$toolchain_zip_temp_path" -DestinationPath "$download_dir" Write-Host -NoNewline "moving.." diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index c72569c64..8d03e0e46 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -161,6 +161,7 @@ Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5_calc.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, +Header,+,lib/toolbox/pipe.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, @@ -371,11 +372,16 @@ Function,-,__utoa,char*,"unsigned, char*, int" Function,+,__wrap___assert,void,"const char*, int, const char*" Function,+,__wrap___assert_func,void,"const char*, int, const char*, const char*" Function,+,__wrap_fflush,int,FILE* +Function,+,__wrap_fgetc,int,FILE* +Function,+,__wrap_fgets,char*,"char*, size_t, FILE*" +Function,+,__wrap_getc,int,FILE* +Function,+,__wrap_getchar,int, Function,+,__wrap_printf,int,"const char*, ..." Function,+,__wrap_putc,int,"int, FILE*" Function,+,__wrap_putchar,int,int Function,+,__wrap_puts,int,const char* Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_ungetc,int,"int, FILE*" Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." Function,-,_asniprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." @@ -1049,6 +1055,7 @@ Function,+,flipper_format_update_uint32,_Bool,"FlipperFormat*, const char*, cons Function,+,flipper_format_write_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_write_comment,_Bool,"FlipperFormat*, FuriString*" Function,+,flipper_format_write_comment_cstr,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_write_empty_line,_Bool,FlipperFormat* Function,+,flipper_format_write_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_write_header,_Bool,"FlipperFormat*, FuriString*, const uint32_t" Function,+,flipper_format_write_header_cstr,_Bool,"FlipperFormat*, const char*, const uint32_t" @@ -1575,6 +1582,7 @@ Function,+,furi_stream_buffer_receive,size_t,"FuriStreamBuffer*, void*, size_t, Function,+,furi_stream_buffer_reset,FuriStatus,FuriStreamBuffer* Function,+,furi_stream_buffer_send,size_t,"FuriStreamBuffer*, const void*, size_t, uint32_t" Function,+,furi_stream_buffer_spaces_available,size_t,FuriStreamBuffer* +Function,+,furi_stream_get_trigger_level,size_t,FuriStreamBuffer* Function,+,furi_stream_set_trigger_level,_Bool,"FuriStreamBuffer*, size_t" Function,+,furi_string_alloc,FuriString*, Function,+,furi_string_alloc_move,FuriString*,FuriString* @@ -1655,6 +1663,7 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* +Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* @@ -1675,9 +1684,12 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" -Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback +Function,+,furi_thread_set_stdin_callback,void,"FuriThreadStdinReadCallback, void*" +Function,+,furi_thread_set_stdout_callback,void,"FuriThreadStdoutWriteCallback, void*" Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* +Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait" +Function,+,furi_thread_stdin_unread,void,"char*, size_t" Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId @@ -2281,6 +2293,22 @@ Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" Function,-,pclose,int,FILE* Function,-,perror,void,const char* +Function,+,pipe_alloc,PipeSideBundle,"size_t, size_t" +Function,+,pipe_alloc_ex,PipeSideBundle,"PipeSideReceiveSettings, PipeSideReceiveSettings" +Function,+,pipe_attach_to_event_loop,void,"PipeSide*, FuriEventLoop*" +Function,+,pipe_bytes_available,size_t,PipeSide* +Function,+,pipe_detach_from_event_loop,void,PipeSide* +Function,+,pipe_free,void,PipeSide* +Function,+,pipe_install_as_stdio,void,PipeSide* +Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t, FuriWait" +Function,+,pipe_role,PipeRole,PipeSide* +Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t, FuriWait" +Function,+,pipe_set_callback_context,void,"PipeSide*, void*" +Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent" +Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent" +Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent" +Function,+,pipe_spaces_available,size_t,PipeSide* +Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" Function,+,plugin_manager_free,void,PluginManager* Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 0627511bf..4f7a69c66 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -242,6 +242,7 @@ Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5_calc.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, +Header,+,lib/toolbox/pipe.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, @@ -461,11 +462,16 @@ Function,-,__utoa,char*,"unsigned, char*, int" Function,+,__wrap___assert,void,"const char*, int, const char*" Function,+,__wrap___assert_func,void,"const char*, int, const char*, const char*" Function,+,__wrap_fflush,int,FILE* +Function,+,__wrap_fgetc,int,FILE* +Function,+,__wrap_fgets,char*,"char*, size_t, FILE*" +Function,+,__wrap_getc,int,FILE* +Function,+,__wrap_getchar,int, Function,+,__wrap_printf,int,"const char*, ..." Function,+,__wrap_putc,int,"int, FILE*" Function,+,__wrap_putchar,int,int Function,+,__wrap_puts,int,const char* Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_ungetc,int,"int, FILE*" Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." Function,-,_asniprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." @@ -1209,6 +1215,7 @@ Function,+,flipper_format_update_uint32,_Bool,"FlipperFormat*, const char*, cons Function,+,flipper_format_write_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_write_comment,_Bool,"FlipperFormat*, FuriString*" Function,+,flipper_format_write_comment_cstr,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_write_empty_line,_Bool,FlipperFormat* Function,+,flipper_format_write_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_write_header,_Bool,"FlipperFormat*, FuriString*, const uint32_t" Function,+,flipper_format_write_header_cstr,_Bool,"FlipperFormat*, const char*, const uint32_t" @@ -1856,6 +1863,7 @@ Function,+,furi_stream_buffer_receive,size_t,"FuriStreamBuffer*, void*, size_t, Function,+,furi_stream_buffer_reset,FuriStatus,FuriStreamBuffer* Function,+,furi_stream_buffer_send,size_t,"FuriStreamBuffer*, const void*, size_t, uint32_t" Function,+,furi_stream_buffer_spaces_available,size_t,FuriStreamBuffer* +Function,+,furi_stream_get_trigger_level,size_t,FuriStreamBuffer* Function,+,furi_stream_set_trigger_level,_Bool,"FuriStreamBuffer*, size_t" Function,+,furi_string_alloc,FuriString*, Function,+,furi_string_alloc_move,FuriString*,FuriString* @@ -1936,6 +1944,7 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* +Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* @@ -1956,9 +1965,12 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" -Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback +Function,+,furi_thread_set_stdin_callback,void,"FuriThreadStdinReadCallback, void*" +Function,+,furi_thread_set_stdout_callback,void,"FuriThreadStdoutWriteCallback, void*" Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* +Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait" +Function,+,furi_thread_stdin_unread,void,"char*, size_t" Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId @@ -2994,6 +3006,22 @@ Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" Function,-,pclose,int,FILE* Function,-,perror,void,const char* +Function,+,pipe_alloc,PipeSideBundle,"size_t, size_t" +Function,+,pipe_alloc_ex,PipeSideBundle,"PipeSideReceiveSettings, PipeSideReceiveSettings" +Function,+,pipe_attach_to_event_loop,void,"PipeSide*, FuriEventLoop*" +Function,+,pipe_bytes_available,size_t,PipeSide* +Function,+,pipe_detach_from_event_loop,void,PipeSide* +Function,+,pipe_free,void,PipeSide* +Function,+,pipe_install_as_stdio,void,PipeSide* +Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t, FuriWait" +Function,+,pipe_role,PipeRole,PipeSide* +Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t, FuriWait" +Function,+,pipe_set_callback_context,void,"PipeSide*, void*" +Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent" +Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent" +Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent" +Function,+,pipe_spaces_available,size_t,PipeSide* +Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" Function,+,plugin_manager_free,void,PluginManager* Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" diff --git a/targets/f7/ble_glue/furi_ble/gatt.c b/targets/f7/ble_glue/furi_ble/gatt.c index e40786583..b8ab094f9 100644 --- a/targets/f7/ble_glue/furi_ble/gatt.c +++ b/targets/f7/ble_glue/furi_ble/gatt.c @@ -7,6 +7,12 @@ #define GATT_MIN_READ_KEY_SIZE (10) +#ifdef BLE_GATT_STRICT +#define ble_gatt_strict_crash(message) furi_crash(message) +#else +#define ble_gatt_strict_crash(message) +#endif + void ble_gatt_characteristic_init( uint16_t svc_handle, const BleGattCharacteristicParams* char_descriptor, @@ -42,6 +48,7 @@ void ble_gatt_characteristic_init( &char_instance->handle); if(status) { FURI_LOG_E(TAG, "Failed to add %s char: %d", char_descriptor->name, status); + ble_gatt_strict_crash("Failed to add characteristic"); } char_instance->descriptor_handle = 0; @@ -68,6 +75,7 @@ void ble_gatt_characteristic_init( &char_instance->descriptor_handle); if(status) { FURI_LOG_E(TAG, "Failed to add %s char descriptor: %d", char_descriptor->name, status); + ble_gatt_strict_crash("Failed to add characteristic descriptor"); } if(release_data) { free((void*)char_data); @@ -82,6 +90,7 @@ void ble_gatt_characteristic_delete( if(status) { FURI_LOG_E( TAG, "Failed to delete %s char: %d", char_instance->characteristic->name, status); + ble_gatt_strict_crash("Failed to delete characteristic"); } free((void*)char_instance->characteristic); } @@ -111,14 +120,27 @@ bool ble_gatt_characteristic_update( release_data = char_descriptor->data.callback.fn(context, &char_data, &char_data_size); } - tBleStatus result = aci_gatt_update_char_value( - svc_handle, char_instance->handle, 0, char_data_size, char_data); - if(result) { - FURI_LOG_E(TAG, "Failed updating %s characteristic: %d", char_descriptor->name, result); - } + tBleStatus result; + size_t retries_left = 1000; + do { + retries_left--; + result = aci_gatt_update_char_value( + svc_handle, char_instance->handle, 0, char_data_size, char_data); + if(result == BLE_STATUS_INSUFFICIENT_RESOURCES) { + FURI_LOG_W(TAG, "Insufficient resources for %s characteristic", char_descriptor->name); + furi_delay_ms(1); + } + } while(result == BLE_STATUS_INSUFFICIENT_RESOURCES && retries_left); + if(release_data) { free((void*)char_data); } + + if(result != BLE_STATUS_SUCCESS) { + FURI_LOG_E(TAG, "Failed updating %s characteristic: %d", char_descriptor->name, result); + ble_gatt_strict_crash("Failed to update characteristic"); + } + return result != BLE_STATUS_SUCCESS; } @@ -132,6 +154,7 @@ bool ble_gatt_service_add( Service_UUID_Type, Service_UUID, Service_Type, Max_Attribute_Records, Service_Handle); if(result) { FURI_LOG_E(TAG, "Failed to add service: %x", result); + ble_gatt_strict_crash("Failed to add service"); } return result == BLE_STATUS_SUCCESS; @@ -141,6 +164,7 @@ bool ble_gatt_service_delete(uint16_t svc_handle) { tBleStatus result = aci_gatt_del_service(svc_handle); if(result) { FURI_LOG_E(TAG, "Failed to delete service: %x", result); + ble_gatt_strict_crash("Failed to delete service"); } return result == BLE_STATUS_SUCCESS;