packages feed

network-can 0.1.0.0 → 0.2.0.0

raw patch · 30 files changed

+1221/−1147 lines, 30 filesdep +data-defaultdep +io-classesdep −data-default-classdep −mtldep −transformersnew-component:exe:readmePVP ok

version bump matches the API change (PVP)

Dependencies added: data-default, io-classes

Dependencies removed: data-default-class, mtl, transformers, unliftio

API changes (from Hackage documentation)

- Network.CAN.Class: ($dmrecv) :: forall (t :: (Type -> Type) -> Type -> Type) (m' :: Type -> Type). (MonadCAN m, MonadTrans t, MonadCAN m', m ~ t m') => m CANMessage
- Network.CAN.Class: ($dmsend) :: forall (t :: (Type -> Type) -> Type -> Type) (m' :: Type -> Type). (MonadCAN m, MonadTrans t, MonadCAN m', m ~ t m') => CANMessage -> m ()
- Network.CAN.Class: class Monad m => MonadCAN (m :: Type -> Type)
- Network.CAN.Class: instance Network.CAN.Class.MonadCAN m => Network.CAN.Class.MonadCAN (Control.Monad.Trans.Except.ExceptT e m)
- Network.CAN.Class: instance Network.CAN.Class.MonadCAN m => Network.CAN.Class.MonadCAN (Control.Monad.Trans.Reader.ReaderT r m)
- Network.CAN.Class: instance Network.CAN.Class.MonadCAN m => Network.CAN.Class.MonadCAN (Control.Monad.Trans.State.Lazy.StateT s m)
- Network.CAN.Class: recv :: MonadCAN m => m CANMessage
- Network.CAN.Class: send :: MonadCAN m => CANMessage -> m ()
- Network.CAN.Types: CANArbitrationField :: Word32 -> Bool -> Bool -> CANArbitrationField
- Network.CAN.Types: CANMessage :: CANArbitrationField -> [Word8] -> CANMessage
- Network.CAN.Types: [canArbitrationFieldExtended] :: CANArbitrationField -> Bool
- Network.CAN.Types: [canArbitrationFieldID] :: CANArbitrationField -> Word32
- Network.CAN.Types: [canArbitrationFieldRTR] :: CANArbitrationField -> Bool
- Network.CAN.Types: [canMessageArbitrationField] :: CANMessage -> CANArbitrationField
- Network.CAN.Types: [canMessageData] :: CANMessage -> [Word8]
- Network.CAN.Types: data CANArbitrationField
- Network.CAN.Types: data CANMessage
- Network.CAN.Types: extendedID :: Word32 -> CANArbitrationField
- Network.CAN.Types: instance GHC.Classes.Eq Network.CAN.Types.CANArbitrationField
- Network.CAN.Types: instance GHC.Classes.Eq Network.CAN.Types.CANMessage
- Network.CAN.Types: instance GHC.Classes.Ord Network.CAN.Types.CANArbitrationField
- Network.CAN.Types: instance GHC.Classes.Ord Network.CAN.Types.CANMessage
- Network.CAN.Types: instance GHC.Show.Show Network.CAN.Types.CANArbitrationField
- Network.CAN.Types: instance GHC.Show.Show Network.CAN.Types.CANMessage
- Network.CAN.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Network.CAN.Types.CANArbitrationField
- Network.CAN.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Network.CAN.Types.CANMessage
- Network.CAN.Types: setRTR :: CANArbitrationField -> CANArbitrationField
- Network.CAN.Types: standardID :: Word16 -> CANArbitrationField
- Network.CAN.Types: standardMessage :: Word16 -> [Word8] -> CANMessage
- Network.SLCAN: SLCANException_ParseError :: String -> SLCANException
- Network.SLCAN: SLCANT :: ReaderT Transport m a -> SLCANT (m :: Type -> Type) a
- Network.SLCAN: Transport_Handle :: Handle -> Transport
- Network.SLCAN: Transport_UDP :: Socket -> SockAddr -> Transport
- Network.SLCAN: [_unSLCANT] :: SLCANT (m :: Type -> Type) a -> ReaderT Transport m a
- Network.SLCAN: data SLCANException
- Network.SLCAN: data Transport
- Network.SLCAN: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Network.SLCAN.SLCANT m)
- Network.SLCAN: instance Control.Monad.IO.Class.MonadIO m => Network.CAN.Class.MonadCAN (Network.SLCAN.SLCANT m)
- Network.SLCAN: instance Control.Monad.IO.Unlift.MonadUnliftIO m => Control.Monad.IO.Unlift.MonadUnliftIO (Network.SLCAN.SLCANT m)
- Network.SLCAN: instance Control.Monad.Trans.Class.MonadTrans Network.SLCAN.SLCANT
- Network.SLCAN: instance GHC.Base.Applicative m => GHC.Base.Applicative (Network.SLCAN.SLCANT m)
- Network.SLCAN: instance GHC.Base.Functor m => GHC.Base.Functor (Network.SLCAN.SLCANT m)
- Network.SLCAN: instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader Network.SLCAN.Transport (Network.SLCAN.SLCANT m)
- Network.SLCAN: instance GHC.Base.Monad m => GHC.Base.Monad (Network.SLCAN.SLCANT m)
- Network.SLCAN: instance GHC.Exception.Type.Exception Network.SLCAN.SLCANException
- Network.SLCAN: instance GHC.Show.Show Network.SLCAN.SLCANException
- Network.SLCAN: newtype SLCANT (m :: Type -> Type) a
- Network.SLCAN: recvSLCANMessage :: Transport -> IO (Either String SLCANMessage)
- Network.SLCAN: runSLCAN :: (MonadIO m, MonadUnliftIO m) => Transport -> SLCANConfig -> SLCANT m a -> m a
- Network.SLCAN: sendCANMessage :: Transport -> CANMessage -> IO ()
- Network.SLCAN: sendSLCANControl :: Transport -> SLCANControl -> IO ()
- Network.SLCAN: sendSLCANMessage :: Transport -> SLCANMessage -> IO ()
- Network.SLCAN: withSLCANTransport :: Transport -> SLCANConfig -> (Transport -> IO a) -> IO a
- Network.SLCAN.Builder: buildSLCANMessage :: SLCANMessage -> ByteString
- Network.SLCAN.Parser: parseSLCANMessage :: ByteString -> Either String SLCANMessage
- Network.SLCAN.Types: SLCANBitrate_100K :: SLCANBitrate
- Network.SLCAN.Types: SLCANBitrate_10K :: SLCANBitrate
- Network.SLCAN.Types: SLCANBitrate_125K :: SLCANBitrate
- Network.SLCAN.Types: SLCANBitrate_1M :: SLCANBitrate
- Network.SLCAN.Types: SLCANBitrate_20K :: SLCANBitrate
- Network.SLCAN.Types: SLCANBitrate_250K :: SLCANBitrate
- Network.SLCAN.Types: SLCANBitrate_500K :: SLCANBitrate
- Network.SLCAN.Types: SLCANBitrate_50K :: SLCANBitrate
- Network.SLCAN.Types: SLCANBitrate_800K :: SLCANBitrate
- Network.SLCAN.Types: SLCANConfig :: SLCANBitrate -> Bool -> Bool -> SLCANConfig
- Network.SLCAN.Types: SLCANControl_Bitrate :: SLCANBitrate -> SLCANControl
- Network.SLCAN.Types: SLCANControl_Close :: SLCANControl
- Network.SLCAN.Types: SLCANControl_ListenOnly :: SLCANControl
- Network.SLCAN.Types: SLCANControl_Open :: SLCANControl
- Network.SLCAN.Types: SLCANControl_ResetErrors :: SLCANControl
- Network.SLCAN.Types: SLCANCounters :: Word16 -> Word16 -> SLCANCounters
- Network.SLCAN.Types: SLCANError_Ack :: SLCANError
- Network.SLCAN.Types: SLCANError_Bit0 :: SLCANError
- Network.SLCAN.Types: SLCANError_Bit1 :: SLCANError
- Network.SLCAN.Types: SLCANError_CRC :: SLCANError
- Network.SLCAN.Types: SLCANError_Form :: SLCANError
- Network.SLCAN.Types: SLCANError_RxOverrun :: SLCANError
- Network.SLCAN.Types: SLCANError_Stuff :: SLCANError
- Network.SLCAN.Types: SLCANError_TxOverrun :: SLCANError
- Network.SLCAN.Types: SLCANMessage_Control :: SLCANControl -> SLCANMessage
- Network.SLCAN.Types: SLCANMessage_Data :: CANMessage -> SLCANMessage
- Network.SLCAN.Types: SLCANMessage_Error :: Set SLCANError -> SLCANMessage
- Network.SLCAN.Types: SLCANMessage_State :: SLCANState -> SLCANCounters -> SLCANMessage
- Network.SLCAN.Types: SLCANState_Active :: SLCANState
- Network.SLCAN.Types: SLCANState_BusOff :: SLCANState
- Network.SLCAN.Types: SLCANState_Passive :: SLCANState
- Network.SLCAN.Types: SLCANState_Warning :: SLCANState
- Network.SLCAN.Types: [slCANConfigBitrate] :: SLCANConfig -> SLCANBitrate
- Network.SLCAN.Types: [slCANConfigListenOnly] :: SLCANConfig -> Bool
- Network.SLCAN.Types: [slCANConfigResetErrors] :: SLCANConfig -> Bool
- Network.SLCAN.Types: [slCANCountersRxErrors] :: SLCANCounters -> Word16
- Network.SLCAN.Types: [slCANCountersTxErrors] :: SLCANCounters -> Word16
- Network.SLCAN.Types: data SLCANBitrate
- Network.SLCAN.Types: data SLCANConfig
- Network.SLCAN.Types: data SLCANControl
- Network.SLCAN.Types: data SLCANCounters
- Network.SLCAN.Types: data SLCANError
- Network.SLCAN.Types: data SLCANMessage
- Network.SLCAN.Types: data SLCANState
- Network.SLCAN.Types: instance Data.Default.Internal.Default Network.SLCAN.Types.SLCANBitrate
- Network.SLCAN.Types: instance Data.Default.Internal.Default Network.SLCAN.Types.SLCANConfig
- Network.SLCAN.Types: instance GHC.Classes.Eq Network.SLCAN.Types.SLCANBitrate
- Network.SLCAN.Types: instance GHC.Classes.Eq Network.SLCAN.Types.SLCANConfig
- Network.SLCAN.Types: instance GHC.Classes.Eq Network.SLCAN.Types.SLCANControl
- Network.SLCAN.Types: instance GHC.Classes.Eq Network.SLCAN.Types.SLCANCounters
- Network.SLCAN.Types: instance GHC.Classes.Eq Network.SLCAN.Types.SLCANError
- Network.SLCAN.Types: instance GHC.Classes.Eq Network.SLCAN.Types.SLCANMessage
- Network.SLCAN.Types: instance GHC.Classes.Eq Network.SLCAN.Types.SLCANState
- Network.SLCAN.Types: instance GHC.Classes.Ord Network.SLCAN.Types.SLCANBitrate
- Network.SLCAN.Types: instance GHC.Classes.Ord Network.SLCAN.Types.SLCANConfig
- Network.SLCAN.Types: instance GHC.Classes.Ord Network.SLCAN.Types.SLCANControl
- Network.SLCAN.Types: instance GHC.Classes.Ord Network.SLCAN.Types.SLCANCounters
- Network.SLCAN.Types: instance GHC.Classes.Ord Network.SLCAN.Types.SLCANError
- Network.SLCAN.Types: instance GHC.Classes.Ord Network.SLCAN.Types.SLCANMessage
- Network.SLCAN.Types: instance GHC.Classes.Ord Network.SLCAN.Types.SLCANState
- Network.SLCAN.Types: instance GHC.Enum.Bounded Network.SLCAN.Types.SLCANBitrate
- Network.SLCAN.Types: instance GHC.Enum.Bounded Network.SLCAN.Types.SLCANError
- Network.SLCAN.Types: instance GHC.Enum.Bounded Network.SLCAN.Types.SLCANState
- Network.SLCAN.Types: instance GHC.Enum.Enum Network.SLCAN.Types.SLCANBitrate
- Network.SLCAN.Types: instance GHC.Enum.Enum Network.SLCAN.Types.SLCANError
- Network.SLCAN.Types: instance GHC.Enum.Enum Network.SLCAN.Types.SLCANState
- Network.SLCAN.Types: instance GHC.Show.Show Network.SLCAN.Types.SLCANBitrate
- Network.SLCAN.Types: instance GHC.Show.Show Network.SLCAN.Types.SLCANConfig
- Network.SLCAN.Types: instance GHC.Show.Show Network.SLCAN.Types.SLCANControl
- Network.SLCAN.Types: instance GHC.Show.Show Network.SLCAN.Types.SLCANCounters
- Network.SLCAN.Types: instance GHC.Show.Show Network.SLCAN.Types.SLCANError
- Network.SLCAN.Types: instance GHC.Show.Show Network.SLCAN.Types.SLCANMessage
- Network.SLCAN.Types: instance GHC.Show.Show Network.SLCAN.Types.SLCANState
- Network.SLCAN.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Network.SLCAN.Types.SLCANBitrate
- Network.SLCAN.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Network.SLCAN.Types.SLCANControl
- Network.SLCAN.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Network.SLCAN.Types.SLCANCounters
- Network.SLCAN.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Network.SLCAN.Types.SLCANError
- Network.SLCAN.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Network.SLCAN.Types.SLCANMessage
- Network.SLCAN.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Network.SLCAN.Types.SLCANState
- Network.SLCAN.Types: numericBitrate :: SLCANBitrate -> Int
- Network.SocketCAN: data SocketCANT (m :: Type -> Type) a
- Network.SocketCAN: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Network.SocketCAN.SocketCANT m)
- Network.SocketCAN: instance Control.Monad.IO.Class.MonadIO m => Network.CAN.Class.MonadCAN (Network.SocketCAN.SocketCANT m)
- Network.SocketCAN: instance Control.Monad.IO.Unlift.MonadUnliftIO m => Control.Monad.IO.Unlift.MonadUnliftIO (Network.SocketCAN.SocketCANT m)
- Network.SocketCAN: instance Control.Monad.Trans.Class.MonadTrans Network.SocketCAN.SocketCANT
- Network.SocketCAN: instance GHC.Base.Applicative m => GHC.Base.Applicative (Network.SocketCAN.SocketCANT m)
- Network.SocketCAN: instance GHC.Base.Functor m => GHC.Base.Functor (Network.SocketCAN.SocketCANT m)
- Network.SocketCAN: instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader Network.Socket.Types.Socket (Network.SocketCAN.SocketCANT m)
- Network.SocketCAN: instance GHC.Base.Monad m => GHC.Base.Monad (Network.SocketCAN.SocketCANT m)
- Network.SocketCAN: runSocketCAN :: (MonadIO m, MonadUnliftIO m) => CANInterface -> SocketCANT m a -> m a
+ Network.SocketCAN: withSocket :: (MonadIO m, MonadThrow m) => Int -> (Socket -> m a) -> m a
- Network.SocketCAN: withSocketCAN :: Int -> (Socket -> IO a) -> IO a
+ Network.SocketCAN: withSocketCAN :: (MonadIO m, MonadThrow m) => CANInterface -> (CAN m -> m a) -> m a

Files

CHANGELOG.md view
@@ -1,3 +1,20 @@+# Version [0.2.0.0](https://github.com/DistRap/network-can/compare/0.1.0.0...0.2.0.0) (2026-04-29)++* Split `slcan` and `socketcan` into public sublibraries+* Migrate to `io-classes` and switch from `MonadCAN` typeclass+  to `CAN` handle (record of functions style):++  ```+  data CAN m = CAN+  { canSend :: CANMessage -> m ()+  , canRecv :: m CANMessage+  }+  ```+* Runners renamed+  * `runSocketCAN` is now `withSocketCAN`+  * `runSLCAN` is now `withSLCAN`+  to reflect the handle change+ # Version [0.1.0.0](https://github.com/DistRap/network-can/compare/d50564...0.1.0.0) (2025-05-19)  * Initial release
+ README.lhs view
@@ -0,0 +1,30 @@+# network-can++[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/DistRap/network-can/ci.yaml?branch=main)](https://github.com/DistRap/network-can/actions/workflows/ci.yaml)+[![Hackage version](https://img.shields.io/hackage/v/network-can.svg?color=success)](https://hackage.haskell.org/package/network-can)++CAN bus networking using Linux SocketCAN or SLCAN backends.++## Usage++```haskell+import qualified Control.Monad+import qualified Network.CAN+import qualified Network.SocketCAN++main :: IO ()+main = do+  Network.SocketCAN.withSocketCAN+    (Network.SocketCAN.mkCANInterface "vcan0")+    $ \can -> do+        Network.CAN.send+          can+          $ Network.CAN.standardMessage+              0x123+              [0xDE, 0xAD]++        Control.Monad.forever+          $ Network.CAN.recv+              can+            >>= putStrLn . Network.CAN.prettyCANMessage+```
README.md view
@@ -9,21 +9,22 @@  ```haskell import qualified Control.Monad-import qualified Control.Monad.IO.Class import qualified Network.CAN import qualified Network.SocketCAN  main :: IO () main = do-  Network.SocketCAN.runSocketCAN+  Network.SocketCAN.withSocketCAN     (Network.SocketCAN.mkCANInterface "vcan0")-    $ do+    $ \can -> do         Network.CAN.send+          can           $ Network.CAN.standardMessage               0x123               [0xDE, 0xAD]          Control.Monad.forever           $ Network.CAN.recv-            >>= Control.Monad.IO.Class.liftIO . print+              can+            >>= putStrLn . Network.CAN.prettyCANMessage ```
app/CANBridge.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE LambdaCase #-} module Main where -import Control.Monad.Trans (MonadTrans(lift))-import Data.Default.Class (Default(def))+import Control.Monad.Class.MonadAsync (race_)+import Data.Default (Default(def)) import Network.SLCAN (Transport(..)) import System.Hardware.Serialport (CommSpeed(..), SerialPortSettings(..)) @@ -11,7 +11,6 @@ import qualified Network.SocketCAN import qualified Network.SLCAN import qualified System.Hardware.Serialport-import qualified UnliftIO.Async  -- | Bridge vcan0 to slcan over /dev/can4discouart serial port main :: IO ()@@ -21,12 +20,12 @@     (System.Hardware.Serialport.defaultSerialSettings       { commSpeed = CS115200 }     )-  Network.SLCAN.runSLCAN (Transport_Handle h) def $ do-    Network.SocketCAN.runSocketCAN (Network.SocketCAN.mkCANInterface "vcan0") $ do-        UnliftIO.Async.race_+  Network.SLCAN.withSLCAN (Transport_Handle h) def $ \slcan -> do+    Network.SocketCAN.withSocketCAN (Network.SocketCAN.mkCANInterface "vcan0") $ \socketcan -> do+        race_           (Control.Monad.forever-           $ Network.CAN.recv >>= lift . Network.CAN.send+           $ Network.CAN.recv slcan >>= Network.CAN.send socketcan           )           (Control.Monad.forever-           $ lift Network.CAN.recv >>= Network.CAN.send+           $ Network.CAN.recv socketcan >>= Network.CAN.send slcan           )
app/CANDump.hs view
@@ -1,22 +1,16 @@ module Main where  import qualified Control.Monad-import qualified Control.Monad.IO.Class import qualified Network.CAN import qualified Network.SocketCAN  main :: IO () main = do-  Network.SocketCAN.runSocketCAN+  Network.SocketCAN.withSocketCAN     (Network.SocketCAN.mkCANInterface "vcan0")-    (Control.Monad.forever-       $ Network.CAN.recv-         >>= Control.Monad.IO.Class.liftIO . print-    )---- needs Network.CAN.Pretty or Builder or smthing--- that does the same ID formatting as SLCAN.Builder:78--- a la--- $ candump -e vcan0---  vcan0  001237E5   [2]  4C EE---  vcan0       7E5   [2]  4C EE+    $ \can ->+      (Control.Monad.forever+         $ Network.CAN.recv+             can+           >>= putStrLn . Network.CAN.prettyCANMessage+      )
app/SLCANSerial.hs view
@@ -1,9 +1,9 @@ module Main where -import Control.Monad.IO.Class-import Data.Default.Class (Default(def))+import Control.Monad.Class.MonadSay (MonadSay(say))+import Data.Default (Default(def)) import System.Hardware.Serialport (CommSpeed(..), SerialPortSettings(..))-import Network.CAN (MonadCAN)+import Network.CAN (CAN) import Network.SLCAN (Transport(..))  import qualified Control.Monad@@ -23,18 +23,18 @@         { commSpeed = CS115200 }       ) -  Network.SLCAN.runSLCAN+  Network.SLCAN.withSLCAN     (Transport_Handle h)     def     act  act-  :: ( MonadCAN m-     , MonadIO m-     )-  => m ()-act = do+  :: MonadSay m+  => CAN m+  -> m ()+act can = do   Network.CAN.send+    can     $ Network.CAN.standardMessage         -- vendorID SDO         0x601@@ -42,4 +42,5 @@    Control.Monad.forever     $ Network.CAN.recv-      >>= Control.Monad.IO.Class.liftIO . print+        can+      >>= say . Network.CAN.prettyCANMessage
app/SLCANUDP.hs view
@@ -1,8 +1,8 @@ module Main where -import Control.Monad.IO.Class-import Data.Default.Class (Default(def))-import Network.CAN (MonadCAN)+import Control.Monad.Class.MonadSay (MonadSay(say))+import Data.Default (Default(def))+import Network.CAN (CAN) import Network.SLCAN (Transport(..)) import Network.Socket (AddrInfo(..), SocketType(Datagram)) @@ -36,7 +36,7 @@         sock         (addrAddress ourAddrinfo) -      Network.SLCAN.runSLCAN+      Network.SLCAN.withSLCAN         (Transport_UDP sock (addrAddress targetAddrinfo))         def         act@@ -44,16 +44,17 @@     (_, _) -> error "getAddrInfo fail"  act-  :: ( MonadCAN m-     , MonadIO m-     )-  => m ()-act = do+  :: MonadSay m+  => CAN m+  -> m ()+act can = do   Network.CAN.send+    can     $ Network.CAN.standardMessage         0x7E5         [0x4C]    Control.Monad.forever     $ Network.CAN.recv-      >>= Control.Monad.IO.Class.liftIO . print+        can+      >>= say . Network.CAN.prettyCANMessage
network-can.cabal view
@@ -1,6 +1,6 @@-cabal-version:       2.2+cabal-version:       3.0 name:                network-can-version:             0.1.0.0+version:             0.2.0.0 synopsis:            CAN bus networking description:         Talk to CAN buses using Linux SocketCAN and SLCAN homepage:            https://github.com/DistRap/network-can@@ -25,97 +25,141 @@   description:     Build example applications +flag build-readme+  default:+    False+  description:+    Build readme example++common commons+  ghc-options:         -Wall -Wunused-packages+  default-language:    Haskell2010++common execs+  ghc-options:         -threaded+                       -rtsopts+                       "-with-rtsopts -N"+ library-  ghc-options:         -Wall+  import:              commons   hs-source-dirs:      src   exposed-modules:     Network.CAN-                       Network.CAN.Class                        Network.CAN.Types-                       Network.SLCAN+  build-depends:       base >= 4.7 && < 5+                     , QuickCheck++library slcan+  import:              commons+  visibility:          public+  hs-source-dirs:      src-slcan+  exposed-modules:     Network.SLCAN                        Network.SLCAN.Builder                        Network.SLCAN.Parser                        Network.SLCAN.Types-                       Network.SocketCAN-                       Network.SocketCAN.Bindings-                       Network.SocketCAN.Example-                       Network.SocketCAN.LowLevel-                       Network.SocketCAN.Translate-   build-depends:       base >= 4.7 && < 5                      , attoparsec >= 0.14                      , bytestring                      , containers-                     , data-default-class-                     , mtl+                     , data-default+                     , io-classes                      , network >= 3.1+                     , network-can                      , QuickCheck-                     , transformers-                     , unliftio +library socketcan+  import:              commons+  visibility:          public+  hs-source-dirs:      src-socketcan+  exposed-modules:     Network.SocketCAN+                       Network.SocketCAN.Bindings+                       Network.SocketCAN.Example+                       Network.SocketCAN.LowLevel+                       Network.SocketCAN.Translate+  build-depends:       base >= 4.7 && < 5+                     , io-classes+                     , network >= 3.1+                     , network-can   build-tool-depends:  hsc2hs:hsc2hs-  default-language:    Haskell2010  test-suite pure   type:                exitcode-stdio-1.0   ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N   hs-source-dirs:      test   main-is:             Spec.hs-  other-modules:       Samples+  other-modules:       CANSpec+                       Samples                        SLCANSpec                        SocketCANSpec   build-tool-depends:  hspec-discover:hspec-discover   build-depends:       base >= 4.7 && < 5                      , hspec                      , network-can+                     , network-can:slcan+                     , network-can:socketcan   default-language:    Haskell2010  executable hcandump+  import:              commons, execs   if !flag(build-apps)     buildable: False   build-depends:       base >=4.7 && <5                      , network-can-  default-language:    Haskell2010+                     , network-can:socketcan   main-is:             CANDump.hs   hs-source-dirs:      app-  ghc-options:         -Wall -threaded -rtsopts "-with-rtsopts -N"  executable hcanbridge+  import:              commons, execs   if !flag(build-apps)     buildable: False   build-depends:       base >=4.7 && <5                      , network-can-                     , data-default-class-                     , mtl+                     , network-can:slcan+                     , network-can:socketcan+                     , data-default+                     , io-classes                      , serialport >= 0.5.5-                     , unliftio-  default-language:    Haskell2010   main-is:             CANBridge.hs   hs-source-dirs:      app-  ghc-options:         -Wall -threaded -rtsopts "-with-rtsopts -N"  executable hslcanserial+  import:              commons, execs   if !flag(build-apps)     buildable: False   build-depends:       base >=4.7 && <5                      , network-can-                     , data-default-class+                     , network-can:slcan+                     , data-default+                     , io-classes                      , serialport >= 0.5.5-  default-language:    Haskell2010   main-is:             SLCANSerial.hs   hs-source-dirs:      app-  ghc-options:         -Wall -threaded -rtsopts "-with-rtsopts -N"  executable hslcanudp+  import:              commons, execs   if !flag(build-apps)     buildable: False   build-depends:       base >=4.7 && <5                      , network                      , network-can-                     , data-default-class-  default-language:    Haskell2010+                     , network-can:slcan+                     , data-default+                     , io-classes   main-is:             SLCANUDP.hs   hs-source-dirs:      app-  ghc-options:         -Wall -threaded -rtsopts "-with-rtsopts -N"++executable readme+  import:              commons, execs+  if !flag(build-readme)+    buildable: False+  build-depends:+      base >=4.7 && <5+    , network-can+    , network-can:socketcan+  build-tool-depends:+      markdown-unlit:markdown-unlit+  main-is: README.lhs+  ghc-options: -pgmL markdown-unlit -Wall  source-repository head   type:     git
+ src-slcan/Network/SLCAN.hs view
@@ -0,0 +1,168 @@+{-# 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+            }
+ src-slcan/Network/SLCAN/Builder.hs view
@@ -0,0 +1,146 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}++module Network.SLCAN.Builder+  ( buildSLCANMessage+  ) where++import Data.ByteString (ByteString)+import Data.ByteString.Builder (Builder)+import Data.Set (Set)+import Network.CAN.Types (CANArbitrationField(..), CANMessage(..))+import Network.SLCAN.Types+  ( SLCANMessage(..)+  , SLCANControl(..)+  , SLCANState(..)+  , SLCANCounters(..)+  , SLCANError(..)+  )+import qualified Data.Bits+import qualified Data.Set+import qualified Data.ByteString.Lazy+import qualified Data.ByteString.Builder++slCANBuilder+  :: SLCANMessage+  -> Builder+slCANBuilder slcanMsg =+  case slcanMsg of+    SLCANMessage_Control ctrlMsg -> slCANControlBuilder ctrlMsg+    SLCANMessage_Data canMsg -> slCANDataBuilder canMsg+    SLCANMessage_State state counters  -> slCANStateBuilder state counters+    SLCANMessage_Error errs -> slCANErrorBuilder errs+  <> Data.ByteString.Builder.char7 '\r'++slCANControlBuilder+  :: SLCANControl+  -> Builder+slCANControlBuilder SLCANControl_Open =+  Data.ByteString.Builder.char7 'O'+slCANControlBuilder SLCANControl_Close =+  Data.ByteString.Builder.char7 'C'+slCANControlBuilder (SLCANControl_Bitrate bitrate) =+     Data.ByteString.Builder.char7 'S'+  <> Data.ByteString.Builder.intDec+      (fromEnum bitrate)+slCANControlBuilder SLCANControl_ResetErrors =+  Data.ByteString.Builder.char7 'F'+slCANControlBuilder SLCANControl_ListenOnly =+  Data.ByteString.Builder.char7 'L'++slCANDataBuilder+  :: CANMessage+  -> Builder+slCANDataBuilder CANMessage{..} =+     arbitrationId canMessageArbitrationField+  <> Data.ByteString.Builder.word8Hex+      (fromIntegral $ length canMessageData)+  <> mconcat+      (map+         Data.ByteString.Builder.word8HexFixed+         canMessageData+      )++arbitrationId+  :: CANArbitrationField+  -> Builder+arbitrationId CANArbitrationField{..} =+     Data.ByteString.Builder.char7+       (case ( canArbitrationFieldExtended+             , canArbitrationFieldRTR+             )+        of+         (False, False) -> 't'+         (False, True)  -> 'r'+         (True, False)  -> 'T'+         (True, True)   -> 'R'+       )+  <> (if canArbitrationFieldExtended+      then Data.ByteString.Builder.word32HexFixed+      else+        (\word11 ->+           Data.ByteString.Builder.word8Hex+            (fromIntegral (word11 `Data.Bits.shiftR` 8))+        <> Data.ByteString.Builder.word8HexFixed+            (fromIntegral word11)+        )+     )+     canArbitrationFieldID++slCANStateBuilder+  :: SLCANState+  -> SLCANCounters+  -> Builder+slCANStateBuilder state SLCANCounters{..} =+     Data.ByteString.Builder.char7 's'+  <> Data.ByteString.Builder.char7+      (case state of+         SLCANState_Active -> 'a'+         SLCANState_Warning -> 'w'+         SLCANState_Passive -> 'p'+         SLCANState_BusOff -> 'b'+      )+  <> word16Dec3 slCANCountersTxErrors+  <> word16Dec3 slCANCountersRxErrors+  where+    -- encode as 3 bytes (maximum of 999 and zero padded)+    word16Dec3 x =+           (case x of+              _ | x < 10 -> Data.ByteString.Builder.string7 "00"+              _ | x < 100 -> Data.ByteString.Builder.char7 '0'+              _ | otherwise -> mempty+           )+        <> Data.ByteString.Builder.word16Dec+            (min 999 x)++slCANErrorBuilder+  :: Set SLCANError+  -> Builder+slCANErrorBuilder errs =+     Data.ByteString.Builder.char7 'e'+  <> Data.ByteString.Builder.word8Hex+      (fromIntegral $ Data.Set.size errs)+  <> mconcat+      (map+         ( Data.ByteString.Builder.char7+         . \case+             SLCANError_Ack -> 'a'+             SLCANError_Bit0 -> 'b'+             SLCANError_Bit1 -> 'B'+             SLCANError_CRC -> 'c'+             SLCANError_Form -> 'f'+             SLCANError_RxOverrun -> 'o'+             SLCANError_TxOverrun -> 'O'+             SLCANError_Stuff -> 's'+         )+         $ Data.Set.toList+             errs+      )++buildSLCANMessage+  :: SLCANMessage+  -> ByteString+buildSLCANMessage =+   Data.ByteString.Lazy.toStrict+ . Data.ByteString.Builder.toLazyByteString+ . slCANBuilder
+ src-slcan/Network/SLCAN/Parser.hs view
@@ -0,0 +1,179 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}++module Network.SLCAN.Parser+  ( parseSLCANMessage+  ) where++import Data.Attoparsec.ByteString.Char8 (Parser)+import Data.Bits (Bits)+import Data.ByteString (ByteString)+import Data.Set (Set)+import Control.Applicative ((<|>))+import Network.CAN.Types (CANArbitrationField(..), CANMessage(..))+import Network.SLCAN.Types+  ( SLCANMessage(..)+  , SLCANControl(..)+  , SLCANBitrate+  , SLCANState(..)+  , SLCANCounters(..)+  , SLCANError(..)+  )++import qualified Data.Attoparsec.ByteString.Char8+import qualified Data.Set+import qualified Control.Monad++slCANParser :: Parser SLCANMessage+slCANParser = do+  Data.Attoparsec.ByteString.Char8.peekChar'+    >>= \case+      c | Data.Attoparsec.ByteString.Char8.inClass "OCSFL" c ->+        (SLCANMessage_Control <$> slCANControlParser)+      c | Data.Attoparsec.ByteString.Char8.inClass "tTrR" c ->+        (SLCANMessage_Data <$> slCANDataParser)+      's' ->+        Data.Attoparsec.ByteString.Char8.char 's'+        *> (SLCANMessage_State <$> slCANStateParser <*> slCANCountersParser)+      'e' ->+        Data.Attoparsec.ByteString.Char8.char 'e'+        *> (SLCANMessage_Error <$> slCANErrorParser)+      c | otherwise ->+        fail $ "Unknown SLCAN message type: " <> show c+  <* Data.Attoparsec.ByteString.Char8.char '\r'++slCANControlParser :: Parser SLCANControl+slCANControlParser = do+  Data.Attoparsec.ByteString.Char8.anyChar+  >>= \case+    'O' -> pure SLCANControl_Open+    'C' -> pure SLCANControl_Close+    'S' -> (SLCANControl_Bitrate <$> bitrate)+    'F' -> pure SLCANControl_ResetErrors+    'L' -> pure SLCANControl_ListenOnly+    c   -> fail $ "Unknown control message char: " <> show c+  where+    bitrate = do+      d <- Data.Attoparsec.ByteString.Char8.decimal+      if d > fromEnum (maxBound :: SLCANBitrate)+      then fail+             $ "Bitrate out of bounds, got "+             <> show d+             <> "but maximum is "+             <> show (fromEnum (maxBound :: SLCANBitrate))+             <> " ("+             <> show  (maxBound :: SLCANBitrate)+             <> ")"+      else pure $ toEnum d++slCANDataParser :: Parser CANMessage+slCANDataParser = do+  canMessageArbitrationField <- arbitrationId+  msgLen <- hexadecimalWithLength 1+  canMessageData <-+    Control.Monad.replicateM msgLen (hexadecimalWithLength 2)+  pure CANMessage{..}++-- | Parse arbitration ID+-- * t => 11 bit data frame+-- * r => 11 bit RTR frame+-- * T => 29 bit data frame+-- * R => 29 bit RTR frame+arbitrationId :: Parser CANArbitrationField+arbitrationId = do+      Data.Attoparsec.ByteString.Char8.char 't' *> stdID False+  <|> Data.Attoparsec.ByteString.Char8.char 'r' *> stdID True+  <|> Data.Attoparsec.ByteString.Char8.char 'T' *> extID False+  <|> Data.Attoparsec.ByteString.Char8.char 'R' *> extID True++stdID+  :: Bool+  -> Parser CANArbitrationField+stdID isRTR = do+  canArbitrationFieldID+    <- hexadecimalWithLength 3++  let+    canArbitrationFieldExtended = False+    canArbitrationFieldRTR = isRTR++  pure CANArbitrationField{..}++extID+  :: Bool+  -> Parser CANArbitrationField+extID isRTR = do+  canArbitrationFieldID+    <- hexadecimalWithLength 8++  let+    canArbitrationFieldExtended = True+    canArbitrationFieldRTR = isRTR++  pure CANArbitrationField{..}++hexadecimalWithLength+  :: ( Bits a+     , Integral a+     )+  => Int+  -> Parser a+hexadecimalWithLength len =+  Data.Attoparsec.ByteString.Char8.take len+  >>=+  either+    fail+    pure+    . Data.Attoparsec.ByteString.Char8.parseOnly+        Data.Attoparsec.ByteString.Char8.hexadecimal++slCANStateParser :: Parser SLCANState+slCANStateParser =+  Data.Attoparsec.ByteString.Char8.anyChar+  >>= \case+    'a' -> pure SLCANState_Active+    'w' -> pure SLCANState_Warning+    'p' -> pure SLCANState_Passive+    'b' -> pure SLCANState_BusOff+    c   -> fail $ "Unknown state char: " <> show c++slCANCountersParser :: Parser SLCANCounters+slCANCountersParser = do+    slCANCountersTxErrors <- decimal3+    slCANCountersRxErrors <- decimal3+    pure $ SLCANCounters{..}+  where+    decimal3 =+      Data.Attoparsec.ByteString.Char8.take 3+      >>=+      either+        fail+        pure+        . Data.Attoparsec.ByteString.Char8.parseOnly+            Data.Attoparsec.ByteString.Char8.decimal++slCANErrorParser :: Parser (Set SLCANError)+slCANErrorParser = do+  len <- hexadecimalWithLength 1+  Data.Set.fromList+    <$> Control.Monad.replicateM len errorChar+  where+    errorChar =+      Data.Attoparsec.ByteString.Char8.anyChar+      >>= \case+        'a' -> pure SLCANError_Ack+        'b' -> pure SLCANError_Bit0+        'B' -> pure SLCANError_Bit1+        'c' -> pure SLCANError_CRC+        'f' -> pure SLCANError_Form+        'o' -> pure SLCANError_RxOverrun+        'O' -> pure SLCANError_TxOverrun+        's' -> pure SLCANError_Stuff+        c   -> fail $ "Unknown error char: " <> show c++parseSLCANMessage+  :: ByteString+  -> Either String SLCANMessage+parseSLCANMessage =+  Data.Attoparsec.ByteString.Char8.parseOnly+    slCANParser
+ src-slcan/Network/SLCAN/Types.hs view
@@ -0,0 +1,129 @@+{-# LANGUAGE NumericUnderscores #-}+module Network.SLCAN.Types+  ( SLCANMessage(..)+  , SLCANControl(..)+  , SLCANBitrate(..)+  , numericBitrate+  , SLCANState(..)+  , SLCANCounters(..)+  , SLCANError(..)+  , SLCANConfig(..)+  ) where++import Data.Default (Default(def))+import Data.Set (Set)+import Data.Word (Word16)+import Network.CAN.Types (CANMessage)+import Test.QuickCheck (Arbitrary(..))++import qualified Test.QuickCheck++data SLCANMessage+  = SLCANMessage_Control SLCANControl+  | SLCANMessage_Data CANMessage+  | SLCANMessage_State SLCANState SLCANCounters+  | SLCANMessage_Error (Set SLCANError)+  deriving (Eq, Ord, Show)++instance Arbitrary SLCANMessage where+  arbitrary = Test.QuickCheck.oneof+    [ SLCANMessage_Control <$> arbitrary+    , SLCANMessage_Data <$> arbitrary+    , SLCANMessage_State <$> arbitrary <*> arbitrary+    , SLCANMessage_Error <$> arbitrary+    ]++data SLCANControl+  = SLCANControl_Open+  | SLCANControl_Close+  | SLCANControl_Bitrate SLCANBitrate+  | SLCANControl_ResetErrors+  | SLCANControl_ListenOnly+  deriving (Eq, Ord, Show)++instance Arbitrary SLCANControl where+  arbitrary = Test.QuickCheck.oneof+    [ pure SLCANControl_Open+    , pure SLCANControl_Close+    , SLCANControl_Bitrate <$> arbitrary+    , pure SLCANControl_ResetErrors+    , pure SLCANControl_ListenOnly+    ]++data SLCANBitrate+  = SLCANBitrate_10K+  | SLCANBitrate_20K+  | SLCANBitrate_50K+  | SLCANBitrate_100K+  | SLCANBitrate_125K+  | SLCANBitrate_250K+  | SLCANBitrate_500K+  | SLCANBitrate_800K+  | SLCANBitrate_1M+  deriving (Bounded, Eq, Enum, Ord, Show)++instance Arbitrary SLCANBitrate where+  arbitrary = Test.QuickCheck.arbitraryBoundedEnum++instance Default SLCANBitrate where+  def = SLCANBitrate_1M++numericBitrate :: SLCANBitrate -> Int+numericBitrate SLCANBitrate_10K  =    10_000+numericBitrate SLCANBitrate_20K  =    20_000+numericBitrate SLCANBitrate_50K  =    50_000+numericBitrate SLCANBitrate_100K =   100_000+numericBitrate SLCANBitrate_125K =   125_000+numericBitrate SLCANBitrate_250K =   250_000+numericBitrate SLCANBitrate_500K =   500_000+numericBitrate SLCANBitrate_800K =   800_000+numericBitrate SLCANBitrate_1M   = 1_000_000++data SLCANState+  = SLCANState_Active+  | SLCANState_Warning+  | SLCANState_Passive+  | SLCANState_BusOff+  deriving (Bounded, Eq, Enum, Ord, Show)++instance Arbitrary SLCANState where+  arbitrary = Test.QuickCheck.arbitraryBoundedEnum++data SLCANCounters = SLCANCounters+  { slCANCountersRxErrors :: Word16+  , slCANCountersTxErrors :: Word16+  } deriving (Eq, Ord, Show)++instance Arbitrary SLCANCounters where+  arbitrary =+    SLCANCounters+      <$> Test.QuickCheck.choose (0, 999)+      <*> Test.QuickCheck.choose (0, 999)++data SLCANError+  = SLCANError_Ack+  | SLCANError_Bit0+  | SLCANError_Bit1+  | SLCANError_CRC+  | SLCANError_Form+  | SLCANError_RxOverrun+  | SLCANError_TxOverrun+  | SLCANError_Stuff+  deriving (Bounded, Eq, Enum, Ord, Show)++instance Arbitrary SLCANError where+  arbitrary = Test.QuickCheck.arbitraryBoundedEnum++data SLCANConfig = SLCANConfig+  { slCANConfigBitrate :: SLCANBitrate+  , slCANConfigResetErrors :: Bool+  , slCANConfigListenOnly :: Bool+  } deriving (Eq, Ord, Show)++instance Default SLCANConfig where+  def =+    SLCANConfig+    { slCANConfigBitrate = def+    , slCANConfigResetErrors = False+    , slCANConfigListenOnly = False+    }
+ src-socketcan/Network/SocketCAN.hs view
@@ -0,0 +1,96 @@+module Network.SocketCAN+  ( withSocket+  , sendCANMessage+  , recvCANMessage+  , Network.Socket.ifNameToIndex+  , CANInterface+  , mkCANInterface+  , NoSuchInterface(..)+  , withSocketCAN+  ) where++import Network.CAN (CANMessage, CAN(..))+import Network.Socket (Socket)+import Network.SocketCAN.Bindings (SockAddrCAN(..))++import Control.Monad.Class.MonadThrow (Exception(..), MonadThrow(bracket, throwIO))+import Control.Monad.IO.Class (MonadIO(..))++import qualified Network.Socket (ifNameToIndex)+import qualified Network.SocketCAN.LowLevel+import qualified Network.SocketCAN.Translate++withSocket+  :: ( MonadIO m+     , MonadThrow m+     )+  => Int+  -> (Socket -> m a)+  -> m a+withSocket ifaceIdx act = do+  bracket+    (liftIO Network.SocketCAN.LowLevel.socket)+    (liftIO . Network.SocketCAN.LowLevel.close)+    (\canSock -> do+      liftIO+        $ Network.SocketCAN.LowLevel.bind+            canSock+            $ Network.SocketCAN.Bindings.SockAddrCAN+              $ fromIntegral ifaceIdx+      act canSock+    )++sendCANMessage+  :: Socket+  -> CANMessage+  -> IO ()+sendCANMessage canSock cm =+  Network.SocketCAN.LowLevel.send+    canSock+    (Network.SocketCAN.Translate.toSocketCANFrame cm)++recvCANMessage+  :: Socket+  -> IO CANMessage+recvCANMessage canSock =+  Network.SocketCAN.LowLevel.recv canSock+  >>= pure . Network.SocketCAN.Translate.fromSocketCANFrame++newtype CANInterface = CANInterface+  { unCANInterface :: String }+  deriving Eq++instance Show CANInterface where+  show = unCANInterface++mkCANInterface :: String -> CANInterface+mkCANInterface = CANInterface++data NoSuchInterface = NoSuchInterface+    deriving Show++instance Exception NoSuchInterface++withSocketCAN+  :: ( MonadIO m+     , MonadThrow m+     )+  => CANInterface+  -> (CAN m -> m a)+  -> m a+withSocketCAN interface act = do+  mIdx <-+    liftIO+    $ Network.Socket.ifNameToIndex (unCANInterface interface)++  case mIdx of+    Nothing -> throwIO NoSuchInterface+    Just idx ->+      withSocket+        idx+        $ \sock ->+            act+              CAN+                { canSend = liftIO . sendCANMessage sock+                , canRecv = liftIO $ recvCANMessage sock+                }
+ src-socketcan/Network/SocketCAN/Bindings.hsc view
@@ -0,0 +1,79 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Network.SocketCAN.Bindings+  (+  -- * Network package compatibility+    SockAddrCAN(..)+  , pattern CAN_RAW+  -- * SocketCAN bindings+  , SocketCANArbitrationField(..)+  , SocketCANFrame(..)+  ) where++import Data.Word (Word8, Word16, Word32)+import Foreign.Storable (Storable(..))+import Foreign.Marshal.Array (peekArray, pokeArray)+import Foreign.Ptr (plusPtr)+import Network.Socket.Address (SocketAddress(..))+import Network.Socket (ProtocolNumber)++#include <linux/can.h>+#include <sys/socket.h>++-- | CAN Socket type+newtype SockAddrCAN = SockAddrCAN Word32+  deriving (Eq, Ord)++-- Word16+type CSaFamily = (#type sa_family_t)++instance SocketAddress SockAddrCAN where+  sizeOfSocketAddress (SockAddrCAN _) =+    #const sizeof(struct sockaddr_can)+  peekSocketAddress sap = do+    ifidx <- (#peek struct sockaddr_can, can_ifindex) sap+    return (SockAddrCAN ifidx)+  pokeSocketAddress p (SockAddrCAN ifIndex) = do+    (#poke struct sockaddr_can, can_family) p ((#const AF_CAN) :: CSaFamily)+    (#poke struct sockaddr_can, can_ifindex) p ifIndex++-- | CAN RAW protocol family of PF_CAN+pattern CAN_RAW :: ProtocolNumber+pattern CAN_RAW = #const CAN_RAW++-- | SocketCAN Arbitration field (CAN ID including RTR, EFF, ERR bits)+newtype SocketCANArbitrationField =+  SocketCANArbitrationField { unSocketCANArbitrationField :: Word32 }+  deriving (Eq, Ord, Show, Storable)++data SocketCANFrame = SocketCANFrame+  { socketCANFrameArbitrationField :: SocketCANArbitrationField+  , socketCANFrameLength :: Word8+  , socketCANFrameData   :: [Word8]+  } deriving Show++instance Storable SocketCANFrame where+  sizeOf ~_ = #const sizeof(struct can_frame)+  alignment ~_ = #alignment struct can_frame+  peek ptr = do+    socketCANFrameArbitrationField+      <- #{peek struct can_frame, can_id} ptr+    socketCANFrameLength+      <- #{peek struct can_frame, len} ptr+    socketCANFrameData <-+      peekArray+        (fromIntegral socketCANFrameLength)+        (#{ptr struct can_frame, data} ptr)+    pure+      $ SocketCANFrame{..}+  poke ptr SocketCANFrame{..} = do+    #{poke struct can_frame, can_id}+      ptr+      socketCANFrameArbitrationField+    #{poke struct can_frame, len}+      ptr+      socketCANFrameLength+    pokeArray+      (#{ptr struct can_frame, data} ptr)+      socketCANFrameData
+ src-socketcan/Network/SocketCAN/Example.hs view
@@ -0,0 +1,41 @@+module Network.SocketCAN.Example where++import Control.Monad (forever)+import Network.CAN+import Network.Socket (Socket)+import Network.SocketCAN++import qualified Network.Socket++example :: IO ()+example = do+  let interface = "vcan0"+  mIdx <- Network.Socket.ifNameToIndex interface+  case mIdx of+    Nothing -> error $ "Interface " <> interface <> " not found"+    Just idx ->+      withSocket idx act++act :: Socket -> IO ()+act sock = do+  sendCANMessage+    sock+    $ standardMessage+        0x123+        [0xDE, 0xAD]++  sendCANMessage+    sock+    $ CANMessage+        (extendedID 0x123456)+        [0xEE]++  sendCANMessage+    sock+    $ CANMessage+        (setRTR $ extendedID 0x123)+        [0xDE, 0xAD, 0x11]++  forever+    $ recvCANMessage sock+    >>= print
+ src-socketcan/Network/SocketCAN/LowLevel.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE TypeApplications #-}+module Network.SocketCAN.LowLevel+  ( socket+  , bind+  , send+  , recv+  , module Network.Socket+  ) where++import Control.Monad (void)+import Foreign.Ptr (Ptr)+import Network.Socket (Family(AF_CAN), Socket, close)+import Network.SocketCAN.Bindings (SockAddrCAN(..), SocketCANFrame)++import qualified Foreign.Ptr+import qualified Foreign.Marshal.Alloc+import qualified Foreign.Storable+import qualified Network.Socket+import qualified Network.Socket.Address+import qualified Network.SocketCAN.Bindings++-- | Create raw CAN socket+socket+  :: IO Socket+socket =+  Network.Socket.socket+    AF_CAN+    Network.Socket.Raw+    Network.SocketCAN.Bindings.CAN_RAW++-- | Bind CAN socket+bind+  :: Socket+  -> SockAddrCAN+  -> IO ()+bind = Network.Socket.Address.bind++send+  :: Socket+  -> SocketCANFrame+  -> IO ()+send canSock cf =+  Foreign.Marshal.Alloc.alloca $ \ptr -> do+    Foreign.Storable.poke ptr cf+    void+      $ Network.Socket.sendBuf+          canSock+          (Foreign.Ptr.castPtr ptr)+          (Foreign.Storable.sizeOf cf)++recv+  :: Socket+  -> IO SocketCANFrame+recv canSock =+  Foreign.Marshal.Alloc.alloca $ \ptr -> do+    (_nBytes, _sockAddr) <-+      Network.Socket.Address.recvBufFrom+        @SockAddrCAN+        canSock+        (ptr :: Ptr SocketCANFrame)+        (Foreign.Storable.sizeOf (undefined :: SocketCANFrame))+    Foreign.Storable.peek ptr >>= pure
+ src-socketcan/Network/SocketCAN/Translate.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE RecordWildCards #-}++-- | Translation between CANMessage and SocketCANFrame++module Network.SocketCAN.Translate+  ( toSocketCANFrame+  , fromSocketCANFrame+  ) where++import Data.Bits ((.&.), (.|.), shiftL)+import Data.Word (Word32)+import Network.CAN.Types (CANArbitrationField(..), CANMessage(..))+import Network.SocketCAN.Bindings (SocketCANArbitrationField(..), SocketCANFrame(..))+import qualified Data.Bool++toSocketCANFrame+  :: CANMessage+  -> SocketCANFrame+toSocketCANFrame CANMessage{..} =+  SocketCANFrame+  { socketCANFrameArbitrationField =+      toSocketCANArbitrationField+        canMessageArbitrationField+  , socketCANFrameLength =+      fromIntegral $ length canMessageData+  , socketCANFrameData = canMessageData+  }++fromSocketCANFrame+  :: SocketCANFrame+  -> CANMessage+fromSocketCANFrame SocketCANFrame{..} =+  CANMessage+  { canMessageArbitrationField =+      fromSocketCANArbitrationField+        socketCANFrameArbitrationField+  , canMessageData = socketCANFrameData+  }++toSocketCANArbitrationField+  :: CANArbitrationField+  -> SocketCANArbitrationField+toSocketCANArbitrationField CANArbitrationField{..} =+  SocketCANArbitrationField+  $ Data.Bool.bool+      id+      (.|. effBit)+      canArbitrationFieldExtended+  $ Data.Bool.bool+      id+      (.|. rtrBit)+      canArbitrationFieldRTR+  $ canArbitrationFieldID++fromSocketCANArbitrationField+  :: SocketCANArbitrationField+  -> CANArbitrationField+fromSocketCANArbitrationField (SocketCANArbitrationField scid) =+  let+    isEff = scid .&. effBit /= 0+  in+    CANArbitrationField+    { canArbitrationFieldID =+        Data.Bool.bool+          (.&. (1 `shiftL` 12 - 1))+          (.&. (1 `shiftL` 30 - 1))+          isEff+        $ scid+    , canArbitrationFieldExtended = isEff+    , canArbitrationFieldRTR = scid .&. rtrBit /= 0+    }++effBit :: Word32+effBit = 1 `shiftL` 31++rtrBit :: Word32+rtrBit = 1 `shiftL` 30
src/Network/CAN.hs view
@@ -1,7 +1,24 @@ module Network.CAN-  ( module Network.CAN.Class+  ( CAN(..)+  , send+  , recv   , module Network.CAN.Types   ) where -import Network.CAN.Class import Network.CAN.Types++data CAN m = CAN+  { canSend :: CANMessage -> m ()+  , canRecv :: m CANMessage+  }++send+  :: CAN m+  -> CANMessage+  -> m ()+send = canSend++recv+  :: CAN m+  -> m CANMessage+recv = canRecv
− src/Network/CAN/Class.hs
@@ -1,39 +0,0 @@-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TypeOperators #-}-module Network.CAN.Class-  ( MonadCAN(..)-  ) where--import Control.Monad.Trans (MonadTrans, lift)-import Control.Monad.Trans.Except (ExceptT)-import Control.Monad.Trans.Reader (ReaderT)-import Control.Monad.Trans.State (StateT)-import Network.CAN.Types (CANMessage(..))--class Monad m => MonadCAN m where--  send :: CANMessage -> m ()-  default send-    :: ( MonadTrans t-       , MonadCAN m'-       , m ~ t m'-       )-    => CANMessage-    -> m ()-  send = lift . send--  recv :: m CANMessage-  default recv-    :: ( MonadTrans t-       , MonadCAN m'-       , m ~ t m'-       )-    => m CANMessage-  recv = lift recv--instance MonadCAN m => MonadCAN (ExceptT e m)-instance MonadCAN m => MonadCAN (ReaderT r m)-instance MonadCAN m => MonadCAN (StateT s m)
src/Network/CAN/Types.hs view
@@ -8,19 +8,21 @@   -- * Message   , CANMessage(..)   , standardMessage+  , prettyCANMessage   ) where  import Data.Word (Word8, Word16, Word32) import Test.QuickCheck (Arbitrary(..))  import qualified Test.QuickCheck+import qualified Text.Printf  -- * Arbitration  data CANArbitrationField = CANArbitrationField-  { canArbitrationFieldID :: Word32 -- ^ CAN ID-  , canArbitrationFieldExtended :: Bool -- ^ Extended CAN ID-  , canArbitrationFieldRTR :: Bool -- ^ Remote transmission request+  { canArbitrationFieldID       :: Word32 -- ^ CAN ID+  , canArbitrationFieldExtended :: Bool   -- ^ Extended CAN ID+  , canArbitrationFieldRTR      :: Bool   -- ^ Remote transmission request   } deriving (Eq, Ord, Show)  instance Arbitrary CANArbitrationField where@@ -89,3 +91,40 @@   { canMessageArbitrationField = standardID cid   , canMessageData = cdata   }++-- | Pretty print @CANMessage@ similar to candump output+--+-- > prettyCANMessage (standardMessage 123 [0x13, 0x37])+-- "     07B   [2]  13 37"+-- > prettyCANMessage (CANMessage (extendedID 123) [0x13, 0x37])+-- "0000007B   [2]  13 37"+prettyCANMessage+  :: CANMessage+  -> String+prettyCANMessage msg =+  unwords+    $ [ prettyArb+          $ canMessageArbitrationField msg+      , "  [" <> show (length $ canMessageData msg) <> "] "+      ]+      ++ prettyData+           (canArbitrationFieldRTR $ canMessageArbitrationField msg)+           (canMessageData msg)+  where+    prettyArb arb | canArbitrationFieldExtended arb =+      hexFixed+        8+        $ canArbitrationFieldID arb+    prettyArb arb | otherwise =+         replicate 5 ' '+      <> hexFixed+          3+          (canArbitrationFieldID arb)++    prettyData :: Bool -> [Word8] -> [String]+    prettyData True _ = pure "remote request"+    prettyData _    x = map (hexFixed 2) x++    hexFixed width =+      Text.Printf.printf+        $ "%0" <> show (width :: Int) <> "X"
− src/Network/SLCAN.hs
@@ -1,193 +0,0 @@-{-# 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
− src/Network/SLCAN/Builder.hs
@@ -1,146 +0,0 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE RecordWildCards #-}--module Network.SLCAN.Builder-  ( buildSLCANMessage-  ) where--import Data.ByteString (ByteString)-import Data.ByteString.Builder (Builder)-import Data.Set (Set)-import Network.CAN.Types (CANArbitrationField(..), CANMessage(..))-import Network.SLCAN.Types-  ( SLCANMessage(..)-  , SLCANControl(..)-  , SLCANState(..)-  , SLCANCounters(..)-  , SLCANError(..)-  )-import qualified Data.Bits-import qualified Data.Set-import qualified Data.ByteString.Lazy-import qualified Data.ByteString.Builder--slCANBuilder-  :: SLCANMessage-  -> Builder-slCANBuilder slcanMsg =-  case slcanMsg of-    SLCANMessage_Control ctrlMsg -> slCANControlBuilder ctrlMsg-    SLCANMessage_Data canMsg -> slCANDataBuilder canMsg-    SLCANMessage_State state counters  -> slCANStateBuilder state counters-    SLCANMessage_Error errs -> slCANErrorBuilder errs-  <> Data.ByteString.Builder.char7 '\r'--slCANControlBuilder-  :: SLCANControl-  -> Builder-slCANControlBuilder SLCANControl_Open =-  Data.ByteString.Builder.char7 'O'-slCANControlBuilder SLCANControl_Close =-  Data.ByteString.Builder.char7 'C'-slCANControlBuilder (SLCANControl_Bitrate bitrate) =-     Data.ByteString.Builder.char7 'S'-  <> Data.ByteString.Builder.intDec-      (fromEnum bitrate)-slCANControlBuilder SLCANControl_ResetErrors =-  Data.ByteString.Builder.char7 'F'-slCANControlBuilder SLCANControl_ListenOnly =-  Data.ByteString.Builder.char7 'L'--slCANDataBuilder-  :: CANMessage-  -> Builder-slCANDataBuilder CANMessage{..} =-     arbitrationId canMessageArbitrationField-  <> Data.ByteString.Builder.word8Hex-      (fromIntegral $ length canMessageData)-  <> mconcat-      (map-         Data.ByteString.Builder.word8HexFixed-         canMessageData-      )--arbitrationId-  :: CANArbitrationField-  -> Builder-arbitrationId CANArbitrationField{..} =-     Data.ByteString.Builder.char7-       (case ( canArbitrationFieldExtended-             , canArbitrationFieldRTR-             )-        of-         (False, False) -> 't'-         (False, True)  -> 'r'-         (True, False)  -> 'T'-         (True, True)   -> 'R'-       )-  <> (if canArbitrationFieldExtended-      then Data.ByteString.Builder.word32HexFixed-      else-        (\word11 ->-           Data.ByteString.Builder.word8Hex-            (fromIntegral (word11 `Data.Bits.shiftR` 8))-        <> Data.ByteString.Builder.word8HexFixed-            (fromIntegral word11)-        )-     )-     canArbitrationFieldID--slCANStateBuilder-  :: SLCANState-  -> SLCANCounters-  -> Builder-slCANStateBuilder state SLCANCounters{..} =-     Data.ByteString.Builder.char7 's'-  <> Data.ByteString.Builder.char7-      (case state of-         SLCANState_Active -> 'a'-         SLCANState_Warning -> 'w'-         SLCANState_Passive -> 'p'-         SLCANState_BusOff -> 'b'-      )-  <> word16Dec3 slCANCountersTxErrors-  <> word16Dec3 slCANCountersRxErrors-  where-    -- encode as 3 bytes (maximum of 999 and zero padded)-    word16Dec3 x =-           (case x of-              _ | x < 10 -> Data.ByteString.Builder.string7 "00"-              _ | x < 100 -> Data.ByteString.Builder.char7 '0'-              _ | otherwise -> mempty-           )-        <> Data.ByteString.Builder.word16Dec-            (min 999 x)--slCANErrorBuilder-  :: Set SLCANError-  -> Builder-slCANErrorBuilder errs =-     Data.ByteString.Builder.char7 'e'-  <> Data.ByteString.Builder.word8Hex-      (fromIntegral $ Data.Set.size errs)-  <> mconcat-      (map-         ( Data.ByteString.Builder.char7-         . \case-             SLCANError_Ack -> 'a'-             SLCANError_Bit0 -> 'b'-             SLCANError_Bit1 -> 'B'-             SLCANError_CRC -> 'c'-             SLCANError_Form -> 'f'-             SLCANError_RxOverrun -> 'o'-             SLCANError_TxOverrun -> 'O'-             SLCANError_Stuff -> 's'-         )-         $ Data.Set.toList-             errs-      )--buildSLCANMessage-  :: SLCANMessage-  -> ByteString-buildSLCANMessage =-   Data.ByteString.Lazy.toStrict- . Data.ByteString.Builder.toLazyByteString- . slCANBuilder
− src/Network/SLCAN/Parser.hs
@@ -1,179 +0,0 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE RecordWildCards #-}--module Network.SLCAN.Parser-  ( parseSLCANMessage-  ) where--import Data.Attoparsec.ByteString.Char8 (Parser)-import Data.Bits (Bits)-import Data.ByteString (ByteString)-import Data.Set (Set)-import Control.Applicative ((<|>))-import Network.CAN.Types (CANArbitrationField(..), CANMessage(..))-import Network.SLCAN.Types-  ( SLCANMessage(..)-  , SLCANControl(..)-  , SLCANBitrate-  , SLCANState(..)-  , SLCANCounters(..)-  , SLCANError(..)-  )--import qualified Data.Attoparsec.ByteString.Char8-import qualified Data.Set-import qualified Control.Monad--slCANParser :: Parser SLCANMessage-slCANParser = do-  Data.Attoparsec.ByteString.Char8.peekChar'-    >>= \case-      c | Data.Attoparsec.ByteString.Char8.inClass "OCSFL" c ->-        (SLCANMessage_Control <$> slCANControlParser)-      c | Data.Attoparsec.ByteString.Char8.inClass "tTrR" c ->-        (SLCANMessage_Data <$> slCANDataParser)-      's' ->-        Data.Attoparsec.ByteString.Char8.char 's'-        *> (SLCANMessage_State <$> slCANStateParser <*> slCANCountersParser)-      'e' ->-        Data.Attoparsec.ByteString.Char8.char 'e'-        *> (SLCANMessage_Error <$> slCANErrorParser)-      c | otherwise ->-        fail $ "Unknown SLCAN message type: " <> show c-  <* Data.Attoparsec.ByteString.Char8.char '\r'--slCANControlParser :: Parser SLCANControl-slCANControlParser = do-  Data.Attoparsec.ByteString.Char8.anyChar-  >>= \case-    'O' -> pure SLCANControl_Open-    'C' -> pure SLCANControl_Close-    'S' -> (SLCANControl_Bitrate <$> bitrate)-    'F' -> pure SLCANControl_ResetErrors-    'L' -> pure SLCANControl_ListenOnly-    c   -> fail $ "Unknown control message char: " <> show c-  where-    bitrate = do-      d <- Data.Attoparsec.ByteString.Char8.decimal-      if d > fromEnum (maxBound :: SLCANBitrate)-      then fail-             $ "Bitrate out of bounds, got "-             <> show d-             <> "but maximum is "-             <> show (fromEnum (maxBound :: SLCANBitrate))-             <> " ("-             <> show  (maxBound :: SLCANBitrate)-             <> ")"-      else pure $ toEnum d--slCANDataParser :: Parser CANMessage-slCANDataParser = do-  canMessageArbitrationField <- arbitrationId-  msgLen <- hexadecimalWithLength 1-  canMessageData <--    Control.Monad.replicateM msgLen (hexadecimalWithLength 2)-  pure CANMessage{..}---- | Parse arbitration ID--- * t => 11 bit data frame--- * r => 11 bit RTR frame--- * T => 29 bit data frame--- * R => 29 bit RTR frame-arbitrationId :: Parser CANArbitrationField-arbitrationId = do-      Data.Attoparsec.ByteString.Char8.char 't' *> stdID False-  <|> Data.Attoparsec.ByteString.Char8.char 'r' *> stdID True-  <|> Data.Attoparsec.ByteString.Char8.char 'T' *> extID False-  <|> Data.Attoparsec.ByteString.Char8.char 'R' *> extID True--stdID-  :: Bool-  -> Parser CANArbitrationField-stdID isRTR = do-  canArbitrationFieldID-    <- hexadecimalWithLength 3--  let-    canArbitrationFieldExtended = False-    canArbitrationFieldRTR = isRTR--  pure CANArbitrationField{..}--extID-  :: Bool-  -> Parser CANArbitrationField-extID isRTR = do-  canArbitrationFieldID-    <- hexadecimalWithLength 8--  let-    canArbitrationFieldExtended = True-    canArbitrationFieldRTR = isRTR--  pure CANArbitrationField{..}--hexadecimalWithLength-  :: ( Bits a-     , Integral a-     )-  => Int-  -> Parser a-hexadecimalWithLength len =-  Data.Attoparsec.ByteString.Char8.take len-  >>=-  either-    fail-    pure-    . Data.Attoparsec.ByteString.Char8.parseOnly-        Data.Attoparsec.ByteString.Char8.hexadecimal--slCANStateParser :: Parser SLCANState-slCANStateParser =-  Data.Attoparsec.ByteString.Char8.anyChar-  >>= \case-    'a' -> pure SLCANState_Active-    'w' -> pure SLCANState_Warning-    'p' -> pure SLCANState_Passive-    'b' -> pure SLCANState_BusOff-    c   -> fail $ "Unknown state char: " <> show c--slCANCountersParser :: Parser SLCANCounters-slCANCountersParser = do-    slCANCountersTxErrors <- decimal3-    slCANCountersRxErrors <- decimal3-    pure $ SLCANCounters{..}-  where-    decimal3 =-      Data.Attoparsec.ByteString.Char8.take 3-      >>=-      either-        fail-        pure-        . Data.Attoparsec.ByteString.Char8.parseOnly-            Data.Attoparsec.ByteString.Char8.decimal--slCANErrorParser :: Parser (Set SLCANError)-slCANErrorParser = do-  len <- hexadecimalWithLength 1-  Data.Set.fromList-    <$> Control.Monad.replicateM len errorChar-  where-    errorChar =-      Data.Attoparsec.ByteString.Char8.anyChar-      >>= \case-        'a' -> pure SLCANError_Ack-        'b' -> pure SLCANError_Bit0-        'B' -> pure SLCANError_Bit1-        'c' -> pure SLCANError_CRC-        'f' -> pure SLCANError_Form-        'o' -> pure SLCANError_RxOverrun-        'O' -> pure SLCANError_TxOverrun-        's' -> pure SLCANError_Stuff-        c   -> fail $ "Unknown error char: " <> show c--parseSLCANMessage-  :: ByteString-  -> Either String SLCANMessage-parseSLCANMessage =-  Data.Attoparsec.ByteString.Char8.parseOnly-    slCANParser
− src/Network/SLCAN/Types.hs
@@ -1,129 +0,0 @@-{-# LANGUAGE NumericUnderscores #-}-module Network.SLCAN.Types-  ( SLCANMessage(..)-  , SLCANControl(..)-  , SLCANBitrate(..)-  , numericBitrate-  , SLCANState(..)-  , SLCANCounters(..)-  , SLCANError(..)-  , SLCANConfig(..)-  ) where--import Data.Default.Class (Default(def))-import Data.Set (Set)-import Data.Word (Word16)-import Network.CAN.Types (CANMessage)-import Test.QuickCheck (Arbitrary(..))--import qualified Test.QuickCheck--data SLCANMessage-  = SLCANMessage_Control SLCANControl-  | SLCANMessage_Data CANMessage-  | SLCANMessage_State SLCANState SLCANCounters-  | SLCANMessage_Error (Set SLCANError)-  deriving (Eq, Ord, Show)--instance Arbitrary SLCANMessage where-  arbitrary = Test.QuickCheck.oneof-    [ SLCANMessage_Control <$> arbitrary-    , SLCANMessage_Data <$> arbitrary-    , SLCANMessage_State <$> arbitrary <*> arbitrary-    , SLCANMessage_Error <$> arbitrary-    ]--data SLCANControl-  = SLCANControl_Open-  | SLCANControl_Close-  | SLCANControl_Bitrate SLCANBitrate-  | SLCANControl_ResetErrors-  | SLCANControl_ListenOnly-  deriving (Eq, Ord, Show)--instance Arbitrary SLCANControl where-  arbitrary = Test.QuickCheck.oneof-    [ pure SLCANControl_Open-    , pure SLCANControl_Close-    , SLCANControl_Bitrate <$> arbitrary-    , pure SLCANControl_ResetErrors-    , pure SLCANControl_ListenOnly-    ]--data SLCANBitrate-  = SLCANBitrate_10K-  | SLCANBitrate_20K-  | SLCANBitrate_50K-  | SLCANBitrate_100K-  | SLCANBitrate_125K-  | SLCANBitrate_250K-  | SLCANBitrate_500K-  | SLCANBitrate_800K-  | SLCANBitrate_1M-  deriving (Bounded, Eq, Enum, Ord, Show)--instance Arbitrary SLCANBitrate where-  arbitrary = Test.QuickCheck.arbitraryBoundedEnum--instance Default SLCANBitrate where-  def = SLCANBitrate_1M--numericBitrate :: SLCANBitrate -> Int-numericBitrate SLCANBitrate_10K  =    10_000-numericBitrate SLCANBitrate_20K  =    20_000-numericBitrate SLCANBitrate_50K  =    50_000-numericBitrate SLCANBitrate_100K =   100_000-numericBitrate SLCANBitrate_125K =   125_000-numericBitrate SLCANBitrate_250K =   250_000-numericBitrate SLCANBitrate_500K =   500_000-numericBitrate SLCANBitrate_800K =   800_000-numericBitrate SLCANBitrate_1M   = 1_000_000--data SLCANState-  = SLCANState_Active-  | SLCANState_Warning-  | SLCANState_Passive-  | SLCANState_BusOff-  deriving (Bounded, Eq, Enum, Ord, Show)--instance Arbitrary SLCANState where-  arbitrary = Test.QuickCheck.arbitraryBoundedEnum--data SLCANCounters = SLCANCounters-  { slCANCountersRxErrors :: Word16-  , slCANCountersTxErrors :: Word16-  } deriving (Eq, Ord, Show)--instance Arbitrary SLCANCounters where-  arbitrary =-    SLCANCounters-      <$> Test.QuickCheck.choose (0, 999)-      <*> Test.QuickCheck.choose (0, 999)--data SLCANError-  = SLCANError_Ack-  | SLCANError_Bit0-  | SLCANError_Bit1-  | SLCANError_CRC-  | SLCANError_Form-  | SLCANError_RxOverrun-  | SLCANError_TxOverrun-  | SLCANError_Stuff-  deriving (Bounded, Eq, Enum, Ord, Show)--instance Arbitrary SLCANError where-  arbitrary = Test.QuickCheck.arbitraryBoundedEnum--data SLCANConfig = SLCANConfig-  { slCANConfigBitrate :: SLCANBitrate-  , slCANConfigResetErrors :: Bool-  , slCANConfigListenOnly :: Bool-  } deriving (Eq, Ord, Show)--instance Default SLCANConfig where-  def =-    SLCANConfig-    { slCANConfigBitrate = def-    , slCANConfigResetErrors = False-    , slCANConfigListenOnly = False-    }
− src/Network/SocketCAN.hs
@@ -1,122 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-module Network.SocketCAN-  ( withSocketCAN-  , sendCANMessage-  , recvCANMessage-  , Network.Socket.ifNameToIndex-  , SocketCANT-  , CANInterface-  , mkCANInterface-  , NoSuchInterface(..)-  , runSocketCAN-  ) where--import Network.CAN (CANMessage, MonadCAN(..))-import Network.Socket (Socket)-import Network.SocketCAN.Bindings (SockAddrCAN(..))--import Control.Monad.Reader (MonadReader, ask)-import Control.Monad.Trans (MonadTrans(..))-import Control.Monad.Trans.Reader (ReaderT(..))-import UnliftIO--import qualified Control.Exception-import qualified Network.Socket (ifNameToIndex)-import qualified Network.SocketCAN.LowLevel-import qualified Network.SocketCAN.Translate--withSocketCAN-  :: Int-  -> (Socket -> IO a)-  -> IO a-withSocketCAN ifaceIdx act = do-  Control.Exception.bracket-    Network.SocketCAN.LowLevel.socket-    Network.SocketCAN.LowLevel.close-    (\canSock -> do-      Network.SocketCAN.LowLevel.bind-        canSock-        $ Network.SocketCAN.Bindings.SockAddrCAN-          $ fromIntegral ifaceIdx-      act canSock-    )--sendCANMessage-  :: Socket-  -> CANMessage-  -> IO ()-sendCANMessage canSock cm =-  Network.SocketCAN.LowLevel.send-    canSock-    (Network.SocketCAN.Translate.toSocketCANFrame cm)--recvCANMessage-  :: Socket-  -> IO CANMessage-recvCANMessage canSock =-  Network.SocketCAN.LowLevel.recv canSock-  >>= pure . Network.SocketCAN.Translate.fromSocketCANFrame--newtype SocketCANT m a = SocketCANT-  { _unSocketCANT :: ReaderT Socket m a }-  deriving-    ( Functor-    , Applicative-    , Monad-    , MonadReader Socket-    , MonadIO-    , MonadUnliftIO-    )--instance MonadTrans SocketCANT where-  lift = SocketCANT . lift---- | Run SocketCANT transformer-runSocketCANT-  :: Monad m-  => Socket-  -> SocketCANT m a-  -> m a-runSocketCANT sock =-    (`runReaderT` sock)-  . _unSocketCANT--newtype CANInterface = CANInterface-  { unCANInterface :: String }-  deriving Eq--instance Show CANInterface where-  show = unCANInterface--mkCANInterface :: String -> CANInterface-mkCANInterface = CANInterface--data NoSuchInterface = NoSuchInterface-    deriving Show--instance Exception NoSuchInterface--runSocketCAN-  :: ( MonadIO m-     , MonadUnliftIO m-     )-  => CANInterface-  -> SocketCANT m a-  -> m a-runSocketCAN interface act = do-  mIdx <--    liftIO-    $ Network.Socket.ifNameToIndex (unCANInterface interface)--  case mIdx of-    Nothing -> throwIO NoSuchInterface-    Just idx -> withRunInIO $ \runInIO ->-      withSocketCAN idx (\s -> runInIO (runSocketCANT s act))--instance MonadIO m => MonadCAN (SocketCANT m) where-  send cm = do-    canSock <- ask-    liftIO $ sendCANMessage canSock cm-  recv = do-    canSock <- ask-    liftIO $ recvCANMessage canSock
− src/Network/SocketCAN/Bindings.hsc
@@ -1,79 +0,0 @@-{-# LANGUAGE PatternSynonyms #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-module Network.SocketCAN.Bindings-  (-  -- * Network package compatibility-    SockAddrCAN(..)-  , pattern CAN_RAW-  -- * SocketCAN bindings-  , SocketCANArbitrationField(..)-  , SocketCANFrame(..)-  ) where--import Data.Word (Word8, Word16, Word32)-import Foreign.Storable (Storable(..))-import Foreign.Marshal.Array (peekArray, pokeArray)-import Foreign.Ptr (plusPtr)-import Network.Socket.Address (SocketAddress(..))-import Network.Socket (ProtocolNumber)--#include <linux/can.h>-#include <sys/socket.h>---- | CAN Socket type-newtype SockAddrCAN = SockAddrCAN Word32-  deriving (Eq, Ord)---- Word16-type CSaFamily = (#type sa_family_t)--instance SocketAddress SockAddrCAN where-  sizeOfSocketAddress (SockAddrCAN _) =-    #const sizeof(struct sockaddr_can)-  peekSocketAddress sap = do-    ifidx <- (#peek struct sockaddr_can, can_ifindex) sap-    return (SockAddrCAN ifidx)-  pokeSocketAddress p (SockAddrCAN ifIndex) = do-    (#poke struct sockaddr_can, can_family) p ((#const AF_CAN) :: CSaFamily)-    (#poke struct sockaddr_can, can_ifindex) p ifIndex---- | CAN RAW protocol family of PF_CAN-pattern CAN_RAW :: ProtocolNumber-pattern CAN_RAW = #const CAN_RAW---- | SocketCAN Arbitration field (CAN ID including RTR, EFF, ERR bits)-newtype SocketCANArbitrationField =-  SocketCANArbitrationField { unSocketCANArbitrationField :: Word32 }-  deriving (Eq, Ord, Show, Storable)--data SocketCANFrame = SocketCANFrame-  { socketCANFrameArbitrationField :: SocketCANArbitrationField-  , socketCANFrameLength :: Word8-  , socketCANFrameData   :: [Word8]-  } deriving Show--instance Storable SocketCANFrame where-  sizeOf ~_ = #const sizeof(struct can_frame)-  alignment ~_ = #alignment struct can_frame-  peek ptr = do-    socketCANFrameArbitrationField-      <- #{peek struct can_frame, can_id} ptr-    socketCANFrameLength-      <- #{peek struct can_frame, len} ptr-    socketCANFrameData <--      peekArray-        (fromIntegral socketCANFrameLength)-        (#{ptr struct can_frame, data} ptr)-    pure-      $ SocketCANFrame{..}-  poke ptr SocketCANFrame{..} = do-    #{poke struct can_frame, can_id}-      ptr-      socketCANFrameArbitrationField-    #{poke struct can_frame, len}-      ptr-      socketCANFrameLength-    pokeArray-      (#{ptr struct can_frame, data} ptr)-      socketCANFrameData
− src/Network/SocketCAN/Example.hs
@@ -1,41 +0,0 @@-module Network.SocketCAN.Example where--import Control.Monad (forever)-import Network.CAN-import Network.Socket (Socket)-import Network.SocketCAN--import qualified Network.Socket--example :: IO ()-example = do-  let interface = "vcan0"-  mIdx <- Network.Socket.ifNameToIndex interface-  case mIdx of-    Nothing -> error $ "Interface " <> interface <> " not found"-    Just idx ->-      withSocketCAN idx act--act :: Socket -> IO ()-act sock = do-  sendCANMessage-    sock-    $ standardMessage-        0x123-        [0xDE, 0xAD]--  sendCANMessage-    sock-    $ CANMessage-        (extendedID 0x123456)-        [0xEE]--  sendCANMessage-    sock-    $ CANMessage-        (setRTR $ extendedID 0x123)-        [0xDE, 0xAD, 0x11]--  forever-    $ recvCANMessage sock-    >>= print
− src/Network/SocketCAN/LowLevel.hs
@@ -1,62 +0,0 @@-{-# LANGUAGE TypeApplications #-}-module Network.SocketCAN.LowLevel-  ( socket-  , bind-  , send-  , recv-  , module Network.Socket-  ) where--import Control.Monad (void)-import Foreign.Ptr (Ptr)-import Network.Socket (Family(AF_CAN), Socket, close)-import Network.SocketCAN.Bindings (SockAddrCAN(..), SocketCANFrame)--import qualified Foreign.Ptr-import qualified Foreign.Marshal.Alloc-import qualified Foreign.Storable-import qualified Network.Socket-import qualified Network.Socket.Address-import qualified Network.SocketCAN.Bindings---- | Create raw CAN socket-socket-  :: IO Socket-socket =-  Network.Socket.socket-    AF_CAN-    Network.Socket.Raw-    Network.SocketCAN.Bindings.CAN_RAW---- | Bind CAN socket-bind-  :: Socket-  -> SockAddrCAN-  -> IO ()-bind = Network.Socket.Address.bind--send-  :: Socket-  -> SocketCANFrame-  -> IO ()-send canSock cf =-  Foreign.Marshal.Alloc.alloca $ \ptr -> do-    Foreign.Storable.poke ptr cf-    void-      $ Network.Socket.sendBuf-          canSock-          (Foreign.Ptr.castPtr ptr)-          (Foreign.Storable.sizeOf cf)--recv-  :: Socket-  -> IO SocketCANFrame-recv canSock =-  Foreign.Marshal.Alloc.alloca $ \ptr -> do-    (_nBytes, _sockAddr) <--      Network.Socket.Address.recvBufFrom-        @SockAddrCAN-        canSock-        (ptr :: Ptr SocketCANFrame)-        (Foreign.Storable.sizeOf (undefined :: SocketCANFrame))-    Foreign.Storable.peek ptr >>= pure
− src/Network/SocketCAN/Translate.hs
@@ -1,77 +0,0 @@-{-# LANGUAGE RecordWildCards #-}---- | Translation between CANMessage and SocketCANFrame--module Network.SocketCAN.Translate-  ( toSocketCANFrame-  , fromSocketCANFrame-  ) where--import Data.Bits ((.&.), (.|.), shiftL)-import Data.Word (Word32)-import Network.CAN.Types (CANArbitrationField(..), CANMessage(..))-import Network.SocketCAN.Bindings (SocketCANArbitrationField(..), SocketCANFrame(..))-import qualified Data.Bool--toSocketCANFrame-  :: CANMessage-  -> SocketCANFrame-toSocketCANFrame CANMessage{..} =-  SocketCANFrame-  { socketCANFrameArbitrationField =-      toSocketCANArbitrationField-        canMessageArbitrationField-  , socketCANFrameLength =-      fromIntegral $ length canMessageData-  , socketCANFrameData = canMessageData-  }--fromSocketCANFrame-  :: SocketCANFrame-  -> CANMessage-fromSocketCANFrame SocketCANFrame{..} =-  CANMessage-  { canMessageArbitrationField =-      fromSocketCANArbitrationField-        socketCANFrameArbitrationField-  , canMessageData = socketCANFrameData-  }--toSocketCANArbitrationField-  :: CANArbitrationField-  -> SocketCANArbitrationField-toSocketCANArbitrationField CANArbitrationField{..} =-  SocketCANArbitrationField-  $ Data.Bool.bool-      id-      (.|. effBit)-      canArbitrationFieldExtended-  $ Data.Bool.bool-      id-      (.|. rtrBit)-      canArbitrationFieldRTR-  $ canArbitrationFieldID--fromSocketCANArbitrationField-  :: SocketCANArbitrationField-  -> CANArbitrationField-fromSocketCANArbitrationField (SocketCANArbitrationField scid) =-  let-    isEff = scid .&. effBit /= 0-  in-    CANArbitrationField-    { canArbitrationFieldID =-        Data.Bool.bool-          (.&. (1 `shiftL` 12 - 1))-          (.&. (1 `shiftL` 30 - 1))-          isEff-        $ scid-    , canArbitrationFieldExtended = isEff-    , canArbitrationFieldRTR = scid .&. rtrBit /= 0-    }--effBit :: Word32-effBit = 1 `shiftL` 31--rtrBit :: Word32-rtrBit = 1 `shiftL` 30
+ test/CANSpec.hs view
@@ -0,0 +1,21 @@+module CANSpec where++import Test.Hspec (Spec, describe, it, shouldBe)+import Samples++import qualified Network.CAN++spec :: Spec+spec = do+  describe "CAN" $ do+    it "pretty prints samples" $+      map Network.CAN.prettyCANMessage samples+      `shouldBe`+      [ "     000   [0] "+      , "     FFF   [0] "+      , "     123   [2]  DE AD"+      , "     123   [0]  remote request"+      , "00000000   [0] "+      , "00123456   [1]  EE"+      , "00123456   [0]  remote request"+      ]