diff --git a/.ci_files/imgs/fztools/Dockerfile b/.ci_files/imgs/fztools/Dockerfile
new file mode 100644
index 000000000..c14096416
--- /dev/null
+++ b/.ci_files/imgs/fztools/Dockerfile
@@ -0,0 +1,13 @@
+FROM ubuntu:hirsute
+
+RUN apt-get update \
+ && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
+ ca-certificates \
+ git \
+ wget \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+COPY entrypoint.sh /
+
+ENTRYPOINT ["/entrypoint.sh"]
diff --git a/.ci_files/imgs/fztools/entrypoint.sh b/.ci_files/imgs/fztools/entrypoint.sh
new file mode 100644
index 000000000..13dec493b
--- /dev/null
+++ b/.ci_files/imgs/fztools/entrypoint.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+if [ -z "$1" ]; then
+ bash
+else
+ echo "Running $1"
+ set -ex
+ bash -c "$1"
+fi
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d263bb8ba..30c0c1f7d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,119 +1,34 @@
## Main changes
-- Current API: 78.1
-- SubGHz:
- - Frequency analyzer fixes and improvements:
- - **Enforce int module** (like in OFW) usage due to lack of required hardware on external boards (PathIsolate (+rf switch for multiple paths)) and incorrect usage and/or understanding the purpose of frequency analyzer app by users, it should be used only to get frequency of the remote placed around 1-10cm around flipper's left corner
- - **Fix possible GSM mobile towers signal interference** by limiting upper frequency to 920mhz max
- - Fix buttons logic, **fix crash**
- - Protocol improvements:
- - **Keeloq: Monarch full support, with add manually option** (thanks @ashphx !)
- - **Princeton support for second button encoding type** (8bit)
- - GangQi fix serial check and remove broken check from UI
- - Hollarm add more button codes (thanks to @mishamyte for captures)
- - Misc:
- - Add extra settings to disable GPIO pins control used for external modules amplifiers and/or LEDs (in radio settings menu with debug ON)
-- NFC:
- - Read Ultralight block by block (**fix password protected MFUL reading issue**) (by @mishamyte | PR #825 #826)
- - **Update NDEF parser** (SLIX and MFC support) (by @luu176 and @jaylikesbunda and @Willy-JL)
- - OFW PR 3822: **MIFARE Classic Key Recovery Improvements** (by @noproto)
- - OFW PR 3930: NFC Emulation freeze fix (by @RebornedBrain)
- - OFW: H World Hotel Chain Room Key Parser
- - OFW: Parser for Tianjin Railway Transit
- - New keys in system dict
-- Infrared:
- - **Add LEDs universal remote** (DB by @amec0e)
- - Update universal remote assets (by @amec0e | PR #813 #816)
-- JS:
- - OFW: JS modules & SDK -> **Breaking API change**
- - **Backporting custom features** (read about most of the changes after other changes section) (by @xMasterX and @Willy-JL)
- - Add i2c & SPI module (by @jamisonderek)
-* OFW: FuriHal, drivers: rework gauge initialization routine -> **Downgrade to older releases may break battery UI percent indicator, upgrade to this or newer version to restore**
-* OFW: heap: increased size -> **More free RAM!!**
-* OFW: New layout for BadUSB (es-LA)
-* OFW: Require PIN on boot
+- Current API: 79.3
+* SubGHz: Jolly Motors support (with add manually) (Thanks @pkooiman !)
+* Power: Auto Power Off Timer (by @Dmitry422 with some fixes by @xMasterX)
+* OFW: **Fix lost BadBLE keystrokes**
+* OFW: **Add the ability to send a signal once via RPC**
+* OFW PR 4070: Infrared: increase max carrier limit (by @skotopes)
+* OFW PR 4025: Increase system stack's reserved memory size (Fix USB UART Bridge Crash) (by @Astrrra)
+* OFW: merged gsurkov/vcp_break_support branch for usb uart bridge (WIP!!!)
* Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
## Other changes
-* SubGHz: Freq analyzer - Fix duplicated frequency lists and use user config for nearest frequency selector too
-* SubGHz: Code cleanup and fix for rare dupicated (Data) field cases
-* OFW: NFC TRT Parser: Additional checks to prevent false positives
-* OFW PR 3992: Loader: Fix BusFault in handling of OOM (by @Willy-JL)
-* OFW PR 3885: NFC: Add API to enforce ISO15693 mode (by @aaronjamt)
-* OFW: NFC: iso14443_4a improvements (by @RebornedBrain)
-* OFW: NFC: Plantain parser improvements (by @assasinfil) & fixes (by @mxcdoam)
-* OFW: NFC: Moscow social card parser (by @assasinfil)
-* OFW: fix: npm deps
-* OFW: 目覚め時計 (Added alarm option and clock settings)
-* OFW: JS: Backport and more additions & fixes
-* OFW: nfc: add Caltrain zones for Clipper
-* OFW: Update unit tests docs
-* OFW: Fix JS memory corruption (in gpio module)
-* OFW: Full-fledged JS SDK + npm packages
-* OFW: FurEventLoop: add support for FuriEventFlag, simplify API
-* OFW: lib: digital_signal: digital_sequence: add furi_hal.h wrapped in ifdefs
-* OFW: Add warning about stealth mode in vibro CLI
-* OFW: Small fixes in the wifi devboard docs
-* OFW: BadUSB - Improve ChromeOS and GNOME demo scripts
-* OFW: Small JS fixes
-* OFW: Canvas: extended icon draw.
-* OFW: Fixes Mouse Clicker Should have a "0" value setting for "as fast as possible"
-* OFW: Wi-Fi Devboard documentation rework
-* OFW: Furi: A Lot of Fixes
-* OFW PR 3933: furi_hal_random: Wait for ready state and no errors before sampling (by @n1kolasM)
-* OFW: nfc/clipper: Update BART station codes
-* OFW: FuriThread: Improve state callbacks
-* OFW: Documentation: update and cleanup
-* OFW: Improve bit_buffer.h docs
-* OFW: Prevent idle priority threads from potentially starving the FreeRTOS idle task
-* OFW: IR universal remote additions
-* OFW: Fix EM4100 T5577 writing block order (was already done in UL)
-* OFW: kerel typo
-* OFW: Folder rename fails
-* OFW: Put errno into TCB
-* OFW: Fix USB-UART bridge exit screen stopping the bridge prematurely
-**More details on JS changes** (js changelog written by @Willy-JL , thanks!):
-- Our custom JS SDK can be found on npm now: https://www.npmjs.com/org/darkflippers
-- Non-exhaustive list of changes to help you fix your scripts:
- - `badusb`:
- - `setup()`: `mfr_name`, `prod_name`, `layout_path` parameters renamed to `mfrName`, `prodName`, `layoutPath`
- - effort required to update old scripts using badusb: very minimal
- - `dialog`:
- - removed, now replaced by `gui/dialog` and `gui/file_picker` (see below)
- - `event_loop`:
- - new module, allows timer functionality, callbacks and event-driven programming, used heavily alongside gpio and gui modules
- - `gpio`:
- - fully overhauled, now you `get()` pin instances and perform actions on them like `.init()`
- - now supports interrupts, callbacks and more cool things
- - effort required to update old scripts using gpio: moderate
- - `gui`:
- - new module, fully overhauled, replaces dialog, keyboard, submenu, textbox modules
- - higher barrier to entry than older modules (requires usage of `event_loop` and `gui.viewDispatcher`), but much more flexible, powerful and easier to extend
- - includes all previously available js gui functionality (except `widget`), and also adds `gui/loading` and `gui/empty_screen` views
- - currently `gui/file_picker` works different than other new view objects, it is a simple `.pickFile()` synchronous function, but this [may change later](https://github.com/flipperdevices/flipperzero-firmware/pull/3961#discussion_r1805579153)
- - effort required to update old scripts using gui: extensive
- - `keyboard`:
- - removed, now replaced by `gui/text_input` and `gui/byte_input` (see above)
- - `math`:
- - `is_equal()` renamed to `isEqual()`
- - `storage`:
- - fully overhauled, now you `openFile()`s and perform actions on them like `.read()`
- - now supports many more operations including different open modes, directories and much more
- - effort required to update old scripts using storage: moderate
- - `submenu`:
- - removed, now replaced by `gui/submenu` (see above)
- - `textbox`:
- - removed, now replace by `gui/text_box` (see above)
- - `widget`:
- - only gui functionality not ported to new gui module, remains unchanged for now but likely to be ported later on
- - globals:
- - `__filepath` and `__dirpath` renamed to `__filename` and `__dirname` like in nodejs
- - `to_string()` renamed and moved to number class as `n.toString()`, now supports optional base parameter
- - `to_hex_string()` removed, now use `n.toString(16)`
- - `parse_int()` renamed to `parseInt()`, now supports optional base parameter
- - `to_upper_case()` and `to_lower_case()` renamed and moved to string class as `s.toUpperCase()` and `s.toLowerCase()`
- - effort required to update old scripts using these: minimal
- - Added type definitions (typescript files for type checking in IDE, Flipper does not run typescript)
- - Documentation is incomplete and deprecated, from now on you should refer to type definitions (`applications/system/js_app/types`), those will always be correct
- - Type definitions for extra modules we have that OFW doesn't will come later
+* Power & Desktop: Add input events sub check & use event system for auto power off
+* OFW: Rename FuriHalDebuging.md to FuriHalDebugging.md
+* OFW: nfc: Fix MIFARE Plus detection
+* OFW: u2f: Fix leaking message digest contexts
+* OFW: nfc: Fix MFUL PWD_AUTH command creation
+* OFW: Bump cross-spawn in /applications/system/js_app/packages/create-fz-app
+* OFW: **Pipe** (new api funcs)
+* OFW: Fix invalid path errors while deploying SDK by enforcing toolchain to use UTF-8 on initial SDK Extraction
+* OFW: **Added flipper_format_write_empty_line(...)**
+* OFW: Fix skylander ID reading
+* OFW: Work around incorrect serial port handling by the OS
+* OFW: Add winter animations
+* OFW: FBT: Don't lint JS packages
+* OFW: **Loader: Fix BusFault in handling of OOM** (was already included in previous UL release)
+* OFW: **NFC Fix ISO15693 stucking in wrong mode.**
+* OFW: Update `infrared_test.c` reference
+* OFW: **FuriThread stdin**
+* OFW: NFC: Plantain parser Last payment amount fix
+* OFW: NFC clipper: BART station ids for San Lorenzo, Bay Fair
+* OFW: Fix typo for mf_classic_key_cahce_get_next_key() function
#### Known NFC post-refactor regressions list:
- Mifare Mini clones reading is broken (original mini working fine) (OFW)
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/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c
index f6e68b109..0d2c63b3c 100644
--- a/applications/main/gpio/usb_uart_bridge.c
+++ b/applications/main/gpio/usb_uart_bridge.c
@@ -35,12 +35,12 @@ typedef enum {
WorkerEvtLineCfgSet = (1 << 6),
WorkerEvtCtrlLineSet = (1 << 7),
-
+ WorkerEvtSendBreak = (1 << 8),
} WorkerEvtFlags;
#define WORKER_ALL_RX_EVENTS \
(WorkerEvtStop | WorkerEvtRxDone | WorkerEvtCfgChange | WorkerEvtLineCfgSet | \
- WorkerEvtCtrlLineSet | WorkerEvtCdcTxComplete)
+ WorkerEvtCtrlLineSet | WorkerEvtCdcTxComplete | WorkerEvtSendBreak)
#define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtCdcRx)
struct UsbUartBridge {
@@ -69,6 +69,7 @@ static void vcp_on_cdc_rx(void* context);
static void vcp_state_callback(void* context, uint8_t state);
static void vcp_on_cdc_control_line(void* context, uint8_t state);
static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config);
+static void vcp_on_cdc_break(void* context, uint16_t duration);
static const CdcCallbacks cdc_cb = {
vcp_on_cdc_tx_complete,
@@ -76,6 +77,7 @@ static const CdcCallbacks cdc_cb = {
vcp_state_callback,
vcp_on_cdc_control_line,
vcp_on_line_config,
+ vcp_on_cdc_break,
};
/* USB UART worker */
@@ -287,6 +289,9 @@ static int32_t usb_uart_worker(void* context) {
if(events & WorkerEvtCtrlLineSet) {
usb_uart_update_ctrl_lines(usb_uart);
}
+ if(events & WorkerEvtSendBreak) {
+ furi_hal_serial_send_break(usb_uart->serial_handle);
+ }
}
usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch);
usb_uart_serial_deinit(usb_uart);
@@ -377,6 +382,12 @@ static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config
furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtLineCfgSet);
}
+static void vcp_on_cdc_break(void* context, uint16_t duration) {
+ UNUSED(duration);
+ UsbUartBridge* usb_uart = (UsbUartBridge*)context;
+ furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtSendBreak);
+}
+
UsbUartBridge* usb_uart_enable(UsbUartConfig* cfg) {
UsbUartBridge* usb_uart = malloc(sizeof(UsbUartBridge));
diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c
index 3a31b6469..446a4800d 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/helpers/mf_classic_key_cache.c b/applications/main/nfc/helpers/mf_classic_key_cache.c
index 1b945660c..763c4dea7 100644
--- a/applications/main/nfc/helpers/mf_classic_key_cache.c
+++ b/applications/main/nfc/helpers/mf_classic_key_cache.c
@@ -166,7 +166,7 @@ void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfCl
}
}
-bool mf_classic_key_cahce_get_next_key(
+bool mf_classic_key_cache_get_next_key(
MfClassicKeyCache* instance,
uint8_t* sector_num,
MfClassicKey* key,
diff --git a/applications/main/nfc/helpers/mf_classic_key_cache.h b/applications/main/nfc/helpers/mf_classic_key_cache.h
index b09f4526b..50a1f5c30 100644
--- a/applications/main/nfc/helpers/mf_classic_key_cache.h
+++ b/applications/main/nfc/helpers/mf_classic_key_cache.h
@@ -16,7 +16,7 @@ bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid,
void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data);
-bool mf_classic_key_cahce_get_next_key(
+bool mf_classic_key_cache_get_next_key(
MfClassicKeyCache* instance,
uint8_t* sector_num,
MfClassicKey* key,
diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c
index 5f3592c4b..27e9a60f5 100644
--- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c
+++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c
@@ -72,7 +72,7 @@ static NfcCommand nfc_scene_read_poller_callback_mf_classic(NfcGenericEvent even
uint8_t sector_num = 0;
MfClassicKey key = {};
MfClassicKeyType key_type = MfClassicKeyTypeA;
- if(mf_classic_key_cahce_get_next_key(
+ if(mf_classic_key_cache_get_next_key(
instance->mfc_key_cache, §or_num, &key, &key_type)) {
mfc_event->data->read_sector_request_data.sector_num = sector_num;
mfc_event->data->read_sector_request_data.key = key;
diff --git a/applications/main/nfc/plugins/supported_cards/clipper.c b/applications/main/nfc/plugins/supported_cards/clipper.c
index 3c306c9bc..7dc164a7a 100644
--- a/applications/main/nfc/plugins/supported_cards/clipper.c
+++ b/applications/main/nfc/plugins/supported_cards/clipper.c
@@ -102,7 +102,8 @@ static const IdMapping bart_zones[] = {
{.id = 0x001d, .name = "Lake Merrit"},
{.id = 0x001e, .name = "Fruitvale"},
{.id = 0x001f, .name = "Coliseum"},
- {.id = 0x0021, .name = "San Leandro"},
+ {.id = 0x0020, .name = "San Leandro"},
+ {.id = 0x0021, .name = "Bay Fair"},
{.id = 0x0022, .name = "Hayward"},
{.id = 0x0023, .name = "South Hayward"},
{.id = 0x0024, .name = "Union City"},
@@ -132,6 +133,9 @@ static const IdMapping muni_zones[] = {
{.id = 0x000b, .name = "Castro"},
{.id = 0x000c, .name = "Forest Hill"}, // Guessed
{.id = 0x000d, .name = "West Portal"},
+ {.id = 0x0019, .name = "Union Square/Market Street"},
+ {.id = 0x001a, .name = "Chinatown - Rose Pak"},
+ {.id = 0x001b, .name = "Yerba Buena/Moscone"},
};
static const size_t kNumMUNIZones = COUNT(muni_zones);
diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c
index 9f2491691..49bbaebe8 100644
--- a/applications/main/nfc/plugins/supported_cards/plantain.c
+++ b/applications/main/nfc/plugins/supported_cards/plantain.c
@@ -310,9 +310,11 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
last_payment_date.year,
last_payment_date.hour,
last_payment_date.minute);
- //payment amount. This needs to be investigated more, currently it shows incorrect amount on some cards.
- uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8];
- furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100);
+ //Last payment amount.
+ uint16_t last_payment = ((data->block[18].data[10] << 16) |
+ (data->block[18].data[9] << 8) | (data->block[18].data[8])) /
+ 100;
+ furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment);
furi_string_free(card_number_s);
furi_string_free(tmp_s);
//This is for 4K Plantains.
@@ -369,9 +371,11 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
last_payment_date.year,
last_payment_date.hour,
last_payment_date.minute);
- //payment amount
- uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8];
- furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100);
+ //Last payment amount
+ uint16_t last_payment = ((data->block[18].data[10] << 16) |
+ (data->block[18].data[9] << 8) | (data->block[18].data[8])) /
+ 100;
+ furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment);
furi_string_free(card_number_s);
furi_string_free(tmp_s);
}
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/nfc/scenes/nfc_scene_mf_classic_update_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c
index 7c76260b4..a477a08b9 100644
--- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c
+++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c
@@ -34,7 +34,7 @@ NfcCommand nfc_mf_classic_update_initial_worker_callback(NfcGenericEvent event,
uint8_t sector_num = 0;
MfClassicKey key = {};
MfClassicKeyType key_type = MfClassicKeyTypeA;
- if(mf_classic_key_cahce_get_next_key(
+ if(mf_classic_key_cache_get_next_key(
instance->mfc_key_cache, §or_num, &key, &key_type)) {
mfc_event->data->read_sector_request_data.sector_num = sector_num;
mfc_event->data->read_sector_request_data.key = key;
diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h
index e28c018d8..e84c4788d 100644
--- a/applications/main/subghz/helpers/subghz_custom_event.h
+++ b/applications/main/subghz/helpers/subghz_custom_event.h
@@ -32,6 +32,7 @@ typedef enum {
SubGhzCustomEventSceneRpcLoad,
SubGhzCustomEventSceneRpcButtonPress,
SubGhzCustomEventSceneRpcButtonRelease,
+ SubGhzCustomEventSceneRpcButtonPressRelease,
SubGhzCustomEventSceneRpcSessionClose,
SubGhzCustomEventViewReceiverOK,
@@ -82,6 +83,7 @@ typedef enum {
SetTypeAllmatic868,
SetTypeCenturion433,
SetTypeMonarch433,
+ SetTypeJollyMotors433,
SetTypeSommer_FM_434,
SetTypeSommer_FM_868,
SetTypeSommer_FM238_434,
diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes
index 23b7a031a..97e1aab47 100644
--- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes
+++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes
@@ -1,61 +1,62 @@
Filetype: Flipper SubGhz Keystore File
Version: 0
Encryption: 1
-IV: 46 75 72 72 79 20 63 68 65 63 6B 20 3F 3F 21 21
-3FF98B52C38558ECDB26C3E86D118FAC9AF22EDDDE7649CB53726855CEBCCF9E
-06B901C6E36E381B4A83554ED972288977F999C232106D337C3BE4A9A44608F8
-8153E6CAF4B272BA0EA168B9F0F29CF02CEB33E4ABBD4D5C858D1ADBFF474A25
-F61216CE3A7E500E0C9B2173F91C2E7B1BB7D7AA65D4150EAC28169116647DDD
-A3ABC262415035A190447EC9C15CFD1AB1720560AB7C82D7447215342305299732776A9A6DEA4D359C52A23BFCE6B015
-5784D0E77A55E1361F47A1F6CFFBFAF715CFCAD2BD502ED266AA86DC47F98541
-E082BF64C75023F23FB333C53F2590F408FD932EB71DF4ED4D0E0EAF2AEBD488
-EFF328889D57D9F4B9111918F9C9BC641645104223009842FFF7B6F73E24E5B7
-BDCD1DBCBCA789C5A5C3623C0A287D791F1CACCBBD7D144E2EF1F92DA5513D90
-6310362BE6ED7CB5E3F4CA84D3093DF620BABD2C9D419A2BA5AA05F241EE7592
-4C6F97C553276D5403103A6BA6C4DBBE017A9E4159E6E5AC4C28ACD645DB1E8D4551CDC228A0457BEEF49179A081E1FD
-861ECA071AEEF3854005FEC9BF22C7DB76D07B7930968314FFAC70995E28680E473F8A1A7E56E8776EDD1C6E2DD6DD26
-55ADDFBE97BCB0FB43EDEB9CAFF9F32DD788A6306D3702BC2923DA5C69F5F0A5
-AB838DA2B25547AA43DEB7FB40B15E289B9209057BE564E7CB78F0D5DCFC65FD
-21BACE924E522AD0E97F0AAF64A9DB6A5F7BA09B3A1759AAFD4016F3ADD4353D
-4B37D449E44BAE7F377EE5CE52E94882E42617417F77ECE9803D9435892167F2
-E63BA93D98BB3EEEAD34B38C5F271FB777AEABD9E6A05597AE09815E578AA811900AE9F144FBEC7DBFDDEC87D1ED368B
-7DF24DCB7D73F1638FFD22325463B23C101653EE770F086D323BE90868A7E267
-18E533990C408061309323675116429652B3F4D228F00D704310E7AB26F6BAF4
-26042EC21D73490D9E7F968EDCC4CECF3989E6E982427DEB7012478E214CED8797A9BEBC481C81E9646214809A409B4A
-AC4169C26E402720F1E2A0AD690AE2CF708CE203898BC7102178A738C70F361718E794F0D0CB1A2F1938EF35CCA11887
-6962CADB9BD1B86B779DF7F4D7E06E92EB42474C7FD9EA23879F9982D7127357
-23AEC0F6F9FF529DDDCD1CDAA77F7B136CE2CCD3AC8B949954D5D3B2ECFA8CBC
-C6A3A849BC4A8DB438065255BD387DCD77AD7E7FEB3B0E11B6D3A43279AB9087
-CCB71F8BDC8B31F36D6141B3B57BF31B7EDF72B87593B7497871F5738A0B7E00
-345FE0FAD3F60C017D9793406981878EA2226072D624208AC33682793B415C41
-78AF1FCFDB780744708DBB547F7C3F095BA5FAFB34FA83BF4323B32829836ED1
-C2F19F077E71E710D39F5F11C1FE55C2A3CD6F33384CD8603288765F266F1BDF
-084B1A9565AFC88900D0FF25413E659E17AAB649DB2B7A9F0381CB0DF6D2B8E5
-568CECD994676CF3D6B225DEF21D40787DCB41C101F52B0C5AA43ED66709D158
-0742E0AAB6504AC63A9C59FBDD980BD4C4760EFF3E556C8A6241442CDDD23A74
-37BB60EE11BEC7D25A607DE9B0B6D3EAD23321AFEB94995FAB42184A95D1CADA
-42FD71B98A3BEE9CF372B03E158D51181BFAA9CF54F7300A93FE7665402B3D1F
-9346B7D12346E264E84F91145EBF86C53DD0061E0FA6556FAE5F6FBB8CEB799E
-430B0D7FC09C09051AEB667A370E6D9D9BDF0C0C553AD2791682D43CE5DC48B7
-5BBB5129797BC07CDC1D25A8A919A637B9FFF2F920BD42D1721018ABF8D34959AF877AFF450AD91548184E1A0D991CC8
-0BC8D3E1C2D9A8FB445FC55564471007A28C09C9CE602203176F1BFF02AC6328
-1C4CA912791BCD9CB231C64AB479AF240D02EC9F431D7C479B9A172E5B97F5CD
-15A9CDF17E72255DE0942C09E67CD251C3D465246845F9C0B97A7EFEE4AF9110
-F95543395BFF39B0A093AC80D0EF1BEFA218A3769D074250414D1357105A4D34
-138D433824E691C6B67A08CF1DDE55FBCA2D65AC0B7D9320EA1FDCB1742B11BF
-3BE3A385F1C0B8922C4E23EFF6912748DF715A4546CECB5E8972C1D1E47D0D3A
-9ECA2554ED36700326F2E5140C434ADA8DFF55A53382F19541E9AAE45DA5CBF2
-5C75D528678AF199E191C49F310913F401014F97EEA5FA507C7310B48A98FDE3297EA398B08959951FF99EDDB64C5E0D
-22A9C66645B3944925A496D9F2312429CC787B6314948B482EAD9360124F59DA
-DA3A8571664DBBFC1DE97B53E7C141554A2FBCEBA980696D32409CC5ADB7FC41
-20A52AABC518FCC2FC75AE3F5CC7C4838AA4973111DA696B890D884A18098D91
-EBB7163F580A1A5D26F12FBE650A227791193BA9AFCE277584B171F2FE1C77CF
-86369EE5277CB81B9417B6232F8D994FAEAB34D0D5363B143257C62B10CACAFD
-2E2EAB32891E172A3C31D434703480E69793435BB198E6AA06AA066EE8234D85
-745FC576D77C41BAEFF15A822E6B4058A485A2CAA0A3B283928D17AC02299AD9
-1FF8D49F2F7D785D64B6FE365CD9C2BD958E9527F66BB8A85C9AEBC73ECFE064
-3CFB77F3E274C1EB2772CDFA7B5B17255C2554198BE60C058A3405AEC644FABC5AAECC8F9C7F4A4E5B2D5252E8C62628
-AE514C44B55A1A4744E1106FD226C587D1B71CA7B5DCF010265D769E22012866
-2B2D787A4B0F30CAB9CD3DBC7686165637F091B31745CAB53B369A804F76F9EE
-EA4279C80F0B4AC0B32AB9E8B8CFB9C25FF81840BEE65B2160F85E56FA576C48
-E41D853750D68643E929F94F46BCDC050040935883E9A0C45BB238CDE06340DD745BDA7D6C16AE2B028E073EDDC0FC49
+IV: 46 75 72 69 20 63 68 65 63 6B 20 4F 77 4F 21 3F
+796353C129CC2B688FE158D36E82001F7450D58DD763BCF4D6FA1CE6C3D598EA
+1E7DDEB3B54A42B8993C32AF209CBCC9A1137CE334449F016B993D673EB15C69
+F9CB2DDC4E3D45694292C7DE45F1DC0BD74235B36F624AF39E8C3D211A713408
+538F46EF4250801434FEA469EE07E8C8BF71C6179442718A05455BB501D797C4
+F2BF384DBF7F828E025F020E47D1D637B497FB470444F0C6F9DC67C6830EDDE8A26A6EE89A321D3924D9099895AB2EA1
+2CA8A8A4866D5C2B715B520F641B41355A81BA73170842233806D8AA3E3F3D62
+80CF5DEF931CF902EE602319F7CD506D42E5FBC06ABBF9F05D474C8E8A2AAC4E
+15CB465F7A646226C987BB4928E92A61F350ADCDDE355B717730ADDD1B738950
+86B7597ED3EDF6826124ABD7AE419197DC4A93FA064179ACAD853EC670F93995
+28263A286F1ADA0E851E8A27AFD7706CF3513D8A24D41CF7E24A925FE3D86305
+AF29A08AB877F12681706D71B82B8E2447DA18EBF9731EF3CE91FA5ECAC4E98F8FDC817E0F67C3D7348DD2AF128F1E62
+A675C5BB6AC41336B5F5A27FC062FBF30DC39B1E5C498F0FD823261C3177FA58416C5402554742370CB0DAFD2895F6F8
+0073790C26BD0224F5560161A4D4C4F8E05498E995FD2BF12EA9926A5127E9A5
+235ADFE253B51260DCF5C66F9CB9602B91A0C57BE14D1BE1E11EB309643B29DB
+F9C2E3A926AAA108F474075F0B4EDFF3135492B1529EAB150CF78E748BF36E17
+50F442CC9D90B9FF8C786245EC442B911A28A04D829D0E45D7D2D4C5A3F5B864
+9EA00CD21C0BE1BB15A0CAD31097A44CAF42868F259E8ED25F361986C26161514FA85D78F3889B4185D2A1484C1DA88C
+52DFE7D9052F29071922876723C30CF6FE6247D6439590C5C7C0493120ACA096
+D5AF176D59779A92F6EFB34F271D76D60780C3EA83306326E0119CD3B0417687
+C4CC4E589CCCEC609B4DE39F926F83CCFFCC966871B9A998514910A3F59E86FB56C476DFFB4181E3D22E86A760C4C137
+50A64FEDA64B61A5C123906426EF726D98AC70C15F38245A931B5649F0944930679513D9862091275F10A804EBB610DE
+E7938C4F32E571582D73F855ACC40FF0153EFBAB6D184F2DBE8EAC63C4276D92
+7667993F35F1363C0A3AEB5222F07F91904B3F6FA375BF062B269B09706764CB
+9DF764EE4A70D331BA8870F4D27C3E90E811A5E306BC72701A99A0377AC7B189
+324D0FBC096A489983C45E82B2745A83A3725A87D2D2CA676A521075065D5047
+8EA1A30AE08CEBD03A52EB2512C7C69CB824E0B9BE900E25F15FAB17ADBB1188
+CE379182103FC4E0442745F6F202AB6ED8EBC051B5F5537916938D9DB8FCD6BE
+752BD22AA37D030C60E48CB57309AF631682AC4A0D67C3A7D1130EB056717FB6
+9ECFE7D24BF25BD543E1EAE8116D95C110BD4514EE279BCA71234865EB9166BD
+14B3FA8704BA3B284C65F1A6FD114E53B883E12EAFB24B574F84585BD20157DA
+D026E1E2CA70E33291482EEFCE11123540BBF591D400C92CEBB8CAF99A9DE882
+88E618EF5E76D1F5A60926D48D3D58FF67B4A92DE6EECA271EDAFC2419AE787B
+74E14AD8ADE2BC575048325D1B3990669CDB35D840169B913043DAC938862AE4
+BEB5388B6A7D5C9EF65BDD7577D1DD654B7FAECE5A4CF0937BB7D0C0C5494CD0
+761356D494E3C947CBDCA887A30071A675FE1BE6E77FC9016DB9B5659B7FF9E7
+7503725DFC7212F5F719AFC9DF29F07321511BE4896E12D1D10C68BB07C6483A2CE5FFDFD074CFC279E42E6DD860C496
+BD03D78399DE44449AEE9F00EBCABBF419EDEA1701B9979A97D57AECB5139D1E
+E80EB84DF9DA2E34B78F50D6488A30F8EFD11E7C6DBEB7CFEDE83BE5CA86B6DF
+9332A130138F2AC11A12030CF43EFF77E7CABE761EDE14748112E13267496CE9
+E657F3C95EEF0AF92A5C49F66BD9C053A82493C7D6267F1E7C038473AE488116
+6F37491FFE130F90B77D7E5EA4AA75A1DB0CA3644F68B6502DFC302DDB80367B
+3B37C5CCDD510873628D92B352907FEFF0AC2B38C751C2E46C3B97C3E365972B
+A4C845187BEC75669234EE07B839DCFF618678D2EAA5596350F0936A400099A5
+2C961EF4F2454771B2646ECBAA1D0B7DAA2FD8ECC7228037A36E24FE30C43ABAA446C1B5968C0E2DD141C55557A4CCB8
+C4E0D43E9670C2B91F6AF03D60F216625CC19C697331BC443194D2BB88E042DF
+3DC5584F43061AF79907D6342DF3344435B5AA6277B33D7DC56C429D1EB81BC4
+D9186791E907CBB9EF26EDDAEA9F0DBC8D24E213E55942ED5E1790B5A55F8758
+2B54CB727BBFD8567543448D2D24B3865329F89936D3B035ED2185BA88F1DF2E
+A08F0D881AF001E52FF2CF9D232A9A566EB1900B351AFEEFEE666BEC40588F64
+46A11CA89BA1998A247275EEDF504DAFC5F97B41DFF4943F531A9F8F43DF19C5
+EF70A3858E84B13DF606317381B2E9DFFC346E96AF7C1DF0001586B8BF35808F
+38EAF18DB8096C7427EBD36CBC5B0E945A3286278CC0227EB056F7ACB9E450D4
+28278D1DAA263E9A45ED17F67F6B6B0CF00F8C0F58F86C8161F8D4266FE556CB
+0B0C79FDD7C9EA31FAA4AA829EDDD2A3453C05A74F5B53BBEAE83E1F4913FD1C18BD235D14D06D9E567DDB273E4C1F2A
+7D663A93AE1B9A2E00E944B92838DED3376E09C5179C8F3037B2EAE9E7326C2A
+D64EC2C7BD8CFA152368DF6BB75D66EF24EAA9C864A1386184B793C0585D82BC
+51D8EB188E833891CCD15919FAC8FE56ACAB1007699F4FDCB53A6DDAB02E5CAA
+650866D34DECD1D1F3559EFD8D2A4C1DB51C005F5932608CB6062B384D7A1F59C8E3FBF2C0A5AEFFB631D7B88A630AAB
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/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c
index a03c194f8..6c3e44894 100644
--- a/applications/main/subghz/scenes/subghz_scene_set_type.c
+++ b/applications/main/subghz/scenes/subghz_scene_set_type.c
@@ -29,6 +29,7 @@ static const char* submenu_names[SetTypeMAX] = {
[SetTypeAllmatic868] = "KL: Allmatic 868MHz",
[SetTypeCenturion433] = "KL: Centurion 433MHz",
[SetTypeMonarch433] = "KL: Monarch 433MHz",
+ [SetTypeJollyMotors433] = "KL: Jolly Mot. 433MHz",
[SetTypeSommer_FM_434] = "KL: Sommer 434MHz",
[SetTypeSommer_FM_868] = "KL: Sommer 868MHz",
[SetTypeSommer_FM238_434] = "KL: Sommer fm2 434Mhz",
@@ -428,6 +429,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
.keeloq.cnt = 0x03,
.keeloq.manuf = "Monarch"};
break;
+ case SetTypeJollyMotors433:
+ gen_info = (GenInfo){
+ .type = GenKeeloq,
+ .mod = "AM650",
+ .freq = 433920000,
+ .keeloq.serial = (key & 0x000FFFFF),
+ .keeloq.btn = 0x02,
+ .keeloq.cnt = 0x03,
+ .keeloq.manuf = "Jolly_Motors"};
+ break;
case SetTypeElmesElectronic:
gen_info = (GenInfo){
.type = GenKeeloq,
diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c
index 75b318005..cb4fc5504 100644
--- a/applications/main/subghz/subghz.c
+++ b/applications/main/subghz/subghz.c
@@ -49,6 +49,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 34a882c68..f9b556085 100644
--- a/applications/services/cli/cli.c
+++ b/applications/services/cli/cli.c
@@ -435,9 +435,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);
@@ -451,7 +451,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);
}
@@ -465,9 +465,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_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) {
diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h
index ed207b9f9..4361e07b5 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 cdabaaa05..315baa3a2 100644
--- a/applications/services/cli/cli_vcp.c
+++ b/applications/services/cli/cli_vcp.c
@@ -57,6 +57,7 @@ static CdcCallbacks cdc_cb = {
vcp_state_callback,
vcp_on_cdc_control_line,
NULL,
+ NULL,
};
static CliVcp* vcp = NULL;
@@ -242,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);
@@ -267,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);
}
@@ -310,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/desktop/desktop.c b/applications/services/desktop/desktop.c
index 1132760d5..843dbebb0 100644
--- a/applications/services/desktop/desktop.c
+++ b/applications/services/desktop/desktop.c
@@ -13,6 +13,8 @@
#include "scenes/desktop_scene.h"
#include "scenes/desktop_scene_locked.h"
+#include "furi_hal_power.h"
+
#define TAG "Desktop"
static void desktop_auto_lock_arm(Desktop*);
@@ -131,6 +133,7 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) {
}
desktop_auto_lock_inhibit(desktop);
+
desktop->app_running = true;
furi_semaphore_release(desktop->animation_semaphore);
@@ -142,9 +145,13 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) {
} else if(event == DesktopGlobalAutoLock) {
if(!desktop->app_running && !desktop->locked) {
+ // Disable AutoLock if usb_inhibit_autolock option enabled and device have active USB session.
+ if((desktop->settings.usb_inhibit_auto_lock) && (furi_hal_usb_is_locked())) {
+ return (0);
+ }
+
desktop_lock(desktop);
}
-
} else if(event == DesktopGlobalSaveSettings) {
desktop_settings_save(&desktop->settings);
desktop_apply_settings(desktop);
@@ -199,8 +206,10 @@ static void desktop_stop_auto_lock_timer(Desktop* desktop) {
static void desktop_auto_lock_arm(Desktop* desktop) {
if(desktop->settings.auto_lock_delay_ms) {
- desktop->input_events_subscription = furi_pubsub_subscribe(
- desktop->input_events_pubsub, desktop_input_event_callback, desktop);
+ if(!desktop->input_events_subscription) {
+ desktop->input_events_subscription = furi_pubsub_subscribe(
+ desktop->input_events_pubsub, desktop_input_event_callback, desktop);
+ }
desktop_start_auto_lock_timer(desktop);
}
}
diff --git a/applications/services/desktop/desktop_settings.c b/applications/services/desktop/desktop_settings.c
index 828ec5f0d..f5d0cf896 100644
--- a/applications/services/desktop/desktop_settings.c
+++ b/applications/services/desktop/desktop_settings.c
@@ -6,16 +6,20 @@
#define TAG "DesktopSettings"
-#define DESKTOP_SETTINGS_VER_13 (13)
-#define DESKTOP_SETTINGS_VER (14)
+#define DESKTOP_SETTINGS_VER_14 (14)
+#define DESKTOP_SETTINGS_VER (16)
#define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME)
#define DESKTOP_SETTINGS_MAGIC (0x17)
typedef struct {
- uint8_t reserved[11];
- DesktopSettings settings;
-} DesktopSettingsV13;
+ uint32_t auto_lock_delay_ms;
+ uint8_t displayBatteryPercentage;
+ uint8_t dummy_mode;
+ uint8_t display_clock;
+ FavoriteApp favorite_apps[FavoriteAppNumber];
+ FavoriteApp dummy_apps[DummyAppNumber];
+} DesktopSettingsV14;
// Actual size of DesktopSettings v13
//static_assert(sizeof(DesktopSettingsV13) == 1234);
@@ -37,21 +41,31 @@ void desktop_settings_load(DesktopSettings* settings) {
DESKTOP_SETTINGS_MAGIC,
DESKTOP_SETTINGS_VER);
- } else if(version == DESKTOP_SETTINGS_VER_13) {
- DesktopSettingsV13* settings_v13 = malloc(sizeof(DesktopSettingsV13));
+ } else if(version == DESKTOP_SETTINGS_VER_14) {
+ DesktopSettingsV14* settings_v14 = malloc(sizeof(DesktopSettingsV14));
success = saved_struct_load(
DESKTOP_SETTINGS_PATH,
- settings_v13,
- sizeof(DesktopSettingsV13),
+ settings_v14,
+ sizeof(DesktopSettingsV14),
DESKTOP_SETTINGS_MAGIC,
- DESKTOP_SETTINGS_VER_13);
+ DESKTOP_SETTINGS_VER_14);
if(success) {
- *settings = settings_v13->settings;
+ settings->auto_lock_delay_ms = settings_v14->auto_lock_delay_ms;
+ settings->usb_inhibit_auto_lock = 0;
+ settings->displayBatteryPercentage = settings_v14->displayBatteryPercentage;
+ settings->dummy_mode = settings_v14->dummy_mode;
+ settings->display_clock = settings_v14->display_clock;
+ memcpy(
+ settings->favorite_apps,
+ settings_v14->favorite_apps,
+ sizeof(settings->favorite_apps));
+ memcpy(
+ settings->dummy_apps, settings_v14->dummy_apps, sizeof(settings->dummy_apps));
}
- free(settings_v13);
+ free(settings_v14);
}
} while(false);
diff --git a/applications/services/desktop/desktop_settings.h b/applications/services/desktop/desktop_settings.h
index ba5a78840..784b1eeba 100644
--- a/applications/services/desktop/desktop_settings.h
+++ b/applications/services/desktop/desktop_settings.h
@@ -38,6 +38,7 @@ typedef struct {
typedef struct {
uint32_t auto_lock_delay_ms;
+ uint8_t usb_inhibit_auto_lock;
uint8_t displayBatteryPercentage;
uint8_t dummy_mode;
uint8_t display_clock;
diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c
index 8d387612a..511c64c78 100644
--- a/applications/services/power/power_service/power.c
+++ b/applications/services/power/power_service/power.c
@@ -7,6 +7,8 @@
#include
#include
+#include
+
#define TAG "Power"
#define POWER_OFF_TIMEOUT_S (90U)
@@ -379,7 +381,125 @@ static void power_handle_reboot(PowerBootMode mode) {
furi_hal_power_reset();
}
+// get settings from service to settings_app by send message to power queue
+void power_api_get_settings(Power* power, PowerSettings* settings) {
+ furi_assert(power);
+ furi_assert(settings);
+ PowerMessage msg = {
+ .type = PowerMessageTypeGetSettings,
+ .settings = settings,
+ .lock = api_lock_alloc_locked(),
+ };
+
+ furi_check(
+ furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
+ api_lock_wait_unlock_and_free(msg.lock);
+}
+
+// set settings from settings_app to service by send message to power queue
+void power_api_set_settings(Power* power, const PowerSettings* settings) {
+ furi_assert(power);
+ furi_assert(settings);
+
+ PowerMessage msg = {
+ .type = PowerMessageTypeSetSettings,
+ .csettings = settings,
+ .lock = api_lock_alloc_locked(),
+ };
+
+ furi_check(
+ furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
+ api_lock_wait_unlock_and_free(msg.lock);
+}
+
+//start furi timer for autopoweroff
+static void power_start_auto_poweroff_timer(Power* power) {
+ furi_timer_start(
+ power->auto_poweroff_timer, furi_ms_to_ticks(power->settings.auto_poweroff_delay_ms));
+}
+
+//stop furi timer for autopoweroff
+static void power_stop_auto_poweroff_timer(Power* power) {
+ furi_timer_stop(power->auto_poweroff_timer);
+}
+
+static uint32_t power_is_running_auto_poweroff_timer(Power* power) {
+ return furi_timer_is_running(power->auto_poweroff_timer);
+}
+
+// start|restart poweroff timer
+static void power_auto_poweroff_callback(const void* value, void* context) {
+ furi_assert(value);
+ furi_assert(context);
+ Power* power = context;
+ power_start_auto_poweroff_timer(power);
+}
+
+// callback for poweroff timer (what we do when timer end)
+static void power_auto_poweroff_timer_callback(void* context) {
+ furi_assert(context);
+ Power* power = context;
+
+ //Dont poweroff device if charger connected
+ if (furi_hal_power_is_charging()) {
+ FURI_LOG_D(TAG, "We dont auto_power_off until battery is charging");
+ power_start_auto_poweroff_timer(power);
+ } else {
+ power_off(power);
+ }
+}
+
+//start|restart timer and events subscription and callbacks for input events (we restart timer when user press keys)
+static void power_auto_poweroff_arm(Power* power) {
+ if(power->settings.auto_poweroff_delay_ms) {
+ if(power->input_events_subscription == NULL) {
+ power->input_events_subscription = furi_pubsub_subscribe(
+ power->input_events_pubsub, power_auto_poweroff_callback, power);
+ }
+ power_start_auto_poweroff_timer(power);
+ }
+}
+
+// stop timer and event subscription
+static void power_auto_poweroff_disarm(Power* power) {
+ power_stop_auto_poweroff_timer(power);
+ if(power->input_events_subscription) {
+ furi_pubsub_unsubscribe(power->input_events_pubsub, power->input_events_subscription);
+ power->input_events_subscription = NULL;
+ }
+}
+
+//check message queue from Loader - is some app started or not (if started we dont do auto poweroff)
+static void power_loader_callback(const void* message, void* context) {
+ furi_assert(context);
+ Power* power = context;
+ const LoaderEvent* event = message;
+
+ // disarm timer if some apps started
+ if(event->type == LoaderEventTypeApplicationBeforeLoad) {
+ power->app_running = true;
+ power_auto_poweroff_disarm(power);
+ // arm timer if some apps was not loaded or was stoped
+ } else if(
+ event->type == LoaderEventTypeApplicationLoadFailed ||
+ event->type == LoaderEventTypeApplicationStopped) {
+ power->app_running = false;
+ power_auto_poweroff_arm(power);
+ }
+}
+
+// apply power settings
+static void power_settings_apply(Power* power) {
+ //apply auto_poweroff settings
+ if(power->settings.auto_poweroff_delay_ms && !power->app_running) {
+ power_auto_poweroff_arm(power);
+ } else if(power_is_running_auto_poweroff_timer(power)) {
+ power_auto_poweroff_disarm(power);
+ }
+}
+
+// do something depend from power queue message
static void power_message_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
Power* power = context;
@@ -405,6 +525,20 @@ static void power_message_callback(FuriEventLoopObject* object, void* context) {
case PowerMessageTypeShowBatteryLowWarning:
power->show_battery_low_warning = *msg.bool_param;
break;
+ case PowerMessageTypeGetSettings:
+ furi_assert(msg.lock);
+ *msg.settings = power->settings;
+ break;
+ case PowerMessageTypeSetSettings:
+ furi_assert(msg.lock);
+ power->settings = *msg.csettings;
+ power_settings_apply(power);
+ power_settings_save(&power->settings);
+ break;
+ case PowerMessageTypeReloadSettings:
+ power_settings_load(&power->settings);
+ power_settings_apply(power);
+ break;
default:
furi_crash();
}
@@ -436,6 +570,36 @@ static void power_tick_callback(void* context) {
}
}
+static void power_storage_callback(const void* message, void* context) {
+ furi_assert(context);
+ Power* power = context;
+ const StorageEvent* event = message;
+
+ if(event->type == StorageEventTypeCardMount) {
+ PowerMessage msg = {
+ .type = PowerMessageTypeReloadSettings,
+ };
+
+ furi_check(
+ furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk);
+ }
+}
+
+// load inital settings from file for power service
+static void power_init_settings(Power* power) {
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ furi_pubsub_subscribe(storage_get_pubsub(storage), power_storage_callback, power);
+
+ if(storage_sd_status(storage) != FSE_OK) {
+ FURI_LOG_D(TAG, "SD Card not ready, skipping settings");
+ return;
+ }
+
+ power_settings_load(&power->settings);
+ power_settings_apply(power);
+ furi_record_close(RECORD_STORAGE);
+}
+
static Power* power_alloc(void) {
Power* power = malloc(sizeof(Power));
// Pubsub
@@ -449,6 +613,16 @@ static Power* power_alloc(void) {
desktop_settings_load(settings);
power->displayBatteryPercentage = settings->displayBatteryPercentage;
free(settings);
+
+ // auto_poweroff
+ //---define subscription to loader events message (info about started apps) and defina callback for this
+ Loader* loader = furi_record_open(RECORD_LOADER);
+ furi_pubsub_subscribe(loader_get_pubsub(loader), power_loader_callback, power);
+ power->input_events_pubsub = furi_record_open(RECORD_INPUT_EVENTS);
+ //define autopoweroff timer and they callback
+ power->auto_poweroff_timer =
+ furi_timer_alloc(power_auto_poweroff_timer_callback, FuriTimerTypeOnce, power);
+
// Gui
Gui* gui = furi_record_open(RECORD_GUI);
@@ -486,9 +660,19 @@ int32_t power_srv(void* p) {
}
Power* power = power_alloc();
+
+ // load inital settings for power service
+ power_init_settings(power);
+
power_update_info(power);
furi_record_create(RECORD_POWER, power);
+
+ // Can't be done in alloc, other things in startup need power service and it would deadlock by waiting for loader
+ Loader* loader = furi_record_open(RECORD_LOADER);
+ power->app_running = loader_is_locked(loader);
+ furi_record_close(RECORD_LOADER);
+
furi_event_loop_run(power->event_loop);
return 0;
diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h
index 34d58353a..59947fa52 100644
--- a/applications/services/power/power_service/power.h
+++ b/applications/services/power/power_service/power.h
@@ -2,7 +2,7 @@
#include
#include
-
+#include
#include
#ifdef __cplusplus
@@ -102,6 +102,12 @@ void power_enable_low_battery_level_notification(Power* power, bool enable);
*/
void power_trigger_ui_update(Power* power);
+// get settings from service to app
+void power_api_get_settings(Power* instance, PowerSettings* settings);
+
+// set settings from app to service
+void power_api_set_settings(Power* instance, const PowerSettings* settings);
+
#ifdef __cplusplus
}
#endif
diff --git a/applications/services/power/power_service/power_i.h b/applications/services/power/power_service/power_i.h
index d75071f8f..40b4a1ef4 100644
--- a/applications/services/power/power_service/power_i.h
+++ b/applications/services/power/power_service/power_i.h
@@ -36,6 +36,11 @@ struct Power {
uint8_t displayBatteryPercentage;
uint8_t battery_level;
uint8_t power_off_timeout;
+ PowerSettings settings;
+ FuriTimer* auto_poweroff_timer;
+ bool app_running;
+ FuriPubSub* input_events_pubsub;
+ FuriPubSubSubscription* input_events_subscription;
};
typedef enum {
@@ -49,6 +54,9 @@ typedef enum {
PowerMessageTypeGetInfo,
PowerMessageTypeIsBatteryHealthy,
PowerMessageTypeShowBatteryLowWarning,
+ PowerMessageTypeGetSettings,
+ PowerMessageTypeSetSettings,
+ PowerMessageTypeReloadSettings,
} PowerMessageType;
typedef struct {
@@ -57,6 +65,8 @@ typedef struct {
PowerBootMode boot_mode;
PowerInfo* power_info;
bool* bool_param;
+ PowerSettings* settings;
+ const PowerSettings* csettings;
};
FuriApiLock lock;
} PowerMessage;
diff --git a/applications/services/power/power_service/power_settings.c b/applications/services/power/power_service/power_settings.c
new file mode 100644
index 000000000..139323a7a
--- /dev/null
+++ b/applications/services/power/power_service/power_settings.c
@@ -0,0 +1,77 @@
+#include "power_settings.h"
+#include "power_settings_filename.h"
+
+#include
+#include
+
+#define TAG "PowerSettings"
+
+#define POWER_SETTINGS_VER_0 (0) // OLD version number
+#define POWER_SETTINGS_VER (1) // NEW actual version nnumber
+
+#define POWER_SETTINGS_PATH INT_PATH(POWER_SETTINGS_FILE_NAME)
+#define POWER_SETTINGS_MAGIC (0x18)
+
+typedef struct {
+ //inital set - empty
+} PowerSettingsV0;
+
+void power_settings_load(PowerSettings* settings) {
+ furi_assert(settings);
+
+ bool success = false;
+
+ do {
+ uint8_t version;
+ if(!saved_struct_get_metadata(POWER_SETTINGS_PATH, NULL, &version, NULL)) break;
+
+ if(version == POWER_SETTINGS_VER) { // if config actual version - load it directly
+ success = saved_struct_load(
+ POWER_SETTINGS_PATH,
+ settings,
+ sizeof(PowerSettings),
+ POWER_SETTINGS_MAGIC,
+ POWER_SETTINGS_VER);
+
+ } else if(
+ version ==
+ POWER_SETTINGS_VER_0) { // if config previous version - load it and manual set new settings to inital value
+ PowerSettingsV0* settings_v0 = malloc(sizeof(PowerSettingsV0));
+
+ success = saved_struct_load(
+ POWER_SETTINGS_PATH,
+ settings_v0,
+ sizeof(PowerSettingsV0),
+ POWER_SETTINGS_MAGIC,
+ POWER_SETTINGS_VER_0);
+
+ if(success) {
+ settings->auto_poweroff_delay_ms = 0;
+ }
+
+ free(settings_v0);
+ }
+
+ } while(false);
+
+ if(!success) {
+ FURI_LOG_W(TAG, "Failed to load file, using defaults");
+ memset(settings, 0, sizeof(PowerSettings));
+ power_settings_save(settings);
+ }
+}
+
+void power_settings_save(const PowerSettings* settings) {
+ furi_assert(settings);
+
+ const bool success = saved_struct_save(
+ POWER_SETTINGS_PATH,
+ settings,
+ sizeof(PowerSettings),
+ POWER_SETTINGS_MAGIC,
+ POWER_SETTINGS_VER);
+
+ if(!success) {
+ FURI_LOG_E(TAG, "Failed to save file");
+ }
+}
diff --git a/applications/services/power/power_service/power_settings.h b/applications/services/power/power_service/power_settings.h
new file mode 100644
index 000000000..65d8e079e
--- /dev/null
+++ b/applications/services/power/power_service/power_settings.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include
+
+typedef struct {
+ uint32_t auto_poweroff_delay_ms;
+} PowerSettings;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+void power_settings_load(PowerSettings* settings);
+void power_settings_save(const PowerSettings* settings);
+#ifdef __cplusplus
+}
+#endif
diff --git a/applications/services/power/power_service/power_settings_filename.h b/applications/services/power/power_service/power_settings_filename.h
new file mode 100644
index 000000000..e8c6fbfce
--- /dev/null
+++ b/applications/services/power/power_service/power_settings_filename.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#define POWER_SETTINGS_FILE_NAME ".power.settings"
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/settings/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c
index 04cb0182b..9b992c80c 100644
--- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c
+++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c
@@ -9,6 +9,7 @@
typedef enum {
DesktopSettingsPinSetup = 0,
DesktopSettingsAutoLockDelay,
+ DesktopSettingsAutoPowerOff,
DesktopSettingsBatteryDisplay,
DesktopSettingsClockDisplay,
DesktopSettingsChangeName,
@@ -44,6 +45,15 @@ const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = {
const uint32_t auto_lock_delay_value[AUTO_LOCK_DELAY_COUNT] =
{0, 10000, 15000, 30000, 60000, 90000, 120000, 300000, 600000};
+#define USB_INHIBIT_AUTO_LOCK_DELAY_COUNT 2
+
+const char* const usb_inhibit_auto_lock_delay_text[USB_INHIBIT_AUTO_LOCK_DELAY_COUNT] = {
+ "OFF",
+ "ON",
+};
+
+const uint32_t usb_inhibit_auto_lock_delay_value[USB_INHIBIT_AUTO_LOCK_DELAY_COUNT] = {0,1};
+
#define CLOCK_ENABLE_COUNT 2
const char* const clock_enable_text[CLOCK_ENABLE_COUNT] = {
"OFF",
@@ -94,6 +104,14 @@ static void desktop_settings_scene_start_auto_lock_delay_changed(VariableItem* i
app->settings.auto_lock_delay_ms = auto_lock_delay_value[index];
}
+static void desktop_settings_scene_start_usb_inhibit_auto_lock_delay_changed(VariableItem* item) {
+ DesktopSettingsApp* app = variable_item_get_context(item);
+ uint8_t index = variable_item_get_current_value_index(item);
+
+ variable_item_set_current_value_text(item, usb_inhibit_auto_lock_delay_text[index]);
+ app->settings.usb_inhibit_auto_lock = usb_inhibit_auto_lock_delay_value[index];
+}
+
void desktop_settings_scene_start_on_enter(void* context) {
DesktopSettingsApp* app = context;
VariableItemList* variable_item_list = app->variable_item_list;
@@ -115,6 +133,21 @@ void desktop_settings_scene_start_on_enter(void* context) {
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, auto_lock_delay_text[value_index]);
+ // USB connection Inhibit autolock OFF|ON|with opened RPC session
+ item = variable_item_list_add(
+ variable_item_list,
+ "Auto Lock disarm by active USB session",
+ USB_INHIBIT_AUTO_LOCK_DELAY_COUNT,
+ desktop_settings_scene_start_usb_inhibit_auto_lock_delay_changed,
+ app);
+
+ value_index = value_index_uint32(
+ app->settings.usb_inhibit_auto_lock,
+ usb_inhibit_auto_lock_delay_value,
+ USB_INHIBIT_AUTO_LOCK_DELAY_COUNT);
+ variable_item_set_current_value_index(item, value_index);
+ variable_item_set_current_value_text(item, usb_inhibit_auto_lock_delay_text[value_index]);
+
item = variable_item_list_add(
variable_item_list,
"Battery View",
diff --git a/applications/settings/power_settings_app/power_settings_app.c b/applications/settings/power_settings_app/power_settings_app.c
index 57df1344f..c0e73e256 100644
--- a/applications/settings/power_settings_app/power_settings_app.c
+++ b/applications/settings/power_settings_app/power_settings_app.c
@@ -46,10 +46,18 @@ PowerSettingsApp* power_settings_app_alloc(uint32_t first_scene) {
app->submenu = submenu_alloc();
view_dispatcher_add_view(
app->view_dispatcher, PowerSettingsAppViewSubmenu, submenu_get_view(app->submenu));
+ app->variable_item_list = variable_item_list_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ PowerSettingsAppViewVariableItemList,
+ variable_item_list_get_view(app->variable_item_list));
app->dialog = dialog_ex_alloc();
view_dispatcher_add_view(
app->view_dispatcher, PowerSettingsAppViewDialog, dialog_ex_get_view(app->dialog));
+ // get settings from service to app
+ power_api_get_settings(app->power, &app->settings);
+
// Set first scene
scene_manager_next_scene(app->scene_manager, first_scene);
return app;
@@ -57,16 +65,23 @@ PowerSettingsApp* power_settings_app_alloc(uint32_t first_scene) {
void power_settings_app_free(PowerSettingsApp* app) {
furi_assert(app);
+
+ // set settings from app to service
+ power_api_set_settings(app->power, &app->settings);
// Views
view_dispatcher_remove_view(app->view_dispatcher, PowerSettingsAppViewBatteryInfo);
battery_info_free(app->battery_info);
view_dispatcher_remove_view(app->view_dispatcher, PowerSettingsAppViewSubmenu);
submenu_free(app->submenu);
+ view_dispatcher_remove_view(app->view_dispatcher, PowerSettingsAppViewVariableItemList);
+ variable_item_list_free(app->variable_item_list);
view_dispatcher_remove_view(app->view_dispatcher, PowerSettingsAppViewDialog);
dialog_ex_free(app->dialog);
+
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
+
// Records
furi_record_close(RECORD_POWER);
furi_record_close(RECORD_GUI);
diff --git a/applications/settings/power_settings_app/power_settings_app.h b/applications/settings/power_settings_app/power_settings_app.h
index bbbbacd61..f28e6a548 100644
--- a/applications/settings/power_settings_app/power_settings_app.h
+++ b/applications/settings/power_settings_app/power_settings_app.h
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
#include
#include
@@ -10,11 +11,13 @@
#include "views/battery_info.h"
#include
+#include
#include
#include "scenes/power_settings_scene.h"
typedef struct {
+ PowerSettings settings;
Power* power;
Gui* gui;
SceneManager* scene_manager;
@@ -23,12 +26,14 @@ typedef struct {
Submenu* submenu;
DialogEx* dialog;
PowerInfo info;
+ VariableItemList* variable_item_list;
} PowerSettingsApp;
typedef enum {
PowerSettingsAppViewBatteryInfo,
PowerSettingsAppViewSubmenu,
PowerSettingsAppViewDialog,
+ PowerSettingsAppViewVariableItemList
} PowerSettingsAppView;
typedef enum {
diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_start.c b/applications/settings/power_settings_app/scenes/power_settings_scene_start.c
index 63f123d89..3fc3a8a30 100644
--- a/applications/settings/power_settings_app/scenes/power_settings_scene_start.c
+++ b/applications/settings/power_settings_app/scenes/power_settings_scene_start.c
@@ -1,12 +1,32 @@
#include "../power_settings_app.h"
+#include
enum PowerSettingsSubmenuIndex {
PowerSettingsSubmenuIndexBatteryInfo,
PowerSettingsSubmenuIndexReboot,
PowerSettingsSubmenuIndexOff,
+ PowerSettingsSubmenuIndexAutoPowerOff,
};
-static void power_settings_scene_start_submenu_callback(void* context, uint32_t index) {
+#define AUTO_POWEROFF_DELAY_COUNT 8
+const char* const auto_poweroff_delay_text[AUTO_POWEROFF_DELAY_COUNT] =
+ {"OFF", "5min", "10min", "15min", "30min", "45min", "60min", "90min"};
+
+const uint32_t auto_poweroff_delay_value[AUTO_POWEROFF_DELAY_COUNT] =
+ {0, 300000, 600000, 900000, 1800000, 2700000, 3600000, 5400000};
+
+// change variable_item_list visible text and app_poweroff_delay_time_settings when user change item in variable_item_list
+static void power_settings_scene_start_auto_poweroff_delay_changed(VariableItem* item) {
+ PowerSettingsApp* app = variable_item_get_context(item);
+ uint8_t index = variable_item_get_current_value_index(item);
+
+ variable_item_set_current_value_text(item, auto_poweroff_delay_text[index]);
+ app->settings.auto_poweroff_delay_ms = auto_poweroff_delay_value[index];
+}
+
+static void power_settings_scene_start_submenu_callback(
+ void* context,
+ uint32_t index) { //show selected menu screen
furi_assert(context);
PowerSettingsApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
@@ -14,30 +34,37 @@ static void power_settings_scene_start_submenu_callback(void* context, uint32_t
void power_settings_scene_start_on_enter(void* context) {
PowerSettingsApp* app = context;
- Submenu* submenu = app->submenu;
+ VariableItemList* variable_item_list = app->variable_item_list;
- submenu_add_item(
- submenu,
- "Battery Info",
- PowerSettingsSubmenuIndexBatteryInfo,
- power_settings_scene_start_submenu_callback,
- app);
- submenu_add_item(
- submenu,
- "Reboot",
- PowerSettingsSubmenuIndexReboot,
- power_settings_scene_start_submenu_callback,
- app);
- submenu_add_item(
- submenu,
- "Power OFF",
- PowerSettingsSubmenuIndexOff,
- power_settings_scene_start_submenu_callback,
- app);
- submenu_set_selected_item(
- submenu, scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneStart));
+ variable_item_list_add(variable_item_list, "Battery Info", 1, NULL, NULL);
+ variable_item_list_add(variable_item_list, "Reboot", 1, NULL, NULL);
+ variable_item_list_add(variable_item_list, "Power OFF", 1, NULL, NULL);
- view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewSubmenu);
+ VariableItem* item;
+ uint8_t value_index;
+ item = variable_item_list_add(
+ variable_item_list,
+ "Auto PowerOff",
+ AUTO_POWEROFF_DELAY_COUNT,
+ power_settings_scene_start_auto_poweroff_delay_changed, //function for change visible item list value and app settings
+ app);
+
+ value_index = value_index_uint32(
+ app->settings.auto_poweroff_delay_ms,
+ auto_poweroff_delay_value,
+ AUTO_POWEROFF_DELAY_COUNT);
+ variable_item_set_current_value_index(item, value_index);
+ variable_item_set_current_value_text(item, auto_poweroff_delay_text[value_index]);
+
+ variable_item_list_set_selected_item(
+ variable_item_list,
+ scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneStart));
+ variable_item_list_set_enter_callback( //callback to show next mennu screen
+ variable_item_list,
+ power_settings_scene_start_submenu_callback,
+ app);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewVariableItemList);
}
bool power_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
@@ -60,5 +87,6 @@ bool power_settings_scene_start_on_event(void* context, SceneManagerEvent event)
void power_settings_scene_start_on_exit(void* context) {
PowerSettingsApp* app = context;
- submenu_reset(app->submenu);
+ variable_item_list_reset(app->variable_item_list);
+ power_settings_save(&app->settings); //actual need save every time when use ?
}
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 3dbfca258..56a02a068 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": "@darkflippers/fz-sdk-ul",
- "version": "0.1.2",
+ "version": "0.1.3",
"description": "Type declarations and documentation for native JS modules available on Unleashed Custom Firmware for Flipper Zero",
"keywords": [
"unleashed",
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 4fa1a95a7..2cd1c6ab3 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/manifest.txt b/assets/dolphin/external/manifest.txt
index a69395531..7e8bee083 100644
--- a/assets/dolphin/external/manifest.txt
+++ b/assets/dolphin/external/manifest.txt
@@ -195,21 +195,42 @@ Min butthurt: 0
Max butthurt: 8
Min level: 1
Max level: 3
-Weight: 4
+Weight: 3
Name: L3_Intruder_alert_128x64
Min butthurt: 0
Max butthurt: 12
Min level: 3
Max level: 3
-Weight: 4
+Weight: 3
Name: L1_Procrastinating_128x64
Min butthurt: 0
Max butthurt: 8
Min level: 1
Max level: 3
-Weight: 6
+Weight: 3
+
+Name: L1_Happy_holidays_128x64
+Min butthurt: 0
+Max butthurt: 14
+Min level: 1
+Max level: 3
+Weight: 4
+
+Name: L1_Sleigh_ride_128x64
+Min butthurt: 0
+Max butthurt: 14
+Min level: 1
+Max level: 3
+Weight: 4
+
+Name: L1_New_year_128x64
+Min butthurt: 0
+Max butthurt: 14
+Min level: 1
+Max level: 3
+Weight: 4
Name: L3_Fireplace_128x64
Min butthurt: 0
diff --git a/assets/protobuf b/assets/protobuf
index 6c7c0d55e..1c84fa489 160000
--- a/assets/protobuf
+++ b/assets/protobuf
@@ -1 +1 @@
-Subproject commit 6c7c0d55e82cb89223cf4890a540af4cff837fa7
+Subproject commit 1c84fa48919cbb71d1cc65236fc0ee36740e24c6
diff --git a/documentation/Doxyfile b/documentation/Doxyfile
index e3cc3f6fa..1220a5eea 100644
--- a/documentation/Doxyfile
+++ b/documentation/Doxyfile
@@ -1040,6 +1040,7 @@ EXCLUDE = $(DOXY_SRC_ROOT)/lib/mlib \
$(DOXY_SRC_ROOT)/applications/plugins/dap_link/lib/free-dap \
$(DOXY_SRC_ROOT)/applications/debug \
$(DOXY_SRC_ROOT)/applications/main \
+ $(DOXY_SRC_ROOT)/applications/system/js_app/packages \
$(DOXY_SRC_ROOT)/applications/settings \
$(DOXY_SRC_ROOT)/lib/micro-ecc \
$(DOXY_SRC_ROOT)/lib/ReadMe.md \
diff --git a/documentation/FuriHalDebuging.md b/documentation/FuriHalDebugging.md
similarity index 100%
rename from documentation/FuriHalDebuging.md
rename to documentation/FuriHalDebugging.md
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/firmware.scons b/firmware.scons
index 4c5e05873..e7378f957 100644
--- a/firmware.scons
+++ b/firmware.scons
@@ -29,6 +29,8 @@ env = ENV.Clone(
TARGETS_ROOT=Dir("#/targets"),
LINT_SOURCES=[
Dir("applications"),
+ # Not C code
+ Dir("!applications/system/js_app/packages"),
],
LIBPATH=[
"${LIB_DIST_DIR}",
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 fd576ea72..6e5157957 100644
--- a/furi/core/thread.c
+++ b/furi/core/thread.c
@@ -23,12 +23,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) {
@@ -245,6 +252,7 @@ void furi_thread_free(FuriThread* thread) {
}
furi_string_free(thread->output.buffer);
+ furi_string_free(thread->input.unread_buffer);
free(thread);
}
@@ -710,13 +718,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);
@@ -727,19 +744,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);
@@ -772,6 +803,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_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c
index 4ad2ff878..bd32956d6 100644
--- a/lib/nfc/protocols/mf_plus/mf_plus_i.c
+++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c
@@ -27,65 +27,51 @@ MfPlusError mf_plus_get_type_from_version(
MfPlusError error = MfPlusErrorProtocol;
- if(mf_plus_data->version.hw_major == 0x02 || mf_plus_data->version.hw_major == 0x82) {
+ if(mf_plus_data->version.hw_type == 0x02 || mf_plus_data->version.hw_type == 0x82) {
error = MfPlusErrorNone;
- if(iso14443_4a_data->iso14443_3a_data->sak == 0x10) {
- // Mifare Plus 2K SL2
- mf_plus_data->type = MfPlusTypePlus;
+ // Mifare Plus EV1/EV2
+
+ // Revision
+ switch(mf_plus_data->version.hw_major) {
+ case 0x11:
+ mf_plus_data->type = MfPlusTypeEV1;
+ FURI_LOG_D(TAG, "Mifare Plus EV1");
+ break;
+ case 0x22:
+ mf_plus_data->type = MfPlusTypeEV2;
+ FURI_LOG_D(TAG, "Mifare Plus EV2");
+ break;
+ default:
+ mf_plus_data->type = MfPlusTypeUnknown;
+ FURI_LOG_D(TAG, "Unknown Mifare Plus EV type");
+ break;
+ }
+
+ // Storage size
+ switch(mf_plus_data->version.hw_storage) {
+ case 0x16:
mf_plus_data->size = MfPlusSize2K;
- mf_plus_data->security_level = MfPlusSecurityLevel2;
- FURI_LOG_D(TAG, "Mifare Plus 2K SL2");
- } else if(iso14443_4a_data->iso14443_3a_data->sak == 0x11) {
- // Mifare Plus 4K SL3
- mf_plus_data->type = MfPlusTypePlus;
+ FURI_LOG_D(TAG, "2K");
+ break;
+ case 0x18:
mf_plus_data->size = MfPlusSize4K;
+ FURI_LOG_D(TAG, "4K");
+ break;
+ default:
+ mf_plus_data->size = MfPlusSizeUnknown;
+ FURI_LOG_D(TAG, "Unknown storage size");
+ break;
+ }
+
+ // Security level
+ if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) {
+ // Mifare Plus EV1/2 SL3
mf_plus_data->security_level = MfPlusSecurityLevel3;
- FURI_LOG_D(TAG, "Mifare Plus 4K SL3");
+ FURI_LOG_D(TAG, "Mifare Plus EV1/2 SL3");
} else {
- // Mifare Plus EV1/EV2
-
- // Revision
- switch(mf_plus_data->version.hw_major) {
- case 0x11:
- mf_plus_data->type = MfPlusTypeEV1;
- FURI_LOG_D(TAG, "Mifare Plus EV1");
- break;
- case 0x22:
- mf_plus_data->type = MfPlusTypeEV2;
- FURI_LOG_D(TAG, "Mifare Plus EV2");
- break;
- default:
- mf_plus_data->type = MfPlusTypeUnknown;
- FURI_LOG_D(TAG, "Unknown Mifare Plus EV type");
- break;
- }
-
- // Storage size
- switch(mf_plus_data->version.hw_storage) {
- case 0x16:
- mf_plus_data->size = MfPlusSize2K;
- FURI_LOG_D(TAG, "2K");
- break;
- case 0x18:
- mf_plus_data->size = MfPlusSize4K;
- FURI_LOG_D(TAG, "4K");
- break;
- default:
- mf_plus_data->size = MfPlusSizeUnknown;
- FURI_LOG_D(TAG, "Unknown storage size");
- break;
- }
-
- // Security level
- if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) {
- // Mifare Plus EV1/2 SL3
- mf_plus_data->security_level = MfPlusSecurityLevel3;
- FURI_LOG_D(TAG, "Miare Plus EV1/2 SL3");
- } else {
- // Mifare Plus EV1/2 SL1
- mf_plus_data->security_level = MfPlusSecurityLevel1;
- FURI_LOG_D(TAG, "Miare Plus EV1/2 SL1");
- }
+ // Mifare Plus EV1/2 SL1
+ mf_plus_data->security_level = MfPlusSecurityLevel1;
+ FURI_LOG_D(TAG, "Mifare Plus EV1/2 SL1");
}
}
@@ -148,6 +134,24 @@ MfPlusError
FURI_LOG_D(TAG, "Sak 08 but no known Mifare Plus type");
}
+ break;
+ case 0x10:
+ // Mifare Plus X 2K SL2
+ mf_plus_data->type = MfPlusTypeX;
+ mf_plus_data->size = MfPlusSize2K;
+ mf_plus_data->security_level = MfPlusSecurityLevel2;
+ FURI_LOG_D(TAG, "Mifare Plus X 2K SL2");
+ error = MfPlusErrorNone;
+
+ break;
+ case 0x11:
+ // Mifare Plus X 4K SL2
+ mf_plus_data->type = MfPlusTypeX;
+ mf_plus_data->size = MfPlusSize4K;
+ mf_plus_data->security_level = MfPlusSecurityLevel2;
+ FURI_LOG_D(TAG, "Mifare Plus X 4K SL2");
+ error = MfPlusErrorNone;
+
break;
case 0x18:
if(memcmp(
@@ -234,10 +238,22 @@ MfPlusError
}
MfPlusError mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) {
- const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion);
+ bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion);
if(can_parse) {
bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion));
+ } else if(
+ bit_buffer_get_size_bytes(buf) == 8 &&
+ bit_buffer_get_byte(buf, 0) == MF_PLUS_STATUS_ADDITIONAL_FRAME) {
+ // HACK(-nofl): There are supposed to be three parts to the GetVersion command,
+ // with the second and third parts fetched by sending the AdditionalFrame
+ // command. I don't know whether the entire MIFARE Plus line uses status as
+ // the first byte, so let's just assume we only have the first part of
+ // the response if it's size 8 and starts with the AF status. The second
+ // part of the response is the same size and status byte, but so far
+ // we're only reading one response.
+ can_parse = true;
+ bit_buffer_write_bytes_mid(buf, data, 1, bit_buffer_get_size_bytes(buf) - 1);
}
return can_parse ? MfPlusErrorNone : MfPlusErrorProtocol;
diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.h b/lib/nfc/protocols/mf_plus/mf_plus_i.h
index 1b80030f9..302f5a178 100644
--- a/lib/nfc/protocols/mf_plus/mf_plus_i.h
+++ b/lib/nfc/protocols/mf_plus/mf_plus_i.h
@@ -4,6 +4,9 @@
#define MF_PLUS_FFF_PICC_PREFIX "PICC"
+#define MF_PLUS_STATUS_OPERATION_OK (0x90)
+#define MF_PLUS_STATUS_ADDITIONAL_FRAME (0xAF)
+
MfPlusError mf_plus_get_type_from_version(
const Iso14443_4aData* iso14443_4a_data,
MfPlusData* mf_plus_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 d2f538c5e..3bf04d4c6 100644
--- a/lib/signal_reader/parsers/iso15693/iso15693_parser.c
+++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.c
@@ -245,6 +245,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;
@@ -319,4 +321,4 @@ void iso15693_parser_force_1outof256(Iso15693Parser* instance) {
instance->detect_mode = false;
instance->mode = Iso15693ParserMode1OutOf256;
-}
\ No newline at end of file
+}
diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c
index 96f40f9fc..3370f2cdc 100644
--- a/lib/subghz/protocols/bin_raw.c
+++ b/lib/subghz/protocols/bin_raw.c
@@ -314,8 +314,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/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h
index 9ca7d91ec..71265e88c 100644
--- a/lib/subghz/protocols/protocol_items.h
+++ b/lib/subghz/protocols/protocol_items.h
@@ -51,4 +51,4 @@
#include "gangqi.h"
#include "marantec24.h"
#include "hollarm.h"
-#include "hay21.h"
\ No newline at end of file
+#include "hay21.h"
diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript
index d582c34c1..0381ce5b9 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 e8f6b3b77..6d6b3cce5 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/lint.py b/scripts/lint.py
index 8530209be..896cd3812 100755
--- a/scripts/lint.py
+++ b/scripts/lint.py
@@ -35,21 +35,25 @@ class Main(App):
self.parser_format.set_defaults(func=self.format)
@staticmethod
- def _filter_lint_directories(dirnames: list[str]):
+ def _filter_lint_directories(
+ dirpath: str, dirnames: list[str], excludes: tuple[str]
+ ):
# Skipping 3rd-party code - usually resides in subfolder "lib"
if "lib" in dirnames:
dirnames.remove("lib")
- # Skipping hidden folders
+ # Skipping hidden and excluded folders
for dirname in dirnames.copy():
if dirname.startswith("."):
dirnames.remove(dirname)
+ if os.path.join(dirpath, dirname).startswith(excludes):
+ dirnames.remove(dirname)
- def _check_folders(self, folders: list):
+ def _check_folders(self, folders: list, excludes: tuple[str]):
show_message = False
pattern = re.compile(SOURCE_CODE_DIR_PATTERN)
for folder in folders:
for dirpath, dirnames, filenames in os.walk(folder):
- self._filter_lint_directories(dirnames)
+ self._filter_lint_directories(dirpath, dirnames, excludes)
for dirname in dirnames:
if not pattern.match(dirname):
@@ -61,11 +65,11 @@ class Main(App):
"Folders are not renamed automatically, please fix it by yourself"
)
- def _find_sources(self, folders: list):
+ def _find_sources(self, folders: list, excludes: tuple[str]):
output = []
for folder in folders:
for dirpath, dirnames, filenames in os.walk(folder):
- self._filter_lint_directories(dirnames)
+ self._filter_lint_directories(dirpath, dirnames, excludes)
for filename in filenames:
ext = os.path.splitext(filename.lower())[1]
@@ -168,14 +172,20 @@ class Main(App):
def _perform(self, dry_run: bool):
result = 0
- sources = self._find_sources(self.args.input)
+ excludes = []
+ for folder in self.args.input.copy():
+ if folder.startswith("!"):
+ excludes.append(folder.removeprefix("!"))
+ self.args.input.remove(folder)
+ excludes = tuple(excludes)
+ sources = self._find_sources(self.args.input, excludes)
if not self._format_sources(sources, dry_run=dry_run):
result |= 0b001
if not self._apply_file_naming_convention(sources, dry_run=dry_run):
result |= 0b010
if not self._apply_file_permissions(sources, dry_run=dry_run):
result |= 0b100
- self._check_folders(self.args.input)
+ self._check_folders(self.args.input, excludes)
return result
def check(self):
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 b5d51a0dd..6ce1736d0 100644
--- a/targets/f18/api_symbols.csv
+++ b/targets/f18/api_symbols.csv
@@ -1,5 +1,5 @@
entry,status,name,type,params
-Version,+,78.1,,
+Version,+,79.2,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
Header,+,applications/services/cli/cli.h,,
@@ -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"
@@ -1446,6 +1453,7 @@ Function,+,furi_hal_serial_get_gpio_pin,const GpioPin*,"FuriHalSerialHandle*, Fu
Function,+,furi_hal_serial_init,void,"FuriHalSerialHandle*, uint32_t"
Function,+,furi_hal_serial_is_baud_rate_supported,_Bool,"FuriHalSerialHandle*, uint32_t"
Function,+,furi_hal_serial_resume,void,FuriHalSerialHandle*
+Function,+,furi_hal_serial_send_break,void,FuriHalSerialHandle*
Function,+,furi_hal_serial_set_br,void,"FuriHalSerialHandle*, uint32_t"
Function,+,furi_hal_serial_suspend,void,FuriHalSerialHandle*
Function,+,furi_hal_serial_tx,void,"FuriHalSerialHandle*, const uint8_t*, size_t"
@@ -1574,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*
@@ -1654,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*
@@ -1674,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
@@ -2280,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 3e0a90630..b649f1f99 100644
--- a/targets/f7/api_symbols.csv
+++ b/targets/f7/api_symbols.csv
@@ -1,5 +1,5 @@
entry,status,name,type,params
-Version,+,78.1,,
+Version,+,79.3,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
@@ -236,6 +236,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,,
@@ -451,11 +452,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*, ..."
@@ -1189,6 +1195,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"
@@ -1670,6 +1677,7 @@ Function,+,furi_hal_serial_get_gpio_pin,const GpioPin*,"FuriHalSerialHandle*, Fu
Function,+,furi_hal_serial_init,void,"FuriHalSerialHandle*, uint32_t"
Function,+,furi_hal_serial_is_baud_rate_supported,_Bool,"FuriHalSerialHandle*, uint32_t"
Function,+,furi_hal_serial_resume,void,FuriHalSerialHandle*
+Function,+,furi_hal_serial_send_break,void,FuriHalSerialHandle*
Function,+,furi_hal_serial_set_br,void,"FuriHalSerialHandle*, uint32_t"
Function,+,furi_hal_serial_suspend,void,FuriHalSerialHandle*
Function,+,furi_hal_serial_tx,void,"FuriHalSerialHandle*, const uint8_t*, size_t"
@@ -1837,6 +1845,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*
@@ -1917,6 +1926,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*
@@ -1937,9 +1947,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
@@ -2970,6 +2983,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_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent"
+Function,+,pipe_set_callback_context,void,"PipeSide*, void*"
+Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, 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"
@@ -2994,12 +3023,16 @@ Function,-,posix_memalign,int,"void**, size_t, size_t"
Function,-,pow,double,"double, double"
Function,-,pow10,double,double
Function,-,pow10f,float,float
+Function,+,power_api_get_settings,void,"Power*, PowerSettings*"
+Function,+,power_api_set_settings,void,"Power*, const PowerSettings*"
Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool"
Function,+,power_get_info,void,"Power*, PowerInfo*"
Function,+,power_get_pubsub,FuriPubSub*,Power*
Function,+,power_is_battery_healthy,_Bool,Power*
Function,+,power_off,void,Power*
Function,+,power_reboot,void,"Power*, PowerBootMode"
+Function,+,power_settings_load,void,PowerSettings*
+Function,+,power_settings_save,void,const PowerSettings*
Function,-,power_trigger_ui_update,void,Power*
Function,+,powf,float,"float, float"
Function,-,powl,long double,"long double, long double"
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;
diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c
index 5ddb0785f..3f6b575a6 100644
--- a/targets/f7/furi_hal/furi_hal_serial.c
+++ b/targets/f7/furi_hal/furi_hal_serial.c
@@ -950,3 +950,13 @@ const GpioPin*
return furi_hal_serial_config[handle->id].gpio[direction];
}
+
+void furi_hal_serial_send_break(FuriHalSerialHandle* handle) {
+ furi_check(handle);
+
+ if(handle->id == FuriHalSerialIdUsart) {
+ LL_USART_RequestBreakSending(USART1);
+ } else {
+ LL_LPUART_RequestBreakSending(LPUART1);
+ }
+}
diff --git a/targets/f7/furi_hal/furi_hal_serial.h b/targets/f7/furi_hal/furi_hal_serial.h
index 00010d83c..6dad8ec31 100644
--- a/targets/f7/furi_hal/furi_hal_serial.h
+++ b/targets/f7/furi_hal/furi_hal_serial.h
@@ -239,6 +239,12 @@ void furi_hal_serial_dma_rx_stop(FuriHalSerialHandle* handle);
*/
size_t furi_hal_serial_dma_rx(FuriHalSerialHandle* handle, uint8_t* data, size_t len);
+/** Send a break sequence (low level for the whole character duration)
+ *
+ * @param handle Serial handle
+ */
+void furi_hal_serial_send_break(FuriHalSerialHandle* handle);
+
#ifdef __cplusplus
}
#endif
diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c
index cfedb5e76..3408789dd 100644
--- a/targets/f7/furi_hal/furi_hal_usb_cdc.c
+++ b/targets/f7/furi_hal/furi_hal_usb_cdc.c
@@ -122,7 +122,7 @@ static const struct CdcConfigDescriptorSingle cdc_cfg_desc_single = {
.bFunctionLength = sizeof(struct usb_cdc_acm_desc),
.bDescriptorType = USB_DTYPE_CS_INTERFACE,
.bDescriptorSubType = USB_DTYPE_CDC_ACM,
- .bmCapabilities = 0,
+ .bmCapabilities = USB_CDC_CAP_BRK,
},
.cdc_union =
{
@@ -235,7 +235,7 @@ static const struct CdcConfigDescriptorDual
.bFunctionLength = sizeof(struct usb_cdc_acm_desc),
.bDescriptorType = USB_DTYPE_CS_INTERFACE,
.bDescriptorSubType = USB_DTYPE_CDC_ACM,
- .bmCapabilities = 0,
+ .bmCapabilities = USB_CDC_CAP_BRK,
},
.cdc_union =
{
@@ -330,7 +330,7 @@ static const struct CdcConfigDescriptorDual
.bFunctionLength = sizeof(struct usb_cdc_acm_desc),
.bDescriptorType = USB_DTYPE_CS_INTERFACE,
.bDescriptorSubType = USB_DTYPE_CDC_ACM,
- .bmCapabilities = 0,
+ .bmCapabilities = USB_CDC_CAP_BRK,
},
.cdc_union =
{
@@ -680,6 +680,13 @@ static usbd_respond cdc_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal
dev->status.data_ptr = &cdc_config[if_num];
dev->status.data_count = sizeof(cdc_config[0]);
return usbd_ack;
+ case USB_CDC_SEND_BREAK:
+ if(callbacks[if_num] != NULL) {
+ if(callbacks[if_num]->break_callback != NULL) {
+ callbacks[if_num]->break_callback(cb_ctx[if_num], req->wValue);
+ }
+ }
+ return usbd_ack;
default:
return usbd_fail;
}
diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.h b/targets/f7/furi_hal/furi_hal_usb_cdc.h
index 89b68991b..995e9009a 100644
--- a/targets/f7/furi_hal/furi_hal_usb_cdc.h
+++ b/targets/f7/furi_hal/furi_hal_usb_cdc.h
@@ -15,6 +15,7 @@ typedef struct {
void (*state_callback)(void* context, uint8_t state);
void (*ctrl_line_callback)(void* context, uint8_t state);
void (*config_callback)(void* context, struct usb_cdc_line_coding* config);
+ void (*break_callback)(void* context, uint16_t duration);
} CdcCallbacks;
void furi_hal_cdc_set_callbacks(uint8_t if_num, CdcCallbacks* cb, void* context);
diff --git a/targets/f7/stm32wb55xx_flash.ld b/targets/f7/stm32wb55xx_flash.ld
index 524da6fc3..ef61bb238 100644
--- a/targets/f7/stm32wb55xx_flash.ld
+++ b/targets/f7/stm32wb55xx_flash.ld
@@ -3,7 +3,7 @@ ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_stack_end = 0x20030000; /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
-_stack_size = 0x200; /* required amount of stack */
+_stack_size = 0x400; /* required amount of stack */
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
diff --git a/targets/f7/stm32wb55xx_ram_fw.ld b/targets/f7/stm32wb55xx_ram_fw.ld
index f0e8ad678..93579788d 100644
--- a/targets/f7/stm32wb55xx_ram_fw.ld
+++ b/targets/f7/stm32wb55xx_ram_fw.ld
@@ -3,7 +3,7 @@ ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_stack_end = 0x20030000; /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
-_stack_size = 0x200; /* required amount of stack */
+_stack_size = 0x400; /* required amount of stack */
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
diff --git a/targets/furi_hal_include/furi_hal_infrared.h b/targets/furi_hal_include/furi_hal_infrared.h
index 29f7101c1..36eaf122d 100644
--- a/targets/furi_hal_include/furi_hal_infrared.h
+++ b/targets/furi_hal_include/furi_hal_infrared.h
@@ -13,7 +13,7 @@
extern "C" {
#endif
-#define INFRARED_MAX_FREQUENCY 56000
+#define INFRARED_MAX_FREQUENCY 1000000
#define INFRARED_MIN_FREQUENCY 10000
typedef enum {