Skip to content

Commit 6f9a316

Browse files
committed
Tweaked rfid scanner to support emulator & pcsc protocol
1 parent ea3a347 commit 6f9a316

File tree

5 files changed

+229
-6
lines changed

5 files changed

+229
-6
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# TimeKeeper
2+
3+
A time tracking and sign-in management system built for FRC (FIRST Robotics Competition) teams. Track team member attendance across sessions and locations with support for RFID check-in/out, Discord integration, and multi-platform clients.
4+
5+
## Features
6+
7+
- **Session Management** - Create and manage timed sessions at configurable locations
8+
- **RFID Check-In/Out** - Supports both PCSC smart card readers and keyboard-emulating RFID readers
9+
- **Kiosk Mode** - Dedicated fullscreen mode for check-in stations
10+
- **Discord Bot** - Session reminders, attendance commands, leaderboards, and member name syncing
11+
- **Schedule Import** - Import sessions from CSV or ICS (iCalendar) files
12+
- **Multi-Platform Clients** - Desktop (Linux, Windows, macOS), Android, and Web
13+
- **Real-Time Sync** - gRPC streaming keeps all connected clients in sync
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import 'package:flutter/services.dart';
2+
import 'package:flutter_hooks/flutter_hooks.dart';
3+
import 'package:time_keeper/utils/pcsc_scanner.dart';
4+
import 'package:time_keeper/utils/rfid_scanner.dart';
5+
6+
/// Unified RFID scanning hook that listens for input from both
7+
/// PCSC smart card readers (native only) and keyboard-emulating
8+
/// RFID readers (all platforms including web).
9+
///
10+
/// Both sources feed into the same [onScan] callback. Whichever
11+
/// device produces a scan first will trigger the callback.
12+
void useRfidScanner({
13+
required void Function(String uid) onScan,
14+
void Function(String message)? onError,
15+
bool enabled = true,
16+
List<Object?> keys = const [],
17+
}) {
18+
final onScanRef = useRef<void Function(String)>(onScan);
19+
final onErrorRef = useRef<void Function(String)?>(onError);
20+
onScanRef.value = onScan;
21+
onErrorRef.value = onError;
22+
23+
// PCSC smart card reader (native platforms only, stub on web)
24+
useEffect(() {
25+
if (!enabled) return null;
26+
27+
final scanner = PcscScanner(
28+
onScan: (uid) => onScanRef.value(uid),
29+
onError: (msg) => onErrorRef.value?.call(msg),
30+
);
31+
32+
scanner.start();
33+
34+
return scanner.dispose;
35+
}, [enabled, ...keys]);
36+
37+
// Keyboard-emulating RFID reader (all platforms)
38+
useEffect(() {
39+
if (!enabled) return null;
40+
41+
final buffer = RfidScanBuffer(onScan: (input) => onScanRef.value(input));
42+
43+
bool onKeyEvent(KeyEvent event) {
44+
buffer.handleKeyEvent(event);
45+
// Always return false so other key handlers still work
46+
return false;
47+
}
48+
49+
HardwareKeyboard.instance.addHandler(onKeyEvent);
50+
51+
return () {
52+
HardwareKeyboard.instance.removeHandler(onKeyEvent);
53+
buffer.dispose();
54+
};
55+
}, [enabled, ...keys]);
56+
}

client/lib/views/kiosk/kiosk_view.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'package:time_keeper/providers/auth_provider.dart';
66
import 'package:time_keeper/utils/permissions.dart';
77
import 'package:time_keeper/providers/location_provider.dart';
88
import 'package:time_keeper/providers/session_provider.dart';
9-
import 'package:time_keeper/hooks/use_pcsc_scanner.dart';
9+
import 'package:time_keeper/hooks/use_rfid_scanner.dart';
1010
import 'package:time_keeper/utils/time.dart';
1111
import 'package:time_keeper/views/kiosk/checked_in_list.dart';
1212
import 'package:time_keeper/views/kiosk/kiosk_dialog.dart';
@@ -51,11 +51,11 @@ class HomeView extends HookConsumerWidget {
5151
final roles = ref.watch(rolesProvider);
5252
final hasKiosk = roles.any((role) => role.hasPermission(Role.KIOSK));
5353

54-
// PCSC RFID reader - only active when user has KIOSK permission
55-
usePcscScanner(
54+
// RFID scanning (PCSC + keyboard) - only active when user has KIOSK permission
55+
useRfidScanner(
5656
enabled: hasKiosk,
5757
onScan: (uid) {
58-
_log.i('PCSC card UID: $uid');
58+
_log.i('RFID scan: $uid');
5959
if (context.mounted) {
6060
handleKioskScan(input: uid, context: context, ref: ref);
6161
}

client/lib/widgets/rfid_scan_button.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'dart:async';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_hooks/flutter_hooks.dart';
4-
import 'package:time_keeper/hooks/use_pcsc_scanner.dart';
4+
import 'package:time_keeper/hooks/use_rfid_scanner.dart';
55

66
/// A button that listens for an RFID card scan and writes the UID
77
/// into the provided [controller].
@@ -23,7 +23,7 @@ class RfidScanButton extends HookWidget {
2323
listening.value = false;
2424
}
2525

26-
usePcscScanner(
26+
useRfidScanner(
2727
enabled: listening.value,
2828
onScan: (uid) {
2929
controller.text = uid;

rfid_bootstrap

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env bash
2+
# =============================================================================
3+
# ACR122U RFID Reader Bootstrap Script
4+
# Supports: Fedora, Ubuntu/Debian, Arch Linux
5+
# =============================================================================
6+
7+
set -euo pipefail
8+
9+
RED='\033[0;31m'
10+
GREEN='\033[0;32m'
11+
YELLOW='\033[1;33m'
12+
NC='\033[0m' # No Color
13+
14+
log() { echo -e "${GREEN}[INFO]${NC} $*"; }
15+
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
16+
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
17+
18+
# =============================================================================
19+
# Root check
20+
# =============================================================================
21+
if [[ $EUID -ne 0 ]]; then
22+
error "Please run as root: sudo bash setup-rfid.sh"
23+
fi
24+
25+
REAL_USER="${SUDO_USER:-$USER}"
26+
27+
# =============================================================================
28+
# Detect distro
29+
# =============================================================================
30+
detect_distro() {
31+
if [[ -f /etc/os-release ]]; then
32+
. /etc/os-release
33+
echo "${ID}"
34+
else
35+
error "Cannot detect Linux distribution (no /etc/os-release)"
36+
fi
37+
}
38+
39+
DISTRO=$(detect_distro)
40+
log "Detected distro: ${DISTRO}"
41+
42+
# =============================================================================
43+
# Install packages
44+
# =============================================================================
45+
install_packages() {
46+
case "${DISTRO}" in
47+
fedora|rhel|centos)
48+
log "Installing packages via dnf..."
49+
dnf install -y pcsc-lite pcsc-lite-devel pcsc-lite-ccid pcsc-tools
50+
;;
51+
ubuntu|debian|linuxmint|pop)
52+
log "Installing packages via apt..."
53+
apt-get update -y
54+
apt-get install -y pcscd pcsc-tools libpcsclite-dev libccid
55+
;;
56+
arch|manjaro|endeavouros)
57+
log "Installing packages via pacman..."
58+
pacman -Sy --noconfirm pcsclite ccid pcsc-tools
59+
;;
60+
*)
61+
warn "Unrecognised distro '${DISTRO}'. Skipping package install."
62+
warn "Please manually install: pcscd, ccid, pcsc-tools, libpcsclite-dev"
63+
;;
64+
esac
65+
}
66+
67+
install_packages
68+
69+
# =============================================================================
70+
# Blacklist conflicting kernel modules (pn533 / nfc grab device before pcscd)
71+
# =============================================================================
72+
BLACKLIST_FILE="/etc/modprobe.d/blacklist-pn533.conf"
73+
74+
log "Writing kernel module blacklist to ${BLACKLIST_FILE}..."
75+
cat > "${BLACKLIST_FILE}" <<'EOF'
76+
# ACR122U fix: prevent kernel nfc/pn533 modules from claiming the device
77+
# before pcscd (PC/SC daemon) can use it.
78+
blacklist pn533
79+
blacklist pn533_usb
80+
blacklist nfc
81+
EOF
82+
83+
log "Unloading conflicting modules (if loaded)..."
84+
for mod in pn533_usb pn533 nfc; do
85+
if lsmod | grep -q "^${mod}"; then
86+
modprobe -r "${mod}" && log " Unloaded: ${mod}" || warn " Could not unload: ${mod} (may need reboot)"
87+
else
88+
log " Not loaded: ${mod} (nothing to do)"
89+
fi
90+
done
91+
92+
# =============================================================================
93+
# udev rule for ACR122U (idVendor=072f, idProduct=2200)
94+
# =============================================================================
95+
UDEV_FILE="/etc/udev/rules.d/99-acr122u.rules"
96+
97+
log "Writing udev rule to ${UDEV_FILE}..."
98+
cat > "${UDEV_FILE}" <<'EOF'
99+
# ACR122U USB NFC Reader
100+
SUBSYSTEM=="usb", ATTRS{idVendor}=="072f", ATTRS{idProduct}=="2200", MODE="0666", GROUP="plugdev"
101+
EOF
102+
103+
log "Reloading udev rules..."
104+
udevadm control --reload-rules
105+
udevadm trigger
106+
107+
# =============================================================================
108+
# Ensure the real user is in the plugdev group
109+
# =============================================================================
110+
if ! getent group plugdev > /dev/null 2>&1; then
111+
log "Creating plugdev group..."
112+
groupadd plugdev
113+
fi
114+
115+
if id -nG "${REAL_USER}" | grep -qw plugdev; then
116+
log "User '${REAL_USER}' is already in plugdev."
117+
else
118+
log "Adding '${REAL_USER}' to plugdev group..."
119+
usermod -aG plugdev "${REAL_USER}"
120+
warn "Group change requires logout/login to take effect for '${REAL_USER}'."
121+
fi
122+
123+
# =============================================================================
124+
# Enable and restart pcscd
125+
# =============================================================================
126+
log "Enabling and restarting pcscd..."
127+
systemctl enable pcscd
128+
systemctl restart pcscd
129+
130+
# =============================================================================
131+
# Verify
132+
# =============================================================================
133+
echo ""
134+
log "==============================="
135+
log "Setup complete. Verification:"
136+
log "==============================="
137+
138+
if systemctl is-active --quiet pcscd; then
139+
log "pcscd is running."
140+
else
141+
warn "pcscd does not appear to be running. Check: sudo systemctl status pcscd"
142+
fi
143+
144+
if lsmod | grep -q pn533; then
145+
warn "pn533 module is still loaded. A reboot may be required to fully apply the blacklist."
146+
else
147+
log "pn533/nfc modules are not loaded."
148+
fi
149+
150+
echo ""
151+
log "Plug in your ACR122U and run: pcsc_scan"
152+
log "You should see the reader listed and ATR data when a card is tapped."
153+
echo ""
154+
warn "If you were just added to 'plugdev', please log out and back in."

0 commit comments

Comments
 (0)