packages feed

fuyu-gpio-0.1.0.0: src/Fuyu/GPIO/Monitor.hs

{-# LANGUAGE PatternSynonyms #-}

-- |
-- Module      : Fuyu.GPIO.Monitor
-- Description : Read-only metadata query functions for ChipInfo and LineInfo, 
--               and operations for watching GPIO line status events.
-- Maintainer  : BassGT
-- Stability   : experimental
-- Portability : POSIX (Linux gpiod v2)
--
-- This module unifies metadata query functions for 'ChipInfo' and 'LineInfo'
-- snapshots, as well as real-time line status change event monitoring ('InfoEvent').
--
-- It is designed to be imported qualified:
--
-- @
-- import qualified Fuyu.GPIO.Monitor as Monitor
-- @
module Fuyu.GPIO.Monitor
  ( -- * Chip Metadata Snapshot & Inspection
    ChipInfo
  , withChipInfo
  , chipName
  , label
  , numLines

    -- * Line Metadata Snapshot & Inspection
  , LineInfo
  , withLineInfo
  , offset
  , lineName
  , isUsed
  , consumer
  , direction
  , edgeDetection
  , bias
  , drive
  , isActiveLow
  , isDebounced
  , debouncePeriod
  , eventClock

    -- * Line Status Watching & Events
  , ReadyChip(..)
  , readyToChip
  , WaitResult(..)
  , Event
  , EventType
  , pattern Requested
  , pattern Released
  , pattern ConfigChanged
  , withWatchLine
  , watchLine
  , unwatchLine
  , waitEvent
  , withEvent
  , eventType
  , timestampNs
  , lineInfo

    -- * Types & Patterns
  , Chip
  , Offset
  , pattern Offset
  , Direction
  , pattern DirAsIs
  , pattern DirInput
  , pattern DirOutput
  , Edge
  , pattern EdgeNone
  , pattern EdgeRising
  , pattern EdgeFalling
  , pattern EdgeBoth
  , Bias
  , pattern BiasAsIs
  , pattern BiasUnknown
  , pattern BiasDisabled
  , pattern BiasPullUp
  , pattern BiasPullDown
  , Drive
  , pattern PushPull
  , pattern OpenDrain
  , pattern OpenSource
  , Clock
  , pattern Monotonic
  , pattern Realtime
  , pattern Hardware
  , Timeout
  , pattern Nanoseconds
  , pattern Immediate
  , pattern Infinite
  , Timestamp
  ) where

import Control.Exception (bracket, throwIO)
import Foreign.C.Error (Errno(..))
import qualified Data.ByteString.Char8 as C8
import qualified Fuyu.GPIO.Direct as D
import qualified Fuyu.GPIO.Unsafe as Unsafe
import Fuyu.GPIO.Exception
import Fuyu.GPIO.Types hiding (eventType)

--------------------------------------------------------------------------------
-- Domain Type Aliases
--------------------------------------------------------------------------------

-- | Type alias for 'InfoEvent' designed for qualified use (e.g. @Monitor.Event@).
type Event = InfoEvent  

-- | Type alias for 'InfoEventType' designed for qualified use (e.g. @Monitor.EventType@).
type EventType = InfoEventType

--------------------------------------------------------------------------------
-- Chip Metadata Snapshot & Inspection
--------------------------------------------------------------------------------

-- | Retrieve information about a GPIO chip and free it automatically afterwards.
withChipInfo :: Chip -> (ChipInfo -> IO a) -> IO a
withChipInfo chip = bracket (Unsafe.chipInfo chip) Unsafe.freeChipInfo

-- | Get the name of the GPIO chip (e.g. "gpiochip0").
chipName :: ChipInfo -> IO String
chipName info = C8.unpack <$> D.chipInfoName info

-- | Get the label of the GPIO chip.
label :: ChipInfo -> IO String
label info = C8.unpack <$> D.chipInfoLabel info

-- | Get the total number of lines exposed by the GPIO chip.
numLines :: ChipInfo -> IO Word
numLines = D.chipInfoNumLines

--------------------------------------------------------------------------------
-- Line Metadata Snapshot & Inspection
--------------------------------------------------------------------------------

-- | Retrieve information about a specific line on a chip and free it automatically afterwards.
withLineInfo :: Chip -> Offset -> (LineInfo -> IO a) -> IO a
withLineInfo chip offset' = bracket (Unsafe.lineInfo chip offset') Unsafe.freeLineInfo

-- | Get the numeric 'Offset' of the line from a 'LineInfo' snapshot.
offset :: LineInfo -> IO Offset
offset = D.lineInfoOffset

-- | Get the name of the line (e.g. "GPIO17"), if set.
lineName :: LineInfo -> IO (Maybe String)
lineName info = fmap C8.unpack <$> D.lineInfoName info

-- | Check if the line is currently in use by a consumer kernel driver or user process.
isUsed :: LineInfo -> IO Bool
isUsed = D.lineInfoIsUsed

-- | Get the consumer name string of the line, if in use.
consumer :: LineInfo -> IO (Maybe String)
consumer info = fmap C8.unpack <$> D.lineInfoConsumer info

-- | Get the configured direction of the line ('DirInput', 'DirOutput', 'DirAsIs').
direction :: LineInfo -> IO Direction
direction = D.lineInfoDirection

-- | Get the configured edge detection of the line ('EdgeNone', 'EdgeRising', 'EdgeFalling', 'EdgeBoth').
edgeDetection :: LineInfo -> IO Edge
edgeDetection = D.lineInfoEdgeDetection

-- | Get the configured electrical bias ('BiasDisabled', 'BiasPullUp', 'BiasPullDown', etc.).
bias :: LineInfo -> IO Bias
bias = D.lineInfoBias

-- | Get the configured drive mode ('PushPull', 'OpenDrain', 'OpenSource').
drive :: LineInfo -> IO Drive
drive = D.lineInfoDrive

-- | Check if active-low logic is configured for the line.
isActiveLow :: LineInfo -> IO Bool
isActiveLow = D.lineInfoIsActiveLow

-- | Check if hardware debounce is configured for the line.
isDebounced :: LineInfo -> IO Bool
isDebounced = D.lineInfoIsDebounced

-- | Get the debounce period in microseconds for the line.
debouncePeriod :: LineInfo -> IO Word
debouncePeriod = D.lineInfoDebouncePeriod

-- | Get the event clock source configured for the line ('Monotonic', 'Realtime', 'Hardware').
eventClock :: LineInfo -> IO Clock
eventClock = D.lineInfoEventClock

--------------------------------------------------------------------------------
-- Line Status Watching & Events
--------------------------------------------------------------------------------

-- | Start watching a line for status change events (e.g. requested, released, reconfigured)
-- within a bracket, automatically unwatching the line and freeing the initial snapshot when finished.
-- Passes the initial 'LineInfo' snapshot of the line to the callback.
withWatchLine :: Chip -> Offset -> (LineInfo -> IO a) -> IO a
withWatchLine chip offset' =
  bracket
    (watchLine chip offset')
    (\info -> do
       Unsafe.freeLineInfo info
       unwatchLine chip offset')

-- | Start watching a line for status change events (e.g. requested, released, reconfigured).
-- Returns the initial 'LineInfo' snapshot of the line.
watchLine :: Chip -> Offset -> IO LineInfo
watchLine chip offset' = unwrapOrThrow LineInfoFailed (D.chipWatchLineInfo chip offset')

-- | Stop watching a line for status change events.
unwatchLine :: Chip -> Offset -> IO ()
unwatchLine chip offset' = unwrapOrThrow LineInfoFailed (D.chipUnwatchLineInfo chip offset')

-- | Wait for status change info events on any of the watched lines on the chip until the specified timeout.
-- Throws 'WaitInfoEventFailed' on error.
waitEvent :: Chip -> Timeout -> IO (WaitResult ReadyChip)
waitEvent chip timeout = do
  res <- D.chipWaitInfoEvent chip timeout
  case res of
    Left (Errno 4)     -> waitEvent chip timeout  -- Retry on EINTR so GHC RTS can deliver UserInterrupt.
    Left err           -> throwIO (WaitInfoEventFailed err)
    Right D.EventReady -> pure (EventReady (ReadyChip chip))
    Right D.Timeout    -> pure TimeoutResult
    
-- | Read a status change info event from a chip once 'waitEvent' indicates it is ready,
-- and automatically free it afterwards.
withEvent :: ReadyChip -> (Event -> IO a) -> IO a
withEvent readyChip = bracket (Unsafe.readInfoEvent readyChip) Unsafe.freeInfoEvent

-- | Get the event type of an 'InfoEvent' ('Requested', 'Released', 'ConfigChanged').
eventType :: Event -> IO EventType
eventType = D.infoEventType

-- | Get the timestamp in nanoseconds of an 'InfoEvent'.
timestampNs :: Event -> IO Timestamp
timestampNs = D.infoEventTimestamp

-- | Get the line info snapshot associated with an 'InfoEvent'.
lineInfo :: Event -> IO LineInfo
lineInfo = D.infoEventLineInfo