{-| Module : System.GPIO.Linux.Sysfs.Util Description : Useful low-level Linux @sysfs@ functions Copyright : (c) 2016, Drew Hess License : BSD3 Maintainer : Drew Hess Stability : experimental Portability : non-portable Useful low-level Linux @sysfs@ functions. -} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE Safe #-} module System.GPIO.Linux.Sysfs.Util ( -- * Paths and file names sysfsPath , exportFileName , unexportFileName , pinDirName , pinActiveLowFileName , pinDirectionFileName , pinEdgeFileName , pinValueFileName -- * Convert Haskell types to/from their @sysfs@ representation -- -- | A note on newlines: a Linux GPIO pin's /attributes/ -- (i.e., the @sysfs@ files representing a pin's state) are -- read and written as 'ByteString's. When reading their -- contents, the attribute files always return their -- (ASCII-encoded) value followed by a newline character -- (@\\n@). When writing their contents, the attribute files -- will accept their (ASCII-encoded) new value either with or -- without a trailing newline character. For consistency (and -- for the sake of isomorphic conversions back-and-forth), -- these functions always use a trailing newline when -- encoding the ASCII value from the Haskell value. , pinDirectionToBS , pinDirectionValueToBS , bsToPinDirection , sysfsEdgeToBS , bsToSysfsEdge , pinValueToBS , bsToPinValue , activeLowToBS , bsToActiveLow , intToBS , bsToInt ) where import Data.ByteString (ByteString) import qualified Data.ByteString as BS (empty) import Data.ByteString.Builder (toLazyByteString, intDec) import qualified Data.ByteString.Char8 as C8 (readInt) import qualified Data.ByteString.Lazy as LBS (toStrict) import System.FilePath (()) import System.GPIO.Types (Pin(..), PinDirection(..), PinValue(..)) import System.GPIO.Linux.Sysfs.Types (SysfsEdge(..)) -- | The base path to Linux's @sysfs@ GPIO filesystem. -- -- >>> sysfsPath -- "/sys/class/gpio" sysfsPath :: FilePath sysfsPath = "/sys/class/gpio" -- | The name of the control file used to export GPIO pins via -- @sysfs@. -- -- >>> exportFileName -- "/sys/class/gpio/export" exportFileName :: FilePath exportFileName = sysfsPath "export" -- | The name of the control file used to "unexport" GPIO pins via -- @sysfs@. -- -- >>> unexportFileName -- "/sys/class/gpio/unexport" unexportFileName :: FilePath unexportFileName = sysfsPath "unexport" -- | Exporting a GPIO pin via @sysfs@ creates a control directory -- corresponding to that pin. 'pinDirName' gives the name of that -- directory for a given 'Pin'. -- -- >>> pinDirName (Pin 16) -- "/sys/class/gpio/gpio16" pinDirName :: Pin -> FilePath pinDirName (Pin n) = sysfsPath ("gpio" ++ show n) -- | The name of the attribute file used to read and write the pin's -- @active_low@ value. -- -- >>> pinActiveLowFileName (Pin 16) -- "/sys/class/gpio/gpio16/active_low" pinActiveLowFileName :: Pin -> FilePath pinActiveLowFileName p = pinDirName p "active_low" -- | Pins whose direction can be controlled via @sysfs@ provide a -- @direction@ attribute file. 'pinDirectionFileName' gives the name -- of that file for a given 'Pin'. Note that some pins' direction -- cannot be set. In these cases, the file named by this function does -- not actually exist. -- -- >>> pinDirectionFileName (Pin 16) -- "/sys/class/gpio/gpio16/direction" pinDirectionFileName :: Pin -> FilePath pinDirectionFileName p = pinDirName p "direction" -- | Pins that can be configured as interrupt-generating inputs -- provide an @edge@ attribute file. 'pinEdgeFileName' gives the name -- of that file for a given 'Pin'. Note that some pins' edge -- configuration cannot be set. In these cases, the file named by this -- function does not actually exist. -- -- >>> pinEdgeFileName (Pin 16) -- "/sys/class/gpio/gpio16/edge" pinEdgeFileName :: Pin -> FilePath pinEdgeFileName p = pinDirName p "edge" -- | The name of the attribute file used to read and write the pin's -- logical signal value. -- -- >>> pinValueFileName (Pin 16) -- "/sys/class/gpio/gpio16/value" pinValueFileName :: Pin -> FilePath pinValueFileName p = pinDirName p "value" -- | Convert a 'PinDirection' value to the corresponding 'ByteString' -- value expected by a pin's @direction@ attribute in the @sysfs@ GPIO -- filesystem. -- -- >>> pinDirectionToBS In -- "in\n" -- >>> pinDirectionToBS Out -- "out\n" pinDirectionToBS :: PinDirection -> ByteString pinDirectionToBS In = "in\n" pinDirectionToBS Out = "out\n" -- | Convert a 'PinValue' value to the corresponding 'ByteString' -- value expected by a pin's @direction@ attribute in the @sysfs@ -- GPIO, which can be used to configure the pin for output and -- simultaneously set the pin's (physical) signal level; see the -- -- for details. -- -- >>> pinDirectionValueToBS Low -- "low\n" -- >>> pinDirectionValueToBS High -- "high\n" pinDirectionValueToBS :: PinValue -> ByteString pinDirectionValueToBS Low = "low\n" pinDirectionValueToBS High = "high\n" -- | When writing a pin's @direction@ attribute in the @sysfs@ GPIO -- filesystem with a 'ByteString' value, @in\\n@ configures the pin -- for input, and @out\\n@ configures the pin for output while also -- initializing the pin's (physical) signal level to a low value. -- -- Furthermore, you may write @low\\n@ or @high\\n@ to the -- @direction@ attribute to configure the pin for output and -- simulataneously set the pin's physical value. -- -- Therefore, writing a pin's @direction@ attribute affects not only -- its direction, but also (potentially) its value. This function's -- return type reflects that possibility. -- -- See the -- -- for details. -- -- This function converts a @direction@ attribute value, encoded as a -- strict 'ByteString', to its corresponding 'PinDirection' and -- (possible) 'PinValue' pair; or 'Nothing' if the attribute encoding -- is invalid. -- -- >>> :set -XOverloadedStrings -- >>> bsToPinDirection "in\n" -- Just (In,Nothing) -- >>> bsToPinDirection "out\n" -- Just (Out,Just Low) -- >>> bsToPinDirection "low\n" -- Just (Out,Just Low) -- >>> bsToPinDirection "high\n" -- Just (Out,Just High) -- >>> bsToPinDirection "foo\n" -- Nothing bsToPinDirection :: ByteString -> Maybe (PinDirection, Maybe PinValue) bsToPinDirection "in\n" = Just (In, Nothing) bsToPinDirection "out\n" = Just (Out, Just Low) bsToPinDirection "low\n" = Just (Out, Just Low) bsToPinDirection "high\n" = Just (Out, Just High) bsToPinDirection _ = Nothing -- | Convert a 'SysfsEdge' value to the 'ByteString' value expected by -- a pin's @edge@ attribute in the @sysfs@ GPIO filesystem. -- -- >>> sysfsEdgeToBS None -- "none\n" -- >>> sysfsEdgeToBS Rising -- "rising\n" -- >>> sysfsEdgeToBS Falling -- "falling\n" -- >>> sysfsEdgeToBS Both -- "both\n" sysfsEdgeToBS :: SysfsEdge -> ByteString sysfsEdgeToBS None = "none\n" sysfsEdgeToBS Rising = "rising\n" sysfsEdgeToBS Falling = "falling\n" sysfsEdgeToBS Both = "both\n" -- | Inverse of 'sysfsEdgeToBS'. -- -- >>> :set -XOverloadedStrings -- >>> bsToSysfsEdge "none\n" -- Just None -- >>> bsToSysfsEdge "rising\n" -- Just Rising -- >>> bsToSysfsEdge "falling\n" -- Just Falling -- >>> bsToSysfsEdge "both\n" -- Just Both -- >>> bsToSysfsEdge "foo\n" -- Nothing bsToSysfsEdge :: ByteString -> Maybe SysfsEdge bsToSysfsEdge "none\n" = Just None bsToSysfsEdge "rising\n" = Just Rising bsToSysfsEdge "falling\n" = Just Falling bsToSysfsEdge "both\n" = Just Both bsToSysfsEdge _ = Nothing -- | Convert a 'PinValue' to the 'ByteString' value expected by a -- pin's @value@ attribute in the @sysfs@ GPIO filesystem. -- -- >>> pinValueToBS Low -- "0\n" -- >>> pinValueToBS High -- "1\n" pinValueToBS :: PinValue -> ByteString pinValueToBS Low = "0\n" pinValueToBS High = "1\n" -- | Convert a @value@ attribute value, encoded as a strict -- 'ByteString', to its corresponding 'PinValue'. -- -- Note that the @sysfs@ @value@ attribute is quite liberal: a -- 'ByteString' value of @0\\n@ will set the pin's (logical) signal -- level to low, but any other (non-empty) 'ByteString' value will set -- it to high. -- -- >>> :set -XOverloadedStrings -- >>> bsToPinValue "0\n" -- Just Low -- >>> bsToPinValue "1\n" -- Just High -- >>> bsToPinValue "high\n" -- Just High -- >>> bsToPinValue "low\n" -- nota bene! -- Just High -- >>> bsToPinValue "foo\n" -- Just High -- >>> bsToPinValue "" -- Nothing bsToPinValue :: ByteString -> Maybe PinValue bsToPinValue "0\n" = Just Low bsToPinValue bs | bs == BS.empty = Nothing | otherwise = Just High -- | Convert a 'Bool' to the 'ByteString' value expected by a pin's -- @active_low@ attribute in the @sysfs@ GPIO filesystem. -- -- >>> activeLowToBS False -- "0\n" -- >>> activeLowToBS True -- "1\n" activeLowToBS :: Bool -> ByteString activeLowToBS False = "0\n" activeLowToBS True = "1\n" -- | Convert an @active_low@ attribute value, encoded as a strict -- 'ByteString', to its corresponding 'Bool' value. -- -- Note that the @sysfs@ @active_low@ attribute is quite liberal: a -- 'ByteString' value of @0\\n@ returns 'False' and any other -- (non-empty) 'ByteString' value returns 'True'. -- -- >>> :set -XOverloadedStrings -- >>> bsToActiveLow "0\n" -- Just False -- >>> bsToActiveLow "1\n" -- Just True -- >>> bsToActiveLow "high\n" -- Just True -- >>> bsToActiveLow "low\n" -- nota bene! -- Just True -- >>> bsToActiveLow "foo\n" -- Just True -- >>> bsToActiveLow "" -- Nothing bsToActiveLow :: ByteString -> Maybe Bool bsToActiveLow "0\n" = Just False bsToActiveLow bs | bs == BS.empty = Nothing | otherwise = Just True -- | Convert an 'Int' to a decimal ASCII encoding in a strict -- 'ByteString'. -- -- >>> intToBS 37 -- "37" intToBS :: Int -> ByteString intToBS = LBS.toStrict . toLazyByteString . intDec -- | Convert a strict decimal ASCII 'ByteString' encoding of an -- integer to an 'Int' (maybe). If there are any extraneous trailing -- characters after the decimal ASCII encoding, other than a single -- newline character, this is treated as a failure (unlike -- 'C8.readInt', which returns the remaining string). -- -- >>> :set -XOverloadedStrings -- >>> bsToInt "37" -- Just 37 -- >>> bsToInt "37\n" -- Just 37 -- >>> bsToInt "37abc" -- Nothing -- >>> bsToInt "37 a" -- Nothing bsToInt :: ByteString -> Maybe Int bsToInt = go . C8.readInt where go :: Maybe (Int, ByteString) -> Maybe Int go (Just (n, bs)) | bs == BS.empty = Just n | bs == "\n" = Just n | otherwise = Nothing go _ = Nothing