packages feed

network-can-0.2.0.0: src-slcan/Network/SLCAN.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}

module Network.SLCAN
  ( Transport(..)
  , withSLCANTransport
  , sendSLCANMessage
  , sendSLCANControl
  , recvSLCANMessage
  , sendCANMessage
  , module Network.SLCAN.Types
  , SLCANException(..)
  , withSLCAN
  ) where

import Control.Monad.Class.MonadThrow (Exception(..), MonadThrow(throwIO), finally)
import Control.Monad.IO.Class (MonadIO(..))

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

import qualified Control.Monad
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

data Transport =
    Transport_Handle Handle
  | Transport_UDP Socket SockAddr

withSLCANTransport
  :: ( MonadIO m
     , MonadThrow m
     )
  => Transport
  -> SLCANConfig
  -> (Transport -> m a)
  -> m a
withSLCANTransport transport SLCANConfig{..} act = do
  let sendC = sendSLCANControl transport
  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
  :: MonadIO m
  => Transport
  -> SLCANMessage
  -> m ()
sendSLCANMessage (Transport_Handle handle) msg = liftIO $ do
  Control.Monad.void
    $ Data.ByteString.hPutStr
        handle
        $ Network.SLCAN.Builder.buildSLCANMessage
            msg
  System.IO.hFlush handle
sendSLCANMessage (Transport_UDP socket target) msg =
  liftIO
  $ Network.Socket.ByteString.sendAllTo
      socket
      (Network.SLCAN.Builder.buildSLCANMessage msg)
      target

sendSLCANControl
  :: MonadIO m
  => Transport
  -> SLCANControl
  -> m ()
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

data SLCANException = SLCANException_ParseError String
    deriving Show

instance Exception SLCANException

withSLCAN
  :: ( MonadIO m
     , MonadThrow m
     )
  => Transport
  -> SLCANConfig
  -> (CAN m -> m a)
  -> m a
withSLCAN transport config act = do
  withSLCANTransport
    transport
    config
    $ \t ->
        act
          CAN
            { canSend = liftIO . sendCANMessage t
            , canRecv =
                let
                  recv =
                    liftIO
                      (recvSLCANMessage t)
                    >>= \case
                      Left e ->
                        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
                in recv
            }