packages feed

bglib-2.0.0.0: examples/bgapitest.hs

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

import           BGLib.Commands
import           BGLib.Types
import           Control.Concurrent
import           Control.Concurrent.Async
import           Control.Concurrent.STM
import           Control.Monad.IO.Class
import           Control.Monad.Reader
import qualified Data.ByteString.Char8 as BSS
import           Data.Semigroup ((<>))
import           Options.Applicative
import           Prelude hiding (print, putStrLn)
import qualified Prelude as P
import           System.Exit
import           System.IO hiding (print, putStrLn)

-- This is our monad stack, most of the application runs inside this.
type AppM env a = ReaderT env IO a

-- We store the command line options here
data AppOptions = AppOptions
    { appOptSerialPort :: String
    , appOptDebug      :: Bool
    }

-- The data structure will be our "env", the environment stored in the
-- ReaderT env IO monad stack
data App = App
    { appOptions :: AppOptions
    , appHandle  :: Handle
    , appBGChan  :: TChan BgPacket
    }

-- Instances for our environment to properly serve the library
-- functions.

instance HasHandle App where
    getHandle = appHandle

instance HasBGChan App where
    getBGChan = appBGChan
    updateBGChan chan app = app{ appBGChan = chan }

instance HasDebug App where
    getDebug = appOptDebug . appOptions

-- Command line parser
optParser :: Parser AppOptions
optParser = AppOptions
        <$> argument str 
            (  metavar "PORT"
            <> help "Serial port" )
        <*> switch
            ( long "debug"
            <> short 'd'
            <> help "Whether to be quiet" )

-- Takes an environment and runs a program with it inside the IO monad.
execApp :: env -> AppM env a -> IO a
execApp = flip runReaderT

-- RUns a program in a new thread inside out AppM stack
forkApp :: AppM env () -> AppM env ThreadId
forkApp act = do
    env <- ask
    liftIO $ forkIO $ execApp env act

-- Can be used to wait for an event handler to return a value.
-- Waiting for a specific BLE device advertisement to appear for
-- example. Timeout is in microseconds.
withTimeOut :: Int -> AppM env a -> AppM env (Maybe a)
withTimeOut t a = do
    env <- ask
    res <- liftIO $ race (threadDelay t) (execApp env a)
    return $ case res of
        Left () -> Nothing
        Right x -> Just x

-- A few lifted functions

putStrLn :: MonadIO m => String -> m ()
putStrLn = liftIO . P.putStrLn

print :: (MonadIO m, Show a) => a -> m ()
print = liftIO . P.print

main :: IO ()
main = do
    -- Run the command line parser
    appOpts <- execParser $
        info
            ( optParser <**> helper )
            (  fullDesc
            <> progDesc "Execute a short battery of test on port PORT"
            <> header "bgapitest - a short text / example for haskell-bglib"
            )

    -- Build the application environment
    -- With certain hardware, you probably want to set baud rate,
    -- stop and parity bits. You can extract the file descriptor
    -- from the Handle and use System.Posix.Terminal functions
    -- to do that, or use the Serial library to open a serial port
    -- and build a Handle out of the file descriptor of the serial
    -- port.
    app <- App
        <$> return appOpts
        <*> openFile (appOptSerialPort appOpts) ReadWriteMode
        <*> atomically newBroadcastTChan

    -- Run the application
    execApp app $ do

        -- Register an event handler for protocol errors.
        -- Event handlers are blocking. We use forkApp to make it
        -- "run in the background".
        -- The command 'packetBlock' creates a barrier, so new packets
        -- after this are guaranteed to be picked up by the event handlers
        -- in the block.
        packetBlock_ $ forkApp $ evtSystemProtocolError $ \reason -> do
            liftIO $ die $ "*** PROTOCOL ERROR " ++ show reason

        -- Starts a thread that keeps reading packets from the serial port,
        -- pushing them to the broadcast TChan
        startPacketReader

        putStrLn "Running hello"
        systemHello
        putStrLn "If you can read this, we're fine. :)"
        putStrLn ""

        putStrLn "Getting system information:"
        (major, minor, patch, build, llVersion, protocolVersion, hw) <- systemGetInfo
        putStrLn $ "Major version:      " ++ show major
        putStrLn $ "Minor version:      " ++ show minor
        putStrLn $ "Patch version:      " ++ show patch
        putStrLn $ "Build Version:      " ++ show build
        putStrLn $ "Link Layer version: " ++ show llVersion
        putStrLn $ "Protocol version:   " ++ show protocolVersion
        putStrLn $ "Hardware version:   " ++ show hw
        putStrLn ""

        putStrLn "We should get a \"not connected\" error:"
        attclientAttributeWrite 0 0 "e" >>= print
        putStrLn ""

        putStrLn "Getting Bluetooth Address:"
        systemAddressGet >>= print
        putStrLn ""

        putStrLn "Running some encryption-decription tests"
        putStrLn ""

        let aeskey = "abcdefgh12345678"
        putStrLn $ "Setting AES key to " ++ aeskey
        systemAesSetkey $ toUInt8Array $ BSS.pack $ aeskey
        putStrLn ""

        let plaintext = "This is plain"
        putStrLn $ "Encrypting: " ++ plaintext
        encrypted <- systemAesEncrypt $ toUInt8Array $ BSS.pack $ plaintext
        putStrLn $ "Encrypted: " ++ bsShowHex (fromUInt8Array encrypted)
        putStrLn ""

        putStrLn $ "Decrypting"
        decrypted <- systemAesDecrypt encrypted
        putStrLn $ "Decrypted: " ++ BSS.unpack (fromUInt8Array decrypted)
        putStrLn ""
        
        packetBlock_ $ do
            _ <- gapDiscover GapDiscoverGeneric

            -- Register an event handler for scan responses. Can be done anywhere.
            -- The handler forks a thread that runs forever, and can be terminated
            -- later if necessary.
            _ <- withTimeOut 5000000 $ evtGapScanResponse $ \rssi _ sender _ _ _ -> do
                    print rssi
                    print sender
                    putStrLn ""
                    return $ Nothing -- We'd like to listen to further events.

            gapEndProcedure

        putStrLn "Let's cause trouble:"
        h <- askHandle
        _ <- liftIO $ BSS.hPut h "a"
        liftIO $ threadDelay 2000000