packages feed

mercury-api-0.1.0.0: src/System/Hardware/MercuryApi/Records.hsc

{-# LANGUAGE OverloadedStrings, FlexibleInstances, DeriveDataTypeable #-}
module System.Hardware.MercuryApi.Records where

import Control.Applicative ( Applicative((<*>)), (<$>) )
import Control.Exception ( Exception, throwIO )
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.Maybe ( mapMaybe, fromMaybe )
import Data.Monoid ( (<>) )
import Data.Text (Text)
import qualified Data.Text as T ( pack )
import qualified Data.Text.Encoding as T
    ( encodeUtf8, decodeUtf8With )
import qualified Data.Text.Encoding.Error as T ( lenientDecode )
import Data.Typeable ( Typeable )
import Data.Word ( Word8, Word16, Word32, Word64 )
import Foreign
    ( Int32,
      Ptr,
      nullPtr,
      plusPtr,
      Storable(alignment, peek, peekByteOff, poke, pokeByteOff, sizeOf),
      Bits((.&.), (.|.), shiftL),
      castPtr,
      with,
      toBool,
      fromBool,
      withArrayLen,
      pokeArray,
      peekArray,
      copyArray,
      allocaArray )
import Foreign.C ( CString )

import System.Hardware.MercuryApi.Enums

#include <tm_reader.h>
#include <glue.h>
#include <stdbool.h>

-- | A GPIO pin number.  On the M6e Nano, these are numbered 1-4.
type PinNumber = Word8

-- | An antenna number.  On the
-- <https://www.sparkfun.com/products/14066 SparkFun Simultaneous RFID Reader>,
-- there is a single antenna with the number 1.
type AntennaPort = Word8

-- | A 32-bit password (access or kill) in the Gen2 protocol.
type GEN2_Password = Word32

-- | milliseconds since 1\/1\/1970 UTC
type MillisecondsSinceEpoch = Word64

-- | Version number of the Mercury API C library.
apiVersion :: Text
apiVersion = #{const_str TMR_VERSION}

type CBool = #{type bool}
newtype ReaderEtc = ReaderEtc ()

cFalse, cTrue :: CBool
cFalse = 0
cTrue = 1

toBool' :: CBool -> Bool
toBool' = toBool

fromBool' :: Bool -> CBool
fromBool' = fromBool

sizeofReaderEtc :: Int
sizeofReaderEtc = #{size ReaderEtc}

uriPtr :: Ptr ReaderEtc -> CString
uriPtr = #{ptr ReaderEtc, uri}

-- I'm not sure what encoding MercuryApi uses for its strings.
-- I'm guessing UTF-8 for now, but the encoding is encapsulated in
-- these two functions (textFromBS and textToBS) so it can be
-- easily changed.
textFromBS :: ByteString -> Text
textFromBS = T.decodeUtf8With T.lenientDecode

textToBS :: Text -> ByteString
textToBS = T.encodeUtf8

textFromCString :: CString -> IO Text
textFromCString cs = textFromBS <$> B.packCString cs

-- | Indicates whether to read or write the lock bits in
-- 'TagOp_GEN2_BlockPermaLock'.
data ReadWrite = Read !Int       -- ^ number of words of lock bits to read
               | Write ![Word16] -- ^ lock bits to write
               deriving (Eq, Ord, Show, Read)

fromReadWrite :: ReadWrite -> (Word8, [Word16])
fromReadWrite (Read n) = (0, replicate n 0)
fromReadWrite (Write ws) = (1, ws)

toReadWrite :: (Word8, [Word16]) -> ReadWrite
toReadWrite (0, ws) = Read $ length ws
toReadWrite (1, ws) = Write ws
toReadWrite (x, _)  = error $ "didn't expect ReadWrite to be " ++ show x

-- This exception is never seen by the user.  It is caught
-- internally and turned into a MercuryException (with some added fields).
data ParamException = ParamException StatusType Status Text
  deriving (Eq, Ord, Show, Read, Typeable)

instance Exception ParamException

castLen' :: Integral a => a -> Text -> Int -> IO a
castLen' bound description x = do
  let tShow = T.pack . show
      maxLen = fromIntegral bound
  if x > maxLen
    then throwIO ( ParamException ERROR_TYPE_MISC ERROR_TOO_BIG $
                   description <> " had length " <> tShow x <>
                   " but maximum is " <> tShow maxLen )
    else return $ fromIntegral x

castLen :: (Integral a, Bounded a) => Text -> Int -> IO a
castLen = castLen' maxBound

-- | A ReadPlan record specifies the antennas, protocols, and filters
-- to use for a search (read).
--
-- Currently, only @SimpleReadPlan@ is supported.
data ReadPlan =
  SimpleReadPlan
  { rpWeight        :: !Word32          -- ^ The relative weight of this read plan
  , rpEnableAutonomousRead :: !Bool     -- ^ Option for Autonomous read
  , rpAntennas      :: ![AntennaPort]   -- ^ The list of antennas to read on
  , rpProtocol      :: !TagProtocol     -- ^ The protocol to use for reading
  , rpFilter        :: !(Maybe TagFilter) -- ^ The filter to apply to reading
  , rpTagop         :: !(Maybe TagOp)   -- ^ The tag operation to apply to
                                        -- each read tag
  , rpUseFastSearch :: !Bool            -- ^ Option to use the FastSearch
  , rpStopOnCount   :: !(Maybe Word32)  -- ^ Number of tags to be read
  , rpTriggerRead   :: !(Maybe [Word8]) -- ^ The list of GPI ports which should
                                        -- be used to trigger the read
  } deriving (Eq, Ord, Show, Read)

antennasInfo :: Ptr ReadPlan -> (Ptr List16, Word16, Ptr Word8, Text)
antennasInfo rp =
  ( #{ptr ReadPlanEtc, plan.u.simple.antennas} rp
  , #{const GLUE_MAX_ANTENNAS}
  , #{ptr ReadPlanEtc, antennas} rp
  , "rpAntennas"
  )

gpiListInfo :: Ptr ReadPlan -> (Ptr List16, Word16, Ptr Word8, Text)
gpiListInfo rp =
  ( #{ptr ReadPlanEtc, plan.u.simple.triggerRead.gpiList} rp
  , #{const GLUE_MAX_GPIPORTS}
  , #{ptr ReadPlanEtc, gpiPorts} rp
  , "rpTriggerRead"
  )

readPlanTypeSimple :: #{type TMR_ReadPlanType}
readPlanTypeSimple = #{const TMR_READ_PLAN_TYPE_SIMPLE}

instance Storable ReadPlan where
  sizeOf _ = #{size ReadPlanEtc}
  alignment _ = 8

  poke p x = do
    #{poke ReadPlanEtc, plan.type} p readPlanTypeSimple
    #{poke ReadPlanEtc, plan.weight} p (rpWeight x)
    #{poke ReadPlanEtc, plan.enableAutonomousRead} p
      (fromBool' $ rpEnableAutonomousRead x)
    pokeList16 (antennasInfo p) (rpAntennas x)
    #{poke ReadPlanEtc, plan.u.simple.protocol} p
      (fromTagProtocol $ rpProtocol x)
    case rpFilter x of
      Nothing -> #{poke ReadPlanEtc, plan.u.simple.filter} p nullPtr
      Just f -> do
        #{poke ReadPlanEtc, filter} p f
        #{poke ReadPlanEtc, plan.u.simple.filter} p (#{ptr ReadPlanEtc, filter} p)
    case rpTagop x of
      Nothing -> #{poke ReadPlanEtc, plan.u.simple.tagop} p nullPtr
      Just op -> do
        #{poke ReadPlanEtc, tagop} p op
        #{poke ReadPlanEtc, plan.u.simple.tagop} p (#{ptr ReadPlanEtc, tagop} p)
    #{poke ReadPlanEtc, plan.u.simple.useFastSearch} p
      (fromBool' $ rpUseFastSearch x)
    let (stop, nTags) = case rpStopOnCount x of
                          Nothing -> (cFalse, 0)
                          Just n -> (cTrue, n)
    #{poke ReadPlanEtc, plan.u.simple.stopOnCount.stopNTriggerStatus} p stop
    #{poke ReadPlanEtc, plan.u.simple.stopOnCount.noOfTags} p nTags
    let (enable, ports) = case rpTriggerRead x of
                            Nothing -> (cFalse, [])
                            Just ps -> (cTrue, ps)
    #{poke ReadPlanEtc, plan.u.simple.triggerRead.enable} p enable
    pokeList16 (gpiListInfo p) ports

  peek p = do
    weight <- #{peek ReadPlanEtc, plan.weight} p
    enableAutonomousRead <- #{peek ReadPlanEtc, plan.enableAutonomousRead} p
    antennas <- peekList16 (antennasInfo p)
    protocol <- #{peek ReadPlanEtc, plan.u.simple.protocol} p
    fPtr <- #{peek ReadPlanEtc, plan.u.simple.filter} p
    filt <- if fPtr == nullPtr
            then return Nothing
            else Just <$> peek fPtr
    opPtr <- #{peek ReadPlanEtc, plan.u.simple.tagop} p
    op <- if opPtr == nullPtr
          then return Nothing
          else Just <$> peek opPtr
    useFastSearch <- #{peek ReadPlanEtc, plan.u.simple.useFastSearch} p
    stop <- #{peek ReadPlanEtc, plan.u.simple.stopOnCount.stopNTriggerStatus} p
    stopOnCount <- if toBool' stop
                   then Just <$> #{peek ReadPlanEtc, plan.u.simple.stopOnCount.noOfTags} p
                   else return Nothing
    enable <- #{peek ReadPlanEtc, plan.u.simple.triggerRead.enable} p
    triggerRead <- if toBool' enable
                   then Just <$> peekList16 (gpiListInfo p)
                   else return Nothing
    return $ SimpleReadPlan
      { rpWeight = weight
      , rpEnableAutonomousRead = toBool' enableAutonomousRead
      , rpAntennas = antennas
      , rpProtocol = toTagProtocol protocol
      , rpFilter = filt
      , rpTagop = op
      , rpUseFastSearch = toBool' useFastSearch
      , rpStopOnCount = stopOnCount
      , rpTriggerRead = triggerRead
      }

-- | Filter on EPC length, or on a Gen2 bank.
data FilterOn = FilterOnBank GEN2_Bank
              | FilterOnEpcLength
              deriving (Eq, Ord, Show, Read)

instance Storable FilterOn where
  sizeOf _ = #{size TMR_GEN2_Bank}
  alignment _ = 8

  poke p FilterOnEpcLength = do
    let p' = castPtr p :: Ptr RawBank
    poke p' #{const TMR_GEN2_EPC_LENGTH_FILTER}

  poke p (FilterOnBank bank) = do
    let p' = castPtr p :: Ptr RawBank
    poke p' (fromBank bank)

  peek p = do
    x <- peek (castPtr p)
    if x == #{const TMR_GEN2_EPC_LENGTH_FILTER}
      then return FilterOnEpcLength
      else return $ FilterOnBank $ toBank x

-- | Filter on EPC data, or on Gen2-specific information.
data TagFilter = TagFilterEPC TagData
               | TagFilterGen2
               { tfInvert        :: !Bool        -- ^ Whether to invert the
                                                 -- selection (deselect tags
                                                 -- that meet the comparison)
               , tfFilterOn      :: !FilterOn    -- ^ The memory bank in which
                                                 -- to compare the mask
               , tfBitPointer    :: !Word32      -- ^ The location (in bits) at
                                                 -- which to begin comparing
                                                 -- the mask
               , tfMaskBitLength :: !Word16      -- ^ The length (in bits) of
                                                 -- the mask
               , tfMask          :: !ByteString  -- ^ The mask value to compare
                                                 -- with the specified region
                                                 -- of tag memory, MSB first
               }
               deriving (Eq, Ord, Show, Read)

instance Storable TagFilter where
  sizeOf _ = #{size TagFilterEtc}
  alignment _ = 8

  poke p (TagFilterEPC td) = do
    #{poke TagFilterEtc, filter.type} p
      (#{const TMR_FILTER_TYPE_TAG_DATA} :: #{type TMR_FilterType})
    #{poke TagFilterEtc, filter.u.tagData} p td

  poke p tf@(TagFilterGen2 {}) = do
    #{poke TagFilterEtc, filter.type} p
      (#{const TMR_FILTER_TYPE_GEN2_SELECT} :: #{type TMR_FilterType})
    #{poke TagFilterEtc, filter.u.gen2Select.invert} p (fromBool' $ tfInvert tf)
    #{poke TagFilterEtc, filter.u.gen2Select.bank} p (tfFilterOn tf)
    #{poke TagFilterEtc, filter.u.gen2Select.bitPointer} p (tfBitPointer tf)
    #{poke TagFilterEtc, filter.u.gen2Select.maskBitLength} p (tfMaskBitLength tf)
    let maskLenBytes = fromIntegral $ (tfMaskBitLength tf + 7) `div` 8
        origLen = B.length (tfMask tf)
        bs = if origLen < maskLenBytes
               then tfMask tf <> B.pack (replicate (maskLenBytes - origLen) 0)
               else tfMask tf
    B.useAsCStringLen bs $ \(cs, len) -> do
      len' <- castLen' #{const GLUE_MAX_MASK} "tfMask" len
      copyArray (#{ptr TagFilterEtc, mask} p) cs (fromIntegral len')
    #{poke TagFilterEtc, filter.u.gen2Select.mask} p (#{ptr TagFilterEtc, mask} p)

  peek p = do
    ft <- #{peek TagFilterEtc, filter.type} p :: IO #{type TMR_FilterType}
    case ft of
      #{const TMR_FILTER_TYPE_TAG_DATA} ->
        TagFilterEPC <$> #{peek TagFilterEtc, filter.u.tagData} p
      #{const TMR_FILTER_TYPE_GEN2_SELECT} ->
        TagFilterGen2
        <$> (toBool' <$> #{peek TagFilterEtc, filter.u.gen2Select.invert} p)
        <*> #{peek TagFilterEtc, filter.u.gen2Select.bank} p
        <*> #{peek TagFilterEtc, filter.u.gen2Select.bitPointer} p
        <*> #{peek TagFilterEtc, filter.u.gen2Select.maskBitLength} p
        <*> peekMask p

peekMask :: Ptr TagFilter -> IO ByteString
peekMask p = do
  bitLength <- #{peek TagFilterEtc, filter.u.gen2Select.maskBitLength} p :: IO Word32
  let len = fromIntegral $ (bitLength + 7) `div` 8
  maskPtr <- #{peek TagFilterEtc, filter.u.gen2Select.mask} p
  B.packCStringLen (maskPtr, len)

packBits :: Num b => (a -> b) -> [a] -> b
packBits from flags = sum $ map from flags

unpackBits :: (Bounded a, Enum a, Num b, Bits b) => (a -> b) -> b -> [a]
unpackBits from x = mapMaybe f [minBound..maxBound]
  where f flag = if (x .&. from flag) == 0
                 then Nothing
                 else Just flag

packFlags :: [MetadataFlag] -> RawMetadataFlag
packFlags = packBits fromMetadataFlag

unpackFlags :: RawMetadataFlag -> [MetadataFlag]
unpackFlags = unpackBits fromMetadataFlag

packFlags16 :: [MetadataFlag] -> Word16
packFlags16 = fromIntegral . packFlags

unpackFlags16 :: Word16 -> [MetadataFlag]
unpackFlags16 = unpackFlags . fromIntegral

packExtraBanks :: [GEN2_Bank] -> RawBank
packExtraBanks = packBits fromExtraBank

unpackExtraBanks :: RawBank -> [GEN2_Bank]
unpackExtraBanks = unpackBits fromExtraBank

packLockBits :: [GEN2_LockBits] -> RawLockBits
packLockBits = packBits fromLockBits

unpackLockBits :: RawLockBits -> [GEN2_LockBits]
unpackLockBits = unpackBits fromLockBits

packLockBits16 :: [GEN2_LockBits] -> Word16
packLockBits16 = fromIntegral . packLockBits

unpackLockBits16 :: Word16 -> [GEN2_LockBits]
unpackLockBits16 = unpackLockBits . fromIntegral

peekArrayAsByteString :: Ptr Word8 -> Ptr Word8 -> IO ByteString
peekArrayAsByteString arrayPtr lenPtr = do
  len <- peek lenPtr
  B.packCStringLen (castPtr arrayPtr, fromIntegral len)

pokeArrayAsByteString :: Text
                      -> Word8
                      -> Ptr Word8
                      -> Ptr Word8
                      -> ByteString
                      -> IO ()
pokeArrayAsByteString desc maxLen arrayPtr lenPtr bs = do
  B.useAsCStringLen bs $ \(cs, len) -> do
    len' <- castLen' maxLen desc len
    copyArray arrayPtr (castPtr cs) (fromIntegral len')
    poke lenPtr len'

peekListAsByteString :: Ptr List16 -> IO ByteString
peekListAsByteString listPtr = do
  lst <- peek listPtr
  B.packCStringLen (castPtr $ l16_list lst, fromIntegral $ l16_len lst)

peekArrayAsList :: Storable a => Ptr a -> Ptr Word8 -> IO [a]
peekArrayAsList arrayPtr lenPtr = do
  len <- peek lenPtr
  peekArray (fromIntegral len) arrayPtr

peekListAsList :: Storable a => Ptr List16 -> Ptr a -> IO [a]
peekListAsList listPtr _ = do
  lst <- peek listPtr
  peekArray (fromIntegral $ l16_len lst) (castPtr $ l16_list lst)

pokeListAsList :: Storable a
               => Text
               -> Word16
               -> Ptr List16
               -> Ptr a
               -> [a]
               -> IO ()
pokeListAsList desc maxLen listPtr storage xs = do
  withArrayLen xs $ \len tmpPtr -> do
    len' <- castLen' maxLen desc len
    copyArray storage tmpPtr len
    let lst = List16
              { l16_list = castPtr storage
              , l16_max = maxLen
              , l16_len = len'
              }
    poke listPtr lst

peekMaybe :: (Storable a, Storable b)
          => (Ptr a -> IO a)
          -> (b -> Bool)
          -> Ptr a
          -> Ptr b
          -> IO (Maybe a)
peekMaybe oldPeek cond justP condP = do
  c <- peek condP
  if cond c
    then Just <$> oldPeek justP
    else return Nothing

pokeGen2TagData :: Ptr GEN2_TagData
                -> Ptr RawTagProtocol
                -> Maybe GEN2_TagData
                -> IO ()
pokeGen2TagData pGen2 _ mGen2 = do
  let gen2 = fromMaybe (GEN2_TagData B.empty) mGen2
  poke pGen2 gen2

peekSplit64 :: Ptr Word32 -> Ptr Word32 -> IO Word64
peekSplit64 pLow pHigh = do
  lo <- fromIntegral <$> peek pLow
  hi <- fromIntegral <$> peek pHigh
  return $ lo .|. (hi `shiftL` 32)

peekPtr :: Storable a => Ptr (Ptr a) -> Ptr a -> IO a
peekPtr pp _ = do
  p <- peek pp
  peek p

pokePtr :: Storable a => Ptr (Ptr a) -> Ptr a -> a -> IO ()
pokePtr pp p x = do
  poke p x
  poke pp p

pokeOr :: (Storable a, Bits a) => Ptr a -> a -> IO ()
pokeOr p x = do
  old <- peek p
  poke p (x .|. old)

data List16 =
  List16
  { l16_list :: !(Ptr ())
  , l16_max :: !(Word16)
  , l16_len :: !(Word16)
  }

instance Storable List16 where
  sizeOf _ = #{size List16}
  alignment _ = 8
  peek p = List16
           <$> #{peek List16, list} p
           <*> #{peek List16, max} p
           <*> #{peek List16, len} p
  poke p x = do
    #{poke List16, list} p (l16_list x)
    #{poke List16, max} p (l16_max x)
    #{poke List16, len} p (l16_len x)

getList16 :: Storable a => (Ptr () -> IO ()) -> IO [a]
getList16 f = do
  let maxLen = maxBound :: Word16
  allocaArray (fromIntegral maxLen) $ \storage -> do
    let lst = List16
              { l16_list = castPtr storage
              , l16_max = maxLen
              , l16_len = 0
              }
    with lst $ \p -> do
      f (castPtr p)
      lst' <- peek p
      peekArray (fromIntegral (l16_len lst')) storage

setList16 :: Storable a => Text -> [a] -> (Ptr () -> IO ()) -> IO ()
setList16 t x f = do
  withArrayLen x $ \len storage -> do
    len' <- castLen t len
    let lst = List16
              { l16_list = castPtr storage
              , l16_max = len'
              , l16_len = len'
              }
    with lst $ \p -> f (castPtr p)

pokeList16 :: Storable a => (Ptr List16, Word16, Ptr a, Text) -> [a] -> IO ()
pokeList16 (lp, maxLen, storage, name) ws = do
  len <- castLen' maxLen name (length ws)
  poke lp $ List16
    { l16_list = castPtr storage
    , l16_max = maxLen
    , l16_len = len
    }
  pokeArray storage ws

peekList16 :: Storable a => (Ptr List16, Word16, Ptr a, Text) -> IO [a]
peekList16 (lp, _, _, _) = do
  lst <- peek lp
  peekArray (fromIntegral $ l16_len lst) (castPtr $ l16_list lst)

data List8 =
  List8
  { l8_list :: !(Ptr ())
  , l8_max :: !(Word8)
  , l8_len :: !(Word8)
  }

instance Storable List8 where
  sizeOf _ = #{size List8}
  alignment _ = 8
  peek p = List8
           <$> #{peek List8, list} p
           <*> #{peek List8, max} p
           <*> #{peek List8, len} p
  poke p x = do
    #{poke List8, list} p (l8_list x)
    #{poke List8, max} p (l8_max x)
    #{poke List8, len} p (l8_len x)

getList8 :: Storable a => (Ptr () -> IO ()) -> IO [a]
getList8 f = do
  let maxLen = maxBound :: Word8
  allocaArray (fromIntegral maxLen) $ \storage -> do
    let lst = List8
              { l8_list = castPtr storage
              , l8_max = maxLen
              , l8_len = 0
              }
    with lst $ \p -> do
      f (castPtr p)
      lst' <- peek p
      peekArray (fromIntegral (l8_len lst')) storage

setList8 :: Storable a => Text -> [a] -> (Ptr () -> IO ()) -> IO ()
setList8 t x f = do
  withArrayLen x $ \len storage -> do
    len' <- castLen t len
    let lst = List8
              { l8_list = castPtr storage
              , l8_max = len'
              , l8_len = len'
              }
    with lst $ \p -> f (castPtr p)

pokeList8 :: Storable a => (Ptr List8, Word8, Ptr a, Text) -> [a] -> IO ()
pokeList8 (lp, maxLen, storage, name) ws = do
  len <- castLen' maxLen name (length ws)
  poke lp $ List8
    { l8_list = castPtr storage
    , l8_max = maxLen
    , l8_len = len
    }
  pokeArray storage ws

peekList8 :: Storable a => (Ptr List8, Word8, Ptr a, Text) -> IO [a]
peekList8 (lp, _, _, _) = do
  lst <- peek lp
  peekArray (fromIntegral $ l8_len lst) (castPtr $ l8_list lst)

-- | Gen2-specific per-tag data
newtype GEN2_TagData =
  GEN2_TagData
  { g2Pc :: ByteString -- ^ Tag PC
  } deriving (Eq, Ord, Show, Read)

instance Storable GEN2_TagData where
  sizeOf _ = #{size TMR_GEN2_TagData}
  alignment _ = 8

  peek p =
    GEN2_TagData
      <$> peekArrayAsByteString (#{ptr TMR_GEN2_TagData, pc} p) (#{ptr TMR_GEN2_TagData, pcByteCount} p)

  poke p x = do
    pokeArrayAsByteString "pc" #{const TMR_GEN2_MAX_PC_BYTE_COUNT} (#{ptr TMR_GEN2_TagData, pc} p) (#{ptr TMR_GEN2_TagData, pcByteCount} p) (g2Pc x)

-- | A record to represent RFID tags.
data TagData =
  TagData
  { tdEpc :: !ByteString -- ^ Tag EPC
  , tdProtocol :: !TagProtocol -- ^ Protocol of the tag
  , tdCrc :: !Word16 -- ^ Tag CRC
  , tdGen2 :: !(Maybe (GEN2_TagData)) -- ^ Gen2-specific tag information
  } deriving (Eq, Ord, Show, Read)

instance Storable TagData where
  sizeOf _ = #{size TMR_TagData}
  alignment _ = 8

  peek p =
    TagData
      <$> peekArrayAsByteString (#{ptr TMR_TagData, epc} p) (#{ptr TMR_TagData, epcByteCount} p)
      <*> (toTagProtocol <$> #{peek TMR_TagData, protocol} p)
      <*> #{peek TMR_TagData, crc} p
      <*> peekMaybe (peek) (== (#{const TMR_TAG_PROTOCOL_GEN2} :: RawTagProtocol)) (#{ptr TMR_TagData, u.gen2} p) (#{ptr TMR_TagData, protocol} p)

  poke p x = do
    pokeArrayAsByteString "epc" #{const TMR_MAX_EPC_BYTE_COUNT} (#{ptr TMR_TagData, epc} p) (#{ptr TMR_TagData, epcByteCount} p) (tdEpc x)
    #{poke TMR_TagData, protocol} p (fromTagProtocol $ tdProtocol x)
    #{poke TMR_TagData, crc} p (tdCrc x)
    pokeGen2TagData (#{ptr TMR_TagData, u.gen2} p) (#{ptr TMR_TagData, protocol} p) (tdGen2 x)

-- | The identity and state of a single GPIO pin.
data GpioPin =
  GpioPin
  { gpId :: !PinNumber -- ^ The ID number of the pin.
  , gpHigh :: !Bool -- ^ Whether the pin is in the high state.
  , gpOutput :: !Bool -- ^ The direction of the pin
  } deriving (Eq, Ord, Show, Read)

instance Storable GpioPin where
  sizeOf _ = #{size TMR_GpioPin}
  alignment _ = 8

  peek p =
    GpioPin
      <$> #{peek TMR_GpioPin, id} p
      <*> (toBool' <$> #{peek TMR_GpioPin, high} p)
      <*> (toBool' <$> #{peek TMR_GpioPin, output} p)

  poke p x = do
    #{poke TMR_GpioPin, id} p (gpId x)
    #{poke TMR_GpioPin, high} p (fromBool' $ gpHigh x)
    #{poke TMR_GpioPin, output} p (fromBool' $ gpOutput x)

-- | A record to represent a read of an RFID tag.
-- Provides access to the metadata of the read event,
-- such as the time of the read, the antenna that read the tag,
-- and the number of times the tag was seen by the air protocol.
data TagReadData =
  TagReadData
  { trTag :: !TagData -- ^ The tag that was read
  , trMetadataFlags :: ![MetadataFlag] -- ^ The set of metadata items below that are valid
  , trPhase :: !Word16 -- ^ Tag response phase
  , trAntenna :: !AntennaPort -- ^ Antenna where the tag was read
  , trGpio :: ![GpioPin] -- ^ State of GPIO pins at the moment of the tag read
  , trReadCount :: !Word32 -- ^ Number of times the tag was read
  , trRssi :: !Int32 -- ^ Strength of the signal received from the tag
  , trFrequency :: !Word32 -- ^ RF carrier frequency the tag was read with
  , trTimestamp :: !MillisecondsSinceEpoch -- ^ Absolute time of the read, in milliseconds since 1\/1\/1970 UTC
  , trData :: !ByteString -- ^ Data read from the tag
  , trEpcMemData :: !ByteString -- ^ Read EPC bank data bytes  (Only if 'GEN2_BANK_EPC' is present in 'opExtraBanks')
  , trTidMemData :: !ByteString -- ^ Read TID bank data bytes  (Only if 'GEN2_BANK_TID' is present in 'opExtraBanks')
  , trUserMemData :: !ByteString -- ^ Read USER bank data bytes  (Only if 'GEN2_BANK_USER' is present in 'opExtraBanks')
  , trReservedMemData :: !ByteString -- ^ Read RESERVED bank data bytes  (Only if 'GEN2_BANK_RESERVED' is present in 'opExtraBanks')
  } deriving (Eq, Ord, Show, Read)

instance Storable TagReadData where
  sizeOf _ = #{size TMR_TagReadData}
  alignment _ = 8

  peek p =
    TagReadData
      <$> #{peek TMR_TagReadData, tag} p
      <*> (unpackFlags16 <$> #{peek TMR_TagReadData, metadataFlags} p)
      <*> #{peek TMR_TagReadData, phase} p
      <*> #{peek TMR_TagReadData, antenna} p
      <*> peekArrayAsList (#{ptr TMR_TagReadData, gpio} p) (#{ptr TMR_TagReadData, gpioCount} p)
      <*> #{peek TMR_TagReadData, readCount} p
      <*> #{peek TMR_TagReadData, rssi} p
      <*> #{peek TMR_TagReadData, frequency} p
      <*> peekSplit64 (#{ptr TMR_TagReadData, timestampLow} p) (#{ptr TMR_TagReadData, timestampHigh} p)
      <*> peekListAsByteString (#{ptr TMR_TagReadData, data} p)
      <*> peekListAsByteString (#{ptr TMR_TagReadData, epcMemData} p)
      <*> peekListAsByteString (#{ptr TMR_TagReadData, tidMemData} p)
      <*> peekListAsByteString (#{ptr TMR_TagReadData, userMemData} p)
      <*> peekListAsByteString (#{ptr TMR_TagReadData, reservedMemData} p)

  poke p x = error "poke not implemented for TagReadData"

-- | An operation that can be performed on a tag.  Can be used
-- as an argument to 'System.Hardware.MercuryApi.executeTagOp',
-- or can be embedded into a 'System.Hardware.MercuryApi.ReadPlan'.
-- (However, on the M6e Nano, only 'TagOp_GEN2_ReadData' may be
-- embedded in a 'System.Hardware.MercuryApi.ReadPlan'.)
data TagOp =
    TagOp_GEN2_ReadData
    { opBank :: !GEN2_Bank -- ^ Gen2 memory bank to operate on
    , opExtraBanks :: ![GEN2_Bank] -- ^ Additional Gen2 memory banks to read from  (seems buggy, though; I\'ve had strange results with it)
    , opWordAddress :: !Word32 -- ^ Word address to start at
    , opLen :: !Word8 -- ^ Number of words to read
    }
  | TagOp_GEN2_WriteTag
    { opEpc :: !TagData -- ^ Tag EPC
    }
  | TagOp_GEN2_WriteData
    { opBank :: !GEN2_Bank -- ^ Gen2 memory bank to operate on
    , opWordAddress :: !Word32 -- ^ Word address to start at
    , opData :: ![Word16] -- ^ Data to write
    }
  | TagOp_GEN2_Lock
    { opMask :: ![GEN2_LockBits] -- ^ Bitmask indicating which lock bits to change
    , opAction :: ![GEN2_LockBits] -- ^ New values of each bit specified in the mask
    , opAccessPassword :: !GEN2_Password -- ^ Access Password to use to lock the tag
    }
  | TagOp_GEN2_Kill
    { opPassword :: !GEN2_Password -- ^ Kill password to use to kill the tag
    }
  | TagOp_GEN2_BlockWrite
    { opBank :: !GEN2_Bank -- ^ Gen2 memory bank to operate on
    , opWordPtr :: !Word32 -- ^ The word address to start at
    , opData :: ![Word16] -- ^ The data to write
    }
  | TagOp_GEN2_BlockErase
    { opBank :: !GEN2_Bank -- ^ Gen2 memory bank to operate on
    , opWordPtr :: !Word32 -- ^ The word address to start at
    , opWordCount :: !Word8 -- ^ Number of words to erase
    }
  | TagOp_GEN2_BlockPermaLock
    { opBank :: !GEN2_Bank -- ^ Gen2 memory bank to operate on
    , opBlockPtr :: !Word32 -- ^ The starting word address to lock
    , opReadWrite :: !ReadWrite -- ^ Read lock status or write it?
    }
  deriving (Eq, Ord, Show, Read)

instance Storable TagOp where
  sizeOf _ = #{size TagOpEtc}
  alignment _ = 8

  peek p = do
    x <- #{peek TagOpEtc, tagop.type} p :: IO #{type TMR_TagOpType}
    case x of
      #{const TMR_TAGOP_GEN2_READDATA} -> do
        TagOp_GEN2_ReadData
          <$> ((toBank . (.&. 3)) <$> #{peek TagOpEtc, tagop.u.gen2.u.readData.bank} p)
          <*> (unpackExtraBanks <$> #{peek TagOpEtc, tagop.u.gen2.u.readData.bank} p)
          <*> #{peek TagOpEtc, tagop.u.gen2.u.readData.wordAddress} p
          <*> #{peek TagOpEtc, tagop.u.gen2.u.readData.len} p
      #{const TMR_TAGOP_GEN2_WRITETAG} -> do
        TagOp_GEN2_WriteTag
          <$> peekPtr (#{ptr TagOpEtc, tagop.u.gen2.u.writeTag.epcptr} p) (#{ptr TagOpEtc, u.epc} p)
      #{const TMR_TAGOP_GEN2_WRITEDATA} -> do
        TagOp_GEN2_WriteData
          <$> ((toBank . (.&. 3)) <$> #{peek TagOpEtc, tagop.u.gen2.u.writeData.bank} p)
          <*> #{peek TagOpEtc, tagop.u.gen2.u.writeData.wordAddress} p
          <*> peekListAsList (#{ptr TagOpEtc, tagop.u.gen2.u.writeData.data} p) (#{ptr TagOpEtc, u.data16} p)
      #{const TMR_TAGOP_GEN2_LOCK} -> do
        TagOp_GEN2_Lock
          <$> (unpackLockBits16 <$> #{peek TagOpEtc, tagop.u.gen2.u.lock.mask} p)
          <*> (unpackLockBits16 <$> #{peek TagOpEtc, tagop.u.gen2.u.lock.action} p)
          <*> #{peek TagOpEtc, tagop.u.gen2.u.lock.accessPassword} p
      #{const TMR_TAGOP_GEN2_KILL} -> do
        TagOp_GEN2_Kill
          <$> #{peek TagOpEtc, tagop.u.gen2.u.kill.password} p
      #{const TMR_TAGOP_GEN2_BLOCKWRITE} -> do
        TagOp_GEN2_BlockWrite
          <$> ((toBank . (.&. 3)) <$> #{peek TagOpEtc, tagop.u.gen2.u.blockWrite.bank} p)
          <*> #{peek TagOpEtc, tagop.u.gen2.u.blockWrite.wordPtr} p
          <*> peekListAsList (#{ptr TagOpEtc, tagop.u.gen2.u.blockWrite.data} p) (#{ptr TagOpEtc, u.data16} p)
      #{const TMR_TAGOP_GEN2_BLOCKERASE} -> do
        TagOp_GEN2_BlockErase
          <$> ((toBank . (.&. 3)) <$> #{peek TagOpEtc, tagop.u.gen2.u.blockErase.bank} p)
          <*> #{peek TagOpEtc, tagop.u.gen2.u.blockErase.wordPtr} p
          <*> #{peek TagOpEtc, tagop.u.gen2.u.blockErase.wordCount} p
      #{const TMR_TAGOP_GEN2_BLOCKPERMALOCK} -> do
        rw <- #{peek TagOpEtc, tagop.u.gen2.u.blockPermaLock.readLock} p
        ws <- peekListAsList (#{ptr TagOpEtc, tagop.u.gen2.u.blockPermaLock.mask} p) (#{ptr TagOpEtc, u.data16} p)
        TagOp_GEN2_BlockPermaLock
          <$> ((toBank . (.&. 3)) <$> #{peek TagOpEtc, tagop.u.gen2.u.blockPermaLock.bank} p)
          <*> #{peek TagOpEtc, tagop.u.gen2.u.blockPermaLock.blockPtr} p
          <*> (return $ toReadWrite (rw, ws))

  poke p x@(TagOp_GEN2_ReadData {}) = do
    #{poke TagOpEtc, tagop.type} p (#{const TMR_TAGOP_GEN2_READDATA} :: #{type TMR_TagOpType})
    #{poke TagOpEtc, tagop.u.gen2.u.readData.bank} p (fromBank $ opBank x)
    pokeOr (#{ptr TagOpEtc, tagop.u.gen2.u.readData.bank} p) (packExtraBanks $ opExtraBanks x)
    #{poke TagOpEtc, tagop.u.gen2.u.readData.wordAddress} p (opWordAddress x)
    #{poke TagOpEtc, tagop.u.gen2.u.readData.len} p (opLen x)

  poke p x@(TagOp_GEN2_WriteTag {}) = do
    #{poke TagOpEtc, tagop.type} p (#{const TMR_TAGOP_GEN2_WRITETAG} :: #{type TMR_TagOpType})
    pokePtr (#{ptr TagOpEtc, tagop.u.gen2.u.writeTag.epcptr} p) (#{ptr TagOpEtc, u.epc} p) (opEpc x)

  poke p x@(TagOp_GEN2_WriteData {}) = do
    #{poke TagOpEtc, tagop.type} p (#{const TMR_TAGOP_GEN2_WRITEDATA} :: #{type TMR_TagOpType})
    #{poke TagOpEtc, tagop.u.gen2.u.writeData.bank} p (fromBank $ opBank x)
    #{poke TagOpEtc, tagop.u.gen2.u.writeData.wordAddress} p (opWordAddress x)
    pokeListAsList "data" #{const GLUE_MAX_DATA16} (#{ptr TagOpEtc, tagop.u.gen2.u.writeData.data} p) (#{ptr TagOpEtc, u.data16} p) (opData x)

  poke p x@(TagOp_GEN2_Lock {}) = do
    #{poke TagOpEtc, tagop.type} p (#{const TMR_TAGOP_GEN2_LOCK} :: #{type TMR_TagOpType})
    #{poke TagOpEtc, tagop.u.gen2.u.lock.mask} p (packLockBits16 $ opMask x)
    #{poke TagOpEtc, tagop.u.gen2.u.lock.action} p (packLockBits16 $ opAction x)
    #{poke TagOpEtc, tagop.u.gen2.u.lock.accessPassword} p (opAccessPassword x)

  poke p x@(TagOp_GEN2_Kill {}) = do
    #{poke TagOpEtc, tagop.type} p (#{const TMR_TAGOP_GEN2_KILL} :: #{type TMR_TagOpType})
    #{poke TagOpEtc, tagop.u.gen2.u.kill.password} p (opPassword x)

  poke p x@(TagOp_GEN2_BlockWrite {}) = do
    #{poke TagOpEtc, tagop.type} p (#{const TMR_TAGOP_GEN2_BLOCKWRITE} :: #{type TMR_TagOpType})
    #{poke TagOpEtc, tagop.u.gen2.u.blockWrite.bank} p (fromBank $ opBank x)
    #{poke TagOpEtc, tagop.u.gen2.u.blockWrite.wordPtr} p (opWordPtr x)
    pokeListAsList "data" #{const GLUE_MAX_DATA16} (#{ptr TagOpEtc, tagop.u.gen2.u.blockWrite.data} p) (#{ptr TagOpEtc, u.data16} p) (opData x)

  poke p x@(TagOp_GEN2_BlockErase {}) = do
    #{poke TagOpEtc, tagop.type} p (#{const TMR_TAGOP_GEN2_BLOCKERASE} :: #{type TMR_TagOpType})
    #{poke TagOpEtc, tagop.u.gen2.u.blockErase.bank} p (fromBank $ opBank x)
    #{poke TagOpEtc, tagop.u.gen2.u.blockErase.wordPtr} p (opWordPtr x)
    #{poke TagOpEtc, tagop.u.gen2.u.blockErase.wordCount} p (opWordCount x)

  poke p x@(TagOp_GEN2_BlockPermaLock {}) = do
    let (rw, ws) = fromReadWrite $ opReadWrite x
    #{poke TagOpEtc, tagop.type} p (#{const TMR_TAGOP_GEN2_BLOCKPERMALOCK} :: #{type TMR_TagOpType})
    #{poke TagOpEtc, tagop.u.gen2.u.blockPermaLock.readLock} p rw
    #{poke TagOpEtc, tagop.u.gen2.u.blockPermaLock.bank} p (fromBank $ opBank x)
    #{poke TagOpEtc, tagop.u.gen2.u.blockPermaLock.blockPtr} p (opBlockPtr x)
    pokeListAsList "mask" #{const GLUE_MAX_DATA16} (#{ptr TagOpEtc, tagop.u.gen2.u.blockPermaLock.mask} p) (#{ptr TagOpEtc, u.data16} p) ws

tagOpName :: TagOp -> Text
tagOpName TagOp_GEN2_ReadData {} = "TagOp_GEN2_ReadData"
tagOpName TagOp_GEN2_WriteTag {} = "TagOp_GEN2_WriteTag"
tagOpName TagOp_GEN2_WriteData {} = "TagOp_GEN2_WriteData"
tagOpName TagOp_GEN2_Lock {} = "TagOp_GEN2_Lock"
tagOpName TagOp_GEN2_Kill {} = "TagOp_GEN2_Kill"
tagOpName TagOp_GEN2_BlockWrite {} = "TagOp_GEN2_BlockWrite"
tagOpName TagOp_GEN2_BlockErase {} = "TagOp_GEN2_BlockErase"
tagOpName TagOp_GEN2_BlockPermaLock {} = "TagOp_GEN2_BlockPermaLock"