diff --git a/applications/debug/file_browser_test/file_browser_app.c b/applications/debug/file_browser_test/file_browser_app.c index 6cb50d385..996cb2bd2 100644 --- a/applications/debug/file_browser_test/file_browser_app.c +++ b/applications/debug/file_browser_test/file_browser_app.c @@ -48,7 +48,7 @@ FileBrowserApp* file_browser_app_alloc(char* arg) { app->file_path = furi_string_alloc(); app->file_browser = file_browser_alloc(app->file_path); - file_browser_configure(app->file_browser, "*", true, &I_badusb_10px, true); + file_browser_configure(app->file_browser, "*", NULL, true, &I_badusb_10px, true); view_dispatcher_add_view( app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget)); diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index 16996ba8c..dc1327529 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -215,26 +215,26 @@ static UartEchoApp* uart_echo_app_alloc() { view_dispatcher_add_view(app->view_dispatcher, 0, app->view); view_dispatcher_switch_to_view(app->view_dispatcher, 0); + app->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 1024, uart_echo_worker, app); + furi_thread_start(app->worker_thread); + // Enable uart listener furi_hal_console_disable(); furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200); furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app); - app->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 1024, uart_echo_worker, app); - furi_thread_start(app->worker_thread); - return app; } static void uart_echo_app_free(UartEchoApp* app) { furi_assert(app); + furi_hal_console_enable(); // this will also clear IRQ callback so thread is no longer referenced + furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop); furi_thread_join(app->worker_thread); furi_thread_free(app->worker_thread); - furi_hal_console_enable(); - // Free views view_dispatcher_remove_view(app->view_dispatcher, 0); diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c new file mode 100644 index 000000000..e942c5933 --- /dev/null +++ b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c @@ -0,0 +1,116 @@ +#include +#include +#include +#include +#include "../minunit.h" + +#define DATA_SIZE 4 + +static void furi_hal_i2c_int_setup() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); +} + +static void furi_hal_i2c_int_teardown() { + furi_hal_i2c_release(&furi_hal_i2c_handle_power); +} + +MU_TEST(furi_hal_i2c_int_1b) { + bool ret = false; + uint8_t data_one = 0; + + // 1 byte: read, write, read + ret = furi_hal_i2c_read_reg_8( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + LP5562_CHANNEL_BLUE_CURRENT_REGISTER, + &data_one, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "0 read_reg_8 failed"); + mu_assert(data_one != 0, "0 invalid data"); + ret = furi_hal_i2c_write_reg_8( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + LP5562_CHANNEL_BLUE_CURRENT_REGISTER, + data_one, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "1 write_reg_8 failed"); + ret = furi_hal_i2c_read_reg_8( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + LP5562_CHANNEL_BLUE_CURRENT_REGISTER, + &data_one, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "2 read_reg_8 failed"); + mu_assert(data_one != 0, "2 invalid data"); +} + +MU_TEST(furi_hal_i2c_int_3b) { + bool ret = false; + uint8_t data_many[DATA_SIZE] = {0}; + + // 3 byte: read, write, read + data_many[0] = LP5562_CHANNEL_BLUE_CURRENT_REGISTER; + ret = furi_hal_i2c_tx( + &furi_hal_i2c_handle_power, LP5562_ADDRESS, data_many, 1, LP5562_I2C_TIMEOUT); + mu_assert(ret, "3 tx failed"); + ret = furi_hal_i2c_rx( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + data_many + 1, + DATA_SIZE - 1, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "4 rx failed"); + for(size_t i = 0; i < DATA_SIZE; i++) mu_assert(data_many[i] != 0, "4 invalid data_many"); + + ret = furi_hal_i2c_tx( + &furi_hal_i2c_handle_power, LP5562_ADDRESS, data_many, DATA_SIZE, LP5562_I2C_TIMEOUT); + mu_assert(ret, "5 tx failed"); + + ret = furi_hal_i2c_tx( + &furi_hal_i2c_handle_power, LP5562_ADDRESS, data_many, 1, LP5562_I2C_TIMEOUT); + mu_assert(ret, "6 tx failed"); + ret = furi_hal_i2c_rx( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + data_many + 1, + DATA_SIZE - 1, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "7 rx failed"); + for(size_t i = 0; i < DATA_SIZE; i++) mu_assert(data_many[i] != 0, "7 invalid data_many"); +} + +MU_TEST(furi_hal_i2c_int_1b_fail) { + bool ret = false; + uint8_t data_one = 0; + + // 1 byte: fail, read, fail, write, fail, read + data_one = 0; + ret = furi_hal_i2c_read_reg_8( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS + 0x10, + LP5562_CHANNEL_BLUE_CURRENT_REGISTER, + &data_one, + LP5562_I2C_TIMEOUT); + mu_assert(!ret, "8 read_reg_8 failed"); + mu_assert(data_one == 0, "8 invalid data"); + ret = furi_hal_i2c_read_reg_8( + &furi_hal_i2c_handle_power, + LP5562_ADDRESS, + LP5562_CHANNEL_BLUE_CURRENT_REGISTER, + &data_one, + LP5562_I2C_TIMEOUT); + mu_assert(ret, "9 read_reg_8 failed"); + mu_assert(data_one != 0, "9 invalid data"); +} + +MU_TEST_SUITE(furi_hal_i2c_int_suite) { + MU_SUITE_CONFIGURE(&furi_hal_i2c_int_setup, &furi_hal_i2c_int_teardown); + MU_RUN_TEST(furi_hal_i2c_int_1b); + MU_RUN_TEST(furi_hal_i2c_int_3b); + MU_RUN_TEST(furi_hal_i2c_int_1b_fail); +} + +int run_minunit_test_furi_hal() { + MU_RUN_SUITE(furi_hal_i2c_int_suite); + return MU_EXIT_CODE; +} diff --git a/applications/debug/unit_tests/minunit.h b/applications/debug/unit_tests/minunit.h index 17eb7b3f1..69bfba6d9 100644 --- a/applications/debug/unit_tests/minunit.h +++ b/applications/debug/unit_tests/minunit.h @@ -316,7 +316,7 @@ void minunit_print_fail(const char* error); MU__SAFE_BLOCK( \ double minunit_tmp_e; double minunit_tmp_r; minunit_assert++; minunit_tmp_e = (expected); \ minunit_tmp_r = (result); \ - if(fabs(minunit_tmp_e - minunit_tmp_r) > MINUNIT_EPSILON) { \ + if(fabs(minunit_tmp_e - minunit_tmp_r) > (double)MINUNIT_EPSILON) { \ int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON); \ snprintf( \ minunit_last_message, \ diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 07ec73a03..4218482c7 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -393,7 +393,7 @@ static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) { "nfc_device_save == true assert failed\r\n"); // Verify that key cache is saved FuriString* key_cache_name = furi_string_alloc(); - furi_string_set_str(key_cache_name, "/ext/nfc/cache/"); + furi_string_set_str(key_cache_name, "/ext/nfc/.cache/"); for(size_t i = 0; i < uid_len; i++) { furi_string_cat_printf(key_cache_name, "%02X", uid[i]); } diff --git a/applications/debug/unit_tests/power/power_test.c b/applications/debug/unit_tests/power/power_test.c new file mode 100644 index 000000000..ce2c7aad7 --- /dev/null +++ b/applications/debug/unit_tests/power/power_test.c @@ -0,0 +1,62 @@ +#include +#include +#include "../minunit.h" + +static void power_test_deinit(void) { + // Try to reset to default charging voltage + furi_hal_power_set_battery_charging_voltage(4.208f); +} + +MU_TEST(test_power_charge_voltage_exact) { + // Power of 16mV charge voltages get applied exactly + // (bq25896 charge controller works in 16mV increments) + // + // This test may need adapted if other charge controllers are used in the future. + for(uint16_t charge_mv = 3840; charge_mv <= 4208; charge_mv += 16) { + float charge_volt = (float)charge_mv / 1000.0f; + furi_hal_power_set_battery_charging_voltage(charge_volt); + mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charging_voltage()); + } +} + +MU_TEST(test_power_charge_voltage_floating_imprecision) { + // 4.016f should act as 4.016 V, even with floating point imprecision + furi_hal_power_set_battery_charging_voltage(4.016f); + mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charging_voltage()); +} + +MU_TEST(test_power_charge_voltage_inexact) { + // Charge voltages that are not power of 16mV get truncated down + furi_hal_power_set_battery_charging_voltage(3.841f); + mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage()); + + furi_hal_power_set_battery_charging_voltage(3.900f); + mu_assert_double_eq(3.888, furi_hal_power_get_battery_charging_voltage()); + + furi_hal_power_set_battery_charging_voltage(4.200f); + mu_assert_double_eq(4.192, furi_hal_power_get_battery_charging_voltage()); +} + +MU_TEST(test_power_charge_voltage_invalid_clamped) { + // Out-of-range charge voltages get clamped to 3.840 V and 4.208 V + furi_hal_power_set_battery_charging_voltage(3.808f); + mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage()); + + // NOTE: Intentionally picking a small increment above 4.208 V to reduce the risk of an + // unhappy battery if this fails. + furi_hal_power_set_battery_charging_voltage(4.240f); + mu_assert_double_eq(4.208, furi_hal_power_get_battery_charging_voltage()); +} + +MU_TEST_SUITE(test_power_suite) { + MU_RUN_TEST(test_power_charge_voltage_exact); + MU_RUN_TEST(test_power_charge_voltage_floating_imprecision); + MU_RUN_TEST(test_power_charge_voltage_inexact); + MU_RUN_TEST(test_power_charge_voltage_invalid_clamped); + power_test_deinit(); +} + +int run_minunit_test_power() { + MU_RUN_SUITE(test_power_suite); + return MU_EXIT_CODE; +} diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 2009d4a5b..65fa23c01 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -9,6 +9,7 @@ #define TAG "UnitTests" int run_minunit_test_furi(); +int run_minunit_test_furi_hal(); int run_minunit_test_furi_string(); int run_minunit_test_infrared(); int run_minunit_test_rpc(); @@ -18,6 +19,7 @@ int run_minunit_test_stream(); int run_minunit_test_storage(); int run_minunit_test_subghz(); int run_minunit_test_dirwalk(); +int run_minunit_test_power(); int run_minunit_test_protocol_dict(); int run_minunit_test_lfrfid_protocols(); int run_minunit_test_nfc(); @@ -32,6 +34,7 @@ typedef struct { const UnitTest unit_tests[] = { {.name = "furi", .entry = run_minunit_test_furi}, + {.name = "furi_hal", .entry = run_minunit_test_furi_hal}, {.name = "furi_string", .entry = run_minunit_test_furi_string}, {.name = "storage", .entry = run_minunit_test_storage}, {.name = "stream", .entry = run_minunit_test_stream}, @@ -42,6 +45,7 @@ const UnitTest unit_tests[] = { {.name = "subghz", .entry = run_minunit_test_subghz}, {.name = "infrared", .entry = run_minunit_test_infrared}, {.name = "nfc", .entry = run_minunit_test_nfc}, + {.name = "power", .entry = run_minunit_test_power}, {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict}, {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols}, {.name = "bit_lib", .entry = run_minunit_test_bit_lib}, diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 149b39089..6c8c9633a 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -80,10 +80,12 @@ static void archive_file_browser_set_path( ArchiveBrowserView* browser, FuriString* path, const char* filter_ext, - bool skip_assets) { + bool skip_assets, + bool hide_dot_files) { furi_assert(browser); if(!browser->worker_running) { - browser->worker = file_browser_worker_alloc(path, filter_ext, skip_assets); + browser->worker = + file_browser_worker_alloc(path, NULL, filter_ext, skip_assets, hide_dot_files); file_browser_worker_set_callback_context(browser->worker, browser); file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb); file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb); @@ -92,7 +94,8 @@ static void archive_file_browser_set_path( browser->worker_running = true; } else { furi_assert(browser->worker); - file_browser_worker_set_config(browser->worker, path, filter_ext, skip_assets); + file_browser_worker_set_config( + browser->worker, path, filter_ext, skip_assets, hide_dot_files); } } @@ -472,8 +475,10 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { tab = archive_get_tab(browser); if(archive_is_dir_exists(browser->path)) { bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; + // Hide dot files everywhere except Browser + bool hide_dot_files = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; archive_file_browser_set_path( - browser, browser->path, archive_get_tab_ext(tab), skip_assets); + browser, browser->path, archive_get_tab_ext(tab), skip_assets, hide_dot_files); tab_empty = false; // Empty check will be performed later } } diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c index c562fc2de..9264eb976 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_file_select.c @@ -1,12 +1,14 @@ #include "../bad_usb_app_i.h" #include "furi_hal_power.h" #include "furi_hal_usb.h" +#include static bool bad_usb_file_select(BadUsbApp* bad_usb) { furi_assert(bad_usb); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, BAD_USB_APP_EXTENSION, &I_badusb_10px); + browser_options.base_path = BAD_USB_APP_PATH_FOLDER; // Input events and views are managed by file_browser bool res = dialog_file_browser_show( diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 10cec086a..901866746 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -148,6 +148,7 @@ static bool fap_loader_select_app(FapLoader* loader) { .hide_ext = true, .item_loader_callback = fap_loader_item_callback, .item_loader_context = loader, + .base_path = EXT_PATH("apps"), }; return dialog_file_browser_show( diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index b6d8361b3..b7c8223b0 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -218,6 +218,7 @@ void ibutton_free(iButton* ibutton) { bool ibutton_file_select(iButton* ibutton) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px); + browser_options.base_path = IBUTTON_APP_FOLDER; bool success = dialog_file_browser_show( ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options); diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 1667352d1..55f14416b 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -7,6 +7,7 @@ void infrared_scene_remote_list_on_enter(void* context) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px); + browser_options.base_path = INFRARED_APP_FOLDER; bool success = dialog_file_browser_show( infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options); diff --git a/applications/main/lfrfid/lfrfid.c b/applications/main/lfrfid/lfrfid.c index d391c5e89..2207e7e07 100644 --- a/applications/main/lfrfid/lfrfid.c +++ b/applications/main/lfrfid/lfrfid.c @@ -230,6 +230,7 @@ bool lfrfid_load_key_from_file_select(LfRfid* app) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, LFRFID_APP_EXTENSION, &I_125_10px); + browser_options.base_path = LFRFID_APP_FOLDER; // Input events and views are managed by file_browser bool result = diff --git a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c index 5ddb60992..f90197679 100644 --- a/applications/main/nfc/scenes/nfc_scene_emulate_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_emulate_uid.c @@ -37,8 +37,8 @@ static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) { FuriString* info_str; info_str = furi_string_alloc(); - widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61); - widget_add_string_element(widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating UID"); + widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); + widget_add_string_element(widget, 57, 13, AlignLeft, AlignTop, FontPrimary, "Emulating UID"); if(strcmp(nfc->dev->dev_name, "")) { furi_string_printf(info_str, "%s", nfc->dev->dev_name); } else { @@ -48,7 +48,7 @@ static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) { } furi_string_trim(info_str); widget_add_text_box_element( - widget, 56, 43, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); + widget, 57, 28, 67, 25, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true); furi_string_free(info_str); if(data_received) { widget_add_button_element( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c index 68eda7040..1bd9a85a8 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_emulate.c @@ -17,13 +17,14 @@ void nfc_scene_mf_classic_emulate_on_enter(void* context) { // Setup view Popup* popup = nfc->popup; + popup_set_header(popup, "Emulating", 67, 13, AlignLeft, AlignTop); if(strcmp(nfc->dev->dev_name, "")) { - nfc_text_store_set(nfc, "Emulating\n%s", nfc->dev->dev_name); + nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); } else { - nfc_text_store_set(nfc, "Emulating\nMf Classic", nfc->dev->dev_name); + nfc_text_store_set(nfc, "MIFARE\nClassic"); } - popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); - popup_set_header(popup, nfc->text_store, 56, 31, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61); + popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop); // Setup and start worker view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c index adcadaaf2..c9c617cbe 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_emulate.c @@ -16,14 +16,20 @@ void nfc_scene_mf_ultralight_emulate_on_enter(void* context) { Nfc* nfc = context; // Setup view + MfUltralightType type = nfc->dev->dev_data.mf_ul_data.type; + bool is_ultralight = (type == MfUltralightTypeUL11) || (type == MfUltralightTypeUL21) || + (type == MfUltralightTypeUnknown); Popup* popup = nfc->popup; + popup_set_header(popup, "Emulating", 67, 13, AlignLeft, AlignTop); if(strcmp(nfc->dev->dev_name, "")) { - nfc_text_store_set(nfc, "Emulating\n%s", nfc->dev->dev_name); + nfc_text_store_set(nfc, "%s", nfc->dev->dev_name); + } else if(is_ultralight) { + nfc_text_store_set(nfc, "MIFARE\nUltralight"); } else { - nfc_text_store_set(nfc, "Emulating\nMf Ultralight", nfc->dev->dev_name); + nfc_text_store_set(nfc, "MIFARE\nNTAG"); } - popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); - popup_set_header(popup, nfc->text_store, 56, 31, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61); + popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop); // Setup and start worker view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); diff --git a/applications/main/nfc/scenes/nfc_scene_rpc.c b/applications/main/nfc/scenes/nfc_scene_rpc.c index e5128a52f..60d01a30d 100644 --- a/applications/main/nfc/scenes/nfc_scene_rpc.c +++ b/applications/main/nfc/scenes/nfc_scene_rpc.c @@ -7,7 +7,7 @@ void nfc_scene_rpc_on_enter(void* context) { popup_set_header(popup, "NFC", 89, 42, AlignCenter, AlignBottom); popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61); + popup_set_icon(popup, 0, 12, &I_NFC_dolphin_emulation_47x61); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); diff --git a/applications/main/subghz/helpers/subghz_error_type.h b/applications/main/subghz/helpers/subghz_error_type.h new file mode 100644 index 000000000..e481aa4be --- /dev/null +++ b/applications/main/subghz/helpers/subghz_error_type.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +/** SubGhzErrorType */ +typedef enum { + SubGhzErrorTypeNoError = 0, /** There are no errors */ + SubGhzErrorTypeParseFile = + 1, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */ + SubGhzErrorTypeOnlyRX = + 2, /** Transmission on this frequency is blocked by regional settings */ +} SubGhzErrorType; diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index 79295aaa6..a1c0e41fd 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -43,6 +43,12 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { result = subghz_tx_start(subghz, subghz->txrx->fff_data); if(result) subghz_blink_start(subghz); } + if(!result) { + rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeOnlyRX); + rpc_system_app_set_error_text( + subghz->rpc_ctx, + "Transmission on this frequency is restricted in your region"); + } rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { bool result = false; @@ -74,6 +80,9 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { popup_set_text(popup, subghz->file_name_tmp, 89, 44, AlignCenter, AlignTop); furi_string_free(file_name); + } else { + rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeParseFile); + rpc_system_app_set_error_text(subghz->rpc_ctx, "Cannot parse file"); } } rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventLoadFile, result); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index d9070ba8c..736bcf360 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -454,6 +454,7 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px); + browser_options.base_path = SUBGHZ_APP_FOLDER; // Input events and views are managed by file_select bool res = dialog_file_browser_show( diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 0a40196bb..234365155 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -1,6 +1,7 @@ #pragma once #include "helpers/subghz_types.h" +#include "helpers/subghz_error_type.h" #include #include "subghz.h" #include "views/receiver.h" diff --git a/applications/plugins/music_player/music_player.c b/applications/plugins/music_player/music_player.c index 28872284b..07d4e2df4 100644 --- a/applications/plugins/music_player/music_player.c +++ b/applications/plugins/music_player/music_player.c @@ -313,6 +313,7 @@ int32_t music_player_app(void* p) { dialog_file_browser_set_basic_options( &browser_options, MUSIC_PLAYER_APP_EXTENSION, &I_music_10px); browser_options.hide_ext = false; + browser_options.base_path = MUSIC_PLAYER_APP_PATH_FOLDER; DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); diff --git a/applications/plugins/picopass/picopass_device.c b/applications/plugins/picopass/picopass_device.c index 199b79e97..fd8ddbfbd 100644 --- a/applications/plugins/picopass/picopass_device.c +++ b/applications/plugins/picopass/picopass_device.c @@ -231,6 +231,7 @@ bool picopass_file_select(PicopassDevice* dev) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, PICOPASS_APP_EXTENSION, &I_Nfc_10px); + browser_options.base_path = PICOPASS_APP_FOLDER; bool res = dialog_file_browser_show( dev->dialogs, dev->load_path, picopass_app_folder, &browser_options); diff --git a/applications/plugins/weather_station/protocols/oregon2.c b/applications/plugins/weather_station/protocols/oregon2.c index d294548e6..8779e9596 100644 --- a/applications/plugins/weather_station/protocols/oregon2.c +++ b/applications/plugins/weather_station/protocols/oregon2.c @@ -52,6 +52,11 @@ static const SubGhzBlockConst ws_oregon2_const = { #define ID_UV800 0xd874 #define ID_THN129 0xcc43 // THN129 Temp only #define ID_RTHN129 0x0cd3 // RTHN129 Temp, clock sensors +#define ID_RTHN129_1 0x9cd3 +#define ID_RTHN129_2 0xacd3 +#define ID_RTHN129_3 0xbcd3 +#define ID_RTHN129_4 0xccd3 +#define ID_RTHN129_5 0xdcd3 #define ID_BTHGN129 0x5d53 // Baro, Temp, Hygro sensor #define ID_UVR128 0xec70 #define ID_THGR328N 0xcc23 // Temp & Hygro sensor similar to THR228N with 5 channel instead of 3 @@ -137,11 +142,19 @@ static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration // From sensor id code return amount of bits in variable section // https://temofeev.ru/info/articles/o-dekodirovanii-protokola-pogodnykh-datchikov-oregon-scientific static uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) { - if(sensor_id == ID_THR228N) return 16; - - if(sensor_id == ID_THGR122N) return 24; - - return 0; + switch(sensor_id) { + case ID_THR228N: + case ID_RTHN129_1: + case ID_RTHN129_2: + case ID_RTHN129_3: + case ID_RTHN129_4: + case ID_RTHN129_5: + return 16; + case ID_THGR122N: + return 24; + default: + return 0; + } } static void ws_oregon2_decode_const_data(WSBlockGeneric* ws_block) { @@ -171,16 +184,22 @@ static float ws_oregon2_decode_temp(uint32_t data) { } static void ws_oregon2_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) { - if(sensor_id == ID_THR228N) { + switch(sensor_id) { + case ID_THR228N: + case ID_RTHN129_1: + case ID_RTHN129_2: + case ID_RTHN129_3: + case ID_RTHN129_4: + case ID_RTHN129_5: ws_b->temp = ws_oregon2_decode_temp(data); ws_b->humidity = WS_NO_HUMIDITY; return; - } - - if(sensor_id == ID_THGR122N) { + case ID_THGR122N: ws_b->humidity = bcd_decode_short(data); ws_b->temp = ws_oregon2_decode_temp(data >> 8); return; + default: + break; } } diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index ad26dca47..454c99d7a 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -11,6 +11,12 @@ #define VCP_IF_NUM 0 +#ifdef CLI_VCP_DEBUG +#define VCP_DEBUG(...) FURI_LOG_D(TAG, __VA_ARGS__) +#else +#define VCP_DEBUG(...) +#endif + typedef enum { VcpEvtStop = (1 << 0), VcpEvtConnect = (1 << 1), @@ -104,9 +110,8 @@ static int32_t vcp_worker(void* context) { // VCP session opened if(flags & VcpEvtConnect) { -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "Connect"); -#endif + VCP_DEBUG("Connect"); + if(vcp->connected == false) { vcp->connected = true; furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever); @@ -115,9 +120,8 @@ static int32_t vcp_worker(void* context) { // VCP session closed if(flags & VcpEvtDisconnect) { -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "Disconnect"); -#endif + VCP_DEBUG("Disconnect"); + if(vcp->connected == true) { vcp->connected = false; furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); @@ -127,9 +131,8 @@ static int32_t vcp_worker(void* context) { // Rx buffer was read, maybe there is enough space for new data? if((flags & VcpEvtStreamRx) && (missed_rx > 0)) { -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "StreamRx"); -#endif + VCP_DEBUG("StreamRx"); + if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { flags |= VcpEvtRx; missed_rx--; @@ -140,9 +143,8 @@ static int32_t vcp_worker(void* context) { if(flags & VcpEvtRx) { if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "Rx %d", len); -#endif + VCP_DEBUG("Rx %ld", len); + if(len > 0) { furi_check( furi_stream_buffer_send( @@ -150,18 +152,15 @@ static int32_t vcp_worker(void* context) { (size_t)len); } } else { -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "Rx missed"); -#endif + VCP_DEBUG("Rx missed"); missed_rx++; } } // New data in Tx buffer if(flags & VcpEvtStreamTx) { -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "StreamTx"); -#endif + VCP_DEBUG("StreamTx"); + if(tx_idle) { flags |= VcpEvtTx; } @@ -171,9 +170,9 @@ static int32_t vcp_worker(void* context) { if(flags & VcpEvtTx) { size_t len = furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "Tx %d", len); -#endif + + VCP_DEBUG("Tx %d", len); + if(len > 0) { // Some data left in Tx buffer. Sending it now tx_idle = false; furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len); @@ -216,9 +215,7 @@ static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { return 0; } -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "rx %u start", size); -#endif + VCP_DEBUG("rx %u start", size); size_t rx_cnt = 0; @@ -227,19 +224,21 @@ static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE; size_t len = furi_stream_buffer_receive(vcp->rx_stream, buffer, batch_size, timeout); -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "rx %u ", batch_size); -#endif + VCP_DEBUG("rx %u ", batch_size); + if(len == 0) break; + if(vcp->running == false) { + // EOT command is received after VCP session close + rx_cnt += len; + break; + } furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamRx); size -= len; buffer += len; rx_cnt += len; } -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "rx %u end", size); -#endif + VCP_DEBUG("rx %u end", size); return rx_cnt; } @@ -251,9 +250,7 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) { return; } -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "tx %u start", size); -#endif + VCP_DEBUG("tx %u start", size); while(size > 0 && vcp->connected) { size_t batch_size = size; @@ -261,17 +258,13 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) { furi_stream_buffer_send(vcp->tx_stream, buffer, batch_size, FuriWaitForever); furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamTx); -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "tx %u", batch_size); -#endif + VCP_DEBUG("tx %u", batch_size); size -= batch_size; buffer += batch_size; } -#ifdef CLI_VCP_DEBUG - FURI_LOG_D(TAG, "tx %u end", size); -#endif + VCP_DEBUG("tx %u end", size); } static void cli_vcp_tx_stdout(const char* data, size_t size) { diff --git a/applications/services/dialogs/dialogs.c b/applications/services/dialogs/dialogs.c index 3c6588889..3908ca31b 100644 --- a/applications/services/dialogs/dialogs.c +++ b/applications/services/dialogs/dialogs.c @@ -14,6 +14,7 @@ void dialog_file_browser_set_basic_options( options->hide_ext = true; options->item_loader_callback = NULL; options->item_loader_context = NULL; + options->base_path = NULL; } static DialogsApp* dialogs_app_alloc() { diff --git a/applications/services/dialogs/dialogs.h b/applications/services/dialogs/dialogs.h index 4e836e365..4c1b675a6 100644 --- a/applications/services/dialogs/dialogs.h +++ b/applications/services/dialogs/dialogs.h @@ -18,7 +18,9 @@ typedef struct DialogsApp DialogsApp; /** * File browser dialog extra options * @param extension file extension to be offered for selection + * @param base_path root folder path for navigation with back key * @param skip_assets true - do not show assets folders + * @param hide_dot_files true - hide dot files * @param icon file icon pointer, NULL for default icon * @param hide_ext true - hide extensions for files * @param item_loader_callback callback function for providing custom icon & entry name @@ -26,7 +28,9 @@ typedef struct DialogsApp DialogsApp; */ typedef struct { const char* extension; + const char* base_path; bool skip_assets; + bool hide_dot_files; const Icon* icon; bool hide_ext; FileBrowserLoadItemCallback item_loader_callback; diff --git a/applications/services/dialogs/dialogs_api.c b/applications/services/dialogs/dialogs_api.c index c1de59993..5e2b0683e 100644 --- a/applications/services/dialogs/dialogs_api.c +++ b/applications/services/dialogs/dialogs_api.c @@ -20,9 +20,11 @@ bool dialog_file_browser_show( .file_icon = options ? options->icon : NULL, .hide_ext = options ? options->hide_ext : true, .skip_assets = options ? options->skip_assets : true, + .hide_dot_files = options ? options->hide_dot_files : true, .preselected_filename = path, .item_callback = options ? options->item_loader_callback : NULL, .item_callback_context = options ? options->item_loader_context : NULL, + .base_path = options ? options->base_path : NULL, }}; DialogsAppReturn return_data; diff --git a/applications/services/dialogs/dialogs_message.h b/applications/services/dialogs/dialogs_message.h index d01c5aa9c..1c8c4fb50 100644 --- a/applications/services/dialogs/dialogs_message.h +++ b/applications/services/dialogs/dialogs_message.h @@ -11,11 +11,13 @@ typedef struct { const char* extension; bool skip_assets; bool hide_ext; + bool hide_dot_files; const Icon* file_icon; FuriString* result_path; FuriString* preselected_filename; FileBrowserLoadItemCallback item_callback; void* item_callback_context; + const char* base_path; } DialogsAppMessageDataFileBrowser; typedef struct { diff --git a/applications/services/dialogs/dialogs_module_file_browser.c b/applications/services/dialogs/dialogs_module_file_browser.c index 7e67d6c60..8d486dbaf 100644 --- a/applications/services/dialogs/dialogs_module_file_browser.c +++ b/applications/services/dialogs/dialogs_module_file_browser.c @@ -38,7 +38,13 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow file_browser_set_callback( file_browser, dialogs_app_file_browser_callback, file_browser_context); file_browser_configure( - file_browser, data->extension, data->skip_assets, data->file_icon, data->hide_ext); + file_browser, + data->extension, + data->base_path, + data->skip_assets, + data->hide_dot_files, + data->file_icon, + data->hide_ext); file_browser_set_item_callback(file_browser, data->item_callback, data->item_callback_context); file_browser_start(file_browser, data->preselected_filename); diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index 203be23ed..a5daa91ec 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -83,7 +83,9 @@ struct FileBrowser { View* view; BrowserWorker* worker; const char* ext_filter; + const char* base_path; bool skip_assets; + bool hide_dot_files; bool hide_ext; FileBrowserCallback callback; @@ -162,7 +164,9 @@ View* file_browser_get_view(FileBrowser* browser) { void file_browser_configure( FileBrowser* browser, const char* extension, + const char* base_path, bool skip_assets, + bool hide_dot_files, const Icon* file_icon, bool hide_ext) { furi_assert(browser); @@ -170,6 +174,8 @@ void file_browser_configure( browser->ext_filter = extension; browser->skip_assets = skip_assets; browser->hide_ext = hide_ext; + browser->base_path = base_path; + browser->hide_dot_files = hide_dot_files; with_view_model( browser->view, @@ -183,7 +189,12 @@ void file_browser_configure( void file_browser_start(FileBrowser* browser, FuriString* path) { furi_assert(browser); - browser->worker = file_browser_worker_alloc(path, browser->ext_filter, browser->skip_assets); + browser->worker = file_browser_worker_alloc( + path, + browser->base_path, + browser->ext_filter, + browser->skip_assets, + browser->hide_dot_files); file_browser_worker_set_callback_context(browser->worker, browser); file_browser_worker_set_folder_callback(browser->worker, browser_folder_open_cb); file_browser_worker_set_list_callback(browser->worker, browser_list_load_cb); diff --git a/applications/services/gui/modules/file_browser.h b/applications/services/gui/modules/file_browser.h index c9fdddb55..879d62c4e 100644 --- a/applications/services/gui/modules/file_browser.h +++ b/applications/services/gui/modules/file_browser.h @@ -29,7 +29,9 @@ View* file_browser_get_view(FileBrowser* browser); void file_browser_configure( FileBrowser* browser, const char* extension, + const char* base_path, bool skip_assets, + bool hide_dot_files, const Icon* file_icon, bool hide_ext); diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index a85a14b75..d8b515d03 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -42,6 +42,7 @@ struct BrowserWorker { uint32_t load_offset; uint32_t load_count; bool skip_assets; + bool hide_dot_files; idx_last_array_t idx_last; void* cb_ctx; @@ -76,6 +77,13 @@ static bool browser_path_trim(FuriString* path) { } static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, bool is_folder) { + // Skip dot files if enabled + if(browser->hide_dot_files) { + if(furi_string_start_with_str(name, ".")) { + return false; + } + } + if(is_folder) { // Skip assets folders (if enabled) if(browser->skip_assets) { @@ -361,20 +369,26 @@ static int32_t browser_worker(void* context) { return 0; } -BrowserWorker* - file_browser_worker_alloc(FuriString* path, const char* filter_ext, bool skip_assets) { +BrowserWorker* file_browser_worker_alloc( + FuriString* path, + const char* base_path, + const char* filter_ext, + bool skip_assets, + bool hide_dot_files) { BrowserWorker* browser = malloc(sizeof(BrowserWorker)); //-V773 idx_last_array_init(browser->idx_last); browser->filter_extension = furi_string_alloc_set(filter_ext); browser->skip_assets = skip_assets; - browser->path_start = furi_string_alloc_set(path); + browser->hide_dot_files = hide_dot_files; + browser->path_current = furi_string_alloc_set(path); browser->path_next = furi_string_alloc_set(path); - if(browser_path_is_file(browser->path_start)) { - browser_path_trim(browser->path_start); + browser->path_start = furi_string_alloc(); + if(base_path) { + furi_string_set_str(browser->path_start, base_path); } browser->thread = furi_thread_alloc_ex("BrowserWorker", 2048, browser_worker, browser); @@ -437,11 +451,13 @@ void file_browser_worker_set_config( BrowserWorker* browser, FuriString* path, const char* filter_ext, - bool skip_assets) { + bool skip_assets, + bool hide_dot_files) { furi_assert(browser); furi_string_set(browser->path_next, path); furi_string_set(browser->filter_extension, filter_ext); browser->skip_assets = skip_assets; + browser->hide_dot_files = hide_dot_files; furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange); } diff --git a/applications/services/gui/modules/file_browser_worker.h b/applications/services/gui/modules/file_browser_worker.h index 2f8155401..3b4be6aa7 100644 --- a/applications/services/gui/modules/file_browser_worker.h +++ b/applications/services/gui/modules/file_browser_worker.h @@ -21,8 +21,12 @@ typedef void (*BrowserWorkerListItemCallback)( bool is_last); typedef void (*BrowserWorkerLongLoadCallback)(void* context); -BrowserWorker* - file_browser_worker_alloc(FuriString* path, const char* filter_ext, bool skip_assets); +BrowserWorker* file_browser_worker_alloc( + FuriString* path, + const char* base_path, + const char* filter_ext, + bool skip_assets, + bool hide_dot_files); void file_browser_worker_free(BrowserWorker* browser); @@ -48,7 +52,8 @@ void file_browser_worker_set_config( BrowserWorker* browser, FuriString* path, const char* filter_ext, - bool skip_assets); + bool skip_assets, + bool hide_dot_files); void file_browser_worker_folder_enter(BrowserWorker* browser, FuriString* path, int32_t item_idx); diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 89886b0f8..5df611a74 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -12,6 +12,20 @@ void power_draw_battery_callback(Canvas* canvas, void* context) { if(power->info.gauge_is_ok) { canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4); + if(power->info.voltage_battery_charging < 4.2) { + // Battery charging voltage is modified, indicate with cross pattern + canvas_invert_color(canvas); + uint8_t battery_bar_width = (power->info.charge + 4) / 5; + bool cross_odd = false; + // Start 1 further in from the battery bar's x position + for(uint8_t x = 3; x <= battery_bar_width; x++) { + // Cross pattern is from the center of the battery bar + // y = 2 + 1 (inset) + 1 (for every other) + canvas_draw_dot(canvas, x, 3 + (uint8_t)cross_odd); + cross_odd = !cross_odd; + } + canvas_invert_color(canvas); + } if(power->state == PowerStateCharging) { canvas_set_bitmap_mode(canvas, 1); canvas_set_color(canvas, ColorWhite); @@ -132,6 +146,7 @@ static bool power_update_info(Power* power) { info.capacity_full = furi_hal_power_get_battery_full_capacity(); info.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger); info.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge); + info.voltage_battery_charging = furi_hal_power_get_battery_charging_voltage(); info.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger); info.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge); info.voltage_vbus = furi_hal_power_get_usb_voltage(); diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h index 32433ebca..8b9019c42 100644 --- a/applications/services/power/power_service/power.h +++ b/applications/services/power/power_service/power.h @@ -41,6 +41,7 @@ typedef struct { float current_charger; float current_gauge; + float voltage_battery_charging; float voltage_charger; float voltage_gauge; float voltage_vbus; diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index 4e1af1fb1..b96f043ac 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -32,6 +32,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) furi_assert(request->which_content == PB_Main_app_start_request_tag); RpcAppSystem* rpc_app = context; RpcSession* session = rpc_app->session; + rpc_system_app_error_reset(rpc_app); furi_assert(session); char args_temp[RPC_SYSTEM_APP_TEMP_ARGS_SIZE]; @@ -79,6 +80,7 @@ static void rpc_system_app_lock_status_process(const PB_Main* request, void* con furi_assert(request->which_content == PB_Main_app_lock_status_request_tag); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -108,6 +110,7 @@ static void rpc_system_app_exit_request(const PB_Main* request, void* context) { furi_assert(request->which_content == PB_Main_app_exit_request_tag); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -133,6 +136,7 @@ static void rpc_system_app_load_file(const PB_Main* request, void* context) { furi_assert(request->which_content == PB_Main_app_load_file_request_tag); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -158,6 +162,7 @@ static void rpc_system_app_button_press(const PB_Main* request, void* context) { furi_assert(request->which_content == PB_Main_app_button_press_request_tag); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -183,6 +188,7 @@ static void rpc_system_app_button_release(const PB_Main* request, void* context) furi_assert(context); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -222,6 +228,7 @@ static void rpc_system_app_data_exchange_process(const PB_Main* request, void* c furi_assert(context); RpcAppSystem* rpc_app = context; + rpc_system_app_error_reset(rpc_app); RpcSession* session = rpc_app->session; furi_assert(session); @@ -326,6 +333,13 @@ void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text content->text = error_text ? strdup(error_text) : NULL; } +void rpc_system_app_error_reset(RpcAppSystem* rpc_app) { + furi_assert(rpc_app); + + rpc_system_app_set_error_code(rpc_app, 0); + rpc_system_app_set_error_text(rpc_app, NULL); +} + void rpc_system_app_set_data_exchange_callback( RpcAppSystem* rpc_app, RpcAppSystemDataExchangeCallback callback, diff --git a/applications/services/rpc/rpc_app.h b/applications/services/rpc/rpc_app.h index 70febe491..d5c6fee96 100644 --- a/applications/services/rpc/rpc_app.h +++ b/applications/services/rpc/rpc_app.h @@ -33,6 +33,8 @@ void rpc_system_app_set_error_code(RpcAppSystem* rpc_app, uint32_t error_code); void rpc_system_app_set_error_text(RpcAppSystem* rpc_app, const char* error_text); +void rpc_system_app_error_reset(RpcAppSystem* rpc_app); + void rpc_system_app_set_data_exchange_callback( RpcAppSystem* rpc_app, RpcAppSystemDataExchangeCallback callback, diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index bdd9589ed..cf474c546 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -106,6 +106,7 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e .hide_ext = true, .item_loader_callback = favorite_fap_selector_item_callback, .item_loader_context = app, + .base_path = EXT_PATH("apps"), }; if(primary_favorite) { // Select favorite fap in file browser diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c b/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c index 0085c31dc..5fa38df72 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c @@ -7,6 +7,7 @@ static void power_settings_scene_battery_info_update_model(PowerSettingsApp* app .gauge_voltage = app->info.voltage_gauge, .gauge_current = app->info.current_gauge, .gauge_temperature = app->info.temperature_gauge, + .charging_voltage = app->info.voltage_battery_charging, .charge = app->info.charge, .health = app->info.health, }; diff --git a/applications/settings/power_settings_app/views/battery_info.c b/applications/settings/power_settings_app/views/battery_info.c index bbb0acb9a..d760164b9 100644 --- a/applications/settings/power_settings_app/views/battery_info.c +++ b/applications/settings/power_settings_app/views/battery_info.c @@ -68,6 +68,16 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA"); } else if(drain_current != 0) { snprintf(header, 20, "..."); + } else if(data->charging_voltage < 4.2) { + // Non-default battery charging limit, mention it + snprintf(emote, sizeof(emote), "Charged!"); + snprintf(header, sizeof(header), "Limited to"); + snprintf( + value, + sizeof(value), + "%ld.%ldV", + (uint32_t)(data->charging_voltage), + (uint32_t)(data->charging_voltage * 10) % 10); } else { snprintf(header, sizeof(header), "Charged!"); } diff --git a/applications/settings/power_settings_app/views/battery_info.h b/applications/settings/power_settings_app/views/battery_info.h index e62aa44fa..7bfacf69e 100644 --- a/applications/settings/power_settings_app/views/battery_info.h +++ b/applications/settings/power_settings_app/views/battery_info.h @@ -9,6 +9,7 @@ typedef struct { float gauge_voltage; float gauge_current; float gauge_temperature; + float charging_voltage; uint8_t charge; uint8_t health; } BatteryInfoModel; diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_0.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_0.png new file mode 100644 index 000000000..912090032 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_1.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_1.png new file mode 100644 index 000000000..0b99a32ff Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_10.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_10.png new file mode 100644 index 000000000..9da72ac1d Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_11.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_11.png new file mode 100644 index 000000000..8d54da6c6 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_12.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_12.png new file mode 100644 index 000000000..84046a46c Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_13.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_13.png new file mode 100644 index 000000000..3e1c9c329 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_13.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_14.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_14.png new file mode 100644 index 000000000..f4f6ccd66 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_14.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_15.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_15.png new file mode 100644 index 000000000..5dc1a6525 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_15.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_16.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_16.png new file mode 100644 index 000000000..bec472921 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_16.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_17.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_17.png new file mode 100644 index 000000000..82e5176c8 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_17.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_18.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_18.png new file mode 100644 index 000000000..3b5e60dfd Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_18.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_19.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_19.png new file mode 100644 index 000000000..5f76c7d23 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_19.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_2.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_2.png new file mode 100644 index 000000000..84d6aaf35 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_20.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_20.png new file mode 100644 index 000000000..2f8394fd5 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_20.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_3.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_3.png new file mode 100644 index 000000000..48adde113 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_4.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_4.png new file mode 100644 index 000000000..5889835b7 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_5.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_5.png new file mode 100644 index 000000000..7f980f57f Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_6.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_6.png new file mode 100644 index 000000000..497136000 Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_7.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_7.png new file mode 100644 index 000000000..03d67134a Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_8.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_8.png new file mode 100644 index 000000000..9f523cace Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/frame_9.png b/assets/dolphin/external/L2_Wake_up_128x64/frame_9.png new file mode 100644 index 000000000..5a565b1af Binary files /dev/null and b/assets/dolphin/external/L2_Wake_up_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L2_Wake_up_128x64/meta.txt b/assets/dolphin/external/L2_Wake_up_128x64/meta.txt new file mode 100644 index 000000000..06c710f03 --- /dev/null +++ b/assets/dolphin/external/L2_Wake_up_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 10 +Active frames: 18 +Frames order: 0 1 0 1 0 1 0 2 3 4 0 5 6 7 8 9 10 11 10 12 13 14 15 16 17 18 19 20 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 0 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index a6c7ca694..7f03d6595 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -36,19 +36,12 @@ Min level: 1 Max level: 1 Weight: 3 -Name: L2_Furippa2_128x64 +Name: L2_Wake_up_128x64 Min butthurt: 0 -Max butthurt: 6 +Max butthurt: 12 Min level: 2 -Max level: 2 -Weight: 3 - -Name: L3_Furippa3_128x64 -Min butthurt: 0 -Max butthurt: 6 -Min level: 3 Max level: 3 -Weight: 3 +Weight: 4 Name: L1_Read_books_128x64 Min butthurt: 0 @@ -57,13 +50,6 @@ Min level: 1 Max level: 1 Weight: 3 -Name: L2_Hacking_pc_128x64 -Min butthurt: 0 -Max butthurt: 8 -Min level: 2 -Max level: 2 -Weight: 3 - Name: L1_Cry_128x64 Min butthurt: 8 Max butthurt: 13 @@ -90,14 +76,49 @@ Min butthurt: 0 Max butthurt: 9 Min level: 1 Max level: 3 -Weight: 5 +Weight: 4 Name: L1_Painting_128x64 Min butthurt: 0 Max butthurt: 7 Min level: 1 Max level: 3 -Weight: 4 +Weight: 3 + +Name: L1_Leaving_sad_128x64 +Min butthurt: 14 +Max butthurt: 14 +Min level: 1 +Max level: 3 +Weight: 3 + +Name: L2_Furippa2_128x64 +Min butthurt: 0 +Max butthurt: 6 +Min level: 2 +Max level: 2 +Weight: 3 + +Name: L2_Hacking_pc_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 2 +Max level: 2 +Weight: 3 + +Name: L2_Soldering_128x64 +Min butthurt: 0 +Max butthurt: 10 +Min level: 2 +Max level: 2 +Weight: 3 + +Name: L3_Furippa3_128x64 +Min butthurt: 0 +Max butthurt: 6 +Min level: 3 +Max level: 3 +Weight: 3 Name: L3_Hijack_radio_128x64 Min butthurt: 0 @@ -112,17 +133,3 @@ Max butthurt: 10 Min level: 3 Max level: 3 Weight: 3 - -Name: L2_Soldering_128x64 -Min butthurt: 0 -Max butthurt: 10 -Min level: 2 -Max level: 2 -Weight: 3 - -Name: L1_Leaving_sad_128x64 -Min butthurt: 14 -Max butthurt: 14 -Min level: 1 -Max level: 3 -Weight: 3 diff --git a/assets/icons/NFC/NFC_dolphin_emulation_47x61.png b/assets/icons/NFC/NFC_dolphin_emulation_47x61.png new file mode 100644 index 000000000..178353128 Binary files /dev/null and b/assets/icons/NFC/NFC_dolphin_emulation_47x61.png differ diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir index 8945465c7..3965013da 100755 --- a/assets/resources/infrared/assets/tv.ir +++ b/assets/resources/infrared/assets/tv.ir @@ -1620,3 +1620,39 @@ type: raw frequency: 38000 duty_cycle: 0.33 data: 7847 3931 470 1448 467 495 472 1443 472 490 467 1451 464 491 466 499 468 491 466 4413 467 1455 470 486 471 1449 466 495 472 1441 464 501 466 492 465 494 463 22093 7851 3934 467 1454 471 490 467 1446 469 496 471 1446 469 489 468 494 473 484 473 4410 470 1449 466 489 468 1455 470 489 468 1449 466 496 471 486 471 490 467 +# +name: Power +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 0D 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 11 00 00 00 +# +name: Ch_next +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 20 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 21 00 00 00 diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 4f8c67f0a..77b3182fa 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,8.3,, +Version,+,10.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -821,14 +821,14 @@ Function,-,fgetpos,int,"FILE*, fpos_t*" Function,-,fgets,char*,"char*, int, FILE*" Function,-,fgets_unlocked,char*,"char*, int, FILE*" Function,+,file_browser_alloc,FileBrowser*,FuriString* -Function,+,file_browser_configure,void,"FileBrowser*, const char*, _Bool, const Icon*, _Bool" +Function,+,file_browser_configure,void,"FileBrowser*, const char*, const char*, _Bool, _Bool, const Icon*, _Bool" Function,+,file_browser_free,void,FileBrowser* Function,+,file_browser_get_view,View*,FileBrowser* Function,+,file_browser_set_callback,void,"FileBrowser*, FileBrowserCallback, void*" Function,+,file_browser_set_item_callback,void,"FileBrowser*, FileBrowserLoadItemCallback, void*" Function,+,file_browser_start,void,"FileBrowser*, FuriString*" Function,+,file_browser_stop,void,FileBrowser* -Function,+,file_browser_worker_alloc,BrowserWorker*,"FuriString*, const char*, _Bool" +Function,+,file_browser_worker_alloc,BrowserWorker*,"FuriString*, const char*, const char*, _Bool, _Bool" Function,+,file_browser_worker_folder_enter,void,"BrowserWorker*, FuriString*, int32_t" Function,+,file_browser_worker_folder_exit,void,BrowserWorker* Function,+,file_browser_worker_folder_refresh,void,"BrowserWorker*, int32_t" @@ -836,7 +836,7 @@ Function,+,file_browser_worker_free,void,BrowserWorker* Function,+,file_browser_worker_is_in_start_folder,_Bool,BrowserWorker* Function,+,file_browser_worker_load,void,"BrowserWorker*, uint32_t, uint32_t" Function,+,file_browser_worker_set_callback_context,void,"BrowserWorker*, void*" -Function,+,file_browser_worker_set_config,void,"BrowserWorker*, FuriString*, const char*, _Bool" +Function,+,file_browser_worker_set_config,void,"BrowserWorker*, FuriString*, const char*, _Bool, _Bool" Function,+,file_browser_worker_set_folder_callback,void,"BrowserWorker*, BrowserWorkerFolderOpenCallback" Function,+,file_browser_worker_set_item_callback,void,"BrowserWorker*, BrowserWorkerListItemCallback" Function,+,file_browser_worker_set_list_callback,void,"BrowserWorker*, BrowserWorkerListLoadCallback" @@ -1209,6 +1209,7 @@ Function,+,furi_hal_power_enable_external_3_3v,void, Function,+,furi_hal_power_enable_otg,void, Function,+,furi_hal_power_gauge_is_ok,_Bool, Function,+,furi_hal_power_get_bat_health_pct,uint8_t, +Function,+,furi_hal_power_get_battery_charging_voltage,float, Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC Function,+,furi_hal_power_get_battery_design_capacity,uint32_t, Function,+,furi_hal_power_get_battery_full_capacity,uint32_t, @@ -1227,6 +1228,7 @@ Function,+,furi_hal_power_is_charging_done,_Bool, Function,+,furi_hal_power_is_otg_enabled,_Bool, Function,+,furi_hal_power_off,void, Function,+,furi_hal_power_reset,void, +Function,+,furi_hal_power_set_battery_charging_voltage,void,float Function,+,furi_hal_power_shutdown,void, Function,+,furi_hal_power_sleep,void, Function,+,furi_hal_power_sleep_available,_Bool, @@ -2301,6 +2303,7 @@ Function,+,rpc_session_set_context,void,"RpcSession*, void*" Function,+,rpc_session_set_send_bytes_callback,void,"RpcSession*, RpcSendBytesCallback" Function,+,rpc_session_set_terminated_callback,void,"RpcSession*, RpcSessionTerminatedCallback" Function,+,rpc_system_app_confirm,void,"RpcAppSystem*, RpcAppSystemEvent, _Bool" +Function,+,rpc_system_app_error_reset,void,RpcAppSystem* Function,+,rpc_system_app_exchange_data,void,"RpcAppSystem*, const uint8_t*, size_t" Function,+,rpc_system_app_get_data,const char*,RpcAppSystem* Function,+,rpc_system_app_send_exited,void,RpcAppSystem* diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index 67321285e..c5948fc79 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -259,6 +259,9 @@ bool furi_hal_nfc_listen( params.lmConfigPA.SEL_RES = sak; rfalNfcDiscover(¶ms); + // Disable EMD suppression. + st25r3916ModifyRegister(ST25R3916_REG_EMD_SUP_CONF, ST25R3916_REG_EMD_SUP_CONF_emd_emv, 0); + uint32_t start = DWT->CYCCNT; while(state != RFAL_NFC_STATE_ACTIVATED) { rfalNfcWorker(); diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index ddb056906..2d709620d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -341,6 +341,20 @@ bool furi_hal_power_is_otg_enabled() { return ret; } +float furi_hal_power_get_battery_charging_voltage() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + float ret = (float)bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power) / 1000.0f; + furi_hal_i2c_release(&furi_hal_i2c_handle_power); + return ret; +} + +void furi_hal_power_set_battery_charging_voltage(float voltage) { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + // Adding 0.0005 is necessary because 4.016f is 4.015999794000, which gets truncated + bq25896_set_vreg_voltage(&furi_hal_i2c_handle_power, (uint16_t)(voltage * 1000.0f + 0.0005f)); + furi_hal_i2c_release(&furi_hal_i2c_handle_power); +} + void furi_hal_power_check_otg_status() { furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); if(bq25896_check_otg_fault(&furi_hal_i2c_handle_power)) @@ -470,10 +484,10 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) if(sep == '.') { property_value_out(&property_context, NULL, 2, "format", "major", "2"); - property_value_out(&property_context, NULL, 2, "format", "minor", "0"); + property_value_out(&property_context, NULL, 2, "format", "minor", "1"); } else { property_value_out(&property_context, NULL, 3, "power", "info", "major", "1"); - property_value_out(&property_context, NULL, 3, "power", "info", "minor", "0"); + property_value_out(&property_context, NULL, 3, "power", "info", "minor", "1"); } uint8_t charge = furi_hal_power_get_pct(); @@ -481,7 +495,7 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) const char* charge_state; if(furi_hal_power_is_charging()) { - if(charge < 100) { + if((charge < 100) && (!furi_hal_power_is_charging_done())) { charge_state = "charging"; } else { charge_state = "charged"; @@ -491,6 +505,8 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) } property_value_out(&property_context, NULL, 2, "charge", "state", charge_state); + uint16_t charge_voltage = (uint16_t)(furi_hal_power_get_battery_charging_voltage() * 1000.f); + property_value_out(&property_context, "%u", 2, "charge", "voltage", charge_voltage); uint16_t voltage = (uint16_t)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f); property_value_out(&property_context, "%u", 2, "battery", "voltage", voltage); @@ -567,6 +583,13 @@ void furi_hal_power_debug_get(PropertyValueCallback out, void* context) { "charger", "vbat", bq25896_get_vbat_voltage(&furi_hal_i2c_handle_power)); + property_value_out( + &property_context, + "%d", + 2, + "charger", + "vreg", + bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power)); property_value_out( &property_context, "%d", diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index a78d09fe4..39a11e99f 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -121,6 +121,22 @@ void furi_hal_power_check_otg_status(); */ bool furi_hal_power_is_otg_enabled(); +/** Get battery charging voltage in V + * + * @return voltage in V + */ +float furi_hal_power_get_battery_charging_voltage(); + +/** Set battery charging voltage in V + * + * Invalid values will be clamped to the nearest valid value. + * + * @param voltage[in] voltage in V + * + * @return voltage in V + */ +void furi_hal_power_set_battery_charging_voltage(float voltage); + /** Get remaining battery battery capacity in mAh * * @return capacity in mAh diff --git a/lib/drivers/bq25896.c b/lib/drivers/bq25896.c index 1fb9d53e7..7e3008d62 100644 --- a/lib/drivers/bq25896.c +++ b/lib/drivers/bq25896.c @@ -132,6 +132,33 @@ bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle) { return bq25896_regs.r03.OTG_CONFIG; } +uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle) { + furi_hal_i2c_read_reg_8( + handle, BQ25896_ADDRESS, 0x06, (uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT); + return (uint16_t)bq25896_regs.r06.VREG * 16 + 3840; +} + +void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) { + if(vreg_voltage < 3840) { + // Minimum value is 3840 mV + bq25896_regs.r06.VREG = 0; + } else { + // Find the nearest voltage value (subtract offset, divide into sections) + // Values are truncated downward as needed (e.g. 4200mV -> 4192 mV) + bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16); + } + + // Do not allow values above 23 (0x17, 4208mV) + // Exceeding 4.2v will overcharge the battery! + if(bq25896_regs.r06.VREG > 23) { + bq25896_regs.r06.VREG = 23; + } + + // Apply changes + furi_hal_i2c_write_reg_8( + handle, BQ25896_ADDRESS, 0x06, *(uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT); +} + bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x0C, (uint8_t*)&bq25896_regs.r0C, BQ25896_I2C_TIMEOUT); diff --git a/lib/drivers/bq25896.h b/lib/drivers/bq25896.h index c8da0a064..c8a8526a1 100644 --- a/lib/drivers/bq25896.h +++ b/lib/drivers/bq25896.h @@ -36,6 +36,15 @@ void bq25896_disable_otg(FuriHalI2cBusHandle* handle); /** Is otg enabled */ bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle); +/** Get VREG (charging) voltage in mV */ +uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle); + +/** Set VREG (charging) voltage in mV + * + * Valid range: 3840mV - 4208mV, in steps of 16mV + */ +void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage); + /** Check OTG BOOST Fault status */ bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle); diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index eb911f54b..533c22696 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -8,11 +8,11 @@ #include #define TAG "NfcDevice" -#define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/cache") +#define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/.cache") #define NFC_DEVICE_KEYS_EXTENSION ".keys" static const char* nfc_file_header = "Flipper NFC device"; -static const uint32_t nfc_file_version = 2; +static const uint32_t nfc_file_version = 3; static const char* nfc_keys_file_header = "Flipper NFC keys"; static const uint32_t nfc_keys_file_version = 1; @@ -27,6 +27,11 @@ NfcDevice* nfc_device_alloc() { nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS); nfc_dev->load_path = furi_string_alloc(); nfc_dev->dev_data.parsed_data = furi_string_alloc(); + + // Rename cache folder name for backward compatibility + if(storage_common_stat(nfc_dev->storage, "/ext/nfc/cache", NULL) == FSE_OK) { + storage_common_rename(nfc_dev->storage, "/ext/nfc/cache", NFC_DEVICE_KEYS_FOLDER); + } return nfc_dev; } @@ -1041,7 +1046,9 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) { if(!flipper_format_write_comment_cstr(file, "UID, ATQA and SAK are common for all formats")) break; if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break; - if(!flipper_format_write_hex(file, "ATQA", data->a_data.atqa, 2)) break; + // Save ATQA in MSB order for correct companion apps display + uint8_t atqa[2] = {data->a_data.atqa[1], data->a_data.atqa[0]}; + if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; if(!flipper_format_write_hex(file, "SAK", &data->a_data.sak, 1)) break; // Save more data if necessary if(dev->format == NfcDeviceSaveFormatMifareUl) { @@ -1089,6 +1096,9 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia temp_str = furi_string_alloc(); bool deprecated_version = false; + // Version 2 of file format had ATQA bytes swapped + uint32_t version_with_lsb_atqa = 2; + if(dev->loading_cb) { dev->loading_cb(dev->loading_cb_ctx, true); } @@ -1107,9 +1117,12 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia // Read and verify file header uint32_t version = 0; if(!flipper_format_read_header(file, temp_str, &version)) break; - if(furi_string_cmp_str(temp_str, nfc_file_header) || (version != nfc_file_version)) { - deprecated_version = true; - break; + if(furi_string_cmp_str(temp_str, nfc_file_header)) break; + if(version != nfc_file_version) { + if(version < version_with_lsb_atqa) { + deprecated_version = true; + break; + } } // Read Nfc device type if(!flipper_format_read_string(file, "Device type", temp_str)) break; @@ -1119,7 +1132,14 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia if(!(data_cnt == 4 || data_cnt == 7)) break; data->uid_len = data_cnt; if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; - if(!flipper_format_read_hex(file, "ATQA", data->a_data.atqa, 2)) break; + if(version == version_with_lsb_atqa) { + if(!flipper_format_read_hex(file, "ATQA", data->a_data.atqa, 2)) break; + } else { + uint8_t atqa[2] = {}; + if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; + data->a_data.atqa[0] = atqa[1]; + data->a_data.atqa[1] = atqa[0]; + } if(!flipper_format_read_hex(file, "SAK", &data->a_data.sak, 1)) break; // Load CUID uint8_t* cuid_start = data->uid; @@ -1187,10 +1207,12 @@ bool nfc_file_select(NfcDevice* dev) { const DialogsFileBrowserOptions browser_options = { .extension = NFC_APP_EXTENSION, .skip_assets = true, + .hide_dot_files = true, .icon = &I_Nfc_10px, .hide_ext = true, .item_loader_callback = NULL, .item_loader_context = NULL, + .base_path = NFC_APP_FOLDER, }; bool res = diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index b7a52bc01..f4c7353a8 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -712,46 +712,16 @@ uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data furi_assert(tx_rx); furi_assert(data); - uint8_t sectors_read = 0; - Crypto1 crypto = {}; uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type); - uint64_t key_a = 0; - uint64_t key_b = 0; - MfClassicSectorReader sec_reader = {}; - MfClassicSector temp_sector = {}; for(size_t i = 0; i < total_sectors; i++) { - MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); - // Load key A - if(mf_classic_is_key_found(data, i, MfClassicKeyA)) { - sec_reader.key_a = nfc_util_bytes2num(sec_tr->key_a, 6); - } else { - sec_reader.key_a = MF_CLASSIC_NO_KEY; - } - // Load key B - if(mf_classic_is_key_found(data, i, MfClassicKeyB)) { - sec_reader.key_b = nfc_util_bytes2num(sec_tr->key_b, 6); - } else { - sec_reader.key_b = MF_CLASSIC_NO_KEY; - } - if((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)) { - sec_reader.sector_num = i; - if(mf_classic_read_sector_with_reader(tx_rx, &crypto, &sec_reader, &temp_sector)) { - uint8_t first_block = mf_classic_get_first_block_num_of_sector(i); - for(uint8_t j = 0; j < temp_sector.total_blocks; j++) { - mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]); - } - sectors_read++; - } else { - // Invalid key, set it to not found - if(key_a != MF_CLASSIC_NO_KEY) { - mf_classic_set_key_not_found(data, i, MfClassicKeyA); - } else { - mf_classic_set_key_not_found(data, i, MfClassicKeyB); - } - } - } + mf_classic_read_sector(tx_rx, data, i); } + uint8_t sectors_read = 0; + uint8_t keys_found = 0; + mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); + FURI_LOG_D(TAG, "Read %d sectors and %d keys", sectors_read, keys_found); + return sectors_read; } diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 6b53f7477..9c9f52958 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -238,9 +238,9 @@ class FlipperStorage: while read_size < size: self.read.until("Ready?" + self.CLI_EOL) self.send("y") - read_size = min(size - read_size, buffer_size) - filedata.extend(self.port.read(read_size)) - read_size = read_size + read_size + chunk_size = min(size - read_size, buffer_size) + filedata.extend(self.port.read(chunk_size)) + read_size = read_size + chunk_size percent = str(math.ceil(read_size / size * 100)) total_chunks = str(math.ceil(size / buffer_size))