packages feed

fuyu-gpio-0.0.9.0: src/Fuyu/GPIO/Exception.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- |
-- Module      : Fuyu.GPIO.Exception
-- Description : Exception types and high-level application handler for fuyu-gpio operations.
-- Maintainer  : BassGT
-- Stability   : experimental
-- Portability : POSIX
--
-- High-level exception type 'GpioException' thrown by fuyu-gpio operations,
-- and managed application runner 'withGpioApp' for graceful signal handling.
module Fuyu.GPIO.Exception
  ( GpioException(..)
  , unwrapOrThrow
  , withGpioApp
  ) where

import Control.Exception (Exception, SomeException, catch, fromException, throwIO, AsyncException(UserInterrupt))
import Foreign.C.Error (Errno(..))
import Control.Monad (void)

-- | High-level exceptions thrown by fuyu-gpio operations.
data GpioException
  = ChipOpenFailed FilePath Errno
  | ChipInfoFailed Errno
  | LineInfoFailed Errno
  | LineSettingsNewFailed Errno
  | LineSettingsSetFailed Errno
  | LineConfigNewFailed Errno
  | RequestConfigNewFailed Errno
  | LineRequestFailed Errno
  | EventBufferNewFailed Errno
  | LineValueReadFailed Errno
  | LineValueWriteFailed Errno
  | LineReconfigureFailed Errno
  | WaitEdgeEventsFailed Errno
  | ReadEdgeEventsFailed Errno
  | WaitInfoEventFailed Errno
  | ReadInfoEventFailed Errno
  | RawEdgeEventCopyFailed Errno
  | LineInfoCopyFailed Errno
  | CustomGpioError String Errno
  | InvalidArgument String
  deriving (Exception)

instance Eq GpioException where
  ChipOpenFailed p1 e1 == ChipOpenFailed p2 e2 = p1 == p2 && e1 == e2
  ChipInfoFailed e1 == ChipInfoFailed e2 = e1 == e2
  LineInfoFailed e1 == LineInfoFailed e2 = e1 == e2
  LineSettingsNewFailed e1 == LineSettingsNewFailed e2 = e1 == e2
  LineSettingsSetFailed e1 == LineSettingsSetFailed e2 = e1 == e2
  LineConfigNewFailed e1 == LineConfigNewFailed e2 = e1 == e2
  RequestConfigNewFailed e1 == RequestConfigNewFailed e2 = e1 == e2
  LineRequestFailed e1 == LineRequestFailed e2 = e1 == e2
  EventBufferNewFailed e1 == EventBufferNewFailed e2 = e1 == e2
  LineValueReadFailed e1 == LineValueReadFailed e2 = e1 == e2
  LineValueWriteFailed e1 == LineValueWriteFailed e2 = e1 == e2
  LineReconfigureFailed e1 == LineReconfigureFailed e2 = e1 == e2
  WaitEdgeEventsFailed e1 == WaitEdgeEventsFailed e2 = e1 == e2
  ReadEdgeEventsFailed e1 == ReadEdgeEventsFailed e2 = e1 == e2
  WaitInfoEventFailed e1 == WaitInfoEventFailed e2 = e1 == e2
  ReadInfoEventFailed e1 == ReadInfoEventFailed e2 = e1 == e2
  RawEdgeEventCopyFailed e1 == RawEdgeEventCopyFailed e2 = e1 == e2
  LineInfoCopyFailed e1 == LineInfoCopyFailed e2 = e1 == e2
  CustomGpioError s1 e1 == CustomGpioError s2 e2 = s1 == s2 && e1 == e2
  InvalidArgument msg1 == InvalidArgument msg2 = msg1 == msg2
  _ == _ = False

instance Show GpioException where
  show (ChipOpenFailed path (Errno e)) = "ChipOpenFailed: Failed to open chip at '" ++ path ++ "' (errno " ++ show e ++ ")"
  show (ChipInfoFailed (Errno e)) = "ChipInfoFailed (errno " ++ show e ++ ")"
  show (LineInfoFailed (Errno e)) = "LineInfoFailed (errno " ++ show e ++ ")"
  show (LineSettingsNewFailed (Errno e)) = "LineSettingsNewFailed (errno " ++ show e ++ ")"
  show (LineSettingsSetFailed (Errno e)) = "LineSettingsSetFailed (errno " ++ show e ++ ")"
  show (LineConfigNewFailed (Errno e)) = "LineConfigNewFailed (errno " ++ show e ++ ")"
  show (RequestConfigNewFailed (Errno e)) = "RequestConfigNewFailed (errno " ++ show e ++ ")"
  show (LineRequestFailed (Errno e)) = "LineRequestFailed (errno " ++ show e ++ ")"
  show (EventBufferNewFailed (Errno e)) = "EventBufferNewFailed (errno " ++ show e ++ ")"
  show (LineValueReadFailed (Errno e)) = "LineValueReadFailed (errno " ++ show e ++ ")"
  show (LineValueWriteFailed (Errno e)) = "LineValueWriteFailed (errno " ++ show e ++ ")"
  show (LineReconfigureFailed (Errno e)) = "LineReconfigureFailed (errno " ++ show e ++ ")"
  show (WaitEdgeEventsFailed (Errno e)) = "WaitEdgeEventsFailed (errno " ++ show e ++ ")"
  show (ReadEdgeEventsFailed (Errno e)) = "ReadEdgeEventsFailed (errno " ++ show e ++ ")"
  show (WaitInfoEventFailed (Errno e)) = "WaitInfoEventFailed (errno " ++ show e ++ ")"
  show (ReadInfoEventFailed (Errno e)) = "ReadInfoEventFailed (errno " ++ show e ++ ")"
  show (RawEdgeEventCopyFailed (Errno e)) = "RawEdgeEventCopyFailed (errno " ++ show e ++ ")"
  show (LineInfoCopyFailed (Errno e)) = "LineInfoCopyFailed (errno " ++ show e ++ ")"
  show (CustomGpioError msg (Errno e)) = "CustomGpioError '" ++ msg ++ "' (errno " ++ show e ++ ")"
  show (InvalidArgument msg) = "InvalidArgument: " ++ msg

-- | Helper to unwrap an 'Either Errno a' from low-level FFI calls or throw a 'GpioException'.
unwrapOrThrow :: (Errno -> GpioException) -> IO (Either Errno a) -> IO a
unwrapOrThrow mkExc action = do
  res <- action
  case res of
    Left errno -> throwIO (mkExc errno)
    Right val  -> pure val

-- | High-level managed application runner.
-- Automatically handles 'Ctrl+C' ('UserInterrupt'), interrupted system calls ('EINTR' / 'WaitEdgeEventsFailed'),
-- and prints formatted 'GpioException' messages cleanly without uncaught backtraces.
withGpioApp :: IO a -> IO ()
withGpioApp action = void action `catch` handleAppException
  where
    handleAppException :: SomeException -> IO ()
    handleAppException exc
      | isUserInterrupt exc = putStrLn "\nLoop terminated successfully!"
      | Just (WaitEdgeEventsFailed (Errno 4)) <- fromException exc = putStrLn "\nLoop terminated successfully!"
      | Just (gpioErr :: GpioException) <- fromException exc = putStrLn $ "\n[GPIO Exception]: " ++ show gpioErr
      | otherwise = throwIO exc

    isUserInterrupt :: SomeException -> Bool
    isUserInterrupt e = case fromException e of
      Just UserInterrupt -> True
      _                  -> False