Merge branch 'dev' into astra/3934-alarm-improvements

This commit is contained in:
Astra
2024-12-23 14:27:45 +09:00
committed by GitHub
115 changed files with 3535 additions and 1409 deletions

View File

@@ -12,6 +12,7 @@ env:
DEFAULT_TARGET: f7
FBT_TOOLCHAIN_PATH: /runner/_work
FBT_GIT_SUBMODULE_SHALLOW: 1
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
main:

View File

@@ -6,6 +6,7 @@ on:
env:
FBT_TOOLCHAIN_PATH: /runner/_work
FBT_GIT_SUBMODULE_SHALLOW: 1
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
compact:

View File

@@ -9,6 +9,7 @@ on:
env:
TARGETS: f7
DEFAULT_TARGET: f7
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
check-secret:

View File

@@ -7,6 +7,7 @@ on:
env:
FBT_TOOLCHAIN_PATH: /runner/_work
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
merge_report:

View File

@@ -11,6 +11,7 @@ env:
DEFAULT_TARGET: f7
FBT_TOOLCHAIN_PATH: /runner/_work
FBT_GIT_SUBMODULE_SHALLOW: 1
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
analyse_c_cpp:

View File

@@ -5,54 +5,39 @@ on:
env:
TARGETS: f7
DEFAULT_TARGET: f7
FBT_TOOLCHAIN_PATH: /opt
FBT_TOOLCHAIN_PATH: /opt/
FBT_GIT_SUBMODULE_SHALLOW: 1
jobs:
run_units_on_bench:
runs-on: [self-hosted, FlipperZeroUnitTest]
runs-on: [ self-hosted, FlipperZeroTest ]
steps:
- name: 'Wipe workspace'
run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ github.event.pull_request.head.sha }}
- name: 'Get flipper from device manager (mock)'
id: device
run: |
echo "flipper=auto" >> $GITHUB_OUTPUT
- name: 'Flash unit tests firmware'
id: flashing
if: success()
timeout-minutes: 10
run: |
./fbt resources firmware_latest flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1
- name: 'Wait for flipper and format ext'
id: format_ext
if: steps.flashing.outcome == 'success'
timeout-minutes: 5
timeout-minutes: 20
run: |
source scripts/toolchain/fbtenv.sh
python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=120 await_flipper
python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext
./fbt resources firmware_latest flash LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1
- name: 'Copy assets and unit data, reboot and wait for flipper'
id: copy
if: steps.format_ext.outcome == 'success'
if: steps.flashing.outcome == 'success'
timeout-minutes: 7
run: |
source scripts/toolchain/fbtenv.sh
python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=15 await_flipper
rm -rf build/latest/resources/dolphin
python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send build/latest/resources /ext
python3 scripts/power.py -p ${{steps.device.outputs.flipper}} reboot
python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=15 await_flipper
python3 scripts/testops.py -t=15 await_flipper
python3 scripts/storage.py -f send build/latest/resources /ext
python3 scripts/storage.py -f send /region_data /ext/.int/.region_data
python3 scripts/power.py reboot
python3 scripts/testops.py -t=30 await_flipper
- name: 'Run units and validate results'
id: run_units
@@ -60,9 +45,16 @@ jobs:
timeout-minutes: 7
run: |
source scripts/toolchain/fbtenv.sh
python3 scripts/testops.py run_units -p ${{steps.device.outputs.flipper}}
python3 scripts/testops.py run_units
- name: 'Upload test results'
if: failure() && steps.flashing.outcome == 'success' && steps.run_units.outcome != 'skipped'
uses: actions/upload-artifact@v4
with:
name: unit-tests_output
path: unit_tests*.txt
- name: 'Check GDB output'
if: failure() && steps.flashing.outcome == 'success'
run: |
./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1
./fbt gdb_trace_all LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1

View File

@@ -1,39 +1,31 @@
name: 'Updater test'
on:
pull_request:
env:
TARGETS: f7
DEFAULT_TARGET: f7
FBT_TOOLCHAIN_PATH: /opt
FBT_TOOLCHAIN_PATH: /opt/
FBT_GIT_SUBMODULE_SHALLOW: 1
jobs:
test_updater_on_bench:
runs-on: [self-hosted, FlipperZeroUpdaterTest]
runs-on: [self-hosted, FlipperZeroTest ]
steps:
- name: 'Wipe workspace'
run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: false
ref: ${{ github.event.pull_request.head.sha }}
- name: 'Get flipper from device manager (mock)'
id: device
run: |
echo "flipper=auto" >> $GITHUB_OUTPUT
echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT
- name: 'Flashing target firmware'
id: first_full_flash
timeout-minutes: 10
timeout-minutes: 20
run: |
source scripts/toolchain/fbtenv.sh
./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1
python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=180 await_flipper
python3 scripts/testops.py -t=180 await_flipper
./fbt flash_usb_full FORCE=1
- name: 'Validating updater'
id: second_full_flash
@@ -41,33 +33,7 @@ jobs:
if: success()
run: |
source scripts/toolchain/fbtenv.sh
./fbt flash_usb PORT=${{steps.device.outputs.flipper}} FORCE=1
python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=180 await_flipper
python3 scripts/testops.py -t=180 await_flipper
./fbt flash_usb FORCE=1
python3 scripts/testops.py -t=180 await_flipper
- name: 'Get last release tag'
id: release_tag
if: failure()
run: |
echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT
- name: 'Wipe workspace'
run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
- name: 'Checkout latest release'
uses: actions/checkout@v4
if: failure()
with:
fetch-depth: 1
ref: ${{ steps.release_tag.outputs.tag }}
- name: 'Flash last release'
if: failure()
run: |
./fbt flash SWD_TRANSPORT_SERIAL=${{steps.device.outputs.stlink}} FORCE=1
- name: 'Wait for flipper and format ext'
if: failure()
run: |
source scripts/toolchain/fbtenv.sh
python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=180 await_flipper
python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext

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

@@ -88,6 +88,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);
@@ -411,6 +424,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

@@ -4650,3 +4650,42 @@ type: parsed
protocol: NECext
address: 7F 01 00 00
command: 69 96 00 00
#
# Model : NAD DR2 remote for NAD D7050 and D3020
#
name: Power
type: parsed
protocol: NECext
address: 87 7C 00 00
command: 25 DA 00 00
#
name: Vol_up
type: parsed
protocol: NECext
address: 87 7C 00 00
command: 88 77 00 00
#
name: Vol_dn
type: parsed
protocol: NECext
address: 87 7C 00 00
command: 8C 73 00 00
#
name: Mute
type: parsed
protocol: NECext
address: 87 7C 00 00
command: 94 6B 00 00
#
name: Next
type: parsed
protocol: NECext
address: 87 7C 00 00
command: 1A E5 00 00
#
name: Prev
type: parsed
protocol: NECext
address: 87 7C 00 00
command: 1D E2 00 00
#

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

@@ -101,7 +101,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"},
@@ -131,6 +132,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

@@ -7,13 +7,36 @@
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include <flipper_format/flipper_format.h>
#define TAG "Skylanders"
#define TAG "Skylanders"
#define POLY UINT64_C(0x42f0e1eba9ea3693)
#define TOP UINT64_C(0x800000000000)
#define UID_LEN 4
#define KEY_MASK 0xFFFFFFFFFFFF
static const uint64_t skylanders_key = 0x4b0b20107ccb;
static const char* nfc_resources_header = "Flipper NFC resources";
static const uint32_t nfc_resources_file_version = 1;
uint64_t crc64_like(uint64_t result, uint8_t sector) {
result ^= (uint64_t)sector << 40;
for(int i = 0; i < 8; i++) {
result = (result & TOP) ? (result << 1) ^ POLY : result << 1;
}
return result;
}
uint64_t taghash(uint32_t uid) {
uint64_t result = 0x9AE903260CC4;
uint8_t uidBytes[UID_LEN] = {0};
memcpy(uidBytes, &uid, UID_LEN);
for(int i = 0; i < UID_LEN; i++) {
result = crc64_like(result, uidBytes[i]);
}
return result;
}
static bool skylanders_search_data(
Storage* storage,
const char* file_name,
@@ -88,6 +111,12 @@ static bool skylanders_read(Nfc* nfc, NfcDevice* device) {
MfClassicData* data = mf_classic_alloc();
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
size_t* uid_len = 0;
const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len);
uint32_t uid = 0;
memcpy(&uid, uid_bytes, sizeof(uid));
uint64_t hash = taghash(uid);
do {
MfClassicType type = MfClassicType1k;
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
@@ -96,10 +125,18 @@ static bool skylanders_read(Nfc* nfc, NfcDevice* device) {
data->type = type;
MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_b[i].data);
FURI_BIT_SET(keys.key_b_mask, i);
if(i == 0) {
bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
} else {
uint64_t sectorhash = crc64_like(hash, i);
uint64_t key = sectorhash & KEY_MASK;
uint8_t* keyBytes = (uint8_t*)&key;
memcpy(keys.key_a[i].data, keyBytes, sizeof(MfClassicKey));
FURI_BIT_SET(keys.key_a_mask, i);
memset(keys.key_b[i].data, 0, sizeof(MfClassicKey));
FURI_BIT_SET(keys.key_b_mask, i);
}
}
error = mf_classic_poller_sync_read(nfc, &keys, data);
@@ -134,7 +171,7 @@ static bool skylanders_parse(const NfcDevice* device, FuriString* parsed_data) {
uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
if(key != skylanders_key) break;
const uint16_t id = (uint16_t)*data->block[1].data;
const uint16_t id = data->block[1].data[1] << 8 | data->block[1].data[0];
if(id == 0) break;
Storage* storage = furi_record_open(RECORD_STORAGE);

File diff suppressed because it is too large Load Diff

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

@@ -49,6 +49,7 @@ typedef enum {
SubGhzCustomEventSceneRpcLoad,
SubGhzCustomEventSceneRpcButtonPress,
SubGhzCustomEventSceneRpcButtonRelease,
SubGhzCustomEventSceneRpcButtonPressRelease,
SubGhzCustomEventSceneRpcSessionClose,
SubGhzCustomEventViewReceiverOK,

View File

@@ -85,6 +85,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

@@ -43,6 +43,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

@@ -431,9 +431,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);
@@ -447,7 +447,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);
}
@@ -461,9 +461,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

@@ -242,6 +242,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 +272,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 +316,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

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

@@ -20,8 +20,11 @@ static const NotificationSequence sequence_note_c = {
NULL,
};
#define CONTRAST_COUNT 11
#define CONTRAST_COUNT 17
const char* const contrast_text[CONTRAST_COUNT] = {
"-8",
"-7",
"-6",
"-5",
"-4",
"-3",
@@ -33,8 +36,14 @@ const char* const contrast_text[CONTRAST_COUNT] = {
"+3",
"+4",
"+5",
"+6",
"+7",
"+8",
};
const int32_t contrast_value[CONTRAST_COUNT] = {
-8,
-7,
-6,
-5,
-4,
-3,
@@ -46,44 +55,47 @@ const int32_t contrast_value[CONTRAST_COUNT] = {
3,
4,
5,
6,
7,
8,
};
#define BACKLIGHT_COUNT 5
#define BACKLIGHT_COUNT 21
const char* const backlight_text[BACKLIGHT_COUNT] = {
"0%",
"25%",
"50%",
"75%",
"100%",
"0%", "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%",
"55%", "60%", "65%", "70%", "75%", "80%", "85%", "90%", "95%", "100%",
};
const float backlight_value[BACKLIGHT_COUNT] = {
0.0f,
0.25f,
0.5f,
0.75f,
1.0f,
0.00f, 0.05f, 0.10f, 0.15f, 0.20f, 0.25f, 0.30f, 0.35f, 0.40f, 0.45f, 0.50f,
0.55f, 0.60f, 0.65f, 0.70f, 0.75f, 0.80f, 0.85f, 0.90f, 0.95f, 1.00f,
};
#define VOLUME_COUNT 5
#define VOLUME_COUNT 21
const char* const volume_text[VOLUME_COUNT] = {
"0%",
"25%",
"50%",
"75%",
"100%",
"0%", "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%",
"55%", "60%", "65%", "70%", "75%", "80%", "85%", "90%", "95%", "100%",
};
const float volume_value[VOLUME_COUNT] = {
0.00f, 0.05f, 0.10f, 0.15f, 0.20f, 0.25f, 0.30f, 0.35f, 0.40f, 0.45f, 0.50f,
0.55f, 0.60f, 0.65f, 0.70f, 0.75f, 0.80f, 0.85f, 0.90f, 0.95f, 1.00f,
};
const float volume_value[VOLUME_COUNT] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
#define DELAY_COUNT 6
#define DELAY_COUNT 11
const char* const delay_text[DELAY_COUNT] = {
"1s",
"5s",
"10s",
"15s",
"30s",
"60s",
"90s",
"120s",
"5min",
"10min",
"30min",
};
const uint32_t delay_value[DELAY_COUNT] = {1000, 5000, 15000, 30000, 60000, 120000};
const uint32_t delay_value[DELAY_COUNT] =
{1000, 5000, 10000, 15000, 30000, 60000, 90000, 120000, 300000, 600000, 1800000};
#define VIBRO_COUNT 2
const char* const vibro_text[VIBRO_COUNT] = {

View File

@@ -1,6 +1,6 @@
{
"name": "@flipperdevices/fz-sdk",
"version": "0.1.2",
"version": "0.1.3",
"description": "Type declarations and documentation for native JS modules available on Flipper Zero",
"keywords": [
"flipper",

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

@@ -85,9 +85,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

View File

@@ -195,18 +195,32 @@ Min butthurt: 0
Max butthurt: 8
Min level: 1
Max level: 3
Weight: 4
Weight: 3
Name: L3_Intruder_alert_128x64
Min butthurt: 0
Max butthurt: 12
Min level: 3
Max level: 3
Weight: 4
Weight: 3
Name: L1_Procrastinating_128x64
Min butthurt: 0
Max butthurt: 8
Min level: 1
Max level: 3
Weight: 6
Weight: 3
Name: L1_Happy_holidays_128x64
Min butthurt: 0
Max butthurt: 14
Min level: 1
Max level: 3
Weight: 4
Name: L1_Sleigh_ride_128x64
Min butthurt: 0
Max butthurt: 14
Min level: 1
Max level: 3
Weight: 4

View File

@@ -43,7 +43,7 @@ To add unit tests for your protocol, follow these steps:
1. Create a file named `test_<your_protocol_name>.irtest` in the [assets](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests/infrared) directory.
2. Fill it with the test data (more on it below).
3. Add the test code to [infrared_test.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/infrared/infrared_test.c).
3. Add the test code to [infrared_test.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/tests/infrared/infrared_test.c).
4. Build and install firmware with resources, install it on your Flipper and run the tests to see if they pass.
##### Test data format

View File

@@ -1042,6 +1042,7 @@ EXCLUDE = $(DOXY_SRC_ROOT)/lib/mlib \
$(DOXY_SRC_ROOT)/applications/plugins/dap_link/lib/free-dap \
$(DOXY_SRC_ROOT)/applications/debug \
$(DOXY_SRC_ROOT)/applications/main \
$(DOXY_SRC_ROOT)/applications/system/js_app/packages \
$(DOXY_SRC_ROOT)/applications/settings \
$(DOXY_SRC_ROOT)/lib/micro-ecc \
$(DOXY_SRC_ROOT)/lib/ReadMe.md \

View File

@@ -29,6 +29,8 @@ env = ENV.Clone(
TARGETS_ROOT=Dir("#/targets"),
LINT_SOURCES=[
Dir("applications"),
# Not C code
Dir("!applications/system/js_app/packages"),
],
LIBPATH=[
"${LIB_DIST_DIR}",

View File

@@ -106,5 +106,7 @@ void* aligned_malloc(size_t size, size_t alignment) {
}
void aligned_free(void* p) {
free(((void**)p)[-1]);
if(p) {
free(((void**)p)[-1]);
}
}

View File

@@ -129,12 +129,12 @@ void furi_string_swap(FuriString* string_1, FuriString* string_2);
/** Move string_2 content to string_1.
*
* Set the string to the other one, and destroy the other one.
* Copy data from one string to another and destroy the source.
*
* @param string_1 The FuriString instance 1
* @param string_2 The FuriString instance 2
* @param destination The destination FuriString
* @param source The source FuriString
*/
void furi_string_move(FuriString* string_1, FuriString* string_2);
void furi_string_move(FuriString* destination, FuriString* source);
/** Compute a hash for the string.
*

View File

@@ -23,12 +23,17 @@
#define THREAD_MAX_STACK_SIZE (UINT16_MAX * sizeof(StackType_t))
typedef struct FuriThreadStdout FuriThreadStdout;
struct FuriThreadStdout {
typedef struct {
FuriThreadStdoutWriteCallback write_callback;
FuriString* buffer;
};
void* context;
} FuriThreadStdout;
typedef struct {
FuriThreadStdinReadCallback read_callback;
FuriString* unread_buffer; // <! stores data from `ungetc` and friends
void* context;
} FuriThreadStdin;
struct FuriThread {
StaticTask_t container;
@@ -55,6 +60,7 @@ struct FuriThread {
size_t heap_size;
FuriThreadStdout output;
FuriThreadStdin input;
// Keep all non-alignable byte types in one place,
// this ensures that the size of this structure is minimal
@@ -136,6 +142,7 @@ static void furi_thread_body(void* context) {
static void furi_thread_init_common(FuriThread* thread) {
thread->output.buffer = furi_string_alloc();
thread->input.unread_buffer = furi_string_alloc();
FuriThread* parent = NULL;
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
@@ -245,6 +252,7 @@ void furi_thread_free(FuriThread* thread) {
}
furi_string_free(thread->output.buffer);
furi_string_free(thread->input.unread_buffer);
free(thread);
}
@@ -710,13 +718,22 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) {
static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size) {
if(thread->output.write_callback != NULL) {
thread->output.write_callback(data, size);
thread->output.write_callback(data, size, thread->output.context);
} else {
furi_log_tx((const uint8_t*)data, size);
}
return size;
}
static size_t
__furi_thread_stdin_read(FuriThread* thread, char* data, size_t size, FuriWait timeout) {
if(thread->input.read_callback != NULL) {
return thread->input.read_callback(data, size, timeout, thread->input.context);
} else {
return 0;
}
}
static int32_t __furi_thread_stdout_flush(FuriThread* thread) {
FuriString* buffer = thread->output.buffer;
size_t size = furi_string_size(buffer);
@@ -727,19 +744,33 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread) {
return 0;
}
void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
__furi_thread_stdout_flush(thread);
thread->output.write_callback = callback;
}
FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
return thread->output.write_callback;
}
FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
return thread->input.read_callback;
}
void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
__furi_thread_stdout_flush(thread);
thread->output.write_callback = callback;
thread->output.context = context;
}
void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
thread->input.read_callback = callback;
thread->input.context = context;
}
size_t furi_thread_stdout_write(const char* data, size_t size) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
@@ -772,6 +803,31 @@ int32_t furi_thread_stdout_flush(void) {
return __furi_thread_stdout_flush(thread);
}
size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
size_t from_buffer = MIN(furi_string_size(thread->input.unread_buffer), size);
size_t from_input = size - from_buffer;
size_t from_input_actual =
__furi_thread_stdin_read(thread, buffer + from_buffer, from_input, timeout);
memcpy(buffer, furi_string_get_cstr(thread->input.unread_buffer), from_buffer);
furi_string_right(thread->input.unread_buffer, from_buffer);
return from_buffer + from_input_actual;
}
void furi_thread_stdin_unread(char* buffer, size_t size) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
FuriString* new_buf = furi_string_alloc(); // there's no furi_string_alloc_set_strn :(
furi_string_set_strn(new_buf, buffer, size);
furi_string_cat(new_buf, thread->input.unread_buffer);
furi_string_free(thread->input.unread_buffer);
thread->input.unread_buffer = new_buf;
}
void furi_thread_suspend(FuriThreadId thread_id) {
furi_check(thread_id);

View File

@@ -74,8 +74,23 @@ typedef int32_t (*FuriThreadCallback)(void* context);
*
* @param[in] data pointer to the data to be written to the standard out
* @param[in] size size of the data in bytes
* @param[in] context optional context
*/
typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size);
typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size, void* context);
/**
* @brief Standard input callback function pointer type
*
* The function to be used as a standard input callback MUST follow this signature.
*
* @param[out] buffer buffer to read data into
* @param[in] size maximum number of bytes to read into the buffer
* @param[in] timeout how long to wait for (in ticks) before giving up
* @param[in] context optional context
* @returns number of bytes that was actually read into the buffer
*/
typedef size_t (
*FuriThreadStdinReadCallback)(char* buffer, size_t size, FuriWait timeout, void* context);
/**
* @brief State change callback function pointer type.
@@ -468,13 +483,30 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id);
*/
FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void);
/**
* @brief Get the standard input callback for the current thead.
*
* @return pointer to the standard in callback function
*/
FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void);
/** Set standard output callback for the current thread.
*
* @param[in] callback pointer to the callback function or NULL to clear
* @param[in] context context to be passed to the callback
*/
void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback);
void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context);
/** Set standard input callback for the current thread.
*
* @param[in] callback pointer to the callback function or NULL to clear
* @param[in] context context to be passed to the callback
*/
void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context);
/** Write data to buffered standard output.
*
* @note You can also use the standard C `putc`, `puts`, `printf` and friends.
*
* @param[in] data pointer to the data to be written
* @param[in] size data size in bytes
@@ -489,6 +521,29 @@ size_t furi_thread_stdout_write(const char* data, size_t size);
*/
int32_t furi_thread_stdout_flush(void);
/** Read data from the standard input
*
* @note You can also use the standard C `getc`, `gets` and friends.
*
* @param[in] buffer pointer to the buffer to read data into
* @param[in] size how many bytes to read into the buffer
* @param[in] timeout how long to wait for (in ticks) before giving up
* @return number of bytes that was actually read
*/
size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout);
/** Puts data back into the standard input buffer
*
* `furi_thread_stdin_read` will return the bytes in the same order that they
* were supplied to this function.
*
* @note You can also use the standard C `ungetc`.
*
* @param[in] buffer pointer to the buffer to get data from
* @param[in] size how many bytes to read from the buffer
*/
void furi_thread_stdin_unread(char* buffer, size_t size);
/**
* @brief Suspend a thread.
*

View File

@@ -10,13 +10,13 @@
#define TAG "BleHid"
#define BLE_SVC_HID_REPORT_MAP_MAX_LEN (255)
#define BLE_SVC_HID_REPORT_MAX_LEN (255)
#define BLE_SVC_HID_REPORT_REF_LEN (2)
#define BLE_SVC_HID_INFO_LEN (4)
#define BLE_SVC_HID_CONTROL_POINT_LEN (1)
#define BLE_SVC_HID_REPORT_MAX_LEN (255)
#define BLE_SVC_HID_REPORT_REF_LEN (2)
#define BLE_SVC_HID_INFO_LEN (4)
#define BLE_SVC_HID_CONTROL_POINT_LEN (1)
#define BLE_SVC_HID_INPUT_REPORT_COUNT (3)
#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0)
#define BLE_SVC_HID_INPUT_REPORT_COUNT (3)
#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0)
#define BLE_SVC_HID_FEATURE_REPORT_COUNT (0)
#define BLE_SVC_HID_REPORT_COUNT \
(BLE_SVC_HID_INPUT_REPORT_COUNT + BLE_SVC_HID_OUTPUT_REPORT_COUNT + \
@@ -157,6 +157,7 @@ static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) {
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data);
evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data;
// aci_gatt_attribute_modified_event_rp0* attribute_modified;
if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) {
if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) {
// Process modification events
@@ -274,6 +275,7 @@ bool ble_svc_hid_update_input_report(
.data_ptr = data,
.data_len = len,
};
return ble_gatt_characteristic_update(
hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data);
}

View File

@@ -8,11 +8,11 @@
#define TAG "Elf"
#define ELF_NAME_BUFFER_LEN 32
#define SECTION_OFFSET(e, n) ((e)->section_table + (n) * sizeof(Elf32_Shdr))
#define IS_FLAGS_SET(v, m) (((v) & (m)) == (m))
#define ELF_NAME_BUFFER_LEN 32
#define SECTION_OFFSET(e, n) ((e)->section_table + (n) * sizeof(Elf32_Shdr))
#define IS_FLAGS_SET(v, m) (((v) & (m)) == (m))
#define RESOLVER_THREAD_YIELD_STEP 30
#define FAST_RELOCATION_VERSION 1
#define FAST_RELOCATION_VERSION 1
// #define ELF_DEBUG_LOG 1
@@ -830,9 +830,7 @@ void elf_file_free(ELFFile* elf) {
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it);
ELFSectionDict_next(it)) {
const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it);
if(itref->value.data) {
aligned_free(itref->value.data);
}
aligned_free(itref->value.data);
if(itref->value.fast_rel) {
aligned_free(itref->value.fast_rel->data);
free(itref->value.fast_rel);

View File

@@ -403,6 +403,11 @@ bool flipper_format_write_comment_cstr(FlipperFormat* flipper_format, const char
return flipper_format_stream_write_comment_cstr(flipper_format->stream, data);
}
bool flipper_format_write_empty_line(FlipperFormat* flipper_format) {
furi_check(flipper_format);
return flipper_format_stream_write_eol(flipper_format->stream);
}
bool flipper_format_delete_key(FlipperFormat* flipper_format, const char* key) {
furi_check(flipper_format);
FlipperStreamWriteData write_data = {

View File

@@ -518,6 +518,14 @@ bool flipper_format_write_comment(FlipperFormat* flipper_format, FuriString* dat
*/
bool flipper_format_write_comment_cstr(FlipperFormat* flipper_format, const char* data);
/** Write empty line (Improves readability for human based parsing)
*
* @param flipper_format Pointer to a FlipperFormat instance
*
* @return True on success
*/
bool flipper_format_write_empty_line(FlipperFormat* flipper_format);
/** Removes the first matching key and its value. Sets the RW pointer to a
* position of deleted data.
*

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