Skip to content

Commit 3c2f31b

Browse files
committed
Added base RFID using PSCS protocol
1 parent ce6c3e8 commit 3c2f31b

File tree

10 files changed

+315
-90
lines changed

10 files changed

+315
-90
lines changed

client/lib/utils/pcsc_scanner.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export 'pcsc_scanner_stub.dart'
2+
if (dart.library.io) 'pcsc_scanner_native.dart'
3+
if (dart.library.js_interop) 'pcsc_scanner_web.dart';
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import 'dart:async';
2+
import 'dart:typed_data';
3+
import 'package:async/async.dart';
4+
import 'package:dart_pcsc/dart_pcsc.dart';
5+
import 'package:logger/logger.dart';
6+
7+
final _log = Logger();
8+
9+
String _hexColon(List<int> bytes) => bytes
10+
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
11+
.join(':');
12+
13+
class PcscScanner {
14+
final void Function(String scannedUid) onScan;
15+
final void Function(String message)? onError;
16+
17+
bool _running = false;
18+
Context? _context;
19+
CancelableOperation<List<String>>? _pendingWait;
20+
21+
/// APDU command to read UID from most ISO 14443 cards.
22+
static final _getUidCommand = Uint8List.fromList([
23+
0xFF,
24+
0xCA,
25+
0x00,
26+
0x00,
27+
0x00,
28+
]);
29+
30+
/// Debounce: ignore the same UID within this window.
31+
static const _debounceDuration = Duration(seconds: 3);
32+
String? _lastUid;
33+
DateTime? _lastScanTime;
34+
35+
PcscScanner({required this.onScan, this.onError});
36+
37+
Future<void> start() async {
38+
_running = true;
39+
_log.i('[PCSC] Scanner starting');
40+
41+
while (_running) {
42+
try {
43+
await _scanLoop();
44+
} catch (e) {
45+
if (!_running) break;
46+
_log.e('[PCSC] Scan loop error, retrying in 2s: $e');
47+
await Future<void>.delayed(const Duration(seconds: 2));
48+
}
49+
}
50+
}
51+
52+
Future<void> _scanLoop() async {
53+
final context = Context(Scope.user);
54+
_context = context;
55+
56+
try {
57+
await context.establish();
58+
59+
final readers = await context.listReaders();
60+
if (readers.isEmpty) {
61+
_log.w('[PCSC] No readers found, retrying in 3s');
62+
await context.release();
63+
_context = null;
64+
await Future<void>.delayed(const Duration(seconds: 3));
65+
return;
66+
}
67+
68+
_log.i('[PCSC] Using reader: "${readers.first}"');
69+
final reader = readers.first;
70+
71+
while (_running) {
72+
final waitOp = context.waitForCard([reader]);
73+
_pendingWait = waitOp;
74+
final readersWithCard = await waitOp.valueOrCancellation(null);
75+
_pendingWait = null;
76+
77+
if (readersWithCard == null || !_running) break;
78+
79+
Card? card;
80+
try {
81+
card = await context.connect(
82+
readersWithCard.first,
83+
ShareMode.shared,
84+
Protocol.any,
85+
);
86+
87+
final response = await card.transmit(_getUidCommand);
88+
89+
if (response.length >= 2) {
90+
final sw1 = response[response.length - 2];
91+
final sw2 = response[response.length - 1];
92+
final uid = response.sublist(0, response.length - 2);
93+
94+
if (sw1 == 0x90 && sw2 == 0x00 && uid.isNotEmpty) {
95+
final hexUid = _hexColon(uid);
96+
97+
_log.i('[PCSC] Card UID: $hexUid (${uid.length} bytes)');
98+
99+
// Debounce: skip if same card scanned within window
100+
final now = DateTime.now();
101+
if (hexUid != _lastUid ||
102+
_lastScanTime == null ||
103+
now.difference(_lastScanTime!) > _debounceDuration) {
104+
_lastUid = hexUid;
105+
_lastScanTime = now;
106+
onScan(hexUid);
107+
}
108+
} else {
109+
_log.w(
110+
'[PCSC] Card error status: '
111+
'${sw1.toRadixString(16).padLeft(2, '0')} '
112+
'${sw2.toRadixString(16).padLeft(2, '0')}',
113+
);
114+
}
115+
}
116+
} catch (e) {
117+
_log.e('[PCSC] Error reading card: $e');
118+
onError?.call('Could not read card. Please try again.');
119+
} finally {
120+
if (card != null) {
121+
try {
122+
await card.disconnect(Disposition.resetCard);
123+
} catch (_) {}
124+
}
125+
}
126+
127+
if (_running) {
128+
await Future<void>.delayed(const Duration(seconds: 2));
129+
}
130+
}
131+
} finally {
132+
try {
133+
await context.release();
134+
} catch (_) {}
135+
_context = null;
136+
}
137+
}
138+
139+
void dispose() {
140+
_running = false;
141+
_pendingWait?.cancel();
142+
_pendingWait = null;
143+
final ctx = _context;
144+
if (ctx != null) {
145+
ctx.release().catchError((_) {});
146+
_context = null;
147+
}
148+
}
149+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import 'package:logger/logger.dart';
2+
3+
final _log = Logger();
4+
5+
class PcscScanner {
6+
final void Function(String scannedUid) onScan;
7+
final void Function(String message)? onError;
8+
9+
PcscScanner({required this.onScan, this.onError});
10+
11+
Future<void> start() async {
12+
_log.w('PCSC scanning is not supported on this platform');
13+
}
14+
15+
void dispose() {}
16+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export 'pcsc_scanner_stub.dart';

client/lib/utils/rfid_scanner.dart

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)