Reading input from RFID USB reader in Elixir on Raspberry Pi platform
I recently bought a cheap RFID USB reader which serves as a keyboard. If a RFID card is swept on it, it will behave as card number is typed in keyboard. It is a convenient way to read RFID on Raspberry PI. But for headless setup, how do I receive the input from RFID USB reader if I connect to Raspberry PI through ssh ?
A typical keyboard shows up as /de/tty1. A simple command might work:
sudo cat /dev/tty1
But for some reasons, I keep getting different numbers by swiping the same card again and again.
There is a better option: /dev/hidraw0
sudo cat /dev/hidraw0
Now, I can get consistent result. It will look better if it is dumped as bytes
sudo cat /dev/hidraw0 | od -x (or -o for octal number)
Then the next thing to do is to open this device in Elixir like this
file = File.open("/dev/hidraw0")
You need to sudo to open an device like this. But it will return an error saying /dev/hidraw0 is a directory, not a file. It is what Erlang will do for devices. To get it work, use Port instead of File.
I follow this discussion using python to read RFID USB reader. Here is the code in Elixir. It is not thoroughly tested.
defmodule HIDRaw do @hid %{ 4 => 'a', 5 => 'b', 6 => 'c', 7 => 'd', 8 => 'e', 9 => 'f', 10 => 'g', 11 => 'h', 12 => 'i', 13 => 'j', 14 => 'k', 15 => 'l', 16 => 'm', 17 => 'n', 18 => 'o', 19 => 'p', 20 => 'q', 21 => 'r', 22 => 's', 23 => 't', 24 => 'u', 25 => 'v', 26 => 'w', 27 => 'x', 28 => 'y', 29 => 'z', 30 => '1', 31 => '2', 32 => '3', 33 => '4', 34 => '5', 35 => '6', 36 => '7', 37 => '8', 38 => '9', 39 => '0', 44 => ' ', 45 => '-', 46 => '=', 47 => '[', 48 => ']', 49 => '\\', 51 => ';' , 52 => '\'', 53 => '~', 54 => ',', 55 => '.', 56 => '/' , 40 => '' } @hid2 %{ 4 => 'A', 5 => 'B', 6 => 'C', 7 => 'D', 8 => 'E', 9 => 'F', 10 => 'G', 11 => 'H', 12 => 'I', 13 => 'J', 14 => 'K', 15 => 'L', 16 => 'M', 17 => 'N', 18 => 'O', 19 => 'P', 20 => 'Q', 21 => 'R', 22 => 'S', 23 => 'T', 24 => 'U', 25 => 'V', 26 => 'W', 27 => 'X', 28 => 'Y', 29 => 'Z', 30 => '!', 31 => '@', 32 => '#', 33 => '$', 34 => '%', 35 => '^', 36 => '&', 37 => '*', 38 => '(', 39 => ')', 44 => ' ', 45 => '_', 46 => '+', 47 => '{', 48 => '}', 49 => '|', 51 => ':', 52 => '"', 53 => '~', 54 => '<', 55 => '>', 56 => '?', 40 => '' } def decode_hid([h|t], shift \\ false) do
if h == 40 do
# end
''
else
if shift do
if h == 2 do
shift = true
else
[Map.fetch!(@hid2, h) | decode_hid(t, shift)] shift = false
end
else
if h == 2 do
shift = true
else
[Map.fetch!(@hid, h) | decode_hid(t, shift)]
end
end
end
end def read_hid(pipe) do
fifo = Port.open(pipe, [:stream, :eof])
z = receive_hid([])
data = z
|> Enum.filter(fn y -> y > 0 end )
decode_hid(data)
end def receive_hid(d) do
x = receive do
{fifo, {:data, data}} ->
data
_ -> "something else"
end
x = [d | x] |> List.flatten()
IO.puts(inspect x)
IO.puts(length x)
if Enum.any?(x, fn y -> y == 40 end) do
IO.puts "end"
x
else
receive_hid(x)
end
end
endHIDRaw.read_hid('/dev/hidraw0')
It first opens a port on device, then read data as stream until ‘40’, which means the end of input. Zero is removed from data and HID mapping is used to get the correct result. The code can probably be optimized better.
Here is the gist.