
2026/04/18 2:14
USB RFID カードリーダーから入力を読み取る。
RSS: https://news.ycombinator.com/rss
要約▶
日本語訳:
本テキストは、外部の Python ライブラリを必要とせず、安価な RFID リーダーを専用アルファベット/数字入力デバイスに変えることを目的とした Golang ベースのアプリケーションについて記述しています。
EVIOCGRAB を用いて排他的入力量取を行うことで、プログラムがディスプレイマネージャー(X や Wayland など)へキーイベントを転送するのをブロックし、キーボードストリームに対するアプリケーションの絶対的な制御を確保します。実装では、/dev/input/by-id 内の一意なパス(例:usb-something_Barcode_Reader-event-kbd)をスキャンしてハードウェアを特定します。生入力量はカスタムの keyMap を通じて文字コードに変換され、KEY_ENTER イベント(コード 28)が発生するまで入力量をバッファリングし、その時点でバッファされたテキストが出力され、バッファがクリアされます。システムは sys.Open、os.Open、syscall.Syscall から返される戻り値を確認することでエラーを処理し、EOF または Ctrl-C で適切に終了するとともに、終了前に排他的アクセス(EVIOCGRAB を用いて nil)を明示的に解放します。このアプローチは、標準的な Golang 環境内での RFID インテグレーションに対して、Python ベースのツールへの軽量で依存関係のない代替手段を提供します。本文
なぜわざわざこれをやるのか
自宅に安価な RFID レーダーが放置されていた。いつ、どのような理由で購入したか正確には覚えていない。 ソフトウェアのアイデアは持っていたものの、実際にデスクトップで必要になる状況になかったため、こうして公開することにした。
このデバイスを接続すると、通常のキーボードとして機能する。ただそこにあるだけで、カードまたは RFID トークンをスワイプすると、その入力が標準入力に書き込まれ、ターミナルや現在利用中のアプリケーションのいずれかに取り込まれる。 この機器を有効に使うためには、単一のアプリケーションからの入力のみをキャッチし、他の何らかの動作への不要なデータ送信(スパム)を防ぐよう工夫する必要があった。
名付け親
結局、私が求めた機能には正式名称があり、それは「EVIOCGRAB ioctl」であった。もちろん C プログラミングでは理にかなっているが、高階層のソフトウェアコンポーネントにとっては馴染みのない概念だ。インターネット上を探したところ、当然ながら Python で実装できる例が見つかった。 しかし、私は外部ライブラリへの依存を避けたい。インストールに必要な依存関係を毎回思い出さなければならないのは好ましくないからだ。 そのため、より高階層で堅牢な Golang のコードを実装することに決めた。
デバイスを検出するには、まず接続し、以下のコマンドを実行する必要がある:
find /dev/input/by-id
実装
とある例として、私のデバイスのパスが以下であると仮定する:
/dev/input/by-id/usb-something_Barcode_Reader-event-kbd
このコードは、他のアプリケーションに影響を及ぼすことなく、このデバイスの入力を独占的に読み取り表示するためのものとなる。
package main import ( "encoding/binary" "fmt" "os" "syscall" "unsafe" ) // 入力イベントの構造体定義 type inputEvent struct { Sec uint64 Usec uint64 Type uint16 Code uint16 Value int32 } const ( // 独占アクセスを取得するための ioctl コマンド EVIOCGRAB = 0x40044590 // イベント種別:キー入力 EV_KEY = 0x01 // キーコード:Enter キー 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() // 独占アクセスを取得して、カーネルがイベントを 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("デバイスが読み込み完了しました。カードスキャンを待機中 (Ctrl-C で終了)") 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 } // キー入力イベント(Value == 1)のみ処理 if ev.Type != EV_KEY || ev.Value != 1 { continue } if ev.Code == KEY_ENTER { // RFID レーダーは各カード読み取り後に Enter を送信するため、ここで出力 fmt.Printf("RFID 値: %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))) }
もしこのわだかまりのある記述が興味深いと感じたら、The Post Cloud ML に参加するところをお勧めします。