ble-0.4.2: examples/HeartRate.hs
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
module Main (main) where
-- This example contains a demonstration of the standard Heart Rate Service
-- (HRS). It serves as an examples of using notifications.
--
-- The program takes a command line argument to allow using controllers other
-- than the default (hci0). If you have multiple controllers on a single
-- machine (either hardware or virtual with something such as 'btvirt'), you
-- can thus test this service with the heart-rate client executable.
import Bluetooth
import Bluetooth.Internal.Interfaces (bluezPath)
import Control.Concurrent
import Control.Monad
import Control.Monad.IO.Class
import Data.IORef
import Data.Monoid
import Data.String (fromString)
import Data.Word (Word8)
import System.Log.Logger
import System.Random (randomRIO)
import qualified Data.ByteString as BS
import qualified Options.Applicative as Opt
main :: IO ()
main = do
options <- Opt.execParser opts
writeIORef bluezPath $ fromString $ "/org/bluez/" ++ controller options
when (verbose options) $ updateGlobalLogger rootLoggerName (setLevel DEBUG)
heartRateRef <- newIORef 0
let s = AppState heartRateRef
conn <- connect
x <- runBluetoothM (registerAndAdvertiseApplication $ app s) conn
registered <- case x of
Right registered -> do
putStrLn "Started BLE Heart Rate Service application!"
return registered
Left e -> error $ "Error starting application" ++ show e
forever $ do
newValue <- randomRIO (50, 150)
writeIORef heartRateRef newValue
res <- runBluetoothM (triggerNotification registered $ heartRateMeasurement s) conn
case res of
Right () -> putStrLn "Notification sent!"
Left e -> error $ "Bluetooth error:\n" ++ show e
-- We update the value every ten seconds
threadDelay (10 ^ 7)
data AppState = AppState
{ currentHeartRate :: IORef Word8
}
data Options = Options
{ controller :: String
, verbose :: Bool
}
opts :: Opt.ParserInfo Options
opts = Opt.info
(parser Opt.<**> Opt.helper)
( Opt.fullDesc
<> Opt.progDesc "Print a greeting for TARGET"
<> Opt.header "hello - a test for optparse-applicative" )
parser :: Opt.Parser Options
parser = Options
<$> Opt.strOption
( Opt.long "controller"
<> Opt.metavar "HCIX"
<> Opt.showDefault
<> Opt.value "hci0"
<> Opt.help "Controller to use")
<*> Opt.switch
( Opt.long "verbose"
<> Opt.short 'v'
<> Opt.help "Print debugging info to console")
app :: AppState -> Application
app appState
= "/com/turingjump/example/hrs"
& services .~ [heartRateService appState]
heartRateService :: AppState -> Service 'Local
heartRateService appState
= "0000180d-0000-1000-8000-00805f9b34fb"
& characteristics .~ [heartRateMeasurement appState, bodySensorLocation]
heartRateMeasurement :: AppState -> CharacteristicBS 'Local
heartRateMeasurement appState
= "00002a37-0000-1000-8000-00805f9b34fb"
& readValue ?~ fmap heartRateToBS (liftIO readV)
-- Even though we add a @writeValue@, this does not mean that the
-- characteristic is writable (for that, we would need to add the CPWrite
-- property to it). Instead, we can use the writeValue internally to
-- update the value and send notifications each time the value is changed.
& writeValue ?~ encodeWrite (liftIO <$> write)
& properties .~ [ CPNotify, CPRead, CPWrite ]
where
readV = do
putStrLn "Reading value"
readIORef $ currentHeartRate appState
write v = do
putStrLn "Received value"
writeIORef (currentHeartRate appState) v
putStrLn "Wrote value"
return True
heartRateToBS :: Word8 -> BS.ByteString
heartRateToBS i = BS.singleton 0x06 <> BS.singleton i
bodySensorLocation :: CharacteristicBS 'Local
bodySensorLocation
= "00002a38-0000-1000-8000-00805f9b34fb"
& readValue ?~ encodeRead (return (0x01 :: Word))
& properties .~ [CPRead]