V39 Release Candidate Changes (#91)

This commit is contained in:
Clara K
2023-01-17 05:36:57 +01:00
committed by GitHub
355 changed files with 8033 additions and 6365 deletions
+7
View File
@@ -148,5 +148,12 @@ $ ./fbt resources icons dolphin_ext
<img src="https://user-images.githubusercontent.com/55334727/212134625-21383102-02f3-453f-b1d7-8a9c65b27612.svg">
</p>
----
## SAST Tools
This helps us a lot, thanks for the free license for this project!
[PVS-Studio](https://pvs-studio.com/en/pvs-studio/?utm_source=github&utm_medium=organic&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code.
----
<p align="center"> "What we do for ourselves dies with us. What we do for others and the world remains and is immortal.” ― Albert Pike </p>
@@ -64,7 +64,13 @@ static void
archive_add_file_item(browser, is_folder, furi_string_get_cstr(item_path));
} else {
with_view_model(
browser->view, ArchiveBrowserViewModel * model, { files_array_sort(model->files); model->list_loading = false; }, true);
browser->view,
ArchiveBrowserViewModel * model,
{
files_array_sort(model->files);
model->list_loading = false;
},
true);
}
}
@@ -139,7 +145,7 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) {
archive_get_items(browser, furi_string_get_cstr(browser->path));
if(!archive_file_get_array_size(browser) && archive_is_home(browser)) {
archive_switch_tab(browser, TAB_RIGHT);
archive_switch_tab(browser, TAB_LEFT);
} else {
with_view_model(
browser->view,
@@ -206,7 +212,7 @@ void archive_file_array_rm_selected(ArchiveBrowserView* browser) {
false);
if((items_cnt == 0) && (archive_is_home(browser))) {
archive_switch_tab(browser, TAB_RIGHT);
archive_switch_tab(browser, TAB_LEFT);
}
archive_update_offset(browser);
@@ -3,7 +3,7 @@
#include "../archive_i.h"
#include <storage/storage.h>
#define TAB_RIGHT InputKeyRight // Default tab switch direction
#define TAB_LEFT InputKeyLeft // Default tab switch direction
#define TAB_DEFAULT ArchiveTabFavorites // Start tab
#define FILE_LIST_BUF_LEN 50
@@ -34,7 +34,7 @@ void bad_usb_scene_error_on_enter(void* context) {
app->widget, GuiButtonTypeLeft, "Back", bad_usb_scene_error_event_callback, app);
} else if(app->error == BadUsbAppErrorCloseRpc) {
widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nis active!");
widget_add_string_multiline_element(
@@ -45,8 +45,7 @@ void bad_usb_scene_error_on_enter(void* context) {
AlignTop,
FontSecondary,
"Disconnect from\nPC or phone to\nuse this function.");
}
else {
} else {
widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "I am not\na whore!");
widget_add_string_multiline_element(
@@ -50,10 +50,9 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) ||
(model->state.state == BadUsbStateNotConnected)) {
if (settings->sfw_mode) {
if(settings->sfw_mode) {
elements_button_center(canvas, "Start");
}
else {
} else {
elements_button_center(canvas, "Cum");
}
} else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) {
@@ -70,21 +69,19 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
if(model->state.state == BadUsbStateNotConnected) {
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
canvas_set_font(canvas, FontPrimary);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect to");
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "a device");
}
else {
} else {
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Plug me");
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "in, Daddy");
}
} else if(model->state.state == BadUsbStateWillRun) {
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
canvas_set_font(canvas, FontPrimary);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run");
}
else {
} else {
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will cum");
}
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect");
@@ -12,10 +12,9 @@ void ibutton_scene_delete_success_on_enter(void* context) {
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
}
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
@@ -17,10 +17,9 @@ void ibutton_scene_read_on_enter(void* context) {
popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom);
popup_set_text(popup, "Waiting\nfor key ...", 95, 30, AlignCenter, AlignTop);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59);
}
@@ -12,10 +12,9 @@ void ibutton_scene_save_success_on_enter(void* context) {
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
}
else {
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
@@ -13,10 +13,9 @@ void ibutton_scene_write_success_on_enter(void* context) {
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 12, &I_iButtonDolphinVerySuccess_108x52_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 12, &I_iButtonDolphinVerySuccess_108x52);
}
popup_set_text(popup, "Successfully written!", 40, 12, AlignLeft, AlignBottom);
@@ -7,10 +7,9 @@ void infrared_scene_edit_rename_done_on_enter(void* context) {
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
}
else {
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
@@ -7,16 +7,14 @@ void infrared_scene_learn_done_on_enter(void* context) {
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
if (infrared->app_state.is_learning_new_remote) {
if(infrared->app_state.is_learning_new_remote) {
popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop);
}
else {
} else {
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
}
}
else {
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
@@ -50,10 +50,9 @@ void infrared_scene_learn_success_on_enter(void* context) {
dialog_ex_set_left_button_text(dialog_ex, "Retry");
dialog_ex_set_right_button_text(dialog_ex, "Save");
dialog_ex_set_center_button_text(dialog_ex, "Send");
if (settings->sfw_mode) {
if(settings->sfw_mode) {
dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63_sfw);
}
else {
} else {
dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63);
}
dialog_ex_set_result_callback(dialog_ex, infrared_scene_learn_success_dialog_result_callback);
@@ -71,7 +71,6 @@ bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) {
} else if(
submenu_index == SubmenuIndexLearnNewRemote ||
submenu_index == SubmenuIndexLearnNewRemoteRaw) {
// enable automatic signal decoding if "Learn New Remote"
// disable automatic signal decoding if "Learn New Remote (RAW)"
infrared_worker_rx_enable_signal_decoding(
@@ -45,10 +45,9 @@ static void lfrfid_clear_t5577_password_and_config_to_EM(LfRfid* app) {
writer_initialize(t55xxtiming);
popup_set_header(popup, "Removing\npassword", 90, 36, AlignCenter, AlignCenter);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
}
popup_set_text(popup, curr_buf, 90, 56, AlignCenter, AlignCenter);
@@ -81,10 +80,9 @@ void lfrfid_scene_clear_t5577_on_enter(void* context) {
notification_message(app->notifications, &sequence_success);
popup_set_header(popup, "Done!", 94, 10, AlignCenter, AlignTop);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 7, &I_RFIDDolphinSuccess_108x57_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 7, &I_RFIDDolphinSuccess_108x57);
}
popup_set_context(popup, app);
@@ -7,10 +7,9 @@ void lfrfid_scene_delete_success_on_enter(void* context) {
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
}
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
@@ -19,10 +19,9 @@ void lfrfid_scene_emulate_on_enter(void* context) {
AlignCenter,
AlignTop);
}
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
}
@@ -20,7 +20,7 @@ void lfrfid_scene_extra_actions_on_enter(void* context) {
submenu_add_item(
submenu,
"Read ASK (FDX,Regular)",
"Read ASK (Animal, FDX)",
SubmenuIndexASK,
lfrfid_scene_extra_actions_submenu_callback,
app);
@@ -35,10 +35,9 @@ void lfrfid_scene_raw_read_on_enter(void* context) {
LfRfidReadRawState* state = malloc(sizeof(LfRfidReadRawState));
scene_manager_set_scene_state(app->scene_manager, LfRfidSceneRawRead, (uint32_t)state);
state->string_file_name = furi_string_alloc();
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
}
view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup);
@@ -9,10 +9,9 @@ void lfrfid_scene_rpc_on_enter(void* context) {
popup_set_header(popup, "LF RFID", 89, 42, AlignCenter, AlignBottom);
popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61);
}
@@ -9,10 +9,9 @@ void lfrfid_scene_save_success_on_enter(void* context) {
// Clear state of data enter scene
scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveData, 0);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
}
else {
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
@@ -36,10 +36,9 @@ void lfrfid_scene_write_on_enter(void* context) {
AlignCenter,
AlignTop);
}
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
}
@@ -8,10 +8,9 @@ void lfrfid_scene_write_success_on_enter(void* context) {
DESKTOP_SETTINGS_LOAD(settings);
popup_set_header(popup, "Successfully\nwritten!", 94, 3, AlignCenter, AlignTop);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57);
}
popup_set_context(popup, app);
@@ -13,10 +13,9 @@ void nfc_scene_delete_success_on_enter(void* context) {
// Setup view
Popup* popup = nfc->popup;
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
}
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
@@ -1,4 +1,5 @@
#include "../nfc_i.h"
#include "../../../settings/desktop_settings/desktop_settings_app.h"
#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (100)
@@ -37,7 +38,15 @@ static void nfc_scene_emulate_nfcv_widget_config(Nfc* nfc, bool data_received) {
FuriString* info_str;
info_str = furi_string_alloc();
widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61);
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(settings->sfw_mode) {
widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61_sfw);
} else {
widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61);
}
widget_add_string_element(
widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating NfcV");
if(strcmp(nfc->dev->dev_name, "")) {
@@ -55,6 +64,7 @@ static void nfc_scene_emulate_nfcv_widget_config(Nfc* nfc, bool data_received) {
widget_add_button_element(
widget, GuiButtonTypeCenter, "Log", nfc_scene_emulate_nfcv_widget_callback, nfc);
}
free(settings);
}
void nfc_scene_emulate_nfcv_on_enter(void* context) {
@@ -40,10 +40,9 @@ static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) {
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61_sfw);
}
else {
} else {
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61);
}
widget_add_string_element(widget, 57, 13, AlignLeft, AlignTop, FontPrimary, "Emulating UID");
@@ -26,10 +26,9 @@ void nfc_scene_mf_classic_emulate_on_enter(void* context) {
} else {
nfc_text_store_set(nfc, "MIFARE\nClassic");
}
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61);
}
popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop);
@@ -16,10 +16,9 @@ void nfc_scene_mf_classic_update_success_on_enter(void* context) {
notification_message(nfc->notifications, &sequence_success);
Popup* popup = nfc->popup;
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
}
else {
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom);
@@ -16,10 +16,9 @@ void nfc_scene_mf_classic_write_success_on_enter(void* context) {
notification_message(nfc->notifications, &sequence_success);
Popup* popup = nfc->popup;
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
}
else {
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom);
@@ -66,7 +66,15 @@ void nfc_scene_mf_ultralight_emulate_widget_config(Nfc* nfc, bool auth_attempted
FuriString* info_str;
info_str = furi_string_alloc();
widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61);
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if(settings->sfw_mode) {
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61_sfw);
} else {
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61);
}
if(strcmp(nfc->dev->dev_name, "")) {
furi_string_printf(info_str, "Emulating\n%s", nfc->dev->dev_name);
} else {
@@ -84,6 +92,7 @@ void nfc_scene_mf_ultralight_emulate_widget_config(Nfc* nfc, bool auth_attempted
nfc_scene_mf_ultralight_emulate_widget_callback,
nfc);
}
free(settings);
}
void nfc_scene_mf_ultralight_emulate_on_enter(void* context) {
@@ -114,10 +123,9 @@ void nfc_scene_mf_ultralight_emulate_on_enter(void* context) {
} else {
nfc_text_store_set(nfc, "MIFARE\nNTAG");
}
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 3, &I_NFC_dolphin_emulation_47x61);
}
popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop);
@@ -107,7 +107,7 @@ void nfc_scene_passport_auth_on_enter(void* context) {
item = variable_item_list_add(variable_item_list, "Document Nr.", 1, NULL, NULL);
strncpy(temp_str, mrtd_data->auth.doc_number, temp_str_size);
temp_str[temp_str_size -1] = '\x00';
temp_str[temp_str_size - 1] = '\x00';
if(strlen(temp_str) > 8) {
temp_str[8] = '.';
temp_str[9] = '.';
@@ -31,7 +31,6 @@ void nfc_scene_passport_date_on_enter(void* context) {
date_value.month = 0;
date_value.day = 0;
switch(date_type) {
case NFC_PASSPORT_DATE_BIRTH:
text_input_set_header_text(text_input, "Birth Date");
@@ -13,10 +13,9 @@ void nfc_scene_restore_original_on_enter(void* context) {
// Setup view
Popup* popup = nfc->popup;
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
}
else {
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Original file\nrestored", 13, 22, AlignLeft, AlignBottom);
+2 -3
View File
@@ -10,10 +10,9 @@ void nfc_scene_rpc_on_enter(void* context) {
popup_set_header(popup, "NFC", 89, 42, AlignCenter, AlignBottom);
popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 12, &I_NFC_dolphin_emulation_47x61_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 12, &I_NFC_dolphin_emulation_47x61);
}
@@ -13,10 +13,9 @@ void nfc_scene_save_success_on_enter(void* context) {
// Setup view
Popup* popup = nfc->popup;
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
}
else {
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
+2 -1
View File
@@ -683,7 +683,8 @@ int32_t playlist_app(void* p) {
dialog_file_browser_set_basic_options(&browser_options, PLAYLIST_EXT, &I_sub1_10px);
browser_options.base_path = PLAYLIST_FOLDER;
bool res = dialog_file_browser_show(dialogs, app->file_path, app->file_path, &browser_options);
bool res =
dialog_file_browser_show(dialogs, app->file_path, app->file_path, &browser_options);
furi_record_close(RECORD_DIALOGS);
// check if a file was selected
@@ -15,10 +15,9 @@ void subghz_scene_delete_success_on_enter(void* context) {
// Setup view
Popup* popup = subghz->popup;
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
}
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
@@ -16,10 +16,9 @@ void subghz_scene_rpc_on_enter(void* context) {
popup_set_header(popup, "Sub-GHz", 89, 42, AlignCenter, AlignBottom);
popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61_sfw);
}
else {
} else {
popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61);
}
@@ -14,10 +14,9 @@ void subghz_scene_save_success_on_enter(void* context) {
// Setup view
Popup* popup = subghz->popup;
if (settings->sfw_mode) {
if(settings->sfw_mode) {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59_sfw);
}
else {
} else {
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
}
popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
+10 -11
View File
@@ -387,7 +387,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) {
}
printf(
"Listening at \033[0;33m%s\033[0m.\r\n\r\nPress CTRL+C to stop\r\n\r\n",
"Listening at %s.\r\n\r\nPress CTRL+C to stop\r\n\r\n",
furi_string_get_cstr(file_name));
LevelDuration level_duration;
@@ -521,7 +521,8 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) {
furi_string_free(source);
}
static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
static void subghz_cli_command_chat(Cli* cli, FuriString* args, void* context) {
UNUSED(context);
uint32_t frequency = 433920000;
if(furi_string_size(args)) {
@@ -577,7 +578,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
furi_string_printf(name, "\033[0;33m%s\033[0m: ", furi_hal_version_get_name_ptr());
furi_string_printf(name, "%s: ", furi_hal_version_get_name_ptr());
furi_string_set(input, name);
printf("%s", furi_string_get_cstr(input));
fflush(stdout);
@@ -658,18 +659,14 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
notification_message(notification, &sequence_single_vibro);
break;
case SubGhzChatEventUserEntrance:
furi_string_printf(
sysmsg,
"\033[0;34m%s joined chat.\033[0m\r\n",
furi_hal_version_get_name_ptr());
furi_string_printf(sysmsg, "%s joined chat.\r\n", furi_hal_version_get_name_ptr());
subghz_chat_worker_write(
subghz_chat,
(uint8_t*)furi_string_get_cstr(sysmsg),
strlen(furi_string_get_cstr(sysmsg)));
break;
case SubGhzChatEventUserExit:
furi_string_printf(
sysmsg, "\033[0;31m%s left chat.\033[0m\r\n", furi_hal_version_get_name_ptr());
furi_string_printf(sysmsg, "%s left chat.\r\n", furi_hal_version_get_name_ptr());
subghz_chat_worker_write(
subghz_chat,
(uint8_t*)furi_string_get_cstr(sysmsg),
@@ -714,7 +711,7 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) {
}
if(furi_string_cmp_str(cmd, "chat") == 0) {
subghz_cli_command_chat(cli, args);
subghz_cli_command_chat(cli, args, NULL);
break;
}
@@ -766,9 +763,11 @@ void subghz_on_system_start() {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command, NULL);
// psst RM... i know you dont care much about errors, but if you ever see this... incompatible pointer type :3
cli_add_command(cli, "chat", CliCommandFlagDefault, subghz_cli_command_chat, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(subghz_cli_command);
#endif
}
}
+4 -6
View File
@@ -234,10 +234,9 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
if(model->history_item == 0) {
if(model->mode == SubGhzViewReceiverModeLive) {
if (settings->sfw_mode) {
if(settings->sfw_mode) {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52_sfw);
}
else {
} else {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
}
canvas_set_font(canvas, FontPrimary);
@@ -245,10 +244,9 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
canvas_draw_line(canvas, 46, 51, 125, 51);
canvas_set_font(canvas, FontSecondary);
} else {
if (settings->sfw_mode) {
if(settings->sfw_mode) {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52_sfw);
}
else {
} else {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
}
canvas_set_font(canvas, FontPrimary);
@@ -29,7 +29,7 @@ void u2f_scene_error_on_enter(void* context) {
app->widget, GuiButtonTypeLeft, "Back", u2f_scene_error_event_callback, app);
} else if(app->error == U2fAppErrorCloseRpc) {
widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nis active!");
widget_add_string_multiline_element(
@@ -40,8 +40,7 @@ void u2f_scene_error_on_enter(void* context) {
AlignTop,
FontSecondary,
"Disconnect from\nPC or phone to\nuse this function.");
}
else {
} else {
widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "I am not\na whore!");
widget_add_string_multiline_element(
+37 -41
View File
@@ -18,70 +18,66 @@ static void u2f_view_draw_callback(Canvas* canvas, void* _model) {
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
canvas_draw_icon(canvas, 8, 14, &I_Drive_112x35);
canvas_set_font(canvas, FontSecondary);
if (model->display_msg == U2fMsgNotConnected) {
if (settings->sfw_mode) {
if(model->display_msg == U2fMsgNotConnected) {
if(settings->sfw_mode) {
canvas_draw_icon(canvas, 22, 15, &I_Connect_me_62x31_sfw);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connect to a device");
}
else {
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connect to a device");
} else {
canvas_draw_icon(canvas, 22, 15, &I_Connect_me_62x31);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Plug me in d-daddy");
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Plug me in d-daddy");
}
}
else if (model->display_msg == U2fMsgIdle) {
if (settings->sfw_mode) {
} else if(model->display_msg == U2fMsgIdle) {
if(settings->sfw_mode) {
canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31_sfw);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connected!");
}
else {
} else {
canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Connected!");
}
}
else if (model->display_msg == U2fMsgRegister) {
if (settings->sfw_mode) {
} else if(model->display_msg == U2fMsgRegister) {
if(settings->sfw_mode) {
elements_button_center(canvas, "OK");
canvas_draw_icon(canvas, 22, 15, &I_Auth_62x31_sfw);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press OK to register");
}
else {
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press OK to register");
} else {
elements_button_center(canvas, "CUM");
canvas_draw_icon(canvas, 22, 15, &I_Auth_62x31);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press CUM to register");
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press CUM to register");
}
}
else if (model->display_msg == U2fMsgAuth) {
if (settings->sfw_mode) {
} else if(model->display_msg == U2fMsgAuth) {
if(settings->sfw_mode) {
elements_button_center(canvas, "OK");
canvas_draw_icon(canvas, 22, 15, &I_Auth_62x31_sfw);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press OK to authenticate");
}
else {
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press OK to authenticate");
} else {
elements_button_center(canvas, "CUM");
canvas_draw_icon(canvas, 22, 15, &I_Auth_62x31);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press CUM to authenticate");
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Press CUM to authenticate");
}
}
else if (model->display_msg == U2fMsgSuccess) {
if (settings->sfw_mode) {
} else if(model->display_msg == U2fMsgSuccess) {
if(settings->sfw_mode) {
canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31_sfw);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Authentication successful!");
}
else {
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Authentication successful!");
} else {
canvas_draw_icon(canvas, 22, 15, &I_Connected_62x31);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Cum released~");
}
}
else if (model->display_msg == U2fMsgError) {
if (settings->sfw_mode) {
} else if(model->display_msg == U2fMsgError) {
if(settings->sfw_mode) {
canvas_draw_icon(canvas, 22, 15, &I_Error_62x31_sfw);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Certificate error");
}
else {
canvas_draw_str_aligned(
canvas, 128 / 2, 3, AlignCenter, AlignTop, "Certificate error");
} else {
canvas_draw_icon(canvas, 22, 15, &I_Error_62x31);
canvas_draw_str_aligned(canvas, 128 / 2, 3, AlignCenter, AlignTop, "Unable to cum");
}
@@ -94,10 +90,10 @@ static bool u2f_view_input_callback(InputEvent* event, void* context) {
U2fView* u2f = context;
bool consumed = false;
if (event->type == InputTypeShort) {
if (event->key == InputKeyOk) {
if(event->type == InputTypeShort) {
if(event->key == InputKeyOk) {
consumed = true;
if (u2f->callback != NULL) u2f->callback(InputTypeShort, u2f->context);
if(u2f->callback != NULL) u2f->callback(InputTypeShort, u2f->context);
}
}
@@ -277,7 +277,7 @@ void reset_level(Canvas* canvas, ArkanoidState* arkanoid_state) {
arkanoid_state->brickCount = 0;
arkanoid_state->ball_state.released = false;
arkanoid_state->gameStarted = false;
// Reset all brick hit states
for(unsigned int row = 0; row < arkanoid_state->ROWS; row++) {
for(unsigned int column = 0; column < arkanoid_state->COLUMNS; column++) {
+194 -215
View File
@@ -32,42 +32,42 @@
/* ============================ Data structures ============================= */
typedef struct Ship {
float x, /* Ship x position. */
y, /* Ship y position. */
vx, /* x velocity. */
vy, /* y velocity. */
rot; /* Current rotation. 2*PI full rotation. */
float x, /* Ship x position. */
y, /* Ship y position. */
vx, /* x velocity. */
vy, /* y velocity. */
rot; /* Current rotation. 2*PI full rotation. */
} Ship;
typedef struct Bullet {
float x, y, vx, vy; /* Fields like in ship. */
uint32_t ttl; /* Time to live, in ticks. */
float x, y, vx, vy; /* Fields like in ship. */
uint32_t ttl; /* Time to live, in ticks. */
} Bullet;
typedef struct Asteroid {
float x, y, vx, vy, rot, /* Fields like ship. */
rot_speed, /* Angular velocity (rot speed and sense). */
size; /* Asteroid size. */
uint8_t shape_seed; /* Seed to give random shape. */
float x, y, vx, vy, rot, /* Fields like ship. */
rot_speed, /* Angular velocity (rot speed and sense). */
size; /* Asteroid size. */
uint8_t shape_seed; /* Seed to give random shape. */
} Asteroid;
#define MAXBUL 10 /* Max bullets on the screen. */
#define MAXAST 32 /* Max asteroids on the screen. */
#define MAXBUL 10 /* Max bullets on the screen. */
#define MAXAST 32 /* Max asteroids on the screen. */
#define SHIP_HIT_ANIMATION_LEN 15
typedef struct AsteroidsApp {
/* GUI */
Gui *gui;
ViewPort *view_port; /* We just use a raw viewport and we render
Gui* gui;
ViewPort* view_port; /* We just use a raw viewport and we render
everything into the low level canvas. */
FuriMessageQueue *event_queue; /* Key press events go here. */
FuriMessageQueue* event_queue; /* Key press events go here. */
/* Game state. */
int running; /* Once false exists the app. */
bool gameover; /* Game over status. */
uint32_t ticks; /* Game ticks. Increments at each refresh. */
uint32_t score; /* Game score. */
uint32_t lives; /* Number of lives in the current game. */
uint32_t ship_hit; /* When non zero, the ship was hit by an asteroid
int running; /* Once false exists the app. */
bool gameover; /* Game over status. */
uint32_t ticks; /* Game ticks. Increments at each refresh. */
uint32_t score; /* Game score. */
uint32_t lives; /* Number of lives in the current game. */
uint32_t ship_hit; /* When non zero, the ship was hit by an asteroid
and we need to show an animation as long as
its value is non-zero (and decrease it's value
at each tick of animation). */
@@ -76,26 +76,26 @@ typedef struct AsteroidsApp {
struct Ship ship;
/* Bullets state. */
struct Bullet bullets[MAXBUL]; /* Each bullet state. */
int bullets_num; /* Active bullets. */
uint32_t last_bullet_tick; /* Tick the last bullet was fired. */
struct Bullet bullets[MAXBUL]; /* Each bullet state. */
int bullets_num; /* Active bullets. */
uint32_t last_bullet_tick; /* Tick the last bullet was fired. */
/* Asteroids state. */
Asteroid asteroids[MAXAST]; /* Each asteroid state. */
int asteroids_num; /* Active asteroids. */
Asteroid asteroids[MAXAST]; /* Each asteroid state. */
int asteroids_num; /* Active asteroids. */
uint32_t pressed[InputKeyMAX]; /* pressed[id] is true if pressed.
Each array item contains the time
in milliseconds the key was pressed. */
bool fire; /* Short press detected: fire a bullet. */
bool fire; /* Short press detected: fire a bullet. */
} AsteroidsApp;
/* ============================== Prototypes ================================ */
// Only functions called before their definition are here.
void restart_game_after_gameover(AsteroidsApp *app);
uint32_t key_pressed_time(AsteroidsApp *app, InputKey key);
void restart_game_after_gameover(AsteroidsApp* app);
uint32_t key_pressed_time(AsteroidsApp* app, InputKey key);
/* ============================ 2D drawing ================================== */
@@ -110,29 +110,21 @@ typedef struct Poly {
} Poly;
/* Define the polygons we use. */
Poly ShipPoly = {
{-3, 0, 3},
{-3, 6, -3},
3
};
Poly ShipPoly = {{-3, 0, 3}, {-3, 6, -3}, 3};
Poly ShipFirePoly = {
{-1.5, 0, 1.5},
{-3, -6, -3},
3
};
Poly ShipFirePoly = {{-1.5, 0, 1.5}, {-3, -6, -3}, 3};
/* Rotate the point of the polygon 'poly' and store the new rotated
* polygon in 'rot'. The polygon is rotated by an angle 'a', with
* center at 0,0. */
void rotate_poly(Poly *rot, Poly *poly, float a) {
void rotate_poly(Poly* rot, Poly* poly, float a) {
/* We want to compute sin(a) and cos(a) only one time
* for every point to rotate. It's a slow operation. */
float sin_a = (float)sin(a);
float cos_a = (float)cos(a);
for (uint32_t j = 0; j < poly->points; j++) {
rot->x[j] = poly->x[j]*cos_a - poly->y[j]*sin_a;
rot->y[j] = poly->y[j]*cos_a + poly->x[j]*sin_a;
for(uint32_t j = 0; j < poly->points; j++) {
rot->x[j] = poly->x[j] * cos_a - poly->y[j] * sin_a;
rot->y[j] = poly->y[j] * cos_a + poly->x[j] * sin_a;
}
rot->points = poly->points;
}
@@ -140,36 +132,34 @@ void rotate_poly(Poly *rot, Poly *poly, float a) {
/* This is an 8 bit LFSR we use to generate a predictable and fast
* pseudorandom sequence of numbers, to give a different shape to
* each asteroid. */
void lfsr_next(unsigned char *prev) {
void lfsr_next(unsigned char* prev) {
unsigned char lsb = *prev & 1;
*prev = *prev >> 1;
if (lsb == 1) *prev ^= 0b11000111;
*prev ^= *prev<<7; /* Mix things a bit more. */
if(lsb == 1) *prev ^= 0b11000111;
*prev ^= *prev << 7; /* Mix things a bit more. */
}
/* Render the polygon 'poly' at x,y, rotated by the specified angle. */
void draw_poly(Canvas *const canvas, Poly *poly, uint8_t x, uint8_t y, float a)
{
void draw_poly(Canvas* const canvas, Poly* poly, uint8_t x, uint8_t y, float a) {
Poly rot;
rotate_poly(&rot,poly,a);
rotate_poly(&rot, poly, a);
canvas_set_color(canvas, ColorBlack);
for (uint32_t j = 0; j < rot.points; j++) {
for(uint32_t j = 0; j < rot.points; j++) {
uint32_t a = j;
uint32_t b = j+1;
if (b == rot.points) b = 0;
canvas_draw_line(canvas,x+rot.x[a],y+rot.y[a],
x+rot.x[b],y+rot.y[b]);
uint32_t b = j + 1;
if(b == rot.points) b = 0;
canvas_draw_line(canvas, x + rot.x[a], y + rot.y[a], x + rot.x[b], y + rot.y[b]);
}
}
/* A bullet is just a + pixels pattern. A single pixel is not
* visible enough. */
void draw_bullet(Canvas *const canvas, Bullet *b) {
canvas_draw_dot(canvas,b->x-1,b->y);
canvas_draw_dot(canvas,b->x+1,b->y);
canvas_draw_dot(canvas,b->x,b->y);
canvas_draw_dot(canvas,b->x,b->y-1);
canvas_draw_dot(canvas,b->x,b->y+1);
void draw_bullet(Canvas* const canvas, Bullet* b) {
canvas_draw_dot(canvas, b->x - 1, b->y);
canvas_draw_dot(canvas, b->x + 1, b->y);
canvas_draw_dot(canvas, b->x, b->y);
canvas_draw_dot(canvas, b->x, b->y - 1);
canvas_draw_dot(canvas, b->x, b->y + 1);
}
/* Draw an asteroid. The asteroid shapes is computed on the fly and
@@ -177,15 +167,15 @@ void draw_bullet(Canvas *const canvas, Bullet *b) {
* the shape, we use an initial fixed shape that we resize according
* to the asteroid size, perturbed according to the asteroid shape
* seed, and finally draw it rotated of the right amount. */
void draw_asteroid(Canvas *const canvas, Asteroid *ast) {
void draw_asteroid(Canvas* const canvas, Asteroid* ast) {
Poly ap;
/* Start with what is kinda of a circle. Note that this could be
* stored into a template and copied here, to avoid computing
* sin() / cos(). But the Flipper can handle it without problems. */
uint8_t r = ast->shape_seed;
for (int j = 0; j < 8; j++) {
float a = (PI*2)/8*j;
for(int j = 0; j < 8; j++) {
float a = (PI * 2) / 8 * j;
/* Before generating the point, to make the shape unique generate
* a random factor between .7 and 1.3 to scale the distance from
@@ -193,75 +183,73 @@ void draw_asteroid(Canvas *const canvas, Asteroid *ast) {
* that remains always the same, so we use a predictable PRNG
* implemented by an 8 bit shift register. */
lfsr_next(&r);
float scaling = .7+((float)r/255*.6);
float scaling = .7 + ((float)r / 255 * .6);
ap.x[j] = (float)sin(a) * ast->size * scaling;
ap.y[j] = (float)cos(a) * ast->size * scaling;
}
ap.points = 8;
draw_poly(canvas,&ap,ast->x,ast->y,ast->rot);
draw_poly(canvas, &ap, ast->x, ast->y, ast->rot);
}
/* Draw small ships in the top-right part of the screen, one for
* each left live. */
void draw_left_lives(Canvas *const canvas, AsteroidsApp *app) {
void draw_left_lives(Canvas* const canvas, AsteroidsApp* app) {
int lives = app->lives;
int x = SCREEN_XRES-5;
int x = SCREEN_XRES - 5;
Poly mini_ship = {
{-2, 0, 2},
{-2, 4, -2},
3
};
Poly mini_ship = {{-2, 0, 2}, {-2, 4, -2}, 3};
while(lives--) {
draw_poly(canvas,&mini_ship,x,6,PI);
draw_poly(canvas, &mini_ship, x, 6, PI);
x -= 6;
}
}
/* Given the current position, update it according to the velocity and
* wrap it back to the other side if the object went over the screen. */
void update_pos_by_velocity(float *x, float *y, float vx, float vy) {
void update_pos_by_velocity(float* x, float* y, float vx, float vy) {
/* Return back from one side to the other of the screen. */
*x += vx;
*y += vy;
if (*x >= SCREEN_XRES) *x = 0;
else if (*x < 0) *x = SCREEN_XRES-1;
if (*y >= SCREEN_YRES) *y = 0;
else if (*y < 0) *y = SCREEN_YRES-1;
if(*x >= SCREEN_XRES)
*x = 0;
else if(*x < 0)
*x = SCREEN_XRES - 1;
if(*y >= SCREEN_YRES)
*y = 0;
else if(*y < 0)
*y = SCREEN_YRES - 1;
}
/* Render the current game screen. */
void render_callback(Canvas *const canvas, void *ctx) {
AsteroidsApp *app = ctx;
void render_callback(Canvas* const canvas, void* ctx) {
AsteroidsApp* app = ctx;
/* Clear screen. */
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 0, 0, SCREEN_XRES-1, SCREEN_YRES-1);
canvas_draw_box(canvas, 0, 0, SCREEN_XRES - 1, SCREEN_YRES - 1);
/* Draw score. */
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontSecondary);
char score[32];
snprintf(score,sizeof(score),"%lu",app->score);
snprintf(score, sizeof(score), "%lu", app->score);
canvas_draw_str(canvas, 0, 8, score);
/* Draw left ships. */
draw_left_lives(canvas,app);
draw_left_lives(canvas, app);
/* Draw ship, asteroids, bullets. */
draw_poly(canvas,&ShipPoly,app->ship.x,app->ship.y,app->ship.rot);
if (key_pressed_time(app,InputKeyOk) > SHIP_ACCELERATION_KEYPRESS_TIME)
draw_poly(canvas,&ShipFirePoly,app->ship.x,app->ship.y,app->ship.rot);
draw_poly(canvas, &ShipPoly, app->ship.x, app->ship.y, app->ship.rot);
if(key_pressed_time(app, InputKeyOk) > SHIP_ACCELERATION_KEYPRESS_TIME)
draw_poly(canvas, &ShipFirePoly, app->ship.x, app->ship.y, app->ship.rot);
for (int j = 0; j < app->bullets_num; j++)
draw_bullet(canvas,&app->bullets[j]);
for(int j = 0; j < app->bullets_num; j++) draw_bullet(canvas, &app->bullets[j]);
for (int j = 0; j < app->asteroids_num; j++)
draw_asteroid(canvas,&app->asteroids[j]);
for(int j = 0; j < app->asteroids_num; j++) draw_asteroid(canvas, &app->asteroids[j]);
/* Game over text. */
if (app->gameover) {
if(app->gameover) {
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 28, 35, "GAME OVER");
@@ -273,9 +261,9 @@ void render_callback(Canvas *const canvas, void *ctx) {
/* ============================ Game logic ================================== */
float distance(float x1, float y1, float x2, float y2) {
float dx = x1-x2;
float dy = y1-y2;
return sqrt(dx*dx+dy*dy);
float dx = x1 - x2;
float dy = y1 - y2;
return sqrt(dx * dx + dy * dy);
}
/* Detect a collision between the object at x1,y1 of radius r1 and
@@ -289,10 +277,7 @@ float distance(float x1, float y1, float x2, float y2) {
* spheres (this is why this function only takes the radius). This
* is, after all, kinda accurate for asteroids, for bullets, and
* even for the ship "core" itself. */
bool objects_are_colliding(float x1, float y1, float r1,
float x2, float y2, float r2,
float factor)
{
bool objects_are_colliding(float x1, float y1, float r1, float x2, float y2, float r2, float factor) {
/* The objects are colliding if the distance between object 1 and 2
* is smaller than the sum of the two radiuses r1 and r2.
* So it would be like: sqrt((x1-x2)^2+(y1-y2)^2) < r1+r2.
@@ -301,24 +286,24 @@ bool objects_are_colliding(float x1, float y1, float r1,
* the comparison like this:
*
* (x1-x2)^2+(y1-y2)^2 < (r1+r2)^2. */
float dx = (x1-x2)*factor;
float dy = (y1-y2)*factor;
float rsum = r1+r2;
return dx*dx+dy*dy < rsum*rsum;
float dx = (x1 - x2) * factor;
float dy = (y1 - y2) * factor;
float rsum = r1 + r2;
return dx * dx + dy * dy < rsum * rsum;
}
/* Create a new bullet headed in the same direction of the ship. */
void ship_fire_bullet(AsteroidsApp *app) {
if (app->bullets_num == MAXBUL) return;
Bullet *b = &app->bullets[app->bullets_num];
void ship_fire_bullet(AsteroidsApp* app) {
if(app->bullets_num == MAXBUL) return;
Bullet* b = &app->bullets[app->bullets_num];
b->x = app->ship.x;
b->y = app->ship.y;
b->vx = -sin(app->ship.rot);
b->vy = cos(app->ship.rot);
/* Ship should fire from its head, not in the middle. */
b->x += b->vx*5;
b->y += b->vy*5;
b->x += b->vx * 5;
b->y += b->vy * 5;
/* Give the bullet some velocity (for now the vector is just
* normalized to 1). */
@@ -336,68 +321,68 @@ void ship_fire_bullet(AsteroidsApp *app) {
}
/* Remove the specified bullet by id (index in the array). */
void remove_bullet(AsteroidsApp *app, int bid) {
void remove_bullet(AsteroidsApp* app, int bid) {
/* Replace the top bullet with the empty space left
* by the removal of this bullet. This way we always take the
* array dense, which is an advantage when looping. */
int n = --app->bullets_num;
if (n && bid != n) app->bullets[bid] = app->bullets[n];
if(n && bid != n) app->bullets[bid] = app->bullets[n];
}
/* Create a new asteroid, away from the ship. Return the
* pointer to the asteroid object, so that the caller can change
* certain things of the asteroid if needed. */
Asteroid *add_asteroid(AsteroidsApp *app) {
if (app->asteroids_num == MAXAST) return NULL;
float size = 4+rand()%15;
Asteroid* add_asteroid(AsteroidsApp* app) {
if(app->asteroids_num == MAXAST) return NULL;
float size = 4 + rand() % 15;
float min_distance = 20;
float x,y;
float x, y;
do {
x = rand() % SCREEN_XRES;
y = rand() % SCREEN_YRES;
} while(distance(app->ship.x,app->ship.y,x,y) < min_distance+size);
Asteroid *a = &app->asteroids[app->asteroids_num++];
} while(distance(app->ship.x, app->ship.y, x, y) < min_distance + size);
Asteroid* a = &app->asteroids[app->asteroids_num++];
a->x = x;
a->y = y;
a->vx = 2*(-.5 + ((float)rand()/RAND_MAX));
a->vy = 2*(-.5 + ((float)rand()/RAND_MAX));
a->vx = 2 * (-.5 + ((float)rand() / RAND_MAX));
a->vy = 2 * (-.5 + ((float)rand() / RAND_MAX));
a->size = size;
a->rot = 0;
a->rot_speed = ((float)rand()/RAND_MAX)/10;
if (app->ticks & 1) a->rot_speed = -(a->rot_speed);
a->rot_speed = ((float)rand() / RAND_MAX) / 10;
if(app->ticks & 1) a->rot_speed = -(a->rot_speed);
a->shape_seed = rand() & 255;
return a;
}
/* Remove the specified asteroid by id (index in the array). */
void remove_asteroid(AsteroidsApp *app, int id) {
void remove_asteroid(AsteroidsApp* app, int id) {
/* Replace the top asteroid with the empty space left
* by the removal of this one. This way we always take the
* array dense, which is an advantage when looping. */
int n = --app->asteroids_num;
if (n && id != n) app->asteroids[id] = app->asteroids[n];
if(n && id != n) app->asteroids[id] = app->asteroids[n];
}
/* Called when an asteroid was reached by a bullet. The asteroid
* hit is the one with the specified 'id'. */
void asteroid_was_hit(AsteroidsApp *app, int id) {
void asteroid_was_hit(AsteroidsApp* app, int id) {
float sizelimit = 6; // Smaller than that, they disappear in one shot.
Asteroid *a = &app->asteroids[id];
Asteroid* a = &app->asteroids[id];
/* Asteroid is large enough to break into fragments. */
float size = a->size;
float x = a->x, y = a->y;
remove_asteroid(app,id);
if (size > sizelimit) {
remove_asteroid(app, id);
if(size > sizelimit) {
int max_fragments = size / sizelimit;
int fragments = 2+rand()%max_fragments;
float newsize = size/fragments;
if (newsize < 2) newsize = 2;
for (int j = 0; j < fragments; j++) {
int fragments = 2 + rand() % max_fragments;
float newsize = size / fragments;
if(newsize < 2) newsize = 2;
for(int j = 0; j < fragments; j++) {
a = add_asteroid(app);
if (a == NULL) break; // Too many asteroids on screen.
a->x = x + -(size/2) + rand() % (int)newsize;
a->y = y + -(size/2) + rand() % (int)newsize;
if(a == NULL) break; // Too many asteroids on screen.
a->x = x + -(size / 2) + rand() % (int)newsize;
a->y = y + -(size / 2) + rand() % (int)newsize;
a->size = newsize;
}
} else {
@@ -407,18 +392,19 @@ void asteroid_was_hit(AsteroidsApp *app, int id) {
/* Set game over state. When in game-over mode, the game displays a
* game over text with a background of many asteroids floating around. */
void game_over(AsteroidsApp *app) {
void game_over(AsteroidsApp* app) {
restart_game_after_gameover(app);
app->gameover = true;
int asteroids = 8;
while(asteroids-- && add_asteroid(app) != NULL);
while(asteroids-- && add_asteroid(app) != NULL)
;
}
/* Function called when a collision between the asteroid and the
* ship is detected. */
void ship_was_hit(AsteroidsApp *app) {
void ship_was_hit(AsteroidsApp* app) {
app->ship_hit = SHIP_HIT_ANIMATION_LEN;
if (app->lives) {
if(app->lives) {
app->lives--;
} else {
game_over(app);
@@ -427,10 +413,10 @@ void ship_was_hit(AsteroidsApp *app) {
/* Restart game after the ship is hit. Will reset the ship position, bullets
* and asteroids to restart the game. */
void restart_game(AsteroidsApp *app) {
void restart_game(AsteroidsApp* app) {
app->ship.x = SCREEN_XRES / 2;
app->ship.y = SCREEN_YRES / 2;
app->ship.rot = PI; /* Start headed towards top. */
app->ship.rot = PI; /* Start headed towards top. */
app->ship.vx = 0;
app->ship.vy = 0;
app->bullets_num = 0;
@@ -440,22 +426,22 @@ void restart_game(AsteroidsApp *app) {
/* Called after game over to restart the game. This function
* also calls restart_game(). */
void restart_game_after_gameover(AsteroidsApp *app) {
void restart_game_after_gameover(AsteroidsApp* app) {
app->gameover = false;
app->ticks = 0;
app->score = 0;
app->ship_hit = 0;
app->lives = GAME_START_LIVES-1; /* -1 to account for current one. */
app->lives = GAME_START_LIVES - 1; /* -1 to account for current one. */
restart_game(app);
}
/* Move bullets. */
void update_bullets_position(AsteroidsApp *app) {
for (int j = 0; j < app->bullets_num; j++) {
update_pos_by_velocity(&app->bullets[j].x,&app->bullets[j].y,
app->bullets[j].vx,app->bullets[j].vy);
if (--app->bullets[j].ttl == 0) {
remove_bullet(app,j);
void update_bullets_position(AsteroidsApp* app) {
for(int j = 0; j < app->bullets_num; j++) {
update_pos_by_velocity(
&app->bullets[j].x, &app->bullets[j].y, app->bullets[j].vx, app->bullets[j].vy);
if(--app->bullets[j].ttl == 0) {
remove_bullet(app, j);
j--; /* Process this bullet index again: the removal will
fill it with the top bullet to take the array dense. */
}
@@ -463,28 +449,28 @@ void update_bullets_position(AsteroidsApp *app) {
}
/* Move asteroids. */
void update_asteroids_position(AsteroidsApp *app) {
for (int j = 0; j < app->asteroids_num; j++) {
update_pos_by_velocity(&app->asteroids[j].x,&app->asteroids[j].y,
app->asteroids[j].vx,app->asteroids[j].vy);
void update_asteroids_position(AsteroidsApp* app) {
for(int j = 0; j < app->asteroids_num; j++) {
update_pos_by_velocity(
&app->asteroids[j].x, &app->asteroids[j].y, app->asteroids[j].vx, app->asteroids[j].vy);
app->asteroids[j].rot += app->asteroids[j].rot_speed;
if (app->asteroids[j].rot < 0) app->asteroids[j].rot = 2*PI;
else if (app->asteroids[j].rot > 2*PI) app->asteroids[j].rot = 0;
if(app->asteroids[j].rot < 0)
app->asteroids[j].rot = 2 * PI;
else if(app->asteroids[j].rot > 2 * PI)
app->asteroids[j].rot = 0;
}
}
/* Collision detection and game state update based on collisions. */
void detect_collisions(AsteroidsApp *app) {
void detect_collisions(AsteroidsApp* app) {
/* Detect collision between bullet and asteroid. */
for (int j = 0; j < app->bullets_num; j++) {
Bullet *b = &app->bullets[j];
for (int i = 0; i < app->asteroids_num; i++) {
Asteroid *a = &app->asteroids[i];
if (objects_are_colliding(a->x, a->y, a->size,
b->x, b->y, 1.5, 1))
{
asteroid_was_hit(app,i);
remove_bullet(app,j);
for(int j = 0; j < app->bullets_num; j++) {
Bullet* b = &app->bullets[j];
for(int i = 0; i < app->asteroids_num; i++) {
Asteroid* a = &app->asteroids[i];
if(objects_are_colliding(a->x, a->y, a->size, b->x, b->y, 1.5, 1)) {
asteroid_was_hit(app, i);
remove_bullet(app, j);
/* The bullet no longer exist. Break the loop.
* However we want to start processing from the
* same bullet index, since now it is used by
@@ -496,11 +482,9 @@ void detect_collisions(AsteroidsApp *app) {
}
/* Detect collision between ship and asteroid. */
for (int j = 0; j < app->asteroids_num; j++) {
Asteroid *a = &app->asteroids[j];
if (objects_are_colliding(a->x, a->y, a->size,
app->ship.x, app->ship.y, 4, 1))
{
for(int j = 0; j < app->asteroids_num; j++) {
Asteroid* a = &app->asteroids[j];
if(objects_are_colliding(a->x, a->y, a->size, app->ship.x, app->ship.y, 4, 1)) {
ship_was_hit(app);
break;
}
@@ -513,26 +497,26 @@ void detect_collisions(AsteroidsApp *app) {
* on velocity. Detect collisions. Update the score and so forth.
*
* Each time this function is called, app->tick is incremented. */
void game_tick(void *ctx) {
AsteroidsApp *app = ctx;
void game_tick(void* ctx) {
AsteroidsApp* app = ctx;
/* There are two special screens:
*
* 1. Ship was hit, we frozen the game as long as ship_hit isn't zero
* again, and show an animation of a rotating ship. */
if (app->ship_hit) {
if(app->ship_hit) {
app->ship.rot += 0.5;
app->ship_hit--;
view_port_update(app->view_port);
if (app->ship_hit == 0) {
if(app->ship_hit == 0) {
restart_game(app);
}
return;
} else if (app->gameover) {
/* 2. Game over. We need to update only background asteroids. In this
} else if(app->gameover) {
/* 2. Game over. We need to update only background asteroids. In this
* state the game just displays a GAME OVER text with the floating
* asteroids in background. */
if (key_pressed_time(app,InputKeyOk) > 100) {
if(key_pressed_time(app, InputKeyOk) > 100) {
restart_game_after_gameover(app);
}
update_asteroids_position(app);
@@ -541,12 +525,12 @@ void game_tick(void *ctx) {
}
/* Handle key presses. */
if (app->pressed[InputKeyLeft]) app->ship.rot -= .35;
if (app->pressed[InputKeyRight]) app->ship.rot += .35;
if (key_pressed_time(app,InputKeyOk) > SHIP_ACCELERATION_KEYPRESS_TIME) {
app->ship.vx -= 0.5*(float)sin(app->ship.rot);
app->ship.vy += 0.5*(float)cos(app->ship.rot);
} else if (app->pressed[InputKeyDown]) {
if(app->pressed[InputKeyLeft]) app->ship.rot -= .35;
if(app->pressed[InputKeyRight]) app->ship.rot += .35;
if(key_pressed_time(app, InputKeyOk) > SHIP_ACCELERATION_KEYPRESS_TIME) {
app->ship.vx -= 0.5 * (float)sin(app->ship.rot);
app->ship.vy += 0.5 * (float)cos(app->ship.rot);
} else if(app->pressed[InputKeyDown]) {
app->ship.vx *= 0.75;
app->ship.vy *= 0.75;
}
@@ -554,10 +538,10 @@ void game_tick(void *ctx) {
/* Fire a bullet if needed. app->fire is set in
* asteroids_update_keypress_state() since depends on exact
* pressure timing. */
if (app->fire) {
if(app->fire) {
uint32_t bullet_min_period = 200; // In milliseconds
uint32_t now = furi_get_tick();
if (now - app->last_bullet_tick >= bullet_min_period) {
if(now - app->last_bullet_tick >= bullet_min_period) {
ship_fire_bullet(app);
app->last_bullet_tick = now;
}
@@ -565,7 +549,7 @@ void game_tick(void *ctx) {
}
/* Update positions and detect collisions. */
update_pos_by_velocity(&app->ship.x,&app->ship.y,app->ship.vx,app->ship.vy);
update_pos_by_velocity(&app->ship.x, &app->ship.y, app->ship.vx, app->ship.vy);
update_bullets_position(app);
update_asteroids_position(app);
detect_collisions(app);
@@ -573,9 +557,7 @@ void game_tick(void *ctx) {
/* From time to time, create a new asteroid. The more asteroids
* already on the screen, the smaller probability of creating
* a new one. */
if (app->asteroids_num == 0 ||
(random() % 5000) < (30/(1+app->asteroids_num)))
{
if(app->asteroids_num == 0 || (random() % 5000) < (30 / (1 + app->asteroids_num))) {
add_asteroid(app);
}
@@ -587,16 +569,15 @@ void game_tick(void *ctx) {
/* Here all we do is putting the events into the queue that will be handled
* in the while() loop of the app entry point function. */
void input_callback(InputEvent* input_event, void* ctx)
{
AsteroidsApp *app = ctx;
furi_message_queue_put(app->event_queue,input_event,FuriWaitForever);
void input_callback(InputEvent* input_event, void* ctx) {
AsteroidsApp* app = ctx;
furi_message_queue_put(app->event_queue, input_event, FuriWaitForever);
}
/* Allocate the application state and initialize a number of stuff.
* This is called in the entry point to create the application state. */
AsteroidsApp* asteroids_app_alloc() {
AsteroidsApp *app = malloc(sizeof(AsteroidsApp));
AsteroidsApp* app = malloc(sizeof(AsteroidsApp));
app->gui = furi_record_open(RECORD_GUI);
app->view_port = view_port_alloc();
@@ -605,16 +586,16 @@ AsteroidsApp* asteroids_app_alloc() {
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
app->running = 1; /* Turns 0 when back is pressed. */
app->running = 1; /* Turns 0 when back is pressed. */
restart_game_after_gameover(app);
memset(app->pressed,0,sizeof(app->pressed));
memset(app->pressed, 0, sizeof(app->pressed));
return app;
}
/* Free what the application allocated. It is not clear to me if the
* Flipper OS, once the application exits, will be able to reclaim space
* even if we forget to free something here. */
void asteroids_app_free(AsteroidsApp *app) {
void asteroids_app_free(AsteroidsApp* app) {
furi_assert(app);
// View related.
@@ -630,28 +611,27 @@ void asteroids_app_free(AsteroidsApp *app) {
/* Return the time in milliseconds the specified key is continuously
* pressed. Or 0 if it is not pressed. */
uint32_t key_pressed_time(AsteroidsApp *app, InputKey key) {
return app->pressed[key] == 0 ? 0 :
furi_get_tick() - app->pressed[key];
uint32_t key_pressed_time(AsteroidsApp* app, InputKey key) {
return app->pressed[key] == 0 ? 0 : furi_get_tick() - app->pressed[key];
}
/* Handle keys interaction. */
void asteroids_update_keypress_state(AsteroidsApp *app, InputEvent input) {
if (input.type == InputTypePress) {
void asteroids_update_keypress_state(AsteroidsApp* app, InputEvent input) {
if(input.type == InputTypePress) {
app->pressed[input.key] = furi_get_tick();
} else if (input.type == InputTypeRelease) {
uint32_t dur = key_pressed_time(app,input.key);
} else if(input.type == InputTypeRelease) {
uint32_t dur = key_pressed_time(app, input.key);
app->pressed[input.key] = 0;
if (dur < 200 && input.key == InputKeyOk) app->fire = true;
if(dur < 200 && input.key == InputKeyOk) app->fire = true;
}
}
int32_t asteroids_app_entry(void* p) {
UNUSED(p);
AsteroidsApp *app = asteroids_app_alloc();
AsteroidsApp* app = asteroids_app_alloc();
/* Create a timer. We do data analysis in the callback. */
FuriTimer *timer = furi_timer_alloc(game_tick, FuriTimerTypePeriodic, app);
FuriTimer* timer = furi_timer_alloc(game_tick, FuriTimerTypePeriodic, app);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 10);
/* This is the main event loop: here we get the events that are pushed
@@ -660,25 +640,24 @@ int32_t asteroids_app_entry(void* p) {
InputEvent input;
while(app->running) {
FuriStatus qstat = furi_message_queue_get(app->event_queue, &input, 100);
if (qstat == FuriStatusOk) {
if (DEBUG_MSG) FURI_LOG_E(TAG, "Main Loop - Input: type %d key %u",
input.type, input.key);
if(qstat == FuriStatusOk) {
if(DEBUG_MSG)
FURI_LOG_E(TAG, "Main Loop - Input: type %d key %u", input.type, input.key);
/* Handle navigation here. Then handle view-specific inputs
* in the view specific handling function. */
if (input.type == InputTypeShort &&
input.key == InputKeyBack)
{
if(input.type == InputTypeShort && input.key == InputKeyBack) {
app->running = 0;
} else {
asteroids_update_keypress_state(app,input);
asteroids_update_keypress_state(app, input);
}
} else {
/* Useful to understand if the app is still alive when it
* does not respond because of bugs. */
if (DEBUG_MSG) {
static int c = 0; c++;
if (!(c % 20)) FURI_LOG_E(TAG, "Loop timeout");
if(DEBUG_MSG) {
static int c = 0;
c++;
if(!(c % 20)) FURI_LOG_E(TAG, "Loop timeout");
}
}
}
@@ -1,6 +1,6 @@
App(
appid="cli_gui",
name="CLI-GUI Bridge",
name="CLI (subghz chat)",
apptype=FlipperAppType.EXTERNAL,
entry_point="cligui_main",
requires=["gui","cli"],
@@ -24,14 +24,14 @@ static void cligui_tick_event_cb(void* context) {
furi_string_push_back(app->text_box_store, c);
}
}
if (available > 0) {
if(available > 0) {
text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store));
}
// Set input header stuff
size_t len = furi_string_size(app->text_box_store);
size_t idx = len - 2;
while (idx > 0) {
if (furi_string_get_char(app->text_box_store, idx) == '\n') {
while(idx > 0) {
if(furi_string_get_char(app->text_box_store, idx) == '\n') {
idx++;
break;
}
@@ -9,5 +9,4 @@ void console_output_input_handler(CliguiApp* app, InputEvent* event) {
char eot = 0x03;
furi_stream_buffer_send(app->data->streams.app_tx, &eot, 1, FuriWaitForever);
}
}
@@ -88,7 +88,6 @@ typedef struct {
void* pubsub;
} Loader_internal;
typedef struct {
CliCallback callback;
void* context;
+4 -4
View File
@@ -490,8 +490,9 @@ int32_t dap_link_app(void* p) {
if(furi_hal_usb_is_locked()) {
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc();
if (settings->sfw_mode) {
dialog_message_set_header(message, "Connection\nis active!", 3, 2, AlignLeft, AlignTop);
if(settings->sfw_mode) {
dialog_message_set_header(
message, "Connection\nis active!", 3, 2, AlignLeft, AlignTop);
dialog_message_set_text(
message,
"Disconnect from\nPC or phone to\nuse this function.",
@@ -499,8 +500,7 @@ int32_t dap_link_app(void* p) {
30,
AlignLeft,
AlignTop);
}
else {
} else {
dialog_message_set_header(message, "I am not\na whore!", 3, 2, AlignLeft, AlignTop);
dialog_message_set_text(
message,
@@ -209,10 +209,9 @@ void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) {
canvas_set_color(canvas, ColorBlack);
if(model->history_item == 0) {
if (settings->sfw_mode) {
if(settings->sfw_mode) {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52_sfw);
}
else {
} else {
canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
}
canvas_set_font(canvas, FontPrimary);
@@ -236,10 +235,9 @@ void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) {
canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8);
canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8);
canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42_sfw);
}
else {
} else {
canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42);
}
canvas_draw_dot(canvas, 17, 61);
+110 -119
View File
@@ -28,34 +28,39 @@ typedef struct {
InputEvent input;
} EventApp;
typedef struct Players
{
uint8_t player1_X,player1_Y,player2_X,player2_Y;
uint16_t player1_score,player2_score;
uint8_t ball_X,ball_Y,ball_X_speed,ball_Y_speed,ball_X_direction,ball_Y_direction;
typedef struct Players {
uint8_t player1_X, player1_Y, player2_X, player2_Y;
uint16_t player1_score, player2_score;
uint8_t ball_X, ball_Y, ball_X_speed, ball_Y_speed, ball_X_direction, ball_Y_direction;
} Players;
static void draw_callback(Canvas* canvas, void* ctx)
{
static void draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
Players* playersMutex = (Players*)acquire_mutex_block((ValueMutex*)ctx);
canvas_draw_frame(canvas, 0, 0, 128, 64);
canvas_draw_box(canvas, playersMutex->player1_X, playersMutex->player1_Y, PAD_SIZE_X, PAD_SIZE_Y);
canvas_draw_box(canvas, playersMutex->player2_X, playersMutex->player2_Y, PAD_SIZE_X, PAD_SIZE_Y);
canvas_draw_box(
canvas, playersMutex->player1_X, playersMutex->player1_Y, PAD_SIZE_X, PAD_SIZE_Y);
canvas_draw_box(
canvas, playersMutex->player2_X, playersMutex->player2_Y, PAD_SIZE_X, PAD_SIZE_Y);
canvas_draw_box(canvas, playersMutex->ball_X, playersMutex->ball_Y, BALL_SIZE, BALL_SIZE);
canvas_set_font(canvas, FontPrimary);
canvas_set_font_direction(canvas, CanvasDirectionBottomToTop);
char buffer[16];
snprintf(buffer, sizeof(buffer), "%u - %u", playersMutex->player1_score, playersMutex->player2_score);
canvas_draw_str_aligned(canvas, SCREEN_SIZE_X/2+15, SCREEN_SIZE_Y/2+2, AlignCenter, AlignTop, buffer);
snprintf(
buffer,
sizeof(buffer),
"%u - %u",
playersMutex->player1_score,
playersMutex->player2_score);
canvas_draw_str_aligned(
canvas, SCREEN_SIZE_X / 2 + 15, SCREEN_SIZE_Y / 2 + 2, AlignCenter, AlignTop, buffer);
release_mutex((ValueMutex*)ctx, playersMutex);
}
static void input_callback(InputEvent* input_event, void* ctx)
{
static void input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
EventApp event = {.type = EventTypeInput, .input = *input_event};
@@ -69,48 +74,44 @@ static void clock_tick(void* ctx) {
furi_message_queue_put(queue, &event, 0);
}
bool insidePad(uint8_t x, uint8_t y, uint8_t playerX, uint8_t playerY)
{
if (x >= playerX && x <= playerX+PAD_SIZE_X && y >= playerY && y <= playerY+PAD_SIZE_Y) return true;
bool insidePad(uint8_t x, uint8_t y, uint8_t playerX, uint8_t playerY) {
if(x >= playerX && x <= playerX + PAD_SIZE_X && y >= playerY && y <= playerY + PAD_SIZE_Y)
return true;
return false;
}
uint8_t changeSpeed()
{
uint8_t changeSpeed() {
uint8_t randomuint8[1];
while(1)
{
furi_hal_random_fill_buf(randomuint8,1);
randomuint8[0] &= 0b00000011;
if (randomuint8[0] >= 1) break;
while(1) {
furi_hal_random_fill_buf(randomuint8, 1);
randomuint8[0] &= 0b00000011;
if(randomuint8[0] >= 1) break;
}
return randomuint8[0];
}
uint8_t changeDirection()
{
uint8_t changeDirection() {
uint8_t randomuint8[1];
furi_hal_random_fill_buf(randomuint8,1);
furi_hal_random_fill_buf(randomuint8, 1);
randomuint8[0] &= 0b1;
return randomuint8[0];
return randomuint8[0];
}
int32_t flipper_pong_app()
{
int32_t flipper_pong_app() {
EventApp event;
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(EventApp));
Players players;
players.player1_X = SCREEN_SIZE_X-PAD_SIZE_X-1;
players.player1_Y = SCREEN_SIZE_Y/2 - PAD_SIZE_Y/2;
players.player1_X = SCREEN_SIZE_X - PAD_SIZE_X - 1;
players.player1_Y = SCREEN_SIZE_Y / 2 - PAD_SIZE_Y / 2;
players.player1_score = 0;
players.player2_X = 1;
players.player2_Y = SCREEN_SIZE_Y/2 - PAD_SIZE_Y/2;
players.player2_Y = SCREEN_SIZE_Y / 2 - PAD_SIZE_Y / 2;
players.player2_score = 0;
players.ball_X = SCREEN_SIZE_X/2 - BALL_SIZE/2;
players.ball_Y = SCREEN_SIZE_Y/2 - BALL_SIZE/2;
players.ball_X = SCREEN_SIZE_X / 2 - BALL_SIZE / 2;
players.ball_Y = SCREEN_SIZE_Y / 2 - BALL_SIZE / 2;
players.ball_X_speed = 1;
players.ball_Y_speed = 1;
players.ball_X_direction = changeDirection();
@@ -127,112 +128,110 @@ int32_t flipper_pong_app()
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
FuriTimer* timer = furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, 1000/FPS);
furi_timer_start(timer, 1000 / FPS);
while(1)
{
while(1) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, FuriWaitForever);
Players* playersMutex = (Players*)acquire_mutex_block(&state_mutex);
if (event_status == FuriStatusOk)
{
if(event.type == EventTypeInput)
{
if(event.input.key == InputKeyBack)
{
if(event_status == FuriStatusOk) {
if(event.type == EventTypeInput) {
if(event.input.key == InputKeyBack) {
release_mutex(&state_mutex, playersMutex);
break;
} else if(event.input.key == InputKeyUp) {
if(playersMutex->player1_Y >= 1 + PLAYER1_PAD_SPEED)
playersMutex->player1_Y -= PLAYER1_PAD_SPEED;
else
playersMutex->player1_Y = 1;
} else if(event.input.key == InputKeyDown) {
if(playersMutex->player1_Y <=
SCREEN_SIZE_Y - PAD_SIZE_Y - PLAYER1_PAD_SPEED - 1)
playersMutex->player1_Y += PLAYER1_PAD_SPEED;
else
playersMutex->player1_Y = SCREEN_SIZE_Y - PAD_SIZE_Y - 1;
}
else if(event.input.key == InputKeyUp)
{
if (playersMutex->player1_Y >= 1+PLAYER1_PAD_SPEED) playersMutex->player1_Y -= PLAYER1_PAD_SPEED;
else playersMutex->player1_Y = 1;
}
else if(event.input.key == InputKeyDown)
{
if (playersMutex->player1_Y <= SCREEN_SIZE_Y - PAD_SIZE_Y - PLAYER1_PAD_SPEED -1) playersMutex->player1_Y += PLAYER1_PAD_SPEED;
else playersMutex->player1_Y = SCREEN_SIZE_Y - PAD_SIZE_Y - 1;
}
}
else if (event.type == ClockEventTypeTick)
{
if (playersMutex->ball_X + BALL_SIZE/2 <= SCREEN_SIZE_X*0.35 && playersMutex->ball_X_direction == 0)
{
if (playersMutex->ball_Y + BALL_SIZE/2 < playersMutex->player2_Y + PAD_SIZE_Y/2)
{
if (playersMutex->player2_Y >= 1+PLAYER2_PAD_SPEED) playersMutex->player2_Y -= PLAYER2_PAD_SPEED;
else playersMutex->player2_Y= 1;
}
else if (playersMutex->ball_Y + BALL_SIZE/2 > playersMutex->player2_Y + PAD_SIZE_Y/2)
{
if (playersMutex->player2_Y <= SCREEN_SIZE_Y - PAD_SIZE_Y - PLAYER2_PAD_SPEED -1) playersMutex->player2_Y += PLAYER2_PAD_SPEED;
else playersMutex->player2_Y = SCREEN_SIZE_Y - PAD_SIZE_Y - 1;
} else if(event.type == ClockEventTypeTick) {
if(playersMutex->ball_X + BALL_SIZE / 2 <= SCREEN_SIZE_X * 0.35 &&
playersMutex->ball_X_direction == 0) {
if(playersMutex->ball_Y + BALL_SIZE / 2 <
playersMutex->player2_Y + PAD_SIZE_Y / 2) {
if(playersMutex->player2_Y >= 1 + PLAYER2_PAD_SPEED)
playersMutex->player2_Y -= PLAYER2_PAD_SPEED;
else
playersMutex->player2_Y = 1;
} else if(
playersMutex->ball_Y + BALL_SIZE / 2 >
playersMutex->player2_Y + PAD_SIZE_Y / 2) {
if(playersMutex->player2_Y <=
SCREEN_SIZE_Y - PAD_SIZE_Y - PLAYER2_PAD_SPEED - 1)
playersMutex->player2_Y += PLAYER2_PAD_SPEED;
else
playersMutex->player2_Y = SCREEN_SIZE_Y - PAD_SIZE_Y - 1;
}
}
uint8_t ball_corner_X[4] = {playersMutex->ball_X, playersMutex->ball_X + BALL_SIZE, playersMutex->ball_X + BALL_SIZE, playersMutex->ball_X};
uint8_t ball_corner_Y[4] = {playersMutex->ball_Y, playersMutex->ball_Y, playersMutex->ball_Y + BALL_SIZE, playersMutex->ball_Y + BALL_SIZE};
uint8_t ball_corner_X[4] = {
playersMutex->ball_X,
playersMutex->ball_X + BALL_SIZE,
playersMutex->ball_X + BALL_SIZE,
playersMutex->ball_X};
uint8_t ball_corner_Y[4] = {
playersMutex->ball_Y,
playersMutex->ball_Y,
playersMutex->ball_Y + BALL_SIZE,
playersMutex->ball_Y + BALL_SIZE};
bool insidePlayer1 = false, insidePlayer2 = false;
for (int i=0;i<4;i++)
{
if (insidePad(ball_corner_X[i], ball_corner_Y[i], playersMutex->player1_X, playersMutex->player1_Y) == true)
{
for(int i = 0; i < 4; i++) {
if(insidePad(
ball_corner_X[i],
ball_corner_Y[i],
playersMutex->player1_X,
playersMutex->player1_Y) == true) {
insidePlayer1 = true;
break;
}
if (insidePad(ball_corner_X[i], ball_corner_Y[i], playersMutex->player2_X, playersMutex->player2_Y) == true)
{
if(insidePad(
ball_corner_X[i],
ball_corner_Y[i],
playersMutex->player2_X,
playersMutex->player2_Y) == true) {
insidePlayer2 = true;
break;
}
}
if (insidePlayer1 == true)
{
if(insidePlayer1 == true) {
playersMutex->ball_X_direction = 0;
playersMutex->ball_X -= playersMutex->ball_X_speed;
playersMutex->ball_X_speed = changeSpeed();
playersMutex->ball_Y_speed = changeSpeed();
}
else if (insidePlayer2 == true)
{
} else if(insidePlayer2 == true) {
playersMutex->ball_X_direction = 1;
playersMutex->ball_X += playersMutex->ball_X_speed;
playersMutex->ball_X_speed = changeSpeed();
playersMutex->ball_Y_speed = changeSpeed();
}
else
{
if (playersMutex->ball_X_direction == 1)
{
if (playersMutex->ball_X <= SCREEN_SIZE_X - BALL_SIZE - 1 - playersMutex->ball_X_speed)
{
} else {
if(playersMutex->ball_X_direction == 1) {
if(playersMutex->ball_X <=
SCREEN_SIZE_X - BALL_SIZE - 1 - playersMutex->ball_X_speed) {
playersMutex->ball_X += playersMutex->ball_X_speed;
}
else
{
playersMutex->ball_X = SCREEN_SIZE_X/2 - BALL_SIZE/2;
playersMutex->ball_Y = SCREEN_SIZE_Y/2 - BALL_SIZE/2;
} else {
playersMutex->ball_X = SCREEN_SIZE_X / 2 - BALL_SIZE / 2;
playersMutex->ball_Y = SCREEN_SIZE_Y / 2 - BALL_SIZE / 2;
playersMutex->ball_X_speed = 1;
playersMutex->ball_Y_speed = 1;
playersMutex->ball_X_direction = 0;
playersMutex->player2_score++;
}
}
else
{
if (playersMutex->ball_X >= 1 + playersMutex->ball_X_speed)
{
} else {
if(playersMutex->ball_X >= 1 + playersMutex->ball_X_speed) {
playersMutex->ball_X -= playersMutex->ball_X_speed;
}
else
{
playersMutex->ball_X = SCREEN_SIZE_X/2 - BALL_SIZE/2;
playersMutex->ball_Y = SCREEN_SIZE_Y/2 - BALL_SIZE/2;
} else {
playersMutex->ball_X = SCREEN_SIZE_X / 2 - BALL_SIZE / 2;
playersMutex->ball_Y = SCREEN_SIZE_Y / 2 - BALL_SIZE / 2;
playersMutex->ball_X_speed = 1;
playersMutex->ball_Y_speed = 1;
playersMutex->ball_X_direction = 1;
@@ -241,28 +240,20 @@ int32_t flipper_pong_app()
}
}
if (playersMutex->ball_Y_direction == 1)
{
if (playersMutex->ball_Y <= SCREEN_SIZE_Y - BALL_SIZE - 1 - playersMutex->ball_Y_speed)
{
if(playersMutex->ball_Y_direction == 1) {
if(playersMutex->ball_Y <=
SCREEN_SIZE_Y - BALL_SIZE - 1 - playersMutex->ball_Y_speed) {
playersMutex->ball_Y += playersMutex->ball_Y_speed;
}
else
{
} else {
playersMutex->ball_Y = SCREEN_SIZE_Y - BALL_SIZE - 1;
playersMutex->ball_X_speed = changeSpeed();
playersMutex->ball_Y_speed = changeSpeed();
playersMutex->ball_Y_direction = 0;
}
}
else
{
if (playersMutex->ball_Y >= 1 + playersMutex->ball_Y_speed)
{
} else {
if(playersMutex->ball_Y >= 1 + playersMutex->ball_Y_speed) {
playersMutex->ball_Y -= playersMutex->ball_Y_speed;
}
else
{
} else {
playersMutex->ball_Y = 1;
playersMutex->ball_X_speed = changeSpeed();
playersMutex->ball_Y_speed = changeSpeed();
+30 -3
View File
@@ -6,11 +6,25 @@ the car keys), the curious person is left wondering what the device is
sending at all. Using ProtoView she or he can visualize the high and low pulses
like in the example image below (showing a Volkswagen key in 2FSK):
![ProtoView screenshot](/images/ProtoViewSignal.jpg)
![ProtoView screenshot raw signal](/images/protoview_1.jpg)
This is often enough to make an initial idea about the encoding used
and if the selected modulation is correct.
Other than that, ProtoView is able to decode a few interesting protocols:
* TPMS sensors: Renault, Toyota, Schrader, Citroen, Ford.
* Microchip HSC200/300/301 Keeloq protocol.
* Oregon thermometer protocol 2.
* PT2262, SC5262 based remotes.
* ... more will be implemented soon, hopefully. Send PRs :)
![ProtoView screenshot Renault TPMS data](/images/protoview_2.jpg)
The app implements a framework that makes adding and experimenting with new
protocols very simple. Check the `protocols` directory to see how the
API works.
The secondary goal of ProtoView is to provide a somewhat-documented application
for the Flipper (even if ProtoView is a pretty atypical application: doesn't make use of the standard widgets and other abstractions provded by the framework).
Many apps dealing with the *subghz subsystem* (the Flipper
@@ -40,7 +54,7 @@ encodings are somewhat self-clocked, so they tend to have just two or
three classes of pulse lengths.
However often pulses of the same theoretical
length have slightly different lenghts in the case of high and low level
length have slightly different lengths in the case of high and low level
(RF on or off), so we classify them separately for robustness.
# Usage
@@ -55,6 +69,10 @@ Under the detected sequence, you will see a small triangle marking a
specific sample. This mark means that the sequence looked coherent up
to that point, and starting from there it could be just noise.
If the protocol is decoded, the bottom-left corner of the screen
will show the name of the protocol, and going in the next screen
with the right arrow will show information about the decoded signal.
In the bottom-right corner the application displays an amount of time
in microseconds. This is the average length of the shortest pulse length
detected among the three classes. Usually the *data rate* of the protocol
@@ -67,7 +85,8 @@ Things to investigate:
* Many cheap remotes (gate openers, remotes, ...) are on the 433.92Mhz or nearby and use OOK modulation.
* Weather stations are often too in the 433.92Mhz OOK.
* For car keys, try 443.92 OOK650 and 868.35 Mhz in OOK or 2FSK.
* For car keys, try 433.92 OOK650 and 868.35 Mhz in OOK or 2FSK.
* For TPMS try 433.92 in TPMS modulation (FSK optimized for these signals).
# Installing the app from source
@@ -101,3 +120,11 @@ The code is released under the BSD license.
# Disclaimer
This application is only provided as an educational tool. The author is not liable in case the application is used to reverse engineer protocols protected by IP or for any other illegal purpose.
# Credits
A big thank you to the RTL433 author, [Benjamin Larsson](https://github.com/merbanan). I used the code and tools he developed in many ways:
* To capture TPMS data with rtl433 and save to a file, to later play the IQ files and speedup the development.
* As a sourve of documentation for protocols.
* As an awesome way to visualize and understand protocols, via [these great web tools](https://triq.org/).
* To have tons of fun with RTLSDR in general, now and in the past.
+11 -17
View File
@@ -1,20 +1,14 @@
Core improvements
=================
- Detection of non Manchester and non RZ encoded signals. Not sure if there are any signals that are not self clocked widely used in RF. Note that the current approach already detects encodings using short high + long low and long high + short low to encode 0 and 1. In addition to the current classifier, it is possible to add one that checks for a sequence of pulses that are all multiples of some base length. This should detect, for instance, even NRZ encodings where 1 and 0 are just clocked as they are.
- Views on-enter on-exit.
Features
========
- Help screen (with press ok for next page).
- Detect the line code used and try to decode the message as hex dump.
- Pressing right/left you browse different modes:
* Current best signal pulse classes.
* Raw square wave display. Central button freezes and resumes (toggle). When frozen we display "paused" (inverted) on the low part of the screen.
Screens sequence (user can navigate with <- and ->):
(default)
[settings] <> [freq] <> [pulses view] <> [raw square view] <> [signal info]
- Decoders should declare the short pulse duration range, so that
only matching decoders will be called. This may also be useful for
modulations. If a signal is only OOK, does not make much sense to
call it for samples obtained in FSK.
- More protocols, especially TPMS and other stuff not supported right now
by the Flipper.
- CC1101 synchronous mode with protocol hopping?
- Protocols decoded can register actions, for instance to generate
sub files with modified signal and so forth.
- Optimize memory usage storing raw samples in a bitfield: 15 bits
duration, 1 bit level.
+38 -10
View File
@@ -89,6 +89,12 @@ static void app_switch_view(ProtoViewApp *app, SwitchViewDirection dir) {
/* Call the enter/exit view callbacks if needed. */
if (old == ViewDirectSampling) view_exit_direct_sampling(app);
if (new == ViewDirectSampling) view_enter_direct_sampling(app);
/* The frequency/modulation settings are actually a single view:
* as long as the user stays between the two modes of this view we
* don't need to call the exit-view callback. */
if ((old == ViewFrequencySettings && new != ViewModulationSettings) ||
(old == ViewModulationSettings && new != ViewFrequencySettings))
view_exit_settings(app);
}
/* Allocate the application state and initialize a number of stuff.
@@ -112,9 +118,11 @@ ProtoViewApp* protoview_app_alloc() {
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
app->current_view = ViewRawPulses;
app->direct_sampling_enabled = false;
// Signal found and visualization defaults
app->signal_bestlen = 0;
app->signal_last_scan_idx = 0;
app->signal_decoded = false;
app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE;
app->signal_offset = 0;
@@ -123,20 +131,24 @@ ProtoViewApp* protoview_app_alloc() {
app->txrx = malloc(sizeof(ProtoViewTxRx));
/* Setup rx worker and environment. */
app->txrx->freq_mod_changed = false;
app->txrx->debug_timer_sampling = false;
app->txrx->last_g0_change_time = DWT->CYCCNT;
app->txrx->last_g0_value = false;
app->txrx->worker = subghz_worker_alloc();
#ifdef PROTOVIEW_DISABLE_SUBGHZ_FILTER
app->txrx->worker->filter_running = 0;
#endif
app->txrx->environment = subghz_environment_alloc();
subghz_environment_set_protocol_registry(
app->txrx->environment, (void*)&protoview_protocol_registry);
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
app->txrx->receiver =
subghz_receiver_alloc_init(app->txrx->environment);
subghz_receiver_set_filter(app->txrx->receiver,
SubGhzProtocolFlag_Decodable);
subghz_worker_set_overrun_callback(
app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
app->txrx->worker,
(SubGhzWorkerOverrunCallback)subghz_receiver_reset);
subghz_worker_set_pair_callback(
app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
subghz_worker_set_context(app->txrx->worker, app->txrx->receiver);
@@ -171,9 +183,11 @@ void protoview_app_free(ProtoViewApp *app) {
subghz_setting_free(app->setting);
// Worker stuff.
subghz_receiver_free(app->txrx->receiver);
subghz_environment_free(app->txrx->environment);
subghz_worker_free(app->txrx->worker);
if (!app->txrx->debug_timer_sampling) {
subghz_receiver_free(app->txrx->receiver);
subghz_environment_free(app->txrx->environment);
subghz_worker_free(app->txrx->worker);
}
free(app->txrx);
// Raw samples buffers.
@@ -189,6 +203,20 @@ void protoview_app_free(ProtoViewApp *app) {
* function is to scan for signals and set DetectedSamples. */
static void timer_callback(void *ctx) {
ProtoViewApp *app = ctx;
uint32_t delta, lastidx = app->signal_last_scan_idx;
/* scan_for_signal(), called by this function, deals with a
* circular buffer. To never miss anything, even if a signal spawns
* cross-boundaries, it is enough if we scan each time the buffer fills
* for 50% more compared to the last scan. Thanks to this check we
* can avoid scanning too many times to just find the same data. */
if (lastidx < RawSamples->idx) {
delta = RawSamples->idx - lastidx;
} else {
delta = RawSamples->total - lastidx + RawSamples->idx;
}
if (delta < RawSamples->total/2) return;
app->signal_last_scan_idx = RawSamples->idx;
scan_for_signal(app);
}
@@ -198,7 +226,7 @@ int32_t protoview_app_entry(void* p) {
/* Create a timer. We do data analysis in the callback. */
FuriTimer *timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, app);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
/* Start listening to signals immediately. */
radio_begin(app);
+24 -1
View File
@@ -65,10 +65,21 @@ extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */
* It receives data and we get our protocol "feed" callback called
* with the level (1 or 0) and duration. */
struct ProtoViewTxRx {
bool freq_mod_changed; /* The user changed frequency and/or modulation
from the interface. There is to restart the
radio with the right parameters. */
SubGhzWorker* worker; /* Our background worker. */
SubGhzEnvironment* environment;
SubGhzReceiver* receiver;
TxRxState txrx_state; /* Receiving, idle or sleeping? */
/* Timer sampling mode state. */
bool debug_timer_sampling; /* Read data from GDO0 in a busy loop. Only
for testing. */
uint32_t last_g0_change_time; /* Last high->low (or reverse) switch. */
bool last_g0_value; /* Current value (high or low): we are
checking the duration in the timer
handler. */
};
typedef struct ProtoViewTxRx ProtoViewTxRx;
@@ -85,6 +96,7 @@ typedef struct ProtoViewMsgInfo {
char info1[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 1. */
char info2[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 2. */
char info3[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 3. */
char info4[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 4. */
uint64_t len; /* Bits consumed from the stream. */
} ProtoViewMsgInfo;
@@ -103,8 +115,12 @@ struct ProtoViewApp {
/* Generic app state. */
int running; /* Once false exists the app. */
uint32_t signal_bestlen; /* Longest coherent signal observed so far. */
uint32_t signal_last_scan_idx; /* Index of the buffer last time we
performed the scan. */
bool signal_decoded; /* Was the current signal decoded? */
ProtoViewMsgInfo signal_info; /* Decoded message, if signal_decoded true. */
bool direct_sampling_enabled; /* This special view needs an explicit
acknowledge to work. */
/* Raw view apps state. */
uint32_t us_scale; /* microseconds per pixel. */
@@ -136,6 +152,8 @@ uint32_t radio_rx(ProtoViewApp* app);
void radio_idle(ProtoViewApp* app);
void radio_rx_end(ProtoViewApp* app);
void radio_sleep(ProtoViewApp* app);
void raw_sampling_worker_start(ProtoViewApp *app);
void raw_sampling_worker_stop(ProtoViewApp *app);
/* signal.c */
uint32_t duration_delta(uint32_t a, uint32_t b);
@@ -144,10 +162,11 @@ void scan_for_signal(ProtoViewApp *app);
bool bitmap_get(uint8_t *b, uint32_t blen, uint32_t bitpos);
void bitmap_set(uint8_t *b, uint32_t blen, uint32_t bitpos, bool val);
void bitmap_set_pattern(uint8_t *b, uint32_t blen, const char *pat);
void bitmap_invert_bytes_bits(uint8_t *p, uint32_t len);
void bitmap_reverse_bytes(uint8_t *p, uint32_t len);
bool bitmap_match_bits(uint8_t *b, uint32_t blen, uint32_t bitpos, const char *bits);
uint32_t bitmap_seek_bits(uint8_t *b, uint32_t blen, uint32_t startpos, uint32_t maxbits, const char *bits);
uint32_t convert_from_line_code(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t offset, const char *zero_pattern, const char *one_pattern);
uint32_t convert_from_diff_manchester(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t off, bool previous);
/* view_*.c */
void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app);
@@ -160,6 +179,10 @@ void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app);
void process_input_direct_sampling(ProtoViewApp *app, InputEvent input);
void view_enter_direct_sampling(ProtoViewApp *app);
void view_exit_direct_sampling(ProtoViewApp *app);
void view_exit_settings(ProtoViewApp *app);
/* ui.c */
void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color);
/* crc.c */
uint8_t crc8(const uint8_t *data, size_t len, uint8_t init, uint8_t poly);
+6 -8
View File
@@ -29,8 +29,7 @@ void raw_samples_reset(RawSamplesBuffer *s) {
s->total = RAW_SAMPLES_NUM;
s->idx = 0;
s->short_pulse_dur = 0;
memset(s->level,0,sizeof(s->level));
memset(s->dur,0,sizeof(s->dur));
memset(s->samples,0,sizeof(s->samples));
furi_mutex_release(s->mutex);
}
@@ -43,8 +42,8 @@ void raw_samples_center(RawSamplesBuffer *s, uint32_t offset) {
/* Add the specified sample in the circular buffer. */
void raw_samples_add(RawSamplesBuffer *s, bool level, uint32_t dur) {
furi_mutex_acquire(s->mutex,FuriWaitForever);
s->level[s->idx] = level;
s->dur[s->idx] = dur;
s->samples[s->idx].level = level;
s->samples[s->idx].dur = dur;
s->idx = (s->idx+1) % RAW_SAMPLES_NUM;
furi_mutex_release(s->mutex);
}
@@ -55,8 +54,8 @@ void raw_samples_get(RawSamplesBuffer *s, uint32_t idx, bool *level, uint32_t *d
{
furi_mutex_acquire(s->mutex,FuriWaitForever);
idx = (s->idx + idx) % RAW_SAMPLES_NUM;
*level = s->level[idx];
*dur = s->dur[idx];
*level = s->samples[idx].level;
*dur = s->samples[idx].dur;
furi_mutex_release(s->mutex);
}
@@ -66,8 +65,7 @@ void raw_samples_copy(RawSamplesBuffer *dst, RawSamplesBuffer *src) {
furi_mutex_acquire(dst->mutex,FuriWaitForever);
dst->idx = src->idx;
dst->short_pulse_dur = src->short_pulse_dur;
memcpy(dst->level,src->level,sizeof(dst->level));
memcpy(dst->dur,src->dur,sizeof(dst->dur));
memcpy(dst->samples,src->samples,sizeof(dst->samples));
furi_mutex_release(src->mutex);
furi_mutex_release(dst->mutex);
}
+4 -3
View File
@@ -7,11 +7,12 @@
#define RAW_SAMPLES_NUM 2048 /* Use a power of two: we take the modulo
of the index quite often to normalize inside
the range, and division is slow. */
typedef struct RawSamplesBuffer {
FuriMutex *mutex;
uint8_t level[RAW_SAMPLES_NUM];
uint32_t dur[RAW_SAMPLES_NUM];
struct {
uint16_t level:1;
uint16_t dur:15;
} samples[RAW_SAMPLES_NUM];
uint32_t idx; /* Current idx (next to write). */
uint32_t total; /* Total samples: same as RAW_SAMPLES_NUM, we provide
this field for a cleaner interface with the user, but
+76 -7
View File
@@ -5,6 +5,12 @@
#include "custom_presets.h"
#include <flipper_format/flipper_format_i.h>
#include <furi_hal_rtc.h>
#include <furi_hal_spi.h>
#include <furi_hal_interrupt.h>
void raw_sampling_worker_start(ProtoViewApp *app);
void raw_sampling_worker_stop(ProtoViewApp *app);
ProtoViewModulation ProtoViewModulations[] = {
{"OOK 650Khz", FuriHalSubGhzPresetOok650Async, NULL},
@@ -13,8 +19,10 @@ ProtoViewModulation ProtoViewModulations[] = {
{"2FSK 47.6Khz", FuriHalSubGhzPreset2FSKDev476Async, NULL},
{"MSK", FuriHalSubGhzPresetMSK99_97KbAsync, NULL},
{"GFSK", FuriHalSubGhzPresetGFSK9_99KbAsync, NULL},
{"TPMS 1 (FSK)", 0, (uint8_t*)protoview_subghz_tpms1_async_regs},
{"TPMS 2 (FSK)", 0, (uint8_t*)protoview_subghz_tpms2_async_regs},
{"TPMS 1 (FSK)", 0, (uint8_t*)protoview_subghz_tpms1_fsk_async_regs},
{"TPMS 2 (OOK)", 0, (uint8_t*)protoview_subghz_tpms2_ook_async_regs},
{"TPMS 3 (FSK)", 0, (uint8_t*)protoview_subghz_tpms3_fsk_async_regs},
{"TPMS 4 (FSK)", 0, (uint8_t*)protoview_subghz_tpms4_fsk_async_regs},
{NULL, 0, NULL} /* End of list sentinel. */
};
@@ -53,9 +61,14 @@ uint32_t radio_rx(ProtoViewApp* app) {
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_flush_rx();
furi_hal_subghz_rx();
if (!app->txrx->debug_timer_sampling) {
furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker);
subghz_worker_start(app->txrx->worker);
furi_hal_subghz_start_async_rx(subghz_worker_rx_callback,
app->txrx->worker);
subghz_worker_start(app->txrx->worker);
} else {
raw_sampling_worker_start(app);
}
app->txrx->txrx_state = TxRxStateRx;
return value;
}
@@ -64,9 +77,13 @@ uint32_t radio_rx(ProtoViewApp* app) {
void radio_rx_end(ProtoViewApp* app) {
furi_assert(app);
if (app->txrx->txrx_state == TxRxStateRx) {
if(subghz_worker_is_running(app->txrx->worker)) {
subghz_worker_stop(app->txrx->worker);
furi_hal_subghz_stop_async_rx();
if (!app->txrx->debug_timer_sampling) {
if(subghz_worker_is_running(app->txrx->worker)) {
subghz_worker_stop(app->txrx->worker);
furi_hal_subghz_stop_async_rx();
}
} else {
raw_sampling_worker_stop(app);
}
}
furi_hal_subghz_idle();
@@ -84,3 +101,55 @@ void radio_sleep(ProtoViewApp* app) {
furi_hal_subghz_sleep();
app->txrx->txrx_state = TxRxStateSleep;
}
/* ============================= Raw sampling mode =============================
* This is a special mode that uses a high frequency timer to sample the
* CC1101 pin directly. It's useful for debugging purposes when we want
* to get the raw data from the chip and completely bypass the subghz
* Flipper system.
* ===========================================================================*/
void protoview_timer_isr(void *ctx) {
ProtoViewApp *app = ctx;
bool level = furi_hal_gpio_read(&gpio_cc1101_g0);
if (app->txrx->last_g0_value != level) {
uint32_t now = DWT->CYCCNT;
uint32_t dur = now - app->txrx->last_g0_change_time;
dur /= furi_hal_cortex_instructions_per_microsecond();
if (dur > 15000) dur = 15000;
raw_samples_add(RawSamples, app->txrx->last_g0_value, dur);
app->txrx->last_g0_value = level;
app->txrx->last_g0_change_time = now;
}
LL_TIM_ClearFlag_UPDATE(TIM2);
}
void raw_sampling_worker_start(ProtoViewApp *app) {
UNUSED(app);
LL_TIM_InitTypeDef tim_init = {
.Prescaler = 63, /* CPU frequency is ~64Mhz. */
.CounterMode = LL_TIM_COUNTERMODE_UP,
.Autoreload = 5, /* Sample every 5 us */
};
LL_TIM_Init(TIM2, &tim_init);
LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_DisableCounter(TIM2);
LL_TIM_SetCounter(TIM2, 0);
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, protoview_timer_isr, app);
LL_TIM_EnableIT_UPDATE(TIM2);
LL_TIM_EnableCounter(TIM2);
FURI_LOG_E(TAG, "Timer enabled");
}
void raw_sampling_worker_stop(ProtoViewApp *app) {
UNUSED(app);
FURI_CRITICAL_ENTER();
LL_TIM_DisableCounter(TIM2);
LL_TIM_DisableIT_UPDATE(TIM2);
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL);
LL_TIM_DeInit(TIM2);
FURI_CRITICAL_EXIT();
}
@@ -1,6 +1,6 @@
App(
appid="protoview",
name="Protocols visualizer",
name="ProtoView",
apptype=FlipperAppType.EXTERNAL,
entry_point="protoview_app_entry",
cdefines=["APP_PROTOVIEW"],
@@ -0,0 +1,4 @@
#!/bin/sh
BINPATH="/Users/antirez/hack/flipper/official/build/f7-firmware-D/.extapps/protoview.fap"
cp $BINPATH .
git commit -a -m 'Binary file updated.'
+20
View File
@@ -0,0 +1,20 @@
#include <stdint.h>
#include <stddef.h>
/* CRC8 with the specified initialization value 'init' and
* polynomial 'poly'. */
uint8_t crc8(const uint8_t *data, size_t len, uint8_t init, uint8_t poly)
{
uint8_t crc = init;
size_t i, j;
for (i = 0; i < len; i++) {
crc ^= data[i];
for (j = 0; j < 8; j++) {
if ((crc & 0x80) != 0)
crc = (uint8_t)((crc << 1) ^ poly);
else
crc <<= 1;
}
}
return crc;
}
+119 -7
View File
@@ -1,8 +1,8 @@
#include <cc1101.h>
/* This is how to configure registers MDMCFG3 and MDMCFG4.
/* ========================== DATA RATE SETTINGS ===============================
*
* Data rate kBaud setting:
* This is how to configure registers MDMCFG3 and MDMCFG4.
*
* MDMCFG3 is the data rate mantissa, the exponent is in MDMCFG4,
* last 4 bits of the register.
@@ -11,10 +11,12 @@
*
* ((256+MDMCFG3)*(2^MDMCFG4:0..3bits)) / 2^28 * 26000000.
*
* For instance for the default values of MDMCFG3 (34) and MDMCFG4 (12):
* For instance for the default values of MDMCFG3[0..3] (34) and MDMCFG4 (12):
*
* ((256+34)*(2^12))/(2^28)*26000000 = 115051.2688000000, that is 115KBaud
*
* ============================ BANDWIDTH FILTER ===============================
*
* Bandwidth filter setting:
*
* BW filter as just 16 possibilities depending on how the first nibble
@@ -38,10 +40,29 @@
* d 82 khz
* e 68 khz
* f 58 khz
*
* ============================== FSK DEVIATION ================================
*
* FSK deviation is controlled by the DEVIATION register. In Ruby:
*
* dev = (26000000.0/2**17)*(8+(deviation&7))*(2**(deviation>>4&7))
*
* deviation&7 (last three bits) is the deviation mantissa, while
* deviation>>4&7 (bits 6,5,4) are the exponent.
*
* Deviations values according to certain configuration of DEVIATION:
*
* 0x04 -> 2.380371 kHz
* 0x24 -> 9.521484 kHz
* 0x34 -> 19.042969 Khz
* 0x40 -> 25.390625 Khz
* 0x43 -> 34.912109 Khz
* 0x45 -> 41.259765 Khz
* 0x47 -> 47.607422 kHz
*/
/* 20 KBaud, 2FSK, 28.56 kHz deviation, 325 Khz bandwidth filter. */
static uint8_t protoview_subghz_tpms1_async_regs[][2] = {
static uint8_t protoview_subghz_tpms1_fsk_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
@@ -85,8 +106,55 @@ static uint8_t protoview_subghz_tpms1_async_regs[][2] = {
{0, 0},
};
/* 40 KBaud, 2FSK, 19 kHz deviation, 102 Khz bandwidth filter. */
static uint8_t protoview_subghz_tpms2_async_regs[][2] = {
/* This is like the default Flipper OOK 640Khz bandwidth preset, but
* the bandwidth is changed to 10kBaud to accomodate TPMS frequency. */
static const uint8_t protoview_subghz_tpms2_ook_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* FIFO and internals */
{CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
// Modem Configuration
{CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync
{CC1101_MDMCFG3, 0x93}, // Data rate is 10kBaud
{CC1101_MDMCFG4, 0x18}, // Rx BW filter is 650.000kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0,
0x91}, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary
{CC1101_AGCCTRL1,
0x0}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
{CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1]
{CC1101_FREND1, 0xB6}, //
/* End */
{0, 0},
};
/* 40 KBaud, 2FSK, 28 kHz deviation, 270 Khz bandwidth filter. */
static uint8_t protoview_subghz_tpms3_fsk_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
@@ -103,7 +171,7 @@ static uint8_t protoview_subghz_tpms2_async_regs[][2] = {
{CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized). Other code reading TPMS uses GFSK, but should be the same when in RX mode.
{CC1101_MDMCFG3, 0x93}, // Data rate is 40kBaud
{CC1101_MDMCFG4, 0x6A}, // 6 = BW filter 270kHz, A = Data rate exp
{CC1101_DEVIATN, 0x41}, // Deviation 19.042 kHz
{CC1101_DEVIATN, 0x41}, // Deviation 28kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
@@ -130,3 +198,47 @@ static uint8_t protoview_subghz_tpms2_async_regs[][2] = {
{0, 0},
};
/* FSK 19k dev, 325 Khz filter, 20kBaud. Works well with Toyota. */
static uint8_t protoview_subghz_tpms4_fsk_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
{CC1101_PKTCTRL1, 0x04},
// // Modem Configuration
{CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02}, // 2 is the channel spacing exponet: not used
{CC1101_MDMCFG2, 0x10}, // GFSK without any other check
{CC1101_MDMCFG3, 0x93}, // Data rate is 20kBaud
{CC1101_MDMCFG4, 0x59}, // Rx bandwidth filter is 325 kHz
{CC1101_DEVIATN, 0x34}, // Deviation 19.04 Khz.
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0, 0x80},
{CC1101_AGCCTRL1, 0x58},
{CC1101_AGCCTRL2, 0x87},
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer
{CC1101_FREND1, 0x56},
/* End */
{0, 0},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

@@ -1,7 +1,7 @@
/* PT/SC remotes. Usually 443.92 Mhz OOK.
*
* This line code is used in many remotes such as Princeton chips
* named PT<number>, Silian Microelectronics SC5262 and others.
* named PT2262, Silian Microelectronics SC5262 and others.
* Basically every 4 pulsee represent a bit, where 1000 means 0, and
* 1110 means 1. Usually we can read 24 bits of data.
* In this specific implementation we check for a prelude that is
@@ -0,0 +1,87 @@
/* Microchip HCS200/HCS300/HSC301 KeeLoq, rolling code remotes.
*
* Usually 443.92 Mhz OOK, ~200us or ~400us pulse len, depending
* on the configuration.
*
* Preamble: 12 pairs of alternating pulse/gap.
* Sync: long gap of around 10 times the duration of the short-pulse.
* Data: pulse width encoded data. Each bit takes three cycles:
*
* 0 = 110
* 1 = 100
*
* There are a total of 66 bits transmitted.
* 0..31: 32 bits of encrypted rolling code.
* 32..59: Remote ID, 28 bits
* 60..63: Buttons pressed
* 64..64: Low battery if set
* 65..65: Always set to 1
*
* Bits in bytes are inverted: least significant bit is first.
* For some reason there is no checksum whatsoever, so we only decode
* if we find everything well formed.
*/
#include "../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
/* In the sync pattern, we require the 12 high/low pulses and at least
* half the gap we expect (5 pulses times, one is the final zero in the
* 24 symbols high/low sequence, then other 4). */
const char *sync_pattern = "101010101010101010101010" "0000";
uint8_t sync_len = 24+4;
if (numbits-sync_len+sync_len < 3*66) return false;
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
off += sync_len;
/* Now there is half the gap left, but we allow from 3 to 7, instead of 5
* symbols of gap, to avoid missing the signal for a matter of wrong
* timing. */
uint8_t gap_len = 0;
while(gap_len <= 7 && bitmap_get(bits,numbytes,off+gap_len) == 0)
gap_len++;
if (gap_len < 3 || gap_len > 7) return false;
off += gap_len;
FURI_LOG_E(TAG, "Keeloq preamble+sync found");
uint8_t raw[9] = {0};
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"110","100"); /* Pulse width modulation. */
FURI_LOG_E(TAG, "Keeloq decoded bits: %lu", decoded);
if (decoded < 66) return false; /* Require the full 66 bits. */
bitmap_reverse_bytes(raw,sizeof(raw)); /* Keeloq is LSB first. */
int buttons = raw[7]>>4;
int s3 = (buttons&1) != 0;
int s0 = (buttons&2) != 0;
int s1 = (buttons&4) != 0;
int s2 = (buttons&8) != 0;
int remote_id = ((raw[7]&0x0f) << 24) |
(raw[6] << 16) |
(raw[5] << 8) |
(raw[4] << 0);
int lowbat = raw[8]&0x80;
snprintf(info->name,sizeof(info->name),"%s","Keeloq remote");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7],raw[8]);
snprintf(info->info1,sizeof(info->info1),"Encrpyted %02X%02X%02X%02X",
raw[3],raw[2],raw[1],raw[0]);
snprintf(info->info2,sizeof(info->info2),"ID %08X", remote_id);
snprintf(info->info3,sizeof(info->info3),"s0-s3: %d%d%d%d",
s0,s1,s2,s3);
snprintf(info->info4,sizeof(info->info4),"Low battery? %s",
lowbat ? "yes" : "no");
return true;
}
ProtoViewDecoder KeeloqDecoder = {
"Keeloq", decode
};
@@ -1,6 +0,0 @@
11001100110011001100110011001100110011001100110011001100110 (Preamble)
10 01 01 10 10 01 01 10 (Sync)
01 10 10 01 10 01 10 01 01 10 10 01 01 10 01 10 10 01 01 10 10 01 10 01 10 01 10 01 10 01 10 01 01 10 10 01 10 01 10 01 01 10 01 10 01 10 01 10 01 10 01 10 10 01 01 10 01 10 10 01 10 01 10 01 10 01 10 01 01 10 10 01 10 01 01 10 01 10 10 01 01 10 10 01 10 01 10 01 10 01 10 01 10 01 11 0
We need to seek the following bytes: 01100110 01100110 10010110 10010110
0x66 0x66 96 96
@@ -0,0 +1,60 @@
/* Citroen TPMS. Usually 443.92 Mhz FSK.
*
* Preamble of ~14 high/low 52 us pulses
* Sync of high 100us pulse then 50us low
* Then Manchester bits, 10 bytes total.
* Simple XOR checksum. */
#include "../../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
/* We consider a preamble of 17 symbols. They are more, but the decoding
* is more likely to happen if we don't pretend to receive from the
* very start of the message. */
uint32_t sync_len = 17;
const char *sync_pattern = "10101010101010110";
if (numbits-sync_len < 8*10) return false; /* Expect 10 bytes. */
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Renault TPMS preamble+sync found");
off += sync_len; /* Skip preamble + sync. */
uint8_t raw[10];
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"01","10"); /* Manchester. */
FURI_LOG_E(TAG, "Citroen TPMS decoded bits: %lu", decoded);
if (decoded < 8*10) return false; /* Require the full 10 bytes. */
/* Check the CRC. It's a simple XOR of bytes 1-9, the first byte
* is not included. The meaning of the first byte is unknown and
* we don't display it. */
uint8_t crc = 0;
for (int j = 1; j < 10; j++) crc ^= raw[j];
if (crc != 0) return false; /* Require sane checksum. */
int repeat = raw[5] & 0xf;
float kpa = (float)raw[6]*1.364;
int temp = raw[7]-50;
int battery = raw[8]; /* This may be the battery. It's not clear. */
snprintf(info->name,sizeof(info->name),"%s","Citroen TPMS");
snprintf(info->raw,sizeof(info->raw),
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7],raw[8],raw[9]);
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X%02X",
raw[1],raw[2],raw[3],raw[4]);
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa);
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp);
snprintf(info->info4,sizeof(info->info4),"Repeat %d, Bat %d", repeat, battery);
return true;
}
ProtoViewDecoder CitroenTPMSDecoder = {
"Citroen TPMS", decode
};
@@ -0,0 +1,64 @@
/* Ford tires TPMS. Usually 443.92 Mhz FSK (in Europe).
*
* 52 us short pules
* Preamble: 0101010101010101010101010101
* Sync: 0110 (that is 52 us gap + 104 us pulse + 52 us gap)
* Data: 8 bytes Manchester encoded
* 01 = zero
* 10 = one
*/
#include "../../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
const char *sync_pattern = "010101010101" "0110";
uint8_t sync_len = 12+4; /* We just use 12 preamble symbols + sync. */
if (numbits-sync_len < 8*8) return false;
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Fort TPMS preamble+sync found");
off += sync_len; /* Skip preamble and sync. */
uint8_t raw[8];
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"01","10"); /* Manchester. */
FURI_LOG_E(TAG, "Ford TPMS decoded bits: %lu", decoded);
if (decoded < 8*8) return false; /* Require the full 8 bytes. */
/* CRC is just the sum of the first 7 bytes MOD 256. */
uint8_t crc = 0;
for (int j = 0; j < 7; j++) crc += raw[j];
if (crc != raw[7]) return false; /* Require sane CRC. */
float psi = 0.25 * (((raw[6]&0x20)<<3)|raw[4]);
/* Temperature apperas to be valid only if the most significant
* bit of the value is not set. Otherwise its meaning is unknown.
* Likely useful to alternatively send temperature or other info. */
int temp = raw[5] & 0x80 ? 0 : raw[5]-56;
int flags = raw[5] & 0x7f;
int car_moving = (raw[6] & 0x44) == 0x44;
snprintf(info->name,sizeof(info->name),"%s","Ford TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7]);
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3]);
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f psi", (double)psi);
if (temp)
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp);
else
snprintf(info->info3,sizeof(info->info3),"Flags %d", flags);
snprintf(info->info4,sizeof(info->info4),"Moving %s", car_moving ? "yes" : "no");
return true;
}
ProtoViewDecoder FordTPMSDecoder = {
"Ford TPMS", decode
};
@@ -1,16 +1,16 @@
/* Renault tires TPMS. Usually 443.92 Mhz FSK.
*
* Preamble + marshal-encoded bits. 9 Bytes in total if we don't
* count the preamble. */
* Preamble + sync + Manchester bits. ~48us short pulse.
* 9 Bytes in total not counting the preamble. */
#include "../app.h"
#include "../../app.h"
#define USE_TEST_VECTOR 0
static const char *test_vector =
"10101010" "10101010" "10101010" "10101001" // Preamble + sync.
"...01010101010101010110" // Preamble + sync
/* The following is marshal encoded, so each two characters are
* actaully one bit. 01 = 1, 10 = 0. */
/* The following is Marshal encoded, so each two characters are
* actaully one bit. 01 = 0, 10 = 1. */
"010110010110" // Flags.
"10011001101010011001" // Pressure, multiply by 0.75 to obtain kpa.
// 244 kpa here.
@@ -29,22 +29,23 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
numbits = strlen(test_vector);
}
if (numbits < 13*8) return false;
if (numbits-12 < 9*8) return false;
const char *sync_pattern = "10101010" "10101010" "10101010" "10101001";
const char *sync_pattern = "01010101010101010110";
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Renault TPMS preamble+sync found");
off += 32; /* Skip preamble. */
off += 20; /* Skip preamble. */
uint8_t raw[9];
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"10","01"); /* Manchester. */
"01","10"); /* Manchester. */
FURI_LOG_E(TAG, "Renault TPMS decoded bits: %lu", decoded);
if (decoded < 8*9) return false; /* Require the full 9 bytes. */
if (crc8(raw,8,0,7) != raw[8]) return false; /* Require sane CRC. */
float kpa = 0.75 *((uint32_t)((raw[0]&3)<<8) | raw[1]);
int temp = raw[2]-30;
@@ -53,8 +54,10 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7],raw[8]);
snprintf(info->info1,sizeof(info->info1),"Pressure %.2f kpa", (double)kpa);
snprintf(info->info2,sizeof(info->info2),"Temperature %d C", temp);
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X",
raw[3],raw[4],raw[5]);
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa);
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp);
return true;
}
@@ -0,0 +1,65 @@
/* Schrader TPMS. Usually 443.92 Mhz OOK, 120us pulse len.
*
* 500us high pulse + Preamble + Manchester coded bits where:
* 1 = 10
* 0 = 01
*
* 60 bits of data total (first 4 nibbles is the preamble, 0xF).
*
* Used in FIAT-Chrysler, Mercedes, ... */
#include "../../app.h"
#define USE_TEST_VECTOR 0
static const char *test_vector = "000000111101010101011010010110010110101001010110100110011001100101010101011010100110100110011010101010101010101010101010101010101010101010101010";
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
if (USE_TEST_VECTOR) { /* Test vector to check that decoding works. */
bitmap_set_pattern(bits,numbytes,test_vector);
numbits = strlen(test_vector);
}
if (numbits < 64) return false; /* Preamble + data. */
const char *sync_pattern = "1111010101" "01011010";
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Schrader TPMS gap+preamble found");
off += 10; /* Skip just the long pulse and the first 3 bits of sync, so
that we have the first byte of data with the sync nibble
0011 = 0x3. */
uint8_t raw[8];
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"01","10"); /* Manchester code. */
FURI_LOG_E(TAG, "Schrader TPMS decoded bits: %lu", decoded);
if (decoded < 64) return false; /* Require the full 8 bytes. */
raw[0] |= 0xf0; // Fix the preamble nibble for checksum computation.
uint8_t cksum = crc8(raw,sizeof(raw)-1,0xf0,0x7);
if (cksum != raw[7]) {
FURI_LOG_E(TAG, "Schrader TPMS checksum mismatch");
return false;
}
float kpa = (float)raw[5]*2.5;
int temp = raw[6]-50;
snprintf(info->name,sizeof(info->name),"%s","Schrader TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7]);
snprintf(info->info1,sizeof(info->info1),"Tire ID %01X%02X%02X%02X",
raw[1]&7,raw[2],raw[3],raw[4]); /* Only 28 bits of ID, not 32. */
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa);
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp);
return true;
}
ProtoViewDecoder SchraderTPMSDecoder = {
"Schrader TPMS", decode
};
@@ -0,0 +1,63 @@
/* Schrader variant EG53MA4 TPMS.
* Usually 443.92 Mhz OOK, 100us pulse len.
*
* Preamble: alternating pulse/gap, 100us.
* Sync (as pulses and gaps): "01100101", already part of the data stream
* (first nibble) corresponding to 0x4
*
* A total of 10 bytes payload, Manchester encoded.
*
* 0 = 01
* 1 = 10
*
* Used in certain Open cars and others.
*/
#include "../../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
const char *sync_pattern = "010101010101" "01100101";
uint8_t sync_len = 12+8; /* We just use 12 preamble symbols + sync. */
if (numbits-sync_len+8 < 8*10) return false;
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Schrader EG53MA4 TPMS preamble+sync found");
off += sync_len-8; /* Skip preamble, not sync that is part of the data. */
uint8_t raw[10];
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"01","10"); /* Manchester code. */
FURI_LOG_E(TAG, "Schrader EG53MA4 TPMS decoded bits: %lu", decoded);
if (decoded < 10*8) return false; /* Require the full 10 bytes. */
/* CRC is just all bytes added mod 256. */
uint8_t crc = 0;
for (int j = 0; j < 9; j++) crc += raw[j];
if (crc != raw[9]) return false; /* Require sane CRC. */
/* To convert the raw pressure to kPa, RTL433 uses 2.5, but is likely
* wrong. Searching on Google for users experimenting with the value
* reported, the value appears to be 2.75. */
float kpa = (float)raw[7]*2.75;
int temp_f = raw[8];
int temp_c = (temp_f-32)*5/9; /* Convert Fahrenheit to Celsius. */
snprintf(info->name,sizeof(info->name),"%s","Schrader EG53MA4 TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7],raw[8],raw[9]);
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X",
raw[4],raw[5],raw[6]); /* Only 28 bits of ID, not 32. */
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa);
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp_c);
return true;
}
ProtoViewDecoder SchraderEG53MA4TPMSDecoder = {
"Schrader EG53MA4 TPMS", decode
};
@@ -0,0 +1,77 @@
/* Toyota tires TPMS. Usually 443.92 Mhz FSK (In Europe).
*
* Preamble + sync + 64 bits of data. ~48us short pulse length.
*
* The preamble + sync is something like:
*
* 10101010101 (preamble) + 001111[1] (sync)
*
* Note: the final [1] means that sometimes it is four 1s, sometimes
* five, depending on the short pulse length detection and the exact
* duration of the high long pulse. After the sync, a differential
* Manchester encoded payload follows. However the Flipper's CC1101
* often can't decode correctly the initial alternating pattern 101010101,
* so what we do is to seek just the sync, that is "001111" or "0011111",
* however we now that it must be followed by one differenitally encoded
* bit, so we can use also the first symbol of data to force a more robust
* detection, and look for one of the following:
*
* [001111]00
* [0011111]00
* [001111]01
* [0011111]01
*/
#include "../../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
if (numbits-6 < 64*2) return false; /* Ask for 64 bit of data (each bit
is two symbols in the bitmap). */
char *sync[] = {
"00111100",
"001111100",
"00111101",
"001111101",
NULL
};
int j;
uint32_t off = 0;
for (j = 0; sync[j]; j++) {
off = bitmap_seek_bits(bits,numbytes,0,numbits,sync[j]);
if (off != BITMAP_SEEK_NOT_FOUND) {
off += strlen(sync[j])-2;
break;
}
}
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Toyota TPMS sync[%s] found", sync[j]);
uint8_t raw[9];
uint32_t decoded =
convert_from_diff_manchester(raw,sizeof(raw),bits,numbytes,off,true);
FURI_LOG_E(TAG, "Toyota TPMS decoded bits: %lu", decoded);
if (decoded < 8*9) return false; /* Require the full 8 bytes. */
if (crc8(raw,8,0x80,7) != raw[8]) return false; /* Require sane CRC. */
float kpa = (float)((raw[4]&0x7f)<<1 | raw[5]>>7) * 0.25 - 7;
int temp = ((raw[5]&0x7f)<<1 | raw[6]>>7) - 40;
snprintf(info->name,sizeof(info->name),"%s","Toyota TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7],raw[8]);
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3]);
snprintf(info->info1,sizeof(info->info1),"Pressure %.2f psi", (double)kpa);
snprintf(info->info2,sizeof(info->info2),"Temperature %d C", temp);
return true;
}
ProtoViewDecoder ToyotaTPMSDecoder = {
"Toyota TPMS", decode
};
+73 -28
View File
@@ -53,6 +53,7 @@ uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) {
bool level;
uint32_t dur;
raw_samples_get(s, j, &level, &dur);
if (dur < minlen || dur > maxlen) break; /* return. */
/* Let's see if it matches a class we already have or if we
@@ -70,7 +71,7 @@ uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) {
/* Is the difference in duration between this signal and
* the class we are inspecting less than a given percentage?
* If so, accept this signal. */
if (delta < classavg/8) { /* 100%/8 = 12%. */
if (delta < classavg/5) { /* 100%/5 = 20%. */
/* It is useful to compute the average of the class
* we are observing. We know how many samples we got so
* far, so we can recompute the average easily.
@@ -130,9 +131,9 @@ void scan_for_signal(ProtoViewApp *app) {
/* Try to seek on data that looks to have a regular high low high low
* pattern. */
uint32_t minlen = 13; /* Min run of coherent samples. Up to
12 samples it's very easy to mistake
noise for signal. */
uint32_t minlen = 18; /* Min run of coherent samples. With less
than a few samples it's very easy to
mistake noise for signal. */
ProtoViewMsgInfo *info = malloc(sizeof(ProtoViewMsgInfo));
uint32_t i = 0;
@@ -152,18 +153,25 @@ void scan_for_signal(ProtoViewApp *app) {
the signal in the loop. */
/* Accept this signal as the new signal if either it's longer
* than the previous one, or the previous one was unknown and
* this is decoded. */
if (thislen > app->signal_bestlen ||
(app->signal_decoded == false && decoded))
* than the previous undecoded one, or the previous one was
* unknown and this is decoded. */
if ((thislen > app->signal_bestlen && app->signal_decoded == false)
|| (app->signal_decoded == false && decoded))
{
app->signal_info = *info;
app->signal_bestlen = thislen;
app->signal_decoded = decoded;
raw_samples_copy(DetectedSamples,copy);
raw_samples_center(DetectedSamples,i);
FURI_LOG_E(TAG, "Displayed sample updated (%d samples %lu us)",
FURI_LOG_E(TAG, "===> Displayed sample updated (%d samples %lu us)",
(int)thislen, DetectedSamples->short_pulse_dur);
/* Adjust raw view scale if the signal has an high
* data rate. */
if (DetectedSamples->short_pulse_dur < 75)
app->us_scale = 10;
else if (DetectedSamples->short_pulse_dur < 145)
app->us_scale = 30;
}
}
i += thislen ? thislen : 1;
@@ -207,14 +215,19 @@ bool bitmap_get(uint8_t *b, uint32_t blen, uint32_t bitpos) {
return (b[byte] & (1<<bit)) != 0;
}
/* We decode bits assuming the first bit we receive is the LSB
* (see bitmap_set/get functions). Many devices send data
/* We decode bits assuming the first bit we receive is the MSB
* (see bitmap_set/get functions). Certain devices send data
* encoded in the reverse way. */
void bitmap_invert_bytes_bits(uint8_t *p, uint32_t len) {
for (uint32_t j = 0; j < len*8; j += 8) {
bool bits[8];
for (int i = 0; i < 8; i++) bits[i] = bitmap_get(p,len,j+i);
for (int i = 0; i < 8; i++) bitmap_set(p,len,j+i,bits[7-i]);
void bitmap_reverse_bytes(uint8_t *p, uint32_t len) {
for (uint32_t j = 0; j < len; j++) {
uint32_t b = p[j];
/* Step 1: swap the two nibbles: 12345678 -> 56781234 */
b = (b&0xf0)>>4 | (b&0x0f)<<4;
/* Step 2: swap adjacent pairs : 56781234 -> 78563412 */
b = (b&0xcc)>>2 | (b&0x33)<<2;
/* Step 3: swap adjacent bits : 78563412 -> 87654321 */
b = (b&0xaa)>>1 | (b&0x55)<<1;
p[j] = b;
}
}
@@ -317,11 +330,9 @@ uint32_t convert_signal_to_bits(uint8_t *b, uint32_t blen, RawSamplesBuffer *s,
/* This function converts the line code used to the final data representation.
* The representation is put inside 'buf', for up to 'buflen' bytes of total
* data. For instance in order to convert manchester I can use "10" and "01"
* as zero and one patterns. It is possible to use "?" inside patterns in
* order to skip certain bits. For instance certain devices encode data twice,
* with each bit encoded in manchester encoding and then in its reversed
* representation. In such a case I could use "10??" and "01??".
* data. For instance in order to convert manchester you can use "10" and "01"
* as zero and one patterns. However this function does not handle differential
* encodings. See below for convert_from_diff_manchester().
*
* The function returns the number of bits converted. It will stop as soon
* as it finds a pattern that does not match zero or one patterns, or when
@@ -350,17 +361,50 @@ uint32_t convert_from_line_code(uint8_t *buf, uint64_t buflen, uint8_t *bits, ui
return decoded;
}
/* Convert the differential Manchester code to bits. This is similar to
* convert_from_line_code() but specific for Manchester. The user must
* supply the value of the previous symbol before this stream, since
* in differential codings the next bits depend on the previous one.
*
* Parameters and return values are like convert_from_line_code(). */
uint32_t convert_from_diff_manchester(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t off, bool previous)
{
uint32_t decoded = 0;
len *= 8; /* Conver to bits. */
for (uint32_t j = off; j < len; j += 2) {
bool b0 = bitmap_get(bits,len,j);
bool b1 = bitmap_get(bits,len,j+1);
if (b0 == previous) break; /* Each new bit must switch value. */
bitmap_set(buf,buflen,decoded++,b0 == b1);
previous = b1;
if (decoded/8 == buflen) break; /* No space left on target buffer. */
}
return decoded;
}
/* Supported protocols go here, with the relevant implementation inside
* protocols/<name>.c */
extern ProtoViewDecoder Oregon2Decoder;
extern ProtoViewDecoder B4B1Decoder;
extern ProtoViewDecoder RenaultTPMSDecoder;
extern ProtoViewDecoder ToyotaTPMSDecoder;
extern ProtoViewDecoder SchraderTPMSDecoder;
extern ProtoViewDecoder SchraderEG53MA4TPMSDecoder;
extern ProtoViewDecoder CitroenTPMSDecoder;
extern ProtoViewDecoder FordTPMSDecoder;
extern ProtoViewDecoder KeeloqDecoder;
ProtoViewDecoder *Decoders[] = {
&Oregon2Decoder, /* Oregon sensors v2.1 protocol. */
&B4B1Decoder, /* PT, SC, ... 24 bits remotes. */
&RenaultTPMSDecoder, /* Renault TPMS. */
&Oregon2Decoder, /* Oregon sensors v2.1 protocol. */
&B4B1Decoder, /* PT, SC, ... 24 bits remotes. */
&RenaultTPMSDecoder, /* Renault TPMS. */
&ToyotaTPMSDecoder, /* Toyota TPMS. */
&SchraderTPMSDecoder, /* Schrader TPMS. */
&SchraderEG53MA4TPMSDecoder, /* Schrader EG53MA4 TPMS. */
&CitroenTPMSDecoder, /* Citroen TPMS. */
&FordTPMSDecoder, /* Ford TPMS. */
&KeeloqDecoder, /* Keeloq remote. */
NULL
};
@@ -378,12 +422,13 @@ bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info) {
uint32_t bitmap_bits_size = 4096*8;
uint32_t bitmap_size = bitmap_bits_size/8;
/* We call the decoders with an offset a few bits before the actual
/* We call the decoders with an offset a few samples before the actual
* signal detected and for a len of a few bits after its end. */
uint32_t before_after_bits = 2;
uint32_t before_samples = 20;
uint32_t after_samples = 100;
uint8_t *bitmap = malloc(bitmap_size);
uint32_t bits = convert_signal_to_bits(bitmap,bitmap_size,s,-before_after_bits,len+before_after_bits*2,s->short_pulse_dur);
uint32_t bits = convert_signal_to_bits(bitmap,bitmap_size,s,-before_samples,len+before_samples+after_samples,s->short_pulse_dur);
if (DEBUG_MSG) { /* Useful for debugging purposes. Don't remove. */
char *str = malloc(1024);
@@ -413,7 +458,7 @@ bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info) {
if (!decoded) {
FURI_LOG_E(TAG, "No decoding possible");
} else {
FURI_LOG_E(TAG, "Decoded %s, raw=%s info=[%s,%s,%s]", info->name, info->raw, info->info1, info->info2, info->info3);
FURI_LOG_E(TAG, "Decoded %s, raw=%s info=[%s,%s,%s,%s]", info->name, info->raw, info->info1, info->info2, info->info3, info->info4);
}
free(bitmap);
return decoded;
@@ -8,7 +8,18 @@
/* Read directly from the G0 CC1101 pin, and draw a black or white
* dot depending on the level. */
void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app) {
UNUSED(app);
if (!app->direct_sampling_enabled) {
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas,2,9,"Direct sampling is a special");
canvas_draw_str(canvas,2,18,"mode that displays the signal");
canvas_draw_str(canvas,2,27,"captured in real time. Like in");
canvas_draw_str(canvas,2,36,"a old CRT TV. It's very slow.");
canvas_draw_str(canvas,2,45,"Can crash your Flipper.");
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas,14,60,"To enable press OK");
return;
}
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 128; x++) {
bool level = furi_hal_gpio_read(&gpio_cc1101_g0);
@@ -16,31 +27,41 @@ void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app) {
/* Busy loop: this is a terrible approach as it blocks
* everything else, but for now it's the best we can do
* to obtain direct data with some spacing. */
uint32_t x = 500; while(x--);
uint32_t x = 250; while(x--);
}
}
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_with_border(canvas,40,60,"Direct sampling",
canvas_draw_str_with_border(canvas,36,60,"Direct sampling",
ColorWhite,ColorBlack);
}
/* Handle input */
void process_input_direct_sampling(ProtoViewApp *app, InputEvent input) {
UNUSED(app);
UNUSED(input);
if (input.type == InputTypePress && input.key == InputKeyOk) {
app->direct_sampling_enabled = !app->direct_sampling_enabled;
}
}
/* Enter view. Stop the subghz thread to prevent access as we read
* the CC1101 data directly. */
void view_enter_direct_sampling(ProtoViewApp *app) {
if (app->txrx->txrx_state == TxRxStateRx) {
if (app->txrx->txrx_state == TxRxStateRx &&
!app->txrx->debug_timer_sampling)
{
subghz_worker_stop(app->txrx->worker);
} else {
raw_sampling_worker_stop(app);
}
}
/* Exit view. Restore the subghz thread. */
void view_exit_direct_sampling(ProtoViewApp *app) {
if (app->txrx->txrx_state == TxRxStateRx) {
if (app->txrx->txrx_state == TxRxStateRx &&
!app->txrx->debug_timer_sampling)
{
subghz_worker_start(app->txrx->worker);
} else {
raw_sampling_worker_start(app);
}
app->direct_sampling_enabled = false;
}
+11 -10
View File
@@ -25,17 +25,18 @@ void render_view_info(Canvas *const canvas, ProtoViewApp *app) {
canvas_draw_str(canvas, 0, y, buf);
y += lineheight;
}
canvas_draw_str(canvas, 0, y, app->signal_info.info1);
y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info2);
y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info3);
y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info1); y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info2); y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info3); y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info4); y += lineheight;
}
/* Handle input for the settings view. */
/* Handle input for the info view. */
void process_input_info(ProtoViewApp *app, InputEvent input) {
UNUSED(app);
UNUSED(input);
return;
if (input.type == InputTypeShort) {
if (input.key == InputKeyOk) {
/* Reset the current sample to capture the next. */
reset_current_signal(app);
}
}
}
+29 -5
View File
@@ -20,6 +20,9 @@ void render_view_settings(Canvas *const canvas, ProtoViewApp *app) {
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas,10,61,"Use up and down to modify");
if (app->txrx->debug_timer_sampling)
canvas_draw_str(canvas,3,52,"(DEBUG timer sampling is ON)");
/* Show frequency. We can use big numbers font since it's just a number. */
if (app->current_view == ViewFrequencySettings) {
char buf[16];
@@ -40,6 +43,18 @@ void process_input_settings(ProtoViewApp *app, InputEvent input) {
* modulation. */
app->frequency = subghz_setting_get_default_frequency(app->setting);
app->modulation = 0;
} else if (0 && input.type == InputTypeLong && input.key == InputKeyDown) {
/* Long pressing to down switches between normal and debug
* timer sampling mode. NOTE: this feature is disabled for users,
* only useful for devs (if useful at all). */
/* We have to stop the previous sampling system. */
radio_rx_end(app);
/* Then switch mode and start the new one. */
app->txrx->debug_timer_sampling = !app->txrx->debug_timer_sampling;
radio_begin(app);
radio_rx(app);
} else if (input.type == InputTypePress &&
(input.key != InputKeyDown || input.key != InputKeyUp))
{
@@ -85,9 +100,18 @@ void process_input_settings(ProtoViewApp *app, InputEvent input) {
return;
}
/* Apply changes. */
FURI_LOG_E(TAG, "Setting view, setting frequency/modulation to %lu %s", app->frequency, ProtoViewModulations[app->modulation].name);
radio_rx_end(app);
radio_begin(app);
radio_rx(app);
/* Apply changes when switching to other views. */
app->txrx->freq_mod_changed = true;
}
/* When the user switches to some other view, if they changed the parameters
* we need to restart the radio with the right frequency and modulation. */
void view_exit_settings(ProtoViewApp *app) {
if (app->txrx->freq_mod_changed) {
FURI_LOG_E(TAG, "Setting view, setting frequency/modulation to %lu %s", app->frequency, ProtoViewModulations[app->modulation].name);
radio_rx_end(app);
radio_begin(app);
radio_rx(app);
app->txrx->freq_mod_changed = false;
}
}
File diff suppressed because it is too large Load Diff
+27 -27
View File
@@ -33,7 +33,6 @@
* See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp
*/
#ifndef __QRCODE_H_
#define __QRCODE_H_
@@ -46,55 +45,56 @@
#include <stdbool.h>
#include <stdint.h>
// QR Code Format Encoding
#define MODE_NUMERIC 0
#define MODE_ALPHANUMERIC 1
#define MODE_BYTE 2
#define MODE_NUMERIC 0
#define MODE_ALPHANUMERIC 1
#define MODE_BYTE 2
// Error Correction Code Levels
#define ECC_LOW 0
#define ECC_MEDIUM 1
#define ECC_QUARTILE 2
#define ECC_HIGH 3
#define ECC_LOW 0
#define ECC_MEDIUM 1
#define ECC_QUARTILE 2
#define ECC_HIGH 3
// If set to non-zero, this library can ONLY produce QR codes at that version
// This saves a lot of dynamic memory, as the codeword tables are skipped
#ifndef LOCK_VERSION
#define LOCK_VERSION 0
#define LOCK_VERSION 0
#endif
typedef struct QRCode {
uint8_t version;
uint8_t size;
uint8_t ecc;
uint8_t mode;
uint8_t mask;
uint8_t *modules;
uint8_t* modules;
} QRCode;
#ifdef __cplusplus
extern "C"{
#endif /* __cplusplus */
extern "C" {
#endif /* __cplusplus */
uint16_t qrcode_getBufferSize(uint8_t version);
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data);
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y);
int8_t qrcode_initText(
QRCode* qrcode,
uint8_t* modules,
uint8_t version,
uint8_t ecc,
const char* data);
int8_t qrcode_initBytes(
QRCode* qrcode,
uint8_t* modules,
uint8_t version,
uint8_t ecc,
uint8_t* data,
uint16_t length);
bool qrcode_getModule(QRCode* qrcode, uint8_t x, uint8_t y);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __cplusplus */
#endif /* __QRCODE_H_ */
#endif /* __QRCODE_H_ */
+162 -110
View File
@@ -28,22 +28,22 @@ static const uint16_t MAX_LENGTH[3][4][MAX_QRCODE_VERSION] = {
// Numeric
{41, 77, 127, 187, 255, 322, 370, 461, 552, 652, 772}, // Low
{34, 63, 101, 149, 202, 255, 293, 365, 432, 513, 604}, // Medium
{27, 48, 77, 111, 144, 178, 207, 259, 312, 364, 427}, // Quartile
{17, 34, 58, 82, 106, 139, 154, 202, 235, 288, 331}, // High
{27, 48, 77, 111, 144, 178, 207, 259, 312, 364, 427}, // Quartile
{17, 34, 58, 82, 106, 139, 154, 202, 235, 288, 331}, // High
},
{
// Alphanumeric
{25, 47, 77, 114, 154, 195, 224, 279, 335, 395, 468}, // Low
{20, 38, 61, 90, 122, 154, 178, 221, 262, 311, 366}, // Medium
{16, 29, 47, 67, 87, 108, 125, 157, 189, 221, 259}, // Quartile
{10, 20, 35, 50, 64, 84, 93, 122, 143, 174, 200}, // High
{20, 38, 61, 90, 122, 154, 178, 221, 262, 311, 366}, // Medium
{16, 29, 47, 67, 87, 108, 125, 157, 189, 221, 259}, // Quartile
{10, 20, 35, 50, 64, 84, 93, 122, 143, 174, 200}, // High
},
{
// Binary
{17, 32, 53, 78, 106, 134, 154, 192, 230, 271, 321}, // Low
{14, 26, 42, 62, 84, 106, 122, 152, 180, 213, 251}, // Medium
{11, 20, 32, 46, 60, 74, 86, 108, 130, 151, 177}, // Quartile
{7, 14, 24, 34, 44, 58, 64, 84, 98, 119, 137}, // High
{14, 26, 42, 62, 84, 106, 122, 152, 180, 213, 251}, // Medium
{11, 20, 32, 46, 60, 74, 86, 108, 130, 151, 177}, // Quartile
{7, 14, 24, 34, 44, 58, 64, 84, 98, 119, 137}, // High
},
};
@@ -72,12 +72,17 @@ typedef struct {
* @returns a character corresponding to the ecc level
*/
static char get_ecc_char(uint8_t ecc) {
switch (ecc) {
case 0: return 'L';
case 1: return 'M';
case 2: return 'Q';
case 3: return 'H';
default: return '?';
switch(ecc) {
case 0:
return 'L';
case 1:
return 'M';
case 2:
return 'Q';
case 3:
return 'H';
default:
return '?';
}
}
@@ -86,12 +91,17 @@ static char get_ecc_char(uint8_t ecc) {
* @returns a character corresponding to the mode
*/
static char get_mode_char(uint8_t mode) {
switch (mode) {
case 0: return 'N';
case 1: return 'A';
case 2: return 'B';
case 3: return 'K';
default: return '?';
switch(mode) {
case 0:
return 'N';
case 1:
return 'A';
case 2:
return 'B';
case 3:
return 'K';
default:
return '?';
}
}
@@ -114,68 +124,108 @@ static void render_callback(Canvas* canvas, void* ctx) {
uint8_t font_height = canvas_current_font_height(canvas);
uint8_t width = canvas_width(canvas);
uint8_t height = canvas_height(canvas);
if (instance->loading) {
canvas_draw_str_aligned(canvas, width / 2, height / 2, AlignCenter, AlignCenter, "Loading...");
} else if (instance->qrcode) {
if(instance->loading) {
canvas_draw_str_aligned(
canvas, width / 2, height / 2, AlignCenter, AlignCenter, "Loading...");
} else if(instance->qrcode) {
uint8_t size = instance->qrcode->size;
uint8_t pixel_size = height / size;
uint8_t top = (height - pixel_size * size) / 2;
uint8_t left = ((instance->show_stats ? 65 : width) - pixel_size * size) / 2;
for (uint8_t y = 0; y < size; y++) {
for (uint8_t x = 0; x < size; x++) {
if (qrcode_getModule(instance->qrcode, x, y)) {
if (pixel_size == 1) {
for(uint8_t y = 0; y < size; y++) {
for(uint8_t x = 0; x < size; x++) {
if(qrcode_getModule(instance->qrcode, x, y)) {
if(pixel_size == 1) {
canvas_draw_dot(canvas, left + x * pixel_size, top + y * pixel_size);
} else {
canvas_draw_box(canvas, left + x * pixel_size, top + y * pixel_size, pixel_size, pixel_size);
canvas_draw_box(
canvas,
left + x * pixel_size,
top + y * pixel_size,
pixel_size,
pixel_size);
}
}
}
}
if (instance->show_stats) {
if(instance->show_stats) {
top = 10;
left = 66;
FuriString* str = furi_string_alloc();
if (!instance->edit || instance->selected_idx == 0) {
if(!instance->edit || instance->selected_idx == 0) {
furi_string_printf(str, "Ver: %i", instance->set_version);
canvas_draw_str(canvas, left + 5, top + font_height, furi_string_get_cstr(str));
if (instance->selected_idx == 0) {
canvas_draw_triangle(canvas, left, top + font_height / 2, font_height - 4, 4, CanvasDirectionLeftToRight);
if(instance->selected_idx == 0) {
canvas_draw_triangle(
canvas,
left,
top + font_height / 2,
font_height - 4,
4,
CanvasDirectionLeftToRight);
}
if (instance->edit) {
if(instance->edit) {
uint8_t arrow_left = left + 5 + canvas_string_width(canvas, "Ver: 8") / 2;
canvas_draw_triangle(canvas, arrow_left, top, font_height - 4, 4, CanvasDirectionBottomToTop);
canvas_draw_triangle(canvas, arrow_left, top + font_height + 1, font_height - 4, 4, CanvasDirectionTopToBottom);
canvas_draw_triangle(
canvas, arrow_left, top, font_height - 4, 4, CanvasDirectionBottomToTop);
canvas_draw_triangle(
canvas,
arrow_left,
top + font_height + 1,
font_height - 4,
4,
CanvasDirectionTopToBottom);
}
}
if (!instance->edit || instance->selected_idx == 1) {
if(!instance->edit || instance->selected_idx == 1) {
furi_string_printf(str, "ECC: %c", get_ecc_char(instance->set_ecc));
canvas_draw_str(canvas, left + 5, 2 * font_height + top + 2, furi_string_get_cstr(str));
if (instance->selected_idx == 1) {
canvas_draw_triangle(canvas, left, 3 * font_height / 2 + top + 2, font_height - 4, 4, CanvasDirectionLeftToRight);
canvas_draw_str(
canvas, left + 5, 2 * font_height + top + 2, furi_string_get_cstr(str));
if(instance->selected_idx == 1) {
canvas_draw_triangle(
canvas,
left,
3 * font_height / 2 + top + 2,
font_height - 4,
4,
CanvasDirectionLeftToRight);
}
if (instance->edit) {
if(instance->edit) {
uint8_t arrow_left = left + 5 + canvas_string_width(canvas, "ECC: H") / 2;
canvas_draw_triangle(canvas, arrow_left, font_height + top + 2, font_height - 4, 4, CanvasDirectionBottomToTop);
canvas_draw_triangle(canvas, arrow_left, 2 * font_height + top + 3, font_height - 4, 4, CanvasDirectionTopToBottom);
canvas_draw_triangle(
canvas,
arrow_left,
font_height + top + 2,
font_height - 4,
4,
CanvasDirectionBottomToTop);
canvas_draw_triangle(
canvas,
arrow_left,
2 * font_height + top + 3,
font_height - 4,
4,
CanvasDirectionTopToBottom);
}
}
if (!instance->edit) {
if(!instance->edit) {
furi_string_printf(str, "Mod: %c", get_mode_char(instance->qrcode->mode));
canvas_draw_str(canvas, left + 5, 3 * font_height + top + 4, furi_string_get_cstr(str));
canvas_draw_str(
canvas, left + 5, 3 * font_height + top + 4, furi_string_get_cstr(str));
}
furi_string_free(str);
}
} else {
uint8_t margin = (height - font_height * 2) / 3;
canvas_draw_str_aligned(canvas, width / 2, margin, AlignCenter, AlignTop, "Could not load qrcode.");
if (instance->too_long) {
canvas_draw_str_aligned(
canvas, width / 2, margin, AlignCenter, AlignTop, "Could not load qrcode.");
if(instance->too_long) {
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, width / 2, margin * 2 + font_height, "Message is too long.");
}
@@ -192,7 +242,7 @@ static void render_callback(Canvas* canvas, void* ctx) {
static void input_callback(InputEvent* input_event, void* ctx) {
furi_assert(input_event);
furi_assert(ctx);
if (input_event->type == InputTypeShort) {
if(input_event->type == InputTypeShort) {
QRCodeApp* instance = ctx;
furi_message_queue_put(instance->input_queue, input_event, 0);
}
@@ -205,9 +255,9 @@ static void input_callback(InputEvent* input_event, void* ctx) {
*/
static bool is_numeric(const char* str, uint16_t len) {
furi_assert(str);
while (len > 0) {
while(len > 0) {
char c = str[--len];
if (c < '0' || c > '9') return false;
if(c < '0' || c > '9') return false;
}
return true;
}
@@ -219,19 +269,12 @@ static bool is_numeric(const char* str, uint16_t len) {
*/
static bool is_alphanumeric(const char* str, uint16_t len) {
furi_assert(str);
while (len > 0) {
while(len > 0) {
char c = str[--len];
if (c >= '0' && c <= '9') continue;
if (c >= 'A' && c <= 'Z') continue;
if (c == ' '
|| c == '$'
|| c == '%'
|| c == '*'
|| c == '+'
|| c == '-'
|| c == '.'
|| c == '/'
|| c == ':')
if(c >= '0' && c <= '9') continue;
if(c >= 'A' && c <= 'Z') continue;
if(c == ' ' || c == '$' || c == '%' || c == '*' || c == '+' || c == '-' || c == '.' ||
c == '/' || c == ':')
continue;
return false;
}
@@ -277,8 +320,9 @@ static bool rebuild_qrcode(QRCodeApp* instance, uint8_t version, uint8_t ecc) {
uint16_t len = strlen(cstr);
instance->qrcode = qrcode_alloc(version);
int8_t res = qrcode_initBytes(instance->qrcode, instance->qrcode->modules, version, ecc, (uint8_t*)cstr, len);
if (res != 0) {
int8_t res = qrcode_initBytes(
instance->qrcode, instance->qrcode->modules, version, ecc, (uint8_t*)cstr, len);
if(res != 0) {
FURI_LOG_E(TAG, "Could not create qrcode");
qrcode_free(instance->qrcode);
@@ -300,11 +344,11 @@ static bool qrcode_load_string(QRCodeApp* instance, FuriString* str) {
furi_assert(str);
furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
if (instance->message) {
if(instance->message) {
furi_string_free(instance->message);
instance->message = NULL;
}
if (instance->qrcode) {
if(instance->qrcode) {
qrcode_free(instance->qrcode);
instance->qrcode = NULL;
}
@@ -319,15 +363,17 @@ static bool qrcode_load_string(QRCodeApp* instance, FuriString* str) {
uint16_t len = strlen(cstr);
instance->message = furi_string_alloc_set(str);
if (!instance->message) {
if(!instance->message) {
FURI_LOG_E(TAG, "Could not allocate message");
break;
}
// figure out the qrcode "mode"
uint8_t mode = MODE_BYTE;
if (is_numeric(cstr, len)) mode = MODE_NUMERIC;
else if (is_alphanumeric(cstr, len)) mode = MODE_ALPHANUMERIC;
if(is_numeric(cstr, len))
mode = MODE_NUMERIC;
else if(is_alphanumeric(cstr, len))
mode = MODE_ALPHANUMERIC;
// Figure out the smallest qrcode version that'll fit all of the data -
// we prefer the smallest version to maximize the pixel size of each
@@ -336,11 +382,11 @@ static bool qrcode_load_string(QRCodeApp* instance, FuriString* str) {
// number, so we'll add one later.
uint8_t ecc = ECC_LOW;
uint8_t version = 0;
while (version < MAX_QRCODE_VERSION && MAX_LENGTH[mode][ecc][version] < len) {
while(version < MAX_QRCODE_VERSION && MAX_LENGTH[mode][ecc][version] < len) {
version++;
}
if (version == MAX_QRCODE_VERSION) {
if(version == MAX_QRCODE_VERSION) {
instance->too_long = true;
break;
}
@@ -350,13 +396,13 @@ static bool qrcode_load_string(QRCodeApp* instance, FuriString* str) {
// above that ECC_LOW (0) works... don't forget to add one to that
// version number...
ecc = ECC_HIGH;
while (MAX_LENGTH[mode][ecc][version] < len) {
while(MAX_LENGTH[mode][ecc][version] < len) {
ecc--;
}
version++;
// Build the qrcode
if (!rebuild_qrcode(instance, version, ecc)) {
if(!rebuild_qrcode(instance, version, ecc)) {
furi_string_free(instance->message);
instance->message = NULL;
break;
@@ -365,7 +411,7 @@ static bool qrcode_load_string(QRCodeApp* instance, FuriString* str) {
instance->min_version = instance->set_version = version;
instance->max_ecc_at_min_version = instance->set_ecc = ecc;
result = true;
} while (false);
} while(false);
instance->loading = false;
@@ -391,27 +437,26 @@ static bool qrcode_load_file(QRCodeApp* instance, const char* file_path) {
FlipperFormat* file = flipper_format_file_alloc(storage);
do {
if (!flipper_format_file_open_existing(file, file_path)) break;
if(!flipper_format_file_open_existing(file, file_path)) break;
uint32_t version = 0;
if (!flipper_format_read_header(file, temp_str, &version)) break;
if (furi_string_cmp_str(temp_str, QRCODE_FILETYPE)
|| version != QRCODE_FILE_VERSION) {
if(!flipper_format_read_header(file, temp_str, &version)) break;
if(furi_string_cmp_str(temp_str, QRCODE_FILETYPE) || version != QRCODE_FILE_VERSION) {
FURI_LOG_E(TAG, "Incorrect file format or version");
break;
}
if (!flipper_format_read_string(file, "Message", temp_str)) {
if(!flipper_format_read_string(file, "Message", temp_str)) {
FURI_LOG_E(TAG, "Message is missing");
break;
}
if (!qrcode_load_string(instance, temp_str)) {
if(!qrcode_load_string(instance, temp_str)) {
break;
}
result = true;
} while (false);
} while(false);
furi_record_close(RECORD_STORAGE);
flipper_format_free(file);
@@ -454,8 +499,8 @@ static QRCodeApp* qrcode_app_alloc() {
* @param qrcode_app The app to free
*/
static void qrcode_app_free(QRCodeApp* instance) {
if (instance->message) furi_string_free(instance->message);
if (instance->qrcode) qrcode_free(instance->qrcode);
if(instance->message) furi_string_free(instance->message);
if(instance->qrcode) qrcode_free(instance->qrcode);
gui_remove_view_port(instance->gui, instance->view_port);
furi_record_close(RECORD_GUI);
@@ -475,14 +520,14 @@ int32_t qrcode_app(void* p) {
FuriString* file_path = furi_string_alloc();
do {
if (p && strlen(p)) {
if(p && strlen(p)) {
furi_string_set(file_path, (const char*)p);
} else {
furi_string_set(file_path, QRCODE_FOLDER);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, QRCODE_EXTENSION, &I_qrcode_10px);
&browser_options, QRCODE_EXTENSION, &I_qrcode_10px);
browser_options.hide_ext = true;
browser_options.base_path = QRCODE_FOLDER;
@@ -490,26 +535,27 @@ int32_t qrcode_app(void* p) {
bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
furi_record_close(RECORD_DIALOGS);
if (!res) {
if(!res) {
FURI_LOG_E(TAG, "No file selected");
break;
}
}
if (!qrcode_load_file(instance, furi_string_get_cstr(file_path))) {
if(!qrcode_load_file(instance, furi_string_get_cstr(file_path))) {
FURI_LOG_E(TAG, "Unable to load file");
}
InputEvent input;
while (furi_message_queue_get(instance->input_queue, &input, FuriWaitForever) == FuriStatusOk) {
while(furi_message_queue_get(instance->input_queue, &input, FuriWaitForever) ==
FuriStatusOk) {
furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
if (input.key == InputKeyBack) {
if (instance->message) {
if(input.key == InputKeyBack) {
if(instance->message) {
furi_string_free(instance->message);
instance->message = NULL;
}
if (instance->qrcode) {
if(instance->qrcode) {
qrcode_free(instance->qrcode);
instance->qrcode = NULL;
}
@@ -517,43 +563,49 @@ int32_t qrcode_app(void* p) {
instance->edit = false;
furi_mutex_release(instance->mutex);
break;
} else if (input.key == InputKeyRight) {
} else if(input.key == InputKeyRight) {
instance->show_stats = true;
} else if (input.key == InputKeyLeft) {
} else if(input.key == InputKeyLeft) {
instance->show_stats = false;
} else if (instance->show_stats && !instance->loading && instance->qrcode) {
if (input.key == InputKeyUp) {
if (!instance->edit) {
} else if(instance->show_stats && !instance->loading && instance->qrcode) {
if(input.key == InputKeyUp) {
if(!instance->edit) {
instance->selected_idx = MAX(0, instance->selected_idx - 1);
} else {
if (instance->selected_idx == 0 && instance->set_version < MAX_QRCODE_VERSION) {
if(instance->selected_idx == 0 &&
instance->set_version < MAX_QRCODE_VERSION) {
instance->set_version++;
} else if (instance->selected_idx == 1) {
uint8_t max_ecc = instance->set_version == instance->min_version ? instance->max_ecc_at_min_version : ECC_HIGH;
if (instance->set_ecc < max_ecc) {
} else if(instance->selected_idx == 1) {
uint8_t max_ecc = instance->set_version == instance->min_version ?
instance->max_ecc_at_min_version :
ECC_HIGH;
if(instance->set_ecc < max_ecc) {
instance->set_ecc++;
}
}
}
} else if (input.key == InputKeyDown) {
if (!instance->edit) {
} else if(input.key == InputKeyDown) {
if(!instance->edit) {
instance->selected_idx = MIN(1, instance->selected_idx + 1);
} else {
if (instance->selected_idx == 0 && instance->set_version > instance->min_version) {
if(instance->selected_idx == 0 &&
instance->set_version > instance->min_version) {
instance->set_version--;
if (instance->set_version == instance->min_version) {
instance->set_ecc = MAX(instance->set_ecc, instance->max_ecc_at_min_version);
if(instance->set_version == instance->min_version) {
instance->set_ecc =
MAX(instance->set_ecc, instance->max_ecc_at_min_version);
}
} else if (instance->selected_idx == 1 && instance->set_ecc > 0) {
} else if(instance->selected_idx == 1 && instance->set_ecc > 0) {
instance->set_ecc--;
}
}
} else if (input.key == InputKeyOk) {
if (instance->edit && (instance->set_version != instance->qrcode->version || instance->set_ecc != instance->qrcode->ecc)) {
} else if(input.key == InputKeyOk) {
if(instance->edit && (instance->set_version != instance->qrcode->version ||
instance->set_ecc != instance->qrcode->ecc)) {
QRCode* qrcode = instance->qrcode;
instance->loading = true;
if (rebuild_qrcode(instance, instance->set_version, instance->set_ecc)) {
if(rebuild_qrcode(instance, instance->set_version, instance->set_ecc)) {
qrcode_free(qrcode);
} else {
FURI_LOG_E(TAG, "Could not rebuild qrcode");
@@ -572,12 +624,12 @@ int32_t qrcode_app(void* p) {
view_port_update(instance->view_port);
}
if (p && strlen(p)) {
if(p && strlen(p)) {
// if started with an arg, exit instead
// of looping back to the browser
break;
}
} while (true);
} while(true);
furi_string_free(file_path);
qrcode_app_free(instance);
+1 -2
View File
@@ -255,8 +255,7 @@ bool place_on_top(Card* where, Card what) {
int8_t b_letter = (int8_t)what.character;
if(a_letter == 12) a_letter = -1;
if(b_letter == 12) b_letter = -1;
if(where->disabled && b_letter!=-1)
return false;
if(where->disabled && b_letter != -1) return false;
if((a_letter + 1) == b_letter) {
where->disabled = what.disabled;
where->pip = what.pip;
+15 -4
View File
@@ -11,6 +11,7 @@
#include "commands/move/move.h"
#include "commands/pin/pin.h"
#include "commands/notification/notification.h"
#include "commands/reset/reset.h"
static void totp_cli_print_unknown_command(const FuriString* unknown_command) {
TOTP_CLI_PRINTF(
@@ -20,7 +21,8 @@ static void totp_cli_print_unknown_command(const FuriString* unknown_command) {
}
static void totp_cli_handler(Cli* cli, FuriString* args, void* context) {
PluginState* plugin_state = (PluginState*)context;
TotpCliContext* cli_context = context;
PluginState* plugin_state = cli_context->plugin_state;
FuriString* cmd = furi_string_alloc();
@@ -55,6 +57,8 @@ static void totp_cli_handler(Cli* cli, FuriString* args, void* context) {
totp_cli_command_pin_handle(plugin_state, args, cli);
} else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_NOTIFICATION) == 0) {
totp_cli_command_notification_handle(plugin_state, args, cli);
} else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_RESET) == 0) {
totp_cli_command_reset_handle(cli, cli_context->event_queue);
} else {
totp_cli_print_unknown_command(cmd);
}
@@ -62,15 +66,22 @@ static void totp_cli_handler(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
void totp_cli_register_command_handler(PluginState* plugin_state) {
TotpCliContext*
totp_cli_register_command_handler(PluginState* plugin_state, FuriMessageQueue* event_queue) {
Cli* cli = furi_record_open(RECORD_CLI);
TotpCliContext* context = malloc(sizeof(TotpCliContext));
furi_check(context != NULL);
context->plugin_state = plugin_state;
context->event_queue = event_queue;
cli_add_command(
cli, TOTP_CLI_COMMAND_NAME, CliCommandFlagParallelSafe, totp_cli_handler, plugin_state);
cli, TOTP_CLI_COMMAND_NAME, CliCommandFlagParallelSafe, totp_cli_handler, context);
furi_record_close(RECORD_CLI);
return context;
}
void totp_cli_unregister_command_handler() {
void totp_cli_unregister_command_handler(TotpCliContext* context) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_delete_command(cli, TOTP_CLI_COMMAND_NAME);
furi_record_close(RECORD_CLI);
free(context);
}
+8 -2
View File
@@ -3,5 +3,11 @@
#include <cli/cli.h>
#include "../types/plugin_state.h"
void totp_cli_register_command_handler(PluginState* plugin_state);
void totp_cli_unregister_command_handler();
typedef struct {
PluginState* plugin_state;
FuriMessageQueue* event_queue;
} TotpCliContext;
TotpCliContext*
totp_cli_register_command_handler(PluginState* plugin_state, FuriMessageQueue* event_queue);
void totp_cli_unregister_command_handler(TotpCliContext* context);
@@ -1,5 +1,6 @@
#include "cli_helpers.h"
#include <cli/cli.h>
#include "../types/plugin_event.h"
bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli) {
if(plugin_state->current_scene == TotpSceneAuthentication) {
@@ -17,5 +18,45 @@ bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli) {
}
}
return true;
}
void totp_cli_force_close_app(FuriMessageQueue* event_queue) {
PluginEvent event = {.type = EventForceCloseApp};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input) {
uint8_t c;
while(cli_read(cli, &c, 1) == 1) {
if(c == CliSymbolAsciiEsc) {
// Some keys generating escape-sequences
// We need to ignore them as we care about alpha-numerics only
uint8_t c2;
cli_read_timeout(cli, &c2, 1, 0);
cli_read_timeout(cli, &c2, 1, 0);
} else if(c == CliSymbolAsciiETX) {
cli_nl();
return false;
} else if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
if(mask_user_input) {
putc('*', stdout);
} else {
putc(c, stdout);
}
fflush(stdout);
furi_string_push_back(out_str, c);
} else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) {
size_t out_str_size = furi_string_size(out_str);
if(out_str_size > 0) {
TOTP_CLI_DELETE_LAST_CHAR();
furi_string_left(out_str, out_str_size - 1);
}
} else if(c == CliSymbolAsciiCR) {
cli_nl();
break;
}
}
return true;
}
+16 -1
View File
@@ -45,7 +45,22 @@
* @brief Checks whether user is authenticated and entered correct PIN.
* If user is not authenticated it prompts user to enter correct PIN to authenticate.
* @param plugin_state application state
* @param cli reference to the firmware CLI subsystem
* @param cli pointer to the firmware CLI subsystem
* @return \c true if user is already authenticated or successfully authenticated; \c false otherwise
*/
bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli);
/**
* @brief Forces application to be instantly closed
* @param event_queue main app queue
*/
void totp_cli_force_close_app(FuriMessageQueue* event_queue);
/**
* @brief Reads line of characters from console
* @param cli pointer to the firmware CLI subsystem
* @param out_str pointer to an output string to put read line to
* @param mask_user_input whether to mask input characters in console or not
* @return \c true if line successfully read and confirmed; \c false otherwise
*/
bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input);
@@ -76,43 +76,6 @@ static void furi_string_secure_free(FuriString* str) {
furi_string_free(str);
}
static bool totp_cli_read_secret(Cli* cli, FuriString* out_str, bool mask_user_input) {
uint8_t c;
while(cli_read(cli, &c, 1) == 1) {
if(c == CliSymbolAsciiEsc) {
// Some keys generating escape-sequences
// We need to ignore them as we care about alpha-numerics only
uint8_t c2;
cli_read_timeout(cli, &c2, 1, 0);
cli_read_timeout(cli, &c2, 1, 0);
} else if(c == CliSymbolAsciiETX) {
TOTP_CLI_DELETE_CURRENT_LINE();
TOTP_CLI_PRINTF("Cancelled by user\r\n");
return false;
} else if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
if(mask_user_input) {
putc('*', stdout);
} else {
putc(c, stdout);
}
fflush(stdout);
furi_string_push_back(out_str, c);
} else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) {
size_t out_str_size = furi_string_size(out_str);
if(out_str_size > 0) {
TOTP_CLI_DELETE_LAST_CHAR();
furi_string_left(out_str, out_str_size - 1);
}
} else if(c == CliSymbolAsciiCR) {
cli_nl();
break;
}
}
TOTP_CLI_DELETE_LAST_LINE();
return true;
}
void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
FuriString* temp_str = furi_string_alloc();
TokenInfo* token_info = token_info_alloc();
@@ -178,13 +141,17 @@ void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cl
// Reading token secret
furi_string_reset(temp_str);
TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n");
if(!totp_cli_read_secret(cli, temp_str, mask_user_input) ||
if(!totp_cli_read_line(cli, temp_str, mask_user_input) ||
!totp_cli_ensure_authenticated(plugin_state, cli)) {
TOTP_CLI_DELETE_LAST_LINE();
TOTP_CLI_PRINTF("Cancelled by user\r\n");
furi_string_secure_free(temp_str);
token_info_free(token_info);
return;
}
TOTP_CLI_DELETE_LAST_LINE();
if(!token_info_set_secret(
token_info,
furi_string_get_cstr(temp_str),
@@ -7,6 +7,7 @@
#include "../move/move.h"
#include "../pin/pin.h"
#include "../notification/notification.h"
#include "../reset/reset.h"
void totp_cli_command_help_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_HELP ", " TOTP_CLI_COMMAND_HELP_ALT
@@ -29,6 +30,7 @@ void totp_cli_command_help_handle() {
totp_cli_command_move_docopt_usage();
totp_cli_command_pin_docopt_usage();
totp_cli_command_notification_docopt_usage();
totp_cli_command_reset_docopt_usage();
cli_nl();
TOTP_CLI_PRINTF("Commands:\r\n");
totp_cli_command_help_docopt_commands();
@@ -39,6 +41,7 @@ void totp_cli_command_help_handle() {
totp_cli_command_move_docopt_commands();
totp_cli_command_pin_docopt_commands();
totp_cli_command_notification_docopt_commands();
totp_cli_command_reset_docopt_commands();
cli_nl();
TOTP_CLI_PRINTF("Arguments:\r\n");
totp_cli_command_add_docopt_arguments();
@@ -0,0 +1,37 @@
#include "reset.h"
#include <stdlib.h>
#include <furi/furi.h>
#include "../../cli_helpers.h"
#include "../../../services/config/config.h"
#define TOTP_CLI_RESET_CONFIRMATION_KEYWORD "YES"
void totp_cli_command_reset_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_RESET
" Reset application to default settings\r\n");
}
void totp_cli_command_reset_docopt_usage() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_RESET "\r\n");
}
void totp_cli_command_reset_handle(Cli* cli, FuriMessageQueue* event_queue) {
TOTP_CLI_PRINTF(
"As a result of reset all the settings and tokens will be permanently lost.\r\n");
TOTP_CLI_PRINTF("Do you really want to reset application?\r\n");
TOTP_CLI_PRINTF("Type \"" TOTP_CLI_RESET_CONFIRMATION_KEYWORD
"\" and hit <ENTER> to confirm:\r\n");
FuriString* temp_str = furi_string_alloc();
bool is_confirmed = totp_cli_read_line(cli, temp_str, false) &&
furi_string_cmpi_str(temp_str, TOTP_CLI_RESET_CONFIRMATION_KEYWORD) == 0;
furi_string_free(temp_str);
if(is_confirmed) {
totp_config_file_reset();
TOTP_CLI_PRINTF("Application has been successfully reset to default.\r\n");
TOTP_CLI_PRINTF("Now application will be closed to apply all the changes.\r\n");
totp_cli_force_close_app(event_queue);
} else {
TOTP_CLI_PRINTF("Action was not confirmed by user\r\n");
}
}
@@ -0,0 +1,10 @@
#pragma once
#include <cli/cli.h>
#include "../../../types/plugin_state.h"
#define TOTP_CLI_COMMAND_RESET "reset"
void totp_cli_command_reset_handle(Cli* cli, FuriMessageQueue* event_queue);
void totp_cli_command_reset_docopt_commands();
void totp_cli_command_reset_docopt_usage();
@@ -730,4 +730,10 @@ TotpConfigFileUpdateResult
totp_close_storage();
return update_result;
}
}
void totp_config_file_reset() {
Storage* storage = totp_open_storage();
storage_simply_remove(storage, CONFIG_FILE_PATH);
totp_close_storage();
}
@@ -116,4 +116,9 @@ TotpConfigFileUpdateResult totp_config_file_update_user_settings(const PluginSta
* @return Config file update result
*/
TotpConfigFileUpdateResult
totp_config_file_update_crypto_signatures(const PluginState* plugin_state);
totp_config_file_update_crypto_signatures(const PluginState* plugin_state);
/**
* @brief Reset all the settings to default
*/
void totp_config_file_reset();
+7 -3
View File
@@ -143,7 +143,7 @@ int32_t totp_app() {
return 255;
}
totp_cli_register_command_handler(plugin_state);
TotpCliContext* cli_context = totp_cli_register_command_handler(plugin_state, event_queue);
totp_scene_director_init_scenes(plugin_state);
if(!totp_activate_initial_scene(plugin_state)) {
FURI_LOG_E(LOGGING_TAG, "An error ocurred during activating initial scene\r\n");
@@ -172,7 +172,11 @@ int32_t totp_app() {
last_user_interaction_time = furi_get_tick();
}
processing = totp_scene_director_handle_event(&event, plugin_state_m);
if(event.type == EventForceCloseApp) {
processing = false;
} else {
processing = totp_scene_director_handle_event(&event, plugin_state_m);
}
} else if(
plugin_state_m->pin_set && plugin_state_m->current_scene != TotpSceneAuthentication &&
furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) {
@@ -183,7 +187,7 @@ int32_t totp_app() {
release_mutex(&state_mutex, plugin_state_m);
}
totp_cli_unregister_command_handler();
totp_cli_unregister_command_handler(cli_context);
totp_scene_director_deactivate_active_scene(plugin_state);
totp_scene_director_dispose(plugin_state);
+1 -4
View File
@@ -3,7 +3,4 @@
typedef uint8_t EventType;
enum EventTypes {
EventTypeTick,
EventTypeKey,
};
enum EventTypes { EventTypeTick, EventTypeKey, EventForceCloseApp };
+1 -1
View File
@@ -28,7 +28,7 @@ bool token_info_set_secret(
size_t token_secret_length,
const uint8_t* iv) {
if(token_secret_length == 0) return false;
uint8_t* plain_secret = malloc(token_secret_length);
furi_check(plain_secret != NULL);
int plain_secret_length =
@@ -247,4 +247,4 @@ void totp_scene_app_settings_deactivate(PluginState* plugin_state) {
void totp_scene_app_settings_free(const PluginState* plugin_state) {
UNUSED(plugin_state);
}
}
@@ -172,10 +172,9 @@ static void _draw_singleSensor(Canvas* canvas, Sensor* sensor, const uint8_t pos
static void _draw_view_noSensors(Canvas* canvas) {
DesktopSettings* settings = malloc(sizeof(DesktopSettings));
DESKTOP_SETTINGS_LOAD(settings);
if (settings->sfw_mode) {
if(settings->sfw_mode) {
canvas_draw_icon(canvas, 7, 17, &I_sherlok_53x45_sfw);
}
else {
} else {
canvas_draw_icon(canvas, 7, 17, &I_sherlok_53x45);
}
//Рисование рамки
+6 -12
View File
@@ -5,8 +5,7 @@
void strrev(char* arr, int start, int end) {
char temp;
if (start >= end)
return;
if(start >= end) return;
temp = *(arr + start);
*(arr + start) = *(arr + end);
@@ -17,33 +16,28 @@ void strrev(char* arr, int start, int end) {
strrev(arr, start, end);
}
char *itoa(int number, char *arr, int base)
{
char* itoa(int number, char* arr, int base) {
int i = 0, r, negative = 0;
if (number == 0)
{
if(number == 0) {
arr[i] = '0';
arr[i + 1] = '\0';
return arr;
}
if (number < 0 && base == 10)
{
if(number < 0 && base == 10) {
number *= -1;
negative = 1;
}
while (number != 0)
{
while(number != 0) {
r = number % base;
arr[i] = (r > 9) ? (r - 10) + 'a' : r + '0';
i++;
number /= base;
}
if (negative)
{
if(negative) {
arr[i] = '-';
i++;
}
@@ -1,7 +1,7 @@
#ifndef FLIPPERZERO_FIRMWARE_TOOLS_H
#define FLIPPERZERO_FIRMWARE_TOOLS_H
void strrev(char *arr, int start, int end);
char *itoa(int number, char *arr, int base);
void strrev(char* arr, int start, int end);
char* itoa(int number, char* arr, int base);
#endif //FLIPPERZERO_FIRMWARE_TOOLS_H

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