Merge remote-tracking branch 'origin/release-candidate' into release
							
								
								
									
										2
									
								
								.github/workflows/unit_tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -10,7 +10,7 @@ env: | |||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   run_units_on_bench: |   run_units_on_bench: | ||||||
|     runs-on: [self-hosted, FlipperZeroTest] |     runs-on: [self-hosted, FlipperZeroUnitTest] | ||||||
|     steps: |     steps: | ||||||
|       - name: 'Decontaminate previous build leftovers' |       - name: 'Decontaminate previous build leftovers' | ||||||
|         run: | |         run: | | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								.github/workflows/updater_test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -10,7 +10,7 @@ env: | |||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   test_updater_on_bench: |   test_updater_on_bench: | ||||||
|     runs-on: [self-hosted, FlipperZeroTestMac1] |     runs-on: [self-hosted, FlipperZeroUpdaterTest] | ||||||
|     steps: |     steps: | ||||||
|       - name: 'Decontaminate previous build leftovers' |       - name: 'Decontaminate previous build leftovers' | ||||||
|         run: | |         run: | | ||||||
| @ -27,7 +27,7 @@ jobs: | |||||||
|       - name: 'Get flipper from device manager (mock)' |       - name: 'Get flipper from device manager (mock)' | ||||||
|         id: device |         id: device | ||||||
|         run: | |         run: | | ||||||
|           echo "flipper=/dev/tty.usbmodemflip_Rekigyn1" >> $GITHUB_OUTPUT |           echo "flipper=Rekigyn" >> $GITHUB_OUTPUT | ||||||
|           echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT |           echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT | ||||||
| 
 | 
 | ||||||
|       - name: 'Flashing target firmware' |       - name: 'Flashing target firmware' | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								.vscode/example/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -11,9 +11,10 @@ | |||||||
|             "args": { |             "args": { | ||||||
|                 "useSingleResult": true, |                 "useSingleResult": true, | ||||||
|                 "env": { |                 "env": { | ||||||
|                     "PATH": "${workspaceFolder};${env:PATH}" |                     "PATH": "${workspaceFolder};${env:PATH}", | ||||||
|  |                     "FBT_QUIET": 1 | ||||||
|                 }, |                 }, | ||||||
|                 "command": "./fbt get_blackmagic", |                 "command": "fbt get_blackmagic", | ||||||
|                 "description": "Get Blackmagic device", |                 "description": "Get Blackmagic device", | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -3,56 +3,63 @@ | |||||||
| #include "../minunit.h" | #include "../minunit.h" | ||||||
| 
 | 
 | ||||||
| static void power_test_deinit(void) { | static void power_test_deinit(void) { | ||||||
|     // Try to reset to default charging voltage
 |     // Try to reset to default charge voltage limit
 | ||||||
|     furi_hal_power_set_battery_charging_voltage(4.208f); |     furi_hal_power_set_battery_charge_voltage_limit(4.208f); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MU_TEST(test_power_charge_voltage_exact) { | MU_TEST(test_power_charge_voltage_limit_exact) { | ||||||
|     // Power of 16mV charge voltages get applied exactly
 |     // Power of 16mV charge voltage limits get applied exactly
 | ||||||
|     // (bq25896 charge controller works in 16mV increments)
 |     // (bq25896 charge controller works in 16mV increments)
 | ||||||
|     //
 |     //
 | ||||||
|     // This test may need adapted if other charge controllers are used in the future.
 |     // 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) { |     for(uint16_t charge_mv = 3840; charge_mv <= 4208; charge_mv += 16) { | ||||||
|         float charge_volt = (float)charge_mv / 1000.0f; |         float charge_volt = (float)charge_mv / 1000.0f; | ||||||
|         furi_hal_power_set_battery_charging_voltage(charge_volt); |         furi_hal_power_set_battery_charge_voltage_limit(charge_volt); | ||||||
|         mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charging_voltage()); |         mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charge_voltage_limit()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MU_TEST(test_power_charge_voltage_floating_imprecision) { | MU_TEST(test_power_charge_voltage_limit_floating_imprecision) { | ||||||
|     // 4.016f should act as 4.016 V, even with floating point imprecision
 |     // 4.016f should act as 4.016 V, even with floating point imprecision
 | ||||||
|     furi_hal_power_set_battery_charging_voltage(4.016f); |     furi_hal_power_set_battery_charge_voltage_limit(4.016f); | ||||||
|     mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charging_voltage()); |     mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charge_voltage_limit()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MU_TEST(test_power_charge_voltage_inexact) { | MU_TEST(test_power_charge_voltage_limit_inexact) { | ||||||
|     // Charge voltages that are not power of 16mV get truncated down
 |     // Charge voltage limits that are not power of 16mV get truncated down
 | ||||||
|     furi_hal_power_set_battery_charging_voltage(3.841f); |     furi_hal_power_set_battery_charge_voltage_limit(3.841f); | ||||||
|     mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage()); |     mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit()); | ||||||
| 
 | 
 | ||||||
|     furi_hal_power_set_battery_charging_voltage(3.900f); |     furi_hal_power_set_battery_charge_voltage_limit(3.900f); | ||||||
|     mu_assert_double_eq(3.888, furi_hal_power_get_battery_charging_voltage()); |     mu_assert_double_eq(3.888, furi_hal_power_get_battery_charge_voltage_limit()); | ||||||
| 
 | 
 | ||||||
|     furi_hal_power_set_battery_charging_voltage(4.200f); |     furi_hal_power_set_battery_charge_voltage_limit(4.200f); | ||||||
|     mu_assert_double_eq(4.192, furi_hal_power_get_battery_charging_voltage()); |     mu_assert_double_eq(4.192, furi_hal_power_get_battery_charge_voltage_limit()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MU_TEST(test_power_charge_voltage_invalid_clamped) { | MU_TEST(test_power_charge_voltage_limit_invalid_clamped) { | ||||||
|     // Out-of-range charge voltages get clamped to 3.840 V and 4.208 V
 |     // Out-of-range charge voltage limits get clamped to 3.840 V and 4.208 V
 | ||||||
|     furi_hal_power_set_battery_charging_voltage(3.808f); |     furi_hal_power_set_battery_charge_voltage_limit(3.808f); | ||||||
|     mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage()); |     mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit()); | ||||||
|  |     furi_hal_power_set_battery_charge_voltage_limit(1.0f); | ||||||
|  |     mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit()); | ||||||
| 
 | 
 | ||||||
|     // NOTE: Intentionally picking a small increment above 4.208 V to reduce the risk of an
 |     // NOTE: Intentionally picking a small increment above 4.208 V to reduce the risk of an
 | ||||||
|     // unhappy battery if this fails.
 |     // unhappy battery if this fails.
 | ||||||
|     furi_hal_power_set_battery_charging_voltage(4.240f); |     furi_hal_power_set_battery_charge_voltage_limit(4.240f); | ||||||
|     mu_assert_double_eq(4.208, furi_hal_power_get_battery_charging_voltage()); |     mu_assert_double_eq(4.208, furi_hal_power_get_battery_charge_voltage_limit()); | ||||||
|  |     // Likewise, picking a number that the uint8_t wraparound in the driver would result in a
 | ||||||
|  |     // VREG value under 23 if this test fails.
 | ||||||
|  |     // E.g. (uint8_t)((8105-3840)/16) -> 10
 | ||||||
|  |     furi_hal_power_set_battery_charge_voltage_limit(8.105f); | ||||||
|  |     mu_assert_double_eq(4.208, furi_hal_power_get_battery_charge_voltage_limit()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MU_TEST_SUITE(test_power_suite) { | MU_TEST_SUITE(test_power_suite) { | ||||||
|     MU_RUN_TEST(test_power_charge_voltage_exact); |     MU_RUN_TEST(test_power_charge_voltage_limit_exact); | ||||||
|     MU_RUN_TEST(test_power_charge_voltage_floating_imprecision); |     MU_RUN_TEST(test_power_charge_voltage_limit_floating_imprecision); | ||||||
|     MU_RUN_TEST(test_power_charge_voltage_inexact); |     MU_RUN_TEST(test_power_charge_voltage_limit_inexact); | ||||||
|     MU_RUN_TEST(test_power_charge_voltage_invalid_clamped); |     MU_RUN_TEST(test_power_charge_voltage_limit_invalid_clamped); | ||||||
|     power_test_deinit(); |     power_test_deinit(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -142,10 +142,6 @@ void bad_usb_app_free(BadUsbApp* app) { | |||||||
|         app->bad_usb_script = NULL; |         app->bad_usb_script = NULL; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(app->usb_if_prev) { |  | ||||||
|         furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Views
 |     // Views
 | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork); |     view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork); | ||||||
|     bad_usb_free(app->bad_usb_view); |     bad_usb_free(app->bad_usb_view); | ||||||
| @ -172,6 +168,10 @@ void bad_usb_app_free(BadUsbApp* app) { | |||||||
|     furi_string_free(app->file_path); |     furi_string_free(app->file_path); | ||||||
|     furi_string_free(app->keyboard_layout); |     furi_string_free(app->keyboard_layout); | ||||||
| 
 | 
 | ||||||
|  |     if(app->usb_if_prev) { | ||||||
|  |         furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     free(app); |     free(app); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -32,6 +32,7 @@ struct BadUsbScript { | |||||||
|     FuriString* file_path; |     FuriString* file_path; | ||||||
|     uint32_t defdelay; |     uint32_t defdelay; | ||||||
|     uint16_t layout[128]; |     uint16_t layout[128]; | ||||||
|  |     uint32_t stringdelay; | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
|     uint8_t file_buf[FILE_BUFFER_LEN + 1]; |     uint8_t file_buf[FILE_BUFFER_LEN + 1]; | ||||||
|     uint8_t buf_start; |     uint8_t buf_start; | ||||||
| @ -113,6 +114,8 @@ static const char ducky_cmd_delay[] = {"DELAY "}; | |||||||
| static const char ducky_cmd_string[] = {"STRING "}; | static const char ducky_cmd_string[] = {"STRING "}; | ||||||
| static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "}; | static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "}; | ||||||
| static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "}; | static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "}; | ||||||
|  | static const char ducky_cmd_stringdelay_1[] = {"STRINGDELAY "}; | ||||||
|  | static const char ducky_cmd_stringdelay_2[] = {"STRING_DELAY "}; | ||||||
| static const char ducky_cmd_repeat[] = {"REPEAT "}; | static const char ducky_cmd_repeat[] = {"REPEAT "}; | ||||||
| static const char ducky_cmd_sysrq[] = {"SYSRQ "}; | static const char ducky_cmd_sysrq[] = {"SYSRQ "}; | ||||||
| 
 | 
 | ||||||
| @ -211,14 +214,19 @@ static bool ducky_altstring(const char* param) { | |||||||
| 
 | 
 | ||||||
| static bool ducky_string(BadUsbScript* bad_usb, const char* param) { | static bool ducky_string(BadUsbScript* bad_usb, const char* param) { | ||||||
|     uint32_t i = 0; |     uint32_t i = 0; | ||||||
|  | 
 | ||||||
|     while(param[i] != '\0') { |     while(param[i] != '\0') { | ||||||
|         uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]); |         uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]); | ||||||
|         if(keycode != HID_KEYBOARD_NONE) { |         if(keycode != HID_KEYBOARD_NONE) { | ||||||
|             furi_hal_hid_kb_press(keycode); |             furi_hal_hid_kb_press(keycode); | ||||||
|             furi_hal_hid_kb_release(keycode); |             furi_hal_hid_kb_release(keycode); | ||||||
|  |             if(bad_usb->stringdelay > 0) { | ||||||
|  |                 furi_delay_ms(bad_usb->stringdelay); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         i++; |         i++; | ||||||
|     } |     } | ||||||
|  |     bad_usb->stringdelay = 0; | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -277,6 +285,20 @@ static int32_t | |||||||
|             snprintf(error, error_len, "Invalid number %s", line_tmp); |             snprintf(error, error_len, "Invalid number %s", line_tmp); | ||||||
|         } |         } | ||||||
|         return (state) ? (0) : SCRIPT_STATE_ERROR; |         return (state) ? (0) : SCRIPT_STATE_ERROR; | ||||||
|  |     } else if( | ||||||
|  |         (strncmp(line_tmp, ducky_cmd_stringdelay_1, strlen(ducky_cmd_stringdelay_1)) == 0) || | ||||||
|  |         (strncmp(line_tmp, ducky_cmd_stringdelay_2, strlen(ducky_cmd_stringdelay_2)) == 0)) { | ||||||
|  |         //STRINGDELAY, finally it's here
 | ||||||
|  |         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||||
|  |         state = ducky_get_number(line_tmp, &bad_usb->stringdelay); | ||||||
|  |         if((state) && (bad_usb->stringdelay > 0)) { | ||||||
|  |             return state; | ||||||
|  |         } | ||||||
|  |         if(error != NULL) { | ||||||
|  |             snprintf(error, error_len, "Invalid number %s", line_tmp); | ||||||
|  |         } | ||||||
|  |         return SCRIPT_STATE_ERROR; | ||||||
|  | 
 | ||||||
|     } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { |     } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) { | ||||||
|         // STRING
 |         // STRING
 | ||||||
|         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; |         line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; | ||||||
| @ -484,6 +506,19 @@ static void bad_usb_hid_state_callback(bool state, void* context) { | |||||||
|         furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect); |         furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static uint32_t bad_usb_flags_get(uint32_t flags_mask, uint32_t timeout) { | ||||||
|  |     uint32_t flags = furi_thread_flags_get(); | ||||||
|  |     furi_check((flags & FuriFlagError) == 0); | ||||||
|  |     if(flags == 0) { | ||||||
|  |         flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); | ||||||
|  |         furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout)); | ||||||
|  |     } else { | ||||||
|  |         uint32_t state = furi_thread_flags_clear(flags); | ||||||
|  |         furi_check((state & FuriFlagError) == 0); | ||||||
|  |     } | ||||||
|  |     return flags; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int32_t bad_usb_worker(void* context) { | static int32_t bad_usb_worker(void* context) { | ||||||
|     BadUsbScript* bad_usb = context; |     BadUsbScript* bad_usb = context; | ||||||
| 
 | 
 | ||||||
| @ -520,11 +555,9 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|             bad_usb->st.state = worker_state; |             bad_usb->st.state = worker_state; | ||||||
| 
 | 
 | ||||||
|         } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
 |         } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
 | ||||||
|             uint32_t flags = furi_thread_flags_wait( |             uint32_t flags = bad_usb_flags_get( | ||||||
|                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, |                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); | ||||||
|                 FuriFlagWaitAny, | 
 | ||||||
|                 FuriWaitForever); |  | ||||||
|             furi_check((flags & FuriFlagError) == 0); |  | ||||||
|             if(flags & WorkerEvtEnd) { |             if(flags & WorkerEvtEnd) { | ||||||
|                 break; |                 break; | ||||||
|             } else if(flags & WorkerEvtConnect) { |             } else if(flags & WorkerEvtConnect) { | ||||||
| @ -535,11 +568,9 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|             bad_usb->st.state = worker_state; |             bad_usb->st.state = worker_state; | ||||||
| 
 | 
 | ||||||
|         } else if(worker_state == BadUsbStateIdle) { // State: ready to start
 |         } else if(worker_state == BadUsbStateIdle) { // State: ready to start
 | ||||||
|             uint32_t flags = furi_thread_flags_wait( |             uint32_t flags = bad_usb_flags_get( | ||||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, |                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriWaitForever); | ||||||
|                 FuriFlagWaitAny, | 
 | ||||||
|                 FuriWaitForever); |  | ||||||
|             furi_check((flags & FuriFlagError) == 0); |  | ||||||
|             if(flags & WorkerEvtEnd) { |             if(flags & WorkerEvtEnd) { | ||||||
|                 break; |                 break; | ||||||
|             } else if(flags & WorkerEvtToggle) { // Start executing script
 |             } else if(flags & WorkerEvtToggle) { // Start executing script
 | ||||||
| @ -548,6 +579,7 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|                 bad_usb->buf_len = 0; |                 bad_usb->buf_len = 0; | ||||||
|                 bad_usb->st.line_cur = 0; |                 bad_usb->st.line_cur = 0; | ||||||
|                 bad_usb->defdelay = 0; |                 bad_usb->defdelay = 0; | ||||||
|  |                 bad_usb->stringdelay = 0; | ||||||
|                 bad_usb->repeat_cnt = 0; |                 bad_usb->repeat_cnt = 0; | ||||||
|                 bad_usb->file_end = false; |                 bad_usb->file_end = false; | ||||||
|                 storage_file_seek(script_file, 0, true); |                 storage_file_seek(script_file, 0, true); | ||||||
| @ -558,11 +590,9 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|             bad_usb->st.state = worker_state; |             bad_usb->st.state = worker_state; | ||||||
| 
 | 
 | ||||||
|         } else if(worker_state == BadUsbStateWillRun) { // State: start on connection
 |         } else if(worker_state == BadUsbStateWillRun) { // State: start on connection
 | ||||||
|             uint32_t flags = furi_thread_flags_wait( |             uint32_t flags = bad_usb_flags_get( | ||||||
|                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, |                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever); | ||||||
|                 FuriFlagWaitAny, | 
 | ||||||
|                 FuriWaitForever); |  | ||||||
|             furi_check((flags & FuriFlagError) == 0); |  | ||||||
|             if(flags & WorkerEvtEnd) { |             if(flags & WorkerEvtEnd) { | ||||||
|                 break; |                 break; | ||||||
|             } else if(flags & WorkerEvtConnect) { // Start executing script
 |             } else if(flags & WorkerEvtConnect) { // Start executing script
 | ||||||
| @ -571,12 +601,22 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|                 bad_usb->buf_len = 0; |                 bad_usb->buf_len = 0; | ||||||
|                 bad_usb->st.line_cur = 0; |                 bad_usb->st.line_cur = 0; | ||||||
|                 bad_usb->defdelay = 0; |                 bad_usb->defdelay = 0; | ||||||
|  |                 bad_usb->stringdelay = 0; | ||||||
|                 bad_usb->repeat_cnt = 0; |                 bad_usb->repeat_cnt = 0; | ||||||
|                 bad_usb->file_end = false; |                 bad_usb->file_end = false; | ||||||
|                 storage_file_seek(script_file, 0, true); |                 storage_file_seek(script_file, 0, true); | ||||||
|                 // extra time for PC to recognize Flipper as keyboard
 |                 // extra time for PC to recognize Flipper as keyboard
 | ||||||
|                 furi_thread_flags_wait(0, FuriFlagWaitAny, 1500); |                 flags = furi_thread_flags_wait( | ||||||
|  |                     WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtToggle, | ||||||
|  |                     FuriFlagWaitAny | FuriFlagNoClear, | ||||||
|  |                     1500); | ||||||
|  |                 if(flags == (unsigned)FuriFlagErrorTimeout) { | ||||||
|  |                     // If nothing happened - start script execution
 | ||||||
|                     worker_state = BadUsbStateRunning; |                     worker_state = BadUsbStateRunning; | ||||||
|  |                 } else if(flags & WorkerEvtToggle) { | ||||||
|  |                     worker_state = BadUsbStateIdle; | ||||||
|  |                     furi_thread_flags_clear(WorkerEvtToggle); | ||||||
|  |                 } | ||||||
|             } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution
 |             } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution
 | ||||||
|                 worker_state = BadUsbStateNotConnected; |                 worker_state = BadUsbStateNotConnected; | ||||||
|             } |             } | ||||||
| @ -586,6 +626,7 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); |             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); | ||||||
|             uint32_t flags = furi_thread_flags_wait( |             uint32_t flags = furi_thread_flags_wait( | ||||||
|                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); |                 WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur); | ||||||
|  | 
 | ||||||
|             delay_val -= delay_cur; |             delay_val -= delay_cur; | ||||||
|             if(!(flags & FuriFlagError)) { |             if(!(flags & FuriFlagError)) { | ||||||
|                 if(flags & WorkerEvtEnd) { |                 if(flags & WorkerEvtEnd) { | ||||||
| @ -629,9 +670,9 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|         } else if( |         } else if( | ||||||
|             (worker_state == BadUsbStateFileError) || |             (worker_state == BadUsbStateFileError) || | ||||||
|             (worker_state == BadUsbStateScriptError)) { // State: error
 |             (worker_state == BadUsbStateScriptError)) { // State: error
 | ||||||
|             uint32_t flags = furi_thread_flags_wait( |             uint32_t flags = | ||||||
|                 WorkerEvtEnd, FuriFlagWaitAny, FuriWaitForever); // Waiting for exit command
 |                 bad_usb_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command
 | ||||||
|             furi_check((flags & FuriFlagError) == 0); | 
 | ||||||
|             if(flags & WorkerEvtEnd) { |             if(flags & WorkerEvtEnd) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -22,7 +22,6 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) { | |||||||
| void bad_usb_scene_file_select_on_enter(void* context) { | void bad_usb_scene_file_select_on_enter(void* context) { | ||||||
|     BadUsbApp* bad_usb = context; |     BadUsbApp* bad_usb = context; | ||||||
| 
 | 
 | ||||||
|     furi_hal_usb_disable(); |  | ||||||
|     if(bad_usb->bad_usb_script) { |     if(bad_usb->bad_usb_script) { | ||||||
|         bad_usb_script_close(bad_usb->bad_usb_script); |         bad_usb_script_close(bad_usb->bad_usb_script); | ||||||
|         bad_usb->bad_usb_script = NULL; |         bad_usb->bad_usb_script = NULL; | ||||||
| @ -34,7 +33,6 @@ void bad_usb_scene_file_select_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|         scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork); |         scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork); | ||||||
|     } else { |     } else { | ||||||
|         furi_hal_usb_enable(); |  | ||||||
|         view_dispatcher_stop(bad_usb->view_dispatcher); |         view_dispatcher_stop(bad_usb->view_dispatcher); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,7 +16,9 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == InputKeyLeft) { |         if(event.event == InputKeyLeft) { | ||||||
|  |             if(bad_usb_is_idle_state(app->bad_usb_view)) { | ||||||
|                 scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); |                 scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); | ||||||
|  |             } | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == InputKeyOk) { |         } else if(event.event == InputKeyOk) { | ||||||
|             bad_usb_script_toggle(app->bad_usb_script); |             bad_usb_script_toggle(app->bad_usb_script); | ||||||
|  | |||||||
| @ -48,17 +48,13 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | |||||||
|     if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || |     if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || | ||||||
|        (model->state.state == BadUsbStateNotConnected)) { |        (model->state.state == BadUsbStateNotConnected)) { | ||||||
|         elements_button_center(canvas, "Run"); |         elements_button_center(canvas, "Run"); | ||||||
|  |         elements_button_left(canvas, "Config"); | ||||||
|     } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) { |     } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) { | ||||||
|         elements_button_center(canvas, "Stop"); |         elements_button_center(canvas, "Stop"); | ||||||
|     } else if(model->state.state == BadUsbStateWillRun) { |     } else if(model->state.state == BadUsbStateWillRun) { | ||||||
|         elements_button_center(canvas, "Cancel"); |         elements_button_center(canvas, "Cancel"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if((model->state.state == BadUsbStateNotConnected) || |  | ||||||
|        (model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) { |  | ||||||
|         elements_button_left(canvas, "Config"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(model->state.state == BadUsbStateNotConnected) { |     if(model->state.state == BadUsbStateNotConnected) { | ||||||
|         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); |         canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); | ||||||
|         canvas_set_font(canvas, FontPrimary); |         canvas_set_font(canvas, FontPrimary); | ||||||
| @ -203,6 +199,7 @@ void bad_usb_set_layout(BadUsb* bad_usb, const char* layout) { | |||||||
|         { strlcpy(model->layout, layout, MAX_NAME_LEN); }, |         { strlcpy(model->layout, layout, MAX_NAME_LEN); }, | ||||||
|         true); |         true); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) { | void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) { | ||||||
|     furi_assert(st); |     furi_assert(st); | ||||||
|     with_view_model( |     with_view_model( | ||||||
| @ -214,3 +211,19 @@ void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) { | |||||||
|         }, |         }, | ||||||
|         true); |         true); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | bool bad_usb_is_idle_state(BadUsb* bad_usb) { | ||||||
|  |     bool is_idle = false; | ||||||
|  |     with_view_model( | ||||||
|  |         bad_usb->view, | ||||||
|  |         BadUsbModel * model, | ||||||
|  |         { | ||||||
|  |             if((model->state.state == BadUsbStateIdle) || | ||||||
|  |                (model->state.state == BadUsbStateDone) || | ||||||
|  |                (model->state.state == BadUsbStateNotConnected)) { | ||||||
|  |                 is_idle = true; | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         false); | ||||||
|  |     return is_idle; | ||||||
|  | } | ||||||
|  | |||||||
| @ -19,3 +19,5 @@ void bad_usb_set_file_name(BadUsb* bad_usb, const char* name); | |||||||
| void bad_usb_set_layout(BadUsb* bad_usb, const char* layout); | void bad_usb_set_layout(BadUsb* bad_usb, const char* layout); | ||||||
| 
 | 
 | ||||||
| void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); | void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); | ||||||
|  | 
 | ||||||
|  | bool bad_usb_is_idle_state(BadUsb* bad_usb); | ||||||
|  | |||||||
| @ -32,7 +32,7 @@ static void nfc_cli_detect(Cli* cli, FuriString* args) { | |||||||
|     while(!cmd_exit) { |     while(!cmd_exit) { | ||||||
|         cmd_exit |= cli_cmd_interrupt_received(cli); |         cmd_exit |= cli_cmd_interrupt_received(cli); | ||||||
|         if(furi_hal_nfc_detect(&dev_data, 400)) { |         if(furi_hal_nfc_detect(&dev_data, 400)) { | ||||||
|             printf("found: %s ", nfc_get_dev_type(dev_data.type)); |             printf("Found: %s ", nfc_get_dev_type(dev_data.type)); | ||||||
|             printf("UID length: %d, UID:", dev_data.uid_len); |             printf("UID length: %d, UID:", dev_data.uid_len); | ||||||
|             for(size_t i = 0; i < dev_data.uid_len; i++) { |             for(size_t i = 0; i < dev_data.uid_len; i++) { | ||||||
|                 printf("%02X", dev_data.uid[i]); |                 printf("%02X", dev_data.uid[i]); | ||||||
|  | |||||||
| @ -29,6 +29,7 @@ ADD_SCENE(nfc, mf_desfire_menu, MfDesfireMenu) | |||||||
| ADD_SCENE(nfc, mf_desfire_data, MfDesfireData) | ADD_SCENE(nfc, mf_desfire_data, MfDesfireData) | ||||||
| ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) | ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) | ||||||
| ADD_SCENE(nfc, mf_classic_read_success, MfClassicReadSuccess) | ADD_SCENE(nfc, mf_classic_read_success, MfClassicReadSuccess) | ||||||
|  | ADD_SCENE(nfc, mf_classic_data, MfClassicData) | ||||||
| ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu) | ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu) | ||||||
| ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate) | ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate) | ||||||
| ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) | ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) | ||||||
|  | |||||||
							
								
								
									
										106
									
								
								applications/main/nfc/scenes/nfc_scene_mf_classic_data.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,106 @@ | |||||||
|  | #include "../nfc_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_data_on_enter(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     MfClassicType type = nfc->dev->dev_data.mf_classic_data.type; | ||||||
|  |     MfClassicData* data = &nfc->dev->dev_data.mf_classic_data; | ||||||
|  |     TextBox* text_box = nfc->text_box; | ||||||
|  | 
 | ||||||
|  |     text_box_set_font(text_box, TextBoxFontHex); | ||||||
|  | 
 | ||||||
|  |     int card_blocks = 0; | ||||||
|  |     if(type == MfClassicType1k) { | ||||||
|  |         card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; | ||||||
|  |     } else if(type == MfClassicType4k) { | ||||||
|  |         // 16 sectors of 4 blocks each plus 8 sectors of 16 blocks each
 | ||||||
|  |         card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4 + 8 * 16; | ||||||
|  |     } else if(type == MfClassicTypeMini) { | ||||||
|  |         card_blocks = MF_MINI_TOTAL_SECTORS_NUM * 4; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     int bytes_written = 0; | ||||||
|  |     for(int block_num = 0; block_num < card_blocks; block_num++) { | ||||||
|  |         bool is_sec_trailer = mf_classic_is_sector_trailer(block_num); | ||||||
|  |         if(is_sec_trailer) { | ||||||
|  |             uint8_t sector_num = mf_classic_get_sector_by_block(block_num); | ||||||
|  |             MfClassicSectorTrailer* sec_tr = | ||||||
|  |                 mf_classic_get_sector_trailer_by_sector(data, sector_num); | ||||||
|  |             // Key A
 | ||||||
|  |             for(size_t i = 0; i < sizeof(sec_tr->key_a); i += 2) { | ||||||
|  |                 if((bytes_written % 8 == 0) && (bytes_written != 0)) { | ||||||
|  |                     furi_string_push_back(nfc->text_box_store, '\n'); | ||||||
|  |                 } | ||||||
|  |                 if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) { | ||||||
|  |                     furi_string_cat_printf( | ||||||
|  |                         nfc->text_box_store, "%02X%02X ", sec_tr->key_a[i], sec_tr->key_a[i + 1]); | ||||||
|  |                 } else { | ||||||
|  |                     furi_string_cat_printf(nfc->text_box_store, "???? "); | ||||||
|  |                 } | ||||||
|  |                 bytes_written += 2; | ||||||
|  |             } | ||||||
|  |             // Access bytes
 | ||||||
|  |             for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i += 2) { | ||||||
|  |                 if((bytes_written % 8 == 0) && (bytes_written != 0)) { | ||||||
|  |                     furi_string_push_back(nfc->text_box_store, '\n'); | ||||||
|  |                 } | ||||||
|  |                 if(mf_classic_is_block_read(data, block_num)) { | ||||||
|  |                     furi_string_cat_printf( | ||||||
|  |                         nfc->text_box_store, | ||||||
|  |                         "%02X%02X ", | ||||||
|  |                         sec_tr->access_bits[i], | ||||||
|  |                         sec_tr->access_bits[i + 1]); | ||||||
|  |                 } else { | ||||||
|  |                     furi_string_cat_printf(nfc->text_box_store, "???? "); | ||||||
|  |                 } | ||||||
|  |                 bytes_written += 2; | ||||||
|  |             } | ||||||
|  |             // Key B
 | ||||||
|  |             for(size_t i = 0; i < sizeof(sec_tr->key_b); i += 2) { | ||||||
|  |                 if((bytes_written % 8 == 0) && (bytes_written != 0)) { | ||||||
|  |                     furi_string_push_back(nfc->text_box_store, '\n'); | ||||||
|  |                 } | ||||||
|  |                 if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) { | ||||||
|  |                     furi_string_cat_printf( | ||||||
|  |                         nfc->text_box_store, "%02X%02X ", sec_tr->key_b[i], sec_tr->key_b[i + 1]); | ||||||
|  |                 } else { | ||||||
|  |                     furi_string_cat_printf(nfc->text_box_store, "???? "); | ||||||
|  |                 } | ||||||
|  |                 bytes_written += 2; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             // Write data block
 | ||||||
|  |             for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i += 2) { | ||||||
|  |                 if((bytes_written % 8 == 0) && (bytes_written != 0)) { | ||||||
|  |                     furi_string_push_back(nfc->text_box_store, '\n'); | ||||||
|  |                 } | ||||||
|  |                 if(mf_classic_is_block_read(data, block_num)) { | ||||||
|  |                     furi_string_cat_printf( | ||||||
|  |                         nfc->text_box_store, | ||||||
|  |                         "%02X%02X ", | ||||||
|  |                         data->block[block_num].value[i], | ||||||
|  |                         data->block[block_num].value[i + 1]); | ||||||
|  |                 } else { | ||||||
|  |                     furi_string_cat_printf(nfc->text_box_store, "???? "); | ||||||
|  |                 } | ||||||
|  |                 bytes_written += 2; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_classic_data_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     UNUSED(event); | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_data_on_exit(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     // Clean view
 | ||||||
|  |     text_box_reset(nfc->text_box); | ||||||
|  |     furi_string_reset(nfc->text_box_store); | ||||||
|  | } | ||||||
| @ -14,7 +14,8 @@ void nfc_scene_nfc_data_info_on_enter(void* context) { | |||||||
|     NfcDeviceData* dev_data = &nfc->dev->dev_data; |     NfcDeviceData* dev_data = &nfc->dev->dev_data; | ||||||
|     NfcProtocol protocol = dev_data->protocol; |     NfcProtocol protocol = dev_data->protocol; | ||||||
|     uint8_t text_scroll_height = 0; |     uint8_t text_scroll_height = 0; | ||||||
|     if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl)) { |     if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl) || | ||||||
|  |        (protocol == NfcDeviceProtocolMifareClassic)) { | ||||||
|         widget_add_button_element( |         widget_add_button_element( | ||||||
|             widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc); |             widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc); | ||||||
|         text_scroll_height = 52; |         text_scroll_height = 52; | ||||||
| @ -136,6 +137,9 @@ bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) { | |||||||
|             } else if(protocol == NfcDeviceProtocolMifareUl) { |             } else if(protocol == NfcDeviceProtocolMifareUl) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData); | ||||||
|                 consumed = true; |                 consumed = true; | ||||||
|  |             } else if(protocol == NfcDeviceProtocolMifareClassic) { | ||||||
|  |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicData); | ||||||
|  |                 consumed = true; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -150,6 +150,8 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { | |||||||
|                 application_info_present = nfc_supported_card_verify_and_parse(dev_data); |                 application_info_present = nfc_supported_card_verify_and_parse(dev_data); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             FURI_LOG_I("nfc", "application_info_present: %d", application_info_present); | ||||||
|  | 
 | ||||||
|             if(application_info_present) { |             if(application_info_present) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); | ||||||
|             } else { |             } else { | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ App( | |||||||
|     stack_size=4 * 1024, |     stack_size=4 * 1024, | ||||||
|     order=20, |     order=20, | ||||||
|     fap_icon="dap_link.png", |     fap_icon="dap_link.png", | ||||||
|     fap_category="Tools", |     fap_category="GPIO", | ||||||
|     fap_private_libs=[ |     fap_private_libs=[ | ||||||
|         Lib( |         Lib( | ||||||
|             name="free-dap", |             name="free-dap", | ||||||
|  | |||||||
| @ -1,10 +1,10 @@ | |||||||
| App( | App( | ||||||
|     appid="hid_usb", |     appid="hid_usb", | ||||||
|     name="USB Remote", |     name="Remote", | ||||||
|     apptype=FlipperAppType.PLUGIN, |     apptype=FlipperAppType.PLUGIN, | ||||||
|     entry_point="hid_usb_app", |     entry_point="hid_usb_app", | ||||||
|     stack_size=1 * 1024, |     stack_size=1 * 1024, | ||||||
|     fap_category="Tools", |     fap_category="USB", | ||||||
|     fap_icon="hid_usb_10px.png", |     fap_icon="hid_usb_10px.png", | ||||||
|     fap_icon_assets="assets", |     fap_icon_assets="assets", | ||||||
|     fap_icon_assets_symbol="hid", |     fap_icon_assets_symbol="hid", | ||||||
| @ -13,11 +13,11 @@ App( | |||||||
| 
 | 
 | ||||||
| App( | App( | ||||||
|     appid="hid_ble", |     appid="hid_ble", | ||||||
|     name="Bluetooth Remote", |     name="Remote", | ||||||
|     apptype=FlipperAppType.PLUGIN, |     apptype=FlipperAppType.PLUGIN, | ||||||
|     entry_point="hid_ble_app", |     entry_point="hid_ble_app", | ||||||
|     stack_size=1 * 1024, |     stack_size=1 * 1024, | ||||||
|     fap_category="Tools", |     fap_category="Bluetooth", | ||||||
|     fap_icon="hid_ble_10px.png", |     fap_icon="hid_ble_10px.png", | ||||||
|     fap_icon_assets="assets", |     fap_icon_assets="assets", | ||||||
|     fap_icon_assets_symbol="hid", |     fap_icon_assets_symbol="hid", | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ App( | |||||||
|     stack_size=2 * 1024, |     stack_size=2 * 1024, | ||||||
|     order=20, |     order=20, | ||||||
|     fap_icon="icons/music_10px.png", |     fap_icon="icons/music_10px.png", | ||||||
|     fap_category="Misc", |     fap_category="Media", | ||||||
|     fap_icon_assets="icons", |     fap_icon_assets="icons", | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ App( | |||||||
|     stack_size=4 * 1024, |     stack_size=4 * 1024, | ||||||
|     order=30, |     order=30, | ||||||
|     fap_icon="../../../assets/icons/Archive/125_10px.png", |     fap_icon="../../../assets/icons/Archive/125_10px.png", | ||||||
|     fap_category="Tools", |     fap_category="NFC", | ||||||
|     fap_private_libs=[ |     fap_private_libs=[ | ||||||
|         Lib( |         Lib( | ||||||
|             name="magic", |             name="magic", | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| App( | App( | ||||||
|     appid="picopass", |     appid="picopass", | ||||||
|     name="PicoPass Reader", |     name="PicoPass", | ||||||
|     apptype=FlipperAppType.EXTERNAL, |     apptype=FlipperAppType.EXTERNAL, | ||||||
|     targets=["f7"], |     targets=["f7"], | ||||||
|     entry_point="picopass_app", |     entry_point="picopass_app", | ||||||
| @ -11,7 +11,7 @@ App( | |||||||
|     stack_size=4 * 1024, |     stack_size=4 * 1024, | ||||||
|     order=30, |     order=30, | ||||||
|     fap_icon="125_10px.png", |     fap_icon="125_10px.png", | ||||||
|     fap_category="Tools", |     fap_category="NFC", | ||||||
|     fap_libs=["mbedtls"], |     fap_libs=["mbedtls"], | ||||||
|     fap_private_libs=[ |     fap_private_libs=[ | ||||||
|         Lib( |         Lib( | ||||||
|  | |||||||
| @ -171,6 +171,16 @@ void picopass_show_loading_popup(void* context, bool show) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) { | ||||||
|  |     bool result = size > 0; | ||||||
|  |     while(size > 0) { | ||||||
|  |         result &= (*data == pattern); | ||||||
|  |         data++; | ||||||
|  |         size--; | ||||||
|  |     } | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| int32_t picopass_app(void* p) { | int32_t picopass_app(void* p) { | ||||||
|     UNUSED(p); |     UNUSED(p); | ||||||
|     Picopass* picopass = picopass_alloc(); |     Picopass* picopass = picopass_alloc(); | ||||||
|  | |||||||
| @ -368,7 +368,7 @@ ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* r | |||||||
| 
 | 
 | ||||||
|         record->CardNumber = (bot >> 1) & 0xFFFF; |         record->CardNumber = (bot >> 1) & 0xFFFF; | ||||||
|         record->FacilityCode = (bot >> 17) & 0xFF; |         record->FacilityCode = (bot >> 17) & 0xFF; | ||||||
|         FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber); |         FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber); | ||||||
|         record->valid = true; |         record->valid = true; | ||||||
|     } else { |     } else { | ||||||
|         record->CardNumber = 0; |         record->CardNumber = 0; | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ | |||||||
| #define PICOPASS_KD_BLOCK_INDEX 3 | #define PICOPASS_KD_BLOCK_INDEX 3 | ||||||
| #define PICOPASS_KC_BLOCK_INDEX 4 | #define PICOPASS_KC_BLOCK_INDEX 4 | ||||||
| #define PICOPASS_AIA_BLOCK_INDEX 5 | #define PICOPASS_AIA_BLOCK_INDEX 5 | ||||||
|  | #define PICOPASS_PACS_CFG_BLOCK_INDEX 6 | ||||||
| 
 | 
 | ||||||
| #define PICOPASS_APP_FOLDER ANY_PATH("picopass") | #define PICOPASS_APP_FOLDER ANY_PATH("picopass") | ||||||
| #define PICOPASS_APP_EXTENSION ".picopass" | #define PICOPASS_APP_EXTENSION ".picopass" | ||||||
|  | |||||||
| @ -81,3 +81,15 @@ void picopass_blink_start(Picopass* picopass); | |||||||
| void picopass_blink_stop(Picopass* picopass); | void picopass_blink_stop(Picopass* picopass); | ||||||
| 
 | 
 | ||||||
| void picopass_show_loading_popup(void* context, bool show); | void picopass_show_loading_popup(void* context, bool show); | ||||||
|  | 
 | ||||||
|  | /** Check if memory is set to pattern
 | ||||||
|  |  * | ||||||
|  |  * @warning    zero size will return false | ||||||
|  |  * | ||||||
|  |  * @param[in]  data     Pointer to the byte array | ||||||
|  |  * @param[in]  pattern  The pattern | ||||||
|  |  * @param[in]  size     The byte array size | ||||||
|  |  * | ||||||
|  |  * @return     True if memory is set to pattern, false otherwise | ||||||
|  |  */ | ||||||
|  | bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size); | ||||||
|  | |||||||
| @ -5,7 +5,8 @@ | |||||||
| #define TAG "PicopassWorker" | #define TAG "PicopassWorker" | ||||||
| 
 | 
 | ||||||
| const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; | const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; | ||||||
| const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; | const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; | ||||||
|  | const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; | ||||||
| 
 | 
 | ||||||
| static void picopass_worker_enable_field() { | static void picopass_worker_enable_field() { | ||||||
|     furi_hal_nfc_ll_txrx_on(); |     furi_hal_nfc_ll_txrx_on(); | ||||||
| @ -197,6 +198,28 @@ static ReturnCode picopass_auth_standard(uint8_t* csn, uint8_t* div_key) { | |||||||
|     return rfalPicoPassPollerCheck(mac, &chkRes); |     return rfalPicoPassPollerCheck(mac, &chkRes); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static ReturnCode picopass_auth_factory(uint8_t* csn, uint8_t* div_key) { | ||||||
|  |     rfalPicoPassReadCheckRes rcRes; | ||||||
|  |     rfalPicoPassCheckRes chkRes; | ||||||
|  | 
 | ||||||
|  |     ReturnCode err; | ||||||
|  | 
 | ||||||
|  |     uint8_t mac[4] = {0}; | ||||||
|  |     uint8_t ccnr[12] = {0}; | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerReadCheck(&rcRes); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  |     memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
 | ||||||
|  | 
 | ||||||
|  |     loclass_diversifyKey(csn, picopass_factory_debit_key, div_key); | ||||||
|  |     loclass_opt_doReaderMAC(ccnr, div_key, mac); | ||||||
|  | 
 | ||||||
|  |     return rfalPicoPassPollerCheck(mac, &chkRes); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static ReturnCode picopass_auth_dict( | static ReturnCode picopass_auth_dict( | ||||||
|     uint8_t* csn, |     uint8_t* csn, | ||||||
|     PicopassPacs* pacs, |     PicopassPacs* pacs, | ||||||
| @ -264,14 +287,23 @@ static ReturnCode picopass_auth_dict( | |||||||
| ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { | ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { | ||||||
|     ReturnCode err; |     ReturnCode err; | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_E(TAG, "Trying standard legacy key"); |     FURI_LOG_I(TAG, "Trying standard legacy key"); | ||||||
|     err = picopass_auth_standard( |     err = picopass_auth_standard( | ||||||
|         AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); |         AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); | ||||||
|     if(err == ERR_NONE) { |     if(err == ERR_NONE) { | ||||||
|  |         memcpy(pacs->key, picopass_iclass_key, PICOPASS_BLOCK_LEN); | ||||||
|         return ERR_NONE; |         return ERR_NONE; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_E(TAG, "Starting user dictionary attack"); |     FURI_LOG_I(TAG, "Trying factory default key"); | ||||||
|  |     err = picopass_auth_factory( | ||||||
|  |         AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); | ||||||
|  |     if(err == ERR_NONE) { | ||||||
|  |         memcpy(pacs->key, picopass_factory_debit_key, PICOPASS_BLOCK_LEN); | ||||||
|  |         return ERR_NONE; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_I(TAG, "Starting user dictionary attack"); | ||||||
|     err = picopass_auth_dict( |     err = picopass_auth_dict( | ||||||
|         AA1[PICOPASS_CSN_BLOCK_INDEX].data, |         AA1[PICOPASS_CSN_BLOCK_INDEX].data, | ||||||
|         pacs, |         pacs, | ||||||
| @ -281,7 +313,7 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { | |||||||
|         return ERR_NONE; |         return ERR_NONE; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_E(TAG, "Starting in-built dictionary attack"); |     FURI_LOG_I(TAG, "Starting system dictionary attack"); | ||||||
|     err = picopass_auth_dict( |     err = picopass_auth_dict( | ||||||
|         AA1[PICOPASS_CSN_BLOCK_INDEX].data, |         AA1[PICOPASS_CSN_BLOCK_INDEX].data, | ||||||
|         pacs, |         pacs, | ||||||
| @ -406,6 +438,84 @@ ReturnCode picopass_write_card(PicopassBlock* AA1) { | |||||||
|     return ERR_NONE; |     return ERR_NONE; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* newBlock) { | ||||||
|  |     rfalPicoPassIdentifyRes idRes; | ||||||
|  |     rfalPicoPassSelectRes selRes; | ||||||
|  |     rfalPicoPassReadCheckRes rcRes; | ||||||
|  |     rfalPicoPassCheckRes chkRes; | ||||||
|  | 
 | ||||||
|  |     ReturnCode err; | ||||||
|  | 
 | ||||||
|  |     uint8_t div_key[8] = {0}; | ||||||
|  |     uint8_t mac[4] = {0}; | ||||||
|  |     uint8_t ccnr[12] = {0}; | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerIdentify(&idRes); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerReadCheck(&rcRes); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  |     memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
 | ||||||
|  | 
 | ||||||
|  |     loclass_diversifyKey(selRes.CSN, pacs->key, div_key); | ||||||
|  |     loclass_opt_doReaderMAC(ccnr, div_key, mac); | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerCheck(mac, &chkRes); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", blockNo); | ||||||
|  |     uint8_t data[9] = { | ||||||
|  |         blockNo, | ||||||
|  |         newBlock[0], | ||||||
|  |         newBlock[1], | ||||||
|  |         newBlock[2], | ||||||
|  |         newBlock[3], | ||||||
|  |         newBlock[4], | ||||||
|  |         newBlock[5], | ||||||
|  |         newBlock[6], | ||||||
|  |         newBlock[7]}; | ||||||
|  |     loclass_doMAC_N(data, sizeof(data), div_key, mac); | ||||||
|  |     FURI_LOG_D( | ||||||
|  |         TAG, | ||||||
|  |         "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x", | ||||||
|  |         blockNo, | ||||||
|  |         data[1], | ||||||
|  |         data[2], | ||||||
|  |         data[3], | ||||||
|  |         data[4], | ||||||
|  |         data[5], | ||||||
|  |         data[6], | ||||||
|  |         data[7], | ||||||
|  |         data[8], | ||||||
|  |         mac[0], | ||||||
|  |         mac[1], | ||||||
|  |         mac[2], | ||||||
|  |         mac[3]); | ||||||
|  | 
 | ||||||
|  |     err = rfalPicoPassPollerWriteBlock(data[0], data + 1, mac); | ||||||
|  |     if(err != ERR_NONE) { | ||||||
|  |         FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ERR_NONE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| int32_t picopass_worker_task(void* context) { | int32_t picopass_worker_task(void* context) { | ||||||
|     PicopassWorker* picopass_worker = context; |     PicopassWorker* picopass_worker = context; | ||||||
| 
 | 
 | ||||||
| @ -414,6 +524,8 @@ int32_t picopass_worker_task(void* context) { | |||||||
|         picopass_worker_detect(picopass_worker); |         picopass_worker_detect(picopass_worker); | ||||||
|     } else if(picopass_worker->state == PicopassWorkerStateWrite) { |     } else if(picopass_worker->state == PicopassWorkerStateWrite) { | ||||||
|         picopass_worker_write(picopass_worker); |         picopass_worker_write(picopass_worker); | ||||||
|  |     } else if(picopass_worker->state == PicopassWorkerStateWriteStandardKey) { | ||||||
|  |         picopass_worker_write_standard_key(picopass_worker); | ||||||
|     } |     } | ||||||
|     picopass_worker_disable_field(ERR_NONE); |     picopass_worker_disable_field(ERR_NONE); | ||||||
| 
 | 
 | ||||||
| @ -448,7 +560,7 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Thank you proxmark!
 |             // Thank you proxmark!
 | ||||||
|             pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); |             pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8); | ||||||
|             pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); |             pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); | ||||||
|             if(pacs->se_enabled) { |             if(pacs->se_enabled) { | ||||||
|                 FURI_LOG_D(TAG, "SE enabled"); |                 FURI_LOG_D(TAG, "SE enabled"); | ||||||
| @ -520,3 +632,46 @@ void picopass_worker_write(PicopassWorker* picopass_worker) { | |||||||
|         furi_delay_ms(100); |         furi_delay_ms(100); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) { | ||||||
|  |     PicopassDeviceData* dev_data = picopass_worker->dev_data; | ||||||
|  |     PicopassBlock* AA1 = dev_data->AA1; | ||||||
|  |     PicopassPacs* pacs = &dev_data->pacs; | ||||||
|  |     ReturnCode err; | ||||||
|  |     PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; | ||||||
|  | 
 | ||||||
|  |     uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; | ||||||
|  |     uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data; | ||||||
|  |     uint8_t fuses = configBlock[7]; | ||||||
|  |     uint8_t* oldKey = AA1[PICOPASS_KD_BLOCK_INDEX].data; | ||||||
|  | 
 | ||||||
|  |     uint8_t newKey[PICOPASS_BLOCK_LEN] = {0}; | ||||||
|  |     loclass_diversifyKey(csn, picopass_iclass_key, newKey); | ||||||
|  | 
 | ||||||
|  |     if((fuses & 0x80) == 0x80) { | ||||||
|  |         FURI_LOG_D(TAG, "Plain write for personalized mode key change"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_D(TAG, "XOR write for application mode key change"); | ||||||
|  |         // XOR when in application mode
 | ||||||
|  |         for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { | ||||||
|  |             newKey[i] ^= oldKey[i]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     while(picopass_worker->state == PicopassWorkerStateWriteStandardKey) { | ||||||
|  |         if(picopass_detect_card(1000) == ERR_NONE) { | ||||||
|  |             err = picopass_write_block(pacs, PICOPASS_KD_BLOCK_INDEX, newKey); | ||||||
|  |             if(err != ERR_NONE) { | ||||||
|  |                 FURI_LOG_E(TAG, "picopass_write_block error %d", err); | ||||||
|  |                 nextState = PicopassWorkerEventFail; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Notify caller and exit
 | ||||||
|  |             if(picopass_worker->callback) { | ||||||
|  |                 picopass_worker->callback(nextState, picopass_worker->context); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         furi_delay_ms(100); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ typedef enum { | |||||||
|     // Main worker states
 |     // Main worker states
 | ||||||
|     PicopassWorkerStateDetect, |     PicopassWorkerStateDetect, | ||||||
|     PicopassWorkerStateWrite, |     PicopassWorkerStateWrite, | ||||||
|  |     PicopassWorkerStateWriteStandardKey, | ||||||
|     // Transition
 |     // Transition
 | ||||||
|     PicopassWorkerStateStop, |     PicopassWorkerStateStop, | ||||||
| } PicopassWorkerState; | } PicopassWorkerState; | ||||||
|  | |||||||
| @ -31,3 +31,4 @@ int32_t picopass_worker_task(void* context); | |||||||
| 
 | 
 | ||||||
| void picopass_worker_detect(PicopassWorker* picopass_worker); | void picopass_worker_detect(PicopassWorker* picopass_worker); | ||||||
| void picopass_worker_write(PicopassWorker* picopass_worker); | void picopass_worker_write(PicopassWorker* picopass_worker); | ||||||
|  | void picopass_worker_write_standard_key(PicopassWorker* picopass_worker); | ||||||
|  | |||||||
| @ -11,3 +11,5 @@ ADD_SCENE(picopass, delete, Delete) | |||||||
| ADD_SCENE(picopass, delete_success, DeleteSuccess) | ADD_SCENE(picopass, delete_success, DeleteSuccess) | ||||||
| ADD_SCENE(picopass, write_card, WriteCard) | ADD_SCENE(picopass, write_card, WriteCard) | ||||||
| ADD_SCENE(picopass, write_card_success, WriteCardSuccess) | ADD_SCENE(picopass, write_card_success, WriteCardSuccess) | ||||||
|  | ADD_SCENE(picopass, read_factory_success, ReadFactorySuccess) | ||||||
|  | ADD_SCENE(picopass, write_key, WriteKey) | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| #include "../picopass_i.h" | #include "../picopass_i.h" | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| 
 | 
 | ||||||
|  | const uint8_t picopass_factory_key_check[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; | ||||||
|  | 
 | ||||||
| void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { | void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { | ||||||
|     UNUSED(event); |     UNUSED(event); | ||||||
|     Picopass* picopass = context; |     Picopass* picopass = context; | ||||||
| @ -34,7 +36,14 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == PicopassCustomEventWorkerExit) { |         if(event.event == PicopassCustomEventWorkerExit) { | ||||||
|  |             if(memcmp( | ||||||
|  |                    picopass->dev->dev_data.pacs.key, | ||||||
|  |                    picopass_factory_key_check, | ||||||
|  |                    PICOPASS_BLOCK_LEN) == 0) { | ||||||
|  |                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess); | ||||||
|  |             } else { | ||||||
|                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); |                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); | ||||||
|  |             } | ||||||
|             consumed = true; |             consumed = true; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ void picopass_scene_read_card_success_widget_callback( | |||||||
| 
 | 
 | ||||||
| void picopass_scene_read_card_success_on_enter(void* context) { | void picopass_scene_read_card_success_on_enter(void* context) { | ||||||
|     Picopass* picopass = context; |     Picopass* picopass = context; | ||||||
|  | 
 | ||||||
|     FuriString* csn_str = furi_string_alloc_set("CSN:"); |     FuriString* csn_str = furi_string_alloc_set("CSN:"); | ||||||
|     FuriString* credential_str = furi_string_alloc(); |     FuriString* credential_str = furi_string_alloc(); | ||||||
|     FuriString* wiegand_str = furi_string_alloc(); |     FuriString* wiegand_str = furi_string_alloc(); | ||||||
| @ -30,27 +31,31 @@ void picopass_scene_read_card_success_on_enter(void* context) { | |||||||
|     PicopassPacs* pacs = &picopass->dev->dev_data.pacs; |     PicopassPacs* pacs = &picopass->dev->dev_data.pacs; | ||||||
|     Widget* widget = picopass->widget; |     Widget* widget = picopass->widget; | ||||||
| 
 | 
 | ||||||
|     uint8_t csn[PICOPASS_BLOCK_LEN]; |     uint8_t csn[PICOPASS_BLOCK_LEN] = {0}; | ||||||
|     memcpy(csn, &AA1->data[PICOPASS_CSN_BLOCK_INDEX], PICOPASS_BLOCK_LEN); |     memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN); | ||||||
|     for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { |     for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { | ||||||
|         furi_string_cat_printf(csn_str, "%02X ", csn[i]); |         furi_string_cat_printf(csn_str, "%02X ", csn[i]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Neither of these are valid.  Indicates the block was all 0x00 or all 0xff
 |     bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN); | ||||||
|     if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { |     bool empty = | ||||||
|  |         picopass_is_memset(AA1[PICOPASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN); | ||||||
|  | 
 | ||||||
|  |     if(no_key) { | ||||||
|         furi_string_cat_printf(wiegand_str, "Read Failed"); |         furi_string_cat_printf(wiegand_str, "Read Failed"); | ||||||
| 
 | 
 | ||||||
|         if(pacs->se_enabled) { |         if(pacs->se_enabled) { | ||||||
|             furi_string_cat_printf(credential_str, "SE enabled"); |             furi_string_cat_printf(credential_str, "SE enabled"); | ||||||
|         } |         } | ||||||
|  |     } else if(empty) { | ||||||
|  |         furi_string_cat_printf(wiegand_str, "Empty"); | ||||||
|  |     } else if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { | ||||||
|  |         // Neither of these are valid.  Indicates the block was all 0x00 or all 0xff
 | ||||||
|  |         furi_string_cat_printf(wiegand_str, "Invalid PACS"); | ||||||
| 
 | 
 | ||||||
|         widget_add_button_element( |         if(pacs->se_enabled) { | ||||||
|             widget, |             furi_string_cat_printf(credential_str, "SE enabled"); | ||||||
|             GuiButtonTypeLeft, |         } | ||||||
|             "Retry", |  | ||||||
|             picopass_scene_read_card_success_widget_callback, |  | ||||||
|             picopass); |  | ||||||
| 
 |  | ||||||
|     } else { |     } else { | ||||||
|         size_t bytesLength = 1 + pacs->record.bitLength / 8; |         size_t bytesLength = 1 + pacs->record.bitLength / 8; | ||||||
|         furi_string_set(credential_str, ""); |         furi_string_set(credential_str, ""); | ||||||
| @ -82,13 +87,6 @@ void picopass_scene_read_card_success_on_enter(void* context) { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         widget_add_button_element( |  | ||||||
|             widget, |  | ||||||
|             GuiButtonTypeLeft, |  | ||||||
|             "Retry", |  | ||||||
|             picopass_scene_read_card_success_widget_callback, |  | ||||||
|             picopass); |  | ||||||
| 
 |  | ||||||
|         widget_add_button_element( |         widget_add_button_element( | ||||||
|             widget, |             widget, | ||||||
|             GuiButtonTypeRight, |             GuiButtonTypeRight, | ||||||
| @ -97,6 +95,13 @@ void picopass_scene_read_card_success_on_enter(void* context) { | |||||||
|             picopass); |             picopass); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, | ||||||
|  |         GuiButtonTypeLeft, | ||||||
|  |         "Retry", | ||||||
|  |         picopass_scene_read_card_success_widget_callback, | ||||||
|  |         picopass); | ||||||
|  | 
 | ||||||
|     widget_add_string_element( |     widget_add_string_element( | ||||||
|         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); |         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); | ||||||
|     widget_add_string_element( |     widget_add_string_element( | ||||||
|  | |||||||
| @ -0,0 +1,78 @@ | |||||||
|  | #include "../picopass_i.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
|  | void picopass_scene_read_factory_success_widget_callback( | ||||||
|  |     GuiButtonType result, | ||||||
|  |     InputType type, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Picopass* picopass = context; | ||||||
|  | 
 | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(picopass->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void picopass_scene_read_factory_success_on_enter(void* context) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  |     FuriString* title = furi_string_alloc_set("Factory Default"); | ||||||
|  |     FuriString* subtitle = furi_string_alloc_set(""); | ||||||
|  | 
 | ||||||
|  |     DOLPHIN_DEED(DolphinDeedNfcReadSuccess); | ||||||
|  | 
 | ||||||
|  |     // Send notification
 | ||||||
|  |     notification_message(picopass->notifications, &sequence_success); | ||||||
|  | 
 | ||||||
|  |     // Setup view
 | ||||||
|  |     Widget* widget = picopass->widget; | ||||||
|  |     //PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
 | ||||||
|  |     PicopassBlock* AA1 = picopass->dev->dev_data.AA1; | ||||||
|  | 
 | ||||||
|  |     uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data; | ||||||
|  |     uint8_t fuses = configBlock[7]; | ||||||
|  | 
 | ||||||
|  |     if((fuses & 0x80) == 0x80) { | ||||||
|  |         furi_string_cat_printf(subtitle, "Personalization mode"); | ||||||
|  |     } else { | ||||||
|  |         furi_string_cat_printf(subtitle, "Application mode"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, | ||||||
|  |         GuiButtonTypeCenter, | ||||||
|  |         "Write Standard iClass Key", | ||||||
|  |         picopass_scene_read_factory_success_widget_callback, | ||||||
|  |         picopass); | ||||||
|  | 
 | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(title)); | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(subtitle)); | ||||||
|  | 
 | ||||||
|  |     furi_string_free(title); | ||||||
|  |     furi_string_free(subtitle); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = scene_manager_previous_scene(picopass->scene_manager); | ||||||
|  |         } else if(event.event == GuiButtonTypeCenter) { | ||||||
|  |             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void picopass_scene_read_factory_success_on_exit(void* context) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  | 
 | ||||||
|  |     // Clear view
 | ||||||
|  |     widget_reset(picopass->widget); | ||||||
|  | } | ||||||
| @ -16,6 +16,7 @@ void picopass_scene_write_card_success_widget_callback( | |||||||
| void picopass_scene_write_card_success_on_enter(void* context) { | void picopass_scene_write_card_success_on_enter(void* context) { | ||||||
|     Picopass* picopass = context; |     Picopass* picopass = context; | ||||||
|     Widget* widget = picopass->widget; |     Widget* widget = picopass->widget; | ||||||
|  |     FuriString* str = furi_string_alloc_set("Write Success!"); | ||||||
| 
 | 
 | ||||||
|     DOLPHIN_DEED(DolphinDeedNfcReadSuccess); |     DOLPHIN_DEED(DolphinDeedNfcReadSuccess); | ||||||
| 
 | 
 | ||||||
| @ -29,6 +30,18 @@ void picopass_scene_write_card_success_on_enter(void* context) { | |||||||
|         picopass_scene_write_card_success_widget_callback, |         picopass_scene_write_card_success_widget_callback, | ||||||
|         picopass); |         picopass); | ||||||
| 
 | 
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, | ||||||
|  |         GuiButtonTypeRight, | ||||||
|  |         "Menu", | ||||||
|  |         picopass_scene_write_card_success_widget_callback, | ||||||
|  |         picopass); | ||||||
|  | 
 | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(str)); | ||||||
|  | 
 | ||||||
|  |     furi_string_free(str); | ||||||
|  | 
 | ||||||
|     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); |     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -0,0 +1,53 @@ | |||||||
|  | #include "../picopass_i.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
|  | void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context) { | ||||||
|  |     UNUSED(event); | ||||||
|  |     Picopass* picopass = context; | ||||||
|  |     view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void picopass_scene_write_key_on_enter(void* context) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  |     DOLPHIN_DEED(DolphinDeedNfcSave); | ||||||
|  | 
 | ||||||
|  |     // Setup view
 | ||||||
|  |     Popup* popup = picopass->popup; | ||||||
|  |     popup_set_header(popup, "Writing\niClass\nkey", 68, 30, AlignLeft, AlignTop); | ||||||
|  |     popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); | ||||||
|  | 
 | ||||||
|  |     // Start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); | ||||||
|  |     picopass_worker_start( | ||||||
|  |         picopass->worker, | ||||||
|  |         PicopassWorkerStateWriteStandardKey, | ||||||
|  |         &picopass->dev->dev_data, | ||||||
|  |         picopass_write_key_worker_callback, | ||||||
|  |         picopass); | ||||||
|  | 
 | ||||||
|  |     picopass_blink_start(picopass); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == PicopassCustomEventWorkerExit) { | ||||||
|  |             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void picopass_scene_write_key_on_exit(void* context) { | ||||||
|  |     Picopass* picopass = context; | ||||||
|  | 
 | ||||||
|  |     // Stop worker
 | ||||||
|  |     picopass_worker_stop(picopass->worker); | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(picopass->popup); | ||||||
|  | 
 | ||||||
|  |     picopass_blink_stop(picopass); | ||||||
|  | } | ||||||
| @ -8,6 +8,6 @@ App( | |||||||
|     stack_size=1 * 1024, |     stack_size=1 * 1024, | ||||||
|     order=50, |     order=50, | ||||||
|     fap_icon="signal_gen_10px.png", |     fap_icon="signal_gen_10px.png", | ||||||
|     fap_category="Tools", |     fap_category="GPIO", | ||||||
|     fap_icon_assets="icons", |     fap_icon_assets="icons", | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ App( | |||||||
|     stack_size=1 * 2048, |     stack_size=1 * 2048, | ||||||
|     order=30, |     order=30, | ||||||
|     fap_icon="images/Dip8_10px.png", |     fap_icon="images/Dip8_10px.png", | ||||||
|     fap_category="Tools", |     fap_category="GPIO", | ||||||
|     fap_icon_assets="images", |     fap_icon_assets="images", | ||||||
|     fap_private_libs=[ |     fap_private_libs=[ | ||||||
|         Lib( |         Lib( | ||||||
|  | |||||||
| @ -9,6 +9,6 @@ App( | |||||||
|     stack_size=4 * 1024, |     stack_size=4 * 1024, | ||||||
|     order=50, |     order=50, | ||||||
|     fap_icon="weather_station_10px.png", |     fap_icon="weather_station_10px.png", | ||||||
|     fap_category="Tools", |     fap_category="Sub-GHz", | ||||||
|     fap_icon_assets="images", |     fap_icon_assets="images", | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -195,6 +195,10 @@ bool weather_station_scene_receiver_on_event(void* context, SceneManagerEvent ev | |||||||
|             ws_hopper_update(app); |             ws_hopper_update(app); | ||||||
|             weather_station_scene_receiver_update_statusbar(app); |             weather_station_scene_receiver_update_statusbar(app); | ||||||
|         } |         } | ||||||
|  |         // Get current RSSI
 | ||||||
|  |         float rssi = furi_hal_subghz_get_rssi(); | ||||||
|  |         ws_view_receiver_set_rssi(app->ws_receiver, rssi); | ||||||
|  | 
 | ||||||
|         if(app->txrx->txrx_state == WSTxRxStateRx) { |         if(app->txrx->txrx_state == WSTxRxStateRx) { | ||||||
|             notification_message(app->notifications, &sequence_blink_cyan_10); |             notification_message(app->notifications, &sequence_blink_cyan_10); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ | |||||||
| #define MENU_ITEMS 4u | #define MENU_ITEMS 4u | ||||||
| #define UNLOCK_CNT 3 | #define UNLOCK_CNT 3 | ||||||
| 
 | 
 | ||||||
|  | #define SUBGHZ_RAW_TRESHOLD_MIN -90.0f | ||||||
| typedef struct { | typedef struct { | ||||||
|     FuriString* item_str; |     FuriString* item_str; | ||||||
|     uint8_t type; |     uint8_t type; | ||||||
| @ -59,8 +60,24 @@ typedef struct { | |||||||
|     uint16_t list_offset; |     uint16_t list_offset; | ||||||
|     uint16_t history_item; |     uint16_t history_item; | ||||||
|     WSReceiverBarShow bar_show; |     WSReceiverBarShow bar_show; | ||||||
|  |     uint8_t u_rssi; | ||||||
| } WSReceiverModel; | } WSReceiverModel; | ||||||
| 
 | 
 | ||||||
|  | void ws_view_receiver_set_rssi(WSReceiver* instance, float rssi) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     with_view_model( | ||||||
|  |         instance->view, | ||||||
|  |         WSReceiverModel * model, | ||||||
|  |         { | ||||||
|  |             if(rssi < SUBGHZ_RAW_TRESHOLD_MIN) { | ||||||
|  |                 model->u_rssi = 0; | ||||||
|  |             } else { | ||||||
|  |                 model->u_rssi = (uint8_t)(rssi - SUBGHZ_RAW_TRESHOLD_MIN); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         true); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock lock) { | void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock lock) { | ||||||
|     furi_assert(ws_receiver); |     furi_assert(ws_receiver); | ||||||
|     ws_receiver->lock_count = 0; |     ws_receiver->lock_count = 0; | ||||||
| @ -164,13 +181,22 @@ static void ws_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrol | |||||||
|     canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11); |     canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void ws_view_rssi_draw(Canvas* canvas, WSReceiverModel* model) { | ||||||
|  |     for(uint8_t i = 1; i < model->u_rssi; i++) { | ||||||
|  |         if(i % 5) { | ||||||
|  |             canvas_draw_dot(canvas, 46 + i, 50); | ||||||
|  |             canvas_draw_dot(canvas, 47 + i, 51); | ||||||
|  |             canvas_draw_dot(canvas, 46 + i, 52); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { | void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { | ||||||
|     canvas_clear(canvas); |     canvas_clear(canvas); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
|     canvas_set_font(canvas, FontSecondary); |     canvas_set_font(canvas, FontSecondary); | ||||||
| 
 | 
 | ||||||
|     elements_button_left(canvas, "Config"); |     elements_button_left(canvas, "Config"); | ||||||
|     canvas_draw_line(canvas, 46, 51, 125, 51); |  | ||||||
| 
 | 
 | ||||||
|     bool scrollbar = model->history_item > 4; |     bool scrollbar = model->history_item > 4; | ||||||
|     FuriString* str_buff; |     FuriString* str_buff; | ||||||
| @ -203,10 +229,12 @@ void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { | |||||||
|         canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); |         canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); | ||||||
|         canvas_set_font(canvas, FontPrimary); |         canvas_set_font(canvas, FontPrimary); | ||||||
|         canvas_draw_str(canvas, 63, 46, "Scanning..."); |         canvas_draw_str(canvas, 63, 46, "Scanning..."); | ||||||
|         canvas_draw_line(canvas, 46, 51, 125, 51); |  | ||||||
|         canvas_set_font(canvas, FontSecondary); |         canvas_set_font(canvas, FontSecondary); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Draw RSSI
 | ||||||
|  |     ws_view_rssi_draw(canvas, model); | ||||||
|  | 
 | ||||||
|     switch(model->bar_show) { |     switch(model->bar_show) { | ||||||
|     case WSReceiverBarShowLock: |     case WSReceiverBarShowLock: | ||||||
|         canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); |         canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); | ||||||
|  | |||||||
| @ -8,6 +8,8 @@ typedef struct WSReceiver WSReceiver; | |||||||
| 
 | 
 | ||||||
| typedef void (*WSReceiverCallback)(WSCustomEvent event, void* context); | typedef void (*WSReceiverCallback)(WSCustomEvent event, void* context); | ||||||
| 
 | 
 | ||||||
|  | void ws_view_receiver_set_rssi(WSReceiver* instance, float rssi); | ||||||
|  | 
 | ||||||
| void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock keyboard); | void ws_view_receiver_set_lock(WSReceiver* ws_receiver, WSLock keyboard); | ||||||
| 
 | 
 | ||||||
| void ws_view_receiver_set_callback( | void ws_view_receiver_set_callback( | ||||||
|  | |||||||
| @ -373,7 +373,7 @@ int32_t bt_srv(void* p) { | |||||||
|     Bt* bt = bt_alloc(); |     Bt* bt = bt_alloc(); | ||||||
| 
 | 
 | ||||||
|     if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { |     if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { | ||||||
|         FURI_LOG_W(TAG, "Skipped BT init: device in special startup mode"); |         FURI_LOG_W(TAG, "Skipping start in special boot mode"); | ||||||
|         ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT); |         ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT); | ||||||
|         furi_record_create(RECORD_BT, bt); |         furi_record_create(RECORD_BT, bt); | ||||||
|         return 0; |         return 0; | ||||||
|  | |||||||
| @ -461,7 +461,7 @@ int32_t cli_srv(void* p) { | |||||||
|     if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { |     if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { | ||||||
|         cli_session_open(cli, &cli_vcp); |         cli_session_open(cli, &cli_vcp); | ||||||
|     } else { |     } else { | ||||||
|         FURI_LOG_W(TAG, "Skipped CLI session open: device in special startup mode"); |         FURI_LOG_W(TAG, "Skipping start in special boot mode"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     while(1) { |     while(1) { | ||||||
|  | |||||||
| @ -12,26 +12,48 @@ | |||||||
| // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
 | // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
 | ||||||
| #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" | #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" | ||||||
| 
 | 
 | ||||||
| void cli_command_device_info_callback(const char* key, const char* value, bool last, void* context) { | void cli_command_info_callback(const char* key, const char* value, bool last, void* context) { | ||||||
|     UNUSED(context); |  | ||||||
|     UNUSED(last); |     UNUSED(last); | ||||||
|  |     UNUSED(context); | ||||||
|     printf("%-30s: %s\r\n", key, value); |     printf("%-30s: %s\r\n", key, value); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* 
 | /** Info Command
 | ||||||
|  * Device Info Command |  * | ||||||
|  * This command is intended to be used by humans |  * This command is intended to be used by humans | ||||||
|  |  * | ||||||
|  |  * Arguments: | ||||||
|  |  * - device - print device info | ||||||
|  |  * - power - print power info | ||||||
|  |  * - power_debug - print power debug info | ||||||
|  |  * | ||||||
|  |  * @param      cli      The cli instance | ||||||
|  |  * @param      args     The arguments | ||||||
|  |  * @param      context  The context | ||||||
|  */ |  */ | ||||||
| void cli_command_device_info(Cli* cli, FuriString* args, void* context) { | void cli_command_info(Cli* cli, FuriString* args, void* context) { | ||||||
|     UNUSED(cli); |     UNUSED(cli); | ||||||
|     UNUSED(args); | 
 | ||||||
|     furi_hal_info_get(cli_command_device_info_callback, '_', context); |     if(context) { | ||||||
|  |         furi_hal_info_get(cli_command_info_callback, '_', NULL); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(!furi_string_cmp(args, "device")) { | ||||||
|  |         furi_hal_info_get(cli_command_info_callback, '.', NULL); | ||||||
|  |     } else if(!furi_string_cmp(args, "power")) { | ||||||
|  |         furi_hal_power_info_get(cli_command_info_callback, '.', NULL); | ||||||
|  |     } else if(!furi_string_cmp(args, "power_debug")) { | ||||||
|  |         furi_hal_power_debug_get(cli_command_info_callback, NULL); | ||||||
|  |     } else { | ||||||
|  |         cli_print_usage("info", "<device|power|power_debug>", furi_string_get_cstr(args)); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cli_command_help(Cli* cli, FuriString* args, void* context) { | void cli_command_help(Cli* cli, FuriString* args, void* context) { | ||||||
|     UNUSED(args); |     UNUSED(args); | ||||||
|     UNUSED(context); |     UNUSED(context); | ||||||
|     printf("Commands we have:"); |     printf("Commands available:"); | ||||||
| 
 | 
 | ||||||
|     // Command count
 |     // Command count
 | ||||||
|     const size_t commands_count = CliCommandTree_size(cli->commands); |     const size_t commands_count = CliCommandTree_size(cli->commands); | ||||||
| @ -61,9 +83,9 @@ void cli_command_help(Cli* cli, FuriString* args, void* context) { | |||||||
| 
 | 
 | ||||||
|     if(furi_string_size(args) > 0) { |     if(furi_string_size(args) > 0) { | ||||||
|         cli_nl(); |         cli_nl(); | ||||||
|         printf("Also I have no clue what '"); |         printf("`"); | ||||||
|         printf("%s", furi_string_get_cstr(args)); |         printf("%s", furi_string_get_cstr(args)); | ||||||
|         printf("' is."); |         printf("` command not found"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -410,8 +432,9 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cli_commands_init(Cli* cli) { | void cli_commands_init(Cli* cli) { | ||||||
|     cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_device_info, NULL); |     cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); | ||||||
|     cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_device_info, NULL); |     cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL); | ||||||
|  |     cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true); | ||||||
| 
 | 
 | ||||||
|     cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); |     cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); | ||||||
|     cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL); |     cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL); | ||||||
|  | |||||||
| @ -17,6 +17,8 @@ | |||||||
| #include "helpers/pin_lock.h" | #include "helpers/pin_lock.h" | ||||||
| #include "helpers/slideshow_filename.h" | #include "helpers/slideshow_filename.h" | ||||||
| 
 | 
 | ||||||
|  | #define TAG "Desktop" | ||||||
|  | 
 | ||||||
| static void desktop_auto_lock_arm(Desktop*); | static void desktop_auto_lock_arm(Desktop*); | ||||||
| static void desktop_auto_lock_inhibit(Desktop*); | static void desktop_auto_lock_inhibit(Desktop*); | ||||||
| static void desktop_start_auto_lock_timer(Desktop*); | static void desktop_start_auto_lock_timer(Desktop*); | ||||||
| @ -321,6 +323,12 @@ static bool desktop_check_file_flag(const char* flag_path) { | |||||||
| 
 | 
 | ||||||
| int32_t desktop_srv(void* p) { | int32_t desktop_srv(void* p) { | ||||||
|     UNUSED(p); |     UNUSED(p); | ||||||
|  | 
 | ||||||
|  |     if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { | ||||||
|  |         FURI_LOG_W(TAG, "Skipping start in special boot mode"); | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     Desktop* desktop = desktop_alloc(); |     Desktop* desktop = desktop_alloc(); | ||||||
| 
 | 
 | ||||||
|     bool loaded = DESKTOP_SETTINGS_LOAD(&desktop->settings); |     bool loaded = DESKTOP_SETTINGS_LOAD(&desktop->settings); | ||||||
|  | |||||||
| @ -154,6 +154,12 @@ static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) { | |||||||
| 
 | 
 | ||||||
| int32_t dolphin_srv(void* p) { | int32_t dolphin_srv(void* p) { | ||||||
|     UNUSED(p); |     UNUSED(p); | ||||||
|  | 
 | ||||||
|  |     if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { | ||||||
|  |         FURI_LOG_W(TAG, "Skipping start in special boot mode"); | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     Dolphin* dolphin = dolphin_alloc(); |     Dolphin* dolphin = dolphin_alloc(); | ||||||
|     furi_record_create(RECORD_DOLPHIN, dolphin); |     furi_record_create(RECORD_DOLPHIN, dolphin); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -26,24 +26,6 @@ void power_cli_reboot2dfu(Cli* cli, FuriString* args) { | |||||||
|     power_reboot(PowerBootModeDfu); |     power_reboot(PowerBootModeDfu); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void power_cli_callback(const char* key, const char* value, bool last, void* context) { |  | ||||||
|     UNUSED(last); |  | ||||||
|     UNUSED(context); |  | ||||||
|     printf("%-24s: %s\r\n", key, value); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void power_cli_info(Cli* cli, FuriString* args) { |  | ||||||
|     UNUSED(cli); |  | ||||||
|     UNUSED(args); |  | ||||||
|     furi_hal_power_info_get(power_cli_callback, '_', NULL); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void power_cli_debug(Cli* cli, FuriString* args) { |  | ||||||
|     UNUSED(cli); |  | ||||||
|     UNUSED(args); |  | ||||||
|     furi_hal_power_debug_get(power_cli_callback, NULL); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void power_cli_5v(Cli* cli, FuriString* args) { | void power_cli_5v(Cli* cli, FuriString* args) { | ||||||
|     UNUSED(cli); |     UNUSED(cli); | ||||||
|     if(!furi_string_cmp(args, "0")) { |     if(!furi_string_cmp(args, "0")) { | ||||||
| @ -74,8 +56,6 @@ static void power_cli_command_print_usage() { | |||||||
|     printf("\toff\t - shutdown power\r\n"); |     printf("\toff\t - shutdown power\r\n"); | ||||||
|     printf("\treboot\t - reboot\r\n"); |     printf("\treboot\t - reboot\r\n"); | ||||||
|     printf("\treboot2dfu\t - reboot to dfu bootloader\r\n"); |     printf("\treboot2dfu\t - reboot to dfu bootloader\r\n"); | ||||||
|     printf("\tinfo\t - show power info\r\n"); |  | ||||||
|     printf("\tdebug\t - show debug information\r\n"); |  | ||||||
|     printf("\t5v <0 or 1>\t - enable or disable 5v ext\r\n"); |     printf("\t5v <0 or 1>\t - enable or disable 5v ext\r\n"); | ||||||
|     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { |     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { | ||||||
|         printf("\t3v3 <0 or 1>\t - enable or disable 3v3 ext\r\n"); |         printf("\t3v3 <0 or 1>\t - enable or disable 3v3 ext\r\n"); | ||||||
| @ -108,16 +88,6 @@ void power_cli(Cli* cli, FuriString* args, void* context) { | |||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if(furi_string_cmp_str(cmd, "info") == 0) { |  | ||||||
|             power_cli_info(cli, args); |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(furi_string_cmp_str(cmd, "debug") == 0) { |  | ||||||
|             power_cli_debug(cli, args); |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(furi_string_cmp_str(cmd, "5v") == 0) { |         if(furi_string_cmp_str(cmd, "5v") == 0) { | ||||||
|             power_cli_5v(cli, args); |             power_cli_5v(cli, args); | ||||||
|             break; |             break; | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ | |||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #define POWER_OFF_TIMEOUT 90 | #define POWER_OFF_TIMEOUT 90 | ||||||
|  | #define TAG "Power" | ||||||
| 
 | 
 | ||||||
| void power_draw_battery_callback(Canvas* canvas, void* context) { | void power_draw_battery_callback(Canvas* canvas, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| @ -12,8 +13,8 @@ void power_draw_battery_callback(Canvas* canvas, void* context) { | |||||||
| 
 | 
 | ||||||
|     if(power->info.gauge_is_ok) { |     if(power->info.gauge_is_ok) { | ||||||
|         canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4); |         canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4); | ||||||
|         if(power->info.voltage_battery_charging < 4.2) { |         if(power->info.voltage_battery_charge_limit < 4.2) { | ||||||
|             // Battery charging voltage is modified, indicate with cross pattern
 |             // Battery charge voltage limit is modified, indicate with cross pattern
 | ||||||
|             canvas_invert_color(canvas); |             canvas_invert_color(canvas); | ||||||
|             uint8_t battery_bar_width = (power->info.charge + 4) / 5; |             uint8_t battery_bar_width = (power->info.charge + 4) / 5; | ||||||
|             bool cross_odd = false; |             bool cross_odd = false; | ||||||
| @ -146,7 +147,7 @@ static bool power_update_info(Power* power) { | |||||||
|     info.capacity_full = furi_hal_power_get_battery_full_capacity(); |     info.capacity_full = furi_hal_power_get_battery_full_capacity(); | ||||||
|     info.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger); |     info.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger); | ||||||
|     info.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge); |     info.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge); | ||||||
|     info.voltage_battery_charging = furi_hal_power_get_battery_charging_voltage(); |     info.voltage_battery_charge_limit = furi_hal_power_get_battery_charge_voltage_limit(); | ||||||
|     info.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger); |     info.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger); | ||||||
|     info.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge); |     info.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge); | ||||||
|     info.voltage_vbus = furi_hal_power_get_usb_voltage(); |     info.voltage_vbus = furi_hal_power_get_usb_voltage(); | ||||||
| @ -217,6 +218,12 @@ static void power_check_battery_level_change(Power* power) { | |||||||
| 
 | 
 | ||||||
| int32_t power_srv(void* p) { | int32_t power_srv(void* p) { | ||||||
|     UNUSED(p); |     UNUSED(p); | ||||||
|  | 
 | ||||||
|  |     if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { | ||||||
|  |         FURI_LOG_W(TAG, "Skipping start in special boot mode"); | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     Power* power = power_alloc(); |     Power* power = power_alloc(); | ||||||
|     power_update_info(power); |     power_update_info(power); | ||||||
|     furi_record_create(RECORD_POWER, power); |     furi_record_create(RECORD_POWER, power); | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ typedef struct { | |||||||
|     float current_charger; |     float current_charger; | ||||||
|     float current_gauge; |     float current_gauge; | ||||||
| 
 | 
 | ||||||
|     float voltage_battery_charging; |     float voltage_battery_charge_limit; | ||||||
|     float voltage_charger; |     float voltage_charger; | ||||||
|     float voltage_gauge; |     float voltage_gauge; | ||||||
|     float voltage_vbus; |     float voltage_vbus; | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ static void power_settings_scene_battery_info_update_model(PowerSettingsApp* app | |||||||
|         .gauge_voltage = app->info.voltage_gauge, |         .gauge_voltage = app->info.voltage_gauge, | ||||||
|         .gauge_current = app->info.current_gauge, |         .gauge_current = app->info.current_gauge, | ||||||
|         .gauge_temperature = app->info.temperature_gauge, |         .gauge_temperature = app->info.temperature_gauge, | ||||||
|         .charging_voltage = app->info.voltage_battery_charging, |         .charge_voltage_limit = app->info.voltage_battery_charge_limit, | ||||||
|         .charge = app->info.charge, |         .charge = app->info.charge, | ||||||
|         .health = app->info.health, |         .health = app->info.health, | ||||||
|     }; |     }; | ||||||
|  | |||||||
| @ -69,7 +69,7 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { | |||||||
|             drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA"); |             drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA"); | ||||||
|     } else if(drain_current != 0) { |     } else if(drain_current != 0) { | ||||||
|         snprintf(header, 20, "..."); |         snprintf(header, 20, "..."); | ||||||
|     } else if(data->charging_voltage < 4.2) { |     } else if(data->charge_voltage_limit < 4.2) { | ||||||
|         // Non-default battery charging limit, mention it
 |         // Non-default battery charging limit, mention it
 | ||||||
|         snprintf(emote, sizeof(emote), "Charged!"); |         snprintf(emote, sizeof(emote), "Charged!"); | ||||||
|         snprintf(header, sizeof(header), "Limited to"); |         snprintf(header, sizeof(header), "Limited to"); | ||||||
| @ -77,8 +77,8 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) { | |||||||
|             value, |             value, | ||||||
|             sizeof(value), |             sizeof(value), | ||||||
|             "%lu.%luV", |             "%lu.%luV", | ||||||
|             (uint32_t)(data->charging_voltage), |             (uint32_t)(data->charge_voltage_limit), | ||||||
|             (uint32_t)(data->charging_voltage * 10) % 10); |             (uint32_t)(data->charge_voltage_limit * 10) % 10); | ||||||
|     } else { |     } else { | ||||||
|         snprintf(header, sizeof(header), "Charged!"); |         snprintf(header, sizeof(header), "Charged!"); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ typedef struct { | |||||||
|     float gauge_voltage; |     float gauge_voltage; | ||||||
|     float gauge_current; |     float gauge_current; | ||||||
|     float gauge_temperature; |     float gauge_temperature; | ||||||
|     float charging_voltage; |     float charge_voltage_limit; | ||||||
|     uint8_t charge; |     uint8_t charge; | ||||||
|     uint8_t health; |     uint8_t health; | ||||||
| } BatteryInfoModel; | } BatteryInfoModel; | ||||||
|  | |||||||
| @ -103,6 +103,9 @@ static void storage_settings_scene_benchmark(StorageSettings* app) { | |||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         furi_string_cat_printf(app->text_string, "R %luK", bench_r_speed[i]); |         furi_string_cat_printf(app->text_string, "R %luK", bench_r_speed[i]); | ||||||
|  | 
 | ||||||
|  |         storage_common_remove(app->fs_api, BENCH_FILE); | ||||||
|  | 
 | ||||||
|         dialog_ex_set_text( |         dialog_ex_set_text( | ||||||
|             dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); |             dialog_ex, furi_string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -97,7 +97,16 @@ static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_ | |||||||
|                 path_concat( |                 path_concat( | ||||||
|                     STORAGE_EXT_PATH_PREFIX, furi_string_get_cstr(entry_ptr->name), file_path); |                     STORAGE_EXT_PATH_PREFIX, furi_string_get_cstr(entry_ptr->name), file_path); | ||||||
|                 FURI_LOG_D(TAG, "Removing %s", furi_string_get_cstr(file_path)); |                 FURI_LOG_D(TAG, "Removing %s", furi_string_get_cstr(file_path)); | ||||||
|                 storage_simply_remove(update_task->storage, furi_string_get_cstr(file_path)); | 
 | ||||||
|  |                 FS_Error result = | ||||||
|  |                     storage_common_remove(update_task->storage, furi_string_get_cstr(file_path)); | ||||||
|  |                 if(result != FSE_OK && result != FSE_EXIST) { | ||||||
|  |                     FURI_LOG_E( | ||||||
|  |                         TAG, | ||||||
|  |                         "%s remove failed, cause %s", | ||||||
|  |                         furi_string_get_cstr(file_path), | ||||||
|  |                         storage_error_get_desc(result)); | ||||||
|  |                 } | ||||||
|                 furi_string_free(file_path); |                 furi_string_free(file_path); | ||||||
|             } else if(entry_ptr->type == ResourceManifestEntryTypeDirectory) { |             } else if(entry_ptr->type == ResourceManifestEntryTypeDirectory) { | ||||||
|                 n_dir_entries++; |                 n_dir_entries++; | ||||||
| @ -116,7 +125,6 @@ static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_ | |||||||
|                             n_dir_entries); |                             n_dir_entries); | ||||||
| 
 | 
 | ||||||
|                 FuriString* folder_path = furi_string_alloc(); |                 FuriString* folder_path = furi_string_alloc(); | ||||||
|                 File* folder_file = storage_file_alloc(update_task->storage); |  | ||||||
| 
 | 
 | ||||||
|                 do { |                 do { | ||||||
|                     path_concat( |                     path_concat( | ||||||
| @ -125,24 +133,17 @@ static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_ | |||||||
|                         folder_path); |                         folder_path); | ||||||
| 
 | 
 | ||||||
|                     FURI_LOG_D(TAG, "Removing folder %s", furi_string_get_cstr(folder_path)); |                     FURI_LOG_D(TAG, "Removing folder %s", furi_string_get_cstr(folder_path)); | ||||||
|                     if(!storage_dir_open(folder_file, furi_string_get_cstr(folder_path))) { |                     FS_Error result = storage_common_remove( | ||||||
|                         FURI_LOG_W( |                         update_task->storage, furi_string_get_cstr(folder_path)); | ||||||
|  |                     if(result != FSE_OK && result != FSE_EXIST) { | ||||||
|  |                         FURI_LOG_E( | ||||||
|                             TAG, |                             TAG, | ||||||
|                             "%s can't be opened, skipping", |                             "%s remove failed, cause %s", | ||||||
|                             furi_string_get_cstr(folder_path)); |                             furi_string_get_cstr(folder_path), | ||||||
|                         break; |                             storage_error_get_desc(result)); | ||||||
|                     } |                     } | ||||||
| 
 |  | ||||||
|                     if(storage_dir_read(folder_file, NULL, NULL, 0)) { |  | ||||||
|                         FURI_LOG_I( |  | ||||||
|                             TAG, "%s is not empty, skipping", furi_string_get_cstr(folder_path)); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     storage_simply_remove(update_task->storage, furi_string_get_cstr(folder_path)); |  | ||||||
|                 } while(false); |                 } while(false); | ||||||
| 
 | 
 | ||||||
|                 storage_file_free(folder_file); |  | ||||||
|                 furi_string_free(folder_path); |                 furi_string_free(folder_path); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | |||||||
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 1.8 KiB | 
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 1.8 KiB | 
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 1.8 KiB | 
| Before Width: | Height: | Size: 1.8 KiB | 
| Before Width: | Height: | Size: 1.8 KiB | 
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 1.8 KiB | 
| Before Width: | Height: | Size: 1.8 KiB | 
| Before Width: | Height: | Size: 1.8 KiB | 
| @ -1,23 +0,0 @@ | |||||||
| 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 |  | ||||||
							
								
								
									
										7
									
								
								assets/dolphin/external/manifest.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -36,13 +36,6 @@ Min level: 1 | |||||||
| Max level: 1 | Max level: 1 | ||||||
| Weight: 3 | Weight: 3 | ||||||
| 
 | 
 | ||||||
| Name: L1_Happy_holidays_128x64 |  | ||||||
| Min butthurt: 0 |  | ||||||
| Max butthurt: 14 |  | ||||||
| Min level: 1 |  | ||||||
| Max level: 3 |  | ||||||
| Weight: 4 |  | ||||||
| 
 |  | ||||||
| Name: L1_Read_books_128x64 | Name: L1_Read_books_128x64 | ||||||
| Min butthurt: 0 | Min butthurt: 0 | ||||||
| Max butthurt: 8 | Max butthurt: 8 | ||||||
|  | |||||||
| @ -285,3 +285,46 @@ type: parsed | |||||||
| protocol: NECext | protocol: NECext | ||||||
| address: 10 E7 00 00 | address: 10 E7 00 00 | ||||||
| command: 41 BE 00 00 | command: 41 BE 00 00 | ||||||
|  | #  | ||||||
|  | # Model: Grundig CMS 5000 | ||||||
|  | name: Power | ||||||
|  | type: parsed | ||||||
|  | protocol: NECext | ||||||
|  | address: 30 FC 00 00 | ||||||
|  | command: 10 EF 00 00 | ||||||
|  | # Also Pause | ||||||
|  | name: Play | ||||||
|  | type: parsed | ||||||
|  | protocol: NECext | ||||||
|  | address: 30 FC 00 00 | ||||||
|  | command: 02 FD 00 00 | ||||||
|  | # | ||||||
|  | name: Vol_up | ||||||
|  | type: parsed | ||||||
|  | protocol: NECext | ||||||
|  | address: 30 FC 00 00 | ||||||
|  | command: 0D F2 00 00 | ||||||
|  | #  | ||||||
|  | name: Vol_dn | ||||||
|  | type: parsed | ||||||
|  | protocol: NECext | ||||||
|  | address: 30 FC 00 00 | ||||||
|  | command: 17 E8 00 00 | ||||||
|  | # | ||||||
|  | name: Next | ||||||
|  | type: parsed | ||||||
|  | protocol: NECext | ||||||
|  | address: 30 FC 00 00 | ||||||
|  | command: 13 EC 00 00 | ||||||
|  | #  | ||||||
|  | name: Prev | ||||||
|  | type: parsed | ||||||
|  | protocol: NECext | ||||||
|  | address: 30 FC 00 00 | ||||||
|  | command: 11 EE 00 00 | ||||||
|  | #  | ||||||
|  | name: Mute | ||||||
|  | type: parsed | ||||||
|  | protocol: NECext | ||||||
|  | address: 30 FC 00 00 | ||||||
|  | command: 0C F3 00 00 | ||||||
|  | |||||||
| Before Width: | Height: | Size: 562 B After Width: | Height: | Size: 656 B | 
| @ -3,17 +3,27 @@ | |||||||
| FBT is the entry point for firmware-related commands and utilities. | FBT is the entry point for firmware-related commands and utilities. | ||||||
| It is invoked by `./fbt` in the firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. | It is invoked by `./fbt` in the firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. | ||||||
| 
 | 
 | ||||||
| ## Requirements | ## Environment | ||||||
| 
 | 
 | ||||||
| Install Python packages required by assets build scripts: `pip3 install -r scripts/requirements.txt` | To use `fbt`, you only need `git` installed in your system. | ||||||
| 
 | 
 | ||||||
| ## NB | `fbt` by default downloads and unpacks a pre-built toolchain, and then modifies environment variables for itself to use it. It does not contaminate your global system's path with the toolchain. | ||||||
|  |  > However, if you wish to use tools supplied with the toolchain outside `fbt`, you can open an *fbt shell*, with properly configured environment. | ||||||
|  |  >    - On Windows, simply run `scripts/toochain/fbtenv.cmd`. | ||||||
|  |  >    - On Linux & MacOS, run `source scripts/toochain/fbtenv.sh` in a new shell. | ||||||
|   |   | ||||||
| - `fbt` constructs all referenced environments and their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding the construction of certain targets behind command-line options. |  If your system is not supported by pre-built toolchain variants or you want to use custom versions of dependencies, you can `set FBT_NOENV=1`. `fbt` will skip toolchain & environment configuration and will expect all tools to be available on your system's `PATH`. *(this option is not available on Windows)* | ||||||
| - `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in the environment: |   | ||||||
|  |  If `FBT_TOOLCHAIN_PATH` variable is set, `fbt` will use that directory to unpack toolchain into. By default, it downloads toolchain into `toolchain` subdirectory repo's root. | ||||||
|  | 
 | ||||||
|  | If you want to enable extra debug output for `fbt` and toolchain management scripts, you can `set FBT_VERBOSE=1`. | ||||||
|  | 
 | ||||||
|  | `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in the environment: | ||||||
|   - On Windows, it's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from |   - On Windows, it's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from | ||||||
|   - On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...` |   - On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...` | ||||||
| - `fbt` builds updater & firmware in separate subdirectories in `build`, and their names depend on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, the latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder (used for code completion support in IDE). | 
 | ||||||
|  |  > There are more variables controlling basic `fbt` behavior. See `fbt` & `fbtenv` scripts' sources for details. | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| ## Invoking FBT | ## Invoking FBT | ||||||
| 
 | 
 | ||||||
| @ -23,6 +33,12 @@ To build with FBT, call it and specify configuration options & targets to build. | |||||||
| 
 | 
 | ||||||
| To run cleanup (think of `make clean`) for specified targets, add the `-c` option. | To run cleanup (think of `make clean`) for specified targets, add the `-c` option. | ||||||
| 
 | 
 | ||||||
|  | ## Build directories | ||||||
|  | 
 | ||||||
|  | `fbt` builds updater & firmware in separate subdirectories in `build`, and their names depend on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, the latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder (it is used for code completion support in IDEs). | ||||||
|  |   | ||||||
|  | `build/latest` symlink & compilation database are only updated upon *firmware build targets* - that is, when you're re-building the firmware itself. Running other tasks, like firmware flashing or building update bundles *for a different debug/release configuration or hardware target*, does not update `built/latest` dir to point to that configuration. | ||||||
|  | 
 | ||||||
| ## VSCode integration | ## VSCode integration | ||||||
| 
 | 
 | ||||||
| `fbt` includes basic development environment configuration for VS Code. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VS Code and choosing the firmware root folder in the "File > Open Folder" menu. | `fbt` includes basic development environment configuration for VS Code. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VS Code and choosing the firmware root folder in the "File > Open Folder" menu. | ||||||
|  | |||||||
| @ -75,6 +75,14 @@ Can be combined with a special key command or a single character. | |||||||
| | ------- | ----------- | ----------------- | | | ------- | ----------- | ----------------- | | ||||||
| | STRING  | Text string | Print text string | | | STRING  | Text string | Print text string | | ||||||
| 
 | 
 | ||||||
|  | ## String delay | ||||||
|  | 
 | ||||||
|  | Delay between keypresses. | ||||||
|  | |Command|Parameters|Notes| | ||||||
|  | |-|-|-| | ||||||
|  | |STRING_DELAY|Delay value in ms|Applied once to next appearing string| | ||||||
|  | |STRINGDELAY|Delay value in ms|Same as STRING_DELAY| | ||||||
|  | 
 | ||||||
| ## Repeat | ## Repeat | ||||||
| 
 | 
 | ||||||
| | Command | Parameters                   | Notes                   | | | Command | Parameters                   | Notes                   | | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								fbt
									
									
									
									
									
								
							
							
						
						| @ -6,16 +6,21 @@ set -eu; | |||||||
| 
 | 
 | ||||||
| # private variables | # private variables | ||||||
| SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd -P)"; | SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd -P)"; | ||||||
| SCONS_DEFAULT_FLAGS="-Q --warn=target-not-built"; | SCONS_DEFAULT_FLAGS="--warn=target-not-built"; | ||||||
| SCONS_EP="python3 -m SCons" | SCONS_EP="python3 -m SCons"; | ||||||
| 
 | 
 | ||||||
| # public variables | # public variables | ||||||
| FBT_NOENV="${FBT_NOENV:-""}"; | FBT_NOENV="${FBT_NOENV:-""}"; | ||||||
| FBT_NO_SYNC="${FBT_NO_SYNC:-""}"; | FBT_NO_SYNC="${FBT_NO_SYNC:-""}"; | ||||||
| FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; | FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; | ||||||
|  | FBT_VERBOSE="${FBT_VERBOSE:-""}"; | ||||||
| 
 | 
 | ||||||
| if [ -z "$FBT_NOENV" ]; then | if [ -z "$FBT_NOENV" ]; then | ||||||
|     . "$SCRIPT_PATH/scripts/toolchain/fbtenv.sh"; |     FBT_VERBOSE="$FBT_VERBOSE" . "$SCRIPT_PATH/scripts/toolchain/fbtenv.sh"; | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ -z "$FBT_VERBOSE" ]; then | ||||||
|  |     SCONS_DEFAULT_FLAGS="$SCONS_DEFAULT_FLAGS -Q"; | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
| if [ -z "$FBT_NO_SYNC" ]; then | if [ -z "$FBT_NO_SYNC" ]; then | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								fbt.cmd
									
									
									
									
									
								
							
							
						
						| @ -12,5 +12,10 @@ if [%FBT_NO_SYNC%] == [] ( | |||||||
|     ) |     ) | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| set "SCONS_DEFAULT_FLAGS=-Q --warn=target-not-built" | set "SCONS_DEFAULT_FLAGS=--warn=target-not-built" | ||||||
|  | 
 | ||||||
|  | if not defined FBT_VERBOSE ( | ||||||
|  |     set "SCONS_DEFAULT_FLAGS=%SCONS_DEFAULT_FLAGS% -Q" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| %SCONS_EP% %SCONS_DEFAULT_FLAGS% %* | %SCONS_EP% %SCONS_DEFAULT_FLAGS% %* | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| entry,status,name,type,params | entry,status,name,type,params | ||||||
| Version,+,14.0,, | Version,+,15.0,, | ||||||
| Header,+,applications/services/bt/bt_service/bt.h,, | Header,+,applications/services/bt/bt_service/bt.h,, | ||||||
| Header,+,applications/services/cli/cli.h,, | Header,+,applications/services/cli/cli.h,, | ||||||
| Header,+,applications/services/cli/cli_vcp.h,, | Header,+,applications/services/cli/cli_vcp.h,, | ||||||
| @ -979,7 +979,7 @@ Function,+,furi_hal_power_enable_external_3_3v,void, | |||||||
| Function,+,furi_hal_power_enable_otg,void, | Function,+,furi_hal_power_enable_otg,void, | ||||||
| Function,+,furi_hal_power_gauge_is_ok,_Bool, | Function,+,furi_hal_power_gauge_is_ok,_Bool, | ||||||
| Function,+,furi_hal_power_get_bat_health_pct,uint8_t, | 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_charge_voltage_limit,float, | ||||||
| Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC | 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_design_capacity,uint32_t, | ||||||
| Function,+,furi_hal_power_get_battery_full_capacity,uint32_t, | Function,+,furi_hal_power_get_battery_full_capacity,uint32_t, | ||||||
| @ -998,7 +998,7 @@ Function,+,furi_hal_power_is_charging_done,_Bool, | |||||||
| Function,+,furi_hal_power_is_otg_enabled,_Bool, | Function,+,furi_hal_power_is_otg_enabled,_Bool, | ||||||
| Function,+,furi_hal_power_off,void, | Function,+,furi_hal_power_off,void, | ||||||
| Function,+,furi_hal_power_reset,void, | Function,+,furi_hal_power_reset,void, | ||||||
| Function,+,furi_hal_power_set_battery_charging_voltage,void,float | Function,+,furi_hal_power_set_battery_charge_voltage_limit,void,float | ||||||
| Function,+,furi_hal_power_shutdown,void, | Function,+,furi_hal_power_shutdown,void, | ||||||
| Function,+,furi_hal_power_sleep,void, | Function,+,furi_hal_power_sleep,void, | ||||||
| Function,+,furi_hal_power_sleep_available,_Bool, | Function,+,furi_hal_power_sleep_available,_Bool, | ||||||
|  | |||||||
| 
 | 
| @ -1,5 +1,5 @@ | |||||||
| entry,status,name,type,params | entry,status,name,type,params | ||||||
| Version,+,14.0,, | Version,+,15.0,, | ||||||
| Header,+,applications/services/bt/bt_service/bt.h,, | Header,+,applications/services/bt/bt_service/bt.h,, | ||||||
| Header,+,applications/services/cli/cli.h,, | Header,+,applications/services/cli/cli.h,, | ||||||
| Header,+,applications/services/cli/cli_vcp.h,, | Header,+,applications/services/cli/cli_vcp.h,, | ||||||
| @ -1171,6 +1171,7 @@ Function,+,furi_hal_mpu_protect_disable,void,FuriHalMpuRegion | |||||||
| Function,+,furi_hal_mpu_protect_no_access,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" | Function,+,furi_hal_mpu_protect_no_access,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" | ||||||
| Function,+,furi_hal_mpu_protect_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" | Function,+,furi_hal_mpu_protect_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize" | ||||||
| Function,+,furi_hal_nfc_activate_nfca,_Bool,"uint32_t, uint32_t*" | Function,+,furi_hal_nfc_activate_nfca,_Bool,"uint32_t, uint32_t*" | ||||||
|  | Function,-,furi_hal_nfc_deinit,void, | ||||||
| Function,+,furi_hal_nfc_detect,_Bool,"FuriHalNfcDevData*, uint32_t" | Function,+,furi_hal_nfc_detect,_Bool,"FuriHalNfcDevData*, uint32_t" | ||||||
| Function,+,furi_hal_nfc_emulate_nfca,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, FuriHalNfcEmulateCallback, void*, uint32_t" | Function,+,furi_hal_nfc_emulate_nfca,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, FuriHalNfcEmulateCallback, void*, uint32_t" | ||||||
| Function,+,furi_hal_nfc_exit_sleep,void, | Function,+,furi_hal_nfc_exit_sleep,void, | ||||||
| @ -1210,7 +1211,7 @@ Function,+,furi_hal_power_enable_external_3_3v,void, | |||||||
| Function,+,furi_hal_power_enable_otg,void, | Function,+,furi_hal_power_enable_otg,void, | ||||||
| Function,+,furi_hal_power_gauge_is_ok,_Bool, | Function,+,furi_hal_power_gauge_is_ok,_Bool, | ||||||
| Function,+,furi_hal_power_get_bat_health_pct,uint8_t, | 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_charge_voltage_limit,float, | ||||||
| Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC | 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_design_capacity,uint32_t, | ||||||
| Function,+,furi_hal_power_get_battery_full_capacity,uint32_t, | Function,+,furi_hal_power_get_battery_full_capacity,uint32_t, | ||||||
| @ -1229,7 +1230,7 @@ Function,+,furi_hal_power_is_charging_done,_Bool, | |||||||
| Function,+,furi_hal_power_is_otg_enabled,_Bool, | Function,+,furi_hal_power_is_otg_enabled,_Bool, | ||||||
| Function,+,furi_hal_power_off,void, | Function,+,furi_hal_power_off,void, | ||||||
| Function,+,furi_hal_power_reset,void, | Function,+,furi_hal_power_reset,void, | ||||||
| Function,+,furi_hal_power_set_battery_charging_voltage,void,float | Function,+,furi_hal_power_set_battery_charge_voltage_limit,void,float | ||||||
| Function,+,furi_hal_power_shutdown,void, | Function,+,furi_hal_power_shutdown,void, | ||||||
| Function,+,furi_hal_power_sleep,void, | Function,+,furi_hal_power_sleep,void, | ||||||
| Function,+,furi_hal_power_sleep_available,_Bool, | Function,+,furi_hal_power_sleep_available,_Bool, | ||||||
|  | |||||||
| 
 | 
| @ -17,7 +17,6 @@ | |||||||
| #define SD_DUMMY_BYTE 0xFF | #define SD_DUMMY_BYTE 0xFF | ||||||
| #define SD_ANSWER_RETRY_COUNT 8 | #define SD_ANSWER_RETRY_COUNT 8 | ||||||
| #define SD_IDLE_RETRY_COUNT 100 | #define SD_IDLE_RETRY_COUNT 100 | ||||||
| #define SD_BLOCK_SIZE 512 |  | ||||||
| 
 | 
 | ||||||
| #define FLAG_SET(x, y) (((x) & (y)) == (y)) | #define FLAG_SET(x, y) (((x) & (y)) == (y)) | ||||||
| 
 | 
 | ||||||
| @ -598,23 +597,6 @@ static SdSpiStatus sd_spi_get_cid(SD_CID* Cid) { | |||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static inline bool sd_cache_get(uint32_t address, uint32_t* data) { |  | ||||||
|     uint8_t* cached_data = sector_cache_get(address); |  | ||||||
|     if(cached_data) { |  | ||||||
|         memcpy(data, cached_data, SD_BLOCK_SIZE); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static inline void sd_cache_put(uint32_t address, uint32_t* data) { |  | ||||||
|     sector_cache_put(address, (uint8_t*)data); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static inline void sd_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector) { |  | ||||||
|     sector_cache_invalidate_range(start_sector, end_sector); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static SdSpiStatus | static SdSpiStatus | ||||||
|     sd_spi_cmd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { |     sd_spi_cmd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { | ||||||
|     uint32_t block_address = address; |     uint32_t block_address = address; | ||||||
| @ -833,30 +815,12 @@ SdSpiStatus sd_get_card_info(SD_CardInfo* card_info) { | |||||||
| 
 | 
 | ||||||
| SdSpiStatus | SdSpiStatus | ||||||
|     sd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { |     sd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { | ||||||
|     SdSpiStatus status = SdSpiStatusError; |     SdSpiStatus status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms); | ||||||
| 
 |  | ||||||
|     bool single_sector_read = (blocks == 1); |  | ||||||
| 
 |  | ||||||
|     if(single_sector_read) { |  | ||||||
|         if(sd_cache_get(address, data)) { |  | ||||||
|             return SdSpiStatusOK; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms); |  | ||||||
| 
 |  | ||||||
|         if(status == SdSpiStatusOK) { |  | ||||||
|             sd_cache_put(address, data); |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return status; |     return status; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| SdSpiStatus | SdSpiStatus | ||||||
|     sd_write_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { |     sd_write_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) { | ||||||
|     sd_cache_invalidate_range(address, address + blocks); |  | ||||||
|     SdSpiStatus status = sd_spi_cmd_write_blocks(data, address, blocks, timeout_ms); |     SdSpiStatus status = sd_spi_cmd_write_blocks(data, address, blocks, timeout_ms); | ||||||
|     return status; |     return status; | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ | |||||||
| #define __IO volatile | #define __IO volatile | ||||||
| 
 | 
 | ||||||
| #define SD_TIMEOUT_MS (1000) | #define SD_TIMEOUT_MS (1000) | ||||||
|  | #define SD_BLOCK_SIZE 512 | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     SdSpiStatusOK, |     SdSpiStatusOK, | ||||||
|  | |||||||
| @ -36,6 +36,7 @@ | |||||||
| /* Includes ------------------------------------------------------------------*/ | /* Includes ------------------------------------------------------------------*/ | ||||||
| #include "user_diskio.h" | #include "user_diskio.h" | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
|  | #include "sector_cache.h" | ||||||
| /* Private typedef -----------------------------------------------------------*/ | /* Private typedef -----------------------------------------------------------*/ | ||||||
| /* Private define ------------------------------------------------------------*/ | /* Private define ------------------------------------------------------------*/ | ||||||
| 
 | 
 | ||||||
| @ -79,6 +80,26 @@ Diskio_drvTypeDef USER_Driver = { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /* Private functions ---------------------------------------------------------*/ | /* Private functions ---------------------------------------------------------*/ | ||||||
|  | static inline bool sd_cache_get(uint32_t address, uint32_t* data) { | ||||||
|  |     uint8_t* cached_data = sector_cache_get(address); | ||||||
|  |     if(cached_data) { | ||||||
|  |         memcpy(data, cached_data, SD_BLOCK_SIZE); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void sd_cache_put(uint32_t address, uint32_t* data) { | ||||||
|  |     sector_cache_put(address, (uint8_t*)data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void sd_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector) { | ||||||
|  |     sector_cache_invalidate_range(start_sector, end_sector); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void sd_cache_invalidate_all() { | ||||||
|  |     sector_cache_init(); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|   * @brief  Initializes a Drive |   * @brief  Initializes a Drive | ||||||
| @ -125,6 +146,14 @@ DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { | |||||||
|     UNUSED(pdrv); |     UNUSED(pdrv); | ||||||
|     DRESULT res = RES_ERROR; |     DRESULT res = RES_ERROR; | ||||||
| 
 | 
 | ||||||
|  |     bool single_sector = count == 1; | ||||||
|  | 
 | ||||||
|  |     if(single_sector) { | ||||||
|  |         if(sd_cache_get(sector, (uint32_t*)buff)) { | ||||||
|  |             return RES_OK; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); |     furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); | ||||||
|     furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; |     furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; | ||||||
| 
 | 
 | ||||||
| @ -145,6 +174,10 @@ DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { | |||||||
|     furi_hal_sd_spi_handle = NULL; |     furi_hal_sd_spi_handle = NULL; | ||||||
|     furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); |     furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_fast); | ||||||
| 
 | 
 | ||||||
|  |     if(single_sector && res == RES_OK) { | ||||||
|  |         sd_cache_put(sector, (uint32_t*)buff); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return res; |     return res; | ||||||
|     /* USER CODE END READ */ |     /* USER CODE END READ */ | ||||||
| } | } | ||||||
| @ -164,6 +197,8 @@ DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { | |||||||
|     UNUSED(pdrv); |     UNUSED(pdrv); | ||||||
|     DRESULT res = RES_ERROR; |     DRESULT res = RES_ERROR; | ||||||
| 
 | 
 | ||||||
|  |     sd_cache_invalidate_range(sector, sector + count); | ||||||
|  | 
 | ||||||
|     furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); |     furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); | ||||||
|     furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; |     furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; | ||||||
| 
 | 
 | ||||||
| @ -175,6 +210,8 @@ DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { | |||||||
|         res = RES_OK; |         res = RES_OK; | ||||||
|         while(sd_get_card_state() != SdSpiStatusOK) { |         while(sd_get_card_state() != SdSpiStatusOK) { | ||||||
|             if(furi_hal_cortex_timer_is_expired(timer)) { |             if(furi_hal_cortex_timer_is_expired(timer)) { | ||||||
|  |                 sd_cache_invalidate_all(); | ||||||
|  | 
 | ||||||
|                 res = RES_ERROR; |                 res = RES_ERROR; | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -24,13 +24,29 @@ FuriEventFlag* event = NULL; | |||||||
| #define FURI_HAL_NFC_UID_INCOMPLETE (0x04) | #define FURI_HAL_NFC_UID_INCOMPLETE (0x04) | ||||||
| 
 | 
 | ||||||
| void furi_hal_nfc_init() { | void furi_hal_nfc_init() { | ||||||
|  |     furi_assert(!event); | ||||||
|  |     event = furi_event_flag_alloc(); | ||||||
|  | 
 | ||||||
|     ReturnCode ret = rfalNfcInitialize(); |     ReturnCode ret = rfalNfcInitialize(); | ||||||
|     if(ret == ERR_NONE) { |     if(ret == ERR_NONE) { | ||||||
|         furi_hal_nfc_start_sleep(); |         furi_hal_nfc_start_sleep(); | ||||||
|         event = furi_event_flag_alloc(); |  | ||||||
|         FURI_LOG_I(TAG, "Init OK"); |         FURI_LOG_I(TAG, "Init OK"); | ||||||
|     } else { |     } else { | ||||||
|         FURI_LOG_W(TAG, "Initialization failed, RFAL returned: %d", ret); |         FURI_LOG_W(TAG, "Init Failed, RFAL returned: %d", ret); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void furi_hal_nfc_deinit() { | ||||||
|  |     ReturnCode ret = rfalDeinitialize(); | ||||||
|  |     if(ret == ERR_NONE) { | ||||||
|  |         FURI_LOG_I(TAG, "Deinit OK"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_W(TAG, "Deinit Failed, RFAL returned: %d", ret); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(event) { | ||||||
|  |         furi_event_flag_free(event); | ||||||
|  |         event = NULL; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -101,6 +101,10 @@ typedef struct { | |||||||
|  */ |  */ | ||||||
| void furi_hal_nfc_init(); | void furi_hal_nfc_init(); | ||||||
| 
 | 
 | ||||||
|  | /** Deinit nfc
 | ||||||
|  |  */ | ||||||
|  | void furi_hal_nfc_deinit(); | ||||||
|  | 
 | ||||||
| /** Check if nfc worker is busy
 | /** Check if nfc worker is busy
 | ||||||
|  * |  * | ||||||
|  * @return     true if busy |  * @return     true if busy | ||||||
|  | |||||||
| @ -341,14 +341,14 @@ bool furi_hal_power_is_otg_enabled() { | |||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| float furi_hal_power_get_battery_charging_voltage() { | float furi_hal_power_get_battery_charge_voltage_limit() { | ||||||
|     furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); |     furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); | ||||||
|     float ret = (float)bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power) / 1000.0f; |     float ret = (float)bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power) / 1000.0f; | ||||||
|     furi_hal_i2c_release(&furi_hal_i2c_handle_power); |     furi_hal_i2c_release(&furi_hal_i2c_handle_power); | ||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void furi_hal_power_set_battery_charging_voltage(float voltage) { | void furi_hal_power_set_battery_charge_voltage_limit(float voltage) { | ||||||
|     furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); |     furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); | ||||||
|     // Adding 0.0005 is necessary because 4.016f is 4.015999794000, which gets truncated
 |     // 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)); |     bq25896_set_vreg_voltage(&furi_hal_i2c_handle_power, (uint16_t)(voltage * 1000.0f + 0.0005f)); | ||||||
| @ -486,7 +486,7 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) | |||||||
|         property_value_out(&property_context, NULL, 2, "format", "major", "2"); |         property_value_out(&property_context, NULL, 2, "format", "major", "2"); | ||||||
|         property_value_out(&property_context, NULL, 2, "format", "minor", "1"); |         property_value_out(&property_context, NULL, 2, "format", "minor", "1"); | ||||||
|     } else { |     } else { | ||||||
|         property_value_out(&property_context, NULL, 3, "power", "info", "major", "1"); |         property_value_out(&property_context, NULL, 3, "power", "info", "major", "2"); | ||||||
|         property_value_out(&property_context, NULL, 3, "power", "info", "minor", "1"); |         property_value_out(&property_context, NULL, 3, "power", "info", "minor", "1"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -505,8 +505,10 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     property_value_out(&property_context, NULL, 2, "charge", "state", charge_state); |     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); |     uint16_t charge_voltage_limit = | ||||||
|     property_value_out(&property_context, "%u", 2, "charge", "voltage", charge_voltage); |         (uint16_t)(furi_hal_power_get_battery_charge_voltage_limit() * 1000.f); | ||||||
|  |     property_value_out( | ||||||
|  |         &property_context, "%u", 3, "charge", "voltage", "limit", charge_voltage_limit); | ||||||
|     uint16_t voltage = |     uint16_t voltage = | ||||||
|         (uint16_t)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f); |         (uint16_t)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f); | ||||||
|     property_value_out(&property_context, "%u", 2, "battery", "voltage", voltage); |     property_value_out(&property_context, "%u", 2, "battery", "voltage", voltage); | ||||||
|  | |||||||
| @ -121,21 +121,21 @@ void furi_hal_power_check_otg_status(); | |||||||
|  */ |  */ | ||||||
| bool furi_hal_power_is_otg_enabled(); | bool furi_hal_power_is_otg_enabled(); | ||||||
| 
 | 
 | ||||||
| /** Get battery charging voltage in V
 | /** Get battery charge voltage limit in V
 | ||||||
|  * |  * | ||||||
|  * @return     voltage in V |  * @return     voltage in V | ||||||
|  */ |  */ | ||||||
| float furi_hal_power_get_battery_charging_voltage(); | float furi_hal_power_get_battery_charge_voltage_limit(); | ||||||
| 
 | 
 | ||||||
| /** Set battery charging voltage in V
 | /** Set battery charge voltage limit in V
 | ||||||
|  * |  * | ||||||
|  * Invalid values will be clamped to the nearest valid value. |  * Invalid values will be clamped downward to the nearest valid value. | ||||||
|  * |  * | ||||||
|  * @param      voltage[in]  voltage in V |  * @param      voltage[in]  voltage in V | ||||||
|  * |  * | ||||||
|  * @return     voltage in V |  * @return     voltage in V | ||||||
|  */ |  */ | ||||||
| void furi_hal_power_set_battery_charging_voltage(float voltage); | void furi_hal_power_set_battery_charge_voltage_limit(float voltage); | ||||||
| 
 | 
 | ||||||
| /** Get remaining battery battery capacity in mAh
 | /** Get remaining battery battery capacity in mAh
 | ||||||
|  * |  * | ||||||
|  | |||||||
| @ -96,9 +96,9 @@ static void furi_thread_body(void* context) { | |||||||
|     furi_assert(thread->state == FuriThreadStateRunning); |     furi_assert(thread->state == FuriThreadStateRunning); | ||||||
| 
 | 
 | ||||||
|     if(thread->is_service) { |     if(thread->is_service) { | ||||||
|         FURI_LOG_E( |         FURI_LOG_W( | ||||||
|             TAG, |             TAG, | ||||||
|             "%s service thread exited. Thread memory cannot be reclaimed.", |             "%s service thread TCB memory will not be reclaimed", | ||||||
|             thread->name ? thread->name : "<unknown service>"); |             thread->name ? thread->name : "<unknown service>"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal_version.h> | #include <furi_hal_version.h> | ||||||
| #include <furi_hal_memory.h> | #include <furi_hal_memory.h> | ||||||
|  | #include <furi_hal_rtc.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "Flipper" | #define TAG "Flipper" | ||||||
| 
 | 
 | ||||||
| @ -29,10 +30,10 @@ static void flipper_print_version(const char* target, const Version* version) { | |||||||
| void flipper_init() { | void flipper_init() { | ||||||
|     flipper_print_version("Firmware", furi_hal_version_get_firmware_version()); |     flipper_print_version("Firmware", furi_hal_version_get_firmware_version()); | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(TAG, "starting services"); |     FURI_LOG_I(TAG, "Boot mode %d, starting services", furi_hal_rtc_get_boot_mode()); | ||||||
| 
 | 
 | ||||||
|     for(size_t i = 0; i < FLIPPER_SERVICES_COUNT; i++) { |     for(size_t i = 0; i < FLIPPER_SERVICES_COUNT; i++) { | ||||||
|         FURI_LOG_I(TAG, "starting service %s", FLIPPER_SERVICES[i].name); |         FURI_LOG_I(TAG, "Starting service %s", FLIPPER_SERVICES[i].name); | ||||||
| 
 | 
 | ||||||
|         FuriThread* thread = furi_thread_alloc_ex( |         FuriThread* thread = furi_thread_alloc_ex( | ||||||
|             FLIPPER_SERVICES[i].name, |             FLIPPER_SERVICES[i].name, | ||||||
| @ -44,7 +45,7 @@ void flipper_init() { | |||||||
|         furi_thread_start(thread); |         furi_thread_start(thread); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(TAG, "services startup complete"); |     FURI_LOG_I(TAG, "Startup complete"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void vApplicationGetIdleTaskMemory( | void vApplicationGetIdleTaskMemory( | ||||||
|  | |||||||
| @ -140,19 +140,16 @@ uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle) { | |||||||
| 
 | 
 | ||||||
| void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) { | void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) { | ||||||
|     if(vreg_voltage < 3840) { |     if(vreg_voltage < 3840) { | ||||||
|         // Minimum value is 3840 mV
 |         // Minimum valid value is 3840 mV
 | ||||||
|         bq25896_regs.r06.VREG = 0; |         vreg_voltage = 3840; | ||||||
|     } else { |     } else if(vreg_voltage > 4208) { | ||||||
|  |         // Maximum safe value is 4208 mV
 | ||||||
|  |         vreg_voltage = 4208; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // Find the nearest voltage value (subtract offset, divide into sections)
 |     // Find the nearest voltage value (subtract offset, divide into sections)
 | ||||||
|     // Values are truncated downward as needed (e.g. 4200mV -> 4192 mV)
 |     // Values are truncated downward as needed (e.g. 4200mV -> 4192 mV)
 | ||||||
|     bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16); |     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
 |     // Apply changes
 | ||||||
|     furi_hal_i2c_write_reg_8( |     furi_hal_i2c_write_reg_8( | ||||||
|  | |||||||
| @ -36,10 +36,10 @@ void bq25896_disable_otg(FuriHalI2cBusHandle* handle); | |||||||
| /** Is otg enabled */ | /** Is otg enabled */ | ||||||
| bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle); | bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle); | ||||||
| 
 | 
 | ||||||
| /** Get VREG (charging) voltage in mV */ | /** Get VREG (charging limit) voltage in mV */ | ||||||
| uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle); | uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle); | ||||||
| 
 | 
 | ||||||
| /** Set VREG (charging) voltage in mV
 | /** Set VREG (charging limit) voltage in mV
 | ||||||
|  * |  * | ||||||
|  * Valid range: 3840mV - 4208mV, in steps of 16mV |  * Valid range: 3840mV - 4208mV, in steps of 16mV | ||||||
|  */ |  */ | ||||||
|  | |||||||
| @ -56,6 +56,8 @@ void nfc_worker_start( | |||||||
|     while(furi_hal_nfc_is_busy()) { |     while(furi_hal_nfc_is_busy()) { | ||||||
|         furi_delay_ms(10); |         furi_delay_ms(10); | ||||||
|     } |     } | ||||||
|  |     furi_hal_nfc_deinit(); | ||||||
|  |     furi_hal_nfc_init(); | ||||||
| 
 | 
 | ||||||
|     nfc_worker->callback = callback; |     nfc_worker->callback = callback; | ||||||
|     nfc_worker->context = context; |     nfc_worker->context = context; | ||||||
|  | |||||||
| @ -22,7 +22,6 @@ def _convert_image(source_filename: str): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DolphinBubbleAnimation: | class DolphinBubbleAnimation: | ||||||
| 
 |  | ||||||
|     FILE_TYPE = "Flipper Animation" |     FILE_TYPE = "Flipper Animation" | ||||||
|     FILE_VERSION = 1 |     FILE_VERSION = 1 | ||||||
| 
 | 
 | ||||||
| @ -243,7 +242,6 @@ class DolphinBubbleAnimation: | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DolphinManifest: | class DolphinManifest: | ||||||
| 
 |  | ||||||
|     FILE_TYPE = "Flipper Animation Manifest" |     FILE_TYPE = "Flipper Animation Manifest" | ||||||
|     FILE_VERSION = 1 |     FILE_VERSION = 1 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -105,7 +105,7 @@ def file2image(file): | |||||||
|     data_enc = bytearray([len(data_enc) & 0xFF, len(data_enc) >> 8]) + data_enc |     data_enc = bytearray([len(data_enc) & 0xFF, len(data_enc) >> 8]) + data_enc | ||||||
| 
 | 
 | ||||||
|     # Use encoded data only if its length less than original, including header |     # Use encoded data only if its length less than original, including header | ||||||
|     if len(data_enc) < len(data_bin) + 1: |     if len(data_enc) + 2 < len(data_bin) + 1: | ||||||
|         data = b"\x01\x00" + data_enc |         data = b"\x01\x00" + data_enc | ||||||
|     else: |     else: | ||||||
|         data = b"\x00" + data_bin |         data = b"\x00" + data_bin | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import serial.tools.list_ports as list_ports | import serial.tools.list_ports as list_ports | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| # Returns a valid port or None, if it cannot be found | # Returns a valid port or None, if it cannot be found | ||||||
| def resolve_port(logger, portname: str = "auto"): | def resolve_port(logger, portname: str = "auto"): | ||||||
|     if portname != "auto": |     if portname != "auto": | ||||||
|  | |||||||
| @ -173,12 +173,14 @@ class Templite: | |||||||
|         """Renders the template according to the given namespace.""" |         """Renders the template according to the given namespace.""" | ||||||
|         stack = [] |         stack = [] | ||||||
|         namespace["__file__"] = self.file |         namespace["__file__"] = self.file | ||||||
|  | 
 | ||||||
|         # add write method |         # add write method | ||||||
|         def write(*args): |         def write(*args): | ||||||
|             for value in args: |             for value in args: | ||||||
|                 stack.append(str(value)) |                 stack.append(str(value)) | ||||||
| 
 | 
 | ||||||
|         namespace["write"] = write |         namespace["write"] = write | ||||||
|  | 
 | ||||||
|         # add include method |         # add include method | ||||||
|         def include(file): |         def include(file): | ||||||
|             if not os.path.isabs(file): |             if not os.path.isabs(file): | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ if not ["%FBT_NOENV%"] == [""] ( | |||||||
|     exit /b 0 |     exit /b 0 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| set "FLIPPER_TOOLCHAIN_VERSION=19" | set "FLIPPER_TOOLCHAIN_VERSION=21" | ||||||
| 
 | 
 | ||||||
| if ["%FBT_TOOLCHAIN_ROOT%"] == [""] ( | if ["%FBT_TOOLCHAIN_ROOT%"] == [""] ( | ||||||
|     set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" |     set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" | ||||||
| @ -31,7 +31,13 @@ if not exist "%FBT_TOOLCHAIN_VERSION_FILE%" ( | |||||||
| 
 | 
 | ||||||
| set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_VERSION_FILE%" | set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_VERSION_FILE%" | ||||||
| if not "%REAL_TOOLCHAIN_VERSION%" == "%FLIPPER_TOOLCHAIN_VERSION%" ( | if not "%REAL_TOOLCHAIN_VERSION%" == "%FLIPPER_TOOLCHAIN_VERSION%" ( | ||||||
|  |     echo FBT: starting toolchain upgrade process.. | ||||||
|     powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" |     powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" | ||||||
|  |     set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_VERSION_FILE%" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | if defined FBT_VERBOSE ( | ||||||
|  |     echo FBT: using toolchain version %REAL_TOOLCHAIN_VERSION% | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| set "HOME=%USERPROFILE%" | set "HOME=%USERPROFILE%" | ||||||
|  | |||||||
| @ -5,15 +5,16 @@ | |||||||
| # public variables | # public variables | ||||||
| DEFAULT_SCRIPT_PATH="$(pwd -P)"; | DEFAULT_SCRIPT_PATH="$(pwd -P)"; | ||||||
| SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; | SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; | ||||||
| FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"19"}"; | FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"21"}"; | ||||||
| FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; | FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; | ||||||
|  | FBT_VERBOSE="${FBT_VERBOSE:-""}"; | ||||||
| 
 | 
 | ||||||
| fbtenv_show_usage() | fbtenv_show_usage() | ||||||
| { | { | ||||||
|     echo "Running this script manually is wrong, please source it"; |     echo "Running this script manually is wrong, please source it"; | ||||||
|     echo "Example:"; |     echo "Example:"; | ||||||
|     printf "\tsource scripts/toolchain/fbtenv.sh\n"; |     printf "\tsource scripts/toolchain/fbtenv.sh\n"; | ||||||
|     echo "To restore your environment source fbtenv.sh with '--restore'." |     echo "To restore your environment, source fbtenv.sh with '--restore'." | ||||||
|     echo "Example:"; |     echo "Example:"; | ||||||
|     printf "\tsource scripts/toolchain/fbtenv.sh --restore\n"; |     printf "\tsource scripts/toolchain/fbtenv.sh --restore\n"; | ||||||
| } | } | ||||||
| @ -35,16 +36,26 @@ fbtenv_restore_env() | |||||||
|     PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/bin://g")"; |     PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/bin://g")"; | ||||||
|     PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/protobuf\/bin://g")"; |     PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/protobuf\/bin://g")"; | ||||||
|     PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openocd\/bin://g")"; |     PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openocd\/bin://g")"; | ||||||
|  |     PATH="$(echo "$PATH" | /usr/bin/sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openssl\/bin://g")"; | ||||||
|     if [ -n "${PS1:-""}" ]; then |     if [ -n "${PS1:-""}" ]; then | ||||||
|         PS1="$(echo "$PS1" | sed 's/\[fbt\]//g')"; |         PS1="$(echo "$PS1" | sed 's/\[fbt\]//g')"; | ||||||
|     elif [ -n "${PROMPT:-""}" ]; then |     elif [ -n "${PROMPT:-""}" ]; then | ||||||
|         PROMPT="$(echo "$PROMPT" | sed 's/\[fbt\]//g')"; |         PROMPT="$(echo "$PROMPT" | sed 's/\[fbt\]//g')"; | ||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
|     PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE"; |     if [ -n "$SAVED_SSL_CERT_FILE" ]; then | ||||||
|     PYTHONPATH="$SAVED_PYTHONPATH"; |         export SSL_CERT_FILE="$SAVED_SSL_CERT_FILE"; | ||||||
|     PYTHONHOME="$SAVED_PYTHONHOME"; |         export REQUESTS_CA_BUNDLE="$SAVED_REQUESTS_CA_BUNDLE"; | ||||||
|  |     else | ||||||
|  |         unset SSL_CERT_FILE; | ||||||
|  |         unset REQUESTS_CA_BUNDLE; | ||||||
|  |     fi | ||||||
|  |     export PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE"; | ||||||
|  |     export PYTHONPATH="$SAVED_PYTHONPATH"; | ||||||
|  |     export PYTHONHOME="$SAVED_PYTHONHOME"; | ||||||
| 
 | 
 | ||||||
|  |     unset SAVED_SSL_CERT_FILE; | ||||||
|  |     unset SAVED_REQUESTS_CA_BUNDLE; | ||||||
|     unset SAVED_PYTHONNOUSERSITE; |     unset SAVED_PYTHONNOUSERSITE; | ||||||
|     unset SAVED_PYTHONPATH; |     unset SAVED_PYTHONPATH; | ||||||
|     unset SAVED_PYTHONHOME; |     unset SAVED_PYTHONHOME; | ||||||
| @ -121,7 +132,7 @@ fbtenv_get_kernel_type() | |||||||
|         TOOLCHAIN_ARCH_DIR="$FBT_TOOLCHAIN_PATH/toolchain/x86_64-linux"; |         TOOLCHAIN_ARCH_DIR="$FBT_TOOLCHAIN_PATH/toolchain/x86_64-linux"; | ||||||
|         TOOLCHAIN_URL="https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-10.3-x86_64-linux-flipper-$FBT_TOOLCHAIN_VERSION.tar.gz"; |         TOOLCHAIN_URL="https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-10.3-x86_64-linux-flipper-$FBT_TOOLCHAIN_VERSION.tar.gz"; | ||||||
|     elif echo "$SYS_TYPE" | grep -q "MINGW"; then |     elif echo "$SYS_TYPE" | grep -q "MINGW"; then | ||||||
|         echo "In MinGW shell use \"[u]fbt.cmd\" instead of \"[u]fbt\""; |         echo "In MinGW shell, use \"[u]fbt.cmd\" instead of \"[u]fbt\""; | ||||||
|         return 1; |         return 1; | ||||||
|     else |     else | ||||||
|         echo "Your system configuration is not supported. Sorry.. Please report us your configuration."; |         echo "Your system configuration is not supported. Sorry.. Please report us your configuration."; | ||||||
| @ -248,6 +259,7 @@ fbtenv_check_download_toolchain() | |||||||
|     elif [ ! -f "$TOOLCHAIN_ARCH_DIR/VERSION" ]; then |     elif [ ! -f "$TOOLCHAIN_ARCH_DIR/VERSION" ]; then | ||||||
|         fbtenv_download_toolchain || return 1; |         fbtenv_download_toolchain || return 1; | ||||||
|     elif [ "$(cat "$TOOLCHAIN_ARCH_DIR/VERSION")" -ne "$FBT_TOOLCHAIN_VERSION" ]; then |     elif [ "$(cat "$TOOLCHAIN_ARCH_DIR/VERSION")" -ne "$FBT_TOOLCHAIN_VERSION" ]; then | ||||||
|  |         echo "FBT: starting toolchain upgrade process.." | ||||||
|         fbtenv_download_toolchain || return 1; |         fbtenv_download_toolchain || return 1; | ||||||
|     fi |     fi | ||||||
|     return 0; |     return 0; | ||||||
| @ -269,6 +281,13 @@ fbtenv_download_toolchain() | |||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | fbtenv_print_version() | ||||||
|  | { | ||||||
|  |     if [ -n "$FBT_VERBOSE" ]; then | ||||||
|  |         echo "FBT: using toolchain version $(cat "$TOOLCHAIN_ARCH_DIR/VERSION")"; | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
| fbtenv_main() | fbtenv_main() | ||||||
| { | { | ||||||
|     fbtenv_check_sourced || return 1; |     fbtenv_check_sourced || return 1; | ||||||
| @ -281,18 +300,25 @@ fbtenv_main() | |||||||
|     fbtenv_check_script_path || return 1; |     fbtenv_check_script_path || return 1; | ||||||
|     fbtenv_check_download_toolchain || return 1; |     fbtenv_check_download_toolchain || return 1; | ||||||
|     fbtenv_set_shell_prompt; |     fbtenv_set_shell_prompt; | ||||||
|  |     fbtenv_print_version; | ||||||
|     PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; |     PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; | ||||||
|     PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; |     PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; | ||||||
|     PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; |     PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; | ||||||
|     PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH"; |     PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH"; | ||||||
|  |     PATH="$TOOLCHAIN_ARCH_DIR/openssl/bin:$PATH"; | ||||||
|  |     export PATH; | ||||||
| 
 | 
 | ||||||
|     SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}"; |     export SAVED_SSL_CERT_FILE="${SSL_CERT_FILE:-""}"; | ||||||
|     SAVED_PYTHONPATH="${PYTHONPATH:-""}"; |     export SAVED_REQUESTS_CA_BUNDLE="${REQUESTS_CA_BUNDLE:-""}"; | ||||||
|     SAVED_PYTHONHOME="${PYTHONHOME:-""}"; |     export SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}"; | ||||||
|  |     export SAVED_PYTHONPATH="${PYTHONPATH:-""}"; | ||||||
|  |     export SAVED_PYTHONHOME="${PYTHONHOME:-""}"; | ||||||
| 
 | 
 | ||||||
|     PYTHONNOUSERSITE=1; |     export SSL_CERT_FILE="$TOOLCHAIN_ARCH_DIR/python/lib/python3.11/site-packages/certifi/cacert.pem"; | ||||||
|     PYTHONPATH=; |     export REQUESTS_CA_BUNDLE="$SSL_CERT_FILE"; | ||||||
|     PYTHONHOME=; |     export PYTHONNOUSERSITE=1; | ||||||
|  |     export PYTHONPATH=; | ||||||
|  |     export PYTHONHOME=; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fbtenv_main "${1:-""}"; | fbtenv_main "${1:-""}"; | ||||||
|  | |||||||
| @ -199,7 +199,7 @@ class Main(App): | |||||||
| 
 | 
 | ||||||
|     def disclaimer(self): |     def disclaimer(self): | ||||||
|         self.logger.error( |         self.logger.error( | ||||||
|             "You might brick you device into a state in which you'd need an SWD programmer to fix it." |             "You might brick your device into a state in which you'd need an SWD programmer to fix it." | ||||||
|         ) |         ) | ||||||
|         self.logger.error( |         self.logger.error( | ||||||
|             "Please confirm that you REALLY want to do that with --I-understand-what-I-am-doing=yes" |             "Please confirm that you REALLY want to do that with --I-understand-what-I-am-doing=yes" | ||||||
|  | |||||||
 Aleksandr Kutuzov
						Aleksandr Kutuzov