Files
Odoo-Modules/tools/fusion_clock_acr_wedge
gsinghpal 00981a502a feat(acr-wedge+kiosk): SSE bridge for ACR122U / PC-SC readers
macOS keystroke injection from a CLI-launched Python hits multiple
TCC permission walls (Accessibility AND Automation, both attaching
to identities macOS often can't resolve cleanly). After bouncing
through Quartz, AppleScript, and pyautogui fallbacks, none of them
worked reliably in our test environment.

Switch to a proper IPC channel instead of pretending to be a
keyboard.

Daemon (wedge.py):
  - Adds a ThreadingHTTPServer on 127.0.0.1:8765 exposing /events
  - SSE stream pushes each detected UID as one event
  - 30s keep-alive comments to keep idle connections open
  - CORS: Access-Control-Allow-Origin: * (kiosk page may be on any
    client-domain HTTPS origin; SSE source is always localhost)
  - Keystroke injection kept as best-effort fallback for non-SSE
    clients

Kiosk JS (fusion_clock_nfc_kiosk.js):
  - Adds startWedgeSseListener() that opens EventSource to
    http://localhost:8765/events on setup
  - On message: same handleTap()/_onEnrollTap() flow as Web NFC + HID
  - EventSource auto-reconnects; first error is logged then silenced
  - http://localhost is a "potentially trustworthy origin" so this
    works from https:// pages without mixed-content blocking

Result: ACR122U + wedge.py daemon now drives the kiosk with zero
macOS permission prompts and no focused-window dependency. Same
input plumbing as Web NFC and HID — penalty/photo/activity log
fire identically.

Bump fusion_clock to 19.0.3.3.0.
2026-05-15 20:10:40 -04:00
..

fusion_clock ACR122U Wedge

Turns an ACR122U (or any PC/SC ACS reader) into a USB-HID-style keyboard wedge so the fusion_clock NFC kiosk can use it.

Why this exists

The ACR122U is a fantastic 13.56 MHz reader (better range than cheap HID readers, reads MIFARE Classic/DESFire/NTAG/FeliCa/ISO15693 — basically everything Ubiquiti UA-Pockets and most enterprise cards). But it speaks PC/SC (CCID), not HID keyboard. Browsers can't talk to PC/SC devices directly.

This daemon bridges the gap: it polls the ACR122U over PC/SC, reads tag UIDs, and types them into the focused window as keystrokes — exactly like a USB-HID reader would. The fusion_clock kiosk page's existing keyboard listener picks them up the same way.

ACR122U → PC/SC → wedge.py → keystrokes → kiosk page in Chrome

Setup

macOS

brew install swig
pip3 install -r requirements.txt
python3 wedge.py --verbose

The first time the daemon tries to type a UID, macOS will pop up an Accessibility permission prompt. Grant it to Terminal.app (or whatever shell ran the script). This is one-time.

If pyscard fails to install: ensure Xcode command-line tools are installed (xcode-select --install).

Windows

pip install -r requirements.txt
python wedge.py --verbose

No swig required — pyscard ships pre-built wheels on Windows.

Linux

sudo apt install pcscd pcsc-tools libpcsclite-dev swig
sudo systemctl enable --now pcscd
pip3 install -r requirements.txt
python3 wedge.py --verbose

You may need to run with sudo or add your user to the plugdev group.

Usage

# Quiet mode — just types UIDs on tap
python3 wedge.py

# Verbose — prints every tap and reader event
python3 wedge.py --verbose

# Detect-only — prints UIDs but doesn't type them
# (use this to verify the reader is working before granting Accessibility perms)
python3 wedge.py --no-type --verbose

Output format

UIDs are typed as colon-separated uppercase hex, followed by Enter:

04:10:5B:CA:FD:22:90<Enter>

This matches what FusionClockNfcKiosk._normalize_uid() expects, so the kiosk recognizes it without any server-side translation.

Debounce

The daemon won't re-emit the same UID more than once within 2 seconds (so holding a card on the reader doesn't fire repeated clock-ins). Tap a different card, or wait 2 seconds, and the same card will re-emit.

Running as a service

For kiosk deployments, you want this to start at boot and stay running.

macOS — LaunchAgent

Create ~/Library/LaunchAgents/com.nexa.fusion-clock-acr-wedge.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.nexa.fusion-clock-acr-wedge</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/python3</string>
        <string>/Users/USERNAME/path/to/wedge.py</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/tmp/fusion-clock-wedge.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/fusion-clock-wedge.err</string>
</dict>
</plist>

Load it:

launchctl load ~/Library/LaunchAgents/com.nexa.fusion-clock-acr-wedge.plist

Windows — Task Scheduler

Create a task to run python wedge.py at login. Or use NSSM to install it as a Windows service.

Linux — systemd user service

Create ~/.config/systemd/user/fusion-clock-wedge.service:

[Unit]
Description=Fusion Clock ACR122U Wedge
After=graphical-session.target

[Service]
ExecStart=/usr/bin/python3 /home/USER/path/to/wedge.py
Restart=always

[Install]
WantedBy=default.target

Enable it:

systemctl --user enable --now fusion-clock-wedge.service

Troubleshooting

"No PC/SC readers detected"

  • macOS: PC/SC is built into the OS, no service to start. Just make sure the reader is plugged in.
  • Linux: sudo systemctl status pcscd — make sure it's running.
  • Windows: built-in.

"pyautogui can't type on macOS"

  • Grant Accessibility permission to your terminal in System Settings → Privacy & Security → Accessibility.

"Reader detected but no UID on tap"

  • Try --verbose to see error messages.
  • The card might not respond to the standard GET_UID APDU. Most 13.56 MHz cards do; some FeliCa variants don't.
  • Try python3 wedge.py --no-type --verbose to confirm detection works without involving the keyboard layer.

Keystrokes appearing in the wrong window

  • pyautogui types into whatever window has focus. Make sure the kiosk page is focused when you tap.
  • This is the same behavior as a USB-HID reader — they both depend on the OS focused window.

Future packaging

This is the prototype. For client distribution we'll wrap it in:

  • macOS .app bundle (PyInstaller + py2app)
  • Windows .exe (PyInstaller)
  • Linux AppImage

Plus autostart and an idle "no reader detected" status indicator. See parent module's todo list.