Merge remote-tracking branch 'origin/release-candidate' into release
2
.github/workflows/unit_tests.yml
vendored
@ -10,7 +10,7 @@ env:
|
||||
|
||||
jobs:
|
||||
run_units_on_bench:
|
||||
runs-on: [self-hosted, FlipperZeroTest]
|
||||
runs-on: [self-hosted, FlipperZeroUnitTest]
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
|
||||
4
.github/workflows/updater_test.yml
vendored
@ -10,7 +10,7 @@ env:
|
||||
|
||||
jobs:
|
||||
test_updater_on_bench:
|
||||
runs-on: [self-hosted, FlipperZeroTestMac1]
|
||||
runs-on: [self-hosted, FlipperZeroUpdaterTest]
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
@ -27,7 +27,7 @@ jobs:
|
||||
- name: 'Get flipper from device manager (mock)'
|
||||
id: device
|
||||
run: |
|
||||
echo "flipper=/dev/tty.usbmodemflip_Rekigyn1" >> $GITHUB_OUTPUT
|
||||
echo "flipper=Rekigyn" >> $GITHUB_OUTPUT
|
||||
echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 'Flashing target firmware'
|
||||
|
||||
5
.vscode/example/launch.json
vendored
@ -11,9 +11,10 @@
|
||||
"args": {
|
||||
"useSingleResult": true,
|
||||
"env": {
|
||||
"PATH": "${workspaceFolder};${env:PATH}"
|
||||
"PATH": "${workspaceFolder};${env:PATH}",
|
||||
"FBT_QUIET": 1
|
||||
},
|
||||
"command": "./fbt get_blackmagic",
|
||||
"command": "fbt get_blackmagic",
|
||||
"description": "Get Blackmagic device",
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,56 +3,63 @@
|
||||
#include "../minunit.h"
|
||||
|
||||
static void power_test_deinit(void) {
|
||||
// Try to reset to default charging voltage
|
||||
furi_hal_power_set_battery_charging_voltage(4.208f);
|
||||
// Try to reset to default charge voltage limit
|
||||
furi_hal_power_set_battery_charge_voltage_limit(4.208f);
|
||||
}
|
||||
|
||||
MU_TEST(test_power_charge_voltage_exact) {
|
||||
// Power of 16mV charge voltages get applied exactly
|
||||
MU_TEST(test_power_charge_voltage_limit_exact) {
|
||||
// Power of 16mV charge voltage limits get applied exactly
|
||||
// (bq25896 charge controller works in 16mV increments)
|
||||
//
|
||||
// This test may need adapted if other charge controllers are used in the future.
|
||||
for(uint16_t charge_mv = 3840; charge_mv <= 4208; charge_mv += 16) {
|
||||
float charge_volt = (float)charge_mv / 1000.0f;
|
||||
furi_hal_power_set_battery_charging_voltage(charge_volt);
|
||||
mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charging_voltage());
|
||||
furi_hal_power_set_battery_charge_voltage_limit(charge_volt);
|
||||
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
|
||||
furi_hal_power_set_battery_charging_voltage(4.016f);
|
||||
mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charging_voltage());
|
||||
furi_hal_power_set_battery_charge_voltage_limit(4.016f);
|
||||
mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
}
|
||||
|
||||
MU_TEST(test_power_charge_voltage_inexact) {
|
||||
// Charge voltages that are not power of 16mV get truncated down
|
||||
furi_hal_power_set_battery_charging_voltage(3.841f);
|
||||
mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage());
|
||||
MU_TEST(test_power_charge_voltage_limit_inexact) {
|
||||
// Charge voltage limits that are not power of 16mV get truncated down
|
||||
furi_hal_power_set_battery_charge_voltage_limit(3.841f);
|
||||
mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
|
||||
furi_hal_power_set_battery_charging_voltage(3.900f);
|
||||
mu_assert_double_eq(3.888, furi_hal_power_get_battery_charging_voltage());
|
||||
furi_hal_power_set_battery_charge_voltage_limit(3.900f);
|
||||
mu_assert_double_eq(3.888, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
|
||||
furi_hal_power_set_battery_charging_voltage(4.200f);
|
||||
mu_assert_double_eq(4.192, furi_hal_power_get_battery_charging_voltage());
|
||||
furi_hal_power_set_battery_charge_voltage_limit(4.200f);
|
||||
mu_assert_double_eq(4.192, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
}
|
||||
|
||||
MU_TEST(test_power_charge_voltage_invalid_clamped) {
|
||||
// Out-of-range charge voltages get clamped to 3.840 V and 4.208 V
|
||||
furi_hal_power_set_battery_charging_voltage(3.808f);
|
||||
mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage());
|
||||
MU_TEST(test_power_charge_voltage_limit_invalid_clamped) {
|
||||
// Out-of-range charge voltage limits get clamped to 3.840 V and 4.208 V
|
||||
furi_hal_power_set_battery_charge_voltage_limit(3.808f);
|
||||
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
|
||||
// unhappy battery if this fails.
|
||||
furi_hal_power_set_battery_charging_voltage(4.240f);
|
||||
mu_assert_double_eq(4.208, furi_hal_power_get_battery_charging_voltage());
|
||||
furi_hal_power_set_battery_charge_voltage_limit(4.240f);
|
||||
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_RUN_TEST(test_power_charge_voltage_exact);
|
||||
MU_RUN_TEST(test_power_charge_voltage_floating_imprecision);
|
||||
MU_RUN_TEST(test_power_charge_voltage_inexact);
|
||||
MU_RUN_TEST(test_power_charge_voltage_invalid_clamped);
|
||||
MU_RUN_TEST(test_power_charge_voltage_limit_exact);
|
||||
MU_RUN_TEST(test_power_charge_voltage_limit_floating_imprecision);
|
||||
MU_RUN_TEST(test_power_charge_voltage_limit_inexact);
|
||||
MU_RUN_TEST(test_power_charge_voltage_limit_invalid_clamped);
|
||||
power_test_deinit();
|
||||
}
|
||||
|
||||
|
||||
@ -142,10 +142,6 @@ void bad_usb_app_free(BadUsbApp* app) {
|
||||
app->bad_usb_script = NULL;
|
||||
}
|
||||
|
||||
if(app->usb_if_prev) {
|
||||
furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL));
|
||||
}
|
||||
|
||||
// Views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork);
|
||||
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->keyboard_layout);
|
||||
|
||||
if(app->usb_if_prev) {
|
||||
furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL));
|
||||
}
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ struct BadUsbScript {
|
||||
FuriString* file_path;
|
||||
uint32_t defdelay;
|
||||
uint16_t layout[128];
|
||||
uint32_t stringdelay;
|
||||
FuriThread* thread;
|
||||
uint8_t file_buf[FILE_BUFFER_LEN + 1];
|
||||
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_defdelay_1[] = {"DEFAULT_DELAY "};
|
||||
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_sysrq[] = {"SYSRQ "};
|
||||
|
||||
@ -211,14 +214,19 @@ static bool ducky_altstring(const char* param) {
|
||||
|
||||
static bool ducky_string(BadUsbScript* bad_usb, const char* param) {
|
||||
uint32_t i = 0;
|
||||
|
||||
while(param[i] != '\0') {
|
||||
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
|
||||
if(keycode != HID_KEYBOARD_NONE) {
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
if(bad_usb->stringdelay > 0) {
|
||||
furi_delay_ms(bad_usb->stringdelay);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
bad_usb->stringdelay = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -277,6 +285,20 @@ static int32_t
|
||||
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||
}
|
||||
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) {
|
||||
// STRING
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
BadUsbScript* bad_usb = context;
|
||||
|
||||
@ -520,11 +555,9 @@ static int32_t bad_usb_worker(void* context) {
|
||||
bad_usb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
uint32_t flags = bad_usb_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever);
|
||||
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtConnect) {
|
||||
@ -535,11 +568,9 @@ static int32_t bad_usb_worker(void* context) {
|
||||
bad_usb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadUsbStateIdle) { // State: ready to start
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
uint32_t flags = bad_usb_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriWaitForever);
|
||||
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} 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->st.line_cur = 0;
|
||||
bad_usb->defdelay = 0;
|
||||
bad_usb->stringdelay = 0;
|
||||
bad_usb->repeat_cnt = 0;
|
||||
bad_usb->file_end = false;
|
||||
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;
|
||||
|
||||
} else if(worker_state == BadUsbStateWillRun) { // State: start on connection
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
uint32_t flags = bad_usb_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever);
|
||||
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} 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->st.line_cur = 0;
|
||||
bad_usb->defdelay = 0;
|
||||
bad_usb->stringdelay = 0;
|
||||
bad_usb->repeat_cnt = 0;
|
||||
bad_usb->file_end = false;
|
||||
storage_file_seek(script_file, 0, true);
|
||||
// extra time for PC to recognize Flipper as keyboard
|
||||
furi_thread_flags_wait(0, FuriFlagWaitAny, 1500);
|
||||
worker_state = BadUsbStateRunning;
|
||||
flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtToggle,
|
||||
FuriFlagWaitAny | FuriFlagNoClear,
|
||||
1500);
|
||||
if(flags == (unsigned)FuriFlagErrorTimeout) {
|
||||
// If nothing happened - start script execution
|
||||
worker_state = BadUsbStateRunning;
|
||||
} else if(flags & WorkerEvtToggle) {
|
||||
worker_state = BadUsbStateIdle;
|
||||
furi_thread_flags_clear(WorkerEvtToggle);
|
||||
}
|
||||
} else if(flags & WorkerEvtToggle) { // Cancel scheduled execution
|
||||
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);
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur);
|
||||
|
||||
delay_val -= delay_cur;
|
||||
if(!(flags & FuriFlagError)) {
|
||||
if(flags & WorkerEvtEnd) {
|
||||
@ -629,9 +670,9 @@ static int32_t bad_usb_worker(void* context) {
|
||||
} else if(
|
||||
(worker_state == BadUsbStateFileError) ||
|
||||
(worker_state == BadUsbStateScriptError)) { // State: error
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd, FuriFlagWaitAny, FuriWaitForever); // Waiting for exit command
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
uint32_t flags =
|
||||
bad_usb_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command
|
||||
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -22,7 +22,6 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) {
|
||||
void bad_usb_scene_file_select_on_enter(void* context) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
|
||||
furi_hal_usb_disable();
|
||||
if(bad_usb->bad_usb_script) {
|
||||
bad_usb_script_close(bad_usb->bad_usb_script);
|
||||
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);
|
||||
} else {
|
||||
furi_hal_usb_enable();
|
||||
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.event == InputKeyLeft) {
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
|
||||
if(bad_usb_is_idle_state(app->bad_usb_view)) {
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == InputKeyOk) {
|
||||
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) ||
|
||||
(model->state.state == BadUsbStateNotConnected)) {
|
||||
elements_button_center(canvas, "Run");
|
||||
elements_button_left(canvas, "Config");
|
||||
} else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) {
|
||||
elements_button_center(canvas, "Stop");
|
||||
} else if(model->state.state == BadUsbStateWillRun) {
|
||||
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) {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
|
||||
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); },
|
||||
true);
|
||||
}
|
||||
|
||||
void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
|
||||
furi_assert(st);
|
||||
with_view_model(
|
||||
@ -214,3 +211,19 @@ void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
|
||||
},
|
||||
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_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) {
|
||||
cmd_exit |= cli_cmd_interrupt_received(cli);
|
||||
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);
|
||||
for(size_t i = 0; i < dev_data.uid_len; 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_app, MfDesfireApp)
|
||||
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_emulate, MfClassicEmulate)
|
||||
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;
|
||||
NfcProtocol protocol = dev_data->protocol;
|
||||
uint8_t text_scroll_height = 0;
|
||||
if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl)) {
|
||||
if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl) ||
|
||||
(protocol == NfcDeviceProtocolMifareClassic)) {
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc);
|
||||
text_scroll_height = 52;
|
||||
@ -136,6 +137,9 @@ bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(protocol == NfcDeviceProtocolMifareUl) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData);
|
||||
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);
|
||||
}
|
||||
|
||||
FURI_LOG_I("nfc", "application_info_present: %d", application_info_present);
|
||||
|
||||
if(application_info_present) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo);
|
||||
} else {
|
||||
|
||||
@ -10,7 +10,7 @@ App(
|
||||
stack_size=4 * 1024,
|
||||
order=20,
|
||||
fap_icon="dap_link.png",
|
||||
fap_category="Tools",
|
||||
fap_category="GPIO",
|
||||
fap_private_libs=[
|
||||
Lib(
|
||||
name="free-dap",
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
App(
|
||||
appid="hid_usb",
|
||||
name="USB Remote",
|
||||
name="Remote",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="hid_usb_app",
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Tools",
|
||||
fap_category="USB",
|
||||
fap_icon="hid_usb_10px.png",
|
||||
fap_icon_assets="assets",
|
||||
fap_icon_assets_symbol="hid",
|
||||
@ -13,11 +13,11 @@ App(
|
||||
|
||||
App(
|
||||
appid="hid_ble",
|
||||
name="Bluetooth Remote",
|
||||
name="Remote",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="hid_ble_app",
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Tools",
|
||||
fap_category="Bluetooth",
|
||||
fap_icon="hid_ble_10px.png",
|
||||
fap_icon_assets="assets",
|
||||
fap_icon_assets_symbol="hid",
|
||||
|
||||
@ -12,7 +12,7 @@ App(
|
||||
stack_size=2 * 1024,
|
||||
order=20,
|
||||
fap_icon="icons/music_10px.png",
|
||||
fap_category="Misc",
|
||||
fap_category="Media",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ App(
|
||||
stack_size=4 * 1024,
|
||||
order=30,
|
||||
fap_icon="../../../assets/icons/Archive/125_10px.png",
|
||||
fap_category="Tools",
|
||||
fap_category="NFC",
|
||||
fap_private_libs=[
|
||||
Lib(
|
||||
name="magic",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
App(
|
||||
appid="picopass",
|
||||
name="PicoPass Reader",
|
||||
name="PicoPass",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
targets=["f7"],
|
||||
entry_point="picopass_app",
|
||||
@ -11,7 +11,7 @@ App(
|
||||
stack_size=4 * 1024,
|
||||
order=30,
|
||||
fap_icon="125_10px.png",
|
||||
fap_category="Tools",
|
||||
fap_category="NFC",
|
||||
fap_libs=["mbedtls"],
|
||||
fap_private_libs=[
|
||||
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) {
|
||||
UNUSED(p);
|
||||
Picopass* picopass = picopass_alloc();
|
||||
|
||||
@ -368,7 +368,7 @@ ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* r
|
||||
|
||||
record->CardNumber = (bot >> 1) & 0xFFFF;
|
||||
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;
|
||||
} else {
|
||||
record->CardNumber = 0;
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
#define PICOPASS_KD_BLOCK_INDEX 3
|
||||
#define PICOPASS_KC_BLOCK_INDEX 4
|
||||
#define PICOPASS_AIA_BLOCK_INDEX 5
|
||||
#define PICOPASS_PACS_CFG_BLOCK_INDEX 6
|
||||
|
||||
#define PICOPASS_APP_FOLDER ANY_PATH("picopass")
|
||||
#define PICOPASS_APP_EXTENSION ".picopass"
|
||||
|
||||
@ -81,3 +81,15 @@ void picopass_blink_start(Picopass* picopass);
|
||||
void picopass_blink_stop(Picopass* picopass);
|
||||
|
||||
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"
|
||||
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
|
||||
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(
|
||||
uint8_t* csn,
|
||||
PicopassPacs* pacs,
|
||||
@ -264,14 +287,23 @@ static ReturnCode picopass_auth_dict(
|
||||
ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) {
|
||||
ReturnCode err;
|
||||
|
||||
FURI_LOG_E(TAG, "Trying standard legacy key");
|
||||
FURI_LOG_I(TAG, "Trying standard legacy key");
|
||||
err = picopass_auth_standard(
|
||||
AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data);
|
||||
if(err == ERR_NONE) {
|
||||
memcpy(pacs->key, picopass_iclass_key, PICOPASS_BLOCK_LEN);
|
||||
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(
|
||||
AA1[PICOPASS_CSN_BLOCK_INDEX].data,
|
||||
pacs,
|
||||
@ -281,7 +313,7 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) {
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
FURI_LOG_E(TAG, "Starting in-built dictionary attack");
|
||||
FURI_LOG_I(TAG, "Starting system dictionary attack");
|
||||
err = picopass_auth_dict(
|
||||
AA1[PICOPASS_CSN_BLOCK_INDEX].data,
|
||||
pacs,
|
||||
@ -406,6 +438,84 @@ ReturnCode picopass_write_card(PicopassBlock* AA1) {
|
||||
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) {
|
||||
PicopassWorker* picopass_worker = context;
|
||||
|
||||
@ -414,6 +524,8 @@ int32_t picopass_worker_task(void* context) {
|
||||
picopass_worker_detect(picopass_worker);
|
||||
} else if(picopass_worker->state == PicopassWorkerStateWrite) {
|
||||
picopass_worker_write(picopass_worker);
|
||||
} else if(picopass_worker->state == PicopassWorkerStateWriteStandardKey) {
|
||||
picopass_worker_write_standard_key(picopass_worker);
|
||||
}
|
||||
picopass_worker_disable_field(ERR_NONE);
|
||||
|
||||
@ -448,7 +560,7 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) {
|
||||
}
|
||||
|
||||
// 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);
|
||||
if(pacs->se_enabled) {
|
||||
FURI_LOG_D(TAG, "SE enabled");
|
||||
@ -520,3 +632,46 @@ void picopass_worker_write(PicopassWorker* picopass_worker) {
|
||||
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
|
||||
PicopassWorkerStateDetect,
|
||||
PicopassWorkerStateWrite,
|
||||
PicopassWorkerStateWriteStandardKey,
|
||||
// Transition
|
||||
PicopassWorkerStateStop,
|
||||
} PicopassWorkerState;
|
||||
|
||||
@ -31,3 +31,4 @@ int32_t picopass_worker_task(void* context);
|
||||
|
||||
void picopass_worker_detect(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, write_card, WriteCard)
|
||||
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 <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) {
|
||||
UNUSED(event);
|
||||
Picopass* picopass = context;
|
||||
@ -34,7 +36,14 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == PicopassCustomEventWorkerExit) {
|
||||
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
|
||||
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);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ void picopass_scene_read_card_success_widget_callback(
|
||||
|
||||
void picopass_scene_read_card_success_on_enter(void* context) {
|
||||
Picopass* picopass = context;
|
||||
|
||||
FuriString* csn_str = furi_string_alloc_set("CSN:");
|
||||
FuriString* credential_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;
|
||||
Widget* widget = picopass->widget;
|
||||
|
||||
uint8_t csn[PICOPASS_BLOCK_LEN];
|
||||
memcpy(csn, &AA1->data[PICOPASS_CSN_BLOCK_INDEX], PICOPASS_BLOCK_LEN);
|
||||
uint8_t csn[PICOPASS_BLOCK_LEN] = {0};
|
||||
memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN);
|
||||
for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
furi_string_cat_printf(csn_str, "%02X ", csn[i]);
|
||||
}
|
||||
|
||||
// Neither of these are valid. Indicates the block was all 0x00 or all 0xff
|
||||
if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) {
|
||||
bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN);
|
||||
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");
|
||||
|
||||
if(pacs->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(
|
||||
widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Retry",
|
||||
picopass_scene_read_card_success_widget_callback,
|
||||
picopass);
|
||||
|
||||
if(pacs->se_enabled) {
|
||||
furi_string_cat_printf(credential_str, "SE enabled");
|
||||
}
|
||||
} else {
|
||||
size_t bytesLength = 1 + pacs->record.bitLength / 8;
|
||||
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,
|
||||
GuiButtonTypeRight,
|
||||
@ -97,6 +95,13 @@ void picopass_scene_read_card_success_on_enter(void* context) {
|
||||
picopass);
|
||||
}
|
||||
|
||||
widget_add_button_element(
|
||||
widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Retry",
|
||||
picopass_scene_read_card_success_widget_callback,
|
||||
picopass);
|
||||
|
||||
widget_add_string_element(
|
||||
widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str));
|
||||
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) {
|
||||
Picopass* picopass = context;
|
||||
Widget* widget = picopass->widget;
|
||||
FuriString* str = furi_string_alloc_set("Write Success!");
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
order=50,
|
||||
fap_icon="signal_gen_10px.png",
|
||||
fap_category="Tools",
|
||||
fap_category="GPIO",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
|
||||
@ -7,7 +7,7 @@ App(
|
||||
stack_size=1 * 2048,
|
||||
order=30,
|
||||
fap_icon="images/Dip8_10px.png",
|
||||
fap_category="Tools",
|
||||
fap_category="GPIO",
|
||||
fap_icon_assets="images",
|
||||
fap_private_libs=[
|
||||
Lib(
|
||||
|
||||
@ -9,6 +9,6 @@ App(
|
||||
stack_size=4 * 1024,
|
||||
order=50,
|
||||
fap_icon="weather_station_10px.png",
|
||||
fap_category="Tools",
|
||||
fap_category="Sub-GHz",
|
||||
fap_icon_assets="images",
|
||||
)
|
||||
|
||||
@ -195,6 +195,10 @@ bool weather_station_scene_receiver_on_event(void* context, SceneManagerEvent ev
|
||||
ws_hopper_update(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) {
|
||||
notification_message(app->notifications, &sequence_blink_cyan_10);
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#define MENU_ITEMS 4u
|
||||
#define UNLOCK_CNT 3
|
||||
|
||||
#define SUBGHZ_RAW_TRESHOLD_MIN -90.0f
|
||||
typedef struct {
|
||||
FuriString* item_str;
|
||||
uint8_t type;
|
||||
@ -59,8 +60,24 @@ typedef struct {
|
||||
uint16_t list_offset;
|
||||
uint16_t history_item;
|
||||
WSReceiverBarShow bar_show;
|
||||
uint8_t u_rssi;
|
||||
} 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) {
|
||||
furi_assert(ws_receiver);
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
elements_button_left(canvas, "Config");
|
||||
canvas_draw_line(canvas, 46, 51, 125, 51);
|
||||
|
||||
bool scrollbar = model->history_item > 4;
|
||||
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_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 63, 46, "Scanning...");
|
||||
canvas_draw_line(canvas, 46, 51, 125, 51);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
}
|
||||
|
||||
// Draw RSSI
|
||||
ws_view_rssi_draw(canvas, model);
|
||||
|
||||
switch(model->bar_show) {
|
||||
case WSReceiverBarShowLock:
|
||||
canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8);
|
||||
|
||||
@ -8,6 +8,8 @@ typedef struct WSReceiver WSReceiver;
|
||||
|
||||
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_callback(
|
||||
|
||||
@ -373,7 +373,7 @@ int32_t bt_srv(void* p) {
|
||||
Bt* bt = bt_alloc();
|
||||
|
||||
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);
|
||||
furi_record_create(RECORD_BT, bt);
|
||||
return 0;
|
||||
|
||||
@ -461,7 +461,7 @@ int32_t cli_srv(void* p) {
|
||||
if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) {
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
} 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) {
|
||||
|
||||
@ -12,26 +12,48 @@
|
||||
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
|
||||
#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) {
|
||||
UNUSED(context);
|
||||
void cli_command_info_callback(const char* key, const char* value, bool last, void* context) {
|
||||
UNUSED(last);
|
||||
UNUSED(context);
|
||||
printf("%-30s: %s\r\n", key, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* Device Info Command
|
||||
/** Info Command
|
||||
*
|
||||
* 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(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) {
|
||||
UNUSED(args);
|
||||
UNUSED(context);
|
||||
printf("Commands we have:");
|
||||
printf("Commands available:");
|
||||
|
||||
// Command count
|
||||
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) {
|
||||
cli_nl();
|
||||
printf("Also I have no clue what '");
|
||||
printf("`");
|
||||
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) {
|
||||
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_device_info, NULL);
|
||||
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_device_info, NULL);
|
||||
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
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, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
#include "helpers/pin_lock.h"
|
||||
#include "helpers/slideshow_filename.h"
|
||||
|
||||
#define TAG "Desktop"
|
||||
|
||||
static void desktop_auto_lock_arm(Desktop*);
|
||||
static void desktop_auto_lock_inhibit(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) {
|
||||
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();
|
||||
|
||||
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) {
|
||||
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();
|
||||
furi_record_create(RECORD_DOLPHIN, dolphin);
|
||||
|
||||
|
||||
@ -26,24 +26,6 @@ void power_cli_reboot2dfu(Cli* cli, FuriString* args) {
|
||||
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) {
|
||||
UNUSED(cli);
|
||||
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("\treboot\t - reboot\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");
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
power_cli_5v(cli, args);
|
||||
break;
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <furi_hal.h>
|
||||
|
||||
#define POWER_OFF_TIMEOUT 90
|
||||
#define TAG "Power"
|
||||
|
||||
void power_draw_battery_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
@ -12,8 +13,8 @@ void power_draw_battery_callback(Canvas* canvas, void* context) {
|
||||
|
||||
if(power->info.gauge_is_ok) {
|
||||
canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4);
|
||||
if(power->info.voltage_battery_charging < 4.2) {
|
||||
// Battery charging voltage is modified, indicate with cross pattern
|
||||
if(power->info.voltage_battery_charge_limit < 4.2) {
|
||||
// Battery charge voltage limit is modified, indicate with cross pattern
|
||||
canvas_invert_color(canvas);
|
||||
uint8_t battery_bar_width = (power->info.charge + 4) / 5;
|
||||
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.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger);
|
||||
info.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge);
|
||||
info.voltage_battery_charging = furi_hal_power_get_battery_charging_voltage();
|
||||
info.voltage_battery_charge_limit = furi_hal_power_get_battery_charge_voltage_limit();
|
||||
info.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger);
|
||||
info.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge);
|
||||
info.voltage_vbus = furi_hal_power_get_usb_voltage();
|
||||
@ -217,6 +218,12 @@ static void power_check_battery_level_change(Power* power) {
|
||||
|
||||
int32_t power_srv(void* 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_update_info(power);
|
||||
furi_record_create(RECORD_POWER, power);
|
||||
|
||||
@ -41,7 +41,7 @@ typedef struct {
|
||||
float current_charger;
|
||||
float current_gauge;
|
||||
|
||||
float voltage_battery_charging;
|
||||
float voltage_battery_charge_limit;
|
||||
float voltage_charger;
|
||||
float voltage_gauge;
|
||||
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_current = app->info.current_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,
|
||||
.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");
|
||||
} else if(drain_current != 0) {
|
||||
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
|
||||
snprintf(emote, sizeof(emote), "Charged!");
|
||||
snprintf(header, sizeof(header), "Limited to");
|
||||
@ -77,8 +77,8 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) {
|
||||
value,
|
||||
sizeof(value),
|
||||
"%lu.%luV",
|
||||
(uint32_t)(data->charging_voltage),
|
||||
(uint32_t)(data->charging_voltage * 10) % 10);
|
||||
(uint32_t)(data->charge_voltage_limit),
|
||||
(uint32_t)(data->charge_voltage_limit * 10) % 10);
|
||||
} else {
|
||||
snprintf(header, sizeof(header), "Charged!");
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ typedef struct {
|
||||
float gauge_voltage;
|
||||
float gauge_current;
|
||||
float gauge_temperature;
|
||||
float charging_voltage;
|
||||
float charge_voltage_limit;
|
||||
uint8_t charge;
|
||||
uint8_t health;
|
||||
} BatteryInfoModel;
|
||||
|
||||
@ -103,6 +103,9 @@ static void storage_settings_scene_benchmark(StorageSettings* app) {
|
||||
break;
|
||||
|
||||
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, 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(
|
||||
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));
|
||||
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);
|
||||
} else if(entry_ptr->type == ResourceManifestEntryTypeDirectory) {
|
||||
n_dir_entries++;
|
||||
@ -116,7 +125,6 @@ static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_
|
||||
n_dir_entries);
|
||||
|
||||
FuriString* folder_path = furi_string_alloc();
|
||||
File* folder_file = storage_file_alloc(update_task->storage);
|
||||
|
||||
do {
|
||||
path_concat(
|
||||
@ -125,24 +133,17 @@ static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_
|
||||
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))) {
|
||||
FURI_LOG_W(
|
||||
FS_Error result = storage_common_remove(
|
||||
update_task->storage, furi_string_get_cstr(folder_path));
|
||||
if(result != FSE_OK && result != FSE_EXIST) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"%s can't be opened, skipping",
|
||||
furi_string_get_cstr(folder_path));
|
||||
break;
|
||||
"%s remove failed, cause %s",
|
||||
furi_string_get_cstr(folder_path),
|
||||
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);
|
||||
|
||||
storage_file_free(folder_file);
|
||||
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
|
||||
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
|
||||
Min butthurt: 0
|
||||
Max butthurt: 8
|
||||
|
||||
@ -285,3 +285,46 @@ type: parsed
|
||||
protocol: NECext
|
||||
address: 10 E7 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.
|
||||
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.
|
||||
|
||||
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)*
|
||||
|
||||
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.
|
||||
|
||||
- `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.
|
||||
- `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in the environment:
|
||||
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 \*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
|
||||
|
||||
@ -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.
|
||||
|
||||
## 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
|
||||
|
||||
`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 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
|
||||
|
||||
| Command | Parameters | Notes |
|
||||
|
||||
11
fbt
@ -6,16 +6,21 @@ set -eu;
|
||||
|
||||
# private variables
|
||||
SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd -P)";
|
||||
SCONS_DEFAULT_FLAGS="-Q --warn=target-not-built";
|
||||
SCONS_EP="python3 -m SCons"
|
||||
SCONS_DEFAULT_FLAGS="--warn=target-not-built";
|
||||
SCONS_EP="python3 -m SCons";
|
||||
|
||||
# public variables
|
||||
FBT_NOENV="${FBT_NOENV:-""}";
|
||||
FBT_NO_SYNC="${FBT_NO_SYNC:-""}";
|
||||
FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}";
|
||||
FBT_VERBOSE="${FBT_VERBOSE:-""}";
|
||||
|
||||
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
|
||||
|
||||
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% %*
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,14.0,,
|
||||
Version,+,15.0,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/cli/cli.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_gauge_is_ok,_Bool,
|
||||
Function,+,furi_hal_power_get_bat_health_pct,uint8_t,
|
||||
Function,+,furi_hal_power_get_battery_charging_voltage,float,
|
||||
Function,+,furi_hal_power_get_battery_charge_voltage_limit,float,
|
||||
Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC
|
||||
Function,+,furi_hal_power_get_battery_design_capacity,uint32_t,
|
||||
Function,+,furi_hal_power_get_battery_full_capacity,uint32_t,
|
||||
@ -998,7 +998,7 @@ Function,+,furi_hal_power_is_charging_done,_Bool,
|
||||
Function,+,furi_hal_power_is_otg_enabled,_Bool,
|
||||
Function,+,furi_hal_power_off,void,
|
||||
Function,+,furi_hal_power_reset,void,
|
||||
Function,+,furi_hal_power_set_battery_charging_voltage,void,float
|
||||
Function,+,furi_hal_power_set_battery_charge_voltage_limit,void,float
|
||||
Function,+,furi_hal_power_shutdown,void,
|
||||
Function,+,furi_hal_power_sleep,void,
|
||||
Function,+,furi_hal_power_sleep_available,_Bool,
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,14.0,,
|
||||
Version,+,15.0,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/cli/cli.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_read_only,void,"FuriHalMpuRegion, uint32_t, FuriHalMPURegionSize"
|
||||
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_emulate_nfca,_Bool,"uint8_t*, uint8_t, uint8_t*, uint8_t, FuriHalNfcEmulateCallback, void*, uint32_t"
|
||||
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_gauge_is_ok,_Bool,
|
||||
Function,+,furi_hal_power_get_bat_health_pct,uint8_t,
|
||||
Function,+,furi_hal_power_get_battery_charging_voltage,float,
|
||||
Function,+,furi_hal_power_get_battery_charge_voltage_limit,float,
|
||||
Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC
|
||||
Function,+,furi_hal_power_get_battery_design_capacity,uint32_t,
|
||||
Function,+,furi_hal_power_get_battery_full_capacity,uint32_t,
|
||||
@ -1229,7 +1230,7 @@ Function,+,furi_hal_power_is_charging_done,_Bool,
|
||||
Function,+,furi_hal_power_is_otg_enabled,_Bool,
|
||||
Function,+,furi_hal_power_off,void,
|
||||
Function,+,furi_hal_power_reset,void,
|
||||
Function,+,furi_hal_power_set_battery_charging_voltage,void,float
|
||||
Function,+,furi_hal_power_set_battery_charge_voltage_limit,void,float
|
||||
Function,+,furi_hal_power_shutdown,void,
|
||||
Function,+,furi_hal_power_sleep,void,
|
||||
Function,+,furi_hal_power_sleep_available,_Bool,
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
#define SD_DUMMY_BYTE 0xFF
|
||||
#define SD_ANSWER_RETRY_COUNT 8
|
||||
#define SD_IDLE_RETRY_COUNT 100
|
||||
#define SD_BLOCK_SIZE 512
|
||||
|
||||
#define FLAG_SET(x, y) (((x) & (y)) == (y))
|
||||
|
||||
@ -598,23 +597,6 @@ static SdSpiStatus sd_spi_get_cid(SD_CID* Cid) {
|
||||
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
|
||||
sd_spi_cmd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) {
|
||||
uint32_t block_address = address;
|
||||
@ -833,30 +815,12 @@ SdSpiStatus sd_get_card_info(SD_CardInfo* card_info) {
|
||||
|
||||
SdSpiStatus
|
||||
sd_read_blocks(uint32_t* data, uint32_t address, uint32_t blocks, uint32_t timeout_ms) {
|
||||
SdSpiStatus status = SdSpiStatusError;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
SdSpiStatus status = sd_spi_cmd_read_blocks(data, address, blocks, timeout_ms);
|
||||
return status;
|
||||
}
|
||||
|
||||
SdSpiStatus
|
||||
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);
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#define __IO volatile
|
||||
|
||||
#define SD_TIMEOUT_MS (1000)
|
||||
#define SD_BLOCK_SIZE 512
|
||||
|
||||
typedef enum {
|
||||
SdSpiStatusOK,
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
/* Includes ------------------------------------------------------------------*/
|
||||
#include "user_diskio.h"
|
||||
#include <furi_hal.h>
|
||||
#include "sector_cache.h"
|
||||
/* Private typedef -----------------------------------------------------------*/
|
||||
/* Private define ------------------------------------------------------------*/
|
||||
|
||||
@ -79,6 +80,26 @@ Diskio_drvTypeDef USER_Driver = {
|
||||
};
|
||||
|
||||
/* 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
|
||||
@ -125,6 +146,14 @@ DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) {
|
||||
UNUSED(pdrv);
|
||||
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_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_spi_release(&furi_hal_spi_bus_handle_sd_fast);
|
||||
|
||||
if(single_sector && res == RES_OK) {
|
||||
sd_cache_put(sector, (uint32_t*)buff);
|
||||
}
|
||||
|
||||
return res;
|
||||
/* USER CODE END READ */
|
||||
}
|
||||
@ -164,6 +197,8 @@ DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) {
|
||||
UNUSED(pdrv);
|
||||
DRESULT res = RES_ERROR;
|
||||
|
||||
sd_cache_invalidate_range(sector, sector + count);
|
||||
|
||||
furi_hal_spi_acquire(&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;
|
||||
while(sd_get_card_state() != SdSpiStatusOK) {
|
||||
if(furi_hal_cortex_timer_is_expired(timer)) {
|
||||
sd_cache_invalidate_all();
|
||||
|
||||
res = RES_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -24,13 +24,29 @@ FuriEventFlag* event = NULL;
|
||||
#define FURI_HAL_NFC_UID_INCOMPLETE (0x04)
|
||||
|
||||
void furi_hal_nfc_init() {
|
||||
furi_assert(!event);
|
||||
event = furi_event_flag_alloc();
|
||||
|
||||
ReturnCode ret = rfalNfcInitialize();
|
||||
if(ret == ERR_NONE) {
|
||||
furi_hal_nfc_start_sleep();
|
||||
event = furi_event_flag_alloc();
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
} 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();
|
||||
|
||||
/** Deinit nfc
|
||||
*/
|
||||
void furi_hal_nfc_deinit();
|
||||
|
||||
/** Check if nfc worker is busy
|
||||
*
|
||||
* @return true if busy
|
||||
|
||||
@ -341,14 +341,14 @@ bool furi_hal_power_is_otg_enabled() {
|
||||
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);
|
||||
float ret = (float)bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power) / 1000.0f;
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void furi_hal_power_set_battery_charging_voltage(float voltage) {
|
||||
void furi_hal_power_set_battery_charge_voltage_limit(float voltage) {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
// Adding 0.0005 is necessary because 4.016f is 4.015999794000, which gets truncated
|
||||
bq25896_set_vreg_voltage(&furi_hal_i2c_handle_power, (uint16_t)(voltage * 1000.0f + 0.0005f));
|
||||
@ -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", "minor", "1");
|
||||
} 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");
|
||||
}
|
||||
|
||||
@ -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);
|
||||
uint16_t charge_voltage = (uint16_t)(furi_hal_power_get_battery_charging_voltage() * 1000.f);
|
||||
property_value_out(&property_context, "%u", 2, "charge", "voltage", charge_voltage);
|
||||
uint16_t charge_voltage_limit =
|
||||
(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)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f);
|
||||
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();
|
||||
|
||||
/** Get battery charging voltage in V
|
||||
/** Get battery charge voltage limit 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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
|
||||
@ -96,9 +96,9 @@ static void furi_thread_body(void* context) {
|
||||
furi_assert(thread->state == FuriThreadStateRunning);
|
||||
|
||||
if(thread->is_service) {
|
||||
FURI_LOG_E(
|
||||
FURI_LOG_W(
|
||||
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>");
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal_version.h>
|
||||
#include <furi_hal_memory.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
|
||||
#define TAG "Flipper"
|
||||
|
||||
@ -29,10 +30,10 @@ static void flipper_print_version(const char* target, const Version* version) {
|
||||
void flipper_init() {
|
||||
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++) {
|
||||
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(
|
||||
FLIPPER_SERVICES[i].name,
|
||||
@ -44,7 +45,7 @@ void flipper_init() {
|
||||
furi_thread_start(thread);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "services startup complete");
|
||||
FURI_LOG_I(TAG, "Startup complete");
|
||||
}
|
||||
|
||||
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) {
|
||||
if(vreg_voltage < 3840) {
|
||||
// Minimum value is 3840 mV
|
||||
bq25896_regs.r06.VREG = 0;
|
||||
} else {
|
||||
// Find the nearest voltage value (subtract offset, divide into sections)
|
||||
// Values are truncated downward as needed (e.g. 4200mV -> 4192 mV)
|
||||
bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16);
|
||||
// Minimum valid value is 3840 mV
|
||||
vreg_voltage = 3840;
|
||||
} else if(vreg_voltage > 4208) {
|
||||
// Maximum safe value is 4208 mV
|
||||
vreg_voltage = 4208;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
// Find the nearest voltage value (subtract offset, divide into sections)
|
||||
// Values are truncated downward as needed (e.g. 4200mV -> 4192 mV)
|
||||
bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16);
|
||||
|
||||
// Apply changes
|
||||
furi_hal_i2c_write_reg_8(
|
||||
|
||||
@ -36,10 +36,10 @@ void bq25896_disable_otg(FuriHalI2cBusHandle* handle);
|
||||
/** Is otg enabled */
|
||||
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);
|
||||
|
||||
/** Set VREG (charging) voltage in mV
|
||||
/** Set VREG (charging limit) voltage in mV
|
||||
*
|
||||
* Valid range: 3840mV - 4208mV, in steps of 16mV
|
||||
*/
|
||||
|
||||
@ -56,6 +56,8 @@ void nfc_worker_start(
|
||||
while(furi_hal_nfc_is_busy()) {
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
furi_hal_nfc_deinit();
|
||||
furi_hal_nfc_init();
|
||||
|
||||
nfc_worker->callback = callback;
|
||||
nfc_worker->context = context;
|
||||
|
||||
@ -22,7 +22,6 @@ def _convert_image(source_filename: str):
|
||||
|
||||
|
||||
class DolphinBubbleAnimation:
|
||||
|
||||
FILE_TYPE = "Flipper Animation"
|
||||
FILE_VERSION = 1
|
||||
|
||||
@ -243,7 +242,6 @@ class DolphinBubbleAnimation:
|
||||
|
||||
|
||||
class DolphinManifest:
|
||||
|
||||
FILE_TYPE = "Flipper Animation Manifest"
|
||||
FILE_VERSION = 1
|
||||
|
||||
|
||||
@ -105,7 +105,7 @@ def file2image(file):
|
||||
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
|
||||
if len(data_enc) < len(data_bin) + 1:
|
||||
if len(data_enc) + 2 < len(data_bin) + 1:
|
||||
data = b"\x01\x00" + data_enc
|
||||
else:
|
||||
data = b"\x00" + data_bin
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import serial.tools.list_ports as list_ports
|
||||
|
||||
|
||||
# Returns a valid port or None, if it cannot be found
|
||||
def resolve_port(logger, portname: str = "auto"):
|
||||
if portname != "auto":
|
||||
|
||||
@ -173,12 +173,14 @@ class Templite:
|
||||
"""Renders the template according to the given namespace."""
|
||||
stack = []
|
||||
namespace["__file__"] = self.file
|
||||
|
||||
# add write method
|
||||
def write(*args):
|
||||
for value in args:
|
||||
stack.append(str(value))
|
||||
|
||||
namespace["write"] = write
|
||||
|
||||
# add include method
|
||||
def include(file):
|
||||
if not os.path.isabs(file):
|
||||
|
||||
@ -13,7 +13,7 @@ if not ["%FBT_NOENV%"] == [""] (
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
set "FLIPPER_TOOLCHAIN_VERSION=19"
|
||||
set "FLIPPER_TOOLCHAIN_VERSION=21"
|
||||
|
||||
if ["%FBT_TOOLCHAIN_ROOT%"] == [""] (
|
||||
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%"
|
||||
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%"
|
||||
set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_VERSION_FILE%"
|
||||
)
|
||||
|
||||
if defined FBT_VERBOSE (
|
||||
echo FBT: using toolchain version %REAL_TOOLCHAIN_VERSION%
|
||||
)
|
||||
|
||||
set "HOME=%USERPROFILE%"
|
||||
|
||||
@ -5,15 +5,16 @@
|
||||
# public variables
|
||||
DEFAULT_SCRIPT_PATH="$(pwd -P)";
|
||||
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_VERBOSE="${FBT_VERBOSE:-""}";
|
||||
|
||||
fbtenv_show_usage()
|
||||
{
|
||||
echo "Running this script manually is wrong, please source it";
|
||||
echo "Example:";
|
||||
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:";
|
||||
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\/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\/openssl\/bin://g")";
|
||||
if [ -n "${PS1:-""}" ]; then
|
||||
PS1="$(echo "$PS1" | sed 's/\[fbt\]//g')";
|
||||
elif [ -n "${PROMPT:-""}" ]; then
|
||||
PROMPT="$(echo "$PROMPT" | sed 's/\[fbt\]//g')";
|
||||
fi
|
||||
|
||||
PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE";
|
||||
PYTHONPATH="$SAVED_PYTHONPATH";
|
||||
PYTHONHOME="$SAVED_PYTHONHOME";
|
||||
if [ -n "$SAVED_SSL_CERT_FILE" ]; then
|
||||
export SSL_CERT_FILE="$SAVED_SSL_CERT_FILE";
|
||||
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_PYTHONPATH;
|
||||
unset SAVED_PYTHONHOME;
|
||||
@ -121,7 +132,7 @@ fbtenv_get_kernel_type()
|
||||
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";
|
||||
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;
|
||||
else
|
||||
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
|
||||
fbtenv_download_toolchain || return 1;
|
||||
elif [ "$(cat "$TOOLCHAIN_ARCH_DIR/VERSION")" -ne "$FBT_TOOLCHAIN_VERSION" ]; then
|
||||
echo "FBT: starting toolchain upgrade process.."
|
||||
fbtenv_download_toolchain || return 1;
|
||||
fi
|
||||
return 0;
|
||||
@ -269,6 +281,13 @@ fbtenv_download_toolchain()
|
||||
return 0;
|
||||
}
|
||||
|
||||
fbtenv_print_version()
|
||||
{
|
||||
if [ -n "$FBT_VERBOSE" ]; then
|
||||
echo "FBT: using toolchain version $(cat "$TOOLCHAIN_ARCH_DIR/VERSION")";
|
||||
fi
|
||||
}
|
||||
|
||||
fbtenv_main()
|
||||
{
|
||||
fbtenv_check_sourced || return 1;
|
||||
@ -281,18 +300,25 @@ fbtenv_main()
|
||||
fbtenv_check_script_path || return 1;
|
||||
fbtenv_check_download_toolchain || return 1;
|
||||
fbtenv_set_shell_prompt;
|
||||
fbtenv_print_version;
|
||||
PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH";
|
||||
PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH";
|
||||
PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH";
|
||||
PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH";
|
||||
PATH="$TOOLCHAIN_ARCH_DIR/openssl/bin:$PATH";
|
||||
export PATH;
|
||||
|
||||
SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}";
|
||||
SAVED_PYTHONPATH="${PYTHONPATH:-""}";
|
||||
SAVED_PYTHONHOME="${PYTHONHOME:-""}";
|
||||
export SAVED_SSL_CERT_FILE="${SSL_CERT_FILE:-""}";
|
||||
export SAVED_REQUESTS_CA_BUNDLE="${REQUESTS_CA_BUNDLE:-""}";
|
||||
export SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}";
|
||||
export SAVED_PYTHONPATH="${PYTHONPATH:-""}";
|
||||
export SAVED_PYTHONHOME="${PYTHONHOME:-""}";
|
||||
|
||||
PYTHONNOUSERSITE=1;
|
||||
PYTHONPATH=;
|
||||
PYTHONHOME=;
|
||||
export SSL_CERT_FILE="$TOOLCHAIN_ARCH_DIR/python/lib/python3.11/site-packages/certifi/cacert.pem";
|
||||
export REQUESTS_CA_BUNDLE="$SSL_CERT_FILE";
|
||||
export PYTHONNOUSERSITE=1;
|
||||
export PYTHONPATH=;
|
||||
export PYTHONHOME=;
|
||||
}
|
||||
|
||||
fbtenv_main "${1:-""}";
|
||||
|
||||
@ -199,7 +199,7 @@ class Main(App):
|
||||
|
||||
def disclaimer(self):
|
||||
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(
|
||||
"Please confirm that you REALLY want to do that with --I-understand-what-I-am-doing=yes"
|
||||
|
||||