Haskell with SparkFun Simultaneous RFID Reader

Posted on June 6, 2017
Tags: haskell, making

SparkFun Simultaneous RFID Reader board

A couple of months ago, I bought a SparkFun Simultaneous RFID Reader board. As always, I’d rather program it in Haskell, so my first task was to write a Haskell binding to the C library (“Mercury API”) that the manufacturer provides.

I recently finished my Haskell binding and released it to Hackage. My binding supports all the basic operations you’d want to do to a tag: reading, writing, locking, unlocking, and killing. It works on Linux (including Raspberry Pi), Mac OS X, and Windows.

Along the way, I found a few bugs in the Mercury API C code, and I have some patches for them, which I’ve also submitted upstream to ThingMagic.

So, counting my Haskell binding, Mercury API is now available in five languages:

Here is a short example in Haskell that reads tags at maximum power for 1 second, and then prints the results:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text.IO as T
import qualified System.Hardware.MercuryApi as TMR
import qualified System.Hardware.MercuryApi.Params as TMR

main = do
  rdr <- TMR.create "tmr:///dev/ttyUSB0"
  TMR.paramSetTransportTimeout rdr 10000
  TMR.connect rdr
  TMR.paramSetBasics rdr TMR.REGION_NA2 2700 TMR.sparkFunAntennas
  tags <- TMR.read rdr 1000
  putStrLn $ "read " ++ show (length tags) ++ " tags"
  mapM_ T.putStrLn $ concatMap TMR.displayTagReadData tags
  TMR.destroy rdr

My goal is to use RFID tags to tag my stuff. For example, I lost my Bananagrams for several months, and it turned out they had been at the bottom of my backpack all along! So now I can stick an RFID tag in my Bananagrams, with the string “Bananagrams” written to the user bank of the tag. With the right antenna, I should be able to read the tag from 16 feet away. Unfortunately, that antenna isn’t available yet. Hopefully SparkFun will start shipping it soon!

Bananagrams with RFID tag

In order to read the user bank (where I’m storing the names of my things), it’s necessary to create a ReadData tagop:

readUser =
  TMR.TagOp_GEN2_ReadData
  { TMR.opBank = TMR.GEN2_BANK_USER
  , TMR.opExtraBanks = []
  , TMR.opWordAddress = 0
  , TMR.opLen = 32
  }

and then insert this tagop into the read plan, by adding this line before the TMR.read:

  TMR.paramSetReadPlanTagop rdr (Just readUser)