1
mirror of https://github.com/DarkFlippers/unleashed-firmware.git synced 2025-12-12 04:34:43 +04:00

Merge branch 'dev' into release

This commit is contained in:
MX
2025-01-23 17:57:13 +03:00
143 changed files with 2321 additions and 435 deletions

View 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"]

View File

@@ -0,0 +1,10 @@
#!/bin/bash
if [ -z "$1" ]; then
bash
else
echo "Running $1"
set -ex
bash -c "$1"
fi

View File

@@ -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)

View File

@@ -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"],
)

View File

@@ -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;

View 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);
}

View File

@@ -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);
}

View 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)

View File

@@ -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));

View File

@@ -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);

View File

@@ -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.
*

View File

@@ -21,6 +21,8 @@ enum InfraredCustomEventType {
InfraredCustomEventTypeRpcButtonPressName,
InfraredCustomEventTypeRpcButtonPressIndex,
InfraredCustomEventTypeRpcButtonRelease,
InfraredCustomEventTypeRpcButtonPressReleaseName,
InfraredCustomEventTypeRpcButtonPressReleaseIndex,
InfraredCustomEventTypeRpcSessionClose,
InfraredCustomEventTypeGpioTxPinChanged,

View File

@@ -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 ||

View File

@@ -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,

View File

@@ -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,

View File

@@ -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, &sector_num, &key, &key_type)) {
mfc_event->data->read_sector_request_data.sector_num = sector_num;
mfc_event->data->read_sector_request_data.key = key;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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, &sector_num, &key, &key_type)) {
mfc_event->data->read_sector_request_data.sector_num = sector_num;
mfc_event->data->read_sector_request_data.key = key;

View File

@@ -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,

View File

@@ -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

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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);
};

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View 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");
}
}

View 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

View File

@@ -0,0 +1,3 @@
#pragma once
#define POWER_SETTINGS_FILE_NAME ".power.settings"

View File

@@ -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);

View File

@@ -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.

View File

@@ -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",

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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 ?
}

View File

@@ -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: {}

View File

@@ -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",

View File

@@ -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)

View File

@@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 863 B

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 B

View 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

Some files were not shown because too many files have changed in this diff Show More