Merge branch 'dev' into release
13
.ci_files/imgs/fztools/Dockerfile
Normal file
@@ -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"]
|
||||
10
.ci_files/imgs/fztools/entrypoint.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
bash
|
||||
else
|
||||
echo "Running $1"
|
||||
set -ex
|
||||
bash -c "$1"
|
||||
fi
|
||||
|
||||
141
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
|
||||
<br><br>
|
||||
#### Known NFC post-refactor regressions list:
|
||||
- Mifare Mini clones reading is broken (original mini working fine) (OFW)
|
||||
|
||||
@@ -236,3 +236,11 @@ App(
|
||||
entry_point="get_api",
|
||||
requires=["unit_tests"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="test_pipe",
|
||||
sources=["tests/common/*.c", "tests/pipe/*.c"],
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="get_api",
|
||||
requires=["unit_tests"],
|
||||
)
|
||||
|
||||
@@ -265,6 +265,7 @@ static bool test_write(const char* file_name) {
|
||||
if(!flipper_format_file_open_always(file, file_name)) break;
|
||||
if(!flipper_format_write_header_cstr(file, test_filetype, test_version)) break;
|
||||
if(!flipper_format_write_comment_cstr(file, "This is comment")) break;
|
||||
if(!flipper_format_write_empty_line(file)) break;
|
||||
if(!flipper_format_write_string_cstr(file, test_string_key, test_string_data)) break;
|
||||
if(!flipper_format_write_int32(file, test_int_key, test_int_data, COUNT_OF(test_int_data)))
|
||||
break;
|
||||
|
||||
108
applications/debug/unit_tests/tests/furi/furi_stdio_test.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#include <furi.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include "../test.h" // IWYU pragma: keep
|
||||
|
||||
#define TAG "StdioTest"
|
||||
|
||||
#define CONTEXT_MAGIC ((void*)0xDEADBEEF)
|
||||
|
||||
// stdin
|
||||
|
||||
static char mock_in[256];
|
||||
static size_t mock_in_len, mock_in_pos;
|
||||
|
||||
static void set_mock_in(const char* str) {
|
||||
size_t len = strlen(str);
|
||||
strcpy(mock_in, str);
|
||||
mock_in_len = len;
|
||||
mock_in_pos = 0;
|
||||
}
|
||||
|
||||
static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context) {
|
||||
UNUSED(wait);
|
||||
furi_check(context == CONTEXT_MAGIC);
|
||||
size_t remaining = mock_in_len - mock_in_pos;
|
||||
size = MIN(remaining, size);
|
||||
memcpy(buffer, mock_in + mock_in_pos, size);
|
||||
mock_in_pos += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
void test_stdin(void) {
|
||||
FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback();
|
||||
furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC);
|
||||
char buf[256];
|
||||
|
||||
// plain in
|
||||
set_mock_in("Hello, World!\n");
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
mu_assert_string_eq("Hello, World!\n", buf);
|
||||
mu_assert_int_eq(EOF, getchar());
|
||||
|
||||
// ungetc
|
||||
ungetc('i', stdin);
|
||||
ungetc('H', stdin);
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
mu_assert_string_eq("Hi", buf);
|
||||
mu_assert_int_eq(EOF, getchar());
|
||||
|
||||
// ungetc + plain in
|
||||
set_mock_in(" World");
|
||||
ungetc('i', stdin);
|
||||
ungetc('H', stdin);
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
mu_assert_string_eq("Hi World", buf);
|
||||
mu_assert_int_eq(EOF, getchar());
|
||||
|
||||
// partial plain in
|
||||
set_mock_in("Hello, World!\n");
|
||||
fgets(buf, strlen("Hello") + 1, stdin);
|
||||
mu_assert_string_eq("Hello", buf);
|
||||
mu_assert_int_eq(',', getchar());
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
mu_assert_string_eq(" World!\n", buf);
|
||||
|
||||
furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC);
|
||||
}
|
||||
|
||||
// stdout
|
||||
|
||||
static FuriString* mock_out;
|
||||
FuriThreadStdoutWriteCallback original_out_cb;
|
||||
|
||||
static void mock_out_cb(const char* data, size_t size, void* context) {
|
||||
furi_check(context == CONTEXT_MAGIC);
|
||||
// there's no furi_string_cat_strn :(
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
furi_string_push_back(mock_out, data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void assert_and_clear_mock_out(const char* expected) {
|
||||
// return the original stdout callback for the duration of the check
|
||||
// if the check fails, we don't want the error to end up in our buffer,
|
||||
// we want to be able to see it!
|
||||
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
|
||||
mu_assert_string_eq(expected, furi_string_get_cstr(mock_out));
|
||||
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);
|
||||
|
||||
furi_string_reset(mock_out);
|
||||
}
|
||||
|
||||
void test_stdout(void) {
|
||||
original_out_cb = furi_thread_get_stdout_callback();
|
||||
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);
|
||||
mock_out = furi_string_alloc();
|
||||
|
||||
puts("Hello, World!");
|
||||
assert_and_clear_mock_out("Hello, World!\n");
|
||||
|
||||
printf("He");
|
||||
printf("llo!");
|
||||
fflush(stdout);
|
||||
assert_and_clear_mock_out("Hello!");
|
||||
|
||||
furi_string_free(mock_out);
|
||||
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
|
||||
}
|
||||
@@ -10,6 +10,8 @@ void test_furi_memmgr(void);
|
||||
void test_furi_event_loop(void);
|
||||
void test_errno_saving(void);
|
||||
void test_furi_primitives(void);
|
||||
void test_stdin(void);
|
||||
void test_stdout(void);
|
||||
|
||||
static int foo = 0;
|
||||
|
||||
@@ -52,6 +54,11 @@ MU_TEST(mu_test_furi_primitives) {
|
||||
test_furi_primitives();
|
||||
}
|
||||
|
||||
MU_TEST(mu_test_stdio) {
|
||||
test_stdin();
|
||||
test_stdout();
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_suite) {
|
||||
MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
|
||||
MU_RUN_TEST(test_check);
|
||||
@@ -61,6 +68,7 @@ MU_TEST_SUITE(test_suite) {
|
||||
MU_RUN_TEST(mu_test_furi_pubsub);
|
||||
MU_RUN_TEST(mu_test_furi_memmgr);
|
||||
MU_RUN_TEST(mu_test_furi_event_loop);
|
||||
MU_RUN_TEST(mu_test_stdio);
|
||||
MU_RUN_TEST(mu_test_errno_saving);
|
||||
MU_RUN_TEST(mu_test_furi_primitives);
|
||||
}
|
||||
|
||||
153
applications/debug/unit_tests/tests/pipe/pipe_test.c
Normal file
@@ -0,0 +1,153 @@
|
||||
#include "../test.h" // IWYU pragma: keep
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/toolbox/pipe.h>
|
||||
|
||||
#define PIPE_SIZE 128U
|
||||
#define PIPE_TRG_LEVEL 1U
|
||||
|
||||
MU_TEST(pipe_test_trivial) {
|
||||
PipeSideBundle bundle = pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL);
|
||||
PipeSide* alice = bundle.alices_side;
|
||||
PipeSide* bob = bundle.bobs_side;
|
||||
|
||||
mu_assert_int_eq(PipeRoleAlice, pipe_role(alice));
|
||||
mu_assert_int_eq(PipeRoleBob, pipe_role(bob));
|
||||
mu_assert_int_eq(PipeStateOpen, pipe_state(alice));
|
||||
mu_assert_int_eq(PipeStateOpen, pipe_state(bob));
|
||||
|
||||
mu_assert_int_eq(PIPE_SIZE, pipe_spaces_available(alice));
|
||||
mu_assert_int_eq(PIPE_SIZE, pipe_spaces_available(bob));
|
||||
mu_assert_int_eq(0, pipe_bytes_available(alice));
|
||||
mu_assert_int_eq(0, pipe_bytes_available(bob));
|
||||
|
||||
for(uint8_t i = 0;; ++i) {
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(alice));
|
||||
mu_assert_int_eq(i, pipe_bytes_available(bob));
|
||||
|
||||
if(pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(bob));
|
||||
mu_assert_int_eq(i, pipe_bytes_available(alice));
|
||||
|
||||
if(pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pipe_free(alice);
|
||||
mu_assert_int_eq(PipeStateBroken, pipe_state(bob));
|
||||
|
||||
for(uint8_t i = 0;; ++i) {
|
||||
mu_assert_int_eq(PIPE_SIZE - i, pipe_bytes_available(bob));
|
||||
|
||||
uint8_t value;
|
||||
if(pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
|
||||
break;
|
||||
}
|
||||
|
||||
mu_assert_int_eq(i, value);
|
||||
}
|
||||
|
||||
pipe_free(bob);
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
TestFlagDataArrived = 1 << 0,
|
||||
TestFlagSpaceFreed = 1 << 1,
|
||||
TestFlagBecameBroken = 1 << 2,
|
||||
} TestFlag;
|
||||
|
||||
typedef struct {
|
||||
TestFlag flag;
|
||||
FuriEventLoop* event_loop;
|
||||
} AncillaryThreadContext;
|
||||
|
||||
static void on_data_arrived(PipeSide* pipe, void* context) {
|
||||
AncillaryThreadContext* ctx = context;
|
||||
ctx->flag |= TestFlagDataArrived;
|
||||
uint8_t buffer[PIPE_SIZE];
|
||||
size_t size = pipe_receive(pipe, buffer, sizeof(buffer), 0);
|
||||
pipe_send(pipe, buffer, size, 0);
|
||||
}
|
||||
|
||||
static void on_space_freed(PipeSide* pipe, void* context) {
|
||||
AncillaryThreadContext* ctx = context;
|
||||
ctx->flag |= TestFlagSpaceFreed;
|
||||
const char* message = "Hi!";
|
||||
pipe_send(pipe, message, strlen(message), 0);
|
||||
}
|
||||
|
||||
static void on_became_broken(PipeSide* pipe, void* context) {
|
||||
UNUSED(pipe);
|
||||
AncillaryThreadContext* ctx = context;
|
||||
ctx->flag |= TestFlagBecameBroken;
|
||||
furi_event_loop_stop(ctx->event_loop);
|
||||
}
|
||||
|
||||
static int32_t ancillary_thread(void* context) {
|
||||
PipeSide* pipe = context;
|
||||
AncillaryThreadContext thread_ctx = {
|
||||
.flag = 0,
|
||||
.event_loop = furi_event_loop_alloc(),
|
||||
};
|
||||
|
||||
pipe_attach_to_event_loop(pipe, thread_ctx.event_loop);
|
||||
pipe_set_callback_context(pipe, &thread_ctx);
|
||||
pipe_set_data_arrived_callback(pipe, on_data_arrived, 0);
|
||||
pipe_set_space_freed_callback(pipe, on_space_freed, FuriEventLoopEventFlagEdge);
|
||||
pipe_set_broken_callback(pipe, on_became_broken, 0);
|
||||
|
||||
furi_event_loop_run(thread_ctx.event_loop);
|
||||
|
||||
pipe_detach_from_event_loop(pipe);
|
||||
pipe_free(pipe);
|
||||
furi_event_loop_free(thread_ctx.event_loop);
|
||||
return thread_ctx.flag;
|
||||
}
|
||||
|
||||
MU_TEST(pipe_test_event_loop) {
|
||||
PipeSideBundle bundle = pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL);
|
||||
PipeSide* alice = bundle.alices_side;
|
||||
PipeSide* bob = bundle.bobs_side;
|
||||
|
||||
FuriThread* thread = furi_thread_alloc_ex("PipeTestAnc", 2048, ancillary_thread, bob);
|
||||
furi_thread_start(thread);
|
||||
|
||||
const char* message = "Hello!";
|
||||
pipe_send(alice, message, strlen(message), FuriWaitForever);
|
||||
|
||||
char buffer_1[16];
|
||||
size_t size = pipe_receive(alice, buffer_1, sizeof(buffer_1), FuriWaitForever);
|
||||
buffer_1[size] = 0;
|
||||
|
||||
char buffer_2[16];
|
||||
const char* expected_reply = "Hi!";
|
||||
size = pipe_receive(alice, buffer_2, sizeof(buffer_2), FuriWaitForever);
|
||||
buffer_2[size] = 0;
|
||||
|
||||
pipe_free(alice);
|
||||
furi_thread_join(thread);
|
||||
|
||||
mu_assert_string_eq(message, buffer_1);
|
||||
mu_assert_string_eq(expected_reply, buffer_2);
|
||||
mu_assert_int_eq(
|
||||
TestFlagDataArrived | TestFlagSpaceFreed | TestFlagBecameBroken,
|
||||
furi_thread_get_return_code(thread));
|
||||
|
||||
furi_thread_free(thread);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_pipe) {
|
||||
MU_RUN_TEST(pipe_test_trivial);
|
||||
MU_RUN_TEST(pipe_test_event_loop);
|
||||
}
|
||||
|
||||
int run_minunit_test_pipe(void) {
|
||||
MU_RUN_SUITE(test_pipe);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
|
||||
TEST_API_DEFINE(run_minunit_test_pipe)
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -79,6 +79,19 @@ static void infrared_rpc_command_callback(const RpcAppSystemEvent* event, void*
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressIndex);
|
||||
}
|
||||
} else if(event->type == RpcAppEventTypeButtonPressRelease) {
|
||||
furi_assert(
|
||||
event->data.type == RpcAppSystemEventDataTypeString ||
|
||||
event->data.type == RpcAppSystemEventDataTypeInt32);
|
||||
if(event->data.type == RpcAppSystemEventDataTypeString) {
|
||||
furi_string_set(infrared->button_name, event->data.string);
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressReleaseName);
|
||||
} else {
|
||||
infrared->app_state.current_button_index = event->data.i32;
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressReleaseIndex);
|
||||
}
|
||||
} else if(event->type == RpcAppEventTypeButtonRelease) {
|
||||
view_dispatcher_send_custom_event(
|
||||
infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonRelease);
|
||||
@@ -402,6 +415,26 @@ void infrared_tx_stop(InfraredApp* infrared) {
|
||||
infrared->app_state.last_transmit_time = furi_get_tick();
|
||||
}
|
||||
|
||||
void infrared_tx_send_once(InfraredApp* infrared) {
|
||||
if(infrared->app_state.is_transmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
dolphin_deed(DolphinDeedIrSend);
|
||||
infrared_signal_transmit(infrared->current_signal);
|
||||
}
|
||||
|
||||
InfraredErrorCode infrared_tx_send_once_button_index(InfraredApp* infrared, size_t button_index) {
|
||||
furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote));
|
||||
|
||||
InfraredErrorCode error = infrared_remote_load_signal(
|
||||
infrared->remote, infrared->current_signal, infrared->app_state.current_button_index);
|
||||
if(!INFRARED_ERROR_PRESENT(error)) {
|
||||
infrared_tx_send_once(infrared);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) {
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewLoading);
|
||||
furi_thread_set_callback(infrared->task_thread, callback);
|
||||
|
||||
@@ -218,6 +218,20 @@ InfraredErrorCode infrared_tx_start_button_index(InfraredApp* infrared, size_t b
|
||||
*/
|
||||
void infrared_tx_stop(InfraredApp* infrared);
|
||||
|
||||
/**
|
||||
* @brief Transmit the currently loaded signal once.
|
||||
*
|
||||
* @param[in,out] infrared pointer to the application instance.
|
||||
*/
|
||||
void infrared_tx_send_once(InfraredApp* infrared);
|
||||
|
||||
/**
|
||||
* @brief Load the signal under the given index and transmit it once.
|
||||
*
|
||||
* @param[in,out] infrared pointer to the application instance.
|
||||
*/
|
||||
InfraredErrorCode infrared_tx_send_once_button_index(InfraredApp* infrared, size_t button_index);
|
||||
|
||||
/**
|
||||
* @brief Start a blocking task in a separate thread.
|
||||
*
|
||||
|
||||
@@ -21,6 +21,8 @@ enum InfraredCustomEventType {
|
||||
InfraredCustomEventTypeRpcButtonPressName,
|
||||
InfraredCustomEventTypeRpcButtonPressIndex,
|
||||
InfraredCustomEventTypeRpcButtonRelease,
|
||||
InfraredCustomEventTypeRpcButtonPressReleaseName,
|
||||
InfraredCustomEventTypeRpcButtonPressReleaseIndex,
|
||||
InfraredCustomEventTypeRpcSessionClose,
|
||||
|
||||
InfraredCustomEventTypeGpioTxPinChanged,
|
||||
|
||||
@@ -124,6 +124,49 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
rpc_system_app_confirm(infrared->rpc_ctx, result);
|
||||
|
||||
} else if(
|
||||
event.event == InfraredCustomEventTypeRpcButtonPressReleaseName ||
|
||||
event.event == InfraredCustomEventTypeRpcButtonPressReleaseIndex) {
|
||||
bool result = false;
|
||||
|
||||
// Send the signal once and stop
|
||||
if(rpc_state == InfraredRpcStateLoaded) {
|
||||
if(event.event == InfraredCustomEventTypeRpcButtonPressReleaseName) {
|
||||
const char* button_name = furi_string_get_cstr(infrared->button_name);
|
||||
size_t index;
|
||||
const bool index_found =
|
||||
infrared_remote_get_signal_index(infrared->remote, button_name, &index);
|
||||
app_state->current_button_index = index_found ? (signed)index :
|
||||
InfraredButtonIndexNone;
|
||||
FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name);
|
||||
} else {
|
||||
FURI_LOG_D(
|
||||
TAG, "Sending signal with index \"%ld\"", app_state->current_button_index);
|
||||
}
|
||||
if(infrared->app_state.current_button_index != InfraredButtonIndexNone) {
|
||||
InfraredErrorCode error = infrared_tx_send_once_button_index(
|
||||
infrared, app_state->current_button_index);
|
||||
if(!INFRARED_ERROR_PRESENT(error)) {
|
||||
const char* remote_name = infrared_remote_get_name(infrared->remote);
|
||||
infrared_text_store_set(infrared, 0, "emulating\n%s", remote_name);
|
||||
|
||||
infrared_scene_rpc_show(infrared);
|
||||
result = true;
|
||||
} else {
|
||||
rpc_system_app_set_error_code(
|
||||
infrared->rpc_ctx, RpcAppSystemErrorCodeInternalParse);
|
||||
rpc_system_app_set_error_text(
|
||||
infrared->rpc_ctx, "Cannot load button data");
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(result) {
|
||||
scene_manager_set_scene_state(
|
||||
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
|
||||
}
|
||||
rpc_system_app_confirm(infrared->rpc_ctx, result);
|
||||
} else if(
|
||||
event.event == InfraredCustomEventTypeRpcExit ||
|
||||
event.event == InfraredCustomEventTypeRpcSessionClose ||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -8,12 +8,35 @@
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#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,11 +125,19 @@ 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++) {
|
||||
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);
|
||||
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
} 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);
|
||||
if(error != MfClassicErrorNone) {
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
desktop_lock(desktop);
|
||||
// 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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <update_util/update_operation.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <loader/loader.h>
|
||||
|
||||
#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;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <power/power_service/power_settings.h>
|
||||
#include <core/pubsub.h>
|
||||
|
||||
#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
|
||||
|
||||
@@ -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;
|
||||
|
||||
77
applications/services/power/power_service/power_settings.c
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "power_settings.h"
|
||||
#include "power_settings_filename.h"
|
||||
|
||||
#include <saved_struct.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#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");
|
||||
}
|
||||
}
|
||||
16
applications/services/power/power_service/power_settings.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define POWER_SETTINGS_FILE_NAME ".power.settings"
|
||||
@@ -258,6 +258,41 @@ static void rpc_system_app_button_release(const PB_Main* request, void* context)
|
||||
}
|
||||
}
|
||||
|
||||
static void rpc_system_app_button_press_release(const PB_Main* request, void* context) {
|
||||
furi_assert(request);
|
||||
furi_assert(request->which_content == PB_Main_app_button_press_release_request_tag);
|
||||
|
||||
RpcAppSystem* rpc_app = context;
|
||||
furi_assert(rpc_app);
|
||||
|
||||
if(rpc_app->callback) {
|
||||
FURI_LOG_D(TAG, "ButtonPressRelease");
|
||||
|
||||
RpcAppSystemEvent event;
|
||||
event.type = RpcAppEventTypeButtonPressRelease;
|
||||
|
||||
if(strlen(request->content.app_button_press_release_request.args) != 0) {
|
||||
event.data.type = RpcAppSystemEventDataTypeString;
|
||||
event.data.string = request->content.app_button_press_release_request.args;
|
||||
} else {
|
||||
event.data.type = RpcAppSystemEventDataTypeInt32;
|
||||
event.data.i32 = request->content.app_button_press_release_request.index;
|
||||
}
|
||||
|
||||
rpc_system_app_error_reset(rpc_app);
|
||||
rpc_system_app_set_last_command(rpc_app, request->command_id, &event);
|
||||
|
||||
rpc_app->callback(&event, rpc_app->callback_context);
|
||||
|
||||
} else {
|
||||
rpc_system_app_send_error_response(
|
||||
rpc_app,
|
||||
request->command_id,
|
||||
PB_CommandStatus_ERROR_APP_NOT_RUNNING,
|
||||
"ButtonPressRelease");
|
||||
}
|
||||
}
|
||||
|
||||
static void rpc_system_app_get_error_process(const PB_Main* request, void* context) {
|
||||
furi_assert(request);
|
||||
furi_assert(request->which_content == PB_Main_app_get_error_request_tag);
|
||||
@@ -332,6 +367,7 @@ void rpc_system_app_confirm(RpcAppSystem* rpc_app, bool result) {
|
||||
rpc_app->last_event_type == RpcAppEventTypeLoadFile ||
|
||||
rpc_app->last_event_type == RpcAppEventTypeButtonPress ||
|
||||
rpc_app->last_event_type == RpcAppEventTypeButtonRelease ||
|
||||
rpc_app->last_event_type == RpcAppEventTypeButtonPressRelease ||
|
||||
rpc_app->last_event_type == RpcAppEventTypeDataExchange);
|
||||
|
||||
const uint32_t last_command_id = rpc_app->last_command_id;
|
||||
@@ -432,6 +468,9 @@ void* rpc_system_app_alloc(RpcSession* session) {
|
||||
rpc_handler.message_handler = rpc_system_app_button_release;
|
||||
rpc_add_handler(session, PB_Main_app_button_release_request_tag, &rpc_handler);
|
||||
|
||||
rpc_handler.message_handler = rpc_system_app_button_press_release;
|
||||
rpc_add_handler(session, PB_Main_app_button_press_release_request_tag, &rpc_handler);
|
||||
|
||||
rpc_handler.message_handler = rpc_system_app_get_error_process;
|
||||
rpc_add_handler(session, PB_Main_app_get_error_request_tag, &rpc_handler);
|
||||
|
||||
|
||||
@@ -90,6 +90,13 @@ typedef enum {
|
||||
* all activities to be conducted while a button is being pressed.
|
||||
*/
|
||||
RpcAppEventTypeButtonRelease,
|
||||
/**
|
||||
* @brief The client has informed the application that a button has been pressed and released.
|
||||
*
|
||||
* This command's meaning is application-specific, e.g. to perform an action
|
||||
* once without repeating it.
|
||||
*/
|
||||
RpcAppEventTypeButtonPressRelease,
|
||||
/**
|
||||
* @brief The client has sent a byte array of arbitrary size.
|
||||
*
|
||||
@@ -162,6 +169,7 @@ void rpc_system_app_send_exited(RpcAppSystem* rpc_app);
|
||||
* - RpcAppEventTypeLoadFile
|
||||
* - RpcAppEventTypeButtonPress
|
||||
* - RpcAppEventTypeButtonRelease
|
||||
* - RpcAppEventTypeButtonPressRelease
|
||||
* - RpcAppEventTypeDataExchange
|
||||
*
|
||||
* Not confirming these events will result in a client-side timeout.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <furi.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <power/power_service/power_settings.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
@@ -10,11 +11,13 @@
|
||||
|
||||
#include "views/battery_info.h"
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
|
||||
#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 {
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
#include "../power_settings_app.h"
|
||||
#include <lib/toolbox/value_index.h>
|
||||
|
||||
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 ?
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ packages:
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
cross-spawn@7.0.3:
|
||||
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
@@ -240,7 +240,7 @@ snapshots:
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
cross-spawn@7.0.3:
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
shebang-command: 2.0.0
|
||||
@@ -256,7 +256,7 @@ snapshots:
|
||||
|
||||
foreground-child@3.3.0:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@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",
|
||||
|
||||
@@ -8,13 +8,6 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
prompts:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
serialport:
|
||||
specifier: ^12.0.0
|
||||
version: 12.0.0
|
||||
devDependencies:
|
||||
esbuild:
|
||||
specifier: ^0.24.0
|
||||
version: 0.24.0
|
||||
@@ -24,6 +17,12 @@ importers:
|
||||
json5:
|
||||
specifier: ^2.2.3
|
||||
version: 2.2.3
|
||||
prompts:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
serialport:
|
||||
specifier: ^12.0.0
|
||||
version: 12.0.0
|
||||
typedoc:
|
||||
specifier: ^0.26.10
|
||||
version: 0.26.10(typescript@5.6.3)
|
||||
|
||||
@@ -91,9 +91,21 @@ async function build(config) {
|
||||
|
||||
async function upload(config) {
|
||||
const appFile = fs.readFileSync(config.input, "utf8");
|
||||
const flippers = (await SerialPort.list()).filter(x => x.serialNumber?.startsWith("flip_"));
|
||||
const serialPorts = await SerialPort.list();
|
||||
|
||||
if (!flippers) {
|
||||
let flippers = serialPorts
|
||||
.filter(x => x.serialNumber?.startsWith("flip_"))
|
||||
.map(x => ({ path: x.path, name: x.serialNumber.replace("flip_", "") }));
|
||||
|
||||
if (!flippers.length) {
|
||||
// some Windows installations don't report the serial number correctly;
|
||||
// filter by STM VCP VID:PID instead
|
||||
flippers = serialPorts
|
||||
.filter(x => x?.vendorId === "0483" && x?.productId === "5740")
|
||||
.map(x => ({ path: x.path, name: x.path }));
|
||||
}
|
||||
|
||||
if (!flippers.length) {
|
||||
console.error("No Flippers found");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png
vendored
Executable file
|
After Width: | Height: | Size: 858 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png
vendored
Executable file
|
After Width: | Height: | Size: 855 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png
vendored
Executable file
|
After Width: | Height: | Size: 872 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png
vendored
Executable file
|
After Width: | Height: | Size: 861 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png
vendored
Executable file
|
After Width: | Height: | Size: 853 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png
vendored
Executable file
|
After Width: | Height: | Size: 851 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png
vendored
Executable file
|
After Width: | Height: | Size: 852 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png
vendored
Executable file
|
After Width: | Height: | Size: 856 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png
vendored
Executable file
|
After Width: | Height: | Size: 850 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png
vendored
Executable file
|
After Width: | Height: | Size: 851 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png
vendored
Executable file
|
After Width: | Height: | Size: 860 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png
vendored
Executable file
|
After Width: | Height: | Size: 857 B |
BIN
assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png
vendored
Executable file
|
After Width: | Height: | Size: 863 B |
23
assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
Filetype: Flipper Animation
|
||||
Version: 1
|
||||
|
||||
Width: 128
|
||||
Height: 64
|
||||
Passive frames: 10
|
||||
Active frames: 18
|
||||
Frames order: 0 1 2 1 0 1 2 1 0 1 2 3 4 5 6 5 4 7 2 8 9 10 11 10 9 10 11 12
|
||||
Active cycles: 1
|
||||
Frame rate: 2
|
||||
Duration: 3600
|
||||
Active cooldown: 7
|
||||
|
||||
Bubble slots: 1
|
||||
|
||||
Slot: 0
|
||||
X: 11
|
||||
Y: 19
|
||||
Text: HAPPY\nHOLIDAYS!
|
||||
AlignH: Right
|
||||
AlignV: Center
|
||||
StartFrame: 22
|
||||
EndFrame: 27
|
||||
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png
vendored
Executable file
|
After Width: | Height: | Size: 820 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png
vendored
Executable file
|
After Width: | Height: | Size: 881 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png
vendored
Executable file
|
After Width: | Height: | Size: 788 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png
vendored
Executable file
|
After Width: | Height: | Size: 816 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png
vendored
Executable file
|
After Width: | Height: | Size: 864 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png
vendored
Executable file
|
After Width: | Height: | Size: 798 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png
vendored
Executable file
|
After Width: | Height: | Size: 813 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png
vendored
Executable file
|
After Width: | Height: | Size: 879 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png
vendored
Executable file
|
After Width: | Height: | Size: 855 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png
vendored
Executable file
|
After Width: | Height: | Size: 772 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png
vendored
Executable file
|
After Width: | Height: | Size: 817 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png
vendored
Executable file
|
After Width: | Height: | Size: 867 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png
vendored
Executable file
|
After Width: | Height: | Size: 866 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png
vendored
Executable file
|
After Width: | Height: | Size: 809 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png
vendored
Executable file
|
After Width: | Height: | Size: 795 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png
vendored
Executable file
|
After Width: | Height: | Size: 870 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png
vendored
Executable file
|
After Width: | Height: | Size: 852 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png
vendored
Executable file
|
After Width: | Height: | Size: 805 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png
vendored
Executable file
|
After Width: | Height: | Size: 858 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png
vendored
Executable file
|
After Width: | Height: | Size: 830 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png
vendored
Executable file
|
After Width: | Height: | Size: 828 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png
vendored
Executable file
|
After Width: | Height: | Size: 585 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png
vendored
Executable file
|
After Width: | Height: | Size: 431 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png
vendored
Executable file
|
After Width: | Height: | Size: 812 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png
vendored
Executable file
|
After Width: | Height: | Size: 281 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png
vendored
Executable file
|
After Width: | Height: | Size: 270 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png
vendored
Executable file
|
After Width: | Height: | Size: 236 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png
vendored
Executable file
|
After Width: | Height: | Size: 485 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png
vendored
Executable file
|
After Width: | Height: | Size: 771 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png
vendored
Executable file
|
After Width: | Height: | Size: 887 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png
vendored
Executable file
|
After Width: | Height: | Size: 809 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png
vendored
Executable file
|
After Width: | Height: | Size: 890 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png
vendored
Executable file
|
After Width: | Height: | Size: 819 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png
vendored
Executable file
|
After Width: | Height: | Size: 799 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png
vendored
Executable file
|
After Width: | Height: | Size: 817 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png
vendored
Executable file
|
After Width: | Height: | Size: 875 B |
BIN
assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png
vendored
Executable file
|
After Width: | Height: | Size: 823 B |
23
assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt
vendored
Executable file
@@ -0,0 +1,23 @@
|
||||
Filetype: Flipper Animation
|
||||
Version: 1
|
||||
|
||||
Width: 128
|
||||
Height: 64
|
||||
Passive frames: 18
|
||||
Active frames: 19
|
||||
Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
||||
Active cycles: 1
|
||||
Frame rate: 2
|
||||
Duration: 3600
|
||||
Active cooldown: 7
|
||||
|
||||
Bubble slots: 1
|
||||
|
||||
Slot: 0
|
||||
X: 21
|
||||
Y: 25
|
||||
Text: AAAAaAAAAHHh!!
|
||||
AlignH: Right
|
||||
AlignV: Bottom
|
||||
StartFrame: 30
|
||||
EndFrame: 32
|
||||