fuyu-gpio-0.1.0.0: src/Fuyu/GPIO/EdgeEvent.hs
{-# LANGUAGE PatternSynonyms #-}
-- |
-- Module : Fuyu.GPIO.EdgeEvent
-- Description : High-level edge event waiting, reading, and buffer management.
-- Maintainer : BassGT
-- Stability : experimental
-- Portability : POSIX (Linux gpiod v2)
--
-- This module provides managed resource brackets ('withBuffer') and functions for waiting
-- on edge events ('waitEvents') and reading them ('readEvents') securely using the
-- 'ReadyRequest' capability token.
--
-- It is designed to be imported qualified:
--
-- @
-- import qualified Fuyu.GPIO.EdgeEvent as Edge
-- @
module Fuyu.GPIO.EdgeEvent
( -- * Security Tokens & Wait Result
WaitResult(..)
, ReadyRequest(..)
, readyToRequest
-- * Buffer & Event Types
, Buffer
, Capacity
, userBufferCapacity
, capacity
, RawEvent
, Timeout
, Event
, EventType
, pattern Nanoseconds
, pattern Immediate
, pattern Infinite
, Timestamp
, EdgeEventType
, pattern Rising
, pattern Falling
-- * Event Data Type & Parser
, NonEmpty(..)
, EdgeEvent(..)
, parseRawEvent
-- * Event Buffer Operations
, withBuffer
, bufferCapacity
, bufferNumEvents
, bufferRawEvent
-- * Waiting & Reading Events
, waitEvents
, readEvents
, forRawEvents
, forRawEvents_
-- * RawEdgeEvent Metadata Accessors
, eventType
, timestampNs
, lineOffset
, globalSeqNo
, lineSeqNo
, copyEvent
) where
import Control.Exception (bracket, throwIO)
import Foreign.C.Error (Errno(..))
import Control.Monad (forM, forM_)
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NE
import Data.Word (Word64)
import qualified Fuyu.GPIO.Direct as D
import Fuyu.GPIO.Unsafe (newEventBuffer, freeEventBuffer, readEventsRaw)
import Fuyu.GPIO.Exception
import Fuyu.GPIO.Types hiding (eventType)
--------------------------------------------------------------------------------
-- Domain Type Aliases
--------------------------------------------------------------------------------
-- | Type alias for 'EdgeEvent' designed for qualified use (e.g. @Edge.Event@).
type Event = EdgeEvent
-- | Type alias for 'EdgeEventType' designed for qualified use (e.g. @Edge.EventType@).
type EventType = EdgeEventType
--------------------------------------------------------------------------------
-- Core Edge Event Functions
--------------------------------------------------------------------------------
-- | Allocate an edge event buffer of the specified capacity and free it automatically afterwards.
withBuffer :: Capacity -> (Buffer -> IO a) -> IO a
withBuffer capacity' = bracket (newEventBuffer capacity') freeEventBuffer
-- | Get the capacity of an event buffer.
bufferCapacity :: Buffer -> IO Capacity
bufferCapacity buf = userBufferCapacity <$> D.eventBufferCapacity buf
-- | Get the number of events currently stored in an event buffer.
bufferNumEvents :: Buffer -> IO Word
bufferNumEvents = D.eventBufferNumEvents
-- | Get a specific edge event from the buffer by index, returning 'Nothing' if the index is out of bounds.
bufferRawEvent :: Buffer -> Word -> IO (Maybe RawEvent)
bufferRawEvent buf idx = do
res <- D.eventBufferGetEvent buf idx
pure $ case res of
Right ev -> Just ev
Left _ -> Nothing
-- | Wait for edge events to occur on requested lines until the specified timeout.
-- Throws 'WaitEdgeEventsFailed' on error.
waitEvents :: Request -> Timeout -> IO (WaitResult ReadyRequest)
waitEvents req timeout = do
res <- D.lineRequestWaitEdgeEvents req timeout
case res of
Left (Errno 4) -> waitEvents req timeout -- Retry on EINTR so GHC RTS can deliver UserInterrupt.
Left err -> throwIO (WaitEdgeEventsFailed err)
Right D.EventReady -> pure (EventReady (ReadyRequest req))
Right D.Timeout -> pure TimeoutResult
-- | Parse a raw edge event pointer into a pure Haskell 'EdgeEvent' structure.
parseRawEvent :: RawEvent -> IO Event
parseRawEvent ev = EdgeEvent
<$> D.rawEdgeEventLineOffset ev
<*> D.rawEdgeEventType ev
<*> D.rawEdgeEventTimestampNs ev
-- | Read buffered edge events once 'waitEvents' indicates they are ready,
-- parsing them into a non-empty list of pure 'EdgeEvent' structures.
readEvents :: ReadyRequest -> Buffer -> IO (NonEmpty Event)
readEvents readyReq buf = forRawEvents readyReq buf parseRawEvent
-- | Process raw edge events directly in the buffer using a callback without intermediate allocations,
-- returning a non-empty list of results.
forRawEvents :: ReadyRequest -> Buffer -> (RawEvent -> IO a) -> IO (NonEmpty a)
forRawEvents readyReq buf action = do
count <- readEventsRaw readyReq buf
results <- forM [0 .. count - 1] $ \idx -> do
Just ev <- bufferRawEvent buf (fromIntegral idx)
action ev
return $ NE.fromList results
-- | Same as 'forRawEvents' but ignore the results
forRawEvents_ :: ReadyRequest -> Buffer -> (RawEvent -> IO b) -> IO ()
forRawEvents_ readyReq buf action = do
count <- readEventsRaw readyReq buf
forM_ [0 .. count - 1] $ \idx -> do
Just ev <- bufferRawEvent buf (fromIntegral idx)
action ev
--------------------------------------------------------------------------------
-- RawEvent Metadata Accessors
--------------------------------------------------------------------------------
-- | Get the type of event ('Rising' or 'Falling').
eventType :: RawEvent -> IO EventType
eventType = D.rawEdgeEventType
-- | Get the event timestamp in nanoseconds.
timestampNs :: RawEvent -> IO Timestamp
timestampNs = D.rawEdgeEventTimestampNs
-- | Get the offset of the line that triggered the event.
lineOffset :: RawEvent -> IO Offset
lineOffset = D.rawEdgeEventLineOffset
-- | Get the global sequence number of the event.
globalSeqNo :: RawEvent -> IO Word64
globalSeqNo = D.rawEdgeEventGlobalSeqNo
-- | Get the line-specific sequence number of the event.
lineSeqNo :: RawEvent -> IO Offset
lineSeqNo = D.rawEdgeEventLineSeqNo
-- | Make a copy of a raw edge event object.
copyEvent :: RawEvent -> IO RawEvent
copyEvent ev = unwrapOrThrow RawEdgeEventCopyFailed (D.rawEdgeEventCopy ev)