diff --git a/.gitignore b/.gitignore index c97ba8da9f1..84b8e831993 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,7 @@ PVS-Studio.log .gdbinit -/fbt_options_local.py \ No newline at end of file +/fbt_options_local.py + +# JS packages +node_modules/ diff --git a/ReadMe.md b/ReadMe.md index f4c0dffedc3..f16adc43289 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -9,8 +9,8 @@ # Flipper Zero Firmware - [Flipper Zero Official Website](https://flipperzero.one). A simple way to explain to your friends what Flipper Zero can do. -- [Flipper Zero Firmware Update](https://update.flipperzero.one). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices. -- [User Documentation](https://docs.flipperzero.one). Learn more about your dolphin: specs, usage guides, and anything you want to ask. +- [Flipper Zero Firmware Update](https://flipperzero.one/update). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices. +- [User Documentation](https://docs.flipper.net). Learn more about your dolphin: specs, usage guides, and anything you want to ask. - [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). Dive into the Flipper Zero Firmware source code: build system, firmware structure, and more. # Contributing @@ -19,7 +19,7 @@ Our main goal is to build a healthy and sustainable community around Flipper, so ## I need help -The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). If you want to contribute to the firmware development, or modify it for your own needs, you can also check our [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). +The best place to search for answers is our [User Documentation](https://docs.flipper.net). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). If you want to contribute to the firmware development or modify it for your own needs, you can also check our [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). ## I want to report an issue @@ -120,3 +120,7 @@ Also, see `ReadMe.md` files inside those directories for further details. - Website: [flipperzero.one](https://flipperzero.one) - Forum: [forum.flipperzero.one](https://forum.flipperzero.one/) - Kickstarter: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers) + +## SAST Tools + +- [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. diff --git a/applications/debug/direct_draw/direct_draw.c b/applications/debug/direct_draw/direct_draw.c index bc1e5545977..1e45a6d949b 100644 --- a/applications/debug/direct_draw/direct_draw.c +++ b/applications/debug/direct_draw/direct_draw.c @@ -1,6 +1,5 @@ #include #include -#include #include #define BUFFER_SIZE (32U) @@ -42,10 +41,11 @@ static DirectDraw* direct_draw_alloc(void) { static void direct_draw_free(DirectDraw* instance) { furi_pubsub_unsubscribe(instance->input, instance->input_subscription); - instance->canvas = NULL; gui_direct_draw_release(instance->gui); furi_record_close(RECORD_GUI); furi_record_close(RECORD_INPUT_EVENTS); + + free(instance); } static void direct_draw_block(Canvas* canvas, uint32_t size, uint32_t counter) { diff --git a/applications/debug/event_loop_blink_test/event_loop_blink_test.c b/applications/debug/event_loop_blink_test/event_loop_blink_test.c index 7f00e63f2e0..1cddfa323d6 100644 --- a/applications/debug/event_loop_blink_test/event_loop_blink_test.c +++ b/applications/debug/event_loop_blink_test/event_loop_blink_test.c @@ -82,7 +82,7 @@ static void view_port_input_callback(InputEvent* input_event, void* context) { furi_message_queue_put(app->input_queue, input_event, 0); } -static bool input_queue_callback(FuriEventLoopObject* object, void* context) { +static void input_queue_callback(FuriEventLoopObject* object, void* context) { FuriMessageQueue* queue = object; EventLoopBlinkTestApp* app = context; @@ -107,8 +107,6 @@ static bool input_queue_callback(FuriEventLoopObject* object, void* context) { furi_event_loop_stop(app->event_loop); } } - - return true; } static void blink_timer_callback(void* context) { diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index c87305847af..dec3283e4e7 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -221,6 +221,14 @@ App( requires=["unit_tests"], ) +App( + appid="test_js", + sources=["tests/common/*.c", "tests/js/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests", "js_app"], +) + App( appid="test_strint", sources=["tests/common/*.c", "tests/strint/*.c"], diff --git a/applications/debug/unit_tests/resources/unit_tests/js/basic.js b/applications/debug/unit_tests/resources/unit_tests/js/basic.js new file mode 100644 index 00000000000..a08041e9fd4 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/basic.js @@ -0,0 +1,15 @@ +let tests = require("tests"); +let flipper = require("flipper"); + +tests.assert_eq(1337, 1337); +tests.assert_eq("hello", "hello"); + +tests.assert_eq("compatible", sdkCompatibilityStatus(0, 1)); +tests.assert_eq("firmwareTooOld", sdkCompatibilityStatus(100500, 0)); +tests.assert_eq("firmwareTooNew", sdkCompatibilityStatus(-100500, 0)); +tests.assert_eq(true, doesSdkSupport(["baseline"])); +tests.assert_eq(false, doesSdkSupport(["abobus", "other-nonexistent-feature"])); + +tests.assert_eq("flipperdevices", flipper.firmwareVendor); +tests.assert_eq(0, flipper.jsSdkVersion[0]); +tests.assert_eq(1, flipper.jsSdkVersion[1]); diff --git a/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js b/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js new file mode 100644 index 00000000000..0437b829325 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js @@ -0,0 +1,30 @@ +let tests = require("tests"); +let event_loop = require("event_loop"); + +let ext = { + i: 0, + received: false, +}; + +let queue = event_loop.queue(16); + +event_loop.subscribe(queue.input, function (_, item, tests, ext) { + tests.assert_eq(123, item); + ext.received = true; +}, tests, ext); + +event_loop.subscribe(event_loop.timer("periodic", 1), function (_, _item, queue, counter, ext) { + ext.i++; + queue.send(123); + if (counter === 10) + event_loop.stop(); + return [queue, counter + 1, ext]; +}, queue, 1, ext); + +event_loop.subscribe(event_loop.timer("oneshot", 1000), function (_, _item, tests) { + tests.fail("event loop was not stopped"); +}, tests); + +event_loop.run(); +tests.assert_eq(10, ext.i); +tests.assert_eq(true, ext.received); diff --git a/applications/debug/unit_tests/resources/unit_tests/js/math.js b/applications/debug/unit_tests/resources/unit_tests/js/math.js new file mode 100644 index 00000000000..ea8d80f9140 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/math.js @@ -0,0 +1,34 @@ +let tests = require("tests"); +let math = require("math"); + +// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 + +// basics +tests.assert_float_close(5, math.abs(-5), math.EPSILON); +tests.assert_float_close(0.5, math.abs(-0.5), math.EPSILON); +tests.assert_float_close(5, math.abs(5), math.EPSILON); +tests.assert_float_close(0.5, math.abs(0.5), math.EPSILON); +tests.assert_float_close(3, math.cbrt(27), math.EPSILON); +tests.assert_float_close(6, math.ceil(5.3), math.EPSILON); +tests.assert_float_close(31, math.clz32(1), math.EPSILON); +tests.assert_float_close(5, math.floor(5.7), math.EPSILON); +tests.assert_float_close(5, math.max(3, 5), math.EPSILON); +tests.assert_float_close(3, math.min(3, 5), math.EPSILON); +tests.assert_float_close(-1, math.sign(-5), math.EPSILON); +tests.assert_float_close(5, math.trunc(5.7), math.EPSILON); + +// trig +tests.assert_float_close(1.0471975511965976, math.acos(0.5), math.EPSILON); +tests.assert_float_close(1.3169578969248166, math.acosh(2), math.EPSILON); +tests.assert_float_close(0.5235987755982988, math.asin(0.5), math.EPSILON); +tests.assert_float_close(1.4436354751788103, math.asinh(2), math.EPSILON); +tests.assert_float_close(0.7853981633974483, math.atan(1), math.EPSILON); +tests.assert_float_close(0.7853981633974483, math.atan2(1, 1), math.EPSILON); +tests.assert_float_close(0.5493061443340549, math.atanh(0.5), math.EPSILON); +tests.assert_float_close(-1, math.cos(math.PI), math.EPSILON * 18); // Error 3.77475828372553223744e-15 +tests.assert_float_close(1, math.sin(math.PI / 2), math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 + +// powers +tests.assert_float_close(5, math.sqrt(25), math.EPSILON); +tests.assert_float_close(8, math.pow(2, 3), math.EPSILON); +tests.assert_float_close(2.718281828459045, math.exp(1), math.EPSILON * 2); // Error 4.44089209850062616169e-16 diff --git a/applications/debug/unit_tests/resources/unit_tests/js/storage.js b/applications/debug/unit_tests/resources/unit_tests/js/storage.js new file mode 100644 index 00000000000..872b29cfbc4 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/storage.js @@ -0,0 +1,136 @@ +let storage = require("storage"); +let tests = require("tests"); + +let baseDir = "/ext/.tmp/unit_tests"; + +tests.assert_eq(true, storage.rmrf(baseDir)); +tests.assert_eq(true, storage.makeDirectory(baseDir)); + +// write +let file = storage.openFile(baseDir + "/helloworld", "w", "create_always"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.write("Hello, World!")); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// read +file = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.size()); +tests.assert_eq("Hello, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// seek +file = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.size()); +tests.assert_eq("Hello, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.seekAbsolute(1)); +tests.assert_eq(true, file.seekRelative(2)); +tests.assert_eq(3, file.tell()); +tests.assert_eq(false, file.eof()); +tests.assert_eq("lo, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.eof()); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// byte-level copy +let src = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +let dst = storage.openFile(baseDir + "/helloworld2", "rw", "create_always"); +tests.assert_eq(true, !!src); +tests.assert_eq(true, src.isOpen()); +tests.assert_eq(true, !!dst); +tests.assert_eq(true, dst.isOpen()); +tests.assert_eq(true, src.copyTo(dst, 10)); +tests.assert_eq(true, dst.seekAbsolute(0)); +tests.assert_eq("Hello, Wor", dst.read("ascii", 128)); +tests.assert_eq(true, src.copyTo(dst, 3)); +tests.assert_eq(true, dst.seekAbsolute(0)); +tests.assert_eq("Hello, World!", dst.read("ascii", 128)); +tests.assert_eq(true, src.eof()); +tests.assert_eq(true, src.close()); +tests.assert_eq(false, src.isOpen()); +tests.assert_eq(true, dst.eof()); +tests.assert_eq(true, dst.close()); +tests.assert_eq(false, dst.isOpen()); + +// truncate +tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld2")); +file = storage.openFile(baseDir + "/helloworld2", "w", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.seekAbsolute(5)); +tests.assert_eq(true, file.truncate()); +tests.assert_eq(true, file.close()); +file = storage.openFile(baseDir + "/helloworld2", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq("Hello", file.read("ascii", 128)); +tests.assert_eq(true, file.close()); + +// existence +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld2")); +tests.assert_eq(false, storage.fileExists(baseDir + "/sus_amogus_123")); +tests.assert_eq(false, storage.directoryExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir)); +tests.assert_eq(true, storage.directoryExists(baseDir)); +tests.assert_eq(true, storage.fileOrDirExists(baseDir)); +tests.assert_eq(true, storage.remove(baseDir + "/helloworld2")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld2")); + +// stat +let stat = storage.stat(baseDir + "/helloworld"); +tests.assert_eq(true, !!stat); +tests.assert_eq(baseDir + "/helloworld", stat.path); +tests.assert_eq(false, stat.isDirectory); +tests.assert_eq(13, stat.size); + +// rename +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.rename(baseDir + "/helloworld", baseDir + "/helloworld123")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.rename(baseDir + "/helloworld123", baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); + +// copy +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld123")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123")); + +// next avail +tests.assert_eq("helloworld1", storage.nextAvailableFilename(baseDir, "helloworld", "", 20)); + +// fs info +let fsInfo = storage.fsInfo("/ext"); +tests.assert_eq(true, !!fsInfo); +tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace); // idk \(-_-)/ +fsInfo = storage.fsInfo("/int"); +tests.assert_eq(true, !!fsInfo); +tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace); + +// path operations +tests.assert_eq(true, storage.arePathsEqual("/ext/test", "/ext/Test")); +tests.assert_eq(false, storage.arePathsEqual("/ext/test", "/ext/Testttt")); +tests.assert_eq(true, storage.isSubpathOf("/ext/test", "/ext/test/sub")); +tests.assert_eq(false, storage.isSubpathOf("/ext/test/sub", "/ext/test")); + +// dir +let entries = storage.readDirectory(baseDir); +tests.assert_eq(true, !!entries); +// FIXME: (-nofl) this test suite assumes that files are listed by +// `readDirectory` in the exact order that they were created, which is not +// something that is actually guaranteed. +// Possible solution: sort and compare the array. +tests.assert_eq("helloworld", entries[0].path); +tests.assert_eq("helloworld123", entries[1].path); + +tests.assert_eq(true, storage.rmrf(baseDir)); +tests.assert_eq(true, storage.makeDirectory(baseDir)); diff --git a/applications/debug/unit_tests/tests/furi/furi_errno_test.c b/applications/debug/unit_tests/tests/furi/furi_errno_test.c new file mode 100644 index 00000000000..b42e7c0828e --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_errno_test.c @@ -0,0 +1,51 @@ +#include +#include +#include "../test.h" // IWYU pragma: keep + +#define TAG "ErrnoTest" +#define THREAD_CNT 16 +#define ITER_CNT 1000 + +static int32_t errno_fuzzer(void* context) { + int start_value = (int)context; + int32_t fails = 0; + + for(int i = start_value; i < start_value + ITER_CNT; i++) { + errno = i; + furi_thread_yield(); + if(errno != i) fails++; + } + + for(int i = 0; i < ITER_CNT; i++) { + errno = 0; + furi_thread_yield(); + UNUSED(strtol("123456", NULL, 10)); // -V530 + furi_thread_yield(); + if(errno != 0) fails++; + + errno = 0; + furi_thread_yield(); + UNUSED(strtol("123456123456123456123456123456123456123456123456", NULL, 10)); // -V530 + furi_thread_yield(); + if(errno != ERANGE) fails++; + } + + return fails; +} + +void test_errno_saving(void) { + FuriThread* threads[THREAD_CNT]; + + for(int i = 0; i < THREAD_CNT; i++) { + int start_value = i * ITER_CNT; + threads[i] = furi_thread_alloc_ex("ErrnoFuzzer", 1024, errno_fuzzer, (void*)start_value); + furi_thread_set_priority(threads[i], FuriThreadPriorityNormal); + furi_thread_start(threads[i]); + } + + for(int i = 0; i < THREAD_CNT; i++) { + furi_thread_join(threads[i]); + mu_assert_int_eq(0, furi_thread_get_return_code(threads[i])); + furi_thread_free(threads[i]); + } +} diff --git a/applications/debug/unit_tests/tests/furi/furi_event_loop.c b/applications/debug/unit_tests/tests/furi/furi_event_loop.c deleted file mode 100644 index 291181c77f5..00000000000 --- a/applications/debug/unit_tests/tests/furi/furi_event_loop.c +++ /dev/null @@ -1,205 +0,0 @@ -#include "../test.h" -#include -#include - -#include -#include - -#define TAG "TestFuriEventLoop" - -#define EVENT_LOOP_EVENT_COUNT (256u) - -typedef struct { - FuriMessageQueue* mq; - - FuriEventLoop* producer_event_loop; - uint32_t producer_counter; - - FuriEventLoop* consumer_event_loop; - uint32_t consumer_counter; -} TestFuriData; - -bool test_furi_event_loop_producer_mq_callback(FuriEventLoopObject* object, void* context) { - furi_check(context); - - TestFuriData* data = context; - furi_check(data->mq == object, "Invalid queue"); - - FURI_LOG_I( - TAG, "producer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter); - - if(data->producer_counter == EVENT_LOOP_EVENT_COUNT / 2) { - furi_event_loop_unsubscribe(data->producer_event_loop, data->mq); - furi_event_loop_subscribe_message_queue( - data->producer_event_loop, - data->mq, - FuriEventLoopEventOut, - test_furi_event_loop_producer_mq_callback, - data); - } - - if(data->producer_counter == EVENT_LOOP_EVENT_COUNT) { - furi_event_loop_stop(data->producer_event_loop); - return false; - } - - data->producer_counter++; - furi_check( - furi_message_queue_put(data->mq, &data->producer_counter, 0) == FuriStatusOk, - "furi_message_queue_put failed"); - furi_delay_us(furi_hal_random_get() % 1000); - - return true; -} - -int32_t test_furi_event_loop_producer(void* p) { - furi_check(p); - - TestFuriData* data = p; - - FURI_LOG_I(TAG, "producer start 1st run"); - - data->producer_event_loop = furi_event_loop_alloc(); - furi_event_loop_subscribe_message_queue( - data->producer_event_loop, - data->mq, - FuriEventLoopEventOut, - test_furi_event_loop_producer_mq_callback, - data); - - furi_event_loop_run(data->producer_event_loop); - - // 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags - xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits); - - furi_event_loop_unsubscribe(data->producer_event_loop, data->mq); - furi_event_loop_free(data->producer_event_loop); - - FURI_LOG_I(TAG, "producer start 2nd run"); - - data->producer_counter = 0; - data->producer_event_loop = furi_event_loop_alloc(); - - furi_event_loop_subscribe_message_queue( - data->producer_event_loop, - data->mq, - FuriEventLoopEventOut, - test_furi_event_loop_producer_mq_callback, - data); - - furi_event_loop_run(data->producer_event_loop); - - furi_event_loop_unsubscribe(data->producer_event_loop, data->mq); - furi_event_loop_free(data->producer_event_loop); - - FURI_LOG_I(TAG, "producer end"); - - return 0; -} - -bool test_furi_event_loop_consumer_mq_callback(FuriEventLoopObject* object, void* context) { - furi_check(context); - - TestFuriData* data = context; - furi_check(data->mq == object); - - furi_delay_us(furi_hal_random_get() % 1000); - furi_check(furi_message_queue_get(data->mq, &data->consumer_counter, 0) == FuriStatusOk); - - FURI_LOG_I( - TAG, "consumer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter); - - if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT / 2) { - furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq); - furi_event_loop_subscribe_message_queue( - data->consumer_event_loop, - data->mq, - FuriEventLoopEventIn, - test_furi_event_loop_consumer_mq_callback, - data); - } - - if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT) { - furi_event_loop_stop(data->consumer_event_loop); - return false; - } - - return true; -} - -int32_t test_furi_event_loop_consumer(void* p) { - furi_check(p); - - TestFuriData* data = p; - - FURI_LOG_I(TAG, "consumer start 1st run"); - - data->consumer_event_loop = furi_event_loop_alloc(); - furi_event_loop_subscribe_message_queue( - data->consumer_event_loop, - data->mq, - FuriEventLoopEventIn, - test_furi_event_loop_consumer_mq_callback, - data); - - furi_event_loop_run(data->consumer_event_loop); - - // 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags - xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits); - - furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq); - furi_event_loop_free(data->consumer_event_loop); - - FURI_LOG_I(TAG, "consumer start 2nd run"); - - data->consumer_counter = 0; - data->consumer_event_loop = furi_event_loop_alloc(); - furi_event_loop_subscribe_message_queue( - data->consumer_event_loop, - data->mq, - FuriEventLoopEventIn, - test_furi_event_loop_consumer_mq_callback, - data); - - furi_event_loop_run(data->consumer_event_loop); - - furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq); - furi_event_loop_free(data->consumer_event_loop); - - FURI_LOG_I(TAG, "consumer end"); - - return 0; -} - -void test_furi_event_loop(void) { - TestFuriData data = {}; - - data.mq = furi_message_queue_alloc(16, sizeof(uint32_t)); - - FuriThread* producer_thread = furi_thread_alloc(); - furi_thread_set_name(producer_thread, "producer_thread"); - furi_thread_set_stack_size(producer_thread, 1 * 1024); - furi_thread_set_callback(producer_thread, test_furi_event_loop_producer); - furi_thread_set_context(producer_thread, &data); - furi_thread_start(producer_thread); - - FuriThread* consumer_thread = furi_thread_alloc(); - furi_thread_set_name(consumer_thread, "consumer_thread"); - furi_thread_set_stack_size(consumer_thread, 1 * 1024); - furi_thread_set_callback(consumer_thread, test_furi_event_loop_consumer); - furi_thread_set_context(consumer_thread, &data); - furi_thread_start(consumer_thread); - - // Wait for thread to complete their tasks - furi_thread_join(producer_thread); - furi_thread_join(consumer_thread); - - // The test itself - mu_assert_int_eq(data.producer_counter, data.consumer_counter); - mu_assert_int_eq(data.producer_counter, EVENT_LOOP_EVENT_COUNT); - - // Release memory - furi_thread_free(consumer_thread); - furi_thread_free(producer_thread); - furi_message_queue_free(data.mq); -} diff --git a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c new file mode 100644 index 00000000000..73f38ab77f8 --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c @@ -0,0 +1,490 @@ +#include "../test.h" +#include +#include + +#include +#include + +#define TAG "TestFuriEventLoop" + +#define MESSAGE_COUNT (256UL) +#define EVENT_FLAG_COUNT (23UL) +#define PRIMITIVE_COUNT (4UL) +#define RUN_COUNT (2UL) + +typedef struct { + FuriEventLoop* event_loop; + uint32_t message_queue_count; + uint32_t stream_buffer_count; + uint32_t event_flag_count; + uint32_t semaphore_count; + uint32_t primitives_tested; +} TestFuriEventLoopThread; + +typedef struct { + FuriMessageQueue* message_queue; + FuriStreamBuffer* stream_buffer; + FuriEventFlag* event_flag; + FuriSemaphore* semaphore; + + TestFuriEventLoopThread producer; + TestFuriEventLoopThread consumer; +} TestFuriEventLoopData; + +static void test_furi_event_loop_pending_callback(void* context) { + furi_check(context); + + TestFuriEventLoopThread* test_thread = context; + furi_check(test_thread->primitives_tested < PRIMITIVE_COUNT); + + test_thread->primitives_tested++; + FURI_LOG_I(TAG, "primitives tested: %lu", test_thread->primitives_tested); + + if(test_thread->primitives_tested == PRIMITIVE_COUNT) { + furi_event_loop_stop(test_thread->event_loop); + } +} + +static void test_furi_event_loop_thread_init(TestFuriEventLoopThread* test_thread) { + memset(test_thread, 0, sizeof(TestFuriEventLoopThread)); + test_thread->event_loop = furi_event_loop_alloc(); +} + +static void test_furi_event_loop_thread_run_and_cleanup(TestFuriEventLoopThread* test_thread) { + furi_event_loop_run(test_thread->event_loop); + // 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags + xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits); + furi_event_loop_free(test_thread->event_loop); +} + +static void test_furi_event_loop_producer_message_queue_callback( + FuriEventLoopObject* object, + void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->message_queue == object); + + FURI_LOG_I( + TAG, + "producer MessageQueue: %lu %lu", + data->producer.message_queue_count, + data->consumer.message_queue_count); + + if(data->producer.message_queue_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(data->producer.event_loop, data->message_queue); + furi_event_loop_subscribe_message_queue( + data->producer.event_loop, + data->message_queue, + FuriEventLoopEventOut, + test_furi_event_loop_producer_message_queue_callback, + data); + + } else if(data->producer.message_queue_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(data->producer.event_loop, data->message_queue); + furi_event_loop_pend_callback( + data->producer.event_loop, test_furi_event_loop_pending_callback, &data->producer); + return; + } + + data->producer.message_queue_count++; + + furi_check( + furi_message_queue_put(data->message_queue, &data->producer.message_queue_count, 0) == + FuriStatusOk); + + furi_delay_us(furi_hal_random_get() % 100); +} + +static void test_furi_event_loop_producer_stream_buffer_callback( + FuriEventLoopObject* object, + void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->stream_buffer == object); + + TestFuriEventLoopThread* producer = &data->producer; + TestFuriEventLoopThread* consumer = &data->consumer; + + FURI_LOG_I( + TAG, + "producer StreamBuffer: %lu %lu", + producer->stream_buffer_count, + consumer->stream_buffer_count); + + if(producer->stream_buffer_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(producer->event_loop, data->stream_buffer); + furi_event_loop_subscribe_stream_buffer( + producer->event_loop, + data->stream_buffer, + FuriEventLoopEventOut, + test_furi_event_loop_producer_stream_buffer_callback, + data); + + } else if(producer->stream_buffer_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(producer->event_loop, data->stream_buffer); + furi_event_loop_pend_callback( + producer->event_loop, test_furi_event_loop_pending_callback, producer); + return; + } + + producer->stream_buffer_count++; + + furi_check( + furi_stream_buffer_send( + data->stream_buffer, &producer->stream_buffer_count, sizeof(uint32_t), 0) == + sizeof(uint32_t)); + + furi_delay_us(furi_hal_random_get() % 100); +} + +static void + test_furi_event_loop_producer_event_flag_callback(FuriEventLoopObject* object, void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->event_flag == object); + + const uint32_t producer_flags = (1UL << data->producer.event_flag_count); + const uint32_t consumer_flags = (1UL << data->consumer.event_flag_count); + + FURI_LOG_I(TAG, "producer EventFlag: 0x%06lX 0x%06lX", producer_flags, consumer_flags); + + furi_check(furi_event_flag_set(data->event_flag, producer_flags) & producer_flags); + + if(data->producer.event_flag_count == EVENT_FLAG_COUNT / 2) { + furi_event_loop_unsubscribe(data->producer.event_loop, data->event_flag); + furi_event_loop_subscribe_event_flag( + data->producer.event_loop, + data->event_flag, + FuriEventLoopEventOut, + test_furi_event_loop_producer_event_flag_callback, + data); + + } else if(data->producer.event_flag_count == EVENT_FLAG_COUNT) { + furi_event_loop_unsubscribe(data->producer.event_loop, data->event_flag); + furi_event_loop_pend_callback( + data->producer.event_loop, test_furi_event_loop_pending_callback, &data->producer); + return; + } + + data->producer.event_flag_count++; + + furi_delay_us(furi_hal_random_get() % 100); +} + +static void + test_furi_event_loop_producer_semaphore_callback(FuriEventLoopObject* object, void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->semaphore == object); + + TestFuriEventLoopThread* producer = &data->producer; + TestFuriEventLoopThread* consumer = &data->consumer; + + FURI_LOG_I( + TAG, "producer Semaphore: %lu %lu", producer->semaphore_count, consumer->semaphore_count); + furi_check(furi_semaphore_release(data->semaphore) == FuriStatusOk); + + if(producer->semaphore_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(producer->event_loop, data->semaphore); + furi_event_loop_subscribe_semaphore( + producer->event_loop, + data->semaphore, + FuriEventLoopEventOut, + test_furi_event_loop_producer_semaphore_callback, + data); + + } else if(producer->semaphore_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(producer->event_loop, data->semaphore); + furi_event_loop_pend_callback( + producer->event_loop, test_furi_event_loop_pending_callback, producer); + return; + } + + data->producer.semaphore_count++; + + furi_delay_us(furi_hal_random_get() % 100); +} + +static int32_t test_furi_event_loop_producer(void* p) { + furi_check(p); + + TestFuriEventLoopData* data = p; + TestFuriEventLoopThread* producer = &data->producer; + + for(uint32_t i = 0; i < RUN_COUNT; ++i) { + FURI_LOG_I(TAG, "producer start run %lu", i); + + test_furi_event_loop_thread_init(producer); + + furi_event_loop_subscribe_message_queue( + producer->event_loop, + data->message_queue, + FuriEventLoopEventOut, + test_furi_event_loop_producer_message_queue_callback, + data); + furi_event_loop_subscribe_stream_buffer( + producer->event_loop, + data->stream_buffer, + FuriEventLoopEventOut, + test_furi_event_loop_producer_stream_buffer_callback, + data); + furi_event_loop_subscribe_event_flag( + producer->event_loop, + data->event_flag, + FuriEventLoopEventOut, + test_furi_event_loop_producer_event_flag_callback, + data); + furi_event_loop_subscribe_semaphore( + producer->event_loop, + data->semaphore, + FuriEventLoopEventOut, + test_furi_event_loop_producer_semaphore_callback, + data); + + test_furi_event_loop_thread_run_and_cleanup(producer); + } + + FURI_LOG_I(TAG, "producer end"); + + return 0; +} + +static void test_furi_event_loop_consumer_message_queue_callback( + FuriEventLoopObject* object, + void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->message_queue == object); + + furi_delay_us(furi_hal_random_get() % 100); + + furi_check( + furi_message_queue_get(data->message_queue, &data->consumer.message_queue_count, 0) == + FuriStatusOk); + + FURI_LOG_I( + TAG, + "consumer MessageQueue: %lu %lu", + data->producer.message_queue_count, + data->consumer.message_queue_count); + + if(data->consumer.message_queue_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(data->consumer.event_loop, data->message_queue); + furi_event_loop_subscribe_message_queue( + data->consumer.event_loop, + data->message_queue, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_message_queue_callback, + data); + + } else if(data->consumer.message_queue_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(data->consumer.event_loop, data->message_queue); + furi_event_loop_pend_callback( + data->consumer.event_loop, test_furi_event_loop_pending_callback, &data->consumer); + } +} + +static void test_furi_event_loop_consumer_stream_buffer_callback( + FuriEventLoopObject* object, + void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->stream_buffer == object); + + TestFuriEventLoopThread* producer = &data->producer; + TestFuriEventLoopThread* consumer = &data->consumer; + + furi_delay_us(furi_hal_random_get() % 100); + + furi_check( + furi_stream_buffer_receive( + data->stream_buffer, &consumer->stream_buffer_count, sizeof(uint32_t), 0) == + sizeof(uint32_t)); + + FURI_LOG_I( + TAG, + "consumer StreamBuffer: %lu %lu", + producer->stream_buffer_count, + consumer->stream_buffer_count); + + if(consumer->stream_buffer_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(consumer->event_loop, data->stream_buffer); + furi_event_loop_subscribe_stream_buffer( + consumer->event_loop, + data->stream_buffer, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_stream_buffer_callback, + data); + + } else if(consumer->stream_buffer_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(data->consumer.event_loop, data->stream_buffer); + furi_event_loop_pend_callback( + consumer->event_loop, test_furi_event_loop_pending_callback, consumer); + } +} + +static void + test_furi_event_loop_consumer_event_flag_callback(FuriEventLoopObject* object, void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->event_flag == object); + + furi_delay_us(furi_hal_random_get() % 100); + + const uint32_t producer_flags = (1UL << data->producer.event_flag_count); + const uint32_t consumer_flags = (1UL << data->consumer.event_flag_count); + + furi_check( + furi_event_flag_wait(data->event_flag, consumer_flags, FuriFlagWaitAny, 0) & + consumer_flags); + + FURI_LOG_I(TAG, "consumer EventFlag: 0x%06lX 0x%06lX", producer_flags, consumer_flags); + + if(data->consumer.event_flag_count == EVENT_FLAG_COUNT / 2) { + furi_event_loop_unsubscribe(data->consumer.event_loop, data->event_flag); + furi_event_loop_subscribe_event_flag( + data->consumer.event_loop, + data->event_flag, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_event_flag_callback, + data); + + } else if(data->consumer.event_flag_count == EVENT_FLAG_COUNT) { + furi_event_loop_unsubscribe(data->consumer.event_loop, data->event_flag); + furi_event_loop_pend_callback( + data->consumer.event_loop, test_furi_event_loop_pending_callback, &data->consumer); + return; + } + + data->consumer.event_flag_count++; +} + +static void + test_furi_event_loop_consumer_semaphore_callback(FuriEventLoopObject* object, void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->semaphore == object); + + furi_delay_us(furi_hal_random_get() % 100); + + TestFuriEventLoopThread* producer = &data->producer; + TestFuriEventLoopThread* consumer = &data->consumer; + + furi_check(furi_semaphore_acquire(data->semaphore, 0) == FuriStatusOk); + + FURI_LOG_I( + TAG, "consumer Semaphore: %lu %lu", producer->semaphore_count, consumer->semaphore_count); + + if(consumer->semaphore_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(consumer->event_loop, data->semaphore); + furi_event_loop_subscribe_semaphore( + consumer->event_loop, + data->semaphore, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_semaphore_callback, + data); + + } else if(consumer->semaphore_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(consumer->event_loop, data->semaphore); + furi_event_loop_pend_callback( + consumer->event_loop, test_furi_event_loop_pending_callback, consumer); + return; + } + + data->consumer.semaphore_count++; +} + +static int32_t test_furi_event_loop_consumer(void* p) { + furi_check(p); + + TestFuriEventLoopData* data = p; + TestFuriEventLoopThread* consumer = &data->consumer; + + for(uint32_t i = 0; i < RUN_COUNT; ++i) { + FURI_LOG_I(TAG, "consumer start run %lu", i); + + test_furi_event_loop_thread_init(consumer); + + furi_event_loop_subscribe_message_queue( + consumer->event_loop, + data->message_queue, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_message_queue_callback, + data); + furi_event_loop_subscribe_stream_buffer( + consumer->event_loop, + data->stream_buffer, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_stream_buffer_callback, + data); + furi_event_loop_subscribe_event_flag( + consumer->event_loop, + data->event_flag, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_event_flag_callback, + data); + furi_event_loop_subscribe_semaphore( + consumer->event_loop, + data->semaphore, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_semaphore_callback, + data); + + test_furi_event_loop_thread_run_and_cleanup(consumer); + } + + FURI_LOG_I(TAG, "consumer end"); + + return 0; +} + +void test_furi_event_loop(void) { + TestFuriEventLoopData data = {}; + + data.message_queue = furi_message_queue_alloc(16, sizeof(uint32_t)); + data.stream_buffer = furi_stream_buffer_alloc(16, sizeof(uint32_t)); + data.event_flag = furi_event_flag_alloc(); + data.semaphore = furi_semaphore_alloc(8, 0); + + FuriThread* producer_thread = + furi_thread_alloc_ex("producer_thread", 1 * 1024, test_furi_event_loop_producer, &data); + furi_thread_start(producer_thread); + + FuriThread* consumer_thread = + furi_thread_alloc_ex("consumer_thread", 1 * 1024, test_furi_event_loop_consumer, &data); + furi_thread_start(consumer_thread); + + // Wait for thread to complete their tasks + furi_thread_join(producer_thread); + furi_thread_join(consumer_thread); + + TestFuriEventLoopThread* producer = &data.producer; + TestFuriEventLoopThread* consumer = &data.consumer; + + // The test itself + mu_assert_int_eq(producer->message_queue_count, consumer->message_queue_count); + mu_assert_int_eq(producer->message_queue_count, MESSAGE_COUNT); + mu_assert_int_eq(producer->stream_buffer_count, consumer->stream_buffer_count); + mu_assert_int_eq(producer->stream_buffer_count, MESSAGE_COUNT); + mu_assert_int_eq(producer->event_flag_count, consumer->event_flag_count); + mu_assert_int_eq(producer->event_flag_count, EVENT_FLAG_COUNT); + mu_assert_int_eq(producer->semaphore_count, consumer->semaphore_count); + mu_assert_int_eq(producer->semaphore_count, MESSAGE_COUNT); + + // Release memory + furi_thread_free(consumer_thread); + furi_thread_free(producer_thread); + + furi_message_queue_free(data.message_queue); + furi_stream_buffer_free(data.stream_buffer); + furi_event_flag_free(data.event_flag); + furi_semaphore_free(data.semaphore); +} diff --git a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c new file mode 100644 index 00000000000..d9ad0303955 --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c @@ -0,0 +1,103 @@ +#include +#include "../test.h" // IWYU pragma: keep + +#define MESSAGE_QUEUE_CAPACITY (16U) +#define MESSAGE_QUEUE_ELEMENT_SIZE (sizeof(uint32_t)) + +#define STREAM_BUFFER_SIZE (32U) +#define STREAM_BUFFER_TRG_LEVEL (STREAM_BUFFER_SIZE / 2U) + +typedef struct { + FuriMessageQueue* message_queue; + FuriStreamBuffer* stream_buffer; +} TestFuriPrimitivesData; + +static void test_furi_message_queue(TestFuriPrimitivesData* data) { + FuriMessageQueue* message_queue = data->message_queue; + + mu_assert_int_eq(0, furi_message_queue_get_count(message_queue)); + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_space(message_queue)); + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_capacity(message_queue)); + mu_assert_int_eq( + MESSAGE_QUEUE_ELEMENT_SIZE, furi_message_queue_get_message_size(message_queue)); + + for(uint32_t i = 0;; ++i) { + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY - i, furi_message_queue_get_space(message_queue)); + mu_assert_int_eq(i, furi_message_queue_get_count(message_queue)); + + if(furi_message_queue_put(message_queue, &i, 0) != FuriStatusOk) { + break; + } + } + + mu_assert_int_eq(0, furi_message_queue_get_space(message_queue)); + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_count(message_queue)); + + for(uint32_t i = 0;; ++i) { + mu_assert_int_eq(i, furi_message_queue_get_space(message_queue)); + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY - i, furi_message_queue_get_count(message_queue)); + + uint32_t value; + if(furi_message_queue_get(message_queue, &value, 0) != FuriStatusOk) { + break; + } + + mu_assert_int_eq(i, value); + } + + mu_assert_int_eq(0, furi_message_queue_get_count(message_queue)); + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_space(message_queue)); +} + +static void test_furi_stream_buffer(TestFuriPrimitivesData* data) { + FuriStreamBuffer* stream_buffer = data->stream_buffer; + + mu_assert(furi_stream_buffer_is_empty(stream_buffer), "Must be empty"); + mu_assert(!furi_stream_buffer_is_full(stream_buffer), "Must be not full"); + mu_assert_int_eq(0, furi_stream_buffer_bytes_available(stream_buffer)); + mu_assert_int_eq(STREAM_BUFFER_SIZE, furi_stream_buffer_spaces_available(stream_buffer)); + + for(uint8_t i = 0;; ++i) { + mu_assert_int_eq(i, furi_stream_buffer_bytes_available(stream_buffer)); + mu_assert_int_eq( + STREAM_BUFFER_SIZE - i, furi_stream_buffer_spaces_available(stream_buffer)); + + if(furi_stream_buffer_send(stream_buffer, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { + break; + } + } + + mu_assert(!furi_stream_buffer_is_empty(stream_buffer), "Must be not empty"); + mu_assert(furi_stream_buffer_is_full(stream_buffer), "Must be full"); + mu_assert_int_eq(STREAM_BUFFER_SIZE, furi_stream_buffer_bytes_available(stream_buffer)); + mu_assert_int_eq(0, furi_stream_buffer_spaces_available(stream_buffer)); + + for(uint8_t i = 0;; ++i) { + mu_assert_int_eq( + STREAM_BUFFER_SIZE - i, furi_stream_buffer_bytes_available(stream_buffer)); + mu_assert_int_eq(i, furi_stream_buffer_spaces_available(stream_buffer)); + + uint8_t value; + if(furi_stream_buffer_receive(stream_buffer, &value, sizeof(uint8_t), 0) != + sizeof(uint8_t)) { + break; + } + + mu_assert_int_eq(i, value); + } +} + +// This is a stub that needs expanding +void test_furi_primitives(void) { + TestFuriPrimitivesData data = { + .message_queue = + furi_message_queue_alloc(MESSAGE_QUEUE_CAPACITY, MESSAGE_QUEUE_ELEMENT_SIZE), + .stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRG_LEVEL), + }; + + test_furi_message_queue(&data); + test_furi_stream_buffer(&data); + + furi_message_queue_free(data.message_queue); + furi_stream_buffer_free(data.stream_buffer); +} diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index be579d2b8c0..193a8124d98 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -8,6 +8,8 @@ void test_furi_concurrent_access(void); void test_furi_pubsub(void); void test_furi_memmgr(void); void test_furi_event_loop(void); +void test_errno_saving(void); +void test_furi_primitives(void); static int foo = 0; @@ -42,6 +44,14 @@ MU_TEST(mu_test_furi_event_loop) { test_furi_event_loop(); } +MU_TEST(mu_test_errno_saving) { + test_errno_saving(); +} + +MU_TEST(mu_test_furi_primitives) { + test_furi_primitives(); +} + MU_TEST_SUITE(test_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_check); @@ -51,6 +61,8 @@ 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_errno_saving); + MU_RUN_TEST(mu_test_furi_primitives); } int run_minunit_test_furi(void) { diff --git a/applications/debug/unit_tests/tests/js/js_test.c b/applications/debug/unit_tests/tests/js/js_test.c new file mode 100644 index 00000000000..af590e89956 --- /dev/null +++ b/applications/debug/unit_tests/tests/js/js_test.c @@ -0,0 +1,88 @@ +#include "../test.h" // IWYU pragma: keep + +#include +#include +#include + +#include +#include + +#include + +#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js") + +typedef enum { + JsTestsFinished = 1, + JsTestsError = 2, +} JsTestFlag; + +typedef struct { + FuriEventFlag* event_flags; + FuriString* error_string; +} JsTestCallbackContext; + +static void js_test_callback(JsThreadEvent event, const char* msg, void* param) { + JsTestCallbackContext* context = param; + if(event == JsThreadEventPrint) { + FURI_LOG_I("js_test", "%s", msg); + } else if(event == JsThreadEventError || event == JsThreadEventErrorTrace) { + context->error_string = furi_string_alloc_set_str(msg); + furi_event_flag_set(context->event_flags, JsTestsFinished | JsTestsError); + } else if(event == JsThreadEventDone) { + furi_event_flag_set(context->event_flags, JsTestsFinished); + } +} + +static void js_test_run(const char* script_path) { + JsTestCallbackContext* context = malloc(sizeof(JsTestCallbackContext)); + context->event_flags = furi_event_flag_alloc(); + + JsThread* thread = js_thread_run(script_path, js_test_callback, context); + uint32_t flags = furi_event_flag_wait( + context->event_flags, JsTestsFinished, FuriFlagWaitAny, FuriWaitForever); + if(flags & FuriFlagError) { + // getting the flags themselves should not fail + furi_crash(); + } + + FuriString* error_string = context->error_string; + + js_thread_stop(thread); + furi_event_flag_free(context->event_flags); + free(context); + + if(flags & JsTestsError) { + // memory leak: not freeing the FuriString if the tests fail, + // because mu_fail executes a return + // + // who cares tho? + mu_fail(furi_string_get_cstr(error_string)); + } +} + +MU_TEST(js_test_basic) { + js_test_run(JS_SCRIPT_PATH("basic")); +} +MU_TEST(js_test_math) { + js_test_run(JS_SCRIPT_PATH("math")); +} +MU_TEST(js_test_event_loop) { + js_test_run(JS_SCRIPT_PATH("event_loop")); +} +MU_TEST(js_test_storage) { + js_test_run(JS_SCRIPT_PATH("storage")); +} + +MU_TEST_SUITE(test_js) { + MU_RUN_TEST(js_test_basic); + MU_RUN_TEST(js_test_math); + MU_RUN_TEST(js_test_event_loop); + MU_RUN_TEST(js_test_storage); +} + +int run_minunit_test_js(void) { + MU_RUN_SUITE(test_js); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_js) diff --git a/applications/debug/unit_tests/tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h index 9310cfc9c9c..9ca3bb403db 100644 --- a/applications/debug/unit_tests/tests/minunit.h +++ b/applications/debug/unit_tests/tests/minunit.h @@ -31,7 +31,7 @@ extern "C" { #include #if defined(_MSC_VER) && _MSC_VER < 1900 #define snprintf _snprintf -#define __func__ __FUNCTION__ +#define __func__ __FUNCTION__ //-V1059 #endif #elif defined(__unix__) || defined(__unix) || defined(unix) || \ @@ -56,7 +56,7 @@ extern "C" { #endif #if __GNUC__ >= 5 && !defined(__STDC_VERSION__) -#define __func__ __extension__ __FUNCTION__ +#define __func__ __extension__ __FUNCTION__ //-V1059 #endif #else @@ -102,6 +102,7 @@ void minunit_printf_warning(const char* format, ...); MU__SAFE_BLOCK(minunit_setup = setup_fun; minunit_teardown = teardown_fun;) /* Test runner */ +//-V:MU_RUN_TEST:550 #define MU_RUN_TEST(test) \ MU__SAFE_BLOCK( \ if(minunit_real_timer == 0 && minunit_proc_timer == 0) { \ diff --git a/applications/debug/unit_tests/tests/nfc/nfc_test.c b/applications/debug/unit_tests/tests/nfc/nfc_test.c index 0898ac8edac..4ba934b6d55 100644 --- a/applications/debug/unit_tests/tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/tests/nfc/nfc_test.c @@ -496,7 +496,7 @@ NfcCommand mf_classic_poller_send_frame_callback(NfcGenericEventEx event, void* MfClassicKey key = { .data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, }; - error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL); + error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL, false); frame_test->state = (error == MfClassicErrorNone) ? NfcTestMfClassicSendFrameTestStateReadBlock : NfcTestMfClassicSendFrameTestStateFail; diff --git a/applications/debug/unit_tests/tests/storage/storage_test.c b/applications/debug/unit_tests/tests/storage/storage_test.c index f317fbf6803..75c52ef9a7a 100644 --- a/applications/debug/unit_tests/tests/storage/storage_test.c +++ b/applications/debug/unit_tests/tests/storage/storage_test.c @@ -6,9 +6,10 @@ // This is a hack to access internal storage functions and definitions #include -#define UNIT_TESTS_PATH(path) EXT_PATH("unit_tests/" path) +#define UNIT_TESTS_RESOURCES_PATH(path) EXT_PATH("unit_tests/" path) +#define UNIT_TESTS_PATH(path) EXT_PATH(".tmp/unit_tests/" path) -#define STORAGE_LOCKED_FILE EXT_PATH("locked_file.test") +#define STORAGE_LOCKED_FILE UNIT_TESTS_PATH("locked_file.test") #define STORAGE_LOCKED_DIR STORAGE_INT_PATH_PREFIX #define STORAGE_TEST_DIR UNIT_TESTS_PATH("test_dir") @@ -369,33 +370,78 @@ MU_TEST(storage_file_rename) { Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); - mu_check(write_file_13DA(storage, EXT_PATH("file.old"))); - mu_check(check_file_13DA(storage, EXT_PATH("file.old"))); + mu_check(write_file_13DA(storage, UNIT_TESTS_PATH("file.old"))); + mu_check(check_file_13DA(storage, UNIT_TESTS_PATH("file.old"))); mu_assert_int_eq( - FSE_OK, storage_common_rename(storage, EXT_PATH("file.old"), EXT_PATH("file.new"))); - mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, EXT_PATH("file.old"), NULL)); - mu_assert_int_eq(FSE_OK, storage_common_stat(storage, EXT_PATH("file.new"), NULL)); - mu_check(check_file_13DA(storage, EXT_PATH("file.new"))); - mu_assert_int_eq(FSE_OK, storage_common_remove(storage, EXT_PATH("file.new"))); + FSE_OK, + storage_common_rename(storage, UNIT_TESTS_PATH("file.old"), UNIT_TESTS_PATH("file.new"))); + mu_assert_int_eq( + FSE_NOT_EXIST, storage_common_stat(storage, UNIT_TESTS_PATH("file.old"), NULL)); + mu_assert_int_eq(FSE_OK, storage_common_stat(storage, UNIT_TESTS_PATH("file.new"), NULL)); + mu_check(check_file_13DA(storage, UNIT_TESTS_PATH("file.new"))); + mu_assert_int_eq(FSE_OK, storage_common_remove(storage, UNIT_TESTS_PATH("file.new"))); storage_file_free(file); furi_record_close(RECORD_STORAGE); } +static const char* dir_rename_tests[][2] = { + {UNIT_TESTS_PATH("dir.old"), UNIT_TESTS_PATH("dir.new")}, + {UNIT_TESTS_PATH("test_dir"), UNIT_TESTS_PATH("test_dir-new")}, + {UNIT_TESTS_PATH("test"), UNIT_TESTS_PATH("test-test")}, +}; + MU_TEST(storage_dir_rename) { Storage* storage = furi_record_open(RECORD_STORAGE); - storage_dir_create(storage, EXT_PATH("dir.old")); + for(size_t i = 0; i < COUNT_OF(dir_rename_tests); i++) { + const char* old_path = dir_rename_tests[i][0]; + const char* new_path = dir_rename_tests[i][1]; + + storage_dir_create(storage, old_path); + mu_check(storage_dir_rename_check(storage, old_path)); + + mu_assert_int_eq(FSE_OK, storage_common_rename(storage, old_path, new_path)); + mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, old_path, NULL)); + mu_check(storage_dir_rename_check(storage, new_path)); + + storage_dir_remove(storage, new_path); + mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, new_path, NULL)); + } + + furi_record_close(RECORD_STORAGE); +} - mu_check(storage_dir_rename_check(storage, EXT_PATH("dir.old"))); +MU_TEST(storage_equiv_and_subdir) { + Storage* storage = furi_record_open(RECORD_STORAGE); mu_assert_int_eq( - FSE_OK, storage_common_rename(storage, EXT_PATH("dir.old"), EXT_PATH("dir.new"))); - mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, EXT_PATH("dir.old"), NULL)); - mu_check(storage_dir_rename_check(storage, EXT_PATH("dir.new"))); + true, + storage_common_equivalent_path(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah"))); + mu_assert_int_eq( + true, + storage_common_equivalent_path( + storage, UNIT_TESTS_PATH("blah/"), UNIT_TESTS_PATH("blah/"))); + mu_assert_int_eq( + false, + storage_common_equivalent_path( + storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah-blah"))); + mu_assert_int_eq( + false, + storage_common_equivalent_path( + storage, UNIT_TESTS_PATH("blah/"), UNIT_TESTS_PATH("blah-blah/"))); - storage_dir_remove(storage, EXT_PATH("dir.new")); - mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, EXT_PATH("dir.new"), NULL)); + mu_assert_int_eq( + true, storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah"))); + mu_assert_int_eq( + true, + storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah/blah"))); + mu_assert_int_eq( + false, + storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah/blah"), UNIT_TESTS_PATH("blah"))); + mu_assert_int_eq( + false, + storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah-blah"))); furi_record_close(RECORD_STORAGE); } @@ -403,10 +449,13 @@ MU_TEST(storage_dir_rename) { MU_TEST_SUITE(storage_rename) { MU_RUN_TEST(storage_file_rename); MU_RUN_TEST(storage_dir_rename); + MU_RUN_TEST(storage_equiv_and_subdir); Storage* storage = furi_record_open(RECORD_STORAGE); - storage_dir_remove(storage, EXT_PATH("dir.old")); - storage_dir_remove(storage, EXT_PATH("dir.new")); + for(size_t i = 0; i < COUNT_OF(dir_rename_tests); i++) { + storage_dir_remove(storage, dir_rename_tests[i][0]); + storage_dir_remove(storage, dir_rename_tests[i][1]); + } furi_record_close(RECORD_STORAGE); } @@ -653,7 +702,7 @@ MU_TEST(test_md5_calc) { Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); - const char* path = UNIT_TESTS_PATH("storage/md5.txt"); + const char* path = UNIT_TESTS_RESOURCES_PATH("storage/md5.txt"); const char* md5_cstr = "2a456fa43e75088fdde41c93159d62a2"; const uint8_t md5[MD5_HASH_SIZE] = { 0x2a, diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 50524e5b7d0..10b08902256 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -7,7 +7,7 @@ #include #include -#include +#include static constexpr auto unit_tests_api_table = sort(create_array_t( API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)), @@ -33,13 +33,9 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( xQueueGenericSend, BaseType_t, (QueueHandle_t, const void* const, TickType_t, const BaseType_t)), - API_METHOD(furi_event_loop_alloc, FuriEventLoop*, (void)), - API_METHOD(furi_event_loop_free, void, (FuriEventLoop*)), API_METHOD( - furi_event_loop_subscribe_message_queue, - void, - (FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*)), - API_METHOD(furi_event_loop_unsubscribe, void, (FuriEventLoop*, FuriEventLoopObject*)), - API_METHOD(furi_event_loop_run, void, (FuriEventLoop*)), - API_METHOD(furi_event_loop_stop, void, (FuriEventLoop*)), + js_thread_run, + JsThread*, + (const char* script_path, JsThreadCallback callback, void* context)), + API_METHOD(js_thread_stop, void, (JsThread * worker)), API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/examples/example_event_loop/application.fam b/applications/examples/example_event_loop/application.fam index a37ffb1a041..15a7c8837f8 100644 --- a/applications/examples/example_event_loop/application.fam +++ b/applications/examples/example_event_loop/application.fam @@ -1,3 +1,12 @@ +App( + appid="example_event_loop_event_flags", + name="Example: Event Loop Event Flags", + apptype=FlipperAppType.EXTERNAL, + sources=["example_event_loop_event_flags.c"], + entry_point="example_event_loop_event_flags_app", + fap_category="Examples", +) + App( appid="example_event_loop_timer", name="Example: Event Loop Timer", diff --git a/applications/examples/example_event_loop/example_event_loop_event_flags.c b/applications/examples/example_event_loop/example_event_loop_event_flags.c new file mode 100644 index 00000000000..5d0acf7f10f --- /dev/null +++ b/applications/examples/example_event_loop/example_event_loop_event_flags.c @@ -0,0 +1,173 @@ +/** + * @file example_event_loop_event_flags.c + * @brief Example application demonstrating the use of the FuriEventFlag primitive in FuriEventLoop instances. + * + * This application receives keystrokes from the input service and sets the appropriate flags, + * which are subsequently processed in the event loop + */ + +#include +#include +#include + +#include + +#define TAG "ExampleEventLoopEventFlags" + +typedef struct { + Gui* gui; + ViewPort* view_port; + FuriEventLoop* event_loop; + FuriEventFlag* event_flag; +} EventLoopEventFlagsApp; + +typedef enum { + EventLoopEventFlagsOk = (1 << 0), + EventLoopEventFlagsUp = (1 << 1), + EventLoopEventFlagsDown = (1 << 2), + EventLoopEventFlagsLeft = (1 << 3), + EventLoopEventFlagsRight = (1 << 4), + EventLoopEventFlagsBack = (1 << 5), + EventLoopEventFlagsExit = (1 << 6), +} EventLoopEventFlags; + +#define EVENT_LOOP_EVENT_FLAGS_MASK \ + (EventLoopEventFlagsOk | EventLoopEventFlagsUp | EventLoopEventFlagsDown | \ + EventLoopEventFlagsLeft | EventLoopEventFlagsRight | EventLoopEventFlagsBack | \ + EventLoopEventFlagsExit) + +// This function is executed in the GUI context each time an input event occurs (e.g. the user pressed a key) +static void event_loop_event_flags_app_input_callback(InputEvent* event, void* context) { + furi_assert(context); + EventLoopEventFlagsApp* app = context; + UNUSED(app); + + if(event->type == InputTypePress) { + if(event->key == InputKeyOk) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsOk); + } else if(event->key == InputKeyUp) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsUp); + } else if(event->key == InputKeyDown) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsDown); + } else if(event->key == InputKeyLeft) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsLeft); + } else if(event->key == InputKeyRight) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsRight); + } else if(event->key == InputKeyBack) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsBack); + } + } else if(event->type == InputTypeLong) { + if(event->key == InputKeyBack) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsExit); + } + } +} + +// This function is executed each time a new event flag is inserted in the input event flag. +static void + event_loop_event_flags_app_event_flags_callback(FuriEventLoopObject* object, void* context) { + furi_assert(context); + EventLoopEventFlagsApp* app = context; + + furi_assert(object == app->event_flag); + + EventLoopEventFlags events = + furi_event_flag_wait(app->event_flag, EVENT_LOOP_EVENT_FLAGS_MASK, FuriFlagWaitAny, 0); + furi_check((events) != 0); + + if(events & EventLoopEventFlagsOk) { + FURI_LOG_I(TAG, "Press \"Ok\""); + } + if(events & EventLoopEventFlagsUp) { + FURI_LOG_I(TAG, "Press \"Up\""); + } + if(events & EventLoopEventFlagsDown) { + FURI_LOG_I(TAG, "Press \"Down\""); + } + if(events & EventLoopEventFlagsLeft) { + FURI_LOG_I(TAG, "Press \"Left\""); + } + if(events & EventLoopEventFlagsRight) { + FURI_LOG_I(TAG, "Press \"Right\""); + } + if(events & EventLoopEventFlagsBack) { + FURI_LOG_I(TAG, "Press \"Back\""); + } + if(events & EventLoopEventFlagsExit) { + FURI_LOG_I(TAG, "Exit App"); + furi_event_loop_stop(app->event_loop); + } +} + +static EventLoopEventFlagsApp* event_loop_event_flags_app_alloc(void) { + EventLoopEventFlagsApp* app = malloc(sizeof(EventLoopEventFlagsApp)); + + // Create event loop instances. + app->event_loop = furi_event_loop_alloc(); + // Create event flag instances. + app->event_flag = furi_event_flag_alloc(); + + // Create GUI instance. + app->gui = furi_record_open(RECORD_GUI); + app->view_port = view_port_alloc(); + // Gain exclusive access to the input events + view_port_input_callback_set(app->view_port, event_loop_event_flags_app_input_callback, app); + gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); + + // Notify the event loop about incoming messages in the event flag + furi_event_loop_subscribe_event_flag( + app->event_loop, + app->event_flag, + FuriEventLoopEventIn | FuriEventLoopEventFlagEdge, + event_loop_event_flags_app_event_flags_callback, + app); + + return app; +} + +static void event_loop_event_flags_app_free(EventLoopEventFlagsApp* app) { + gui_remove_view_port(app->gui, app->view_port); + + furi_record_close(RECORD_GUI); + app->gui = NULL; + + // Delete all instances + view_port_free(app->view_port); + app->view_port = NULL; + + // IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop. + // Failure to do so will result in a crash. + furi_event_loop_unsubscribe(app->event_loop, app->event_flag); + + furi_event_flag_free(app->event_flag); + app->event_flag = NULL; + + furi_event_loop_free(app->event_loop); + app->event_loop = NULL; + + free(app); +} + +static void event_loop_event_flags_app_run(EventLoopEventFlagsApp* app) { + FURI_LOG_I(TAG, "Press keys to see them printed here."); + FURI_LOG_I(TAG, "Quickly press different keys to generate events."); + FURI_LOG_I(TAG, "Long press \"Back\" to exit app."); + + // Run the application event loop. This call will block until the application is about to exit. + furi_event_loop_run(app->event_loop); +} + +/******************************************************************* + * vvv START HERE vvv + * + * The application's entry point - referenced in application.fam + *******************************************************************/ +int32_t example_event_loop_event_flags_app(void* arg) { + UNUSED(arg); + + EventLoopEventFlagsApp* app = event_loop_event_flags_app_alloc(); + event_loop_event_flags_app_run(app); + event_loop_event_flags_app_free(app); + + return 0; +} diff --git a/applications/examples/example_event_loop/example_event_loop_multi.c b/applications/examples/example_event_loop/example_event_loop_multi.c index ebfb0091183..ae748da55fb 100644 --- a/applications/examples/example_event_loop/example_event_loop_multi.c +++ b/applications/examples/example_event_loop/example_event_loop_multi.c @@ -52,7 +52,7 @@ typedef struct { */ // This function is executed each time the data is taken out of the stream buffer. It is used to restart the worker timer. -static bool +static void event_loop_multi_app_stream_buffer_worker_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); EventLoopMultiAppWorker* worker = context; @@ -62,8 +62,6 @@ static bool FURI_LOG_I(TAG, "Data was removed from buffer"); // Restart the timer to generate another block of random data. furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS); - - return true; } // This function is executed when the worker timer expires. The timer will NOT restart automatically @@ -152,7 +150,7 @@ static void event_loop_multi_app_input_callback(InputEvent* event, void* context } // This function is executed each time new data is available in the stream buffer. -static bool +static void event_loop_multi_app_stream_buffer_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); EventLoopMultiApp* app = context; @@ -172,12 +170,10 @@ static bool FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str)); furi_string_free(tmp_str); - - return true; } // This function is executed each time a new message is inserted in the input queue. -static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) { +static void event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); EventLoopMultiApp* app = context; @@ -222,8 +218,6 @@ static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* objec // Not a long press, just print the key's name. FURI_LOG_I(TAG, "Short press: %s", input_get_key_name(event.key)); } - - return true; } // This function is executed each time the countdown timer expires. diff --git a/applications/examples/example_event_loop/example_event_loop_mutex.c b/applications/examples/example_event_loop/example_event_loop_mutex.c index d043f3f8990..20bf7af4b09 100644 --- a/applications/examples/example_event_loop/example_event_loop_mutex.c +++ b/applications/examples/example_event_loop/example_event_loop_mutex.c @@ -59,7 +59,7 @@ static int32_t event_loop_mutex_app_worker_thread(void* context) { } // This function is being run each time when the mutex gets released -static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) { +static void event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); EventLoopMutexApp* app = context; @@ -82,8 +82,6 @@ static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, voi MUTEX_EVENT_AND_FLAGS, event_loop_mutex_app_event_callback, app); - - return true; } static EventLoopMutexApp* event_loop_mutex_app_alloc(void) { diff --git a/applications/examples/example_event_loop/example_event_loop_stream_buffer.c b/applications/examples/example_event_loop/example_event_loop_stream_buffer.c index 65dbd83cf55..6f72809739f 100644 --- a/applications/examples/example_event_loop/example_event_loop_stream_buffer.c +++ b/applications/examples/example_event_loop/example_event_loop_stream_buffer.c @@ -54,7 +54,7 @@ static int32_t event_loop_stream_buffer_app_worker_thread(void* context) { } // This function is being run each time when the number of bytes in the buffer is above its trigger level. -static bool +static void event_loop_stream_buffer_app_event_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); EventLoopStreamBufferApp* app = context; @@ -76,8 +76,6 @@ static bool FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str)); furi_string_free(tmp_str); - - return true; } static EventLoopStreamBufferApp* event_loop_stream_buffer_app_alloc(void) { diff --git a/applications/examples/example_number_input/ReadMe.md b/applications/examples/example_number_input/ReadMe.md index 9d5a0a9e5eb..8a221ba0870 100644 --- a/applications/examples/example_number_input/ReadMe.md +++ b/applications/examples/example_number_input/ReadMe.md @@ -1,7 +1,13 @@ -# Number Input +# Number Input {#example_number_input} -Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need of intense validations after a user input. +Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need for intense validations after a user input. -Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed withing min - max, an additional button is displayed to switch the sign between + and -. +## Source code -It is also possible to define a header text, shown in this example app with the 3 different input options. \ No newline at end of file +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_number_input). + +## General principle + +Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed within min - max, an additional button is displayed to switch the sign between + and -. + +It is also possible to define a header text, as shown in this example app with the 3 different input options. \ No newline at end of file diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index 2d2d4be86ce..eda702cf4d6 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -94,6 +94,11 @@ static void bad_usb_save_settings(BadUsbApp* app) { furi_record_close(RECORD_STORAGE); } +void bad_usb_set_interface(BadUsbApp* app, BadUsbHidInterface interface) { + app->interface = interface; + bad_usb_view_set_interface(app->bad_usb_view, interface); +} + BadUsbApp* bad_usb_app_alloc(char* arg) { BadUsbApp* app = malloc(sizeof(BadUsbApp)); @@ -125,7 +130,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { // Custom Widget app->widget = widget_alloc(); view_dispatcher_add_view( - app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget)); + app->view_dispatcher, BadUsbAppViewWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, BadUsbAppViewPopup, popup_get_view(app->popup)); app->var_item_list = variable_item_list_alloc(); view_dispatcher_add_view( @@ -171,9 +180,13 @@ void bad_usb_app_free(BadUsbApp* app) { bad_usb_view_free(app->bad_usb_view); // Custom Widget - view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError); + view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWidget); widget_free(app->widget); + // Popup + view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewPopup); + popup_free(app->popup); + // Config menu view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig); variable_item_list_free(app->var_item_list); diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index a4dd57d8b95..b34bd5de699 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "views/bad_usb_view.h" #include @@ -33,6 +34,7 @@ struct BadUsbApp { NotificationApp* notifications; DialogsApp* dialogs; Widget* widget; + Popup* popup; VariableItemList* var_item_list; BadUsbAppError error; @@ -46,7 +48,10 @@ struct BadUsbApp { }; typedef enum { - BadUsbAppViewError, + BadUsbAppViewWidget, + BadUsbAppViewPopup, BadUsbAppViewWork, BadUsbAppViewConfig, } BadUsbAppView; + +void bad_usb_set_interface(BadUsbApp* app, BadUsbHidInterface interface); diff --git a/applications/main/bad_usb/resources/badusb/assets/layouts/es-LA.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/es-LA.kl new file mode 100644 index 00000000000..2a6c78adcb1 Binary files /dev/null and b/applications/main/bad_usb/resources/badusb/assets/layouts/es-LA.kl differ diff --git a/applications/main/bad_usb/resources/badusb/demo_chromeos.txt b/applications/main/bad_usb/resources/badusb/demo_chromeos.txt index c5f675fb338..7f42574ce10 100644 --- a/applications/main/bad_usb/resources/badusb/demo_chromeos.txt +++ b/applications/main/bad_usb/resources/badusb/demo_chromeos.txt @@ -1,12 +1,17 @@ -REM This is BadUSB demo script for ChromeOS by kowalski7cc +REM This is BadUSB demo script for Chrome and ChromeOS by kowalski7cc +REM Exit from Overview +ESC REM Open a new tab CTRL t REM wait for some slower chromebooks DELAY 1000 +REM Make sure we have omnibox focus +CTRL l +DELAY 200 REM Open an empty editable page DEFAULT_DELAY 50 -STRING data:text/html,