mirror of
https://github.com/bettse/seader.git
synced 2026-08-22 03:20:03 +00:00
Add multi-tech HF card selection
This commit is contained in:
@@ -3,6 +3,7 @@ ADD_SCENE(seader, sam_present, SamPresent)
|
||||
ADD_SCENE(seader, sam_missing, SamMissing)
|
||||
ADD_SCENE(seader, sam_wrong, SamWrong)
|
||||
ADD_SCENE(seader, read, Read)
|
||||
ADD_SCENE(seader, read_card_type, ReadCardType)
|
||||
ADD_SCENE(seader, read_config_card, ReadConfigCard)
|
||||
ADD_SCENE(seader, read_card_success, ReadCardSuccess)
|
||||
ADD_SCENE(seader, read_config_card_success, ReadConfigCardSuccess)
|
||||
|
||||
@@ -16,6 +16,10 @@ void seader_scene_read_on_enter(void* context) {
|
||||
|
||||
seader_scene_read_prepare(seader);
|
||||
seader_credential_clear(seader->credential);
|
||||
if(seader->selected_read_type == SeaderCredentialTypeNone) {
|
||||
seader->detected_card_type_count = 0;
|
||||
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
|
||||
}
|
||||
seader_worker_start(
|
||||
seader->worker,
|
||||
SeaderWorkerStateReading,
|
||||
@@ -43,8 +47,14 @@ bool seader_scene_read_on_event(void* context, SceneManagerEvent event) {
|
||||
event.event == SeaderWorkerEventSuccess) {
|
||||
scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
|
||||
consumed = true;
|
||||
} else if(event.event == SeaderWorkerEventSelectCardType) {
|
||||
scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardType);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
seader->selected_read_type = SeaderCredentialTypeNone;
|
||||
seader->detected_card_type_count = 0;
|
||||
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
seader->scene_manager, SeaderSceneSamPresent);
|
||||
consumed = true;
|
||||
|
||||
@@ -20,6 +20,9 @@ void seader_scene_read_card_success_on_enter(void* context) {
|
||||
SeaderCredential* credential = seader->credential;
|
||||
PluginWiegand* plugin = seader->plugin_wiegand;
|
||||
Widget* widget = seader->widget;
|
||||
seader->selected_read_type = SeaderCredentialTypeNone;
|
||||
seader->detected_card_type_count = 0;
|
||||
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
|
||||
|
||||
// Use reusable strings instead of allocating new ones
|
||||
FuriString* type_str = seader->temp_string1;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "../seader_i.h"
|
||||
|
||||
static const char* seader_scene_read_card_type_label(SeaderCredentialType type) {
|
||||
switch(type) {
|
||||
case SeaderCredentialType14A:
|
||||
return "Read as 14443A";
|
||||
case SeaderCredentialTypeMifareClassic:
|
||||
return "Read as Mifare Classic";
|
||||
case SeaderCredentialTypePicopass:
|
||||
return "Read as Picopass";
|
||||
default:
|
||||
return "Read";
|
||||
}
|
||||
}
|
||||
|
||||
void seader_scene_read_card_type_submenu_callback(void* context, uint32_t index) {
|
||||
Seader* seader = context;
|
||||
view_dispatcher_send_custom_event(seader->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void seader_scene_read_card_type_on_enter(void* context) {
|
||||
Seader* seader = context;
|
||||
Submenu* submenu = seader->submenu;
|
||||
|
||||
submenu_reset(submenu);
|
||||
for(size_t i = 0; i < seader->detected_card_type_count; i++) {
|
||||
const SeaderCredentialType type = seader->detected_card_types[i];
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
seader_scene_read_card_type_label(type),
|
||||
type,
|
||||
seader_scene_read_card_type_submenu_callback,
|
||||
seader);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewMenu);
|
||||
}
|
||||
|
||||
bool seader_scene_read_card_type_on_event(void* context, SceneManagerEvent event) {
|
||||
Seader* seader = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
const SeaderCredentialType type = event.event;
|
||||
if(type == SeaderCredentialType14A || type == SeaderCredentialTypeMifareClassic ||
|
||||
type == SeaderCredentialTypePicopass) {
|
||||
seader->selected_read_type = type;
|
||||
seader->detected_card_type_count = 0;
|
||||
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
|
||||
scene_manager_next_scene(seader->scene_manager, SeaderSceneRead);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
seader->selected_read_type = SeaderCredentialTypeNone;
|
||||
seader->detected_card_type_count = 0;
|
||||
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
seader->scene_manager, SeaderSceneSamPresent);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void seader_scene_read_card_type_on_exit(void* context) {
|
||||
Seader* seader = context;
|
||||
submenu_reset(seader->submenu);
|
||||
}
|
||||
@@ -33,6 +33,9 @@ Seader* seader_alloc() {
|
||||
seader->samCommand = SamCommand_PR_NOTHING;
|
||||
seader->sam_state = SeaderSamStateIdle;
|
||||
seader->sam_intent = SeaderSamIntentNone;
|
||||
memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types));
|
||||
seader->detected_card_type_count = 0;
|
||||
seader->selected_read_type = SeaderCredentialTypeNone;
|
||||
|
||||
seader->worker = seader_worker_alloc();
|
||||
seader->view_dispatcher = view_dispatcher_alloc();
|
||||
|
||||
+7
-3
@@ -62,9 +62,10 @@
|
||||
WorkerEvtCtrlLineSet | WorkerEvtSamTxComplete)
|
||||
#define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtSamRx)
|
||||
|
||||
#define SEADER_TEXT_STORE_SIZE 128
|
||||
#define SEADER_MAX_ATR_SIZE 33
|
||||
#define MAX_FRAME_HEADERS 32
|
||||
#define SEADER_TEXT_STORE_SIZE 128
|
||||
#define SEADER_MAX_ATR_SIZE 33
|
||||
#define MAX_FRAME_HEADERS 32
|
||||
#define SEADER_MAX_DETECTED_CARD_TYPES 3
|
||||
|
||||
enum SeaderCustomEvent {
|
||||
// Reserve first 100 events for button types and indexes, starting from 0
|
||||
@@ -154,6 +155,9 @@ struct Seader {
|
||||
PicopassPoller* picopass_poller;
|
||||
|
||||
NfcDevice* nfc_device;
|
||||
SeaderCredentialType detected_card_types[SEADER_MAX_DETECTED_CARD_TYPES];
|
||||
size_t detected_card_type_count;
|
||||
SeaderCredentialType selected_read_type;
|
||||
|
||||
PluginManager* plugin_manager;
|
||||
PluginWiegand* plugin_wiegand;
|
||||
|
||||
+150
-55
@@ -19,6 +19,135 @@ void seader_send_card_detected(SeaderUartBridge* seader_uart, CardDetails_t* car
|
||||
void seader_worker_reading(Seader* seader);
|
||||
void seader_worker_poller_conversation(Seader* seader, SeaderPollerContainer* spc);
|
||||
|
||||
typedef struct {
|
||||
bool done;
|
||||
bool detected;
|
||||
} SeaderPicopassDetectContext;
|
||||
|
||||
static NfcCommand
|
||||
seader_worker_picopass_detect_callback(PicopassPollerEvent event, void* context) {
|
||||
SeaderPicopassDetectContext* detect_context = context;
|
||||
|
||||
if(event.type == PicopassPollerEventTypeCardDetected ||
|
||||
event.type == PicopassPollerEventTypeSuccess) {
|
||||
detect_context->detected = true;
|
||||
detect_context->done = true;
|
||||
return NfcCommandStop;
|
||||
} else if(event.type == PicopassPollerEventTypeFail) {
|
||||
detect_context->done = true;
|
||||
return NfcCommandStop;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
static bool seader_worker_detect_picopass(Nfc* nfc) {
|
||||
bool detected = false;
|
||||
PicopassPoller* poller = picopass_poller_alloc(nfc);
|
||||
SeaderPicopassDetectContext detect_context = {0};
|
||||
|
||||
picopass_poller_start(poller, seader_worker_picopass_detect_callback, &detect_context);
|
||||
|
||||
for(uint8_t i = 0; i < 10 && !detect_context.done; i++) {
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
|
||||
detected = detect_context.detected;
|
||||
picopass_poller_stop(poller);
|
||||
picopass_poller_free(poller);
|
||||
|
||||
return detected;
|
||||
}
|
||||
|
||||
static void seader_worker_add_detected_type(
|
||||
SeaderCredentialType* detected_types,
|
||||
size_t* detected_type_count,
|
||||
SeaderCredentialType type) {
|
||||
for(size_t i = 0; i < *detected_type_count; i++) {
|
||||
if(detected_types[i] == type) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(*detected_type_count < SEADER_MAX_DETECTED_CARD_TYPES) {
|
||||
detected_types[*detected_type_count] = type;
|
||||
(*detected_type_count)++;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t seader_worker_detect_supported_types(
|
||||
Seader* seader,
|
||||
SeaderCredentialType* detected_types,
|
||||
size_t detected_capacity) {
|
||||
UNUSED(detected_capacity);
|
||||
size_t detected_type_count = 0;
|
||||
NfcPoller* poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a);
|
||||
if(nfc_poller_detect(poller_detect)) {
|
||||
seader_worker_add_detected_type(
|
||||
detected_types, &detected_type_count, SeaderCredentialType14A);
|
||||
}
|
||||
nfc_poller_free(poller_detect);
|
||||
|
||||
poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic);
|
||||
if(nfc_poller_detect(poller_detect)) {
|
||||
seader_worker_add_detected_type(
|
||||
detected_types, &detected_type_count, SeaderCredentialTypeMifareClassic);
|
||||
}
|
||||
nfc_poller_free(poller_detect);
|
||||
|
||||
if(seader_worker_detect_picopass(seader->nfc)) {
|
||||
seader_worker_add_detected_type(
|
||||
detected_types, &detected_type_count, SeaderCredentialTypePicopass);
|
||||
}
|
||||
|
||||
return detected_type_count;
|
||||
}
|
||||
|
||||
static bool seader_worker_start_read_for_type(Seader* seader, SeaderCredentialType type) {
|
||||
NfcPoller* poller_detect = NULL;
|
||||
|
||||
if(type == SeaderCredentialType14A) {
|
||||
poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a);
|
||||
if(!nfc_poller_detect(poller_detect)) {
|
||||
nfc_poller_free(poller_detect);
|
||||
return false;
|
||||
}
|
||||
FURI_LOG_I(TAG, "Detected ISO14443-4A card");
|
||||
nfc_poller_free(poller_detect);
|
||||
seader->poller = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a);
|
||||
seader->worker->stage = SeaderPollerEventTypeCardDetect;
|
||||
seader->credential->type = SeaderCredentialType14A;
|
||||
nfc_poller_start(seader->poller, seader_worker_poller_callback_iso14443_4a, seader);
|
||||
return true;
|
||||
} else if(type == SeaderCredentialTypeMifareClassic) {
|
||||
poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic);
|
||||
if(!nfc_poller_detect(poller_detect)) {
|
||||
nfc_poller_free(poller_detect);
|
||||
return false;
|
||||
}
|
||||
FURI_LOG_I(TAG, "Detected Mifare Classic card");
|
||||
nfc_poller_free(poller_detect);
|
||||
seader->poller = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic);
|
||||
seader->worker->stage = SeaderPollerEventTypeCardDetect;
|
||||
seader->credential->type = SeaderCredentialTypeMifareClassic;
|
||||
nfc_poller_start(seader->poller, seader_worker_poller_callback_mfc, seader);
|
||||
return true;
|
||||
} else if(type == SeaderCredentialTypePicopass) {
|
||||
if(!seader_worker_detect_picopass(seader->nfc)) {
|
||||
return false;
|
||||
}
|
||||
FURI_LOG_I(TAG, "Detected Picopass card");
|
||||
seader->picopass_poller = picopass_poller_alloc(seader->nfc);
|
||||
seader->worker->stage = SeaderPollerEventTypeCardDetect;
|
||||
seader->credential->type = SeaderCredentialTypePicopass;
|
||||
picopass_poller_start(
|
||||
seader->picopass_poller, seader_worker_poller_callback_picopass, seader);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/***************************** Seader Worker API *******************************/
|
||||
|
||||
SeaderWorker* seader_worker_alloc() {
|
||||
@@ -307,67 +436,33 @@ void seader_worker_reading(Seader* seader) {
|
||||
while(seader_worker->state == SeaderWorkerStateReading) {
|
||||
bool detected = false;
|
||||
SeaderPollerEventType result_stage = SeaderPollerEventTypeFail;
|
||||
SeaderCredentialType type_to_read = seader->selected_read_type;
|
||||
|
||||
// 1. HF Discovery (ISO14443-4A)
|
||||
NfcPoller* poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a);
|
||||
if(nfc_poller_detect(poller_detect)) {
|
||||
FURI_LOG_I(TAG, "Detected ISO14443-4A card");
|
||||
nfc_poller_free(poller_detect);
|
||||
poller_detect = NULL;
|
||||
if(type_to_read == SeaderCredentialTypeNone) {
|
||||
SeaderCredentialType detected_types[SEADER_MAX_DETECTED_CARD_TYPES] = {0};
|
||||
const size_t detected_type_count = seader_worker_detect_supported_types(
|
||||
seader, detected_types, COUNT_OF(detected_types));
|
||||
|
||||
seader->poller = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a);
|
||||
seader_worker->stage = SeaderPollerEventTypeCardDetect;
|
||||
seader->credential->type = SeaderCredentialType14A;
|
||||
nfc_poller_start(seader->poller, seader_worker_poller_callback_iso14443_4a, seader);
|
||||
detected = true;
|
||||
} else {
|
||||
nfc_poller_free(poller_detect);
|
||||
poller_detect = NULL;
|
||||
}
|
||||
|
||||
// 2. HF Discovery (Mifare Classic)
|
||||
if(!detected) {
|
||||
poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic);
|
||||
if(nfc_poller_detect(poller_detect)) {
|
||||
FURI_LOG_I(TAG, "Detected Mifare Classic card");
|
||||
nfc_poller_free(poller_detect);
|
||||
poller_detect = NULL;
|
||||
|
||||
seader->poller = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic);
|
||||
seader_worker->stage = SeaderPollerEventTypeCardDetect;
|
||||
seader->credential->type = SeaderCredentialTypeMifareClassic;
|
||||
nfc_poller_start(seader->poller, seader_worker_poller_callback_mfc, seader);
|
||||
detected = true;
|
||||
} else {
|
||||
nfc_poller_free(poller_detect);
|
||||
poller_detect = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. HF Discovery (Picopass/iCLASS)
|
||||
if(!detected) {
|
||||
seader->picopass_poller = picopass_poller_alloc(seader->nfc);
|
||||
seader_worker->stage = SeaderPollerEventTypeCardDetect;
|
||||
seader->credential->type = SeaderCredentialTypePicopass;
|
||||
|
||||
picopass_poller_start(
|
||||
seader->picopass_poller, seader_worker_poller_callback_picopass, seader);
|
||||
|
||||
// Wait up to 100ms for picopass detection
|
||||
for(int i = 0; i < 10; i++) {
|
||||
if(seader_worker->stage != SeaderPollerEventTypeCardDetect) {
|
||||
detected = true;
|
||||
break;
|
||||
if(detected_type_count > 1) {
|
||||
memcpy(
|
||||
seader->detected_card_types,
|
||||
detected_types,
|
||||
sizeof(seader->detected_card_types));
|
||||
seader->detected_card_type_count = detected_type_count;
|
||||
if(seader_worker->callback) {
|
||||
seader_worker->callback(
|
||||
SeaderWorkerEventSelectCardType, seader_worker->context);
|
||||
}
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
if(!detected) {
|
||||
picopass_poller_stop(seader->picopass_poller);
|
||||
picopass_poller_free(seader->picopass_poller);
|
||||
seader->picopass_poller = NULL;
|
||||
break;
|
||||
} else if(detected_type_count == 1) {
|
||||
type_to_read = detected_types[0];
|
||||
}
|
||||
}
|
||||
|
||||
if(type_to_read != SeaderCredentialTypeNone) {
|
||||
detected = seader_worker_start_read_for_type(seader, type_to_read);
|
||||
}
|
||||
|
||||
if(detected) {
|
||||
// Wait for conversation to finish
|
||||
while(seader_worker->stage != SeaderPollerEventTypeComplete &&
|
||||
|
||||
@@ -38,6 +38,7 @@ typedef enum {
|
||||
SeaderWorkerEventSamMissing,
|
||||
SeaderWorkerEventNoCardDetected,
|
||||
SeaderWorkerEventStartReading,
|
||||
SeaderWorkerEventSelectCardType,
|
||||
SeaderWorkerEventAPDURunnerUpdate,
|
||||
SeaderWorkerEventAPDURunnerSuccess,
|
||||
SeaderWorkerEventAPDURunnerError,
|
||||
|
||||
Reference in New Issue
Block a user