packages feed

hpio-0.9.0.0: examples/Sysfs.hs

{-|

This program demonstrates how to use the native Linux @sysfs@ GPIO
implementation directly, without using the
'System.GPIO.Monad.MonadGpio' monad class.

-}

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

-- Protolude uses <> and options from Semigroups, but
-- optparse-applicative hasn't caught up yet.
import Protolude hiding ((<>))
import Data.Monoid ((<>))
import Data.Text (unwords)
import Options.Applicative
       (Parser, argument, auto, command, execParser, fullDesc, header,
        help, helper, hsubparser, info, long, metavar, progDesc, short,
        showDefault, value)
import qualified Options.Applicative as Options (option)
import System.GPIO.Linux.Sysfs.IO (SysfsIOT(..))
import System.GPIO.Linux.Sysfs.Monad
       (availablePins, exportPin, pollPinValueTimeout, readPinValue,
        unexportPin, writePinDirection, writePinEdge, writePinValue)
import System.GPIO.Linux.Sysfs.Types (SysfsEdge(..))
import System.GPIO.Types (Pin, PinDirection(..), invertValue)

data GlobalOptions =
  GlobalOptions {_cmd :: !Command}

data Command
  = ListPins
  | ReadEdge ReadEdgeOptions

listPinsCmd :: Parser Command
listPinsCmd = pure ListPins

data ReadEdgeOptions =
  ReadEdgeOptions {_period :: !Int
                  ,_edge :: !SysfsEdge
                  ,_timeout :: !Int
                  ,_outputPin :: !Pin
                  ,_inputPin :: !Pin}

readEdgeCmd :: Parser Command
readEdgeCmd = ReadEdge <$> readEdgeOptions

oneSecond :: Int
oneSecond = 1 * 1000000

readEdgeOptions :: Parser ReadEdgeOptions
readEdgeOptions =
  ReadEdgeOptions <$>
    Options.option auto (long "period" <>
                         short 'p' <>
                         metavar "INT" <>
                         value oneSecond <>
                         showDefault <>
                         help "Delay between output pin value toggles (in microseconds)") <*>
    Options.option auto (long "edge" <>
                         short 'e' <>
                         metavar "None|Rising|Falling|Both" <>
                         value Both <>
                         showDefault <>
                         help "Edge on which to trigger the input pin") <*>
    Options.option auto (long "timeout" <>
                         short 'T' <>
                         metavar "INT" <>
                         value (-1) <>
                         help "Use a timeout for readPin (in microseconds)") <*>
    argument auto (metavar "INPIN")  <*>
    argument auto (metavar "OUTPIN")

cmds :: Parser GlobalOptions
cmds =
  GlobalOptions <$>
    hsubparser
      (command "listPins" (info listPinsCmd (progDesc "List the GPIO pins available on the system")) <>
       command "readEdge" (info readEdgeCmd (progDesc "Drive INPIN using OUTPIN. (Make sure the pins are connected!")))

type NativeSysfs a = SysfsIOT IO a

runNativeSysfs :: NativeSysfs a -> IO a
runNativeSysfs = runSysfsIOT

run :: GlobalOptions -> IO ()
run (GlobalOptions (ReadEdge (ReadEdgeOptions period edge timeout inputPin outputPin))) =
  void $
    concurrently
      (runNativeSysfs $ edgeRead inputPin edge timeout)
      (runNativeSysfs $ driveOutput outputPin period)
run (GlobalOptions ListPins) = runNativeSysfs listPins

withPin :: Pin -> NativeSysfs a -> NativeSysfs a
withPin p block = liftIO $ bracket_ (runNativeSysfs $ exportPin p) (runNativeSysfs $ unexportPin p) (runNativeSysfs block)

listPins :: NativeSysfs ()
listPins =
  availablePins >>= \case
    [] -> putText "No GPIO pins found on this system"
    ps -> for_ ps $ putText . show

edgeRead :: Pin -> SysfsEdge -> Int -> NativeSysfs ()
edgeRead p edge timeout =
  withPin p $
    do writePinDirection p In
       writePinEdge p edge
       forever $
         do result <- pollPinValueTimeout p timeout
            case result of
              Nothing -> putText $ unwords ["readPin timed out after", show timeout, "microseconds"]
              Just v -> putText $ unwords ["Input:", show v]

driveOutput :: Pin -> Int -> NativeSysfs ()
driveOutput p delay =
  withPin p $
    do writePinDirection p Out
       forever $
         do liftIO $ threadDelay delay
            v <- readPinValue p
            let notv = invertValue v
            writePinValue p notv
            putText $ unwords ["Output:", show notv]

main :: IO ()
main =execParser opts >>= run
  where
    opts =
      info (helper <*> cmds)
           (fullDesc <>
            progDesc "Example sysfs hpio programs." <>
            header "hpio-sysfs-example - run sysfs hpio demonstrations.")