packages feed

fuyu-gpio-0.0.9.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.
module Fuyu.GPIO.EdgeEvent
  ( -- * Security Token & Wait Result
    WaitResult(..)
  , ReadyRequest(..)
  , readyToRequest

    -- * Buffer & Event Types
  , Buffer
  , Capacity
  , userBufferCapacity
  , capacity
  , Event
  , Timeout
  , pattern Nanoseconds
  , pattern Immediate
  , pattern Infinite
  , Timestamp
  , EdgeEventType
  , pattern Rising
  , pattern Falling

    -- * Event Data Type & Parser
  , NonEmpty(..)
  , EdgeEvent(..)
  , parseEvent

    -- * Event Buffer Operations (Managed)
  , withBuffer
  , bufferCapacity
  , bufferNumEvents
  , bufferEvent

    -- * Waiting & Reading Events
  , waitEvents
  , readEvents
  , withRawEvents

    -- * RawEdgeEvent Metadata Accessors
  , eventType
  , timestampNs
  , lineOffset
  , globalSeqNo
  , lineSeqNo
  , copyEvent
  ) where

import Control.Exception (bracket)
import Control.Monad (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.EdgeEvent.Unsafe (newEventBuffer, freeEventBuffer, readEventsRaw)
import Fuyu.GPIO.Exception
import Fuyu.GPIO.Types hiding (eventType)

-- | 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.
bufferEvent :: Buffer -> Word -> IO Event
bufferEvent buf idx = unwrapOrThrow ReadEdgeEventsFailed (D.eventBufferGetEvent buf idx)

-- | 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 <- unwrapOrThrow WaitEdgeEventsFailed (D.lineRequestWaitEdgeEvents req timeout)
  pure $ case res of
    D.EventReady -> EventReady (ReadyRequest req)
    D.Timeout    -> TimeoutResult

-- | Parse a raw edge event pointer into a pure Haskell 'EdgeEvent' structure.
parseEvent :: Event -> IO EdgeEvent
parseEvent 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 EdgeEvent)
readEvents readyReq buf = withRawEvents readyReq buf parseEvent

-- | Process raw edge events directly in the buffer using a callback without intermediate allocations,
-- returning a non-empty list of results.
withRawEvents :: ReadyRequest -> Buffer -> (Event -> IO a) -> IO (NonEmpty a)
withRawEvents readyReq buf action = do
  count <- readEventsRaw readyReq buf
  results <- forM [0 .. count - 1] $ \idx -> do
    ev <- bufferEvent buf (fromIntegral idx)
    action ev
  case NE.nonEmpty results of
    Just ne -> pure ne
    Nothing -> ioError (userError "readEvents: expected at least one event from ReadyRequest but got none")

--------------------------------------------------------------------------------
-- RawEdgeEvent Metadata Accessors
--------------------------------------------------------------------------------

-- | Get the type of event ('Rising' or 'Falling').
eventType :: Event -> IO EdgeEventType
eventType = D.rawEdgeEventType

-- | Get the event timestamp in nanoseconds.
timestampNs :: Event -> IO Timestamp
timestampNs = D.rawEdgeEventTimestampNs

-- | Get the offset of the line that triggered the event.
lineOffset :: Event -> IO Offset
lineOffset = D.rawEdgeEventLineOffset

-- | Get the global sequence number of the event.
globalSeqNo :: Event -> IO Word64
globalSeqNo = D.rawEdgeEventGlobalSeqNo

-- | Get the line-specific sequence number of the event.
lineSeqNo :: Event -> IO Offset
lineSeqNo = D.rawEdgeEventLineSeqNo

-- | Make a copy of a raw edge event object.
copyEvent :: Event -> IO Event
copyEvent ev = unwrapOrThrow RawEdgeEventCopyFailed (D.rawEdgeEventCopy ev)