Files
nfc-tool/src/main.rs
T

693 lines
29 KiB
Rust

#![windows_subsystem = "windows"]
mod acr122u;
mod mifare;
mod ndef;
mod ntag;
use acr122u::{Acr122u, CardInfo, LedBuzzerConfig};
use eframe::egui;
use mifare::{MifareDump, MifareHandler};
use ndef::NdefCodec;
use ntag::NtagHandler;
use pcsc::Context;
use std::fs;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
pub enum CardEvent {
ReaderStatus { connected: bool, name: String },
CardDetected(CardInfo),
CardRemoved,
}
pub struct NfcApp {
rx: Receiver<CardEvent>,
reader_name: String,
is_reader_connected: bool,
current_card: Option<CardInfo>,
event_logs: Vec<String>,
active_tab: Tab,
// LED / Beep state
led_red: bool,
led_green: bool,
led_blink: bool,
led_beep: bool,
// MIFARE state
mifare_dump: Option<MifareDump>,
mifare_status: String,
// NDEF & Text State
ndef_subtab: NdefSubTab,
ndef_url_input: String,
ndef_text_input: String,
ndef_text_lang: String,
wifi_ssid: String,
wifi_pass: String,
vcard_name: String,
vcard_phone: String,
vcard_email: String,
vcard_company: String,
geo_url: String,
aar_package: String,
ndef_status: String,
// NTAG Password State
pwd_input: String,
pack_input: String,
pwd_status: String,
// APDU State
apdu_input: String,
apdu_response_log: Vec<String>,
pcsc_ctx: Arc<Mutex<Context>>,
is_busy: Arc<AtomicBool>,
}
#[derive(PartialEq)]
enum Tab {
Scanner,
Ndef,
Mifare,
Password,
Hardware,
ExpertApdu,
}
#[derive(PartialEq)]
enum NdefSubTab {
Text,
Url,
Wifi,
VCard,
Geo,
AppLauncher,
}
impl NfcApp {
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
setup_edel_theme(&cc.egui_ctx);
let (tx, rx) = channel();
let ctx = Acr122u::create_context().expect("PC/SC Context konnte nicht erstellt werden!");
let pcsc_ctx = Arc::new(Mutex::new(ctx));
let is_busy = Arc::new(AtomicBool::new(false));
let poll_ctx = pcsc_ctx.clone();
let poll_busy = is_busy.clone();
thread::spawn(move || {
poll_loop(tx, poll_ctx, poll_busy);
});
Self {
rx,
reader_name: "Kein Lesegerät angeschlossen".to_string(),
is_reader_connected: false,
current_card: None,
event_logs: vec!["System bereit. Bitte lege einen NFC-Tag auf das Gerät...".to_string()],
active_tab: Tab::Scanner,
led_red: false,
led_green: true,
led_blink: false,
led_beep: true,
mifare_dump: None,
mifare_status: "Klicke auf 'Karte Auslesen', um alle Speichersektoren zu scannen.".to_string(),
ndef_subtab: NdefSubTab::Text,
ndef_url_input: "https://example.com".to_string(),
ndef_text_input: "Hallo! Das ist meine persönliche Notiz auf dem NFC-Tag.".to_string(),
ndef_text_lang: "de".to_string(),
wifi_ssid: "MeinWLAN".to_string(),
wifi_pass: "MeinPasswort123".to_string(),
vcard_name: "Max Mustermann".to_string(),
vcard_phone: "+49 170 1234567".to_string(),
vcard_email: "max@beispiel.de".to_string(),
vcard_company: "Meine Firma GmbH".to_string(),
geo_url: "https://maps.google.com/?q=Berlin".to_string(),
aar_package: "com.spotify.music".to_string(),
ndef_status: "Bereit zum Beschreiben".to_string(),
pwd_input: "1234".to_string(),
pack_input: "0000".to_string(),
pwd_status: "Gib ein 4-stelliges Passwort ein, um den Tag zu schützen.".to_string(),
apdu_input: "FF 00 48 00 00".to_string(),
apdu_response_log: Vec::new(),
pcsc_ctx,
is_busy,
}
}
}
fn setup_edel_theme(ctx: &egui::Context) {
let mut style = (*ctx.style()).clone();
let mut visuals = egui::Visuals::dark();
visuals.panel_fill = egui::Color32::from_rgb(10, 13, 20);
visuals.window_fill = egui::Color32::from_rgb(19, 25, 39);
visuals.extreme_bg_color = egui::Color32::from_rgb(6, 8, 14);
visuals.widgets.noninteractive.rounding = egui::Rounding::same(10.0);
visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(26, 34, 50);
visuals.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0_f32, egui::Color32::from_rgb(45, 58, 82));
visuals.widgets.inactive.rounding = egui::Rounding::same(10.0);
visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(26, 34, 50);
visuals.widgets.hovered.rounding = egui::Rounding::same(10.0);
visuals.widgets.hovered.bg_fill = egui::Color32::from_rgb(45, 58, 82);
visuals.widgets.hovered.bg_stroke = egui::Stroke::new(1.5_f32, egui::Color32::from_rgb(0, 242, 254));
visuals.widgets.active.rounding = egui::Rounding::same(10.0);
visuals.widgets.active.bg_fill = egui::Color32::from_rgb(0, 242, 254);
visuals.widgets.active.fg_stroke = egui::Stroke::new(1.0_f32, egui::Color32::BLACK);
visuals.selection.bg_fill = egui::Color32::from_rgba_premultiplied(0, 242, 254, 70);
style.visuals = visuals;
style.spacing.item_spacing = egui::vec2(12.0, 12.0);
style.spacing.button_padding = egui::vec2(16.0, 10.0);
ctx.set_style(style);
}
fn poll_loop(tx: Sender<CardEvent>, ctx_store: Arc<Mutex<Context>>, is_busy: Arc<AtomicBool>) {
let mut last_uid = String::new();
loop {
thread::sleep(Duration::from_millis(600));
if is_busy.load(Ordering::SeqCst) {
continue;
}
let ctx = ctx_store.lock().unwrap();
let reader = match Acr122u::find_acr122u(&ctx) {
Ok(r) => {
let _ = tx.send(CardEvent::ReaderStatus {
connected: true,
name: r.clone(),
});
r
}
Err(_) => {
let _ = tx.send(CardEvent::ReaderStatus {
connected: false,
name: "Kein Lesegerät".to_string(),
});
last_uid.clear();
let _ = tx.send(CardEvent::CardRemoved);
continue;
}
};
match Acr122u::read_card_info(&ctx, &reader) {
Ok(info) => {
if info.uid != last_uid {
last_uid = info.uid.clone();
let _ = Acr122u::beep(&ctx, &reader, 50, 1);
let _ = tx.send(CardEvent::CardDetected(info));
}
}
Err(_) => {
if !last_uid.is_empty() {
last_uid.clear();
let _ = tx.send(CardEvent::CardRemoved);
}
}
}
}
}
impl eframe::App for NfcApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
while let Ok(evt) = self.rx.try_recv() {
let timestamp = chrono::Local::now().format("%H:%M:%S").to_string();
match evt {
CardEvent::ReaderStatus { connected, name } => {
self.is_reader_connected = connected;
self.reader_name = name;
}
CardEvent::CardDetected(info) => {
let friendly_type = match info.uid_bytes.len() {
7 => "Standard NFC-Sticker / Tag (NTAG)",
4 => "MIFARE Chipkarte / Schlüsselanhänger",
_ => "Kontaktloser NFC-Chip",
};
self.event_logs.insert(0, format!("[{}] 💳 NFC-Tag erkannt: ID {} ({})", timestamp, info.uid, friendly_type));
self.current_card = Some(info);
}
CardEvent::CardRemoved => {
if self.current_card.is_some() {
self.event_logs.insert(0, format!("[{}] 🚫 NFC-Tag entfernt", timestamp));
self.current_card = None;
}
}
}
}
ctx.request_repaint_after(Duration::from_millis(200));
egui::TopBottomPanel::top("top_header").show(ctx, |ui| {
ui.add_space(8.0);
ui.horizontal(|ui| {
ui.label(egui::RichText::new("💎 ACR122U NFC Studio Pro").font(egui::FontId::proportional(22.0)).strong().color(egui::Color32::from_rgb(0, 242, 254)));
ui.label(egui::RichText::new("Premium Edition").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184)));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if self.is_reader_connected {
ui.colored_label(egui::Color32::from_rgb(0, 230, 153), format!("🟢 {}", self.reader_name));
} else {
ui.colored_label(egui::Color32::from_rgb(255, 75, 87), "🔴 Gerät nicht verbunden");
}
});
});
ui.add_space(8.0);
});
egui::TopBottomPanel::top("tab_bar").show(ctx, |ui| {
ui.horizontal(|ui| {
ui.selectable_value(&mut self.active_tab, Tab::Scanner, "📡 Live-Erkennung");
ui.selectable_value(&mut self.active_tab, Tab::Ndef, "✏️ NFC-Tag Beschreiben");
ui.selectable_value(&mut self.active_tab, Tab::Mifare, "🔍 Speicher & Klonen");
ui.selectable_value(&mut self.active_tab, Tab::Password, "🔒 Passwort-Schutz");
ui.selectable_value(&mut self.active_tab, Tab::Hardware, "🔔 LEDs & Töne");
ui.selectable_value(&mut self.active_tab, Tab::ExpertApdu, "🛠️ Experten-Befehle");
});
});
egui::CentralPanel::default().show(ctx, |ui| {
match self.active_tab {
Tab::Scanner => self.render_scanner_tab(ui),
Tab::Ndef => self.render_ndef_tab(ui),
Tab::Mifare => self.render_mifare_tab(ui),
Tab::Password => self.render_password_tab(ui),
Tab::Hardware => self.render_hardware_tab(ui),
Tab::ExpertApdu => self.render_apdu_tab(ui),
}
});
}
}
impl NfcApp {
fn render_scanner_tab(&mut self, ui: &mut egui::Ui) {
ui.columns(2, |cols| {
cols[0].group(|ui| {
ui.heading("💳 Aufgelegter NFC-Tag");
ui.label(egui::RichText::new("Hier siehst du sofort alle Infos zur erkannten Karte.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184)));
ui.add_space(8.0);
egui::Frame::none()
.fill(egui::Color32::from_rgb(19, 25, 39))
.stroke(egui::Stroke::new(1.5_f32, egui::Color32::from_rgb(0, 242, 254)))
.rounding(16.0)
.inner_margin(20.0)
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.label(egui::RichText::new("📇 NFC-CHIP").font(egui::FontId::proportional(14.0)).strong().color(egui::Color32::from_rgb(246, 211, 101)));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(egui::RichText::new("Kontaktlos").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(0, 242, 254)));
});
});
ui.add_space(16.0);
ui.label(egui::RichText::new("EINDEUTIGE CHIP-ID (UID):").font(egui::FontId::proportional(11.0)).color(egui::Color32::from_rgb(148, 163, 184)));
if let Some(card) = &self.current_card {
ui.label(egui::RichText::new(&card.uid).font(egui::FontId::monospace(26.0)).strong().color(egui::Color32::from_rgb(246, 211, 101)));
ui.add_space(12.0);
let friendly_type = match card.uid_bytes.len() {
7 => "Standard NFC-Sticker / Tag (NTAG213/215/216)",
4 => "MIFARE Classic Chipkarte / Schlüsselanhänger",
_ => "Kontaktloser NFC-Chip",
};
ui.label(format!("Chip-Typ: {}", friendly_type));
} else {
ui.label(egui::RichText::new("-- -- -- --").font(egui::FontId::monospace(26.0)).color(egui::Color32::GRAY));
ui.add_space(12.0);
ui.colored_label(egui::Color32::GRAY, "Bitte halte einen NFC-Tag auf das Gerät...");
}
});
});
cols[1].group(|ui| {
ui.heading("📜 Echtzeit Aktivitäten-Log");
ui.label(egui::RichText::new("Verlauf der Erfassungen und Aktionen.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184)));
ui.add_space(6.0);
egui::ScrollArea::vertical().max_height(350.0).show(ui, |ui| {
for log in &self.event_logs {
ui.label(egui::RichText::new(log).font(egui::FontId::monospace(13.0)));
}
});
});
});
}
fn render_ndef_tab(&mut self, ui: &mut egui::Ui) {
ui.heading("✏️ NFC-Tag Beschreiben");
ui.label(egui::RichText::new("Wähle aus, was beim Dranhalten eines Smartphones passieren soll:").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184)));
ui.add_space(8.0);
ui.horizontal(|ui| {
ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::Text, "📝 Text & Notiz");
ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::Url, "🌐 Internet-Link (URL)");
ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::Wifi, "📶 WLAN Quick-Connect");
ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::VCard, "🎴 Visitenkarte");
ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::Geo, "🗺️ Standort / Maps");
ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::AppLauncher, "📱 App Öffnen");
});
ui.add_space(12.0);
let mut tlv_payload: Option<Vec<u8>> = None;
match self.ndef_subtab {
NdefSubTab::Text => {
ui.group(|ui| {
ui.label("Deine Nachricht / Notiz:");
ui.add(egui::TextEdit::multiline(&mut self.ndef_text_input).desired_rows(4).desired_width(f32::INFINITY));
ui.horizontal(|ui| {
if ui.button("💾 Text als Datei speichern (.txt)").clicked() {
if let Some(path) = rfd::FileDialog::new().add_filter("Textdatei", &["txt"]).save_file() {
let _ = fs::write(path, &self.ndef_text_input);
self.ndef_status = "Text erfolgreich auf dem PC gespeichert!".to_string();
}
}
if ui.button("📂 Text aus Datei laden (.txt)").clicked() {
if let Some(path) = rfd::FileDialog::new().add_filter("Textdatei", &["txt"]).pick_file() {
if let Ok(content) = fs::read_to_string(path) {
self.ndef_text_input = content;
self.ndef_status = "Text aus Datei geladen!".to_string();
}
}
}
});
tlv_payload = Some(NdefCodec::encode_text(&self.ndef_text_input, &self.ndef_text_lang));
});
}
NdefSubTab::Url => {
ui.group(|ui| {
ui.label("Internet-Adresse (öffnet sich beim Scannen automatisch):");
ui.text_edit_singleline(&mut self.ndef_url_input);
tlv_payload = Some(NdefCodec::encode_url(&self.ndef_url_input));
});
}
NdefSubTab::Wifi => {
ui.group(|ui| {
ui.label("WLAN Netz-Name (SSID):");
ui.text_edit_singleline(&mut self.wifi_ssid);
ui.label("WLAN Passwort:");
ui.text_edit_singleline(&mut self.wifi_pass);
ui.label(egui::RichText::new("💡 Smartphones verbinden sich beim Dranhalten direkt mit dem WLAN.").font(egui::FontId::proportional(11.0)).color(egui::Color32::from_rgb(148, 163, 184)));
tlv_payload = Some(NdefCodec::encode_wifi(&self.wifi_ssid, &self.wifi_pass));
});
}
NdefSubTab::VCard => {
ui.group(|ui| {
ui.label("Name:");
ui.text_edit_singleline(&mut self.vcard_name);
ui.label("Telefonnummer:");
ui.text_edit_singleline(&mut self.vcard_phone);
ui.label("E-Mail:");
ui.text_edit_singleline(&mut self.vcard_email);
ui.label("Firma / Organisation:");
ui.text_edit_singleline(&mut self.vcard_company);
ui.label(egui::RichText::new("💡 Beim Scannen wird der Kontakt direkt in das Handy-Telefonbuch gespeichert.").font(egui::FontId::proportional(11.0)).color(egui::Color32::from_rgb(148, 163, 184)));
tlv_payload = Some(NdefCodec::encode_vcard(&self.vcard_name, &self.vcard_phone, &self.vcard_email, &self.vcard_company));
});
}
NdefSubTab::Geo => {
ui.group(|ui| {
ui.label("Google Maps Standorts-Link:");
ui.text_edit_singleline(&mut self.geo_url);
tlv_payload = Some(NdefCodec::encode_url(&self.geo_url));
});
}
NdefSubTab::AppLauncher => {
ui.group(|ui| {
ui.label("Android App-Name (z. B. com.spotify.music):");
ui.text_edit_singleline(&mut self.aar_package);
tlv_payload = Some(NdefCodec::encode_aar(&self.aar_package));
});
}
}
ui.add_space(15.0);
if ui.button("⚡ Auf NFC-Tag Schreiben").clicked() {
if let Some(tlv) = tlv_payload {
self.is_busy.store(true, Ordering::SeqCst);
thread::sleep(Duration::from_millis(50));
let pcsc = self.pcsc_ctx.lock().unwrap();
if let Ok(reader) = Acr122u::find_acr122u(&pcsc) {
if let Ok(mut card) = Acr122u::connect_card_with_ctx(&pcsc, &reader) {
let _ = NtagHandler::ensure_cc_header(&pcsc, &reader, &mut card);
let mut page = 4;
let mut err = false;
for chunk in tlv.chunks(4) {
let mut p_data = [0u8; 4];
p_data[..chunk.len()].copy_from_slice(chunk);
if NtagHandler::write_page(&pcsc, &reader, &mut card, page, &p_data).is_err() {
err = true;
self.ndef_status = format!("Schreibfehler auf Seite {}", page);
break;
}
page += 1;
}
if !err {
let _ = Acr122u::beep(&pcsc, &reader, 100, 2);
self.ndef_status = "✅ Inhalt erfolgreich auf den NFC-Tag geschrieben!".to_string();
}
} else {
self.ndef_status = "❌ Karte konnte nicht verbunden werden!".to_string();
}
}
self.is_busy.store(false, Ordering::SeqCst);
}
}
ui.add_space(10.0);
ui.colored_label(egui::Color32::from_rgb(0, 242, 254), &self.ndef_status);
}
fn render_mifare_tab(&mut self, ui: &mut egui::Ui) {
ui.heading("🔍 Speicher Auslesen & Klonen");
ui.label(egui::RichText::new("Lies den kompletten Speicherinhalt von MIFARE-Karten aus und speichere ihn ab.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184)));
ui.add_space(10.0);
ui.horizontal(|ui| {
if ui.button("🔄 Karte Auslesen").clicked() {
self.is_busy.store(true, Ordering::SeqCst);
thread::sleep(Duration::from_millis(50));
let pcsc = self.pcsc_ctx.lock().unwrap();
if let Ok(reader) = Acr122u::find_acr122u(&pcsc) {
match MifareHandler::dump_card(&pcsc, &reader) {
Ok(dump) => {
self.mifare_dump = Some(dump);
self.mifare_status = "Auslesen erfolgreich!".to_string();
}
Err(e) => {
self.mifare_status = format!("Fehler beim Auslesen: {}", e);
}
}
}
self.is_busy.store(false, Ordering::SeqCst);
}
if let Some(dump) = &self.mifare_dump {
if ui.button("💾 Als Backup speichern (.json)").clicked() {
if let Some(path) = rfd::FileDialog::new().add_filter("JSON Backup", &["json"]).save_file() {
if let Ok(json) = serde_json::to_string_pretty(dump) {
let _ = fs::write(path, json);
self.mifare_status = "Backup-Datei gespeichert!".to_string();
}
}
}
}
if ui.button("📂 Backup aus Datei laden").clicked() {
if let Some(path) = rfd::FileDialog::new().add_filter("JSON Backup", &["json"]).pick_file() {
if let Ok(content) = fs::read_to_string(path) {
if let Ok(loaded_dump) = serde_json::from_str::<MifareDump>(&content) {
self.mifare_dump = Some(loaded_dump);
self.mifare_status = "Backup geladen!".to_string();
}
}
}
}
});
ui.colored_label(egui::Color32::from_rgb(0, 242, 254), &self.mifare_status);
ui.add_space(10.0);
if let Some(dump) = &self.mifare_dump {
egui::ScrollArea::vertical().show(ui, |ui| {
for sec in &dump.sectors {
ui.group(|ui| {
ui.label(egui::RichText::new(format!("Speicherblock {} (Key: {})", sec.sector_number, sec.key_a.as_deref().unwrap_or("Schlüsselgeschützt"))).strong());
for b in &sec.blocks {
let text_color = if b.is_trailer { egui::Color32::from_rgb(214, 188, 250) } else { egui::Color32::WHITE };
ui.label(egui::RichText::new(format!("Block {:02}: {} | {}", b.block_number, b.hex_data, b.ascii_data)).font(egui::FontId::monospace(12.0)).color(text_color));
}
});
}
});
}
}
fn render_password_tab(&mut self, ui: &mut egui::Ui) {
ui.heading("🔒 Passwort-Schutz & Tag Versiegeln");
ui.label(egui::RichText::new("Schütze deinen NFC-Tag vor unbefugtem Überschreiben.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184)));
ui.add_space(10.0);
ui.group(|ui| {
ui.label("4-stelliges Passwort (z. B. 1234):");
ui.text_edit_singleline(&mut self.pwd_input);
ui.add_space(10.0);
if ui.button("🔐 Passwort auf Tag aktivieren").clicked() {
self.is_busy.store(true, Ordering::SeqCst);
thread::sleep(Duration::from_millis(50));
let pcsc = self.pcsc_ctx.lock().unwrap();
if let Ok(reader) = Acr122u::find_acr122u(&pcsc) {
if let Ok(mut card) = Acr122u::connect_card_with_ctx(&pcsc, &reader) {
let pwd_bytes = self.pwd_input.as_bytes();
if pwd_bytes.len() == 4 {
let mut p_arr = [0u8; 4];
p_arr.copy_from_slice(pwd_bytes);
if NtagHandler::write_page(&pcsc, &reader, &mut card, 134, &p_arr).is_ok() {
self.pwd_status = "✅ Passwort erfolgreich auf dem Tag aktiviert!".to_string();
let _ = Acr122u::beep(&pcsc, &reader, 100, 2);
} else {
self.pwd_status = "❌ Fehler beim Aktivieren des Passworts!".to_string();
}
} else {
self.pwd_status = "Das Passwort muss genau 4 Zeichen lang sein!".to_string();
}
}
}
self.is_busy.store(false, Ordering::SeqCst);
}
});
ui.add_space(15.0);
ui.colored_label(egui::Color32::from_rgb(0, 242, 254), &self.pwd_status);
}
fn render_hardware_tab(&mut self, ui: &mut egui::Ui) {
ui.heading("🔔 LEDs & Signal-Töne des Lesegeräts");
ui.label(egui::RichText::new("Steuere die Hardware-Signale deines ACR122U Geräts.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184)));
ui.add_space(10.0);
ui.checkbox(&mut self.led_red, "Rote LED aktivieren");
ui.checkbox(&mut self.led_green, "Grüne LED aktivieren");
ui.checkbox(&mut self.led_blink, "Blinken aktivieren");
ui.checkbox(&mut self.led_beep, "Ton beim Schalten abspielen");
ui.add_space(12.0);
if ui.button("⚡ LED & Ton-Einstellungen Anwenden").clicked() {
let pcsc = self.pcsc_ctx.lock().unwrap();
let config = LedBuzzerConfig {
red: self.led_red,
green: self.led_green,
blink_red: self.led_blink,
blink_green: self.led_blink,
blink_count: if self.led_blink { 4 } else { 0 },
t1_duration_ms: 100,
t2_duration_ms: 100,
buzzer_mode: if self.led_beep { 1 } else { 0 },
};
if let Ok(reader) = Acr122u::find_acr122u(&pcsc) {
let _ = Acr122u::set_led_buzzer(&pcsc, &reader, &config);
}
}
ui.add_space(20.0);
ui.label("Signalton testen:");
ui.horizontal(|ui| {
if ui.button("🔊 1x Signalton").clicked() {
let pcsc = self.pcsc_ctx.lock().unwrap();
if let Ok(reader) = Acr122u::find_acr122u(&pcsc) {
let _ = Acr122u::beep(&pcsc, &reader, 100, 1);
}
}
if ui.button("🔊 2x Bestätigungston").clicked() {
let pcsc = self.pcsc_ctx.lock().unwrap();
if let Ok(reader) = Acr122u::find_acr122u(&pcsc) {
let _ = Acr122u::beep(&pcsc, &reader, 100, 2);
}
}
});
}
fn render_apdu_tab(&mut self, ui: &mut egui::Ui) {
ui.heading("🛠️ Experten-Befehle (Rohdaten / APDU)");
ui.label(egui::RichText::new("Manuelle Befehlseingabe für fortgeschrittene Nutzer.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184)));
ui.add_space(10.0);
ui.label("Befehl in Hexadezimal (z. B. FF 00 48 00 00):");
ui.text_edit_singleline(&mut self.apdu_input);
if ui.button("Befehl Senden").clicked() {
let pcsc = self.pcsc_ctx.lock().unwrap();
if let Ok(reader) = Acr122u::find_acr122u(&pcsc) {
if let Ok(card) = Acr122u::connect_card_with_ctx(&pcsc, &reader).or_else(|_| Acr122u::connect_direct_with_ctx(&pcsc, &reader)) {
if let Ok(bytes) = hex::decode(self.apdu_input.replace(" ", "")) {
match Acr122u::transmit(&card, &bytes) {
Ok((data, sw1, sw2)) => {
let entry = format!("> {}\n< DATEN: {}\n< STATUS: {:02X}{:02X}", self.apdu_input, hex::encode_upper(&data), sw1, sw2);
self.apdu_response_log.insert(0, entry);
}
Err(e) => {
self.apdu_response_log.insert(0, format!("❌ FEHLER: {}", e));
}
}
}
}
}
}
ui.add_space(15.0);
egui::ScrollArea::vertical().show(ui, |ui| {
for log in &self.apdu_response_log {
ui.label(egui::RichText::new(log).font(egui::FontId::monospace(13.0)));
ui.separator();
}
});
}
}
fn main() -> eframe::Result<()> {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_title("ACR122U NFC Studio Pro (Transparent Reconnect)")
.with_inner_size([1150.0, 780.0]),
..Default::default()
};
eframe::run_native(
"nfc_tool",
options,
Box::new(|cc| Ok(Box::new(NfcApp::new(cc)))),
)
}