Merge branch 'dev' of https://github.com/Flipper-XFW/Xtreme-Firmware into xfw-new-app-system
@@ -455,4 +455,19 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) {
|
||||
return NfcErrorNone;
|
||||
}
|
||||
|
||||
NfcError nfc_felica_listener_set_sensf_res_data(
|
||||
Nfc* instance,
|
||||
const uint8_t* idm,
|
||||
const uint8_t idm_len,
|
||||
const uint8_t* pmm,
|
||||
const uint8_t pmm_len) {
|
||||
furi_assert(instance);
|
||||
furi_assert(idm);
|
||||
furi_assert(pmm);
|
||||
furi_assert(idm_len == 8);
|
||||
furi_assert(pmm_len == 8);
|
||||
|
||||
return NfcErrorNone;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
17
applications/external/uhf_rfid/application.fam
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
App(
|
||||
appid="uhf_rfid",
|
||||
name="[YRM100] UHF RFID",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
targets=["f7"],
|
||||
entry_point="uhf_app_main",
|
||||
requires=[
|
||||
"storage",
|
||||
"gui",
|
||||
],
|
||||
stack_size=8 * 1024,
|
||||
order=30,
|
||||
fap_icon="uhf_10px.png",
|
||||
fap_category="GPIO",
|
||||
fap_icon_assets="icons",
|
||||
fap_icon_assets_symbol="uhf_rfid",
|
||||
)
|
||||
30
applications/external/uhf_rfid/scenes/uhf_scene.c
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "uhf_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const uhf_on_enter_handlers[])(void*) = {
|
||||
#include "uhf_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const uhf_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "uhf_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const uhf_on_exit_handlers[])(void* context) = {
|
||||
#include "uhf_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers uhf_scene_handlers = {
|
||||
.on_enter_handlers = uhf_on_enter_handlers,
|
||||
.on_event_handlers = uhf_on_event_handlers,
|
||||
.on_exit_handlers = uhf_on_exit_handlers,
|
||||
.scene_num = UHFSceneNum,
|
||||
};
|
||||
29
applications/external/uhf_rfid/scenes/uhf_scene.h
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) UHFScene##id,
|
||||
typedef enum {
|
||||
#include "uhf_scene_config.h"
|
||||
UHFSceneNum,
|
||||
} UHFScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers uhf_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "uhf_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "uhf_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "uhf_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
18
applications/external/uhf_rfid/scenes/uhf_scene_config.h
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
ADD_SCENE(uhf, verify, Verify)
|
||||
ADD_SCENE(uhf, start, Start)
|
||||
ADD_SCENE(uhf, read_tag, ReadTag)
|
||||
ADD_SCENE(uhf, read_tag_success, ReadTagSuccess)
|
||||
ADD_SCENE(uhf, tag_menu, TagMenu)
|
||||
ADD_SCENE(uhf, save_name, SaveName)
|
||||
ADD_SCENE(uhf, save_success, SaveSuccess)
|
||||
ADD_SCENE(uhf, saved_menu, SavedMenu)
|
||||
ADD_SCENE(uhf, file_select, FileSelect)
|
||||
ADD_SCENE(uhf, device_info, DeviceInfo)
|
||||
ADD_SCENE(uhf, delete, Delete)
|
||||
ADD_SCENE(uhf, delete_success, DeleteSuccess)
|
||||
ADD_SCENE(uhf, write_tag, WriteTag)
|
||||
ADD_SCENE(uhf, write_tag_success, WriteTagSuccess)
|
||||
ADD_SCENE(uhf, settings, Settings)
|
||||
// ADD_SCENE(uhf, read_factory_success, ReadFactorySuccess)
|
||||
// ADD_SCENE(uhf, write_key, WriteKey)
|
||||
// ADD_SCENE(uhf, key_menu, KeyMenu)
|
||||
50
applications/external/uhf_rfid/scenes/uhf_scene_delete.c
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
void uhf_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void uhf_scene_delete_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Setup Custom Widget view
|
||||
char temp_str[64];
|
||||
snprintf(temp_str, sizeof(temp_str), "\e#Delete %s?\e#", uhf_app->uhf_device->dev_name);
|
||||
widget_add_text_box_element(
|
||||
uhf_app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, temp_str, false);
|
||||
widget_add_button_element(
|
||||
uhf_app->widget, GuiButtonTypeLeft, "Back", uhf_scene_delete_widget_callback, uhf_app);
|
||||
widget_add_button_element(
|
||||
uhf_app->widget, GuiButtonTypeRight, "Delete", uhf_scene_delete_widget_callback, uhf_app);
|
||||
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
}
|
||||
|
||||
bool uhf_scene_delete_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
return scene_manager_previous_scene(uhf_app->scene_manager);
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
if(uhf_device_delete(uhf_app->uhf_device, true)) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDeleteSuccess);
|
||||
} else {
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_delete_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
widget_reset(uhf_app->widget);
|
||||
}
|
||||
40
applications/external/uhf_rfid/scenes/uhf_scene_delete_success.c
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
void uhf_scene_delete_success_popup_callback(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventViewExit);
|
||||
}
|
||||
|
||||
void uhf_scene_delete_success_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Setup view
|
||||
Popup* popup = uhf_app->popup;
|
||||
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
|
||||
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
|
||||
popup_set_timeout(popup, 1500);
|
||||
popup_set_context(popup, uhf_app);
|
||||
popup_set_callback(popup, uhf_scene_delete_success_popup_callback);
|
||||
popup_enable_timeout(popup);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup);
|
||||
}
|
||||
|
||||
bool uhf_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == UHFCustomEventViewExit) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_delete_success_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
}
|
||||
133
applications/external/uhf_rfid/scenes/uhf_scene_device_info.c
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
typedef enum { EPC_INFO, TID_INFO, USER_INFO } UHFTagInfo;
|
||||
|
||||
static UHFTagInfo current_info;
|
||||
|
||||
char* get_current_bank_info_str() {
|
||||
switch(current_info) {
|
||||
case EPC_INFO:
|
||||
return "EPC Bank";
|
||||
case TID_INFO:
|
||||
return "TID Bank";
|
||||
case USER_INFO:
|
||||
return "User Bank";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
char* get_next_bank_info_str() {
|
||||
switch(current_info) {
|
||||
case EPC_INFO:
|
||||
current_info = TID_INFO;
|
||||
return "TID";
|
||||
case TID_INFO:
|
||||
current_info = USER_INFO;
|
||||
return "USER";
|
||||
case USER_INFO:
|
||||
current_info = EPC_INFO;
|
||||
return "EPC";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void uhf_scene_device_info_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void change_view_on_event(UHFApp* uhf_app) {
|
||||
UHFTag* uhf_tag = uhf_app->uhf_device->uhf_tag_wrapper->uhf_tag;
|
||||
FuriString* furi_temp_str;
|
||||
furi_temp_str = furi_string_alloc();
|
||||
char* temp_str;
|
||||
size_t length;
|
||||
|
||||
widget_reset(uhf_app->widget);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 64, 5, AlignCenter, AlignCenter, FontPrimary, get_current_bank_info_str());
|
||||
|
||||
switch(current_info) {
|
||||
case EPC_INFO:
|
||||
temp_str = convertToHexString(uhf_tag->epc->data, uhf_tag->epc->size);
|
||||
length = uhf_tag->epc->size;
|
||||
break;
|
||||
case TID_INFO:
|
||||
temp_str = convertToHexString(uhf_tag->tid->data, uhf_tag->tid->size);
|
||||
length = uhf_tag->tid->size;
|
||||
break;
|
||||
case USER_INFO:
|
||||
temp_str = convertToHexString(uhf_tag->user->data, uhf_tag->user->size);
|
||||
length = uhf_tag->user->size;
|
||||
break;
|
||||
default:
|
||||
temp_str = NULL;
|
||||
length = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_cat_printf(furi_temp_str, "Length: %d bytes", length);
|
||||
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
3,
|
||||
12,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontKeyboard,
|
||||
furi_string_get_cstr(furi_temp_str));
|
||||
|
||||
widget_add_string_multiline_element(
|
||||
uhf_app->widget, 3, 24, AlignLeft, AlignTop, FontKeyboard, temp_str);
|
||||
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeRight,
|
||||
get_next_bank_info_str(),
|
||||
uhf_scene_device_info_widget_callback,
|
||||
uhf_app);
|
||||
|
||||
widget_add_button_element(
|
||||
uhf_app->widget, GuiButtonTypeLeft, "Back", uhf_scene_device_info_widget_callback, uhf_app);
|
||||
|
||||
furi_string_free(furi_temp_str);
|
||||
free(temp_str);
|
||||
}
|
||||
|
||||
void uhf_scene_device_info_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
current_info = EPC_INFO;
|
||||
dolphin_deed(DolphinDeedNfcReadSuccess);
|
||||
change_view_on_event(uhf_app);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
}
|
||||
|
||||
bool uhf_scene_device_info_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
if(event.type == SceneManagerEventTypeTick) return false;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_previous_scene(uhf_app->scene_manager);
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
change_view_on_event(uhf_app);
|
||||
} else if(event.event == UHFCustomEventViewExit) {
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_previous_scene(uhf_app->scene_manager);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_device_info_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Clear views
|
||||
widget_reset(uhf_app->widget);
|
||||
}
|
||||
23
applications/external/uhf_rfid/scenes/uhf_scene_file_select.c
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
void uhf_scene_file_select_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
// Process file_select return
|
||||
uhf_device_set_loading_callback(uhf_app->uhf_device, uhf_show_loading_popup, uhf_app);
|
||||
if(uhf_file_select(uhf_app->uhf_device)) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSavedMenu);
|
||||
} else {
|
||||
scene_manager_search_and_switch_to_previous_scene(uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
uhf_device_set_loading_callback(uhf_app->uhf_device, NULL, uhf_app);
|
||||
}
|
||||
|
||||
bool uhf_scene_file_select_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void uhf_scene_file_select_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
45
applications/external/uhf_rfid/scenes/uhf_scene_read_tag.c
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void uhf_read_tag_worker_callback(UHFWorkerEvent event, void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
if(event == UHFWorkerEventSuccess) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventWorkerExit);
|
||||
}
|
||||
}
|
||||
|
||||
void uhf_scene_read_tag_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
dolphin_deed(DolphinDeedNfcRead);
|
||||
|
||||
// Setup view
|
||||
Popup* popup = uhf_app->popup;
|
||||
popup_set_header(popup, "Detecting\n[UHF] RFID\nTag", 68, 30, AlignLeft, AlignTop);
|
||||
popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
|
||||
|
||||
// Start worker
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup);
|
||||
uhf_worker_start(
|
||||
uhf_app->worker, UHFWorkerStateDetectSingle, uhf_read_tag_worker_callback, uhf_app);
|
||||
|
||||
uhf_blink_start(uhf_app);
|
||||
}
|
||||
|
||||
bool uhf_scene_read_tag_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.event == UHFCustomEventWorkerExit) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneReadTagSuccess);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_read_tag_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
// Stop worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
uhf_blink_stop(uhf_app);
|
||||
}
|
||||
111
applications/external/uhf_rfid/scenes/uhf_scene_read_tag_success.c
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void uhf_read_tag_success_worker_callback(UHFWorkerEvent event, void* ctx) {
|
||||
UNUSED(event);
|
||||
UNUSED(ctx);
|
||||
}
|
||||
|
||||
void uhf_scene_read_card_success_widget_callback(GuiButtonType result, InputType type, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void uhf_scene_read_tag_success_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
UHFTag* uhf_tag = uhf_app->worker->uhf_tag_wrapper->uhf_tag;
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
|
||||
dolphin_deed(DolphinDeedNfcReadSuccess);
|
||||
|
||||
// Send notification
|
||||
notification_message(uhf_app->notifications, &sequence_success);
|
||||
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 32, 5, AlignLeft, AlignCenter, FontPrimary, "Read Success");
|
||||
|
||||
widget_add_string_element(uhf_app->widget, 3, 18, AlignLeft, AlignCenter, FontPrimary, "PC :");
|
||||
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 66, 18, AlignLeft, AlignCenter, FontPrimary, "CRC :");
|
||||
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 3, 32, AlignLeft, AlignCenter, FontPrimary, "EPC :");
|
||||
|
||||
furi_string_cat_printf(temp_str, "%04X", uhf_tag->epc->pc);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
26,
|
||||
19,
|
||||
AlignLeft,
|
||||
AlignCenter,
|
||||
FontKeyboard,
|
||||
furi_string_get_cstr(temp_str));
|
||||
furi_string_reset(temp_str);
|
||||
furi_string_cat_printf(temp_str, "%04X", uhf_tag->epc->crc);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
96,
|
||||
19,
|
||||
AlignLeft,
|
||||
AlignCenter,
|
||||
FontKeyboard,
|
||||
furi_string_get_cstr(temp_str));
|
||||
char* epc = convertToHexString(uhf_tag->epc->data, uhf_tag->epc->size);
|
||||
if(epc != NULL) {
|
||||
widget_add_string_multiline_element(
|
||||
uhf_app->widget, 34, 29, AlignLeft, AlignTop, FontKeyboard, epc);
|
||||
}
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeRight,
|
||||
"More",
|
||||
uhf_scene_read_card_success_widget_callback,
|
||||
uhf_app);
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Exit",
|
||||
uhf_scene_read_card_success_widget_callback,
|
||||
uhf_app);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
free(epc);
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
bool uhf_scene_read_tag_success_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.event == SceneManagerEventTypeBack) {
|
||||
uhf_app->worker->state = UHFWorkerStateStop;
|
||||
}
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
// if 'exit' is pressed go back to home screen
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneTagMenu);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeCenter) {
|
||||
// consumed = scene_manager_search_and_switch_to_another_scene(
|
||||
// picopass->scene_manager, PicopassSceneStart);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_read_tag_success_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
// // Stop worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
// clear widget
|
||||
widget_reset(uhf_app->widget);
|
||||
}
|
||||
74
applications/external/uhf_rfid/scenes/uhf_scene_save_name.c
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <lib/toolbox/name_generator.h>
|
||||
#include <gui/modules/validators.h>
|
||||
#include <toolbox/path.h>
|
||||
|
||||
void uhf_scene_save_name_text_input_callback(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventTextInputDone);
|
||||
}
|
||||
|
||||
void uhf_scene_save_name_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Setup view
|
||||
TextInput* text_input = uhf_app->text_input;
|
||||
name_generator_make_random(uhf_app->text_store, sizeof(uhf_app->text_store));
|
||||
text_input_set_header_text(text_input, "Name the tag");
|
||||
text_input_set_result_callback(
|
||||
text_input,
|
||||
uhf_scene_save_name_text_input_callback,
|
||||
uhf_app,
|
||||
uhf_app->text_store,
|
||||
UHF_DEV_NAME_MAX_LEN,
|
||||
true);
|
||||
|
||||
FuriString* folder_path;
|
||||
folder_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
|
||||
|
||||
if(furi_string_end_with(uhf_app->uhf_device->load_path, UHF_APP_EXTENSION)) {
|
||||
path_extract_dirname(furi_string_get_cstr(uhf_app->uhf_device->load_path), folder_path);
|
||||
}
|
||||
|
||||
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
|
||||
furi_string_get_cstr(folder_path), UHF_APP_EXTENSION, uhf_app->uhf_device->dev_name);
|
||||
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
|
||||
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewTextInput);
|
||||
|
||||
furi_string_free(folder_path);
|
||||
}
|
||||
|
||||
bool uhf_scene_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == UHFCustomEventTextInputDone) {
|
||||
strlcpy(
|
||||
uhf_app->uhf_device->dev_name,
|
||||
uhf_app->text_store,
|
||||
strlen(uhf_app->text_store) + 1);
|
||||
if(uhf_device_save(uhf_app->uhf_device, uhf_app->text_store)) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSaveSuccess);
|
||||
consumed = true;
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_save_name_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Clear view
|
||||
void* validator_context = text_input_get_validator_callback_context(uhf_app->text_input);
|
||||
text_input_set_validator(uhf_app->text_input, NULL, NULL);
|
||||
validator_is_file_free(validator_context);
|
||||
|
||||
text_input_reset(uhf_app->text_input);
|
||||
}
|
||||
47
applications/external/uhf_rfid/scenes/uhf_scene_save_success.c
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void uhf_scene_save_success_popup_callback(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventViewExit);
|
||||
}
|
||||
|
||||
void uhf_scene_save_success_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
dolphin_deed(DolphinDeedNfcSave);
|
||||
|
||||
// Setup view
|
||||
Popup* popup = uhf_app->popup;
|
||||
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
|
||||
popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
|
||||
popup_set_timeout(popup, 1500);
|
||||
popup_set_context(popup, uhf_app);
|
||||
popup_set_callback(popup, uhf_scene_save_success_popup_callback);
|
||||
popup_enable_timeout(popup);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup);
|
||||
}
|
||||
|
||||
bool uhf_scene_save_success_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == UHFCustomEventViewExit) {
|
||||
if(scene_manager_has_previous_scene(uhf_app->scene_manager, UHFSceneTagMenu)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneTagMenu);
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_save_success_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
}
|
||||
59
applications/external/uhf_rfid/scenes/uhf_scene_saved_menu.c
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexInfo,
|
||||
SubmenuIndexWrite,
|
||||
};
|
||||
|
||||
void uhf_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void uhf_scene_saved_menu_on_enter(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
Submenu* submenu = uhf_app->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu, "Delete", SubmenuIndexDelete, uhf_scene_saved_menu_submenu_callback, uhf_app);
|
||||
submenu_add_item(
|
||||
submenu, "Info", SubmenuIndexInfo, uhf_scene_saved_menu_submenu_callback, uhf_app);
|
||||
submenu_add_item(
|
||||
submenu, "Write", SubmenuIndexWrite, uhf_scene_saved_menu_submenu_callback, uhf_app);
|
||||
|
||||
submenu_set_selected_item(
|
||||
uhf_app->submenu,
|
||||
scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneSavedMenu));
|
||||
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu);
|
||||
}
|
||||
|
||||
bool uhf_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(uhf_app->scene_manager, UHFSceneSavedMenu, event.event);
|
||||
|
||||
if(event.event == SubmenuIndexDelete) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDelete);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexInfo) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneDeviceInfo);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWrite) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneWriteTag);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_saved_menu_on_exit(void* context) {
|
||||
UHFApp* uhf_app = context;
|
||||
|
||||
submenu_reset(uhf_app->submenu);
|
||||
}
|
||||
122
applications/external/uhf_rfid/scenes/uhf_scene_settings.c
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include "../uhf_module.h"
|
||||
|
||||
void uhf_settings_set_module_baudrate(VariableItem* item) {
|
||||
M100Module* uhf_module = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
if(index >= BAUD_RATES_COUNT) {
|
||||
return;
|
||||
}
|
||||
uint32_t baudrate = BAUD_RATES[index];
|
||||
m100_set_baudrate(uhf_module, baudrate);
|
||||
char text_buf[10];
|
||||
snprintf(text_buf, sizeof(text_buf), "%lu", uhf_module->baudrate);
|
||||
variable_item_set_current_value_text(item, text_buf);
|
||||
}
|
||||
|
||||
void uhf_settings_set_module_powerdb(VariableItem* item) {
|
||||
M100Module* uhf_module = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
if(index >= POWER_DBM_COUNT) {
|
||||
return;
|
||||
}
|
||||
uint16_t power = POWER_DBM[index];
|
||||
m100_set_transmitting_power(uhf_module, power);
|
||||
char text_buf[10];
|
||||
snprintf(text_buf, sizeof(text_buf), "%ddBm", uhf_module->transmitting_power);
|
||||
variable_item_set_current_value_text(item, text_buf);
|
||||
}
|
||||
|
||||
void uhf_settings_set_module_working_region(VariableItem* item) {
|
||||
M100Module* uhf_module = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
if(index >= WORKING_REGIONS_COUNT) {
|
||||
return;
|
||||
}
|
||||
WorkingRegion region = WORKING_REGIONS[index];
|
||||
m100_set_working_region(uhf_module, region);
|
||||
variable_item_set_current_value_text(item, WORKING_REGIONS_STR[index]);
|
||||
}
|
||||
|
||||
uint8_t uhf_settings_get_module_baudrate_index(M100Module* module) {
|
||||
for(uint8_t i = 0; i < BAUD_RATES_COUNT; i++) {
|
||||
if(BAUD_RATES[i] == module->baudrate) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t uhf_settings_get_module_power_index(M100Module* module) {
|
||||
for(uint8_t i = 0; i < BAUD_RATES_COUNT; i++) {
|
||||
if(POWER_DBM[i] == module->transmitting_power) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t uhf_settings_get_module_working_region_index(M100Module* module) {
|
||||
for(uint8_t i = 0; i < WORKING_REGIONS_COUNT; i++) {
|
||||
if(WORKING_REGIONS[i] == module->region) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void uhf_scene_settings_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
M100Module* uhf_module = uhf_app->worker->module;
|
||||
VariableItem* item;
|
||||
VariableItemList* variable_item_list = uhf_app->variable_item_list;
|
||||
|
||||
uint8_t value_index = uhf_settings_get_module_baudrate_index(uhf_module);
|
||||
char text_buf[10];
|
||||
snprintf(text_buf, sizeof(text_buf), "%lu", uhf_module->baudrate);
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Baudrate:",
|
||||
BAUD_RATES_COUNT,
|
||||
uhf_settings_set_module_baudrate,
|
||||
uhf_module);
|
||||
|
||||
variable_item_set_current_value_text(item, text_buf);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
|
||||
value_index = uhf_settings_get_module_power_index(uhf_module);
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Power(DBM):",
|
||||
POWER_DBM_COUNT,
|
||||
uhf_settings_set_module_powerdb,
|
||||
uhf_module);
|
||||
snprintf(text_buf, sizeof(text_buf), "%ddBm", uhf_module->transmitting_power);
|
||||
variable_item_set_current_value_text(item, text_buf);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
|
||||
value_index = uhf_settings_get_module_working_region_index(uhf_module);
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Region:",
|
||||
WORKING_REGIONS_COUNT,
|
||||
uhf_settings_set_module_working_region,
|
||||
uhf_module);
|
||||
variable_item_set_current_value_text(item, WORKING_REGIONS_STR[value_index]);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewVariableItemList);
|
||||
}
|
||||
|
||||
bool uhf_scene_settings_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
UNUSED(uhf_app);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void uhf_scene_settings_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
variable_item_list_set_selected_item(uhf_app->variable_item_list, 0);
|
||||
variable_item_list_reset(uhf_app->variable_item_list);
|
||||
}
|
||||
54
applications/external/uhf_rfid/scenes/uhf_scene_start.c
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
enum SubmenuIndex { SubmenuIndexRead, SubmenuIndexSaved, SubmenuIndexSettings };
|
||||
|
||||
void uhf_scene_start_submenu_callback(void* ctx, uint32_t index) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void uhf_scene_start_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
Submenu* submenu = uhf_app->submenu;
|
||||
submenu_add_item(
|
||||
submenu, "Read Tag", SubmenuIndexRead, uhf_scene_start_submenu_callback, uhf_app);
|
||||
submenu_add_item(
|
||||
submenu, "Saved", SubmenuIndexSaved, uhf_scene_start_submenu_callback, uhf_app);
|
||||
submenu_add_item(
|
||||
submenu, "Settings", SubmenuIndexSettings, uhf_scene_start_submenu_callback, uhf_app);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneStart));
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu);
|
||||
}
|
||||
|
||||
bool uhf_scene_start_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexRead) {
|
||||
scene_manager_set_scene_state(uhf_app->scene_manager, UHFSceneStart, SubmenuIndexRead);
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneReadTag);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexSaved) {
|
||||
// Explicitly save state so that the correct item is
|
||||
// reselected if the user cancels loading a file.
|
||||
scene_manager_set_scene_state(
|
||||
uhf_app->scene_manager, UHFSceneStart, SubmenuIndexSaved);
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneFileSelect);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexSettings) {
|
||||
scene_manager_set_scene_state(
|
||||
uhf_app->scene_manager, UHFSceneStart, SubmenuIndexSettings);
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSettings);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_start_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
submenu_reset(uhf_app->submenu);
|
||||
}
|
||||
58
applications/external/uhf_rfid/scenes/uhf_scene_tag_menu.c
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexSave,
|
||||
SubmenuIndexChangeKey,
|
||||
};
|
||||
|
||||
void uhf_scene_tag_menu_submenu_callback(void* ctx, uint32_t index) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void uhf_scene_tag_menu_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
Submenu* submenu = uhf_app->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu, "Save", SubmenuIndexSave, uhf_scene_tag_menu_submenu_callback, uhf_app);
|
||||
submenu_add_item(
|
||||
submenu, "Change Key", SubmenuIndexChangeKey, uhf_scene_tag_menu_submenu_callback, uhf_app);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(uhf_app->scene_manager, UHFSceneTagMenu));
|
||||
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewMenu);
|
||||
}
|
||||
|
||||
bool uhf_scene_tag_menu_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexSave) {
|
||||
scene_manager_set_scene_state(
|
||||
uhf_app->scene_manager, UHFSceneTagMenu, SubmenuIndexSave);
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneSaveName);
|
||||
consumed = true;
|
||||
}
|
||||
// else if(event.event == SubmenuIndexChangeKey) {
|
||||
// scene_manager_set_scene_state(
|
||||
// picopass->scene_manager, UHFSceneTagMenu, SubmenuIndexChangeKey);
|
||||
// scene_manager_next_scene(picopass->scene_manager, PicopassSceneKeyMenu);
|
||||
// consumed = true;
|
||||
// }
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_tag_menu_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
submenu_reset(uhf_app->submenu);
|
||||
}
|
||||
147
applications/external/uhf_rfid/scenes/uhf_scene_verify.c
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "../uhf_app_i.h"
|
||||
|
||||
bool verify_success = false;
|
||||
FuriString* temp_str;
|
||||
|
||||
void uhf_scene_verify_callback_event(UHFWorkerEvent event, void* ctx) {
|
||||
UNUSED(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
if(event == UHFWorkerEventSuccess) verify_success = true;
|
||||
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventVerifyDone);
|
||||
}
|
||||
|
||||
void uhf_scene_verify_widget_callback(GuiButtonType result, InputType type, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void uhf_scene_verify_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
uhf_worker_start(
|
||||
uhf_app->worker, UHFWorkerStateVerify, uhf_scene_verify_callback_event, uhf_app);
|
||||
temp_str = furi_string_alloc();
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
}
|
||||
|
||||
bool uhf_scene_verify_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.event == SceneManagerEventTypeBack) {
|
||||
uhf_app->worker->state = UHFWorkerStateStop;
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneStart);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeLeft) {
|
||||
if(!verify_success) {
|
||||
widget_reset(uhf_app->widget);
|
||||
furi_string_reset(temp_str);
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// furi_hal_gpio_write(&gpio_ext, false);
|
||||
// furi_delay_ms(50);
|
||||
// furi_hal_gpio_write(&gpio_ext_pa7, true);
|
||||
// furi_delay_ms(50);
|
||||
uhf_worker_start(
|
||||
uhf_app->worker,
|
||||
UHFWorkerStateVerify,
|
||||
uhf_scene_verify_callback_event,
|
||||
uhf_app);
|
||||
}
|
||||
} else if(event.event == UHFCustomEventVerifyDone) {
|
||||
if(verify_success) {
|
||||
widget_reset(uhf_app->widget);
|
||||
furi_string_reset(temp_str);
|
||||
M100Module* module = uhf_app->worker->module;
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 64, 5, AlignCenter, AlignCenter, FontPrimary, "Module Info");
|
||||
// hardware info
|
||||
furi_string_cat_str(temp_str, "HW Version: ");
|
||||
furi_string_cat_str(temp_str, module->info->hw_version);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
1,
|
||||
15,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
furi_string_get_cstr(temp_str));
|
||||
furi_string_reset(temp_str);
|
||||
// software info
|
||||
furi_string_cat_str(temp_str, "SW Version: ");
|
||||
furi_string_cat_str(temp_str, module->info->sw_version);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
1,
|
||||
27,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
furi_string_get_cstr(temp_str));
|
||||
furi_string_reset(temp_str);
|
||||
// manufacturer info
|
||||
furi_string_cat_str(temp_str, "Manufacturer: ");
|
||||
furi_string_cat_str(temp_str, module->info->manufacturer);
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
1,
|
||||
39,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
furi_string_get_cstr(temp_str));
|
||||
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Continue",
|
||||
uhf_scene_verify_widget_callback,
|
||||
uhf_app);
|
||||
} else {
|
||||
widget_add_string_element(
|
||||
uhf_app->widget,
|
||||
64,
|
||||
5,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
FontPrimary,
|
||||
"No UHF Module found");
|
||||
widget_add_string_multiline_element(
|
||||
uhf_app->widget,
|
||||
64,
|
||||
30,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
FontSecondary,
|
||||
"Please refer to the git@frux-c/uhf_rfid for help.");
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Retry",
|
||||
uhf_scene_verify_widget_callback,
|
||||
uhf_app);
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Skip",
|
||||
uhf_scene_verify_widget_callback,
|
||||
uhf_app);
|
||||
}
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_verify_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
// Clear string
|
||||
furi_string_free(temp_str);
|
||||
// Stop worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// clear widget
|
||||
widget_reset(uhf_app->widget);
|
||||
}
|
||||
49
applications/external/uhf_rfid/scenes/uhf_scene_write_tag.c
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void uhf_write_tag_worker_callback(UHFWorkerEvent event, void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
if(event == UHFWorkerEventSuccess) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, UHFCustomEventWorkerExit);
|
||||
}
|
||||
// } else if(event == UHFWorkerEventAborted) {
|
||||
// scene_manager_search_and_switch_to_previous_scene(uhf_app->scene_manager, UHFSceneStart);
|
||||
// }
|
||||
}
|
||||
|
||||
void uhf_scene_write_tag_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
dolphin_deed(DolphinDeedNfcRead);
|
||||
|
||||
// Setup view
|
||||
Popup* popup = uhf_app->popup;
|
||||
popup_set_header(popup, "Writing\n[UHF] RFID\nTag", 68, 30, AlignLeft, AlignTop);
|
||||
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
|
||||
|
||||
// Start worker
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewPopup);
|
||||
uhf_worker_start(
|
||||
uhf_app->worker, UHFWorkerStateWriteSingle, uhf_write_tag_worker_callback, uhf_app);
|
||||
|
||||
uhf_blink_start(uhf_app);
|
||||
}
|
||||
|
||||
bool uhf_scene_write_tag_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.event == UHFCustomEventWorkerExit) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneWriteTagSuccess);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_write_tag_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
// Stop worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
|
||||
uhf_blink_stop(uhf_app);
|
||||
}
|
||||
94
applications/external/uhf_rfid/scenes/uhf_scene_write_tag_success.c
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "../uhf_app_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void uhf_write_tag_success_worker_callback(UHFWorkerEvent event, void* ctx) {
|
||||
UNUSED(event);
|
||||
UNUSED(ctx);
|
||||
}
|
||||
|
||||
void uhf_scene_write_tag_success_widget_callback(GuiButtonType result, InputType type, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(uhf_app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void uhf_scene_write_tag_success_on_enter(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
dolphin_deed(DolphinDeedNfcReadSuccess);
|
||||
|
||||
// Send notification
|
||||
notification_message(uhf_app->notifications, &sequence_success);
|
||||
|
||||
widget_add_string_element(
|
||||
uhf_app->widget, 32, 5, AlignLeft, AlignCenter, FontPrimary, "Write Success");
|
||||
|
||||
// widget_add_string_element(uhf_app->widget, 3, 18, AlignLeft, AlignCenter, FontPrimary, "PC :");
|
||||
|
||||
// widget_add_string_element(
|
||||
// uhf_app->widget, 66, 18, AlignLeft, AlignCenter, FontPrimary, "CRC :");
|
||||
|
||||
// widget_add_string_element(
|
||||
// uhf_app->widget, 3, 32, AlignLeft, AlignCenter, FontPrimary, "EPC :");
|
||||
|
||||
// char* pc = convertToHexString(uhf_tag->pc, 2);
|
||||
// widget_add_string_element(uhf_app->widget, 26, 19, AlignLeft, AlignCenter, FontKeyboard, pc);
|
||||
// char* crc = convertToHexString(uhf_tag->crc, 2);
|
||||
// widget_add_string_element(uhf_app->widget, 96, 19, AlignLeft, AlignCenter, FontKeyboard, crc);
|
||||
// char* epc = convertToHexString(uhf_tag->epc + 2, uhf_tag->epc_length - 2);
|
||||
// widget_add_string_multiline_element(
|
||||
// uhf_app->widget, 34, 29, AlignLeft, AlignTop, FontKeyboard, epc);
|
||||
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeRight,
|
||||
"More",
|
||||
uhf_scene_write_tag_success_widget_callback,
|
||||
uhf_app);
|
||||
widget_add_button_element(
|
||||
uhf_app->widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Exit",
|
||||
uhf_scene_write_tag_success_widget_callback,
|
||||
uhf_app);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
// free(pc);
|
||||
// free(crc);
|
||||
// free(epc);
|
||||
}
|
||||
|
||||
bool uhf_scene_write_tag_success_on_event(void* ctx, SceneManagerEvent event) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
bool consumed = false;
|
||||
if(event.event == SceneManagerEventTypeBack) {
|
||||
uhf_app->worker->state = UHFWorkerStateStop;
|
||||
}
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
// if 'exit' is pressed go back to home screen
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
uhf_app->scene_manager, UHFSceneStart);
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneTagMenu);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeCenter) {
|
||||
// consumed = scene_manager_search_and_switch_to_another_scene(
|
||||
// picopass->scene_manager, PicopassSceneStart);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void uhf_scene_write_tag_success_on_exit(void* ctx) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
// // Stop worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
// Clear view
|
||||
popup_reset(uhf_app->popup);
|
||||
// clear widget
|
||||
widget_reset(uhf_app->widget);
|
||||
}
|
||||
BIN
applications/external/uhf_rfid/uhf_10px.png
vendored
Normal file
|
After Width: | Height: | Size: 148 B |
214
applications/external/uhf_rfid/uhf_app.c
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
#include "uhf_app_i.h"
|
||||
|
||||
char* convertToHexString(uint8_t* array, size_t length) {
|
||||
if(array == NULL || length == 0) {
|
||||
return " ";
|
||||
}
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
furi_string_cat_printf(temp_str, "%02X ", array[i]);
|
||||
}
|
||||
const char* furi_str = furi_string_get_cstr(temp_str);
|
||||
|
||||
size_t str_len = strlen(furi_str);
|
||||
char* str = (char*)malloc(sizeof(char) * str_len);
|
||||
|
||||
memcpy(str, furi_str, str_len);
|
||||
furi_string_free(temp_str);
|
||||
return str;
|
||||
}
|
||||
|
||||
bool uhf_custom_event_callback(void* ctx, uint32_t event) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
return scene_manager_handle_custom_event(uhf_app->scene_manager, event);
|
||||
}
|
||||
|
||||
bool uhf_back_event_callback(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
return scene_manager_handle_back_event(uhf_app->scene_manager);
|
||||
}
|
||||
|
||||
void uhf_tick_event_callback(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
UHFApp* uhf_app = ctx;
|
||||
scene_manager_handle_tick_event(uhf_app->scene_manager);
|
||||
}
|
||||
|
||||
UHFApp* uhf_alloc() {
|
||||
UHFApp* uhf_app = (UHFApp*)malloc(sizeof(UHFApp));
|
||||
uhf_app->view_dispatcher = view_dispatcher_alloc();
|
||||
uhf_app->scene_manager = scene_manager_alloc(&uhf_scene_handlers, uhf_app);
|
||||
view_dispatcher_enable_queue(uhf_app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(uhf_app->view_dispatcher, uhf_app);
|
||||
view_dispatcher_set_custom_event_callback(uhf_app->view_dispatcher, uhf_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
uhf_app->view_dispatcher, uhf_back_event_callback);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
uhf_app->view_dispatcher, uhf_tick_event_callback, 100);
|
||||
|
||||
// Open GUI record
|
||||
uhf_app->gui = furi_record_open(RECORD_GUI);
|
||||
view_dispatcher_attach_to_gui(
|
||||
uhf_app->view_dispatcher, uhf_app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Variable Item List
|
||||
uhf_app->variable_item_list = variable_item_list_alloc();
|
||||
|
||||
//worker
|
||||
uhf_app->worker = uhf_worker_alloc();
|
||||
|
||||
// device
|
||||
uhf_app->uhf_device = uhf_device_alloc();
|
||||
|
||||
UHFTagWrapper* uhf_tag_wrapper = uhf_tag_wrapper_alloc();
|
||||
|
||||
// // point tag object to worker
|
||||
uhf_app->worker->uhf_tag_wrapper = uhf_tag_wrapper;
|
||||
uhf_app->uhf_device->uhf_tag_wrapper = uhf_tag_wrapper;
|
||||
|
||||
// Open Notification record
|
||||
uhf_app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// Variable Item List
|
||||
uhf_app->variable_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher,
|
||||
UHFViewVariableItemList,
|
||||
variable_item_list_get_view(uhf_app->variable_item_list));
|
||||
|
||||
// Submenu
|
||||
uhf_app->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher, UHFViewMenu, submenu_get_view(uhf_app->submenu));
|
||||
|
||||
// Popup
|
||||
uhf_app->popup = popup_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher, UHFViewPopup, popup_get_view(uhf_app->popup));
|
||||
|
||||
// Loading
|
||||
uhf_app->loading = loading_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher, UHFViewLoading, loading_get_view(uhf_app->loading));
|
||||
|
||||
// Text Input
|
||||
uhf_app->text_input = text_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher, UHFViewTextInput, text_input_get_view(uhf_app->text_input));
|
||||
|
||||
// Custom Widget
|
||||
uhf_app->widget = widget_alloc();
|
||||
view_dispatcher_add_view(
|
||||
uhf_app->view_dispatcher, UHFViewWidget, widget_get_view(uhf_app->widget));
|
||||
|
||||
return uhf_app;
|
||||
}
|
||||
|
||||
void uhf_free(UHFApp* uhf_app) {
|
||||
furi_assert(uhf_app);
|
||||
|
||||
// Submenu
|
||||
view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewMenu);
|
||||
submenu_free(uhf_app->submenu);
|
||||
|
||||
// Popup
|
||||
view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewPopup);
|
||||
popup_free(uhf_app->popup);
|
||||
|
||||
// Loading
|
||||
view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewLoading);
|
||||
loading_free(uhf_app->loading);
|
||||
|
||||
// TextInput
|
||||
view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewTextInput);
|
||||
text_input_free(uhf_app->text_input);
|
||||
|
||||
// Custom Widget
|
||||
view_dispatcher_remove_view(uhf_app->view_dispatcher, UHFViewWidget);
|
||||
widget_free(uhf_app->widget);
|
||||
|
||||
// Tag
|
||||
uhf_tag_wrapper_free(uhf_app->worker->uhf_tag_wrapper);
|
||||
|
||||
// Worker
|
||||
uhf_worker_stop(uhf_app->worker);
|
||||
uhf_worker_free(uhf_app->worker);
|
||||
|
||||
// Device
|
||||
uhf_device_free(uhf_app->uhf_device);
|
||||
|
||||
// View Dispatcher
|
||||
view_dispatcher_free(uhf_app->view_dispatcher);
|
||||
|
||||
// Scene Manager
|
||||
scene_manager_free(uhf_app->scene_manager);
|
||||
|
||||
// GUI
|
||||
furi_record_close(RECORD_GUI);
|
||||
uhf_app->gui = NULL;
|
||||
|
||||
// Variable Item List
|
||||
variable_item_list_free(uhf_app->variable_item_list);
|
||||
|
||||
// Notifications
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
uhf_app->notifications = NULL;
|
||||
|
||||
free(uhf_app);
|
||||
}
|
||||
|
||||
static const NotificationSequence uhf_sequence_blink_start_cyan = {
|
||||
&message_blink_start_10,
|
||||
&message_blink_set_color_cyan,
|
||||
&message_do_not_reset,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const NotificationSequence uhf_sequence_blink_stop = {
|
||||
&message_blink_stop,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void uhf_blink_start(UHFApp* uhf_app) {
|
||||
notification_message(uhf_app->notifications, &uhf_sequence_blink_start_cyan);
|
||||
}
|
||||
|
||||
void uhf_blink_stop(UHFApp* uhf_app) {
|
||||
notification_message(uhf_app->notifications, &uhf_sequence_blink_stop);
|
||||
}
|
||||
|
||||
void uhf_show_loading_popup(void* ctx, bool show) {
|
||||
UHFApp* uhf_app = ctx;
|
||||
|
||||
if(show) {
|
||||
// Raise timer priority so that animations can play
|
||||
furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
|
||||
view_dispatcher_switch_to_view(uhf_app->view_dispatcher, UHFViewLoading);
|
||||
} else {
|
||||
// Restore default timer priority
|
||||
furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t uhf_app_main(void* ctx) {
|
||||
UNUSED(ctx);
|
||||
UHFApp* uhf_app = uhf_alloc();
|
||||
|
||||
// enable 5v pin
|
||||
furi_hal_power_enable_otg();
|
||||
// init pin a2
|
||||
// furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull);
|
||||
furi_hal_uart_set_br(FuriHalUartIdUSART1, DEFAULT_BAUDRATE);
|
||||
scene_manager_next_scene(uhf_app->scene_manager, UHFSceneVerify);
|
||||
view_dispatcher_run(uhf_app->view_dispatcher);
|
||||
|
||||
// disable 5v pin
|
||||
furi_hal_power_disable_otg();
|
||||
// furi_hal_gpio_disable_int_callback()
|
||||
// exit app
|
||||
uhf_free(uhf_app);
|
||||
return 0;
|
||||
}
|
||||
3
applications/external/uhf_rfid/uhf_app.h
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct UHFApp UHFApp;
|
||||
110
applications/external/uhf_rfid/uhf_app_i.h
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
|
||||
#include <input/input.h>
|
||||
|
||||
#include "uhf_app.h"
|
||||
#include "uhf_worker.h"
|
||||
#include "uhf_device.h"
|
||||
#include "scenes/uhf_scene.h"
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include <uhf_rfid_icons.h>
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define UHF_TEXT_STORE_SIZE 128
|
||||
// #define UHF_APPS_DATA_FOLDER EXT_PATH("apps_data")
|
||||
// #define UHF_APPS_STORAGE_FOLDER
|
||||
// UHF_APPS_DATA_FOLDER "/"
|
||||
// "uhf_rfid"
|
||||
// #define UHF_FILE_EXTENSION ".uhf"
|
||||
|
||||
enum UHFCustomEvent {
|
||||
// Reserve first 100 events for button types and indexes, starting from 0
|
||||
UHFCustomEventReserved = 100,
|
||||
|
||||
UHFCustomEventVerifyDone,
|
||||
UHFCustomEventViewExit,
|
||||
UHFCustomEventWorkerExit,
|
||||
UHFCustomEventByteInputDone,
|
||||
UHFCustomEventTextInputDone,
|
||||
UHFCustomEventSceneSettingLock,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
struct UHFApp {
|
||||
UHFWorker* worker;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Gui* gui;
|
||||
NotificationApp* notifications;
|
||||
SceneManager* scene_manager;
|
||||
VariableItemList* variable_item_list;
|
||||
// Storage* storage;
|
||||
UHFDevice* uhf_device;
|
||||
char text_store[UHF_TEXT_STORE_SIZE + 1];
|
||||
FuriString* text_box_store;
|
||||
// Common Views
|
||||
Submenu* submenu;
|
||||
Popup* popup;
|
||||
Loading* loading;
|
||||
TextInput* text_input;
|
||||
Widget* widget;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
UHFViewMenu,
|
||||
UHFViewPopup,
|
||||
UHFViewLoading,
|
||||
UHFViewTextInput,
|
||||
UHFViewWidget,
|
||||
UHFViewVariableItemList,
|
||||
} UHFView;
|
||||
|
||||
UHFApp* uhf_app_alloc();
|
||||
|
||||
void uhf_text_store_set(UHFApp* uhf, const char* text, ...);
|
||||
|
||||
void uhf_text_store_clear(UHFApp* uhf);
|
||||
|
||||
void uhf_blink_start(UHFApp* uhf);
|
||||
|
||||
void uhf_blink_stop(UHFApp* uhf);
|
||||
|
||||
void uhf_show_loading_popup(void* context, bool show);
|
||||
|
||||
/** Check if memory is set to pattern
|
||||
*
|
||||
* @warning zero size will return false
|
||||
*
|
||||
* @param[in] data Pointer to the byte array
|
||||
* @param[in] pattern The pattern
|
||||
* @param[in] size The byte array size
|
||||
*
|
||||
* @return True if memory is set to pattern, false otherwise
|
||||
*/
|
||||
bool uhf_is_memset(const uint8_t* data, const uint8_t pattern, size_t size);
|
||||
|
||||
char* convertToHexString(uint8_t* array, size_t length);
|
||||
|
||||
// bool uhf_save_read_data(UHFResponseData* uhf_response_data, Storage* storage, const char* filename);
|
||||
69
applications/external/uhf_rfid/uhf_buffer.c
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "uhf_buffer.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
Buffer* buffer_alloc(size_t initial_capacity) {
|
||||
Buffer* buf = (Buffer*)malloc(sizeof(Buffer));
|
||||
buf->data = (uint8_t*)malloc(sizeof(uint8_t) * initial_capacity);
|
||||
if(!buf->data) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
buf->size = 0;
|
||||
buf->capacity = initial_capacity;
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool buffer_append_single(Buffer* buf, uint8_t data) {
|
||||
if(buf->closed) return false;
|
||||
if(buf->size + 1 > buf->capacity) {
|
||||
size_t new_capacity = buf->capacity * 2;
|
||||
uint8_t* new_data = (uint8_t*)realloc(buf->data, sizeof(uint8_t) * new_capacity);
|
||||
if(!new_data) return false;
|
||||
buf->data = new_data;
|
||||
buf->capacity = new_capacity;
|
||||
}
|
||||
buf->data[buf->size++] = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool buffer_append(Buffer* buf, uint8_t* data, size_t data_size) {
|
||||
if(buf->closed) return false;
|
||||
if(buf->size + data_size > buf->capacity) {
|
||||
size_t new_capacity = buf->capacity * 2;
|
||||
uint8_t* new_data = (uint8_t*)realloc(buf->data, new_capacity);
|
||||
if(!new_data) return false;
|
||||
|
||||
buf->data = new_data;
|
||||
buf->capacity = new_capacity;
|
||||
}
|
||||
|
||||
memcpy((void*)&buf->data[buf->size], data, data_size);
|
||||
buf->size += data_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t* buffer_get_data(Buffer* buf) {
|
||||
return buf->data;
|
||||
}
|
||||
|
||||
size_t buffer_get_size(Buffer* buf) {
|
||||
return buf->size;
|
||||
}
|
||||
|
||||
void buffer_close(Buffer* buf) {
|
||||
buf->closed = true;
|
||||
}
|
||||
|
||||
void buffer_reset(Buffer* buf) {
|
||||
for(size_t i = 0; i < MAX_BUFFER_SIZE; i++) {
|
||||
buf->data[i] = 0;
|
||||
}
|
||||
buf->size = 0;
|
||||
buf->closed = false;
|
||||
}
|
||||
|
||||
void buffer_free(Buffer* buf) {
|
||||
free(buf->data);
|
||||
free(buf);
|
||||
}
|
||||
22
applications/external/uhf_rfid/uhf_buffer.h
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define MAX_BUFFER_SIZE 200
|
||||
|
||||
typedef struct Buffer {
|
||||
uint8_t* data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
bool closed;
|
||||
} Buffer;
|
||||
|
||||
Buffer* buffer_alloc(size_t inital_capacity);
|
||||
bool buffer_append_single(Buffer* buf, uint8_t value);
|
||||
bool buffer_append(Buffer* buf, uint8_t* data, size_t size);
|
||||
uint8_t* buffer_get_data(Buffer* buf);
|
||||
size_t buffer_get_size(Buffer* buf);
|
||||
void buffer_close(Buffer* buf);
|
||||
void buffer_reset(Buffer* buf);
|
||||
void buffer_free(Buffer* buf);
|
||||
350
applications/external/uhf_rfid/uhf_device.c
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
#include "uhf_device.h"
|
||||
#include <toolbox/path.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <uhf_rfid_icons.h>
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "UHFDevice"
|
||||
|
||||
static const char* uhf_file_header = "Flipper UHF RFID device";
|
||||
static const uint32_t uhf_file_version = 1;
|
||||
// static const uint8_t bank_data_start = 20;
|
||||
// static const uint8_t bank_data_length = 16;
|
||||
|
||||
UHFDevice* uhf_device_alloc() {
|
||||
UHFDevice* uhf_device = malloc(sizeof(UHFDevice));
|
||||
uhf_device->storage = furi_record_open(RECORD_STORAGE);
|
||||
uhf_device->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
uhf_device->load_path = furi_string_alloc();
|
||||
return uhf_device;
|
||||
}
|
||||
|
||||
void uhf_device_set_name(UHFDevice* dev, const char* name) {
|
||||
furi_assert(dev);
|
||||
strlcpy(dev->dev_name, name, UHF_DEV_NAME_MAX_LEN);
|
||||
}
|
||||
|
||||
static bool uhf_device_save_file(
|
||||
UHFDevice* dev,
|
||||
const char* dev_name,
|
||||
const char* folder,
|
||||
const char* extension,
|
||||
bool use_load_path) {
|
||||
furi_assert(dev);
|
||||
|
||||
UHFTag* uhf_tag = dev->uhf_tag_wrapper->uhf_tag;
|
||||
bool saved = false;
|
||||
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
do {
|
||||
if(use_load_path && !furi_string_empty(dev->load_path)) {
|
||||
// Get directory name
|
||||
path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str);
|
||||
// Make path to file to save
|
||||
furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension);
|
||||
} else {
|
||||
// First remove uhf device file if it was saved
|
||||
furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
|
||||
}
|
||||
// Open file
|
||||
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
|
||||
|
||||
// Write header
|
||||
if(!flipper_format_write_header_cstr(file, uhf_file_header, uhf_file_version)) break;
|
||||
|
||||
// Reserved bank might be added
|
||||
// todo : maybe
|
||||
uint32_t temp_arr[1];
|
||||
uint8_t temp_arr2[2];
|
||||
// write pc
|
||||
temp_arr2[0] = (uint8_t)(uhf_tag_get_epc_pc(uhf_tag) >> 8) & 0xFF;
|
||||
temp_arr2[1] = (uint8_t)(uhf_tag_get_epc_pc(uhf_tag) & 0xFF);
|
||||
if(!flipper_format_write_hex(file, UHF_EPC_PC_LABEL, temp_arr2, 2)) break;
|
||||
// write crc
|
||||
temp_arr2[0] = (uint8_t)(uhf_tag_get_epc_crc(uhf_tag) >> 8) & 0xFF;
|
||||
temp_arr2[1] = (uint8_t)(uhf_tag_get_epc_crc(uhf_tag) & 0xFF);
|
||||
if(!flipper_format_write_hex(file, UHF_EPC_CRC_LABEL, temp_arr2, 2)) break;
|
||||
// write epc
|
||||
temp_arr[0] = uhf_tag_get_epc_size(uhf_tag);
|
||||
if(!flipper_format_write_uint32(file, UHF_EPC_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, UHF_EPC_BANK_LABEL, uhf_tag_get_epc(uhf_tag), uhf_tag_get_epc_size(uhf_tag)))
|
||||
break;
|
||||
// write tid
|
||||
temp_arr[0] = uhf_tag_get_tid_size(uhf_tag);
|
||||
if(!flipper_format_write_uint32(file, UHF_TID_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, UHF_TID_BANK_LABEL, uhf_tag_get_tid(uhf_tag), uhf_tag_get_tid_size(uhf_tag)))
|
||||
break;
|
||||
// write user
|
||||
temp_arr[0] = uhf_tag_get_user_size(uhf_tag);
|
||||
if(!flipper_format_write_uint32(file, UHF_USER_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
file,
|
||||
UHF_USER_BANK_LABEL,
|
||||
uhf_tag_get_user(uhf_tag),
|
||||
uhf_tag_get_user_size(uhf_tag)))
|
||||
break;
|
||||
saved = true;
|
||||
} while(0);
|
||||
|
||||
if(!saved) {
|
||||
dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile");
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
flipper_format_free(file);
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool uhf_device_save(UHFDevice* dev, const char* dev_name) {
|
||||
return uhf_device_save_file(
|
||||
dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, UHF_APP_EXTENSION, true);
|
||||
|
||||
return false;
|
||||
}
|
||||
// uncomment
|
||||
|
||||
static bool uhf_device_load_data(UHFDevice* dev, FuriString* path, bool show_dialog) {
|
||||
bool parsed = false;
|
||||
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
|
||||
// UHFResponseData* uhf_response_data = dev->dev_data;
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
bool deprecated_version = false;
|
||||
UHFTag* uhf_tag = uhf_tag_alloc();
|
||||
uhf_tag_reset(uhf_tag);
|
||||
uint32_t temp_arr[1];
|
||||
if(dev->loading_cb) {
|
||||
dev->loading_cb(dev->loading_cb_ctx, true);
|
||||
}
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break;
|
||||
|
||||
// Read and verify file header
|
||||
uint32_t version = 0;
|
||||
if(!flipper_format_read_header(file, temp_str, &version)) break;
|
||||
if(furi_string_cmp_str(temp_str, uhf_file_header) || (version != uhf_file_version)) {
|
||||
deprecated_version = true;
|
||||
break;
|
||||
}
|
||||
// read pc
|
||||
uint8_t temp_arr2[2];
|
||||
if(!flipper_format_read_hex(file, UHF_EPC_PC_LABEL, temp_arr2, 2)) break;
|
||||
uhf_tag_set_epc_pc(uhf_tag, (temp_arr2[0] << 8) + temp_arr2[1]);
|
||||
// read crc
|
||||
if(!flipper_format_read_hex(file, UHF_EPC_CRC_LABEL, temp_arr2, 2)) break;
|
||||
uhf_tag_set_epc_crc(uhf_tag, (temp_arr2[0] << 8) + temp_arr2[1]);
|
||||
// read epc
|
||||
if(!flipper_format_read_uint32(file, UHF_EPC_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
uhf_tag_set_epc_size(uhf_tag, temp_arr[0]);
|
||||
if(!flipper_format_read_hex(
|
||||
file, UHF_EPC_BANK_LABEL, uhf_tag_get_epc(uhf_tag), uhf_tag_get_epc_size(uhf_tag)))
|
||||
break;
|
||||
|
||||
// read tid
|
||||
if(!flipper_format_read_uint32(file, UHF_TID_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
uhf_tag_set_tid_size(uhf_tag, temp_arr[0]);
|
||||
if(!flipper_format_read_hex(
|
||||
file, UHF_TID_BANK_LABEL, uhf_tag_get_tid(uhf_tag), uhf_tag_get_tid_size(uhf_tag)))
|
||||
break;
|
||||
// read user
|
||||
if(!flipper_format_read_uint32(file, UHF_USER_BANK_LENGTH_LABEL, temp_arr, 1)) break;
|
||||
uhf_tag_set_user_size(uhf_tag, temp_arr[0]);
|
||||
if(!flipper_format_read_hex(
|
||||
file,
|
||||
UHF_USER_BANK_LABEL,
|
||||
uhf_tag_get_user(uhf_tag),
|
||||
uhf_tag_get_user_size(uhf_tag)))
|
||||
break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
if(dev->loading_cb) {
|
||||
dev->loading_cb(dev->loading_cb_ctx, false);
|
||||
}
|
||||
|
||||
if((!parsed) && (show_dialog)) {
|
||||
if(deprecated_version) {
|
||||
dialog_message_show_storage_error(dev->dialogs, "File format deprecated");
|
||||
} else {
|
||||
dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile");
|
||||
}
|
||||
}
|
||||
uhf_tag_wrapper_set_tag(dev->uhf_tag_wrapper, uhf_tag);
|
||||
furi_string_free(temp_str);
|
||||
flipper_format_free(file);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// void picopass_device_clear(UHFDevice* dev) {
|
||||
// furi_assert(dev);
|
||||
|
||||
// picopass_device_data_clear(&dev->dev_data);
|
||||
// memset(&dev->dev_data, 0, sizeof(dev->dev_data));
|
||||
// dev->format = PicopassDeviceSaveFormatHF;
|
||||
// furi_string_reset(dev->load_path);
|
||||
// }
|
||||
|
||||
void uhf_device_free(UHFDevice* uhf_dev) {
|
||||
furi_assert(uhf_dev);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_string_free(uhf_dev->load_path);
|
||||
free(uhf_dev);
|
||||
}
|
||||
|
||||
bool uhf_file_select(UHFDevice* dev) {
|
||||
furi_assert(dev);
|
||||
|
||||
FuriString* uhf_app_folder;
|
||||
uhf_app_folder = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(&browser_options, UHF_APP_EXTENSION, &I_Nfc_10px);
|
||||
browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
|
||||
|
||||
bool res =
|
||||
dialog_file_browser_show(dev->dialogs, dev->load_path, uhf_app_folder, &browser_options);
|
||||
|
||||
furi_string_free(uhf_app_folder);
|
||||
if(res) {
|
||||
FuriString* filename;
|
||||
filename = furi_string_alloc();
|
||||
path_extract_filename(dev->load_path, filename, true);
|
||||
strncpy(dev->dev_name, furi_string_get_cstr(filename), UHF_DEV_NAME_MAX_LEN);
|
||||
res = uhf_device_load_data(dev, dev->load_path, true);
|
||||
if(res) {
|
||||
uhf_device_set_name(dev, dev->dev_name);
|
||||
}
|
||||
furi_string_free(filename);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// void uhf_device_data_clear(UHFDevice* dev_data) {
|
||||
// for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) {
|
||||
// memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data));
|
||||
// }
|
||||
// dev_data->pacs.legacy = false;
|
||||
// dev_data->pacs.se_enabled = false;
|
||||
// dev_data->pacs.elite_kdf = false;
|
||||
// dev_data->pacs.pin_length = 0;
|
||||
// }
|
||||
|
||||
bool uhf_device_delete(UHFDevice* dev, bool use_load_path) {
|
||||
furi_assert(dev);
|
||||
|
||||
bool deleted = false;
|
||||
FuriString* file_path;
|
||||
file_path = furi_string_alloc();
|
||||
|
||||
do {
|
||||
// Delete original file
|
||||
if(use_load_path && !furi_string_empty(dev->load_path)) {
|
||||
furi_string_set(file_path, dev->load_path);
|
||||
} else {
|
||||
furi_string_printf(file_path, APP_DATA_PATH("%s%s"), dev->dev_name, UHF_APP_EXTENSION);
|
||||
}
|
||||
if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break;
|
||||
deleted = true;
|
||||
} while(0);
|
||||
|
||||
if(!deleted) {
|
||||
dialog_message_show_storage_error(dev->dialogs, "Can not remove file");
|
||||
}
|
||||
|
||||
furi_string_free(file_path);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
void uhf_device_set_loading_callback(UHFDevice* dev, UHFLoadingCallback callback, void* context) {
|
||||
furi_assert(dev);
|
||||
|
||||
dev->loading_cb = callback;
|
||||
dev->loading_cb_ctx = context;
|
||||
}
|
||||
|
||||
// ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
|
||||
// uint8_t key[32] = {0};
|
||||
// memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey));
|
||||
// mbedtls_des3_context ctx;
|
||||
// mbedtls_des3_init(&ctx);
|
||||
// mbedtls_des3_set2key_dec(&ctx, key);
|
||||
// mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data);
|
||||
// mbedtls_des3_free(&ctx);
|
||||
// return ERR_NONE;
|
||||
// }
|
||||
|
||||
// ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) {
|
||||
// ReturnCode err;
|
||||
|
||||
// pacs->biometrics = AA1[6].data[4];
|
||||
// pacs->pin_length = AA1[6].data[6] & 0x0F;
|
||||
// pacs->encryption = AA1[6].data[7];
|
||||
|
||||
// if(pacs->encryption == PicopassDeviceEncryption3DES) {
|
||||
// FURI_LOG_D(TAG, "3DES Encrypted");
|
||||
// err = picopass_device_decrypt(AA1[7].data, pacs->credential);
|
||||
// if(err != ERR_NONE) {
|
||||
// FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||
// return err;
|
||||
// }
|
||||
|
||||
// err = picopass_device_decrypt(AA1[8].data, pacs->pin0);
|
||||
// if(err != ERR_NONE) {
|
||||
// FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||
// return err;
|
||||
// }
|
||||
|
||||
// err = picopass_device_decrypt(AA1[9].data, pacs->pin1);
|
||||
// if(err != ERR_NONE) {
|
||||
// FURI_LOG_E(TAG, "decrypt error %d", err);
|
||||
// return err;
|
||||
// }
|
||||
// } else if(pacs->encryption == PicopassDeviceEncryptionNone) {
|
||||
// FURI_LOG_D(TAG, "No Encryption");
|
||||
// memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN);
|
||||
// memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN);
|
||||
// memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN);
|
||||
// } else if(pacs->encryption == PicopassDeviceEncryptionDES) {
|
||||
// FURI_LOG_D(TAG, "DES Encrypted");
|
||||
// } else {
|
||||
// FURI_LOG_D(TAG, "Unknown encryption");
|
||||
// }
|
||||
|
||||
// pacs->sio = (AA1[10].data[0] == 0x30); // rough check
|
||||
|
||||
// return ERR_NONE;
|
||||
// }
|
||||
|
||||
// ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) {
|
||||
// uint32_t* halves = (uint32_t*)data;
|
||||
// if(halves[0] == 0) {
|
||||
// uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1]));
|
||||
// record->bitLength = 31 - leading0s;
|
||||
// } else {
|
||||
// uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0]));
|
||||
// record->bitLength = 63 - leading0s;
|
||||
// }
|
||||
// FURI_LOG_D(TAG, "bitLength: %d", record->bitLength);
|
||||
|
||||
// if(record->bitLength == 26) {
|
||||
// uint8_t* v4 = data + 4;
|
||||
// uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24);
|
||||
|
||||
// record->CardNumber = (bot >> 1) & 0xFFFF;
|
||||
// record->FacilityCode = (bot >> 17) & 0xFF;
|
||||
// FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber);
|
||||
// record->valid = true;
|
||||
// } else {
|
||||
// record->CardNumber = 0;
|
||||
// record->FacilityCode = 0;
|
||||
// record->valid = false;
|
||||
// }
|
||||
// return ERR_NONE;
|
||||
// }
|
||||
54
applications/external/uhf_rfid/uhf_device.h
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <storage/storage.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <mbedtls/des.h>
|
||||
#include "uhf_tag.h"
|
||||
|
||||
#define UHF_DEV_NAME_MAX_LEN 22
|
||||
#define UHF_EPC_BANK_LENGTH_LABEL "EPC_LENGTH"
|
||||
#define UHF_TID_BANK_LENGTH_LABEL "TID_LENGTH"
|
||||
#define UHF_USER_BANK_LENGTH_LABEL "USER_LENGTH"
|
||||
#define UHF_EPC_PC_LABEL "PC"
|
||||
#define UHF_EPC_CRC_LABEL "CRC"
|
||||
#define UHF_RFU_BANK_LABEL "RFU"
|
||||
#define UHF_EPC_BANK_LABEL "EPC"
|
||||
#define UHF_TID_BANK_LABEL "TID"
|
||||
#define UHF_USER_BANK_LABEL "USER"
|
||||
|
||||
#define UHF_APP_EXTENSION ".uhf"
|
||||
|
||||
typedef void (*UHFLoadingCallback)(void* context, bool state);
|
||||
|
||||
typedef struct {
|
||||
Storage* storage;
|
||||
DialogsApp* dialogs;
|
||||
UHFTagWrapper* uhf_tag_wrapper;
|
||||
char dev_name[UHF_DEV_NAME_MAX_LEN + 1];
|
||||
FuriString* load_path;
|
||||
UHFLoadingCallback loading_cb;
|
||||
void* loading_cb_ctx;
|
||||
} UHFDevice;
|
||||
|
||||
UHFDevice* uhf_device_alloc();
|
||||
|
||||
void uhf_device_free(UHFDevice* uhf_dev);
|
||||
|
||||
void uhf_device_set_name(UHFDevice* dev, const char* name);
|
||||
|
||||
bool uhf_device_save(UHFDevice* dev, const char* dev_name);
|
||||
|
||||
bool uhf_file_select(UHFDevice* dev);
|
||||
|
||||
// void uhf_device_data_clear(PicopassDeviceData* dev_data);
|
||||
|
||||
void uhf_device_clear(UHFDevice* dev);
|
||||
|
||||
bool uhf_device_delete(UHFDevice* dev, bool use_load_path);
|
||||
|
||||
void uhf_device_set_loading_callback(UHFDevice* dev, UHFLoadingCallback callback, void* context);
|
||||
|
||||
// ReturnCode uhf_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs);
|
||||
// ReturnCode uhf_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record);
|
||||
388
applications/external/uhf_rfid/uhf_module.c
vendored
Normal file
@@ -0,0 +1,388 @@
|
||||
#include "uhf_module.h"
|
||||
#include "uhf_module_cmd.h"
|
||||
|
||||
#define DELAY_MS 100
|
||||
#define WAIT_TICK 8000 // max wait time in between each byte
|
||||
|
||||
volatile uint16_t tick = 0;
|
||||
|
||||
void rx_callback(UartIrqEvent event, uint8_t data, void* ctx) {
|
||||
UNUSED(event);
|
||||
Buffer* buffer = ctx;
|
||||
if(buffer->closed) return; // buffer closed
|
||||
buffer_append_single(buffer, data); // append data
|
||||
if(data == FRAME_END) buffer_close(buffer); // end of frame
|
||||
tick = WAIT_TICK; // reset tick
|
||||
}
|
||||
|
||||
static M100ResponseType setup_and_send_rx(M100Module* module, uint8_t* cmd, size_t cmd_length) {
|
||||
buffer_reset(module->buf);
|
||||
tick = WAIT_TICK;
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, cmd, cmd_length);
|
||||
while(--tick) {
|
||||
furi_delay_us(5);
|
||||
}
|
||||
buffer_close(module->buf);
|
||||
// Validation Checks
|
||||
uint8_t* data = buffer_get_data(module->buf);
|
||||
size_t length = buffer_get_size(module->buf);
|
||||
// check if size > 0
|
||||
if(!length) return M100EmptyResponse;
|
||||
// check if data is valid
|
||||
if(data[0] != FRAME_START || data[length - 1] != FRAME_END) return M100ValidationFail;
|
||||
// check if checksum is correct
|
||||
if(checksum(data + 1, length - 3) != data[length - 2]) return M100ChecksumFail;
|
||||
return M100SuccessResponse;
|
||||
}
|
||||
|
||||
M100ModuleInfo* m100_module_info_alloc() {
|
||||
M100ModuleInfo* module_info = (M100ModuleInfo*)malloc(sizeof(M100ModuleInfo));
|
||||
return module_info;
|
||||
}
|
||||
|
||||
void m100_module_info_free(M100ModuleInfo* module_info) {
|
||||
free(module_info->hw_version);
|
||||
free(module_info->sw_version);
|
||||
free(module_info->manufacturer);
|
||||
free(module_info);
|
||||
}
|
||||
M100Module* m100_module_alloc() {
|
||||
M100Module* module = (M100Module*)malloc(sizeof(M100Module));
|
||||
module->info = m100_module_info_alloc();
|
||||
module->buf = buffer_alloc(MAX_BUFFER_SIZE);
|
||||
module->baudrate = DEFAULT_BAUDRATE;
|
||||
module->transmitting_power = DEFAULT_TRANSMITTING_POWER;
|
||||
module->region = DEFAULT_WORKING_REGION;
|
||||
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, rx_callback, module->buf);
|
||||
return module;
|
||||
}
|
||||
|
||||
void m100_module_free(M100Module* module) {
|
||||
m100_module_info_free(module->info);
|
||||
buffer_free(module->buf);
|
||||
free(module);
|
||||
}
|
||||
|
||||
uint8_t checksum(const uint8_t* data, size_t length) {
|
||||
// CheckSum8 Modulo 256
|
||||
// Sum of Bytes % 256
|
||||
uint64_t sum_val = 0x00;
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
sum_val += data[i];
|
||||
}
|
||||
return (uint8_t)(sum_val % 0x100);
|
||||
}
|
||||
|
||||
uint16_t crc16_genibus(const uint8_t* data, size_t length) {
|
||||
uint16_t crc = 0xFFFF; // Initial value
|
||||
uint16_t polynomial = 0x1021; // CRC-16/GENIBUS polynomial
|
||||
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
crc ^= (data[i] << 8); // Move byte into MSB of 16bit CRC
|
||||
for(int j = 0; j < 8; j++) {
|
||||
if(crc & 0x8000) {
|
||||
crc = (crc << 1) ^ polynomial;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc ^ 0xFFFF; // Post-inversion
|
||||
}
|
||||
|
||||
char* _m100_info_helper(M100Module* module, char** info) {
|
||||
if(!buffer_get_size(module->buf)) return NULL;
|
||||
uint8_t* data = buffer_get_data(module->buf);
|
||||
uint16_t payload_len = data[3];
|
||||
payload_len = (payload_len << 8) + data[4];
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
for(int i = 0; i < payload_len; i++) {
|
||||
furi_string_cat_printf(temp_str, "%c", data[6 + i]);
|
||||
}
|
||||
if(*info == NULL) {
|
||||
*info = (char*)malloc(sizeof(char) * payload_len);
|
||||
} else {
|
||||
for(size_t i = 0; i < strlen(*info); i++) {
|
||||
(*info)[i] = 0;
|
||||
}
|
||||
}
|
||||
memcpy(*info, furi_string_get_cstr(temp_str), payload_len);
|
||||
furi_string_free(temp_str);
|
||||
return *info;
|
||||
}
|
||||
|
||||
char* m100_get_hardware_version(M100Module* module) {
|
||||
setup_and_send_rx(module, (uint8_t*)&CMD_HW_VERSION.cmd[0], CMD_HW_VERSION.length);
|
||||
return _m100_info_helper(module, &module->info->hw_version);
|
||||
}
|
||||
char* m100_get_software_version(M100Module* module) {
|
||||
setup_and_send_rx(module, (uint8_t*)&CMD_SW_VERSION.cmd[0], CMD_SW_VERSION.length);
|
||||
return _m100_info_helper(module, &module->info->sw_version);
|
||||
}
|
||||
char* m100_get_manufacturers(M100Module* module) {
|
||||
setup_and_send_rx(module, (uint8_t*)&CMD_MANUFACTURERS.cmd[0], CMD_MANUFACTURERS.length);
|
||||
return _m100_info_helper(module, &module->info->manufacturer);
|
||||
}
|
||||
|
||||
M100ResponseType m100_single_poll(M100Module* module, UHFTag* uhf_tag) {
|
||||
M100ResponseType rp_type =
|
||||
setup_and_send_rx(module, (uint8_t*)&CMD_SINGLE_POLLING.cmd[0], CMD_SINGLE_POLLING.length);
|
||||
if(rp_type != M100SuccessResponse) return rp_type;
|
||||
uint8_t* data = buffer_get_data(module->buf);
|
||||
size_t length = buffer_get_size(module->buf);
|
||||
uint16_t pc = data[6];
|
||||
uint16_t crc = 0;
|
||||
// mask out epc length from protocol control
|
||||
size_t epc_len = pc;
|
||||
epc_len >>= 3;
|
||||
epc_len *= 2;
|
||||
// get protocol control
|
||||
pc <<= 8;
|
||||
pc += data[7];
|
||||
// get cyclic redundency check
|
||||
crc = data[8 + epc_len];
|
||||
crc <<= 8;
|
||||
crc += data[8 + epc_len + 1];
|
||||
// validate checksum
|
||||
if(checksum(data + 1, length - 3) != data[length - 2]) return M100ValidationFail;
|
||||
// validate crc
|
||||
if(crc16_genibus(data + 6, epc_len + 2) != crc) return M100ValidationFail;
|
||||
uhf_tag_set_epc_pc(uhf_tag, pc);
|
||||
uhf_tag_set_epc_crc(uhf_tag, crc);
|
||||
uhf_tag_set_epc(uhf_tag, data + 8, epc_len);
|
||||
return M100SuccessResponse;
|
||||
}
|
||||
|
||||
M100ResponseType m100_set_select(M100Module* module, UHFTag* uhf_tag) {
|
||||
// Set select
|
||||
uint8_t cmd[MAX_BUFFER_SIZE];
|
||||
size_t cmd_length = CMD_SET_SELECT_PARAMETER.length;
|
||||
size_t mask_length_bytes = uhf_tag->epc->size;
|
||||
size_t mask_length_bits = mask_length_bytes * 8;
|
||||
// payload len == sel param len + ptr len + mask len + epc len
|
||||
size_t payload_len = 7 + mask_length_bytes;
|
||||
memcpy(cmd, CMD_SET_SELECT_PARAMETER.cmd, cmd_length);
|
||||
// set new length
|
||||
cmd_length = 12 + mask_length_bytes + 2;
|
||||
// set payload length
|
||||
cmd[3] = (payload_len >> 8) & 0xFF;
|
||||
cmd[4] = payload_len & 0xFF;
|
||||
// set select param
|
||||
cmd[5] = 0x01; // 0x00=rfu, 0x01=epc, 0x10=tid, 0x11=user
|
||||
// set ptr
|
||||
cmd[9] = 0x20; // epc data begins after 0x20
|
||||
// set mask length
|
||||
cmd[10] = mask_length_bits;
|
||||
// truncate
|
||||
cmd[11] = false;
|
||||
// set mask
|
||||
memcpy((void*)&cmd[12], uhf_tag->epc->data, mask_length_bytes);
|
||||
|
||||
// set checksum
|
||||
cmd[cmd_length - 2] = checksum(cmd + 1, 11 + mask_length_bytes);
|
||||
// end frame
|
||||
cmd[cmd_length - 1] = FRAME_END;
|
||||
|
||||
setup_and_send_rx(module, cmd, 12 + mask_length_bytes + 3);
|
||||
|
||||
uint8_t* data = buffer_get_data(module->buf);
|
||||
if(checksum(data + 1, 5) != data[6]) return M100ValidationFail; // error in rx
|
||||
if(data[5] != 0x00) return M100ValidationFail; // error if not 0
|
||||
|
||||
return M100SuccessResponse;
|
||||
}
|
||||
|
||||
UHFTag* m100_get_select_param(M100Module* module) {
|
||||
buffer_reset(module->buf);
|
||||
furi_hal_uart_set_irq_cb(FuriHalUartIdLPUART1, rx_callback, module->buf);
|
||||
furi_hal_uart_tx(
|
||||
FuriHalUartIdUSART1,
|
||||
(uint8_t*)&CMD_GET_SELECT_PARAMETER.cmd,
|
||||
CMD_GET_SELECT_PARAMETER.length);
|
||||
furi_delay_ms(DELAY_MS);
|
||||
// UHFTag* uhf_tag = uhf_tag_alloc();
|
||||
// uint8_t* data = buffer_get_data(module->buf);
|
||||
// size_t mask_length =
|
||||
// uhf_tag_set_epc(uhf_tag, data + 12, )
|
||||
// TODO : implement
|
||||
return NULL;
|
||||
}
|
||||
|
||||
M100ResponseType m100_read_label_data_storage(
|
||||
M100Module* module,
|
||||
UHFTag* uhf_tag,
|
||||
BankType bank,
|
||||
uint32_t access_pwd,
|
||||
uint16_t word_count) {
|
||||
/*
|
||||
Will probably remove UHFTag as param and get it from get selected tag
|
||||
*/
|
||||
if(bank == EPCBank) return M100SuccessResponse;
|
||||
uint8_t cmd[MAX_BUFFER_SIZE];
|
||||
size_t cmd_length = CMD_READ_LABEL_DATA_STORAGE_AREA.length;
|
||||
memcpy(cmd, CMD_READ_LABEL_DATA_STORAGE_AREA.cmd, cmd_length);
|
||||
// set access password
|
||||
cmd[5] = (access_pwd >> 24) & 0xFF;
|
||||
cmd[6] = (access_pwd >> 16) & 0xFF;
|
||||
cmd[7] = (access_pwd >> 8) & 0xFF;
|
||||
cmd[8] = access_pwd & 0xFF;
|
||||
// set mem bank
|
||||
cmd[9] = (uint8_t)bank;
|
||||
// set word counter
|
||||
cmd[12] = (word_count >> 8) & 0xFF;
|
||||
cmd[13] = word_count & 0xFF;
|
||||
// calc checksum
|
||||
cmd[cmd_length - 2] = checksum(cmd + 1, cmd_length - 3);
|
||||
|
||||
M100ResponseType rp_type = setup_and_send_rx(module, cmd, cmd_length);
|
||||
if(rp_type != M100SuccessResponse) return rp_type;
|
||||
|
||||
uint8_t* data = buffer_get_data(module->buf);
|
||||
|
||||
uint8_t rtn_command = data[2];
|
||||
uint16_t payload_len = data[3];
|
||||
payload_len = (payload_len << 8) + data[4];
|
||||
|
||||
if(rtn_command == 0xFF) {
|
||||
if(payload_len == 0x01) return M100NoTagResponse;
|
||||
return M100MemoryOverrun;
|
||||
}
|
||||
|
||||
size_t ptr_offset = 5 /*<-ptr offset*/ + uhf_tag_get_epc_size(uhf_tag) + 3 /*<-pc + ul*/;
|
||||
size_t bank_data_length = payload_len - (ptr_offset - 5 /*dont include the offset*/);
|
||||
|
||||
if(bank == TIDBank) {
|
||||
uhf_tag_set_tid(uhf_tag, data + ptr_offset, bank_data_length);
|
||||
} else if(bank == UserBank) {
|
||||
uhf_tag_set_user(uhf_tag, data + ptr_offset, bank_data_length);
|
||||
}
|
||||
|
||||
return M100SuccessResponse;
|
||||
}
|
||||
|
||||
M100ResponseType m100_write_label_data_storage(
|
||||
M100Module* module,
|
||||
UHFTag* saved_tag,
|
||||
UHFTag* selected_tag,
|
||||
BankType bank,
|
||||
uint16_t source_address,
|
||||
uint32_t access_pwd) {
|
||||
uint8_t cmd[MAX_BUFFER_SIZE];
|
||||
size_t cmd_length = CMD_WRITE_LABEL_DATA_STORE.length;
|
||||
memcpy(cmd, CMD_WRITE_LABEL_DATA_STORE.cmd, cmd_length);
|
||||
uint16_t payload_len = 9;
|
||||
uint16_t data_length = 0;
|
||||
if(bank == ReservedBank) {
|
||||
// access pwd len + kill pwd len
|
||||
payload_len += 4;
|
||||
data_length = 4;
|
||||
} else if(bank == EPCBank) {
|
||||
// epc len + pc len
|
||||
payload_len += 4 + uhf_tag_get_epc_size(saved_tag);
|
||||
data_length = 4 + uhf_tag_get_epc_size(saved_tag);
|
||||
// set data
|
||||
uint8_t tmp_arr[4];
|
||||
tmp_arr[0] = (uint8_t)((uhf_tag_get_epc_crc(selected_tag) >> 8) & 0xFF);
|
||||
tmp_arr[1] = (uint8_t)(uhf_tag_get_epc_crc(selected_tag) & 0xFF);
|
||||
tmp_arr[2] = (uint8_t)((uhf_tag_get_epc_pc(saved_tag) >> 8) & 0xFF);
|
||||
tmp_arr[3] = (uint8_t)(uhf_tag_get_epc_pc(saved_tag) & 0xFF);
|
||||
memcpy(cmd + 14, tmp_arr, 4);
|
||||
memcpy(cmd + 18, uhf_tag_get_epc(saved_tag), uhf_tag_get_epc_size(saved_tag));
|
||||
} else if(bank == UserBank) {
|
||||
payload_len += uhf_tag_get_user_size(saved_tag);
|
||||
data_length = uhf_tag_get_user_size(saved_tag);
|
||||
// set data
|
||||
memcpy(cmd + 14, uhf_tag_get_user(saved_tag), uhf_tag_get_user_size(saved_tag));
|
||||
}
|
||||
// set payload length
|
||||
cmd[3] = (payload_len >> 8) & 0xFF;
|
||||
cmd[4] = payload_len & 0xFF;
|
||||
// set access password
|
||||
cmd[5] = (access_pwd >> 24) & 0xFF;
|
||||
cmd[6] = (access_pwd >> 16) & 0xFF;
|
||||
cmd[7] = (access_pwd >> 8) & 0xFF;
|
||||
cmd[8] = access_pwd & 0xFF;
|
||||
// set membank
|
||||
cmd[9] = (uint8_t)bank;
|
||||
// set source address
|
||||
cmd[10] = (source_address >> 8) & 0xFF;
|
||||
cmd[11] = source_address & 0xFF;
|
||||
// set data length
|
||||
size_t data_length_words = data_length / 2;
|
||||
cmd[12] = (data_length_words >> 8) & 0xFF;
|
||||
cmd[13] = data_length_words & 0xFF;
|
||||
// update cmd len
|
||||
cmd_length = 7 + payload_len;
|
||||
// calculate checksum
|
||||
cmd[cmd_length - 2] = checksum(cmd + 1, cmd_length - 3);
|
||||
cmd[cmd_length - 1] = FRAME_END;
|
||||
// send cmd
|
||||
// furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, rx_callback, module->buf);
|
||||
// furi_hal_uart_tx(FuriHalUartIdUSART1, cmd, cmd_length);
|
||||
// unsigned int delay = DELAY_MS / 2;
|
||||
// unsigned int timeout = 15;
|
||||
// while(!buffer_get_size(module->buf)) {
|
||||
// furi_delay_ms(delay);
|
||||
// if(!timeout--) break;
|
||||
// }
|
||||
setup_and_send_rx(module, cmd, cmd_length);
|
||||
uint8_t* buff_data = buffer_get_data(module->buf);
|
||||
size_t buff_length = buffer_get_size(module->buf);
|
||||
if(buff_data[2] == 0xFF && buff_length == 8)
|
||||
return M100NoTagResponse;
|
||||
else if(buff_data[2] == 0xFF)
|
||||
return M100ValidationFail;
|
||||
return M100SuccessResponse;
|
||||
}
|
||||
void m100_set_baudrate(M100Module* module, uint32_t baudrate) {
|
||||
size_t length = CMD_SET_COMMUNICATION_BAUD_RATE.length;
|
||||
uint8_t cmd[length];
|
||||
memcpy(cmd, CMD_SET_COMMUNICATION_BAUD_RATE.cmd, length);
|
||||
uint16_t br_mod = baudrate / 100; // module format
|
||||
cmd[6] = 0xFF & br_mod; // pow LSB
|
||||
cmd[5] = 0xFF & (br_mod >> 8); // pow MSB
|
||||
cmd[length - 2] = checksum(cmd + 1, length - 3);
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, cmd, length);
|
||||
furi_hal_uart_set_br(FuriHalUartIdUSART1, baudrate);
|
||||
module->baudrate = baudrate;
|
||||
}
|
||||
|
||||
bool m100_set_working_region(M100Module* module, WorkingRegion region) {
|
||||
size_t length = CMD_SET_WORK_AREA.length;
|
||||
uint8_t cmd[length];
|
||||
memcpy(cmd, CMD_SET_WORK_AREA.cmd, length);
|
||||
cmd[5] = (uint8_t)region;
|
||||
cmd[length - 2] = checksum(cmd + 1, length - 3);
|
||||
setup_and_send_rx(module, cmd, length);
|
||||
module->region = region;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool m100_set_transmitting_power(M100Module* module, uint16_t power) {
|
||||
size_t length = CMD_SET_TRANSMITTING_POWER.length;
|
||||
uint8_t cmd[length];
|
||||
memcpy(cmd, CMD_SET_TRANSMITTING_POWER.cmd, length);
|
||||
cmd[5] = (power >> 8) & 0xFF;
|
||||
cmd[6] = power & 0xFF;
|
||||
cmd[length - 2] = checksum(cmd + 1, length - 3);
|
||||
setup_and_send_rx(module, cmd, length);
|
||||
module->transmitting_power = power;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool m100_set_freq_hopping(M100Module* module, bool hopping) {
|
||||
UNUSED(module);
|
||||
UNUSED(hopping);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool m100_set_power(M100Module* module, uint8_t* power) {
|
||||
UNUSED(module);
|
||||
UNUSED(power);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t m100_get_baudrate(M100Module* module) {
|
||||
return module->baudrate;
|
||||
}
|
||||
82
applications/external/uhf_rfid/uhf_module.h
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <furi_hal.h>
|
||||
#include "uhf_tag.h"
|
||||
#include "uhf_buffer.h"
|
||||
#include "uhf_tag.h"
|
||||
#include <furi_hal.h>
|
||||
#include "uhf_module_settings.h"
|
||||
|
||||
#define FRAME_START 0xBB
|
||||
#define FRAME_END 0x7E
|
||||
#define DEFAULT_BAUDRATE BAUD_RATES[BAUD_RATES_COUNT - 1]
|
||||
#define DEFAULT_TRANSMITTING_POWER POWER_DBM[POWER_DBM_COUNT - 1]
|
||||
#define DEFAULT_WORKING_REGION WR_US
|
||||
|
||||
typedef struct {
|
||||
char* hw_version;
|
||||
char* sw_version;
|
||||
char* manufacturer;
|
||||
} M100ModuleInfo;
|
||||
|
||||
typedef enum {
|
||||
M100SuccessResponse,
|
||||
M100ValidationFail,
|
||||
M100NoTagResponse,
|
||||
M100MemoryOverrun,
|
||||
M100EmptyResponse,
|
||||
M100ChecksumFail
|
||||
} M100ResponseType;
|
||||
|
||||
typedef struct {
|
||||
M100ModuleInfo* info;
|
||||
uint32_t baudrate;
|
||||
WorkingRegion region;
|
||||
uint16_t region_frequency;
|
||||
uint16_t transmitting_power;
|
||||
bool freq_hopping;
|
||||
Buffer* buf;
|
||||
} M100Module;
|
||||
|
||||
M100ModuleInfo* m100_module_info_alloc();
|
||||
void m100_module_info_free(M100ModuleInfo* module_info);
|
||||
|
||||
M100Module* m100_module_alloc();
|
||||
void m100_module_free(M100Module* module);
|
||||
uint16_t crc16_genibus(const uint8_t* data, size_t length);
|
||||
uint8_t checksum(const uint8_t* data, size_t length);
|
||||
uint8_t get_baudrate_count();
|
||||
|
||||
// Function prototypes
|
||||
char* m100_get_hardware_version(M100Module* module);
|
||||
char* m100_get_software_version(M100Module* module);
|
||||
char* m100_get_manufacturers(M100Module* module);
|
||||
|
||||
void m100_set_baudrate(M100Module* module, uint32_t baudrate);
|
||||
bool m100_set_working_region(M100Module* module, WorkingRegion region);
|
||||
bool m100_set_transmitting_power(M100Module* module, uint16_t power);
|
||||
bool m100_set_freq_hopping(M100Module* module, bool hopping);
|
||||
bool m100_set_power(M100Module* module, uint8_t* power);
|
||||
|
||||
// gen2 cmds
|
||||
M100ResponseType m100_single_poll(M100Module* module, UHFTag* uhf_tag);
|
||||
M100ResponseType m100_set_select(M100Module* module, UHFTag* uhf_tag);
|
||||
M100ResponseType m100_read_label_data_storage(
|
||||
M100Module* module,
|
||||
UHFTag* uhf_tag,
|
||||
BankType bank,
|
||||
uint32_t access_pwd,
|
||||
uint16_t word_count);
|
||||
|
||||
M100ResponseType m100_write_label_data_storage(
|
||||
M100Module* module,
|
||||
UHFTag* saved_tag,
|
||||
UHFTag* selected_tag,
|
||||
BankType bank,
|
||||
uint16_t source_address,
|
||||
uint32_t access_pwd);
|
||||
|
||||
uint32_t m100_get_baudrate(M100Module* module);
|
||||
89
applications/external/uhf_rfid/uhf_module_cmd.h
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
const uint8_t* cmd;
|
||||
size_t length;
|
||||
} Command;
|
||||
|
||||
// Define the command data arrays
|
||||
static const uint8_t CMD_HW_VERSION_DATA[] = {0xBB, 0x00, 0x03, 0x00, 0x01, 0x00, 0x04, 0x7E};
|
||||
static const uint8_t CMD_SW_VERSION_DATA[] = {0xBB, 0x00, 0x03, 0x00, 0x01, 0x01, 0x05, 0x7E};
|
||||
static const uint8_t CMD_MANUFACTURERS_DATA[] = {0xBB, 0x00, 0x03, 0x00, 0x01, 0x02, 0x06, 0x7E};
|
||||
static const uint8_t CMD_SINGLE_POLLING_DATA[] = {0xBB, 0x00, 0x22, 0x00, 0x00, 0x22, 0x7E};
|
||||
static const uint8_t CMD_MULTIPLE_POLLING_DATA[] =
|
||||
{0xBB, 0x00, 0x27, 0x00, 0x03, 0x22, 0x27, 0x10, 0x83, 0x7E};
|
||||
static const uint8_t CMD_STOP_MULTIPLE_POLLING_DATA[] = {0xBB, 0x00, 0x28, 0x00, 0x00, 0x28, 0x7E};
|
||||
static const uint8_t CMD_SET_SELECT_PARAMETER_DATA[] = {0xBB, 0x00, 0x0C, 0x00, 0x13, 0x01, 0x00,
|
||||
0x00, 0x00, 0x20, 0x60, 0x00, 0x30, 0x75,
|
||||
0x1F, 0xEB, 0x70, 0x5C, 0x59, 0x04, 0xE3,
|
||||
0xD5, 0x0D, 0x70, 0xAD, 0x7E};
|
||||
static const uint8_t CMD_GET_SELECT_PARAMETER_DATA[] = {0xBB, 0x00, 0x0B, 0x00, 0x00, 0x0B, 0x7E};
|
||||
static const uint8_t CMD_SET_SELECT_MODE_DATA[] = {0xBB, 0x00, 0x12, 0x00, 0x01, 0x01, 0x14, 0x7E};
|
||||
static const uint8_t CMD_READ_LABEL_DATA_STORAGE_AREA_DATA[] =
|
||||
{0xBB, 0x00, 0x39, 0x00, 0x09, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x02, 0x45, 0x7E};
|
||||
static const uint8_t CMD_WRITE_LABEL_DATA_STORE_DATA[] = {0xBB, 0x00, 0x49, 0x00, 0x0D, 0x00, 0x00,
|
||||
0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x02,
|
||||
0x12, 0x34, 0x56, 0x78, 0x6D, 0x7E};
|
||||
static const uint8_t CMD_LOCK_LABEL_DATA_STORE_DATA[] =
|
||||
{0xBB, 0x00, 0x82, 0x00, 0x07, 0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00, 0x80, 0x09, 0x7E};
|
||||
static const uint8_t CMD_INACTIVATE_KILL_TAG_DATA[] =
|
||||
{0xBB, 0x00, 0x65, 0x00, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x67, 0x7E};
|
||||
static const uint8_t CMD_SET_COMMUNICATION_BAUD_RATE_DATA[] =
|
||||
{0xBB, 0x00, 0x11, 0x00, 0x02, 0x00, 0xC0, 0xD3, 0x7E};
|
||||
static const uint8_t CMD_GET_QUERY_PARAMETERS_DATA[] = {0xBB, 0x00, 0x0D, 0x00, 0x00, 0x0D, 0x7E};
|
||||
static const uint8_t CMD_SET_QUERY_PARAMETER_DATA[] =
|
||||
{0xBB, 0x00, 0x0E, 0x00, 0x02, 0x10, 0x20, 0x40, 0x7E};
|
||||
static const uint8_t CMD_SET_WORK_AREA_DATA[] = {0xBB, 0x00, 0x07, 0x00, 0x01, 0x01, 0x09, 0x7E};
|
||||
static const uint8_t CMD_SET_TRANSMITTING_POWER_DATA[] =
|
||||
{0xBB, 0x00, 0xB6, 0x00, 0x02, 0x07, 0xD0, 0x8F, 0x7E};
|
||||
|
||||
// Define the Command structs
|
||||
static const Command CMD_HW_VERSION = {CMD_HW_VERSION_DATA, sizeof(CMD_HW_VERSION_DATA)};
|
||||
static const Command CMD_SW_VERSION = {CMD_SW_VERSION_DATA, sizeof(CMD_SW_VERSION_DATA)};
|
||||
static const Command CMD_MANUFACTURERS = {CMD_MANUFACTURERS_DATA, sizeof(CMD_MANUFACTURERS_DATA)};
|
||||
static const Command CMD_SINGLE_POLLING = {
|
||||
CMD_SINGLE_POLLING_DATA,
|
||||
sizeof(CMD_SINGLE_POLLING_DATA)};
|
||||
static const Command CMD_MULTIPLE_POLLING = {
|
||||
CMD_MULTIPLE_POLLING_DATA,
|
||||
sizeof(CMD_MULTIPLE_POLLING_DATA)};
|
||||
static const Command CMD_STOP_MULTIPLE_POLLING = {
|
||||
CMD_STOP_MULTIPLE_POLLING_DATA,
|
||||
sizeof(CMD_STOP_MULTIPLE_POLLING_DATA)};
|
||||
static const Command CMD_SET_SELECT_PARAMETER = {
|
||||
CMD_SET_SELECT_PARAMETER_DATA,
|
||||
sizeof(CMD_SET_SELECT_PARAMETER_DATA)};
|
||||
static const Command CMD_GET_SELECT_PARAMETER = {
|
||||
CMD_GET_SELECT_PARAMETER_DATA,
|
||||
sizeof(CMD_GET_SELECT_PARAMETER_DATA)};
|
||||
static const Command CMD_SET_SELECT_MODE = {
|
||||
CMD_SET_SELECT_MODE_DATA,
|
||||
sizeof(CMD_SET_SELECT_MODE_DATA)};
|
||||
static const Command CMD_READ_LABEL_DATA_STORAGE_AREA = {
|
||||
CMD_READ_LABEL_DATA_STORAGE_AREA_DATA,
|
||||
sizeof(CMD_READ_LABEL_DATA_STORAGE_AREA_DATA)};
|
||||
static const Command CMD_WRITE_LABEL_DATA_STORE = {
|
||||
CMD_WRITE_LABEL_DATA_STORE_DATA,
|
||||
sizeof(CMD_WRITE_LABEL_DATA_STORE_DATA)};
|
||||
static const Command CMD_LOCK_LABEL_DATA_STORE = {
|
||||
CMD_LOCK_LABEL_DATA_STORE_DATA,
|
||||
sizeof(CMD_LOCK_LABEL_DATA_STORE_DATA)};
|
||||
static const Command CMD_INACTIVATE_KILL_TAG = {
|
||||
CMD_INACTIVATE_KILL_TAG_DATA,
|
||||
sizeof(CMD_INACTIVATE_KILL_TAG_DATA)};
|
||||
static const Command CMD_SET_COMMUNICATION_BAUD_RATE = {
|
||||
CMD_SET_COMMUNICATION_BAUD_RATE_DATA,
|
||||
sizeof(CMD_SET_COMMUNICATION_BAUD_RATE_DATA)};
|
||||
static const Command CMD_GET_QUERY_PARAMETERS = {
|
||||
CMD_GET_QUERY_PARAMETERS_DATA,
|
||||
sizeof(CMD_GET_QUERY_PARAMETERS_DATA)};
|
||||
static const Command CMD_SET_QUERY_PARAMETER = {
|
||||
CMD_SET_QUERY_PARAMETER_DATA,
|
||||
sizeof(CMD_SET_QUERY_PARAMETER_DATA)};
|
||||
static const Command CMD_SET_WORK_AREA = {CMD_SET_WORK_AREA_DATA, sizeof(CMD_SET_WORK_AREA_DATA)};
|
||||
static const Command CMD_SET_TRANSMITTING_POWER = {
|
||||
CMD_SET_TRANSMITTING_POWER_DATA,
|
||||
sizeof(CMD_SET_TRANSMITTING_POWER_DATA)};
|
||||
29
applications/external/uhf_rfid/uhf_module_settings.h
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// UHF module regions
|
||||
typedef enum {
|
||||
WR_CHINA_900 = 1, // Freq_CH-920.125M
|
||||
WR_US, // Freq_CH-902.25M
|
||||
WR_EU, // Freq_CH-865.1M
|
||||
WR_CHINA_800, // Freq_CH-840.125M
|
||||
WR_KOREA = 6 // Freq_CH-917.1M
|
||||
} WorkingRegion;
|
||||
|
||||
// UHF module baudrates
|
||||
static const uint32_t BAUD_RATES[] = {9600, 19200, 115200};
|
||||
static const uint8_t BAUD_RATES_COUNT = sizeof(BAUD_RATES) / sizeof(BAUD_RATES[0]);
|
||||
// RF Power Setting
|
||||
static const uint8_t POWER_DBM[] = {12, 14, 17, 20}; // To be determined ...
|
||||
static const uint8_t POWER_DBM_COUNT = sizeof(POWER_DBM) / sizeof(POWER_DBM[0]);
|
||||
// UHF WorkingArea
|
||||
static const char* WORKING_REGIONS_STR[] = {"CN1", "US", "EU", "CN2", "KR"};
|
||||
static const uint8_t __working_region_str =
|
||||
sizeof(WORKING_REGIONS_STR) / sizeof(WORKING_REGIONS_STR[0]);
|
||||
static const WorkingRegion WORKING_REGIONS[] = {WR_CHINA_900, WR_US, WR_EU, WR_CHINA_800, WR_KOREA};
|
||||
static const uint8_t WORKING_REGIONS_COUNT = sizeof(WORKING_REGIONS) / sizeof(WORKING_REGIONS[0]);
|
||||
// UHF WorkingChannel
|
||||
// static const string WORKING_CHANNELS_STR[] = {"China 900MHz", "US", "EU", "China 800MHz", "Korea"};
|
||||
// static const WorkingChannel WORKING_CHANNELS[] = {WC_CHINA_900, WC_US, WC_EU, WC_CHINA_800, WC_KOREA};
|
||||
// static const uint8_t WORKING_CHANNELS_COUNT = sizeof(WORKING_CHANNELS) / sizeof(WORKING_CHANNELS[0]);
|
||||
116
applications/external/uhf_rfid/uhf_tag.c
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "uhf_tag.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
UHFTagWrapper* uhf_tag_wrapper_alloc() {
|
||||
UHFTagWrapper* uhf_tag_wrapper = (UHFTagWrapper*)malloc(sizeof(UHFTagWrapper));
|
||||
uhf_tag_wrapper->uhf_tag = NULL;
|
||||
return uhf_tag_wrapper;
|
||||
}
|
||||
|
||||
void uhf_tag_wrapper_set_tag(UHFTagWrapper* uhf_tag_wrapper, UHFTag* uhf_tag) {
|
||||
if(uhf_tag_wrapper->uhf_tag != NULL) {
|
||||
uhf_tag_free(uhf_tag_wrapper->uhf_tag);
|
||||
}
|
||||
uhf_tag_wrapper->uhf_tag = uhf_tag;
|
||||
}
|
||||
|
||||
void uhf_tag_wrapper_free(UHFTagWrapper* uhf_tag_wrapper) {
|
||||
uhf_tag_free(uhf_tag_wrapper->uhf_tag);
|
||||
free(uhf_tag_wrapper);
|
||||
}
|
||||
|
||||
UHFTag* uhf_tag_alloc() {
|
||||
UHFTag* uhf_tag = (UHFTag*)malloc(sizeof(UHFTag));
|
||||
uhf_tag->reserved = (ReservedMemoryBank*)malloc(sizeof(ReservedMemoryBank));
|
||||
uhf_tag->epc = (EPCMemoryBank*)malloc(sizeof(EPCMemoryBank));
|
||||
uhf_tag->tid = (TIDMemoryBank*)malloc(sizeof(TIDMemoryBank));
|
||||
uhf_tag->user = (UserMemoryBank*)malloc(sizeof(UserMemoryBank));
|
||||
return uhf_tag;
|
||||
}
|
||||
|
||||
void uhf_tag_reset(UHFTag* uhf_tag) {
|
||||
uhf_tag->epc->crc = 0;
|
||||
uhf_tag->epc->pc = 0;
|
||||
uhf_tag->epc->size = 0;
|
||||
uhf_tag->tid->size = 0;
|
||||
uhf_tag->user->size = 0;
|
||||
}
|
||||
|
||||
void uhf_tag_free(UHFTag* uhf_tag) {
|
||||
if(uhf_tag == NULL) return;
|
||||
free(uhf_tag->reserved);
|
||||
free(uhf_tag->epc);
|
||||
free(uhf_tag->tid);
|
||||
free(uhf_tag->user);
|
||||
free(uhf_tag);
|
||||
}
|
||||
|
||||
void uhf_tag_set_epc_pc(UHFTag* uhf_tag, uint16_t pc) {
|
||||
uhf_tag->epc->pc = pc;
|
||||
}
|
||||
|
||||
void uhf_tag_set_epc_crc(UHFTag* uhf_tag, uint16_t crc) {
|
||||
uhf_tag->epc->crc = crc;
|
||||
}
|
||||
|
||||
void uhf_tag_set_epc(UHFTag* uhf_tag, uint8_t* data_in, size_t size) {
|
||||
memcpy(uhf_tag->epc->data, data_in, size);
|
||||
uhf_tag->epc->size = size;
|
||||
}
|
||||
|
||||
void uhf_tag_set_epc_size(UHFTag* uhf_tag, size_t size) {
|
||||
uhf_tag->epc->size = size;
|
||||
}
|
||||
|
||||
void uhf_tag_set_tid(UHFTag* uhf_tag, uint8_t* data_in, size_t size) {
|
||||
memcpy(uhf_tag->tid->data, data_in, size);
|
||||
uhf_tag->tid->size = size;
|
||||
}
|
||||
|
||||
void uhf_tag_set_tid_size(UHFTag* uhf_tag, size_t size) {
|
||||
uhf_tag->tid->size = size;
|
||||
}
|
||||
|
||||
void uhf_tag_set_user(UHFTag* uhf_tag, uint8_t* data_in, size_t size) {
|
||||
memcpy(uhf_tag->user->data, data_in, size);
|
||||
uhf_tag->user->size = size;
|
||||
}
|
||||
|
||||
void uhf_tag_set_user_size(UHFTag* uhf_tag, size_t size) {
|
||||
uhf_tag->user->size = size;
|
||||
}
|
||||
|
||||
// getters
|
||||
|
||||
uint8_t* uhf_tag_get_epc(UHFTag* uhf_tag) {
|
||||
return uhf_tag->epc->data;
|
||||
}
|
||||
|
||||
size_t uhf_tag_get_epc_size(UHFTag* uhf_tag) {
|
||||
return uhf_tag->epc->size;
|
||||
}
|
||||
|
||||
uint16_t uhf_tag_get_epc_pc(UHFTag* uhf_tag) {
|
||||
return uhf_tag->epc->pc;
|
||||
}
|
||||
|
||||
uint16_t uhf_tag_get_epc_crc(UHFTag* uhf_tag) {
|
||||
return uhf_tag->epc->crc;
|
||||
}
|
||||
|
||||
uint8_t* uhf_tag_get_tid(UHFTag* uhf_tag) {
|
||||
return uhf_tag->tid->data;
|
||||
}
|
||||
|
||||
size_t uhf_tag_get_tid_size(UHFTag* uhf_tag) {
|
||||
return uhf_tag->tid->size;
|
||||
}
|
||||
|
||||
uint8_t* uhf_tag_get_user(UHFTag* uhf_tag) {
|
||||
return uhf_tag->user->data;
|
||||
}
|
||||
|
||||
size_t uhf_tag_get_user_size(UHFTag* uhf_tag) {
|
||||
return uhf_tag->user->size;
|
||||
}
|
||||
80
applications/external/uhf_rfid/uhf_tag.h
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define MAX_BANK_SIZE 256
|
||||
// storage enum
|
||||
typedef enum { ReservedBank, EPCBank, TIDBank, UserBank } BankType;
|
||||
|
||||
// Reserved Memory Bank
|
||||
typedef struct {
|
||||
uint8_t kill_password[2]; // 2 bytes (16 bits) for kill password
|
||||
uint8_t access_password[2]; // 2 bytes (16 bits) for access password
|
||||
} ReservedMemoryBank;
|
||||
|
||||
// EPC Memory Bank
|
||||
typedef struct {
|
||||
size_t size; // Size of EPC memory data
|
||||
uint8_t data[MAX_BANK_SIZE]; // 2 bytes for CRC16, 2 bytes for PC, and max 14 bytes for EPC
|
||||
uint16_t pc;
|
||||
uint16_t crc;
|
||||
} EPCMemoryBank;
|
||||
|
||||
// TID Memory Bank
|
||||
typedef struct {
|
||||
size_t size; // Size of TID memory data
|
||||
uint8_t data[MAX_BANK_SIZE]; // 4 bytes for Class ID
|
||||
} TIDMemoryBank;
|
||||
|
||||
// User Memory Bank
|
||||
typedef struct {
|
||||
size_t size; // Size of user memory data
|
||||
uint8_t data[MAX_BANK_SIZE]; // Assuming max 512 bits (64 bytes) for User Memory
|
||||
} UserMemoryBank;
|
||||
|
||||
// EPC Gen 2 Tag containing all memory banks
|
||||
typedef struct {
|
||||
ReservedMemoryBank* reserved;
|
||||
EPCMemoryBank* epc;
|
||||
TIDMemoryBank* tid;
|
||||
UserMemoryBank* user;
|
||||
} UHFTag;
|
||||
|
||||
typedef struct UHFTagWrapper {
|
||||
UHFTag* uhf_tag;
|
||||
} UHFTagWrapper;
|
||||
|
||||
UHFTagWrapper* uhf_tag_wrapper_alloc();
|
||||
void uhf_tag_wrapper_set_tag(UHFTagWrapper* uhf_tag_wrapper, UHFTag* uhf_tag);
|
||||
void uhf_tag_wrapper_free(UHFTagWrapper* uhf_tag_wrapper);
|
||||
|
||||
UHFTag* uhf_tag_alloc();
|
||||
void uhf_tag_reset(UHFTag* uhf_tag);
|
||||
void uhf_tag_free(UHFTag* uhf_tag);
|
||||
|
||||
void uhf_tag_set_kill_pwd(UHFTag* uhf_tag, uint8_t* data_in);
|
||||
void uhf_tag_set_access_pwd(UHFTag* uhf_tag, uint8_t* data_in);
|
||||
void uhf_tag_set_epc_pc(UHFTag* uhf_tag, uint16_t pc);
|
||||
void uhf_tag_set_epc_crc(UHFTag* uhf_tag, uint16_t crc);
|
||||
void uhf_tag_set_epc(UHFTag* uhf_tag, uint8_t* data_in, size_t size);
|
||||
void uhf_tag_set_epc_size(UHFTag* uhf_tag, size_t size);
|
||||
void uhf_tag_set_tid(UHFTag* uhf_tag, uint8_t* data_in, size_t size);
|
||||
void uhf_tag_set_tid_size(UHFTag* uhf_tag, size_t size);
|
||||
void uhf_tag_set_user(UHFTag* uhf_tag, uint8_t* data_in, size_t size);
|
||||
void uhf_tag_set_user_size(UHFTag* uhf_tag, size_t size);
|
||||
|
||||
uint8_t* uhf_tag_get_kill_pwd(UHFTag* uhf_tag);
|
||||
uint8_t* uhf_tag_get_access_pwd(UHFTag* uhf_tag);
|
||||
uint8_t* uhf_tag_get_epc(UHFTag* uhf_tag);
|
||||
uint16_t uhf_tag_get_epc_pc(UHFTag* uhf_tag);
|
||||
uint16_t uhf_tag_get_epc_crc(UHFTag* uhf_tag);
|
||||
size_t uhf_tag_get_epc_size(UHFTag* uhf_tag);
|
||||
uint8_t* uhf_tag_get_tid(UHFTag* uhf_tag);
|
||||
size_t uhf_tag_get_tid_size(UHFTag* uhf_tag);
|
||||
uint8_t* uhf_tag_get_user(UHFTag* uhf_tag);
|
||||
size_t uhf_tag_get_user_size(UHFTag* uhf_tag);
|
||||
|
||||
// debug
|
||||
char* uhf_tag_get_cstr(UHFTag* uhf_tag);
|
||||
138
applications/external/uhf_rfid/uhf_worker.c
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "uhf_worker.h"
|
||||
#include "uhf_tag.h"
|
||||
|
||||
// yrm100 module commands
|
||||
UHFWorkerEvent verify_module_connected(UHFWorker* uhf_worker) {
|
||||
char* hw_version = m100_get_hardware_version(uhf_worker->module);
|
||||
char* sw_version = m100_get_software_version(uhf_worker->module);
|
||||
char* manufacturer = m100_get_manufacturers(uhf_worker->module);
|
||||
// verify all data exists
|
||||
if(hw_version == NULL || sw_version == NULL || manufacturer == NULL) return UHFWorkerEventFail;
|
||||
return UHFWorkerEventSuccess;
|
||||
}
|
||||
|
||||
UHFTag* send_polling_command(UHFWorker* uhf_worker) {
|
||||
// read epc bank
|
||||
UHFTag* uhf_tag = uhf_tag_alloc();
|
||||
while(true) {
|
||||
M100ResponseType status = m100_single_poll(uhf_worker->module, uhf_tag);
|
||||
if(uhf_worker->state == UHFWorkerStateStop) {
|
||||
uhf_tag_free(uhf_tag);
|
||||
return NULL;
|
||||
}
|
||||
if(status == M100SuccessResponse) break;
|
||||
}
|
||||
return uhf_tag;
|
||||
}
|
||||
|
||||
UHFWorkerEvent read_bank_till_max_length(UHFWorker* uhf_worker, UHFTag* uhf_tag, BankType bank) {
|
||||
unsigned int word_low = 0, word_high = 64;
|
||||
unsigned int word_size;
|
||||
M100ResponseType status;
|
||||
do {
|
||||
if(uhf_worker->state == UHFWorkerStateStop) return UHFWorkerEventAborted;
|
||||
if(word_low >= word_high) return UHFWorkerEventSuccess;
|
||||
word_size = (word_low + word_high) / 2;
|
||||
status = m100_read_label_data_storage(uhf_worker->module, uhf_tag, bank, 0, word_size);
|
||||
if(status == M100SuccessResponse) {
|
||||
word_low = word_size + 1;
|
||||
} else if(status == M100MemoryOverrun) {
|
||||
word_high = word_size - 1;
|
||||
}
|
||||
} while(true);
|
||||
return UHFWorkerEventSuccess;
|
||||
}
|
||||
|
||||
UHFWorkerEvent read_single_card(UHFWorker* uhf_worker) {
|
||||
UHFTag* uhf_tag = send_polling_command(uhf_worker);
|
||||
if(uhf_tag == NULL) return UHFWorkerEventAborted;
|
||||
uhf_tag_wrapper_set_tag(uhf_worker->uhf_tag_wrapper, uhf_tag);
|
||||
// set select
|
||||
if(m100_set_select(uhf_worker->module, uhf_tag) != M100SuccessResponse)
|
||||
return UHFWorkerEventFail;
|
||||
// read tid
|
||||
UHFWorkerEvent event;
|
||||
event = read_bank_till_max_length(uhf_worker, uhf_tag, TIDBank);
|
||||
if(event != UHFWorkerEventSuccess) return event;
|
||||
// read user
|
||||
event = read_bank_till_max_length(uhf_worker, uhf_tag, UserBank);
|
||||
if(event != UHFWorkerEventSuccess) return event;
|
||||
return UHFWorkerEventSuccess;
|
||||
}
|
||||
|
||||
UHFWorkerEvent write_single_card(UHFWorker* uhf_worker) {
|
||||
UHFTag* uhf_tag_des = send_polling_command(uhf_worker);
|
||||
if(uhf_tag_des == NULL) return UHFWorkerEventAborted;
|
||||
UHFTag* uhf_tag_from = uhf_worker->uhf_tag_wrapper->uhf_tag;
|
||||
if(m100_set_select(uhf_worker->module, uhf_tag_des) != M100SuccessResponse)
|
||||
return UHFWorkerEventFail;
|
||||
do {
|
||||
M100ResponseType rp_type = m100_write_label_data_storage(
|
||||
uhf_worker->module, uhf_tag_from, uhf_tag_des, UserBank, 0, 0);
|
||||
if(uhf_worker->state == UHFWorkerStateStop) return UHFWorkerEventAborted;
|
||||
if(rp_type == M100SuccessResponse) break;
|
||||
} while(true);
|
||||
do {
|
||||
M100ResponseType rp_type = m100_write_label_data_storage(
|
||||
uhf_worker->module, uhf_tag_from, uhf_tag_des, EPCBank, 0, 0);
|
||||
if(uhf_worker->state == UHFWorkerStateStop) return UHFWorkerEventAborted;
|
||||
if(rp_type == M100SuccessResponse) break;
|
||||
} while(true);
|
||||
return UHFWorkerEventSuccess;
|
||||
}
|
||||
|
||||
int32_t uhf_worker_task(void* ctx) {
|
||||
UHFWorker* uhf_worker = ctx;
|
||||
if(uhf_worker->state == UHFWorkerStateVerify) {
|
||||
UHFWorkerEvent event = verify_module_connected(uhf_worker);
|
||||
uhf_worker->callback(event, uhf_worker->ctx);
|
||||
} else if(uhf_worker->state == UHFWorkerStateDetectSingle) {
|
||||
UHFWorkerEvent event = read_single_card(uhf_worker);
|
||||
uhf_worker->callback(event, uhf_worker->ctx);
|
||||
} else if(uhf_worker->state == UHFWorkerStateWriteSingle) {
|
||||
UHFWorkerEvent event = write_single_card(uhf_worker);
|
||||
uhf_worker->callback(event, uhf_worker->ctx);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
UHFWorker* uhf_worker_alloc() {
|
||||
UHFWorker* uhf_worker = (UHFWorker*)malloc(sizeof(UHFWorker));
|
||||
uhf_worker->thread = furi_thread_alloc_ex("UHFWorker", 8 * 1024, uhf_worker_task, uhf_worker);
|
||||
uhf_worker->module = m100_module_alloc();
|
||||
uhf_worker->callback = NULL;
|
||||
uhf_worker->ctx = NULL;
|
||||
return uhf_worker;
|
||||
}
|
||||
|
||||
void uhf_worker_change_state(UHFWorker* worker, UHFWorkerState state) {
|
||||
worker->state = state;
|
||||
}
|
||||
|
||||
void uhf_worker_start(
|
||||
UHFWorker* uhf_worker,
|
||||
UHFWorkerState state,
|
||||
UHFWorkerCallback callback,
|
||||
void* ctx) {
|
||||
uhf_worker->state = state;
|
||||
uhf_worker->callback = callback;
|
||||
uhf_worker->ctx = ctx;
|
||||
furi_thread_start(uhf_worker->thread);
|
||||
}
|
||||
|
||||
void uhf_worker_stop(UHFWorker* uhf_worker) {
|
||||
furi_assert(uhf_worker);
|
||||
furi_assert(uhf_worker->thread);
|
||||
|
||||
if(furi_thread_get_state(uhf_worker->thread) != FuriThreadStateStopped) {
|
||||
uhf_worker_change_state(uhf_worker, UHFWorkerStateStop);
|
||||
furi_thread_join(uhf_worker->thread);
|
||||
}
|
||||
}
|
||||
|
||||
void uhf_worker_free(UHFWorker* uhf_worker) {
|
||||
furi_assert(uhf_worker);
|
||||
furi_thread_free(uhf_worker->thread);
|
||||
m100_module_free(uhf_worker->module);
|
||||
free(uhf_worker);
|
||||
}
|
||||
49
applications/external/uhf_rfid/uhf_worker.h
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "uhf_module.h"
|
||||
|
||||
typedef enum {
|
||||
// Init states
|
||||
UHFWorkerStateNone,
|
||||
UHFWorkerStateBroken,
|
||||
UHFWorkerStateReady,
|
||||
UHFWorkerStateVerify,
|
||||
// Main worker states
|
||||
UHFWorkerStateDetectSingle,
|
||||
UHFWorkerStateWriteSingle,
|
||||
UHFWorkerStateWriteKey,
|
||||
// Transition
|
||||
UHFWorkerStateStop,
|
||||
} UHFWorkerState;
|
||||
|
||||
typedef enum {
|
||||
UHFWorkerEventSuccess,
|
||||
UHFWorkerEventFail,
|
||||
UHFWorkerEventNoTagDetected,
|
||||
UHFWorkerEventAborted,
|
||||
UHFWorkerEventCardDetected,
|
||||
} UHFWorkerEvent;
|
||||
|
||||
typedef void (*UHFWorkerCallback)(UHFWorkerEvent event, void* ctx);
|
||||
|
||||
typedef struct UHFWorker {
|
||||
FuriThread* thread;
|
||||
M100Module* module;
|
||||
UHFWorkerCallback callback;
|
||||
UHFWorkerState state;
|
||||
UHFTagWrapper* uhf_tag_wrapper;
|
||||
void* ctx;
|
||||
} UHFWorker;
|
||||
|
||||
int32_t uhf_worker_task(void* ctx);
|
||||
UHFWorker* uhf_worker_alloc();
|
||||
void uhf_worker_change_state(UHFWorker* worker, UHFWorkerState state);
|
||||
void uhf_worker_start(
|
||||
UHFWorker* uhf_worker,
|
||||
UHFWorkerState state,
|
||||
UHFWorkerCallback callback,
|
||||
void* ctx);
|
||||
void uhf_worker_stop(UHFWorker* uhf_worker);
|
||||
void uhf_worker_free(UHFWorker* uhf_worker);
|
||||
@@ -135,11 +135,9 @@ FS_Error archive_copy_rename_file_or_dir(
|
||||
bool copy,
|
||||
bool find_name) {
|
||||
furi_assert(context);
|
||||
const char* dst_cstr = furi_string_get_cstr(dst_path);
|
||||
FURI_LOG_I(
|
||||
TAG, "%s from %s to %s", copy ? "Copy" : "Move", src_path, furi_string_get_cstr(dst_path));
|
||||
|
||||
FURI_LOG_I(TAG, "%s from %s to %s", copy ? "Copy" : "Move", src_path, dst_cstr);
|
||||
|
||||
UNUSED(context);
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
FileInfo fileinfo;
|
||||
@@ -147,17 +145,17 @@ FS_Error archive_copy_rename_file_or_dir(
|
||||
|
||||
FS_Error error = FSE_OK;
|
||||
|
||||
if(!path_contains_only_ascii(dst_cstr)) {
|
||||
if(!path_contains_only_ascii(furi_string_get_cstr(dst_path))) {
|
||||
error = FSE_INVALID_NAME;
|
||||
} else if(!copy && !strcmp(src_path, dst_cstr)) {
|
||||
} else if(!copy && !strcmp(src_path, furi_string_get_cstr(dst_path))) {
|
||||
error = FSE_EXIST;
|
||||
} else {
|
||||
if(find_name && storage_common_exists(fs_api, dst_cstr)) {
|
||||
if(find_name && storage_common_exists(fs_api, furi_string_get_cstr(dst_path))) {
|
||||
FuriString* dir_path = furi_string_alloc();
|
||||
FuriString* filename = furi_string_alloc();
|
||||
FuriString* file_ext = furi_string_alloc();
|
||||
|
||||
path_extract_dirname(dst_cstr, dir_path);
|
||||
path_extract_dirname(furi_string_get_cstr(dst_path), dir_path);
|
||||
path_extract_filename(dst_path, filename, true);
|
||||
path_extract_ext_str(dst_path, file_ext);
|
||||
|
||||
@@ -168,7 +166,8 @@ FS_Error archive_copy_rename_file_or_dir(
|
||||
furi_string_get_cstr(file_ext),
|
||||
dst_path,
|
||||
255);
|
||||
furi_string_cat_printf(dir_path, "/%s%s", dst_cstr, furi_string_get_cstr(file_ext));
|
||||
furi_string_cat_printf(
|
||||
dir_path, "/%s%s", furi_string_get_cstr(dst_path), furi_string_get_cstr(file_ext));
|
||||
furi_string_set(dst_path, dir_path);
|
||||
|
||||
furi_string_free(dir_path);
|
||||
@@ -177,24 +176,31 @@ FS_Error archive_copy_rename_file_or_dir(
|
||||
}
|
||||
|
||||
if(copy) {
|
||||
error = storage_common_copy(fs_api, src_path, dst_cstr);
|
||||
error = storage_common_copy(fs_api, src_path, furi_string_get_cstr(dst_path));
|
||||
} else {
|
||||
error = storage_common_rename(fs_api, src_path, dst_cstr);
|
||||
error = storage_common_rename(fs_api, src_path, furi_string_get_cstr(dst_path));
|
||||
}
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
if(!copy && archive_is_favorite("%s", src_path)) {
|
||||
archive_favorites_rename(src_path, dst_cstr);
|
||||
archive_favorites_rename(src_path, furi_string_get_cstr(dst_path));
|
||||
}
|
||||
|
||||
if(error == FSE_OK) {
|
||||
FURI_LOG_I(TAG, "%s from %s to %s is DONE", copy ? "Copy" : "Move", src_path, dst_cstr);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"%s from %s to %s is DONE",
|
||||
copy ? "Copy" : "Move",
|
||||
src_path,
|
||||
furi_string_get_cstr(dst_path));
|
||||
} else {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"%s failed: %s, Code: %d",
|
||||
"%s from %s to %s failed: %s, Code: %d",
|
||||
copy ? "Copy" : "Move",
|
||||
src_path,
|
||||
furi_string_get_cstr(dst_path),
|
||||
filesystem_api_error_get_desc(error),
|
||||
error);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,9 @@ void gpio_scene_start_on_enter(void* context) {
|
||||
GpioOtgSettingsNum,
|
||||
gpio_scene_start_var_list_change_callback,
|
||||
app);
|
||||
if(furi_hal_power_is_otg_enabled()) {
|
||||
if(furi_hal_power_is_charging()) {
|
||||
variable_item_set_locked(item, true, "Unplug USB!");
|
||||
} else if(furi_hal_power_is_otg_enabled()) {
|
||||
variable_item_set_current_value_index(item, GpioOtgOn);
|
||||
variable_item_set_current_value_text(item, gpio_otg_text[GpioOtgOn]);
|
||||
} else {
|
||||
|
||||
@@ -8,21 +8,19 @@ void ibutton_scene_info_on_enter(void* context) {
|
||||
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
|
||||
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
FuriString* keynumber = furi_string_alloc();
|
||||
|
||||
ibutton_protocols_render_brief_data(ibutton->protocols, key, keynumber);
|
||||
|
||||
furi_string_printf(
|
||||
tmp,
|
||||
"\e#%s\n[%s]\e#",
|
||||
"\e#%s\n[%s]\e#\n%s",
|
||||
ibutton->key_name,
|
||||
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
|
||||
ibutton_protocols_get_name(ibutton->protocols, protocol_id),
|
||||
furi_string_get_cstr(keynumber));
|
||||
|
||||
widget_add_text_box_element(
|
||||
widget, 0, 2, 128, 40, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true);
|
||||
|
||||
furi_string_reset(tmp);
|
||||
ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
|
||||
|
||||
widget_add_string_multiline_element(
|
||||
widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
|
||||
widget, 0, 2, 128, 64, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true);
|
||||
|
||||
if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) &
|
||||
iButtonProtocolFeatureExtData) {
|
||||
@@ -32,6 +30,7 @@ void ibutton_scene_info_on_enter(void* context) {
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
furi_string_free(tmp);
|
||||
furi_string_free(keynumber);
|
||||
}
|
||||
|
||||
bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
@@ -204,6 +204,21 @@ static InfraredApp* infrared_alloc() {
|
||||
infrared->loading = loading_alloc();
|
||||
infrared->progress = infrared_progress_view_alloc();
|
||||
|
||||
infrared->last_settings = infrared_last_settings_alloc();
|
||||
infrared_last_settings_load(infrared->last_settings);
|
||||
|
||||
if(infrared->last_settings->ext_5v) {
|
||||
uint8_t attempts = 0;
|
||||
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
|
||||
furi_hal_power_enable_otg();
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
}
|
||||
|
||||
if(infrared->last_settings->ext_out && !furi_hal_infrared_get_debug_out_status()) {
|
||||
furi_hal_infrared_set_debug_out(true);
|
||||
}
|
||||
|
||||
return infrared;
|
||||
}
|
||||
|
||||
@@ -271,6 +286,14 @@ static void infrared_free(InfraredApp* infrared) {
|
||||
furi_string_free(infrared->file_path);
|
||||
furi_string_free(infrared->button_name);
|
||||
|
||||
if(infrared->last_settings->ext_5v) {
|
||||
if(furi_hal_power_is_otg_enabled()) {
|
||||
furi_hal_power_disable_otg();
|
||||
}
|
||||
}
|
||||
|
||||
infrared_last_settings_free(infrared->last_settings);
|
||||
|
||||
free(infrared);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "infrared_remote.h"
|
||||
#include "infrared_brute_force.h"
|
||||
#include "infrared_custom_event.h"
|
||||
#include "infrared_last_settings.h"
|
||||
|
||||
#include "scenes/infrared_scene.h"
|
||||
#include "views/infrared_progress_view.h"
|
||||
@@ -38,6 +39,7 @@
|
||||
#include "views/infrared_move_view.h"
|
||||
|
||||
#include "rpc/rpc_app.h"
|
||||
#include <furi_hal_infrared.h>
|
||||
|
||||
#define INFRARED_FILE_NAME_SIZE 100
|
||||
#define INFRARED_TEXT_STORE_NUM 2
|
||||
@@ -127,6 +129,7 @@ struct InfraredApp {
|
||||
/** Arbitrary text storage for various inputs. */
|
||||
char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1];
|
||||
InfraredAppState app_state; /**< Application state. */
|
||||
InfraredLastSettings* last_settings; /**< Last settings. */
|
||||
|
||||
void* rpc_ctx; /**< Pointer to the RPC context object. */
|
||||
};
|
||||
|
||||
88
applications/main/infrared/infrared_last_settings.c
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "infrared_last_settings.h"
|
||||
|
||||
#define TAG "InfraredLastSettings"
|
||||
|
||||
#define INFRARED_LAST_SETTINGS_FILE_TYPE "Flipper Infrared Last Settings File"
|
||||
#define INFRARED_LAST_SETTINGS_FILE_VERSION 1
|
||||
#define INFRARED_LAST_SETTINGS_PATH EXT_PATH("infrared/assets/last_infrared.settings")
|
||||
|
||||
#define INFRARED_LAST_SETTINGS_FIELD_EXTPOWER "External5V"
|
||||
#define INFRARED_LAST_SETTINGS_FIELD_EXTOUT "ExternalOut"
|
||||
|
||||
InfraredLastSettings* infrared_last_settings_alloc(void) {
|
||||
InfraredLastSettings* instance = malloc(sizeof(InfraredLastSettings));
|
||||
return instance;
|
||||
}
|
||||
|
||||
void infrared_last_settings_free(InfraredLastSettings* instance) {
|
||||
furi_assert(instance);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void infrared_last_settings_load(InfraredLastSettings* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
|
||||
|
||||
bool temp_extpower = false;
|
||||
bool temp_extout = false;
|
||||
|
||||
if(FSE_OK == storage_sd_status(storage) && INFRARED_LAST_SETTINGS_PATH &&
|
||||
flipper_format_file_open_existing(fff_data_file, INFRARED_LAST_SETTINGS_PATH)) {
|
||||
flipper_format_read_bool(
|
||||
fff_data_file, INFRARED_LAST_SETTINGS_FIELD_EXTPOWER, (bool*)&temp_extpower, 1);
|
||||
flipper_format_read_bool(
|
||||
fff_data_file, INFRARED_LAST_SETTINGS_FIELD_EXTOUT, (bool*)&temp_extout, 1);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Error open file %s", INFRARED_LAST_SETTINGS_PATH);
|
||||
}
|
||||
|
||||
instance->ext_5v = temp_extpower;
|
||||
instance->ext_out = temp_extout;
|
||||
|
||||
flipper_format_file_close(fff_data_file);
|
||||
flipper_format_free(fff_data_file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
bool infrared_last_settings_save(InfraredLastSettings* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bool saved = false;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
|
||||
do {
|
||||
if(FSE_OK != storage_sd_status(storage)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Open file
|
||||
if(!flipper_format_file_open_always(file, INFRARED_LAST_SETTINGS_PATH)) break;
|
||||
|
||||
// Write header
|
||||
if(!flipper_format_write_header_cstr(
|
||||
file, INFRARED_LAST_SETTINGS_FILE_TYPE, INFRARED_LAST_SETTINGS_FILE_VERSION))
|
||||
break;
|
||||
|
||||
if(!flipper_format_insert_or_update_bool(
|
||||
file, INFRARED_LAST_SETTINGS_FIELD_EXTPOWER, &instance->ext_5v, 1))
|
||||
break;
|
||||
if(!flipper_format_insert_or_update_bool(
|
||||
file, INFRARED_LAST_SETTINGS_FIELD_EXTOUT, &instance->ext_out, 1))
|
||||
break;
|
||||
|
||||
saved = true;
|
||||
} while(0);
|
||||
|
||||
if(!saved) {
|
||||
FURI_LOG_E(TAG, "Error save file %s", INFRARED_LAST_SETTINGS_PATH);
|
||||
}
|
||||
|
||||
flipper_format_file_close(file);
|
||||
flipper_format_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return saved;
|
||||
}
|
||||
15
applications/main/infrared/infrared_last_settings.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <storage/storage.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
|
||||
typedef struct {
|
||||
bool ext_5v;
|
||||
bool ext_out;
|
||||
} InfraredLastSettings;
|
||||
|
||||
InfraredLastSettings* infrared_last_settings_alloc(void);
|
||||
void infrared_last_settings_free(InfraredLastSettings* instance);
|
||||
void infrared_last_settings_load(InfraredLastSettings* instance);
|
||||
bool infrared_last_settings_save(InfraredLastSettings* instance);
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "../infrared_app_i.h"
|
||||
#include <furi_hal_infrared.h>
|
||||
|
||||
uint8_t value_index_ir;
|
||||
|
||||
@@ -12,14 +11,17 @@ const char* const infrared_debug_cfg_variables_text[] = {
|
||||
static void infrared_scene_debug_settings_changed(VariableItem* item) {
|
||||
InfraredApp* infrared = variable_item_get_context(item);
|
||||
value_index_ir = variable_item_get_current_value_index(item);
|
||||
UNUSED(infrared);
|
||||
|
||||
variable_item_set_current_value_text(item, infrared_debug_cfg_variables_text[value_index_ir]);
|
||||
|
||||
furi_hal_infrared_set_debug_out(value_index_ir);
|
||||
|
||||
infrared->last_settings->ext_out = value_index_ir == 1;
|
||||
infrared_last_settings_save(infrared->last_settings);
|
||||
}
|
||||
|
||||
static void infrared_scene_debug_settings_power_changed(VariableItem* item) {
|
||||
InfraredApp* infrared = variable_item_get_context(item);
|
||||
bool value = variable_item_get_current_value_index(item);
|
||||
if(value) {
|
||||
for(int i = 0; i < 5 && !furi_hal_power_is_otg_enabled(); i++) {
|
||||
@@ -32,6 +34,9 @@ static void infrared_scene_debug_settings_power_changed(VariableItem* item) {
|
||||
}
|
||||
}
|
||||
variable_item_set_current_value_text(item, value ? "ON" : "OFF");
|
||||
|
||||
infrared->last_settings->ext_5v = value;
|
||||
infrared_last_settings_save(infrared->last_settings);
|
||||
}
|
||||
|
||||
static void infrared_debug_settings_start_var_list_enter_callback(void* context, uint32_t index) {
|
||||
@@ -64,7 +69,8 @@ void infrared_scene_debug_settings_on_enter(void* context) {
|
||||
2,
|
||||
infrared_scene_debug_settings_power_changed,
|
||||
infrared);
|
||||
bool enabled = furi_hal_power_is_otg_enabled();
|
||||
bool enabled = furi_hal_power_is_otg_enabled() ||
|
||||
furi_hal_power_is_charging(); // 5v is enabled via hardware if charging
|
||||
variable_item_set_current_value_index(item, enabled);
|
||||
variable_item_set_current_value_text(item, enabled ? "ON" : "OFF");
|
||||
|
||||
|
||||
@@ -55,6 +55,15 @@ App(
|
||||
sources=["plugins/supported_cards/troika.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="social_moscow_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="social_moscow_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/social_moscow.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="plantain_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
@@ -73,6 +82,33 @@ App(
|
||||
sources=["plugins/supported_cards/two_cities.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="umarsh_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="umarsh_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/umarsh.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="metromoney_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="metromoney_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/metromoney.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="kazan_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="kazan_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/kazan.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="nfc_start",
|
||||
targets=["f7"],
|
||||
|
||||
@@ -67,8 +67,14 @@ static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t even
|
||||
return false;
|
||||
}
|
||||
|
||||
static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) {
|
||||
const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica);
|
||||
instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolFelica, data);
|
||||
nfc_listener_start(instance->listener, NULL, NULL);
|
||||
}
|
||||
|
||||
const NfcProtocolSupportBase nfc_protocol_support_felica = {
|
||||
.features = NfcProtocolFeatureNone,
|
||||
.features = NfcProtocolFeatureEmulateUid,
|
||||
|
||||
.scene_info =
|
||||
{
|
||||
@@ -102,7 +108,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = {
|
||||
},
|
||||
.scene_emulate =
|
||||
{
|
||||
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
||||
.on_enter = nfc_scene_emulate_on_enter_felica,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -586,6 +586,7 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
|
||||
widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating");
|
||||
furi_string_set(
|
||||
temp_str, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull));
|
||||
furi_string_cat_printf(temp_str, "\n%s", furi_string_get_cstr(instance->file_name));
|
||||
}
|
||||
|
||||
widget_add_text_box_element(
|
||||
|
||||
287
applications/main/nfc/plugins/supported_cards/kazan.c
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Parser for Kazan transport card (Kazan, Russia).
|
||||
*
|
||||
* Copyright 2023 Leptoptilos <leptoptilos@icloud.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "core/log.h"
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include "protocols/mf_classic/mf_classic.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
#include "md5.h"
|
||||
|
||||
#define TAG "Kazan"
|
||||
|
||||
typedef struct {
|
||||
uint64_t a;
|
||||
uint64_t b;
|
||||
} MfClassicKeyPair;
|
||||
|
||||
static const MfClassicKeyPair kazan_1k_keys[] = {
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xE954024EE754, .b = 0x0CD464CDC100},
|
||||
{.a = 0xBC305FE2DA65, .b = 0xCF0EC6ACF2F9},
|
||||
{.a = 0xF7A545095C49, .b = 0x6862FD600F78},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
};
|
||||
|
||||
enum SubscriptionType {
|
||||
SUBSCRIPTION_TYPE_UNKNOWN,
|
||||
SUBSCRIPTION_TYPE_PURSE,
|
||||
SUBSCRIPTION_TYPE_ABONNEMENT,
|
||||
};
|
||||
|
||||
enum SubscriptionType get_subscription_type(uint8_t value) {
|
||||
switch(value) {
|
||||
case 0:
|
||||
case 0x60:
|
||||
case 0x67:
|
||||
case 0x0F:
|
||||
return SUBSCRIPTION_TYPE_ABONNEMENT;
|
||||
case 0x53:
|
||||
return SUBSCRIPTION_TYPE_PURSE;
|
||||
default:
|
||||
return SUBSCRIPTION_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static bool kazan_verify(Nfc* nfc) {
|
||||
bool verified = false;
|
||||
|
||||
do {
|
||||
const uint8_t ticket_sector_number = 8;
|
||||
const uint8_t ticket_block_number =
|
||||
mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1;
|
||||
FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number);
|
||||
|
||||
MfClassicKey key = {0};
|
||||
nfc_util_num2bytes(kazan_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data);
|
||||
|
||||
MfClassicAuthContext auth_context;
|
||||
MfClassicError error = mf_classic_poller_sync_auth(
|
||||
nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error);
|
||||
break;
|
||||
}
|
||||
|
||||
verified = true;
|
||||
} while(false);
|
||||
|
||||
return verified;
|
||||
}
|
||||
|
||||
static bool kazan_read(Nfc* nfc, NfcDevice* device) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(device);
|
||||
|
||||
bool is_read = false;
|
||||
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicTypeMini;
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
data->type = type;
|
||||
if(type != MfClassicType1k) break;
|
||||
|
||||
MfClassicDeviceKeys keys = {
|
||||
.key_a_mask = 0,
|
||||
.key_b_mask = 0,
|
||||
};
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
nfc_util_num2bytes(kazan_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
nfc_util_num2bytes(kazan_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
}
|
||||
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
is_read = true;
|
||||
} while(false);
|
||||
|
||||
mf_classic_free(data);
|
||||
|
||||
return is_read;
|
||||
}
|
||||
|
||||
static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
const uint8_t ticket_sector_number = 8;
|
||||
const uint8_t balance_sector_number = 9;
|
||||
|
||||
// Verify keys
|
||||
MfClassicKeyPair keys = {};
|
||||
const MfClassicSectorTrailer* sec_tr =
|
||||
mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number);
|
||||
|
||||
keys.a = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
||||
keys.b = nfc_util_bytes2num(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data));
|
||||
|
||||
if((keys.a != 0xE954024EE754) && (keys.b != 0x0CD464CDC100)) break;
|
||||
|
||||
// Parse data
|
||||
uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(ticket_sector_number);
|
||||
|
||||
const uint8_t* block_start_ptr = &data->block[start_block_num].data[6];
|
||||
|
||||
enum SubscriptionType subscription_type = get_subscription_type(block_start_ptr[0]);
|
||||
|
||||
FuriHalRtcDateTime valid_from;
|
||||
valid_from.year = 2000 + block_start_ptr[1];
|
||||
valid_from.month = block_start_ptr[2];
|
||||
valid_from.day = block_start_ptr[3];
|
||||
|
||||
FuriHalRtcDateTime valid_to;
|
||||
valid_to.year = 2000 + block_start_ptr[4];
|
||||
valid_to.month = block_start_ptr[5];
|
||||
valid_to.day = block_start_ptr[6];
|
||||
|
||||
const uint8_t last_trip_block_number = 2;
|
||||
block_start_ptr = &data->block[start_block_num + last_trip_block_number].data[1];
|
||||
|
||||
FuriHalRtcDateTime last_trip;
|
||||
last_trip.year = 2000 + block_start_ptr[0];
|
||||
last_trip.month = block_start_ptr[1];
|
||||
last_trip.day = block_start_ptr[2];
|
||||
last_trip.hour = block_start_ptr[3];
|
||||
last_trip.minute = block_start_ptr[4];
|
||||
bool is_last_trip_valid = (block_start_ptr[0] | block_start_ptr[1] | block_start_ptr[0]) &&
|
||||
(last_trip.day < 32 && last_trip.month < 12 &&
|
||||
last_trip.hour < 24 && last_trip.minute < 60);
|
||||
|
||||
start_block_num = mf_classic_get_first_block_num_of_sector(balance_sector_number);
|
||||
block_start_ptr = &data->block[start_block_num].data[0];
|
||||
|
||||
const uint32_t trip_counter = (block_start_ptr[3] << 24) | (block_start_ptr[2] << 16) |
|
||||
(block_start_ptr[1] << 8) | (block_start_ptr[0]);
|
||||
|
||||
size_t uid_len = 0;
|
||||
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
|
||||
const uint32_t card_number = (uid[3] << 24) | (uid[2] << 16) | (uid[1] << 8) | (uid[0]);
|
||||
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "\e#Kazan transport card\nCard number: %lu\n", card_number);
|
||||
|
||||
if(subscription_type == SUBSCRIPTION_TYPE_PURSE) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Type: purse\nBalance: %lu RUR\nBalance valid:\nfrom: %02u.%02u.%u\nto: %02u.%02u.%u",
|
||||
trip_counter,
|
||||
valid_from.day,
|
||||
valid_from.month,
|
||||
valid_from.year,
|
||||
valid_to.day,
|
||||
valid_to.month,
|
||||
valid_to.year);
|
||||
}
|
||||
|
||||
if(subscription_type == SUBSCRIPTION_TYPE_ABONNEMENT) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Type: abonnement\nTrips left: %lu\nCard valid:\nfrom: %02u.%02u.%u\nto: %02u.%02u.%u",
|
||||
trip_counter,
|
||||
valid_from.day,
|
||||
valid_from.month,
|
||||
valid_from.year,
|
||||
valid_to.day,
|
||||
valid_to.month,
|
||||
valid_to.year);
|
||||
}
|
||||
|
||||
if(subscription_type == SUBSCRIPTION_TYPE_UNKNOWN) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Type: unknown\nBalance: %lu RUR\nValid from: %02u.%02u.%u\nValid to: %02u.%02u.%u",
|
||||
trip_counter,
|
||||
valid_from.day,
|
||||
valid_from.month,
|
||||
valid_from.year,
|
||||
valid_to.day,
|
||||
valid_to.month,
|
||||
valid_to.year);
|
||||
}
|
||||
|
||||
if(is_last_trip_valid) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nLast trip: %02u.%02u.%u at %02u:%02u",
|
||||
last_trip.day,
|
||||
last_trip.month,
|
||||
last_trip.year,
|
||||
last_trip.hour,
|
||||
last_trip.minute);
|
||||
}
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin kazan_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = kazan_verify,
|
||||
.read = kazan_read,
|
||||
.parse = kazan_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor kazan_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &kazan_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* kazan_plugin_ep() {
|
||||
return &kazan_plugin_descriptor;
|
||||
}
|
||||
191
applications/main/nfc/plugins/supported_cards/metromoney.c
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Parser for Metromoney card (Georgia).
|
||||
*
|
||||
* Copyright 2023 Leptoptilos <leptoptilos@icloud.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include "protocols/mf_classic/mf_classic.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TAG "Metromoney"
|
||||
|
||||
typedef struct {
|
||||
uint64_t a;
|
||||
uint64_t b;
|
||||
} MfClassicKeyPair;
|
||||
|
||||
static const MfClassicKeyPair metromoney_1k_keys[] = {
|
||||
{.a = 0x2803BCB0C7E1, .b = 0x4FA9EB49F75E},
|
||||
{.a = 0x9C616585E26D, .b = 0xD1C71E590D16},
|
||||
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
|
||||
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
|
||||
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
|
||||
{.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0x112233445566, .b = 0x361A62F35BC9},
|
||||
{.a = 0x112233445566, .b = 0x361A62F35BC9},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
|
||||
};
|
||||
|
||||
static bool metromoney_verify(Nfc* nfc) {
|
||||
bool verified = false;
|
||||
|
||||
do {
|
||||
const uint8_t ticket_sector_number = 1;
|
||||
const uint8_t ticket_block_number =
|
||||
mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1;
|
||||
FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number);
|
||||
|
||||
MfClassicKey key = {0};
|
||||
nfc_util_num2bytes(
|
||||
metromoney_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data);
|
||||
|
||||
MfClassicAuthContext auth_context;
|
||||
MfClassicError error = mf_classic_poller_sync_auth(
|
||||
nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error);
|
||||
break;
|
||||
}
|
||||
|
||||
verified = true;
|
||||
} while(false);
|
||||
|
||||
return verified;
|
||||
}
|
||||
|
||||
static bool metromoney_read(Nfc* nfc, NfcDevice* device) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(device);
|
||||
|
||||
bool is_read = false;
|
||||
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicTypeMini;
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
data->type = type;
|
||||
if(type != MfClassicType1k) break;
|
||||
|
||||
MfClassicDeviceKeys keys = {
|
||||
.key_a_mask = 0,
|
||||
.key_b_mask = 0,
|
||||
};
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
nfc_util_num2bytes(metromoney_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
nfc_util_num2bytes(metromoney_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
}
|
||||
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
is_read = true;
|
||||
} while(false);
|
||||
|
||||
mf_classic_free(data);
|
||||
|
||||
return is_read;
|
||||
}
|
||||
|
||||
static bool metromoney_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// Verify key
|
||||
const uint8_t ticket_sector_number = 1;
|
||||
const uint8_t ticket_block_number = 1;
|
||||
|
||||
const MfClassicSectorTrailer* sec_tr =
|
||||
mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number);
|
||||
|
||||
const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
||||
if(key != metromoney_1k_keys[ticket_sector_number].a) break;
|
||||
|
||||
// Parse data
|
||||
const uint8_t start_block_num =
|
||||
mf_classic_get_first_block_num_of_sector(ticket_sector_number);
|
||||
|
||||
const uint8_t* block_start_ptr =
|
||||
&data->block[start_block_num + ticket_block_number].data[0];
|
||||
|
||||
uint32_t balance = (block_start_ptr[3] << 24) | (block_start_ptr[2] << 16) |
|
||||
(block_start_ptr[1] << 8) | (block_start_ptr[0]);
|
||||
|
||||
uint32_t balance_lari = balance / 100;
|
||||
uint8_t balance_tetri = balance % 100;
|
||||
|
||||
size_t uid_len = 0;
|
||||
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
|
||||
uint32_t card_number = (uid[3] << 24) | (uid[2] << 16) | (uid[1] << 8) | (uid[0]);
|
||||
|
||||
furi_string_printf(
|
||||
parsed_data,
|
||||
"\e#Metromoney\nCard number: %lu\nBalance: %lu.%02u GEL",
|
||||
card_number,
|
||||
balance_lari,
|
||||
balance_tetri);
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin metromoney_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = metromoney_verify,
|
||||
.read = metromoney_read,
|
||||
.parse = metromoney_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor metromoney_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &metromoney_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* metromoney_plugin_ep() {
|
||||
return &metromoney_plugin_descriptor;
|
||||
}
|
||||
1629
applications/main/nfc/plugins/supported_cards/social_moscow.c
Normal file
155
applications/main/nfc/plugins/supported_cards/umarsh.c
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Parser for Umarsh card (Russia).
|
||||
*
|
||||
* Copyright 2023 Leptoptilos <leptoptilos@icloud.com>
|
||||
* Thanks https://github.com/krolchonok for the provided dumps and their analysis
|
||||
*
|
||||
* Note: All meaningful data is stored in sectors 0, 8 and 12, reading data
|
||||
* from which is possible only with the B key. The key B for these sectors
|
||||
* is unique for each card. To get it, you should use a nested attack.
|
||||
* More info about Umarsh cards: https://github.com/metrodroid/metrodroid/wiki/Umarsh
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "core/core_defines.h"
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include "protocols/mf_classic/mf_classic.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
|
||||
#define TAG "Umarsh"
|
||||
|
||||
bool parse_datetime(uint16_t date, FuriHalRtcDateTime* result) {
|
||||
result->year = 2000 + (date >> 9);
|
||||
result->month = date >> 5 & 0x0F;
|
||||
result->day = date & 0x1F;
|
||||
return (date != 0);
|
||||
}
|
||||
|
||||
static bool umarsh_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// Verify card type
|
||||
if(data->type != MfClassicType1k) break;
|
||||
|
||||
const uint8_t ticket_sector = 8;
|
||||
|
||||
const uint8_t ticket_sector_start_block_number =
|
||||
mf_classic_get_first_block_num_of_sector(ticket_sector);
|
||||
|
||||
// Validate specific for Umarsh ticket sector header
|
||||
const uint8_t* block_start_ptr = &data->block[ticket_sector_start_block_number].data[0];
|
||||
|
||||
const uint32_t header_part_0 = nfc_util_bytes2num(block_start_ptr, 4);
|
||||
const uint32_t header_part_1 = nfc_util_bytes2num(block_start_ptr + 4, 4);
|
||||
if((header_part_0 + header_part_1) != 0xFFFFFFFF) break;
|
||||
|
||||
// Data parsing from block 1
|
||||
block_start_ptr = &data->block[ticket_sector_start_block_number + 1].data[0];
|
||||
const uint16_t expiry_date = nfc_util_bytes2num(block_start_ptr + 1, 2);
|
||||
const uint8_t region_number = (((block_start_ptr[8] >> 5) & 0x07) << 4) |
|
||||
(block_start_ptr[12] & 0x0F);
|
||||
const uint8_t refill_counter = nfc_util_bytes2num(block_start_ptr + 7, 1);
|
||||
const uint32_t card_number = nfc_util_bytes2num(block_start_ptr + 8, 4) & 0x3FFFFFFF;
|
||||
|
||||
if(card_number == 0) break;
|
||||
|
||||
// Data parsing from block 2
|
||||
block_start_ptr = &data->block[ticket_sector_start_block_number + 2].data[0];
|
||||
const uint16_t valid_to = nfc_util_bytes2num(block_start_ptr, 2);
|
||||
const uint32_t terminal_number = nfc_util_bytes2num(block_start_ptr + 3, 3);
|
||||
const uint16_t last_refill_date = nfc_util_bytes2num(block_start_ptr + 6, 2);
|
||||
const uint16_t balance_rub = (nfc_util_bytes2num(block_start_ptr + 8, 2)) & 0x7FFF;
|
||||
const uint8_t balance_kop = nfc_util_bytes2num(block_start_ptr + 10, 1) & 0x7F;
|
||||
|
||||
FuriHalRtcDateTime expiry_datetime;
|
||||
bool is_expiry_datetime_valid = parse_datetime(expiry_date, &expiry_datetime);
|
||||
|
||||
FuriHalRtcDateTime valid_to_datetime;
|
||||
bool is_valid_to_datetime_valid = parse_datetime(valid_to, &valid_to_datetime);
|
||||
|
||||
FuriHalRtcDateTime last_refill_datetime;
|
||||
bool is_last_refill_datetime_valid =
|
||||
parse_datetime(last_refill_date, &last_refill_datetime);
|
||||
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\e#Umarsh\nCard number: %lu\nRegion: %02u\nTerminal number: %lu\nRefill counter: %u\nBalance: %u.%02u RUR",
|
||||
card_number,
|
||||
region_number,
|
||||
terminal_number,
|
||||
refill_counter,
|
||||
balance_rub,
|
||||
balance_kop);
|
||||
|
||||
if(is_expiry_datetime_valid)
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nExpires: %02u.%02u.%u",
|
||||
expiry_datetime.day,
|
||||
expiry_datetime.month,
|
||||
expiry_datetime.year);
|
||||
if(is_valid_to_datetime_valid)
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nValid to: %02u.%02u.%u",
|
||||
valid_to_datetime.day,
|
||||
valid_to_datetime.month,
|
||||
valid_to_datetime.year);
|
||||
if(is_last_refill_datetime_valid)
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nLast refill: %02u.%02u.%u",
|
||||
last_refill_datetime.day,
|
||||
last_refill_datetime.month,
|
||||
last_refill_datetime.year);
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin umarsh_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = NULL,
|
||||
.read = NULL,
|
||||
.parse = umarsh_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor umarsh_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &umarsh_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* umarsh_plugin_ep() {
|
||||
return &umarsh_plugin_descriptor;
|
||||
}
|
||||
@@ -138,9 +138,6 @@ SubGhzGPS* subghz_gps_init() {
|
||||
|
||||
void subghz_gps_deinit(SubGhzGPS* subghz_gps) {
|
||||
furi_assert(subghz_gps);
|
||||
furi_thread_free(subghz_gps->thread);
|
||||
|
||||
free(subghz_gps);
|
||||
|
||||
furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL);
|
||||
if(UART_CH == FuriHalUartIdLPUART1) {
|
||||
@@ -148,6 +145,11 @@ void subghz_gps_deinit(SubGhzGPS* subghz_gps) {
|
||||
} else {
|
||||
furi_hal_console_enable();
|
||||
}
|
||||
|
||||
furi_thread_free(subghz_gps->thread);
|
||||
furi_stream_buffer_free(subghz_gps->rx_stream);
|
||||
|
||||
free(subghz_gps);
|
||||
}
|
||||
|
||||
void subghz_gps_start(SubGhzGPS* subghz_gps) {
|
||||
|
||||
@@ -219,7 +219,7 @@ void subghz_scene_radio_settings_on_enter(void* context) {
|
||||
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Time In Names",
|
||||
"Protocol Names",
|
||||
TIMESTAMP_NAMES_COUNT,
|
||||
subghz_scene_receiver_config_set_timestamp_file_names,
|
||||
subghz);
|
||||
|
||||
@@ -387,31 +387,22 @@ static StorageAnimation*
|
||||
furi_record_close(RECORD_DOLPHIN);
|
||||
uint32_t whole_weight = 0;
|
||||
|
||||
// Filter valid animations
|
||||
bool fallback = xtreme_settings.fallback_anim;
|
||||
if(StorageAnimationList_size(animation_list) == dolphin_internal_size + 1 && !fallback) {
|
||||
// One ext anim and fallback disabled, dont skip current anim (current = only ext one)
|
||||
avoid_animation = NULL;
|
||||
}
|
||||
|
||||
StorageAnimationList_it_t it;
|
||||
bool unlock = xtreme_settings.unlock_anims;
|
||||
StorageAnimationList_it_t it;
|
||||
for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) {
|
||||
StorageAnimation* storage_animation = *StorageAnimationList_ref(it);
|
||||
const StorageAnimationManifestInfo* manifest_info =
|
||||
animation_storage_get_meta(storage_animation);
|
||||
bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats, unlock);
|
||||
|
||||
if(avoid_animation != NULL && strcmp(manifest_info->name, avoid_animation) == 0) {
|
||||
// Avoid repeating same animation twice
|
||||
valid = false;
|
||||
}
|
||||
if(strcmp(manifest_info->name, HARDCODED_ANIMATION_NAME) == 0 && !fallback) {
|
||||
// Skip fallback animation
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if(valid) {
|
||||
whole_weight += manifest_info->weight;
|
||||
StorageAnimationList_next(it);
|
||||
} else {
|
||||
animation_storage_free_storage_animation(&storage_animation);
|
||||
@@ -420,6 +411,28 @@ static StorageAnimation*
|
||||
}
|
||||
}
|
||||
|
||||
if(StorageAnimationList_size(animation_list) == 1) {
|
||||
// One valid anim, dont skip current anim (current = only ext one)
|
||||
avoid_animation = NULL;
|
||||
}
|
||||
|
||||
// Avoid repeating current animation and calculate weights
|
||||
for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) {
|
||||
StorageAnimation* storage_animation = *StorageAnimationList_ref(it);
|
||||
const StorageAnimationManifestInfo* manifest_info =
|
||||
animation_storage_get_meta(storage_animation);
|
||||
|
||||
if(avoid_animation && strcmp(manifest_info->name, avoid_animation) == 0) {
|
||||
// Avoid repeating same animation twice
|
||||
animation_storage_free_storage_animation(&storage_animation);
|
||||
/* remove and increase iterator */
|
||||
StorageAnimationList_remove(animation_list, it);
|
||||
} else {
|
||||
whole_weight += manifest_info->weight;
|
||||
StorageAnimationList_next(it);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t lucky_number = furi_hal_random_get() % whole_weight;
|
||||
uint32_t weight = 0;
|
||||
|
||||
|
||||
@@ -757,6 +757,10 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) {
|
||||
storage_path_trim_trailing_slashes(path2);
|
||||
storage_process_alias(app, path1, message->data->cequivpath.thread_id, false);
|
||||
storage_process_alias(app, path2, message->data->cequivpath.thread_id, false);
|
||||
// Comparison is done on path name, same beginning of name != same file/folder
|
||||
// Check with a / suffixed to ensure same file/folder name
|
||||
furi_string_cat(path1, "/");
|
||||
furi_string_cat(path2, "/");
|
||||
if(message->data->cequivpath.truncate) {
|
||||
furi_string_left(path2, furi_string_size(path1));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
App(
|
||||
appid="hex_viewer",
|
||||
name="Hex Viewer",
|
||||
name="HEX Viewer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="hex_viewer_app",
|
||||
requires=[
|
||||
@@ -8,10 +8,11 @@ App(
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
order=20,
|
||||
fap_icon="icons/hex_10px.png",
|
||||
fap_category="Tools",
|
||||
fap_icon_assets="icons",
|
||||
fap_category="Tools",
|
||||
fap_author="@QtRoS",
|
||||
fap_version="1.1",
|
||||
fap_version="2.0",
|
||||
fap_description="App allows to view various files as HEX.",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
HexViewerCustomEventStartscreenUp,
|
||||
HexViewerCustomEventStartscreenDown,
|
||||
HexViewerCustomEventStartscreenLeft,
|
||||
HexViewerCustomEventStartscreenRight,
|
||||
HexViewerCustomEventStartscreenOk,
|
||||
HexViewerCustomEventStartscreenBack,
|
||||
HexViewerCustomEventScene1Up,
|
||||
HexViewerCustomEventScene1Down,
|
||||
HexViewerCustomEventScene1Left,
|
||||
HexViewerCustomEventScene1Right,
|
||||
HexViewerCustomEventScene1Ok,
|
||||
HexViewerCustomEventScene1Back,
|
||||
HexViewerCustomEventScene2Up,
|
||||
HexViewerCustomEventScene2Down,
|
||||
HexViewerCustomEventScene2Left,
|
||||
HexViewerCustomEventScene2Right,
|
||||
HexViewerCustomEventScene2Ok,
|
||||
HexViewerCustomEventScene2Back,
|
||||
} HexViewerCustomEvent;
|
||||
|
||||
enum HexViewerCustomEventType {
|
||||
// Reserve first 100 events for button types and indexes, starting from 0
|
||||
HexViewerCustomEventMenuVoid,
|
||||
HexViewerCustomEventMenuSelected,
|
||||
HexViewerCustomEventMenuPercentEntered,
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef union {
|
||||
uint32_t packed_value;
|
||||
struct {
|
||||
uint16_t type;
|
||||
int16_t value;
|
||||
} content;
|
||||
} HexViewerCustomEventMenu;
|
||||
#pragma pack(pop)
|
||||
|
||||
static inline uint32_t hex_viewer_custom_menu_event_pack(uint16_t type, int16_t value) {
|
||||
HexViewerCustomEventMenu event = {.content = {.type = type, .value = value}};
|
||||
return event.packed_value;
|
||||
}
|
||||
static inline void
|
||||
hex_viewer_custom_menu_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) {
|
||||
HexViewerCustomEventMenu event = {.packed_value = packed_value};
|
||||
if(type) *type = event.content.type;
|
||||
if(value) *value = event.content.value;
|
||||
}
|
||||
|
||||
static inline uint16_t hex_viewer_custom_menu_event_get_type(uint32_t packed_value) {
|
||||
uint16_t type;
|
||||
hex_viewer_custom_menu_event_unpack(packed_value, &type, NULL);
|
||||
return type;
|
||||
}
|
||||
|
||||
static inline int16_t hex_viewer_custom_menu_event_get_value(uint32_t packed_value) {
|
||||
int16_t value;
|
||||
hex_viewer_custom_menu_event_unpack(packed_value, NULL, &value);
|
||||
return value;
|
||||
}
|
||||
35
applications/system/hex_viewer/helpers/hex_viewer_haptic.c
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "hex_viewer_haptic.h"
|
||||
#include "../hex_viewer.h"
|
||||
|
||||
void hex_viewer_play_happy_bump(void* context) {
|
||||
HexViewer* app = context;
|
||||
if(app->haptic != 1) {
|
||||
return;
|
||||
}
|
||||
notification_message(app->notification, &sequence_set_vibro_on);
|
||||
furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
|
||||
notification_message(app->notification, &sequence_reset_vibro);
|
||||
}
|
||||
|
||||
void hex_viewer_play_bad_bump(void* context) {
|
||||
HexViewer* app = context;
|
||||
if(app->haptic != 1) {
|
||||
return;
|
||||
}
|
||||
notification_message(app->notification, &sequence_set_vibro_on);
|
||||
furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
|
||||
notification_message(app->notification, &sequence_reset_vibro);
|
||||
}
|
||||
|
||||
void hex_viewer_play_long_bump(void* context) {
|
||||
HexViewer* app = context;
|
||||
if(app->haptic != 1) {
|
||||
return;
|
||||
}
|
||||
for(int i = 0; i < 4; i++) {
|
||||
notification_message(app->notification, &sequence_set_vibro_on);
|
||||
furi_thread_flags_wait(0, FuriFlagWaitAny, 50);
|
||||
notification_message(app->notification, &sequence_reset_vibro);
|
||||
furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
void hex_viewer_play_happy_bump(void* context);
|
||||
|
||||
void hex_viewer_play_bad_bump(void* context);
|
||||
|
||||
void hex_viewer_play_long_bump(void* context);
|
||||
39
applications/system/hex_viewer/helpers/hex_viewer_led.c
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "hex_viewer_led.h"
|
||||
#include "../hex_viewer.h"
|
||||
|
||||
void hex_viewer_led_set_rgb(void* context, int red, int green, int blue) {
|
||||
HexViewer* app = context;
|
||||
if(app->led != 1) {
|
||||
return;
|
||||
}
|
||||
NotificationMessage notification_led_message_1;
|
||||
notification_led_message_1.type = NotificationMessageTypeLedRed;
|
||||
NotificationMessage notification_led_message_2;
|
||||
notification_led_message_2.type = NotificationMessageTypeLedGreen;
|
||||
NotificationMessage notification_led_message_3;
|
||||
notification_led_message_3.type = NotificationMessageTypeLedBlue;
|
||||
|
||||
notification_led_message_1.data.led.value = red;
|
||||
notification_led_message_2.data.led.value = green;
|
||||
notification_led_message_3.data.led.value = blue;
|
||||
const NotificationSequence notification_sequence = {
|
||||
¬ification_led_message_1,
|
||||
¬ification_led_message_2,
|
||||
¬ification_led_message_3,
|
||||
&message_do_not_reset,
|
||||
NULL,
|
||||
};
|
||||
notification_message(app->notification, ¬ification_sequence);
|
||||
furi_thread_flags_wait(
|
||||
0, FuriFlagWaitAny, 10); //Delay, prevent removal from RAM before LED value set
|
||||
}
|
||||
|
||||
void hex_viewer_led_reset(void* context) {
|
||||
HexViewer* app = context;
|
||||
notification_message(app->notification, &sequence_reset_red);
|
||||
notification_message(app->notification, &sequence_reset_green);
|
||||
notification_message(app->notification, &sequence_reset_blue);
|
||||
|
||||
furi_thread_flags_wait(
|
||||
0, FuriFlagWaitAny, 300); //Delay, prevent removal from RAM before LED value set
|
||||
}
|
||||
5
applications/system/hex_viewer/helpers/hex_viewer_led.h
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
void hex_viewer_led_set_rgb(void* context, int red, int green, int blue);
|
||||
|
||||
void hex_viewer_led_reset(void* context);
|
||||
26
applications/system/hex_viewer/helpers/hex_viewer_speaker.c
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "hex_viewer_speaker.h"
|
||||
#include "../hex_viewer.h"
|
||||
|
||||
#define NOTE_INPUT 587.33f
|
||||
|
||||
void hex_viewer_play_input_sound(void* context) {
|
||||
HexViewer* app = context;
|
||||
if(app->speaker != 1) {
|
||||
return;
|
||||
}
|
||||
float volume = 1.0f;
|
||||
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
|
||||
furi_hal_speaker_start(NOTE_INPUT, volume);
|
||||
}
|
||||
}
|
||||
|
||||
void hex_viewer_stop_all_sound(void* context) {
|
||||
HexViewer* app = context;
|
||||
if(app->speaker != 1) {
|
||||
return;
|
||||
}
|
||||
if(furi_hal_speaker_is_mine()) {
|
||||
furi_hal_speaker_stop();
|
||||
furi_hal_speaker_release();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#define NOTE_INPUT 587.33f
|
||||
|
||||
void hex_viewer_play_input_sound(void* context);
|
||||
void hex_viewer_stop_all_sound(void* context);
|
||||
171
applications/system/hex_viewer/helpers/hex_viewer_storage.c
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "hex_viewer_storage.h"
|
||||
|
||||
static Storage* hex_viewer_open_storage() {
|
||||
return furi_record_open(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void hex_viewer_close_storage() {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void hex_viewer_close_config_file(FlipperFormat* file) {
|
||||
if(file == NULL) return;
|
||||
flipper_format_file_close(file);
|
||||
flipper_format_free(file);
|
||||
}
|
||||
|
||||
void hex_viewer_save_settings(void* context) {
|
||||
HexViewer* app = context;
|
||||
if(app->save_settings == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Saving Settings");
|
||||
Storage* storage = hex_viewer_open_storage();
|
||||
FlipperFormat* fff_file = flipper_format_file_alloc(storage);
|
||||
|
||||
// Overwrite wont work, so delete first
|
||||
if(storage_file_exists(storage, HEX_VIEWER_SETTINGS_SAVE_PATH)) {
|
||||
storage_simply_remove(storage, HEX_VIEWER_SETTINGS_SAVE_PATH);
|
||||
}
|
||||
|
||||
// Open File, create if not exists
|
||||
if(!storage_common_stat(storage, HEX_VIEWER_SETTINGS_SAVE_PATH, NULL) == FSE_OK) {
|
||||
FURI_LOG_D(
|
||||
TAG, "Config file %s is not found. Will create new.", HEX_VIEWER_SETTINGS_SAVE_PATH);
|
||||
if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) {
|
||||
FURI_LOG_D(
|
||||
TAG, "Directory %s doesn't exist. Will create new.", CONFIG_FILE_DIRECTORY_PATH);
|
||||
if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
|
||||
FURI_LOG_E(TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!flipper_format_file_open_new(fff_file, HEX_VIEWER_SETTINGS_SAVE_PATH)) {
|
||||
//totp_close_config_file(fff_file);
|
||||
FURI_LOG_E(TAG, "Error creating new file %s", HEX_VIEWER_SETTINGS_SAVE_PATH);
|
||||
hex_viewer_close_storage();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store Settings
|
||||
flipper_format_write_header_cstr(
|
||||
fff_file, HEX_VIEWER_SETTINGS_HEADER, HEX_VIEWER_SETTINGS_FILE_VERSION);
|
||||
flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
|
||||
flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
|
||||
flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_LED, &app->led, 1);
|
||||
flipper_format_write_uint32(
|
||||
fff_file, HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
|
||||
|
||||
if(!flipper_format_rewind(fff_file)) {
|
||||
hex_viewer_close_config_file(fff_file);
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
hex_viewer_close_storage();
|
||||
return;
|
||||
}
|
||||
|
||||
hex_viewer_close_config_file(fff_file);
|
||||
hex_viewer_close_storage();
|
||||
}
|
||||
|
||||
void hex_viewer_read_settings(void* context) {
|
||||
HexViewer* app = context;
|
||||
Storage* storage = hex_viewer_open_storage();
|
||||
FlipperFormat* fff_file = flipper_format_file_alloc(storage);
|
||||
|
||||
if(storage_common_stat(storage, HEX_VIEWER_SETTINGS_SAVE_PATH, NULL) != FSE_OK) {
|
||||
hex_viewer_close_config_file(fff_file);
|
||||
hex_viewer_close_storage();
|
||||
return;
|
||||
}
|
||||
uint32_t file_version;
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
|
||||
if(!flipper_format_file_open_existing(fff_file, HEX_VIEWER_SETTINGS_SAVE_PATH)) {
|
||||
FURI_LOG_E(TAG, "Cannot open file %s", HEX_VIEWER_SETTINGS_SAVE_PATH);
|
||||
hex_viewer_close_config_file(fff_file);
|
||||
hex_viewer_close_storage();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_header(fff_file, temp_str, &file_version)) {
|
||||
FURI_LOG_E(TAG, "Missing Header Data");
|
||||
hex_viewer_close_config_file(fff_file);
|
||||
hex_viewer_close_storage();
|
||||
return;
|
||||
}
|
||||
|
||||
if(file_version < HEX_VIEWER_SETTINGS_FILE_VERSION) {
|
||||
FURI_LOG_I(TAG, "old config version, will be removed.");
|
||||
hex_viewer_close_config_file(fff_file);
|
||||
hex_viewer_close_storage();
|
||||
return;
|
||||
}
|
||||
|
||||
flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
|
||||
flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
|
||||
flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_LED, &app->led, 1);
|
||||
flipper_format_read_uint32(
|
||||
fff_file, HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
|
||||
|
||||
flipper_format_rewind(fff_file);
|
||||
|
||||
hex_viewer_close_config_file(fff_file);
|
||||
hex_viewer_close_storage();
|
||||
}
|
||||
|
||||
bool hex_viewer_open_file(void* context, const char* file_path) {
|
||||
HexViewer* hex_viewer = context;
|
||||
furi_assert(hex_viewer);
|
||||
furi_assert(file_path);
|
||||
|
||||
// TODO Separate function?
|
||||
if(hex_viewer->model->stream) {
|
||||
buffered_file_stream_close(hex_viewer->model->stream);
|
||||
stream_free(hex_viewer->model->stream); // TODO Check
|
||||
hex_viewer->model->file_offset = 0;
|
||||
}
|
||||
|
||||
hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage);
|
||||
bool isOk = true;
|
||||
|
||||
do {
|
||||
if(!buffered_file_stream_open(
|
||||
hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_E(TAG, "Unable to open stream: %s", file_path);
|
||||
isOk = false;
|
||||
break;
|
||||
};
|
||||
|
||||
hex_viewer->model->file_size = stream_size(hex_viewer->model->stream);
|
||||
} while(false);
|
||||
|
||||
return isOk;
|
||||
}
|
||||
|
||||
bool hex_viewer_read_file(void* context) {
|
||||
HexViewer* hex_viewer = context;
|
||||
furi_assert(hex_viewer);
|
||||
furi_assert(hex_viewer->model->stream);
|
||||
furi_assert(hex_viewer->model->file_offset % HEX_VIEWER_BYTES_PER_LINE == 0);
|
||||
|
||||
memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE);
|
||||
bool isOk = true;
|
||||
|
||||
do {
|
||||
uint32_t offset = hex_viewer->model->file_offset;
|
||||
if(!stream_seek(hex_viewer->model->stream, offset, true)) {
|
||||
FURI_LOG_E(TAG, "Unable to seek stream");
|
||||
isOk = false;
|
||||
break;
|
||||
}
|
||||
|
||||
hex_viewer->model->file_read_bytes = stream_read(
|
||||
hex_viewer->model->stream,
|
||||
(uint8_t*)hex_viewer->model->file_bytes,
|
||||
HEX_VIEWER_BUF_SIZE);
|
||||
} while(false);
|
||||
|
||||
return isOk;
|
||||
}
|
||||
23
applications/system/hex_viewer/helpers/hex_viewer_storage.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <storage/storage.h>
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
#include "../hex_viewer.h"
|
||||
|
||||
#define HEX_VIEWER_SETTINGS_FILE_VERSION 1
|
||||
#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/hex_viewer")
|
||||
#define HEX_VIEWER_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/hex_viewer.conf"
|
||||
#define HEX_VIEWER_SETTINGS_SAVE_PATH_TMP HEX_VIEWER_SETTINGS_SAVE_PATH ".tmp"
|
||||
#define HEX_VIEWER_SETTINGS_HEADER "HexViewer Config File"
|
||||
#define HEX_VIEWER_SETTINGS_KEY_HAPTIC "Haptic"
|
||||
#define HEX_VIEWER_SETTINGS_KEY_LED "Led"
|
||||
#define HEX_VIEWER_SETTINGS_KEY_SPEAKER "Speaker"
|
||||
#define HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
|
||||
|
||||
void hex_viewer_save_settings(void* context);
|
||||
void hex_viewer_read_settings(void* context);
|
||||
|
||||
bool hex_viewer_open_file(void* context, const char* file_path);
|
||||
bool hex_viewer_read_file(void* context);
|
||||
@@ -1,295 +1,145 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "hex_viewer.h"
|
||||
|
||||
#include "hex_viewer_icons.h"
|
||||
#include <assets_icons.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/elements.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <stream/stream.h>
|
||||
#include <stream/buffered_file_stream.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
|
||||
#define TAG "HexViewer"
|
||||
|
||||
#define HEX_VIEWER_APP_PATH_FOLDER STORAGE_EXT_PATH_PREFIX
|
||||
#define HEX_VIEWER_APP_EXTENSION "*"
|
||||
|
||||
#define HEX_VIEWER_BYTES_PER_LINE 4u
|
||||
#define HEX_VIEWER_LINES_ON_SCREEN 4u
|
||||
#define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_LINES_ON_SCREEN * HEX_VIEWER_BYTES_PER_LINE)
|
||||
|
||||
typedef struct {
|
||||
uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE];
|
||||
uint32_t file_offset;
|
||||
uint32_t file_read_bytes;
|
||||
uint32_t file_size;
|
||||
Stream* stream;
|
||||
bool mode; // Print address or content
|
||||
} HexViewerModel;
|
||||
|
||||
typedef struct {
|
||||
HexViewerModel* model;
|
||||
FuriMutex** mutex;
|
||||
|
||||
FuriMessageQueue* input_queue;
|
||||
|
||||
ViewPort* view_port;
|
||||
Gui* gui;
|
||||
Storage* storage;
|
||||
} HexViewer;
|
||||
|
||||
static void render_callback(Canvas* canvas, void* ctx) {
|
||||
HexViewer* hex_viewer = ctx;
|
||||
furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
elements_button_left(canvas, hex_viewer->model->mode ? "Addr" : "Text");
|
||||
elements_button_right(canvas, "Info");
|
||||
|
||||
int ROW_HEIGHT = 12;
|
||||
int TOP_OFFSET = 10;
|
||||
int LEFT_OFFSET = 3;
|
||||
|
||||
uint32_t line_count = hex_viewer->model->file_size / HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(hex_viewer->model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1;
|
||||
uint32_t first_line_on_screen = hex_viewer->model->file_offset / HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(line_count > HEX_VIEWER_LINES_ON_SCREEN) {
|
||||
uint8_t width = canvas_width(canvas);
|
||||
elements_scrollbar_pos(
|
||||
canvas,
|
||||
width,
|
||||
0,
|
||||
ROW_HEIGHT * HEX_VIEWER_LINES_ON_SCREEN,
|
||||
first_line_on_screen, // TODO
|
||||
line_count - (HEX_VIEWER_LINES_ON_SCREEN - 1));
|
||||
}
|
||||
|
||||
char temp_buf[32];
|
||||
uint32_t row_iters = hex_viewer->model->file_read_bytes / HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(hex_viewer->model->file_read_bytes % HEX_VIEWER_BYTES_PER_LINE != 0) row_iters += 1;
|
||||
|
||||
for(uint32_t i = 0; i < row_iters; ++i) {
|
||||
uint32_t bytes_left_per_row =
|
||||
hex_viewer->model->file_read_bytes - i * HEX_VIEWER_BYTES_PER_LINE;
|
||||
bytes_left_per_row = MIN(bytes_left_per_row, HEX_VIEWER_BYTES_PER_LINE);
|
||||
|
||||
if(hex_viewer->model->mode) {
|
||||
memcpy(temp_buf, hex_viewer->model->file_bytes[i], bytes_left_per_row);
|
||||
temp_buf[bytes_left_per_row] = '\0';
|
||||
for(uint32_t j = 0; j < bytes_left_per_row; ++j)
|
||||
if(!isprint((int)temp_buf[j])) temp_buf[j] = '.';
|
||||
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
|
||||
} else {
|
||||
uint32_t addr = hex_viewer->model->file_offset + i * HEX_VIEWER_BYTES_PER_LINE;
|
||||
snprintf(temp_buf, 32, "%04lX", addr);
|
||||
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
|
||||
}
|
||||
|
||||
char* p = temp_buf;
|
||||
for(uint32_t j = 0; j < bytes_left_per_row; ++j)
|
||||
p += snprintf(p, 32, "%02X ", hex_viewer->model->file_bytes[i][j]);
|
||||
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
|
||||
}
|
||||
|
||||
furi_mutex_release(hex_viewer->mutex);
|
||||
bool hex_viewer_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
HexViewer* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, void* ctx) {
|
||||
HexViewer* hex_viewer = ctx;
|
||||
if(input_event->type == InputTypeShort || input_event->type == InputTypeRepeat) {
|
||||
furi_message_queue_put(hex_viewer->input_queue, input_event, 0);
|
||||
}
|
||||
void hex_viewer_tick_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HexViewer* app = context;
|
||||
scene_manager_handle_tick_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static HexViewer* hex_viewer_alloc() {
|
||||
HexViewer* instance = malloc(sizeof(HexViewer));
|
||||
|
||||
instance->model = malloc(sizeof(HexViewerModel));
|
||||
memset(instance->model, 0x0, sizeof(HexViewerModel));
|
||||
|
||||
instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
instance->view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(instance->view_port, render_callback, instance);
|
||||
view_port_input_callback_set(instance->view_port, input_callback, instance);
|
||||
|
||||
instance->gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
|
||||
|
||||
instance->storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
return instance;
|
||||
//leave app if back button pressed
|
||||
bool hex_viewer_navigation_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HexViewer* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static void hex_viewer_free(HexViewer* instance) {
|
||||
HexViewer* hex_viewer_app_alloc() {
|
||||
HexViewer* app = malloc(sizeof(HexViewer));
|
||||
|
||||
app->model = malloc(sizeof(HexViewerModel));
|
||||
memset(app->model, 0, sizeof(HexViewerModel));
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->storage = furi_record_open(RECORD_STORAGE);
|
||||
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
//Turn backlight on, believe me this makes testing your app easier
|
||||
notification_message(app->notification, &sequence_display_backlight_on);
|
||||
|
||||
//Scene additions
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
|
||||
app->scene_manager = scene_manager_alloc(&hex_viewer_scene_handlers, app);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, hex_viewer_navigation_event_callback);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
app->view_dispatcher, hex_viewer_tick_event_callback, 100);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, hex_viewer_custom_event_callback);
|
||||
|
||||
app->submenu = submenu_alloc();
|
||||
app->text_input = text_input_alloc();
|
||||
|
||||
// Set defaults, in case no config loaded
|
||||
app->haptic = 1;
|
||||
app->speaker = 1;
|
||||
app->led = 1;
|
||||
app->save_settings = 1;
|
||||
|
||||
// Used for File Browser
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
app->file_path = furi_string_alloc();
|
||||
|
||||
// Load configs
|
||||
hex_viewer_read_settings(app);
|
||||
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HexViewerViewIdMenu, submenu_get_view(app->submenu));
|
||||
|
||||
app->hex_viewer_startscreen = hex_viewer_startscreen_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
HexViewerViewIdStartscreen,
|
||||
hex_viewer_startscreen_get_view(app->hex_viewer_startscreen));
|
||||
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HexViewerViewIdScroll, text_input_get_view(app->text_input));
|
||||
|
||||
app->variable_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
HexViewerViewIdSettings,
|
||||
variable_item_list_get_view(app->variable_item_list));
|
||||
|
||||
//End Scene Additions
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void hex_viewer_app_free(HexViewer* app) {
|
||||
furi_assert(app);
|
||||
|
||||
if(app->model->stream) buffered_file_stream_close(app->model->stream);
|
||||
|
||||
// Scene manager
|
||||
scene_manager_free(app->scene_manager);
|
||||
|
||||
// View Dispatcher
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdMenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdScroll);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdSettings);
|
||||
|
||||
submenu_free(app->submenu);
|
||||
text_input_free(app->text_input);
|
||||
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
gui_remove_view_port(instance->gui, instance->view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(instance->view_port);
|
||||
|
||||
furi_message_queue_free(instance->input_queue);
|
||||
app->storage = NULL;
|
||||
app->gui = NULL;
|
||||
app->notification = NULL;
|
||||
|
||||
furi_mutex_free(instance->mutex);
|
||||
// Close File Browser
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_string_free(app->file_path);
|
||||
|
||||
if(instance->model->stream) buffered_file_stream_close(instance->model->stream);
|
||||
free(app->model);
|
||||
|
||||
free(instance->model);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path) {
|
||||
furi_assert(hex_viewer);
|
||||
furi_assert(file_path);
|
||||
|
||||
hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage);
|
||||
bool isOk = true;
|
||||
|
||||
do {
|
||||
if(!buffered_file_stream_open(
|
||||
hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_E(TAG, "Unable to open stream: %s", file_path);
|
||||
isOk = false;
|
||||
break;
|
||||
};
|
||||
|
||||
hex_viewer->model->file_size = stream_size(hex_viewer->model->stream);
|
||||
} while(false);
|
||||
|
||||
return isOk;
|
||||
}
|
||||
|
||||
static bool hex_viewer_read_file(HexViewer* hex_viewer) {
|
||||
furi_assert(hex_viewer);
|
||||
furi_assert(hex_viewer->model->stream);
|
||||
furi_assert(hex_viewer->model->file_offset % HEX_VIEWER_BYTES_PER_LINE == 0);
|
||||
|
||||
memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE);
|
||||
bool isOk = true;
|
||||
|
||||
do {
|
||||
uint32_t offset = hex_viewer->model->file_offset;
|
||||
if(!stream_seek(hex_viewer->model->stream, offset, true)) {
|
||||
FURI_LOG_E(TAG, "Unable to seek stream");
|
||||
isOk = false;
|
||||
break;
|
||||
}
|
||||
|
||||
hex_viewer->model->file_read_bytes = stream_read(
|
||||
hex_viewer->model->stream,
|
||||
(uint8_t*)hex_viewer->model->file_bytes,
|
||||
HEX_VIEWER_BUF_SIZE);
|
||||
} while(false);
|
||||
|
||||
return isOk;
|
||||
//Remove whatever is left
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t hex_viewer_app(void* p) {
|
||||
HexViewer* hex_viewer = hex_viewer_alloc();
|
||||
UNUSED(p);
|
||||
HexViewer* app = hex_viewer_app_alloc();
|
||||
|
||||
FuriString* file_path;
|
||||
file_path = furi_string_alloc();
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
do {
|
||||
if(p && strlen(p)) {
|
||||
furi_string_set(file_path, (const char*)p);
|
||||
} else {
|
||||
furi_string_set(file_path, HEX_VIEWER_APP_PATH_FOLDER);
|
||||
if(p && strlen(p) && hex_viewer_open_file(app, (const char*)p)) {
|
||||
hex_viewer_read_file(app);
|
||||
scene_manager_next_scene(app->scene_manager, HexViewerSceneStartscreen);
|
||||
} else {
|
||||
scene_manager_next_scene(app->scene_manager, HexViewerSceneStartscreen);
|
||||
scene_manager_next_scene(app->scene_manager, HexViewerSceneOpen);
|
||||
}
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px);
|
||||
browser_options.hide_ext = false;
|
||||
furi_hal_power_suppress_charge_enter();
|
||||
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
if(!res) {
|
||||
FURI_LOG_I(TAG, "No file selected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
hex_viewer_save_settings(app);
|
||||
|
||||
FURI_LOG_I(TAG, "File selected: %s", furi_string_get_cstr(file_path));
|
||||
|
||||
if(!hex_viewer_open_file(hex_viewer, furi_string_get_cstr(file_path))) break;
|
||||
hex_viewer_read_file(hex_viewer);
|
||||
|
||||
InputEvent input;
|
||||
while(1) {
|
||||
if(furi_message_queue_get(hex_viewer->input_queue, &input, 100) == FuriStatusOk) {
|
||||
if(input.key == InputKeyBack) {
|
||||
break;
|
||||
} else if(input.key == InputKeyUp) {
|
||||
furi_check(
|
||||
furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
if(hex_viewer->model->file_offset > 0) {
|
||||
hex_viewer->model->file_offset -= HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(!hex_viewer_read_file(hex_viewer)) break;
|
||||
}
|
||||
furi_mutex_release(hex_viewer->mutex);
|
||||
} else if(input.key == InputKeyDown) {
|
||||
furi_check(
|
||||
furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
uint32_t last_byte_on_screen =
|
||||
hex_viewer->model->file_offset + hex_viewer->model->file_read_bytes;
|
||||
|
||||
if(hex_viewer->model->file_size > last_byte_on_screen) {
|
||||
hex_viewer->model->file_offset += HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(!hex_viewer_read_file(hex_viewer)) break;
|
||||
}
|
||||
furi_mutex_release(hex_viewer->mutex);
|
||||
} else if(input.key == InputKeyLeft) {
|
||||
furi_check(
|
||||
furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
hex_viewer->model->mode = !hex_viewer->model->mode;
|
||||
furi_mutex_release(hex_viewer->mutex);
|
||||
} else if(input.key == InputKeyRight) {
|
||||
FuriString* buffer;
|
||||
buffer = furi_string_alloc();
|
||||
furi_string_printf(
|
||||
buffer,
|
||||
"File path: %s\nFile size: %lu (0x%lX)",
|
||||
furi_string_get_cstr(file_path),
|
||||
hex_viewer->model->file_size,
|
||||
hex_viewer->model->file_size);
|
||||
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_header(
|
||||
message, "Hex Viewer v1.1", 16, 2, AlignLeft, AlignTop);
|
||||
dialog_message_set_icon(message, &I_hex_10px, 3, 2);
|
||||
dialog_message_set_text(
|
||||
message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop);
|
||||
dialog_message_set_buttons(message, NULL, NULL, "Back");
|
||||
dialog_message_show(dialogs, message);
|
||||
|
||||
furi_string_free(buffer);
|
||||
dialog_message_free(message);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
}
|
||||
}
|
||||
|
||||
view_port_update(hex_viewer->view_port);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
furi_string_free(file_path);
|
||||
hex_viewer_free(hex_viewer);
|
||||
furi_hal_power_suppress_charge_exit();
|
||||
hex_viewer_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
93
applications/system/hex_viewer/hex_viewer.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <hex_viewer_icons.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/button_menu.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include "scenes/hex_viewer_scene.h"
|
||||
#include "views/hex_viewer_startscreen.h"
|
||||
#include "helpers/hex_viewer_storage.h"
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <stream/stream.h>
|
||||
#include <stream/buffered_file_stream.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
|
||||
#define TAG "HexViewer"
|
||||
|
||||
#define HEX_VIEWER_APP_PATH_FOLDER "/any" // TODO ANY_PATH
|
||||
#define HEX_VIEWER_APP_EXTENSION "*"
|
||||
#define HEX_VIEWER_PERCENT_INPUT 16
|
||||
|
||||
#define HEX_VIEWER_BYTES_PER_LINE 4u
|
||||
#define HEX_VIEWER_LINES_ON_SCREEN 4u
|
||||
#define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_LINES_ON_SCREEN * HEX_VIEWER_BYTES_PER_LINE)
|
||||
|
||||
typedef struct {
|
||||
uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE];
|
||||
uint32_t file_offset;
|
||||
uint32_t file_read_bytes;
|
||||
uint32_t file_size;
|
||||
|
||||
Stream* stream;
|
||||
} HexViewerModel;
|
||||
|
||||
// TODO Clean
|
||||
typedef struct {
|
||||
HexViewerModel* model;
|
||||
|
||||
Gui* gui;
|
||||
Storage* storage;
|
||||
NotificationApp* notification;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* submenu;
|
||||
TextInput* text_input;
|
||||
SceneManager* scene_manager;
|
||||
VariableItemList* variable_item_list;
|
||||
HexViewerStartscreen* hex_viewer_startscreen;
|
||||
DialogsApp* dialogs; // File Browser
|
||||
FuriString* file_path; // File Browser
|
||||
uint32_t haptic;
|
||||
uint32_t speaker;
|
||||
uint32_t led;
|
||||
uint32_t save_settings;
|
||||
char percent_buf[HEX_VIEWER_PERCENT_INPUT];
|
||||
} HexViewer;
|
||||
|
||||
typedef enum {
|
||||
HexViewerViewIdStartscreen,
|
||||
HexViewerViewIdMenu,
|
||||
HexViewerViewIdScroll,
|
||||
HexViewerViewIdSettings,
|
||||
} HexViewerViewId;
|
||||
|
||||
typedef enum {
|
||||
HexViewerHapticOff,
|
||||
HexViewerHapticOn,
|
||||
} HexViewerHapticState;
|
||||
|
||||
typedef enum {
|
||||
HexViewerSpeakerOff,
|
||||
HexViewerSpeakerOn,
|
||||
} HexViewerSpeakerState;
|
||||
|
||||
typedef enum {
|
||||
HexViewerLedOff,
|
||||
HexViewerLedOn,
|
||||
} HexViewerLedState;
|
||||
|
||||
typedef enum {
|
||||
HexViewerSettingsOff,
|
||||
HexViewerSettingsOn,
|
||||
} HexViewerSettingsStoreState;
|
||||
30
applications/system/hex_viewer/scenes/hex_viewer_scene.c
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "hex_viewer_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const hex_viewer_on_enter_handlers[])(void*) = {
|
||||
#include "hex_viewer_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const hex_viewer_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "hex_viewer_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const hex_viewer_on_exit_handlers[])(void* context) = {
|
||||
#include "hex_viewer_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers hex_viewer_scene_handlers = {
|
||||
.on_enter_handlers = hex_viewer_on_enter_handlers,
|
||||
.on_event_handlers = hex_viewer_on_event_handlers,
|
||||
.on_exit_handlers = hex_viewer_on_exit_handlers,
|
||||
.scene_num = HexViewerSceneNum,
|
||||
};
|
||||
29
applications/system/hex_viewer/scenes/hex_viewer_scene.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) HexViewerScene##id,
|
||||
typedef enum {
|
||||
#include "hex_viewer_scene_config.h"
|
||||
HexViewerSceneNum,
|
||||
} HexViewerScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers hex_viewer_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "hex_viewer_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "hex_viewer_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "hex_viewer_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -0,0 +1,6 @@
|
||||
ADD_SCENE(hex_viewer, startscreen, Startscreen)
|
||||
ADD_SCENE(hex_viewer, menu, Menu)
|
||||
ADD_SCENE(hex_viewer, scroll, Scroll)
|
||||
ADD_SCENE(hex_viewer, info, Info)
|
||||
ADD_SCENE(hex_viewer, open, Open)
|
||||
ADD_SCENE(hex_viewer, settings, Settings)
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "../hex_viewer.h"
|
||||
|
||||
void hex_viewer_scene_info_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
HexViewer* app = context;
|
||||
|
||||
FuriString* buffer;
|
||||
buffer = furi_string_alloc();
|
||||
furi_string_printf(
|
||||
buffer,
|
||||
"File path: %s\nFile size: %lu (0x%lX)",
|
||||
furi_string_get_cstr(app->file_path),
|
||||
app->model->file_size,
|
||||
app->model->file_size);
|
||||
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_header(message, "Hex Viewer v2.0", 16, 2, AlignLeft, AlignTop);
|
||||
dialog_message_set_icon(message, &I_hex_10px, 3, 2);
|
||||
dialog_message_set_text(message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop);
|
||||
dialog_message_set_buttons(message, NULL, NULL, "Back");
|
||||
dialog_message_show(app->dialogs, message);
|
||||
|
||||
furi_string_free(buffer);
|
||||
dialog_message_free(message);
|
||||
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, HexViewerViewIdStartscreen);
|
||||
}
|
||||
|
||||
bool hex_viewer_scene_info_on_event(void* context, SceneManagerEvent event) {
|
||||
HexViewer* app = context;
|
||||
UNUSED(app);
|
||||
UNUSED(event);
|
||||
bool consumed = true;
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void hex_viewer_scene_info_on_exit(void* context) {
|
||||
HexViewer* app = context;
|
||||
UNUSED(app);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#include "../hex_viewer.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexScroll = 10,
|
||||
SubmenuIndexInfo,
|
||||
SubmenuIndexOpen,
|
||||
// SubmenuIndexSettings,
|
||||
};
|
||||
|
||||
void hex_viewer_scene_menu_submenu_callback(void* context, uint32_t index) {
|
||||
HexViewer* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void hex_viewer_scene_menu_on_enter(void* context) {
|
||||
HexViewer* app = context;
|
||||
|
||||
submenu_set_header(app->submenu, "Select action");
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Open file ...",
|
||||
SubmenuIndexOpen,
|
||||
hex_viewer_scene_menu_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Scroll to ...",
|
||||
SubmenuIndexScroll,
|
||||
hex_viewer_scene_menu_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Show info ...",
|
||||
SubmenuIndexInfo,
|
||||
hex_viewer_scene_menu_submenu_callback,
|
||||
app);
|
||||
// submenu_add_item(app->submenu, "Settings", SubmenuIndexSettings, hex_viewer_scene_menu_submenu_callback, app);
|
||||
|
||||
submenu_set_selected_item(
|
||||
app->submenu, scene_manager_get_scene_state(app->scene_manager, HexViewerSceneMenu));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdMenu);
|
||||
}
|
||||
|
||||
bool hex_viewer_scene_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
HexViewer* app = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeBack) {
|
||||
//exit app
|
||||
// scene_manager_stop(app->scene_manager);
|
||||
// view_dispatcher_stop(app->view_dispatcher);
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
return true;
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexScroll) {
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, HexViewerSceneMenu, SubmenuIndexScroll);
|
||||
scene_manager_next_scene(app->scene_manager, HexViewerSceneScroll);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexInfo) {
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, HexViewerSceneMenu, SubmenuIndexInfo);
|
||||
scene_manager_next_scene(app->scene_manager, HexViewerSceneInfo);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexOpen) {
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, HexViewerSceneMenu, SubmenuIndexOpen);
|
||||
scene_manager_next_scene(app->scene_manager, HexViewerSceneOpen);
|
||||
// } else if (event.event == SubmenuIndexSettings) {
|
||||
// scene_manager_set_scene_state(
|
||||
// app->scene_manager, HexViewerSceneMenu, SubmenuIndexSettings);
|
||||
// scene_manager_next_scene(app->scene_manager, HexViewerSceneSettings);
|
||||
// return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void hex_viewer_scene_menu_on_exit(void* context) {
|
||||
HexViewer* app = context;
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "../hex_viewer.h"
|
||||
|
||||
void hex_viewer_scene_open_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
HexViewer* app = context;
|
||||
|
||||
FuriString* initial_path;
|
||||
initial_path = furi_string_alloc();
|
||||
furi_string_set(initial_path, HEX_VIEWER_APP_PATH_FOLDER);
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(&browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px);
|
||||
browser_options.hide_ext = false;
|
||||
|
||||
bool success =
|
||||
dialog_file_browser_show(app->dialogs, app->file_path, initial_path, &browser_options);
|
||||
furi_string_free(initial_path);
|
||||
|
||||
if(success) {
|
||||
success = hex_viewer_open_file(app, furi_string_get_cstr(app->file_path));
|
||||
if(success) hex_viewer_read_file(app);
|
||||
}
|
||||
|
||||
if(success) {
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, HexViewerViewIdStartscreen);
|
||||
} else {
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
}
|
||||
}
|
||||
|
||||
bool hex_viewer_scene_open_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
bool consumed = true;
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void hex_viewer_scene_open_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "../hex_viewer.h"
|
||||
#include "../helpers/hex_viewer_custom_event.h"
|
||||
|
||||
void hex_viewer_scene_scroll_callback(void* context) {
|
||||
HexViewer* app = (HexViewer*)context;
|
||||
view_dispatcher_send_custom_event(
|
||||
app->view_dispatcher, HexViewerCustomEventMenuPercentEntered);
|
||||
}
|
||||
|
||||
void hex_viewer_scene_scroll_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
HexViewer* app = context;
|
||||
|
||||
TextInput* text_input = app->text_input;
|
||||
|
||||
text_input_set_header_text(text_input, "Scroll to percent (0..100)");
|
||||
text_input_set_result_callback(
|
||||
text_input,
|
||||
hex_viewer_scene_scroll_callback,
|
||||
app,
|
||||
app->percent_buf,
|
||||
HEX_VIEWER_PERCENT_INPUT,
|
||||
false);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerSceneScroll);
|
||||
}
|
||||
|
||||
bool hex_viewer_scene_scroll_on_event(void* context, SceneManagerEvent event) {
|
||||
HexViewer* app = (HexViewer*)context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == HexViewerCustomEventMenuPercentEntered) {
|
||||
int ipercent = atoi(app->percent_buf);
|
||||
ipercent = MIN(ipercent, 100);
|
||||
ipercent = MAX(ipercent, 0);
|
||||
float percent = ipercent / 100.0;
|
||||
|
||||
uint32_t line_count = app->model->file_size / HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(app->model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1;
|
||||
uint32_t scrollable_lines = line_count - HEX_VIEWER_LINES_ON_SCREEN;
|
||||
uint32_t target_line = (uint32_t)(percent * scrollable_lines);
|
||||
|
||||
uint32_t new_file_offset = target_line * HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(app->model->file_size > new_file_offset) {
|
||||
app->model->file_offset = new_file_offset;
|
||||
if(!hex_viewer_read_file(app)) new_file_offset = new_file_offset; // TODO Do smth
|
||||
}
|
||||
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, HexViewerViewIdStartscreen);
|
||||
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void hex_viewer_scene_scroll_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#include "../hex_viewer.h"
|
||||
#include <lib/toolbox/value_index.h>
|
||||
|
||||
enum SettingsIndex {
|
||||
SettingsIndexHaptic = 10,
|
||||
SettingsIndexValue1,
|
||||
SettingsIndexValue2,
|
||||
};
|
||||
|
||||
const char* const haptic_text[2] = {
|
||||
"OFF",
|
||||
"ON",
|
||||
};
|
||||
const uint32_t haptic_value[2] = {
|
||||
HexViewerHapticOff,
|
||||
HexViewerHapticOn,
|
||||
};
|
||||
|
||||
const char* const speaker_text[2] = {
|
||||
"OFF",
|
||||
"ON",
|
||||
};
|
||||
const uint32_t speaker_value[2] = {
|
||||
HexViewerSpeakerOff,
|
||||
HexViewerSpeakerOn,
|
||||
};
|
||||
|
||||
const char* const led_text[2] = {
|
||||
"OFF",
|
||||
"ON",
|
||||
};
|
||||
const uint32_t led_value[2] = {
|
||||
HexViewerLedOff,
|
||||
HexViewerLedOn,
|
||||
};
|
||||
|
||||
const char* const settings_text[2] = {
|
||||
"OFF",
|
||||
"ON",
|
||||
};
|
||||
const uint32_t settings_value[2] = {
|
||||
HexViewerSettingsOff,
|
||||
HexViewerSettingsOn,
|
||||
};
|
||||
|
||||
static void hex_viewer_scene_settings_set_haptic(VariableItem* item) {
|
||||
HexViewer* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
|
||||
variable_item_set_current_value_text(item, haptic_text[index]);
|
||||
app->haptic = haptic_value[index];
|
||||
}
|
||||
|
||||
static void hex_viewer_scene_settings_set_speaker(VariableItem* item) {
|
||||
HexViewer* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, speaker_text[index]);
|
||||
app->speaker = speaker_value[index];
|
||||
}
|
||||
|
||||
static void hex_viewer_scene_settings_set_led(VariableItem* item) {
|
||||
HexViewer* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, led_text[index]);
|
||||
app->led = led_value[index];
|
||||
}
|
||||
|
||||
static void hex_viewer_scene_settings_set_save_settings(VariableItem* item) {
|
||||
HexViewer* app = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, settings_text[index]);
|
||||
app->save_settings = settings_value[index];
|
||||
}
|
||||
|
||||
void hex_viewer_scene_settings_submenu_callback(void* context, uint32_t index) {
|
||||
HexViewer* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void hex_viewer_scene_settings_on_enter(void* context) {
|
||||
HexViewer* app = context;
|
||||
VariableItem* item;
|
||||
uint8_t value_index;
|
||||
|
||||
// Vibro on/off
|
||||
item = variable_item_list_add(
|
||||
app->variable_item_list, "Vibro/Haptic:", 2, hex_viewer_scene_settings_set_haptic, app);
|
||||
value_index = value_index_uint32(app->haptic, haptic_value, 2);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, haptic_text[value_index]);
|
||||
|
||||
// Sound on/off
|
||||
item = variable_item_list_add(
|
||||
app->variable_item_list, "Sound:", 2, hex_viewer_scene_settings_set_speaker, app);
|
||||
value_index = value_index_uint32(app->speaker, speaker_value, 2);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, speaker_text[value_index]);
|
||||
|
||||
// LED Effects on/off
|
||||
item = variable_item_list_add(
|
||||
app->variable_item_list, "LED FX:", 2, hex_viewer_scene_settings_set_led, app);
|
||||
value_index = value_index_uint32(app->led, led_value, 2);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, led_text[value_index]);
|
||||
|
||||
// Save Settings to File
|
||||
item = variable_item_list_add(
|
||||
app->variable_item_list,
|
||||
"Save Settings",
|
||||
2,
|
||||
hex_viewer_scene_settings_set_save_settings,
|
||||
app);
|
||||
value_index = value_index_uint32(app->save_settings, settings_value, 2);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, settings_text[value_index]);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdSettings);
|
||||
}
|
||||
|
||||
bool hex_viewer_scene_settings_on_event(void* context, SceneManagerEvent event) {
|
||||
HexViewer* app = context;
|
||||
UNUSED(app);
|
||||
bool consumed = false;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void hex_viewer_scene_settings_on_exit(void* context) {
|
||||
HexViewer* app = context;
|
||||
variable_item_list_set_selected_item(app->variable_item_list, 0);
|
||||
variable_item_list_reset(app->variable_item_list);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "../hex_viewer.h"
|
||||
#include "../helpers/hex_viewer_custom_event.h"
|
||||
#include "../views/hex_viewer_startscreen.h"
|
||||
|
||||
void hex_viewer_scene_startscreen_callback(HexViewerCustomEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
HexViewer* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void hex_viewer_scene_startscreen_on_enter(void* context) {
|
||||
furi_assert(context);
|
||||
HexViewer* app = context;
|
||||
hex_viewer_startscreen_set_callback(
|
||||
app->hex_viewer_startscreen, hex_viewer_scene_startscreen_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdStartscreen);
|
||||
}
|
||||
|
||||
bool hex_viewer_scene_startscreen_on_event(void* context, SceneManagerEvent event) {
|
||||
HexViewer* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case HexViewerCustomEventStartscreenLeft:
|
||||
//app->model->mode = !app->model->mode;
|
||||
consumed = true;
|
||||
break;
|
||||
case HexViewerCustomEventStartscreenRight:
|
||||
consumed = true;
|
||||
break;
|
||||
case HexViewerCustomEventStartscreenUp:
|
||||
consumed = true;
|
||||
break;
|
||||
case HexViewerCustomEventStartscreenDown:
|
||||
consumed = true;
|
||||
break;
|
||||
case HexViewerCustomEventStartscreenOk:
|
||||
if(!app->model->file_size)
|
||||
scene_manager_next_scene(app->scene_manager, HexViewerSceneOpen);
|
||||
else
|
||||
scene_manager_next_scene(app->scene_manager, HexViewerSceneMenu);
|
||||
consumed = true;
|
||||
break;
|
||||
case HexViewerCustomEventStartscreenBack: // TODO Delete
|
||||
notification_message(app->notification, &sequence_reset_red);
|
||||
notification_message(app->notification, &sequence_reset_green);
|
||||
notification_message(app->notification, &sequence_reset_blue);
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, HexViewerSceneStartscreen)) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void hex_viewer_scene_startscreen_on_exit(void* context) {
|
||||
HexViewer* app = context;
|
||||
UNUSED(app);
|
||||
}
|
||||
245
applications/system/hex_viewer/views/hex_viewer_startscreen.c
Normal file
@@ -0,0 +1,245 @@
|
||||
#include "../hex_viewer.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <input/input.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
struct HexViewerStartscreen {
|
||||
View* view;
|
||||
HexViewerStartscreenCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE];
|
||||
uint32_t file_offset;
|
||||
uint32_t file_read_bytes;
|
||||
uint32_t file_size;
|
||||
bool mode;
|
||||
uint32_t dbg;
|
||||
} HexViewerStartscreenModel;
|
||||
|
||||
void hex_viewer_startscreen_set_callback(
|
||||
HexViewerStartscreen* instance,
|
||||
HexViewerStartscreenCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
void hex_viewer_startscreen_draw(Canvas* canvas, HexViewerStartscreenModel* model) {
|
||||
canvas_clear(canvas);
|
||||
|
||||
if(!model->file_size) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "HexViewer v2.0");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Basic hex viewer");
|
||||
canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "for your Flipper");
|
||||
elements_button_center(canvas, "Open");
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
elements_button_left(canvas, model->mode ? "Addr" : "Text");
|
||||
//elements_button_right(canvas, "Info");
|
||||
elements_button_center(canvas, "Menu");
|
||||
|
||||
int ROW_HEIGHT = 12;
|
||||
int TOP_OFFSET = 10;
|
||||
int LEFT_OFFSET = 3;
|
||||
|
||||
uint32_t line_count = model->file_size / HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1;
|
||||
uint32_t first_line_on_screen = model->file_offset / HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(line_count > HEX_VIEWER_LINES_ON_SCREEN) {
|
||||
uint8_t width = canvas_width(canvas);
|
||||
elements_scrollbar_pos(
|
||||
canvas,
|
||||
width,
|
||||
0,
|
||||
ROW_HEIGHT * HEX_VIEWER_LINES_ON_SCREEN,
|
||||
first_line_on_screen, // TODO
|
||||
line_count - (HEX_VIEWER_LINES_ON_SCREEN - 1));
|
||||
}
|
||||
|
||||
char temp_buf[32];
|
||||
uint32_t row_iters = model->file_read_bytes / HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(model->file_read_bytes % HEX_VIEWER_BYTES_PER_LINE != 0) row_iters += 1;
|
||||
|
||||
for(uint32_t i = 0; i < row_iters; ++i) {
|
||||
uint32_t bytes_left_per_row = model->file_read_bytes - i * HEX_VIEWER_BYTES_PER_LINE;
|
||||
bytes_left_per_row = MIN(bytes_left_per_row, HEX_VIEWER_BYTES_PER_LINE);
|
||||
|
||||
if(model->mode) {
|
||||
memcpy(temp_buf, model->file_bytes[i], bytes_left_per_row);
|
||||
temp_buf[bytes_left_per_row] = '\0';
|
||||
for(uint32_t j = 0; j < bytes_left_per_row; ++j)
|
||||
if(!isprint((int)temp_buf[j])) temp_buf[j] = '.';
|
||||
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
|
||||
} else {
|
||||
uint32_t addr = model->file_offset + i * HEX_VIEWER_BYTES_PER_LINE;
|
||||
snprintf(temp_buf, 32, "%04lX", addr);
|
||||
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
|
||||
}
|
||||
|
||||
char* p = temp_buf;
|
||||
for(uint32_t j = 0; j < bytes_left_per_row; ++j)
|
||||
p += snprintf(p, 32, "%02X ", model->file_bytes[i][j]);
|
||||
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
|
||||
}
|
||||
|
||||
// Poor man's debug
|
||||
// snprintf(temp_buf, 32, "D %02lX", model->dbg);
|
||||
// elements_button_right(canvas, temp_buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void hex_viewer_startscreen_model_init(HexViewerStartscreenModel* const model) {
|
||||
memset(model->file_bytes, 0, sizeof(model->file_bytes));
|
||||
model->file_offset = 0;
|
||||
model->file_read_bytes = 0;
|
||||
model->file_size = 0;
|
||||
model->mode = false;
|
||||
model->dbg = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
update_local_model_from_app(HexViewer* const app, HexViewerStartscreenModel* const model) {
|
||||
memcpy(model->file_bytes, app->model->file_bytes, sizeof(model->file_bytes));
|
||||
model->file_offset = app->model->file_offset;
|
||||
model->file_read_bytes = app->model->file_read_bytes;
|
||||
model->file_size = app->model->file_size;
|
||||
//model->mode = app->model->mode;
|
||||
}
|
||||
|
||||
bool hex_viewer_startscreen_input(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HexViewerStartscreen* instance = context;
|
||||
HexViewer* app = instance->context; // TO so good, but works
|
||||
// TODO InputTypeShort?
|
||||
if(event->type == InputTypeRelease || event->type == InputTypeRepeat) {
|
||||
switch(event->key) {
|
||||
case InputKeyBack:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
HexViewerStartscreenModel * model,
|
||||
{
|
||||
instance->callback(HexViewerCustomEventStartscreenBack, instance->context);
|
||||
update_local_model_from_app(instance->context, model);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
HexViewerStartscreenModel * model,
|
||||
{ model->mode = !model->mode; },
|
||||
true);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
with_view_model(
|
||||
instance->view, HexViewerStartscreenModel * model, { model->dbg = 0; }, true);
|
||||
break;
|
||||
case InputKeyUp:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
HexViewerStartscreenModel * model,
|
||||
{
|
||||
if(app->model->file_offset > 0) {
|
||||
app->model->file_offset -= HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(!hex_viewer_read_file(app)) break; // TODO Do smth
|
||||
}
|
||||
|
||||
update_local_model_from_app(instance->context, model);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
HexViewerStartscreenModel * model,
|
||||
{
|
||||
uint32_t last_byte_on_screen =
|
||||
app->model->file_offset + app->model->file_read_bytes;
|
||||
if(app->model->file_size > last_byte_on_screen) {
|
||||
app->model->file_offset += HEX_VIEWER_BYTES_PER_LINE;
|
||||
if(!hex_viewer_read_file(app)) break; // TODO Do smth
|
||||
}
|
||||
|
||||
update_local_model_from_app(instance->context, model);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
with_view_model(
|
||||
instance->view,
|
||||
HexViewerStartscreenModel * model,
|
||||
{
|
||||
instance->callback(HexViewerCustomEventStartscreenOk, instance->context);
|
||||
update_local_model_from_app(instance->context, model);
|
||||
},
|
||||
true);
|
||||
break;
|
||||
case InputKeyMAX:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void hex_viewer_startscreen_exit(void* context) {
|
||||
furi_assert(context);
|
||||
}
|
||||
|
||||
void hex_viewer_startscreen_enter(void* context) {
|
||||
furi_assert(context);
|
||||
HexViewerStartscreen* instance = (HexViewerStartscreen*)context;
|
||||
with_view_model(
|
||||
instance->view,
|
||||
HexViewerStartscreenModel * model,
|
||||
{ update_local_model_from_app(instance->context, model); },
|
||||
true);
|
||||
}
|
||||
|
||||
HexViewerStartscreen* hex_viewer_startscreen_alloc() {
|
||||
HexViewerStartscreen* instance = malloc(sizeof(HexViewerStartscreen));
|
||||
instance->view = view_alloc();
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(HexViewerStartscreenModel));
|
||||
view_set_context(instance->view, instance);
|
||||
view_set_draw_callback(instance->view, (ViewDrawCallback)hex_viewer_startscreen_draw);
|
||||
view_set_input_callback(instance->view, hex_viewer_startscreen_input);
|
||||
view_set_enter_callback(instance->view, hex_viewer_startscreen_enter);
|
||||
view_set_exit_callback(instance->view, hex_viewer_startscreen_exit);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
HexViewerStartscreenModel * model,
|
||||
{ hex_viewer_startscreen_model_init(model); },
|
||||
true);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void hex_viewer_startscreen_free(HexViewerStartscreen* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
with_view_model(
|
||||
instance->view, HexViewerStartscreenModel * model, { UNUSED(model); }, true);
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* hex_viewer_startscreen_get_view(HexViewerStartscreen* instance) {
|
||||
furi_assert(instance);
|
||||
return instance->view;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../helpers/hex_viewer_custom_event.h"
|
||||
|
||||
typedef struct HexViewerStartscreen HexViewerStartscreen;
|
||||
|
||||
typedef void (*HexViewerStartscreenCallback)(HexViewerCustomEvent event, void* context);
|
||||
|
||||
void hex_viewer_startscreen_set_callback(
|
||||
HexViewerStartscreen* hex_viewer_startscreen,
|
||||
HexViewerStartscreenCallback callback,
|
||||
void* context);
|
||||
|
||||
View* hex_viewer_startscreen_get_view(HexViewerStartscreen* hex_viewer_static);
|
||||
|
||||
HexViewerStartscreen* hex_viewer_startscreen_alloc();
|
||||
|
||||
void hex_viewer_startscreen_free(HexViewerStartscreen* hex_viewer_static);
|
||||
BIN
applications/system/hid_app/assets/BtnFrameLeft_3x18.png
Normal file
|
After Width: | Height: | Size: 362 B |
BIN
applications/system/hid_app/assets/BtnFrameRight_2x18.png
Normal file
|
After Width: | Height: | Size: 362 B |
BIN
applications/system/hid_app/assets/Mic_btn_8x10.png
Normal file
|
After Width: | Height: | Size: 141 B |
|
After Width: | Height: | Size: 959 B |
@@ -11,10 +11,12 @@ enum HidDebugSubmenuIndex {
|
||||
HidSubmenuIndexKeyboard,
|
||||
HidSubmenuIndexNumpad,
|
||||
HidSubmenuIndexMedia,
|
||||
HidSubmenuIndexMovie,
|
||||
HidSubmenuIndexTikTok,
|
||||
HidSubmenuIndexMouse,
|
||||
HidSubmenuIndexMouseClicker,
|
||||
HidSubmenuIndexMouseJiggler,
|
||||
HidSubmenuIndexPtt,
|
||||
};
|
||||
|
||||
static void hid_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -37,6 +39,9 @@ static void hid_submenu_callback(void* context, uint32_t index) {
|
||||
} else if(index == HidSubmenuIndexMedia) {
|
||||
app->view_id = HidViewMedia;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia);
|
||||
} else if(index == HidSubmenuIndexMovie) {
|
||||
app->view_id = HidViewMovie;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMovie);
|
||||
} else if(index == HidSubmenuIndexMouse) {
|
||||
app->view_id = HidViewMouse;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse);
|
||||
@@ -49,6 +54,9 @@ static void hid_submenu_callback(void* context, uint32_t index) {
|
||||
} else if(index == HidSubmenuIndexMouseJiggler) {
|
||||
app->view_id = HidViewMouseJiggler;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler);
|
||||
} else if(index == HidSubmenuIndexPtt) {
|
||||
app->view_id = HidViewPtt;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPtt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,27 +75,17 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con
|
||||
hid_keyboard_set_connected_status(hid->hid_keyboard, connected);
|
||||
hid_numpad_set_connected_status(hid->hid_numpad, connected);
|
||||
hid_media_set_connected_status(hid->hid_media, connected);
|
||||
hid_movie_set_connected_status(hid->hid_movie, connected);
|
||||
hid_mouse_set_connected_status(hid->hid_mouse, connected);
|
||||
hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected);
|
||||
hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected);
|
||||
hid_ptt_set_connected_status(hid->hid_ptt, connected);
|
||||
hid_tiktok_set_connected_status(hid->hid_tiktok, connected);
|
||||
}
|
||||
|
||||
static void hid_dialog_callback(DialogExResult result, void* context) {
|
||||
furi_assert(context);
|
||||
Hid* app = context;
|
||||
if(result == DialogExResultLeft) {
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
} else if(result == DialogExResultRight) {
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view
|
||||
} else if(result == DialogExResultCenter) {
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t hid_exit_confirm_view(void* context) {
|
||||
static uint32_t hid_menu_view(void* context) {
|
||||
UNUSED(context);
|
||||
return HidViewExitConfirm;
|
||||
return HidViewSubmenu;
|
||||
}
|
||||
|
||||
static uint32_t hid_exit(void* context) {
|
||||
@@ -128,6 +126,8 @@ Hid* hid_alloc(HidTransport transport) {
|
||||
app->device_type_submenu, "Numpad", HidSubmenuIndexNumpad, hid_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu, "Movie", HidSubmenuIndexMovie, hid_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app);
|
||||
if(app->transport == HidTransportBle) {
|
||||
@@ -150,6 +150,8 @@ Hid* hid_alloc(HidTransport transport) {
|
||||
HidSubmenuIndexMouseJiggler,
|
||||
hid_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu, "PTT", HidSubmenuIndexPtt, hid_submenu_callback, app);
|
||||
view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu));
|
||||
@@ -162,56 +164,52 @@ Hid* hid_app_alloc_view(void* context) {
|
||||
furi_assert(context);
|
||||
Hid* app = context;
|
||||
// Dialog view
|
||||
app->dialog = dialog_ex_alloc();
|
||||
dialog_ex_set_result_callback(app->dialog, hid_dialog_callback);
|
||||
dialog_ex_set_context(app->dialog, app);
|
||||
dialog_ex_set_left_button_text(app->dialog, "Exit");
|
||||
dialog_ex_set_right_button_text(app->dialog, "Stay");
|
||||
dialog_ex_set_center_button_text(app->dialog, "Menu");
|
||||
dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewExitConfirm, dialog_ex_get_view(app->dialog));
|
||||
|
||||
// Keynote view
|
||||
app->hid_keynote = hid_keynote_alloc(app);
|
||||
view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_exit_confirm_view);
|
||||
view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote));
|
||||
|
||||
// Keyboard view
|
||||
app->hid_keyboard = hid_keyboard_alloc(app);
|
||||
view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view);
|
||||
view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard));
|
||||
|
||||
//Numpad keyboard view
|
||||
app->hid_numpad = hid_numpad_alloc(app);
|
||||
view_set_previous_callback(hid_numpad_get_view(app->hid_numpad), hid_exit_confirm_view);
|
||||
view_set_previous_callback(hid_numpad_get_view(app->hid_numpad), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewNumpad, hid_numpad_get_view(app->hid_numpad));
|
||||
|
||||
// Media view
|
||||
app->hid_media = hid_media_alloc(app);
|
||||
view_set_previous_callback(hid_media_get_view(app->hid_media), hid_exit_confirm_view);
|
||||
view_set_previous_callback(hid_media_get_view(app->hid_media), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media));
|
||||
|
||||
// Movie view
|
||||
app->hid_movie = hid_movie_alloc(app);
|
||||
view_set_previous_callback(hid_movie_get_view(app->hid_movie), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewMovie, hid_movie_get_view(app->hid_movie));
|
||||
|
||||
// TikTok view
|
||||
app->hid_tiktok = hid_tiktok_alloc(app);
|
||||
view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_exit_confirm_view);
|
||||
view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok));
|
||||
|
||||
// Mouse view
|
||||
app->hid_mouse = hid_mouse_alloc(app);
|
||||
view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_exit_confirm_view);
|
||||
view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse));
|
||||
|
||||
// Mouse clicker view
|
||||
app->hid_mouse_clicker = hid_mouse_clicker_alloc(app);
|
||||
view_set_previous_callback(
|
||||
hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_exit_confirm_view);
|
||||
view_set_previous_callback(hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
HidViewMouseClicker,
|
||||
@@ -219,13 +217,17 @@ Hid* hid_app_alloc_view(void* context) {
|
||||
|
||||
// Mouse jiggler view
|
||||
app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app);
|
||||
view_set_previous_callback(
|
||||
hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_exit_confirm_view);
|
||||
view_set_previous_callback(hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
HidViewMouseJiggler,
|
||||
hid_mouse_jiggler_get_view(app->hid_mouse_jiggler));
|
||||
|
||||
// Ptt view
|
||||
app->hid_ptt = hid_ptt_alloc(app);
|
||||
view_set_previous_callback(hid_ptt_get_view(app->hid_ptt), hid_menu_view);
|
||||
view_dispatcher_add_view(app->view_dispatcher, HidViewPtt, hid_ptt_get_view(app->hid_ptt));
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
@@ -240,8 +242,6 @@ void hid_free(Hid* app) {
|
||||
// Free views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu);
|
||||
submenu_free(app->device_type_submenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewExitConfirm);
|
||||
dialog_ex_free(app->dialog);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote);
|
||||
hid_keynote_free(app->hid_keynote);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard);
|
||||
@@ -250,12 +250,16 @@ void hid_free(Hid* app) {
|
||||
hid_numpad_free(app->hid_numpad);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia);
|
||||
hid_media_free(app->hid_media);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMovie);
|
||||
hid_movie_free(app->hid_movie);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse);
|
||||
hid_mouse_free(app->hid_mouse);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseClicker);
|
||||
hid_mouse_clicker_free(app->hid_mouse_clicker);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler);
|
||||
hid_mouse_jiggler_free(app->hid_mouse_jiggler);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewPtt);
|
||||
hid_ptt_free(app->hid_ptt);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok);
|
||||
hid_tiktok_free(app->hid_tiktok);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
@@ -437,9 +441,7 @@ int32_t hid_ble_app(void* p) {
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) {
|
||||
FURI_LOG_E(TAG, "Failed to switch to HID profile");
|
||||
}
|
||||
furi_check(bt_set_profile(app->bt, BtProfileHidKeyboard));
|
||||
|
||||
furi_hal_bt_start_advertising();
|
||||
bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app);
|
||||
@@ -457,9 +459,7 @@ int32_t hid_ble_app(void* p) {
|
||||
|
||||
bt_keys_storage_set_default_path(app->bt);
|
||||
|
||||
if(!bt_set_profile(app->bt, BtProfileSerial)) {
|
||||
FURI_LOG_E(TAG, "Failed to switch to Serial profile");
|
||||
}
|
||||
furi_check(bt_set_profile(app->bt, BtProfileSerial));
|
||||
|
||||
hid_free(app);
|
||||
|
||||
|
||||
@@ -14,16 +14,19 @@
|
||||
#include <storage/storage.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include "views/hid_keynote.h"
|
||||
#include "views/hid_keyboard.h"
|
||||
#include "views/hid_numpad.h"
|
||||
#include "views/hid_media.h"
|
||||
#include "views/hid_movie.h"
|
||||
#include "views/hid_mouse.h"
|
||||
#include "views/hid_mouse_clicker.h"
|
||||
#include "views/hid_mouse_jiggler.h"
|
||||
#include "views/hid_tiktok.h"
|
||||
#include "views/hid_ptt.h"
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
|
||||
|
||||
@@ -40,15 +43,16 @@ struct Hid {
|
||||
NotificationApp* notifications;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* device_type_submenu;
|
||||
DialogEx* dialog;
|
||||
HidKeynote* hid_keynote;
|
||||
HidKeyboard* hid_keyboard;
|
||||
HidNumpad* hid_numpad;
|
||||
HidMedia* hid_media;
|
||||
HidMovie* hid_movie;
|
||||
HidMouse* hid_mouse;
|
||||
HidMouseClicker* hid_mouse_clicker;
|
||||
HidMouseJiggler* hid_mouse_jiggler;
|
||||
HidTikTok* hid_tiktok;
|
||||
HidPtt* hid_ptt;
|
||||
|
||||
HidTransport transport;
|
||||
uint32_t view_id;
|
||||
@@ -66,4 +70,4 @@ void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy);
|
||||
void hid_hal_mouse_scroll(Hid* instance, int8_t delta);
|
||||
void hid_hal_mouse_press(Hid* instance, uint16_t event);
|
||||
void hid_hal_mouse_release(Hid* instance, uint16_t event);
|
||||
void hid_hal_mouse_release_all(Hid* instance);
|
||||
void hid_hal_mouse_release_all(Hid* instance);
|
||||
|
||||
|
Before Width: | Height: | Size: 969 B After Width: | Height: | Size: 174 B |
@@ -4,9 +4,10 @@ typedef enum {
|
||||
HidViewKeyboard,
|
||||
HidViewNumpad,
|
||||
HidViewMedia,
|
||||
HidViewMovie,
|
||||
HidViewMouse,
|
||||
HidViewMouseClicker,
|
||||
HidViewMouseJiggler,
|
||||
BtHidViewTikTok,
|
||||
HidViewExitConfirm,
|
||||
} HidView;
|
||||
HidViewPtt,
|
||||
} HidView;
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#include "../hid.h"
|
||||
#include "hid_icons.h"
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "HidKeyboard"
|
||||
|
||||
struct HidKeyboard {
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "HidKeynote"
|
||||
|
||||
struct HidKeynote {
|
||||
@@ -118,16 +116,16 @@ static void hid_keynote_draw_vertical_callback(Canvas* canvas, void* context) {
|
||||
HidKeynoteModel* model = context;
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote");
|
||||
} else {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote");
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "HidMedia"
|
||||
|
||||
struct HidMedia {
|
||||
@@ -87,8 +85,9 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) {
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_media_draw_arrow(canvas, 65, 28, CanvasDirectionRightToLeft);
|
||||
hid_media_draw_arrow(canvas, 67, 28, CanvasDirectionRightToLeft);
|
||||
hid_media_draw_arrow(canvas, 70, 28, CanvasDirectionRightToLeft);
|
||||
canvas_draw_line(canvas, 64, 26, 64, 30);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
@@ -99,7 +98,8 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_media_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight);
|
||||
hid_media_draw_arrow(canvas, 101, 28, CanvasDirectionLeftToRight);
|
||||
hid_media_draw_arrow(canvas, 99, 28, CanvasDirectionLeftToRight);
|
||||
canvas_draw_line(canvas, 102, 26, 102, 30);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "HidMouse"
|
||||
|
||||
struct HidMouse {
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "HidMouseClicker"
|
||||
#define DEFAULT_CLICK_RATE 1
|
||||
#define MAXIMUM_CLICK_RATE 60
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "HidMouseJiggler"
|
||||
|
||||
struct HidMouseJiggler {
|
||||
@@ -84,7 +82,7 @@ static void hid_mouse_jiggler_timer_callback(void* context) {
|
||||
model->counter++;
|
||||
hid_hal_mouse_move(
|
||||
hid_mouse_jiggler->hid,
|
||||
(model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT,
|
||||
(model->counter % 2 == 0) ? MOUSE_MOVE_TINY : -MOUSE_MOVE_TINY,
|
||||
0);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#define MOUSE_MOVE_SHORT 5
|
||||
#define MOUSE_MOVE_LONG 20
|
||||
#define MOUSE_MOVE_TINY 1
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidMouseJiggler HidMouseJiggler;
|
||||
|
||||
229
applications/system/hid_app/views/hid_movie.c
Normal file
@@ -0,0 +1,229 @@
|
||||
#include "hid_movie.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal_bt_hid.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <gui/elements.h>
|
||||
#include "../hid.h"
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidMovie"
|
||||
|
||||
struct HidMovie {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool ok_pressed;
|
||||
bool connected;
|
||||
bool back_pressed;
|
||||
HidTransport transport;
|
||||
} HidMovieModel;
|
||||
|
||||
static void hid_movie_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
|
||||
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
|
||||
if(dir == CanvasDirectionBottomToTop) {
|
||||
canvas_draw_dot(canvas, x, y - 1);
|
||||
} else if(dir == CanvasDirectionTopToBottom) {
|
||||
canvas_draw_dot(canvas, x, y + 1);
|
||||
} else if(dir == CanvasDirectionRightToLeft) {
|
||||
canvas_draw_dot(canvas, x - 1, y);
|
||||
} else if(dir == CanvasDirectionLeftToRight) {
|
||||
canvas_draw_dot(canvas, x + 1, y);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_movie_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidMovieModel* model = context;
|
||||
|
||||
// Header
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Movie");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
// Keypad circles
|
||||
canvas_draw_icon(canvas, 58, 3, &I_OutCircles_70x51);
|
||||
|
||||
// Up
|
||||
if(model->up_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
if(model->down_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
if(model->left_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_movie_draw_arrow(canvas, 65, 28, CanvasDirectionRightToLeft);
|
||||
hid_movie_draw_arrow(canvas, 70, 28, CanvasDirectionRightToLeft);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
if(model->right_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_movie_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight);
|
||||
hid_movie_draw_arrow(canvas, 101, 28, CanvasDirectionLeftToRight);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
if(model->ok_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
hid_movie_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight);
|
||||
canvas_draw_line(canvas, 84, 26, 84, 30);
|
||||
canvas_draw_line(canvas, 86, 26, 86, 30);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Exit
|
||||
if(model->back_pressed) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
|
||||
}
|
||||
|
||||
static void hid_movie_process_press(HidMovie* hid_movie, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_movie->view,
|
||||
HidMovieModel * model,
|
||||
{
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
hid_hal_keyboard_press(hid_movie->hid, HID_KEYBOARD_LEFT_ARROW);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
hid_hal_keyboard_press(hid_movie->hid, HID_KEYBOARD_RIGHT_ARROW);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = true;
|
||||
hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_PLAY_PAUSE);
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_pressed = true;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static void hid_movie_process_release(HidMovie* hid_movie, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_movie->view,
|
||||
HidMovieModel * model,
|
||||
{
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
hid_hal_keyboard_release(hid_movie->hid, HID_KEYBOARD_LEFT_ARROW);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
hid_hal_keyboard_release(hid_movie->hid, HID_KEYBOARD_RIGHT_ARROW);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = false;
|
||||
hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_PLAY_PAUSE);
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->back_pressed = false;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static bool hid_movie_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidMovie* hid_movie = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypePress) {
|
||||
hid_movie_process_press(hid_movie, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
hid_movie_process_release(hid_movie, event);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidMovie* hid_movie_alloc(Hid* hid) {
|
||||
HidMovie* hid_movie = malloc(sizeof(HidMovie));
|
||||
hid_movie->view = view_alloc();
|
||||
hid_movie->hid = hid;
|
||||
view_set_context(hid_movie->view, hid_movie);
|
||||
view_allocate_model(hid_movie->view, ViewModelTypeLocking, sizeof(HidMovieModel));
|
||||
view_set_draw_callback(hid_movie->view, hid_movie_draw_callback);
|
||||
view_set_input_callback(hid_movie->view, hid_movie_input_callback);
|
||||
|
||||
with_view_model(
|
||||
hid_movie->view, HidMovieModel * model, { model->transport = hid->transport; }, true);
|
||||
|
||||
return hid_movie;
|
||||
}
|
||||
|
||||
void hid_movie_free(HidMovie* hid_movie) {
|
||||
furi_assert(hid_movie);
|
||||
view_free(hid_movie->view);
|
||||
free(hid_movie);
|
||||
}
|
||||
|
||||
View* hid_movie_get_view(HidMovie* hid_movie) {
|
||||
furi_assert(hid_movie);
|
||||
return hid_movie->view;
|
||||
}
|
||||
|
||||
void hid_movie_set_connected_status(HidMovie* hid_movie, bool connected) {
|
||||
furi_assert(hid_movie);
|
||||
with_view_model(
|
||||
hid_movie->view, HidMovieModel * model, { model->connected = connected; }, true);
|
||||
}
|
||||
14
applications/system/hid_app/views/hid_movie.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidMovie HidMovie;
|
||||
|
||||
HidMovie* hid_movie_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_movie_free(HidMovie* hid_movie);
|
||||
|
||||
View* hid_movie_get_view(HidMovie* hid_movie);
|
||||
|
||||
void hid_movie_set_connected_status(HidMovie* hid_movie, bool connected);
|
||||
@@ -5,8 +5,6 @@
|
||||
#include "../hid.h"
|
||||
#include "hid_icons.h"
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "HidNumpad"
|
||||
|
||||
struct HidNumpad {
|
||||
|
||||
461
applications/system/hid_app/views/hid_ptt.c
Normal file
@@ -0,0 +1,461 @@
|
||||
#include "hid_ptt.h"
|
||||
#include <gui/elements.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include "../hid.h"
|
||||
#include "../views.h"
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidPtt"
|
||||
|
||||
struct HidPtt {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool muted;
|
||||
bool ptt_pressed;
|
||||
bool mic_pressed;
|
||||
bool connected;
|
||||
bool is_mac_os;
|
||||
uint32_t appIndex;
|
||||
HidTransport transport;
|
||||
} HidPttModel;
|
||||
|
||||
enum HidPttAppIndex {
|
||||
HidPttAppIndexGoogleMeet,
|
||||
HidPttAppIndexZoom,
|
||||
HidPttAppIndexFaceTime,
|
||||
HidPttAppIndexSkype,
|
||||
HidPttAppIndexSize,
|
||||
};
|
||||
|
||||
static void hid_ptt_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidPttModel* model = context;
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
}
|
||||
|
||||
// App selection
|
||||
const uint8_t y_app = 78;
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_icon(canvas, 0, y_app, &I_ButtonLeft_4x7);
|
||||
if(model->appIndex == HidPttAppIndexGoogleMeet) {
|
||||
elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "Google Meet");
|
||||
} else if(model->appIndex == HidPttAppIndexZoom) {
|
||||
elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "Zoom");
|
||||
} else if(model->appIndex == HidPttAppIndexFaceTime) {
|
||||
elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "FaceTime");
|
||||
} else if(model->appIndex == HidPttAppIndexSkype) {
|
||||
elements_multiline_text_aligned(canvas, 7, y_app, AlignLeft, AlignTop, "Skype");
|
||||
}
|
||||
canvas_draw_icon(canvas, 60, y_app, &I_ButtonRight_4x7);
|
||||
|
||||
// OS selection
|
||||
const uint8_t y_os = 88;
|
||||
const uint8_t x_os = 7;
|
||||
// elements_slightly_rounded_box(canvas, model->is_mac_os ? 0 : 26, y_os, model->is_mac_os ? 21 : 26, 11);
|
||||
elements_slightly_rounded_box(
|
||||
canvas, model->is_mac_os ? x_os : x_os + 26, y_os, model->is_mac_os ? 21 : 26, 11);
|
||||
canvas_set_color(canvas, model->is_mac_os ? ColorWhite : ColorBlack);
|
||||
elements_multiline_text_aligned(canvas, x_os + 2, y_os + 1, AlignLeft, AlignTop, "Mac");
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
if(model->appIndex != HidPttAppIndexFaceTime) {
|
||||
elements_multiline_text_aligned(canvas, x_os + 23, y_os + 2, AlignLeft, AlignTop, "|");
|
||||
canvas_set_color(canvas, model->is_mac_os ? ColorBlack : ColorWhite);
|
||||
elements_multiline_text_aligned(canvas, x_os + 28, y_os + 2, AlignLeft, AlignTop, "Linux");
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
// Mic label
|
||||
const uint8_t y_mic = 102;
|
||||
canvas_draw_icon(canvas, 19, y_mic - 1, &I_Pin_back_arrow_rotated_8x10);
|
||||
elements_multiline_text_aligned(canvas, 0, y_mic, AlignLeft, AlignTop, "Hold to sync");
|
||||
elements_multiline_text_aligned(canvas, 20, y_mic + 10, AlignLeft, AlignTop, "mic status");
|
||||
|
||||
// Exit label
|
||||
canvas_draw_icon(canvas, 20, 121, &I_ButtonLeft_4x7);
|
||||
elements_multiline_text_aligned(canvas, 0, 121, AlignLeft, AlignTop, "Hold to exit");
|
||||
|
||||
const uint8_t x_1 = 0;
|
||||
const uint8_t x_2 = x_1 + 19 + 4;
|
||||
const uint8_t x_3 = x_1 + 19 * 2 + 8;
|
||||
|
||||
const uint8_t y_1 = 19;
|
||||
const uint8_t y_2 = y_1 + 19;
|
||||
const uint8_t y_3 = y_2 + 19;
|
||||
|
||||
// Up
|
||||
canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18);
|
||||
if(model->up_pressed) {
|
||||
elements_slightly_rounded_box(canvas, x_2 + 3, y_1 + 2, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
if(model->ptt_pressed) {
|
||||
if(model->appIndex != HidPttAppIndexFaceTime) {
|
||||
elements_multiline_text_aligned(canvas, x_2 + 4, y_1 + 5, AlignLeft, AlignTop, "OS");
|
||||
}
|
||||
} else {
|
||||
canvas_draw_icon(canvas, x_2 + 5, y_1 + 5, &I_Volup_8x6);
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
canvas_draw_icon(canvas, x_2, y_3, &I_Button_18x18);
|
||||
if(model->down_pressed) {
|
||||
elements_slightly_rounded_box(canvas, x_2 + 3, y_3 + 2, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
if(!model->ptt_pressed) {
|
||||
canvas_draw_icon(canvas, x_2 + 6, y_3 + 5, &I_Voldwn_6x6);
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
canvas_draw_icon(canvas, x_1, y_2, &I_Button_18x18);
|
||||
if(model->left_pressed) {
|
||||
elements_slightly_rounded_box(canvas, x_1 + 3, y_2 + 2, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
if(model->ptt_pressed) {
|
||||
canvas_draw_icon(canvas, x_1 + 7, y_2 + 5, &I_ButtonLeft_4x7);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, x_1 + 4, y_2 + 5, &I_Pin_back_arrow_10x8);
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right / Camera
|
||||
canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18);
|
||||
if(model->right_pressed) {
|
||||
elements_slightly_rounded_box(canvas, x_3 + 3, y_2 + 2, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
if(!model->ptt_pressed) {
|
||||
if(model->appIndex != HidPttAppIndexFaceTime) {
|
||||
canvas_draw_icon(canvas, x_3 + 11, y_2 + 5, &I_ButtonLeft_4x7);
|
||||
canvas_draw_box(canvas, x_3 + 4, y_2 + 5, 7, 7);
|
||||
}
|
||||
} else {
|
||||
canvas_draw_icon(canvas, x_3 + 8, y_2 + 5, &I_ButtonRight_4x7);
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Back / Mic
|
||||
const uint8_t x_mic = x_3;
|
||||
canvas_draw_icon(canvas, x_mic, 0, &I_Button_18x18);
|
||||
if(model->mic_pressed) {
|
||||
elements_slightly_rounded_box(canvas, x_mic + 3, 0 + 2, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, x_mic + 5, 0 + 4, &I_Mic_btn_8x10);
|
||||
if(model->muted && !model->ptt_pressed) {
|
||||
canvas_draw_line(canvas, x_mic + 3, 2, x_mic + 3 + 13, 2 + 13);
|
||||
canvas_draw_line(canvas, x_mic + 2, 2, x_mic + 2 + 13, 2 + 13);
|
||||
canvas_draw_line(canvas, x_mic + 3, 2 + 13, x_mic + 3 + 13, 2);
|
||||
canvas_draw_line(canvas, x_mic + 2, 2 + 13, x_mic + 2 + 13, 2);
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok / PTT
|
||||
const uint8_t x_ptt_margin = 4;
|
||||
const uint8_t x_ptt_width = 17;
|
||||
const uint8_t x_ptt = x_1 + 19;
|
||||
canvas_draw_icon(canvas, x_ptt, y_2, &I_BtnFrameLeft_3x18);
|
||||
canvas_draw_icon(canvas, x_ptt + x_ptt_width + 3 + x_ptt_margin, y_2, &I_BtnFrameRight_2x18);
|
||||
canvas_draw_line(canvas, x_ptt + 3, y_2, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2);
|
||||
canvas_draw_line(
|
||||
canvas, x_ptt + 3, y_2 + 16, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 16);
|
||||
canvas_draw_line(
|
||||
canvas, x_ptt + 3, y_2 + 17, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 17);
|
||||
if(model->ptt_pressed) {
|
||||
elements_slightly_rounded_box(canvas, x_ptt + 3, y_2 + 2, x_ptt_width + x_ptt_margin, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(
|
||||
canvas, x_ptt + 2 + x_ptt_margin / 2, y_2 + 13, AlignLeft, AlignBottom, "PTT");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
}
|
||||
|
||||
static void hid_ptt_trigger_mute(HidPtt* hid_ptt, HidPttModel* model) {
|
||||
if(model->appIndex == HidPttAppIndexGoogleMeet && model->is_mac_os) {
|
||||
hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D);
|
||||
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D);
|
||||
} else if(model->appIndex == HidPttAppIndexGoogleMeet && !model->is_mac_os) {
|
||||
hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D);
|
||||
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D);
|
||||
} else if(model->appIndex == HidPttAppIndexZoom && model->is_mac_os) {
|
||||
hid_hal_keyboard_press(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A);
|
||||
hid_hal_keyboard_release(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A);
|
||||
} else if(model->appIndex == HidPttAppIndexFaceTime) {
|
||||
hid_hal_keyboard_press(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
hid_hal_keyboard_release(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
} else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) {
|
||||
hid_hal_keyboard_press(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
hid_hal_keyboard_release(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
} else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) {
|
||||
hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
|
||||
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_ptt_trigger_camera(HidPtt* hid_ptt, HidPttModel* model) {
|
||||
if(model->appIndex == HidPttAppIndexGoogleMeet && model->is_mac_os) {
|
||||
hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E);
|
||||
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E);
|
||||
} else if(model->appIndex == HidPttAppIndexGoogleMeet && !model->is_mac_os) {
|
||||
hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E);
|
||||
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E);
|
||||
} else if(model->appIndex == HidPttAppIndexZoom && model->is_mac_os) {
|
||||
hid_hal_keyboard_press(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
|
||||
hid_hal_keyboard_release(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
|
||||
} else if(model->appIndex == HidPttAppIndexZoom && !model->is_mac_os) {
|
||||
hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V);
|
||||
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V);
|
||||
} else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) {
|
||||
hid_hal_keyboard_press(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
|
||||
hid_hal_keyboard_release(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
|
||||
} else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) {
|
||||
hid_hal_keyboard_press(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
|
||||
hid_hal_keyboard_release(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_ptt_start_ptt(HidPtt* hid_ptt, HidPttModel* model) {
|
||||
if(model->appIndex == HidPttAppIndexGoogleMeet) {
|
||||
hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
|
||||
} else if(model->appIndex == HidPttAppIndexZoom) {
|
||||
hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
|
||||
} else if(model->appIndex == HidPttAppIndexFaceTime) {
|
||||
hid_hal_keyboard_press(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
hid_hal_keyboard_release(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
} else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) {
|
||||
hid_hal_keyboard_press(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
hid_hal_keyboard_release(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
} else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) {
|
||||
hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
|
||||
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_ptt_stop_ptt(HidPtt* hid_ptt, HidPttModel* model) {
|
||||
if(model->appIndex == HidPttAppIndexGoogleMeet) {
|
||||
hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
|
||||
} else if(model->appIndex == HidPttAppIndexZoom) {
|
||||
hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
|
||||
} else if(model->appIndex == HidPttAppIndexFaceTime) {
|
||||
hid_hal_keyboard_press(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
hid_hal_keyboard_release(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
} else if(model->appIndex == HidPttAppIndexSkype && model->is_mac_os) {
|
||||
hid_hal_keyboard_press(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
hid_hal_keyboard_release(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
} else if(model->appIndex == HidPttAppIndexSkype && !model->is_mac_os) {
|
||||
hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
|
||||
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
|
||||
}
|
||||
}
|
||||
|
||||
// Supports only ±1
|
||||
static void hid_ptt_shift_app(HidPttModel* model, int shift) {
|
||||
int i = (short)model->appIndex;
|
||||
if(i + shift >= HidPttAppIndexSize) {
|
||||
model->appIndex = 0;
|
||||
} else if(i + shift <= 0) {
|
||||
model->appIndex = HidPttAppIndexSize - 1;
|
||||
} else {
|
||||
model->appIndex += shift;
|
||||
}
|
||||
// Avoid showing facetime if not macos
|
||||
if(model->appIndex == HidPttAppIndexFaceTime && !model->is_mac_os) {
|
||||
hid_ptt_shift_app(model, shift);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_ptt_process(HidPtt* hid_ptt, InputEvent* event) {
|
||||
with_view_model(
|
||||
hid_ptt->view,
|
||||
HidPttModel * model,
|
||||
{
|
||||
if(event->type == InputTypePress) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
if(!model->ptt_pressed) {
|
||||
hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_INCREMENT);
|
||||
} else {
|
||||
if(model->appIndex != HidPttAppIndexFaceTime) {
|
||||
model->is_mac_os = !model->is_mac_os;
|
||||
notification_message(
|
||||
hid_ptt->hid->notifications, &sequence_single_vibro);
|
||||
}
|
||||
}
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
if(!model->ptt_pressed) {
|
||||
hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else {
|
||||
hid_ptt_shift_app(model, -1);
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
|
||||
}
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
if(model->ptt_pressed) {
|
||||
hid_ptt_shift_app(model, 1);
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
|
||||
}
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
if(model->ptt_pressed) {
|
||||
hid_ptt_shift_app(model, -1);
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
|
||||
}
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ptt_pressed = true;
|
||||
if(model->muted) {
|
||||
hid_ptt_start_ptt(hid_ptt, model);
|
||||
}
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->mic_pressed = true;
|
||||
}
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
if(!model->ptt_pressed) {
|
||||
hid_hal_consumer_key_release(hid_ptt->hid, HID_CONSUMER_VOLUME_INCREMENT);
|
||||
}
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = false;
|
||||
if(!model->ptt_pressed) {
|
||||
hid_hal_consumer_key_release(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
}
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ptt_pressed = false;
|
||||
if(model->muted) {
|
||||
hid_ptt_stop_ptt(hid_ptt, model);
|
||||
} else {
|
||||
hid_ptt_trigger_mute(hid_ptt, model);
|
||||
model->muted = true;
|
||||
}
|
||||
} else if(event->key == InputKeyBack) {
|
||||
model->mic_pressed = false;
|
||||
}
|
||||
} else if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyBack &&
|
||||
!model->ptt_pressed) { // no changes if PTT is pressed
|
||||
model->muted = !model->muted;
|
||||
hid_ptt_trigger_mute(hid_ptt, model);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
if(!model->ptt_pressed) {
|
||||
hid_ptt_trigger_camera(hid_ptt, model);
|
||||
}
|
||||
}
|
||||
} else if(event->type == InputTypeLong) {
|
||||
if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
if(!model->ptt_pressed) {
|
||||
hid_hal_keyboard_release_all(hid_ptt->hid);
|
||||
view_dispatcher_switch_to_view(
|
||||
hid_ptt->hid->view_dispatcher, HidViewSubmenu);
|
||||
// sequence_double_vibro to notify that we quit PTT
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_double_vibro);
|
||||
}
|
||||
} else if(event->key == InputKeyBack && !model->ptt_pressed) { // no changes if PTT is pressed
|
||||
// Change local mic status
|
||||
model->muted = !model->muted;
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
|
||||
}
|
||||
}
|
||||
//LED
|
||||
if(model->muted && !model->ptt_pressed) {
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_reset_red);
|
||||
} else {
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_set_red_255);
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static bool hid_ptt_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidPtt* hid_ptt = context;
|
||||
bool consumed = true;
|
||||
hid_ptt_process(hid_ptt, event);
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidPtt* hid_ptt_alloc(Hid* hid) {
|
||||
HidPtt* hid_ptt = malloc(sizeof(HidPtt));
|
||||
hid_ptt->view = view_alloc();
|
||||
hid_ptt->hid = hid;
|
||||
view_set_context(hid_ptt->view, hid_ptt);
|
||||
view_allocate_model(hid_ptt->view, ViewModelTypeLocking, sizeof(HidPttModel));
|
||||
view_set_draw_callback(hid_ptt->view, hid_ptt_draw_callback);
|
||||
view_set_input_callback(hid_ptt->view, hid_ptt_input_callback);
|
||||
view_set_orientation(hid_ptt->view, ViewOrientationVerticalFlip);
|
||||
|
||||
with_view_model(
|
||||
hid_ptt->view,
|
||||
HidPttModel * model,
|
||||
{
|
||||
model->transport = hid->transport;
|
||||
model->muted = true; // assume we're muted
|
||||
model->is_mac_os = true;
|
||||
},
|
||||
true);
|
||||
return hid_ptt;
|
||||
}
|
||||
|
||||
void hid_ptt_free(HidPtt* hid_ptt) {
|
||||
furi_assert(hid_ptt);
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_reset_red);
|
||||
view_free(hid_ptt->view);
|
||||
free(hid_ptt);
|
||||
}
|
||||
|
||||
View* hid_ptt_get_view(HidPtt* hid_ptt) {
|
||||
furi_assert(hid_ptt);
|
||||
return hid_ptt->view;
|
||||
}
|
||||
|
||||
void hid_ptt_set_connected_status(HidPtt* hid_ptt, bool connected) {
|
||||
furi_assert(hid_ptt);
|
||||
with_view_model(
|
||||
hid_ptt->view, HidPttModel * model, { model->connected = connected; }, true);
|
||||
}
|
||||
14
applications/system/hid_app/views/hid_ptt.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidPtt HidPtt;
|
||||
|
||||
HidPtt* hid_ptt_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_ptt_free(HidPtt* hid_ptt);
|
||||
|
||||
View* hid_ptt_get_view(HidPtt* hid_ptt);
|
||||
|
||||
void hid_ptt_set_connected_status(HidPtt* hid_ptt, bool connected);
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define TAG "HidTikTok"
|
||||
|
||||
struct HidTikTok {
|
||||
|
||||