Reading input from an USB Rfid Card Reader

Why Bother

I had this cheap RFID reader lying around in the house. I can't right remember when and why I had bought it. I had some idea for a software, but then I never really needed to have it on the desk, therefore here you go...

When you plug it, it works like a normal keyboards, it just stays there, but then if you swipe a card or an RFID token, it will just write on your standard input, either on a terminal or whatever application you have useful at the moment.

In order to do anything useful with that piece of equipment, I had to capture the input from a single application, making sure it would not be spamming anything else.

The Name of the Beast

It turns out, what I wanted has a name, and is the EVIOCGRAB ioctl. Sure, in C that makes sense, but for higher level software components that's not a thing. I looked for examples around, and of course, Python got you covered.

But for myself I'd rather not depend on some fluffy PythonN library flying around that I need to remember to install dependencies for.

I'd rather go off and implement som kind-of-high-level Golang code.

To find my device, I had to plug it first, and look what I had available with:

find /dev/input/by-id

The Spell

Let's say now that my device has this name:

/dev/input/by-id/usb-something_Barcode_Reader-event-kbd

There you have the code to read and print the input exclusively, without affecting the other applications:

package main

import (
	"encoding/binary"
	"fmt"
	"os"
	"syscall"
	"unsafe"
)

type inputEvent struct {
	Sec   uint64
	Usec  uint64
	Type  uint16
	Code  uint16
	Value int32
}

const (
	EVIOCGRAB = 0x40044590 // ioctl to grab exclusive access

	EV_KEY    = 0x01
	KEY_ENTER = 28
)

var keyMap = map[uint16]rune{
	2: '1', 3: '2', 4: '3', 5: '4', 6: '5',
	7: '6', 8: '7', 9: '8', 10: '9', 11: '0',
	16: 'Q', 17: 'W', 18: 'E', 19: 'R', 20: 'T',
	21: 'Y', 22: 'U', 23: 'I', 24: 'O', 25: 'P',
	30: 'A', 31: 'S', 32: 'D', 33: 'F', 34: 'G',
	35: 'H', 36: 'J', 37: 'K', 38: 'L',
	44: 'Z', 45: 'X', 46: 'C', 47: 'V', 48: 'B',
	49: 'N', 50: 'M',
}

func main() {
	devPath := "/dev/input/by-id/usb-something_Barcode_Reader-event-kbd"
	if len(os.Args) > 1 {
		devPath = os.Args[1]
	}

	f, err := os.Open(devPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "open %s: %v\n", devPath, err)
		os.Exit(1)
	}
	defer f.Close()

	// Grab exclusive access so the kernel doesn't forward events to X/Wayland.
	if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), EVIOCGRAB, 1); errno != 0 {
		fmt.Fprintf(os.Stderr, "EVIOCGRAB: %v\n", errno)
		os.Exit(1)
	}
	fmt.Println("Grabbed device. Waiting for card scans (Ctrl-C to exit)���")

	var card []rune
	var ev inputEvent

	for {
		if err := binary.Read(f, binary.LittleEndian, &ev); err != nil {
			fmt.Fprintf(os.Stderr, "read: %v\n", err)
			break
		}

		// We only care about key-press events (value == 1).
		if ev.Type != EV_KEY || ev.Value != 1 {
			continue
		}

		if ev.Code == KEY_ENTER {
			// RFID readers terminate each card with Enter.
			fmt.Printf("This is the RFID value: %s\n", string(card))
			card = card[:0]
			continue
		}

		if ch, ok := keyMap[ev.Code]; ok {
			card = append(card, ch)
		}
	}

	syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), EVIOCGRAB, uintptr(unsafe.Pointer(nil)))
}

Consider joining The Post Cloud ML if you find those rambling to be interesting.



[python] [golang] [git]