packages feed

network-can-0.1.0.0: src/Network/SLCAN.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module Network.SLCAN
  ( Transport(..)
  , withSLCANTransport
  , sendSLCANMessage
  , sendSLCANControl
  , recvSLCANMessage
  , sendCANMessage
  , module Network.SLCAN.Types
  , SLCANT(..)
  , SLCANException(..)
  , runSLCAN
  ) where

import Control.Exception (Exception)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Reader (MonadReader, ask)
import Control.Monad.Trans (MonadTrans(..))
import Control.Monad.Trans.Reader (ReaderT(..))

import Network.Socket (Socket, SockAddr)
import Network.CAN (CANMessage, MonadCAN(..))
import Network.SLCAN.Types
import System.IO (Handle)
import UnliftIO (MonadUnliftIO)

import qualified Control.Monad
import qualified Control.Exception
import qualified Data.ByteString
import qualified Data.ByteString.Char8
import qualified System.IO
import qualified Network.SLCAN.Builder
import qualified Network.SLCAN.Parser
import qualified Network.Socket.ByteString
import qualified UnliftIO

data Transport =
    Transport_Handle Handle
  | Transport_UDP Socket SockAddr

withSLCANTransport
  :: Transport
  -> SLCANConfig
  -> (Transport -> IO a)
  -> IO a
withSLCANTransport transport SLCANConfig{..} act = do
  let sendC = sendSLCANControl transport
  Control.Exception.finally
    (do
       sendC SLCANControl_Close
       sendC (SLCANControl_Bitrate slCANConfigBitrate)
       Control.Monad.when
         slCANConfigResetErrors
         (sendC SLCANControl_ResetErrors)
       sendC
         (if slCANConfigListenOnly
          then SLCANControl_ListenOnly
          else SLCANControl_Open
         )

       act transport
    )
    (sendC SLCANControl_Close)

sendSLCANMessage
  :: Transport
  -> SLCANMessage
  -> IO ()
sendSLCANMessage (Transport_Handle handle) msg = do
  Control.Monad.void
    $ Data.ByteString.hPutStr
        handle
        $ Network.SLCAN.Builder.buildSLCANMessage
            msg
  System.IO.hFlush handle
sendSLCANMessage (Transport_UDP socket target) msg = do
  Network.Socket.ByteString.sendAllTo
    socket
    (Network.SLCAN.Builder.buildSLCANMessage msg)
    target

sendSLCANControl
  :: Transport
  -> SLCANControl
  -> IO ()
sendSLCANControl t =
  sendSLCANMessage t
  . SLCANMessage_Control

recvSLCANMessage
  :: Transport
  -> IO (Either String SLCANMessage)
recvSLCANMessage (Transport_Handle handle) = do
  Network.SLCAN.Parser.parseSLCANMessage
  <$> hGetTillCR handle

  where
    hGetTillCR h = do
      msg <-
        Data.ByteString.hGetSome
          h
          1024
      if Data.ByteString.Char8.last msg == '\r'
      then pure msg
      else hGetTillCR h >>= pure . (msg <>)

recvSLCANMessage (Transport_UDP socket _target) = do
  Network.SLCAN.Parser.parseSLCANMessage
  <$> sockGetTillCR socket
  where
    sockGetTillCR s = do
      (msg, _source) <-
        Network.Socket.ByteString.recvFrom
          s
          1024
      if Data.ByteString.Char8.last msg == '\r'
      then pure msg
      else sockGetTillCR s >>= pure . (msg <>)

sendCANMessage
  :: Transport
  -> CANMessage
  -> IO ()
sendCANMessage t =
  sendSLCANMessage t
  . SLCANMessage_Data

newtype SLCANT m a = SLCANT
  { _unSLCANT :: ReaderT Transport m a }
  deriving
    ( Functor
    , Applicative
    , Monad
    , MonadReader Transport
    , MonadIO
    , MonadUnliftIO
    )

instance MonadTrans SLCANT where
  lift = SLCANT . lift

-- | Run SLCANT transformer
runSLCANT
  :: Monad m
  => Transport
  -> SLCANT m a
  -> m a
runSLCANT t =
    (`runReaderT` t)
  . _unSLCANT

data SLCANException = SLCANException_ParseError String
    deriving Show

instance Exception SLCANException

runSLCAN
  :: ( MonadIO m
     , MonadUnliftIO m
     )
  => Transport
  -> SLCANConfig
  -> SLCANT m a
  -> m a
runSLCAN transport config act = do
  UnliftIO.withRunInIO $ \runInIO ->
    withSLCANTransport
      transport
      config
      (\t -> runInIO (runSLCANT t act))

instance MonadIO m => MonadCAN (SLCANT m) where
  send cm = do
    ask >>= liftIO . flip sendCANMessage cm
  recv = do
    transport <- ask
    liftIO
      (recvSLCANMessage transport)
    >>= \case
      Left e ->
        UnliftIO.throwIO $ SLCANException_ParseError e
      Right (SLCANMessage_Data cm) ->
        pure cm
      Right _other ->
        -- TODO: do something with
        -- SLCANMessage_Error
        -- and SLCANMessage_State
        -- like allow registering handlers for these
        -- or throwIO on _Error one
        recv