{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE RecordWildCards #-}
module Network.VRPN (
Device(..)
, RemoteDevice
, PositionCallback
, VelocityCallback
, AccelerationCallback
, ButtonCallback
, AnalogCallback
, DialCallback
, ExitCallback
, openDevice
, closeDevice
, withDevices
, mainLoop
, mainLoops
, TimeVal(..)
, sleep
) where
import Control.Monad (unless, when)
import Foreign.C.String (CString, withCString)
import Foreign.C.Types (CDouble(..), CInt(..), CLong(..))
import Foreign.Concurrent (newForeignPtr)
import Foreign.Marshal.Array (peekArray)
import Foreign.ForeignPtr (ForeignPtr, finalizeForeignPtr, withForeignPtr)
import Foreign.Ptr (FunPtr, Ptr, freeHaskellFunPtr, nullFunPtr)
data Device s b d a =
Tracker
{
device :: String
, positionCallback :: Maybe (PositionCallback s a)
, velocityCallback :: Maybe (VelocityCallback s a)
, accelerationCallback :: Maybe (AccelerationCallback s a)
}
| Button
{
device :: String
, buttonCallback :: Maybe (ButtonCallback b)
}
| Analog
{
device :: String
, analogCallback :: Maybe (AnalogCallback a)
}
| Dial
{
device :: String
, dialCallback :: Maybe (DialCallback d a)
}
data TimeVal =
TimeVal
{
timeSeconds :: Int
, timeMicroSeconds :: Int
}
deriving (Eq, Ord, Read, Show)
type PositionCallback s a = TimeVal
-> s
-> (a, a, a)
-> (a, a, a, a)
-> IO ()
type PositionCallback' = CLong
-> CLong
-> CInt
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> IO ()
foreign import ccall "wrapper"
wrapPositionCallback :: PositionCallback' -> IO (FunPtr PositionCallback')
makePositionCallback :: (Enum s, RealFloat a)
=> PositionCallback s a
-> PositionCallback'
makePositionCallback callback seconds microseconds sensor px py pz ox oy oz ow =
callback
(TimeVal (fromEnum seconds) (fromEnum microseconds))
(toEnum $ fromEnum sensor)
(realToFrac px, realToFrac py, realToFrac pz)
(realToFrac ox, realToFrac oy, realToFrac oz, realToFrac ow)
type VelocityCallback s a = TimeVal
-> s
-> (a, a, a)
-> (a, a, a, a)
-> a
-> IO ()
type VelocityCallback' = CLong
-> CLong
-> CInt
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> IO ()
foreign import ccall "wrapper"
wrapVelocityCallback :: VelocityCallback' -> IO (FunPtr VelocityCallback')
makeVelocityCallback :: (Enum s, RealFloat a)
=> VelocityCallback s a
-> VelocityCallback'
makeVelocityCallback callback seconds microseconds sensor vx vy vz ox oy oz ow dt =
callback
(TimeVal (fromEnum seconds) (fromEnum microseconds))
(toEnum $ fromEnum sensor)
(realToFrac vx, realToFrac vy, realToFrac vz)
(realToFrac ox, realToFrac oy, realToFrac oz, realToFrac ow)
(realToFrac dt)
type AccelerationCallback s a = TimeVal
-> s
-> (a, a, a)
-> (a, a, a, a)
-> a
-> IO ()
type AccelerationCallback' = CLong
-> CLong
-> CInt
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> CDouble
-> IO ()
foreign import ccall "wrapper"
wrapAccelerationCallback :: AccelerationCallback' -> IO (FunPtr AccelerationCallback')
makeAccelerationCallback :: (Enum s, RealFloat a)
=> AccelerationCallback s a
-> AccelerationCallback'
makeAccelerationCallback callback seconds microseconds sensor ax ay az ox oy oz ow dt =
callback
(TimeVal (fromEnum seconds) (fromEnum microseconds))
(toEnum $ fromEnum sensor)
(realToFrac ax, realToFrac ay, realToFrac az)
(realToFrac ox, realToFrac oy, realToFrac oz, realToFrac ow)
(realToFrac dt)
type ButtonCallback b = TimeVal
-> b
-> Bool
-> IO ()
type ButtonCallback' = CLong
-> CLong
-> CInt
-> CInt
-> IO ()
foreign import ccall "wrapper"
wrapButtonCallback :: ButtonCallback' -> IO (FunPtr ButtonCallback')
makeButtonCallback :: Enum b
=> ButtonCallback b
-> ButtonCallback'
makeButtonCallback callback seconds microseconds button state =
callback
(TimeVal (fromEnum seconds) (fromEnum microseconds))
(toEnum $ fromEnum button)
(state /= 0)
type AnalogCallback a = TimeVal
-> [a]
-> IO ()
type AnalogCallback' = CLong
-> CLong
-> CInt
-> Ptr CDouble
-> IO ()
foreign import ccall "wrapper"
wrapAnalogCallback :: AnalogCallback' -> IO (FunPtr AnalogCallback')
makeAnalogCallback :: RealFloat a
=> AnalogCallback a
-> AnalogCallback'
makeAnalogCallback callback seconds microseconds n ptr =
do
values <- peekArray (fromEnum n) ptr
callback
(TimeVal (fromEnum seconds) (fromEnum microseconds))
(map realToFrac values)
type DialCallback d a = TimeVal
-> d
-> a
-> IO ()
type DialCallback' = CLong
-> CLong
-> CInt
-> CDouble
-> IO ()
foreign import ccall "wrapper"
wrapDialCallback :: DialCallback' -> IO (FunPtr DialCallback')
makeDialCallback :: (Enum d, RealFloat a)
=> DialCallback d a
-> DialCallback'
makeDialCallback callback seconds microseconds dial value =
callback
(TimeVal (fromEnum seconds) (fromEnum microseconds))
(toEnum $ fromEnum dial)
(realToFrac value)
type ExitCallback = IO Bool
data Remote
foreign import ccall "makeTracker"
makeTracker :: CString -> FunPtr PositionCallback' -> FunPtr VelocityCallback' -> FunPtr AccelerationCallback' -> IO (Ptr Remote)
foreign import ccall "mainloopTracker"
mainloopTracker :: Ptr Remote -> IO ()
foreign import ccall "deleteTracker"
deleteTracker :: Ptr Remote -> IO ()
foreign import ccall "makeButton"
makeButton :: CString -> FunPtr ButtonCallback' -> IO (Ptr Remote)
foreign import ccall "mainloopButton"
mainloopButton :: Ptr Remote -> IO ()
foreign import ccall "deleteButton"
deleteButton :: Ptr Remote -> IO ()
foreign import ccall "makeAnalog"
makeAnalog :: CString -> FunPtr AnalogCallback' -> IO (Ptr Remote)
foreign import ccall "mainloopAnalog"
mainloopAnalog :: Ptr Remote -> IO ()
foreign import ccall "deleteAnalog"
deleteAnalog :: Ptr Remote -> IO ()
foreign import ccall "makeDial"
makeDial :: CString -> FunPtr DialCallback' -> IO (Ptr Remote)
foreign import ccall "mainloopDial"
mainloopDial :: Ptr Remote -> IO ()
foreign import ccall "deleteDial"
deleteDial :: Ptr Remote -> IO ()
foreign import ccall "vrpnSleep"
vrpnSleep :: CDouble -> IO ()
sleep :: RealFloat a => a
-> IO ()
sleep = vrpnSleep . realToFrac
newtype RemoteDevice = RemoteDevice (ForeignPtr Remote, ForeignPtr Remote -> IO ())
openDevice :: (Enum s, Enum b, Enum d, RealFloat a)
=> Device s b d a
-> IO RemoteDevice
openDevice Tracker{..} =
do
positionCallback' <- maybe (return nullFunPtr) (wrapPositionCallback . makePositionCallback ) positionCallback
velocityCallback' <- maybe (return nullFunPtr) (wrapVelocityCallback . makeVelocityCallback ) velocityCallback
accelerationCallback' <- maybe (return nullFunPtr) (wrapAccelerationCallback . makeAccelerationCallback) accelerationCallback
ptr <-
withCString device $ \device' ->
makeTracker device' positionCallback' velocityCallback' accelerationCallback'
ptr' <-
newForeignPtr ptr $ do
deleteTracker ptr
freeHaskellFunPtr positionCallback'
freeHaskellFunPtr velocityCallback'
freeHaskellFunPtr accelerationCallback'
return $ RemoteDevice (ptr', flip withForeignPtr mainloopTracker)
openDevice Button{..} =
do
buttonCallback' <- maybe (return nullFunPtr) (wrapButtonCallback . makeButtonCallback) buttonCallback
ptr <-
withCString device $ \device' ->
makeButton device' buttonCallback'
ptr' <-
newForeignPtr ptr $ do
deleteButton ptr
freeHaskellFunPtr buttonCallback'
return $ RemoteDevice (ptr', flip withForeignPtr mainloopButton)
openDevice Analog{..} =
do
analogCallback' <- maybe (return nullFunPtr) (wrapAnalogCallback . makeAnalogCallback) analogCallback
ptr <-
withCString device $ \device' ->
makeAnalog device' analogCallback'
ptr' <-
newForeignPtr ptr $ do
deleteAnalog ptr
freeHaskellFunPtr analogCallback'
return $ RemoteDevice (ptr', flip withForeignPtr mainloopAnalog)
openDevice Dial{..} =
do
dialCallback' <- maybe (return nullFunPtr) (wrapDialCallback . makeDialCallback) dialCallback
ptr <-
withCString device $ \device' ->
makeDial device' dialCallback'
ptr' <-
newForeignPtr ptr $ do
deleteDial ptr
freeHaskellFunPtr dialCallback'
return $ RemoteDevice (ptr', flip withForeignPtr mainloopDial)
closeDevice :: RemoteDevice
-> IO ()
closeDevice (RemoteDevice (device, _)) = finalizeForeignPtr device
withDevices :: (Enum s, Enum b, Enum d, RealFloat a)
=> [Device s b d a]
-> ([RemoteDevice] -> IO ())
-> IO ()
withDevices devices operation =
do
remotes <- mapM openDevice devices
operation remotes
mapM_ closeDevice remotes
mainLoop :: RemoteDevice
-> IO ()
mainLoop (RemoteDevice (device, mainloopDevice)) = mainloopDevice device
mainLoops :: RealFloat a
=> ExitCallback
-> a
-> [RemoteDevice]
-> IO ()
mainLoops exitCallback milliseconds devices =
do
mapM_ mainLoop devices
when (milliseconds > 0)
$ sleep milliseconds
exit <- exitCallback
unless exit
$ mainLoops exitCallback milliseconds devices