From f063c3ddff41cc2e75fe1dbc854dbb20df129c46 Mon Sep 17 00:00:00 2001 From: sorki Date: Tue, 7 Apr 2026 17:37:22 +0200 Subject: [PATCH 01/10] split into sub-libraries --- network-can.cabal | 63 ++++++++++++++----- {src => src-slcan}/Network/SLCAN.hs | 0 {src => src-slcan}/Network/SLCAN/Builder.hs | 0 {src => src-slcan}/Network/SLCAN/Parser.hs | 0 {src => src-slcan}/Network/SLCAN/Types.hs | 0 {src => src-socketcan}/Network/SocketCAN.hs | 0 .../Network/SocketCAN/Bindings.hsc | 0 .../Network/SocketCAN/Example.hs | 0 .../Network/SocketCAN/LowLevel.hs | 0 .../Network/SocketCAN/Translate.hs | 0 10 files changed, 47 insertions(+), 16 deletions(-) rename {src => src-slcan}/Network/SLCAN.hs (100%) rename {src => src-slcan}/Network/SLCAN/Builder.hs (100%) rename {src => src-slcan}/Network/SLCAN/Parser.hs (100%) rename {src => src-slcan}/Network/SLCAN/Types.hs (100%) rename {src => src-socketcan}/Network/SocketCAN.hs (100%) rename {src => src-socketcan}/Network/SocketCAN/Bindings.hsc (100%) rename {src => src-socketcan}/Network/SocketCAN/Example.hs (100%) rename {src => src-socketcan}/Network/SocketCAN/LowLevel.hs (100%) rename {src => src-socketcan}/Network/SocketCAN/Translate.hs (100%) diff --git a/network-can.cabal b/network-can.cabal index c2958a1..9864ee6 100644 --- a/network-can.cabal +++ b/network-can.cabal @@ -1,4 +1,4 @@ -cabal-version: 2.2 +cabal-version: 3.0 name: network-can version: 0.1.0.0 synopsis: CAN bus networking @@ -25,22 +25,34 @@ flag build-apps description: Build example applications +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 + , mtl + , transformers + +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 @@ -48,12 +60,27 @@ library , data-default-class , mtl , 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 + , mtl + , network >= 3.1 + , network-can + , transformers + , unliftio build-tool-depends: hsc2hs:hsc2hs - default-language: Haskell2010 test-suite pure type: exitcode-stdio-1.0 @@ -67,43 +94,46 @@ test-suite pure build-depends: base >= 4.7 && < 5 , hspec , network-can + , network-can:slcan + , network-can:socketcan default-language: Haskell2010 executable hcandump if !flag(build-apps) buildable: False + import: commons, execs 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 if !flag(build-apps) buildable: False + import: commons, execs build-depends: base >=4.7 && <5 , network-can + , network-can:slcan + , network-can:socketcan , data-default-class , mtl , 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 if !flag(build-apps) buildable: False + import: commons, execs build-depends: base >=4.7 && <5 , network-can + , network-can:slcan , data-default-class , 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 if !flag(build-apps) @@ -111,6 +141,7 @@ executable hslcanudp build-depends: base >=4.7 && <5 , network , network-can + , network-can:slcan , data-default-class default-language: Haskell2010 main-is: SLCANUDP.hs diff --git a/src/Network/SLCAN.hs b/src-slcan/Network/SLCAN.hs similarity index 100% rename from src/Network/SLCAN.hs rename to src-slcan/Network/SLCAN.hs diff --git a/src/Network/SLCAN/Builder.hs b/src-slcan/Network/SLCAN/Builder.hs similarity index 100% rename from src/Network/SLCAN/Builder.hs rename to src-slcan/Network/SLCAN/Builder.hs diff --git a/src/Network/SLCAN/Parser.hs b/src-slcan/Network/SLCAN/Parser.hs similarity index 100% rename from src/Network/SLCAN/Parser.hs rename to src-slcan/Network/SLCAN/Parser.hs diff --git a/src/Network/SLCAN/Types.hs b/src-slcan/Network/SLCAN/Types.hs similarity index 100% rename from src/Network/SLCAN/Types.hs rename to src-slcan/Network/SLCAN/Types.hs diff --git a/src/Network/SocketCAN.hs b/src-socketcan/Network/SocketCAN.hs similarity index 100% rename from src/Network/SocketCAN.hs rename to src-socketcan/Network/SocketCAN.hs diff --git a/src/Network/SocketCAN/Bindings.hsc b/src-socketcan/Network/SocketCAN/Bindings.hsc similarity index 100% rename from src/Network/SocketCAN/Bindings.hsc rename to src-socketcan/Network/SocketCAN/Bindings.hsc diff --git a/src/Network/SocketCAN/Example.hs b/src-socketcan/Network/SocketCAN/Example.hs similarity index 100% rename from src/Network/SocketCAN/Example.hs rename to src-socketcan/Network/SocketCAN/Example.hs diff --git a/src/Network/SocketCAN/LowLevel.hs b/src-socketcan/Network/SocketCAN/LowLevel.hs similarity index 100% rename from src/Network/SocketCAN/LowLevel.hs rename to src-socketcan/Network/SocketCAN/LowLevel.hs diff --git a/src/Network/SocketCAN/Translate.hs b/src-socketcan/Network/SocketCAN/Translate.hs similarity index 100% rename from src/Network/SocketCAN/Translate.hs rename to src-socketcan/Network/SocketCAN/Translate.hs From 3fa186b4551dfa8b8799bd74448ba2bfdd5bec35 Mon Sep 17 00:00:00 2001 From: sorki Date: Wed, 15 Apr 2026 17:55:54 +0200 Subject: [PATCH 02/10] Migrate to io-classes and record of functions instead of type class / mtl / readers --- CHANGELOG.md | 14 ++++ app/CANBridge.hs | 13 ++- app/CANDump.hs | 9 +- app/SLCANSerial.hs | 13 +-- app/SLCANUDP.hs | 13 +-- network-can.cabal | 20 ++--- src-slcan/Network/SLCAN.hs | 127 ++++++++++++----------------- src-socketcan/Network/SocketCAN.hs | 82 +++++++------------ src/Network/CAN.hs | 21 ++++- src/Network/CAN/Class.hs | 39 --------- 10 files changed, 143 insertions(+), 208 deletions(-) delete mode 100644 src/Network/CAN/Class.hs diff --git a/CHANGELOG.md b/CHANGELOG.md index c17169d..c77bb76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# Version [next](https://github.com/DistRap/network-can/compare/0.1.0.0...master) (2026-MM-DD) + +* Split `slcan` and `socketcan` into public sublibraries +* Migrate to `io-classes` and switch from `MonadCAN` typeclass + to `CANEndpoint` handle (record of functions style): + + ``` + data CANEndpoint m = CANEndpoint + { canEndpointSend :: CANMessage -> m () + , canEndpointRecv :: m CANMessage + } + ``` + + # Version [0.1.0.0](https://github.com/DistRap/network-can/compare/d50564...0.1.0.0) (2025-05-19) * Initial release diff --git a/app/CANBridge.hs b/app/CANBridge.hs index cec255c..8af165a 100644 --- a/app/CANBridge.hs +++ b/app/CANBridge.hs @@ -1,7 +1,7 @@ {-# LANGUAGE LambdaCase #-} module Main where -import Control.Monad.Trans (MonadTrans(lift)) +import Control.Monad.Class.MonadAsync (race_) import Data.Default.Class (Default(def)) import Network.SLCAN (Transport(..)) import System.Hardware.Serialport (CommSpeed(..), SerialPortSettings(..)) @@ -11,7 +11,6 @@ import qualified Network.CAN 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 @@ main = do (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.runSLCAN (Transport_Handle h) def $ \slcan -> do + Network.SocketCAN.runSocketCAN (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 ) diff --git a/app/CANDump.hs b/app/CANDump.hs index f96068e..821ec20 100644 --- a/app/CANDump.hs +++ b/app/CANDump.hs @@ -9,10 +9,11 @@ main :: IO () main = do Network.SocketCAN.runSocketCAN (Network.SocketCAN.mkCANInterface "vcan0") - (Control.Monad.forever - $ Network.CAN.recv - >>= Control.Monad.IO.Class.liftIO . print - ) + $ \can -> + (Control.Monad.forever + $ Network.CAN.recv can + >>= Control.Monad.IO.Class.liftIO . print + ) -- needs Network.CAN.Pretty or Builder or smthing -- that does the same ID formatting as SLCAN.Builder:78 diff --git a/app/SLCANSerial.hs b/app/SLCANSerial.hs index dc5a4c4..0358308 100644 --- a/app/SLCANSerial.hs +++ b/app/SLCANSerial.hs @@ -3,7 +3,7 @@ module Main where import Control.Monad.IO.Class import Data.Default.Class (Default(def)) import System.Hardware.Serialport (CommSpeed(..), SerialPortSettings(..)) -import Network.CAN (MonadCAN) +import Network.CAN (CANEndpoint) import Network.SLCAN (Transport(..)) import qualified Control.Monad @@ -29,12 +29,12 @@ main = do act act - :: ( MonadCAN m - , MonadIO m - ) - => m () -act = do + :: MonadIO m + => CANEndpoint m + -> m () +act can = do Network.CAN.send + can $ Network.CAN.standardMessage -- vendorID SDO 0x601 @@ -42,4 +42,5 @@ act = do Control.Monad.forever $ Network.CAN.recv + can >>= Control.Monad.IO.Class.liftIO . print diff --git a/app/SLCANUDP.hs b/app/SLCANUDP.hs index 11fbde0..793c649 100644 --- a/app/SLCANUDP.hs +++ b/app/SLCANUDP.hs @@ -2,7 +2,7 @@ module Main where import Control.Monad.IO.Class import Data.Default.Class (Default(def)) -import Network.CAN (MonadCAN) +import Network.CAN (CANEndpoint) import Network.SLCAN (Transport(..)) import Network.Socket (AddrInfo(..), SocketType(Datagram)) @@ -44,16 +44,17 @@ main = do (_, _) -> error "getAddrInfo fail" act - :: ( MonadCAN m - , MonadIO m - ) - => m () -act = do + :: MonadIO m + => CANEndpoint m + -> m () +act can = do Network.CAN.send + can $ Network.CAN.standardMessage 0x7E5 [0x4C] Control.Monad.forever $ Network.CAN.recv + can >>= Control.Monad.IO.Class.liftIO . print diff --git a/network-can.cabal b/network-can.cabal index 9864ee6..944fbb9 100644 --- a/network-can.cabal +++ b/network-can.cabal @@ -38,12 +38,9 @@ library import: commons hs-source-dirs: src exposed-modules: Network.CAN - Network.CAN.Class Network.CAN.Types build-depends: base >= 4.7 && < 5 , QuickCheck - , mtl - , transformers library slcan import: commons @@ -58,12 +55,10 @@ library slcan , bytestring , containers , data-default-class - , mtl + , io-classes , network >= 3.1 , network-can , QuickCheck - , transformers - , unliftio library socketcan import: commons @@ -75,11 +70,9 @@ library socketcan Network.SocketCAN.LowLevel Network.SocketCAN.Translate build-depends: base >= 4.7 && < 5 - , mtl + , io-classes , network >= 3.1 , network-can - , transformers - , unliftio build-tool-depends: hsc2hs:hsc2hs test-suite pure @@ -99,9 +92,9 @@ test-suite pure default-language: Haskell2010 executable hcandump + import: commons, execs if !flag(build-apps) buildable: False - import: commons, execs build-depends: base >=4.7 && <5 , network-can , network-can:socketcan @@ -109,24 +102,23 @@ executable hcandump hs-source-dirs: app executable hcanbridge + import: commons, execs if !flag(build-apps) buildable: False - import: commons, execs build-depends: base >=4.7 && <5 , network-can , network-can:slcan , network-can:socketcan , data-default-class - , mtl + , io-classes , serialport >= 0.5.5 - , unliftio main-is: CANBridge.hs hs-source-dirs: app executable hslcanserial + import: commons, execs if !flag(build-apps) buildable: False - import: commons, execs build-depends: base >=4.7 && <5 , network-can , network-can:slcan diff --git a/src-slcan/Network/SLCAN.hs b/src-slcan/Network/SLCAN.hs index 50694ad..8cd882e 100644 --- a/src-slcan/Network/SLCAN.hs +++ b/src-slcan/Network/SLCAN.hs @@ -1,7 +1,6 @@ -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-} + module Network.SLCAN ( Transport(..) , withSLCANTransport @@ -10,45 +9,41 @@ module Network.SLCAN , recvSLCANMessage , sendCANMessage , module Network.SLCAN.Types - , SLCANT(..) , SLCANException(..) , runSLCAN ) where -import Control.Exception (Exception) +import Control.Monad.Class.MonadThrow (Exception(..), MonadThrow(throwIO), finally) 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.CAN (CANMessage, CANEndpoint(..)) 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 + :: ( MonadIO m + , MonadThrow m + ) + => Transport -> SLCANConfig - -> (Transport -> IO a) - -> IO a + -> (Transport -> m a) + -> m a withSLCANTransport transport SLCANConfig{..} act = do let sendC = sendSLCANControl transport - Control.Exception.finally + finally (do sendC SLCANControl_Close sendC (SLCANControl_Bitrate slCANConfigBitrate) @@ -66,26 +61,29 @@ withSLCANTransport transport SLCANConfig{..} act = do (sendC SLCANControl_Close) sendSLCANMessage - :: Transport + :: MonadIO m + => Transport -> SLCANMessage - -> IO () -sendSLCANMessage (Transport_Handle handle) msg = do + -> 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 = do - Network.Socket.ByteString.sendAllTo - socket - (Network.SLCAN.Builder.buildSLCANMessage msg) - target +sendSLCANMessage (Transport_UDP socket target) msg = + liftIO + $ Network.Socket.ByteString.sendAllTo + socket + (Network.SLCAN.Builder.buildSLCANMessage msg) + target sendSLCANControl - :: Transport + :: MonadIO m + => Transport -> SLCANControl - -> IO () + -> m () sendSLCANControl t = sendSLCANMessage t . SLCANMessage_Control @@ -128,30 +126,6 @@ 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 @@ -159,35 +133,36 @@ instance Exception SLCANException runSLCAN :: ( MonadIO m - , MonadUnliftIO m + , MonadThrow m ) => Transport -> SLCANConfig - -> SLCANT m a + -> (CANEndpoint m -> 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 + withSLCANTransport + transport + config + $ \t -> + act + CANEndpoint + { canEndpointSend = liftIO . sendCANMessage t + , canEndpointRecv = + 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 + } diff --git a/src-socketcan/Network/SocketCAN.hs b/src-socketcan/Network/SocketCAN.hs index d1ce974..d4add2b 100644 --- a/src-socketcan/Network/SocketCAN.hs +++ b/src-socketcan/Network/SocketCAN.hs @@ -1,43 +1,42 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Network.SocketCAN ( withSocketCAN , sendCANMessage , recvCANMessage , Network.Socket.ifNameToIndex - , SocketCANT , CANInterface , mkCANInterface , NoSuchInterface(..) , runSocketCAN ) where -import Network.CAN (CANMessage, MonadCAN(..)) +import Network.CAN (CANMessage, CANEndpoint(..)) 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 Control.Monad.Class.MonadThrow (Exception(..), MonadThrow(bracket, throwIO)) +import Control.Monad.IO.Class (MonadIO(..)) -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 + :: ( MonadIO m + , MonadThrow m + ) + => Int + -> (Socket -> m a) + -> m a withSocketCAN ifaceIdx act = do - Control.Exception.bracket - Network.SocketCAN.LowLevel.socket - Network.SocketCAN.LowLevel.close + bracket + (liftIO Network.SocketCAN.LowLevel.socket) + (liftIO . Network.SocketCAN.LowLevel.close) (\canSock -> do - Network.SocketCAN.LowLevel.bind - canSock - $ Network.SocketCAN.Bindings.SockAddrCAN - $ fromIntegral ifaceIdx + liftIO + $ Network.SocketCAN.LowLevel.bind + canSock + $ Network.SocketCAN.Bindings.SockAddrCAN + $ fromIntegral ifaceIdx act canSock ) @@ -57,30 +56,6 @@ 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 @@ -98,10 +73,10 @@ instance Exception NoSuchInterface runSocketCAN :: ( MonadIO m - , MonadUnliftIO m + , MonadThrow m ) => CANInterface - -> SocketCANT m a + -> (CANEndpoint m -> m a) -> m a runSocketCAN interface act = do mIdx <- @@ -110,13 +85,12 @@ runSocketCAN interface act = do 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 + Just idx -> + withSocketCAN + idx + $ \sock -> + act + CANEndpoint + { canEndpointSend = liftIO . sendCANMessage sock + , canEndpointRecv = liftIO $ recvCANMessage sock + } diff --git a/src/Network/CAN.hs b/src/Network/CAN.hs index 8364b86..eacfa62 100644 --- a/src/Network/CAN.hs +++ b/src/Network/CAN.hs @@ -1,7 +1,24 @@ module Network.CAN - ( module Network.CAN.Class + ( CANEndpoint(..) + , send + , recv , module Network.CAN.Types ) where -import Network.CAN.Class import Network.CAN.Types + +data CANEndpoint m = CANEndpoint + { canEndpointSend :: CANMessage -> m () + , canEndpointRecv :: m CANMessage + } + +send + :: CANEndpoint m + -> CANMessage + -> m () +send = canEndpointSend + +recv + :: CANEndpoint m + -> m CANMessage +recv = canEndpointRecv diff --git a/src/Network/CAN/Class.hs b/src/Network/CAN/Class.hs deleted file mode 100644 index 2c713a6..0000000 --- a/src/Network/CAN/Class.hs +++ /dev/null @@ -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) From e3362d0e85459bd66fd806de55b5892aea41a135 Mon Sep 17 00:00:00 2001 From: sorki Date: Sun, 19 Apr 2026 14:36:19 +0200 Subject: [PATCH 03/10] run -> with --- CHANGELOG.md | 5 ++++- app/CANBridge.hs | 4 ++-- app/CANDump.hs | 2 +- app/SLCANSerial.hs | 2 +- app/SLCANUDP.hs | 2 +- src-slcan/Network/SLCAN.hs | 6 +++--- src-socketcan/Network/SocketCAN.hs | 14 +++++++------- src-socketcan/Network/SocketCAN/Example.hs | 2 +- 8 files changed, 20 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c77bb76..30fbaa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,10 @@ , canEndpointRecv :: 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) diff --git a/app/CANBridge.hs b/app/CANBridge.hs index 8af165a..8fd52ee 100644 --- a/app/CANBridge.hs +++ b/app/CANBridge.hs @@ -20,8 +20,8 @@ main = do (System.Hardware.Serialport.defaultSerialSettings { commSpeed = CS115200 } ) - Network.SLCAN.runSLCAN (Transport_Handle h) def $ \slcan -> do - Network.SocketCAN.runSocketCAN (Network.SocketCAN.mkCANInterface "vcan0") $ \socketcan -> do + 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 slcan >>= Network.CAN.send socketcan diff --git a/app/CANDump.hs b/app/CANDump.hs index 821ec20..fbdb560 100644 --- a/app/CANDump.hs +++ b/app/CANDump.hs @@ -7,7 +7,7 @@ import qualified Network.SocketCAN main :: IO () main = do - Network.SocketCAN.runSocketCAN + Network.SocketCAN.withSocketCAN (Network.SocketCAN.mkCANInterface "vcan0") $ \can -> (Control.Monad.forever diff --git a/app/SLCANSerial.hs b/app/SLCANSerial.hs index 0358308..d138e20 100644 --- a/app/SLCANSerial.hs +++ b/app/SLCANSerial.hs @@ -23,7 +23,7 @@ main = do { commSpeed = CS115200 } ) - Network.SLCAN.runSLCAN + Network.SLCAN.withSLCAN (Transport_Handle h) def act diff --git a/app/SLCANUDP.hs b/app/SLCANUDP.hs index 793c649..d75043a 100644 --- a/app/SLCANUDP.hs +++ b/app/SLCANUDP.hs @@ -36,7 +36,7 @@ main = do sock (addrAddress ourAddrinfo) - Network.SLCAN.runSLCAN + Network.SLCAN.withSLCAN (Transport_UDP sock (addrAddress targetAddrinfo)) def act diff --git a/src-slcan/Network/SLCAN.hs b/src-slcan/Network/SLCAN.hs index 8cd882e..976fcc2 100644 --- a/src-slcan/Network/SLCAN.hs +++ b/src-slcan/Network/SLCAN.hs @@ -10,7 +10,7 @@ module Network.SLCAN , sendCANMessage , module Network.SLCAN.Types , SLCANException(..) - , runSLCAN + , withSLCAN ) where import Control.Monad.Class.MonadThrow (Exception(..), MonadThrow(throwIO), finally) @@ -131,7 +131,7 @@ data SLCANException = SLCANException_ParseError String instance Exception SLCANException -runSLCAN +withSLCAN :: ( MonadIO m , MonadThrow m ) @@ -139,7 +139,7 @@ runSLCAN -> SLCANConfig -> (CANEndpoint m -> m a) -> m a -runSLCAN transport config act = do +withSLCAN transport config act = do withSLCANTransport transport config diff --git a/src-socketcan/Network/SocketCAN.hs b/src-socketcan/Network/SocketCAN.hs index d4add2b..96d4ab2 100644 --- a/src-socketcan/Network/SocketCAN.hs +++ b/src-socketcan/Network/SocketCAN.hs @@ -1,12 +1,12 @@ module Network.SocketCAN - ( withSocketCAN + ( withSocket , sendCANMessage , recvCANMessage , Network.Socket.ifNameToIndex , CANInterface , mkCANInterface , NoSuchInterface(..) - , runSocketCAN + , withSocketCAN ) where import Network.CAN (CANMessage, CANEndpoint(..)) @@ -20,14 +20,14 @@ import qualified Network.Socket (ifNameToIndex) import qualified Network.SocketCAN.LowLevel import qualified Network.SocketCAN.Translate -withSocketCAN +withSocket :: ( MonadIO m , MonadThrow m ) => Int -> (Socket -> m a) -> m a -withSocketCAN ifaceIdx act = do +withSocket ifaceIdx act = do bracket (liftIO Network.SocketCAN.LowLevel.socket) (liftIO . Network.SocketCAN.LowLevel.close) @@ -71,14 +71,14 @@ data NoSuchInterface = NoSuchInterface instance Exception NoSuchInterface -runSocketCAN +withSocketCAN :: ( MonadIO m , MonadThrow m ) => CANInterface -> (CANEndpoint m -> m a) -> m a -runSocketCAN interface act = do +withSocketCAN interface act = do mIdx <- liftIO $ Network.Socket.ifNameToIndex (unCANInterface interface) @@ -86,7 +86,7 @@ runSocketCAN interface act = do case mIdx of Nothing -> throwIO NoSuchInterface Just idx -> - withSocketCAN + withSocket idx $ \sock -> act diff --git a/src-socketcan/Network/SocketCAN/Example.hs b/src-socketcan/Network/SocketCAN/Example.hs index 8a1e818..a9dfc95 100644 --- a/src-socketcan/Network/SocketCAN/Example.hs +++ b/src-socketcan/Network/SocketCAN/Example.hs @@ -14,7 +14,7 @@ example = do case mIdx of Nothing -> error $ "Interface " <> interface <> " not found" Just idx -> - withSocketCAN idx act + withSocket idx act act :: Socket -> IO () act sock = do From d4edb260ada569cbd9509e5f78ca2a6a1c30ef8e Mon Sep 17 00:00:00 2001 From: sorki Date: Sun, 19 Apr 2026 19:12:26 +0200 Subject: [PATCH 04/10] align CANArbitrationField --- src/Network/CAN/Types.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Network/CAN/Types.hs b/src/Network/CAN/Types.hs index cd17a20..71fdf25 100644 --- a/src/Network/CAN/Types.hs +++ b/src/Network/CAN/Types.hs @@ -18,9 +18,9 @@ import qualified Test.QuickCheck -- * 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 From 479bdaa98ad42cc05ab663eb065baef49ca30fa9 Mon Sep 17 00:00:00 2001 From: sorki Date: Sun, 19 Apr 2026 19:15:31 +0200 Subject: [PATCH 05/10] add prettyCANMessage --- app/CANDump.hs | 13 +++---------- app/SLCANSerial.hs | 6 +++--- app/SLCANUDP.hs | 6 +++--- network-can.cabal | 8 +++++--- src/Network/CAN/Types.hs | 39 +++++++++++++++++++++++++++++++++++++++ test/CANSpec.hs | 21 +++++++++++++++++++++ 6 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 test/CANSpec.hs diff --git a/app/CANDump.hs b/app/CANDump.hs index fbdb560..fdfecfc 100644 --- a/app/CANDump.hs +++ b/app/CANDump.hs @@ -1,7 +1,6 @@ module Main where import qualified Control.Monad -import qualified Control.Monad.IO.Class import qualified Network.CAN import qualified Network.SocketCAN @@ -11,13 +10,7 @@ main = do (Network.SocketCAN.mkCANInterface "vcan0") $ \can -> (Control.Monad.forever - $ Network.CAN.recv can - >>= Control.Monad.IO.Class.liftIO . print + $ Network.CAN.recv + can + >>= putStrLn . Network.CAN.prettyCANMessage ) - --- 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 diff --git a/app/SLCANSerial.hs b/app/SLCANSerial.hs index d138e20..4cb33b5 100644 --- a/app/SLCANSerial.hs +++ b/app/SLCANSerial.hs @@ -1,6 +1,6 @@ module Main where -import Control.Monad.IO.Class +import Control.Monad.Class.MonadSay (MonadSay(say)) import Data.Default.Class (Default(def)) import System.Hardware.Serialport (CommSpeed(..), SerialPortSettings(..)) import Network.CAN (CANEndpoint) @@ -29,7 +29,7 @@ main = do act act - :: MonadIO m + :: MonadSay m => CANEndpoint m -> m () act can = do @@ -43,4 +43,4 @@ act can = do Control.Monad.forever $ Network.CAN.recv can - >>= Control.Monad.IO.Class.liftIO . print + >>= say . Network.CAN.prettyCANMessage diff --git a/app/SLCANUDP.hs b/app/SLCANUDP.hs index d75043a..47267e2 100644 --- a/app/SLCANUDP.hs +++ b/app/SLCANUDP.hs @@ -1,6 +1,6 @@ module Main where -import Control.Monad.IO.Class +import Control.Monad.Class.MonadSay (MonadSay(say)) import Data.Default.Class (Default(def)) import Network.CAN (CANEndpoint) import Network.SLCAN (Transport(..)) @@ -44,7 +44,7 @@ main = do (_, _) -> error "getAddrInfo fail" act - :: MonadIO m + :: MonadSay m => CANEndpoint m -> m () act can = do @@ -57,4 +57,4 @@ act can = do Control.Monad.forever $ Network.CAN.recv can - >>= Control.Monad.IO.Class.liftIO . print + >>= say . Network.CAN.prettyCANMessage diff --git a/network-can.cabal b/network-can.cabal index 944fbb9..91c03d4 100644 --- a/network-can.cabal +++ b/network-can.cabal @@ -80,7 +80,8 @@ test-suite pure 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 @@ -123,11 +124,13 @@ executable hslcanserial , network-can , network-can:slcan , data-default-class + , io-classes , serialport >= 0.5.5 main-is: SLCANSerial.hs hs-source-dirs: app executable hslcanudp + import: commons, execs if !flag(build-apps) buildable: False build-depends: base >=4.7 && <5 @@ -135,10 +138,9 @@ executable hslcanudp , network-can , network-can:slcan , data-default-class - default-language: Haskell2010 + , io-classes main-is: SLCANUDP.hs hs-source-dirs: app - ghc-options: -Wall -threaded -rtsopts "-with-rtsopts -N" source-repository head type: git diff --git a/src/Network/CAN/Types.hs b/src/Network/CAN/Types.hs index 71fdf25..0166530 100644 --- a/src/Network/CAN/Types.hs +++ b/src/Network/CAN/Types.hs @@ -8,12 +8,14 @@ module Network.CAN.Types -- * Message , CANMessage(..) , standardMessage + , prettyCANMessage ) where import Data.Word (Word8, Word16, Word32) import Test.QuickCheck (Arbitrary(..)) import qualified Test.QuickCheck +import qualified Text.Printf -- * Arbitration @@ -89,3 +91,40 @@ standardMessage cid cdata = CANMessage { 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" diff --git a/test/CANSpec.hs b/test/CANSpec.hs new file mode 100644 index 0000000..a975759 --- /dev/null +++ b/test/CANSpec.hs @@ -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" + ] From 537cff30380b5e49c732e8985888f5fe9e06386b Mon Sep 17 00:00:00 2001 From: sorki Date: Sun, 19 Apr 2026 19:24:12 +0200 Subject: [PATCH 06/10] CANEndpoint -> CAN, buildable readme --- CHANGELOG.md | 8 ++++---- README.lhs | 1 + README.md | 9 +++++---- app/SLCANSerial.hs | 4 ++-- app/SLCANUDP.hs | 4 ++-- cabal.project.local.ci | 2 +- network-can.cabal | 19 +++++++++++++++++++ src-slcan/Network/SLCAN.hs | 10 +++++----- src-socketcan/Network/SocketCAN.hs | 10 +++++----- src/Network/CAN.hs | 16 ++++++++-------- 10 files changed, 52 insertions(+), 31 deletions(-) create mode 120000 README.lhs diff --git a/CHANGELOG.md b/CHANGELOG.md index 30fbaa6..20d252b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,12 @@ * Split `slcan` and `socketcan` into public sublibraries * Migrate to `io-classes` and switch from `MonadCAN` typeclass - to `CANEndpoint` handle (record of functions style): + to `CAN` handle (record of functions style): ``` - data CANEndpoint m = CANEndpoint - { canEndpointSend :: CANMessage -> m () - , canEndpointRecv :: m CANMessage + data CAN m = CAN + { canSend :: CANMessage -> m () + , canRecv :: m CANMessage } ``` * Runners renamed diff --git a/README.lhs b/README.lhs new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/README.lhs @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/README.md b/README.md index d6661a2..dfc4cdb 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,22 @@ CAN bus networking using Linux SocketCAN or SLCAN backends. ```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 ``` diff --git a/app/SLCANSerial.hs b/app/SLCANSerial.hs index 4cb33b5..f56d017 100644 --- a/app/SLCANSerial.hs +++ b/app/SLCANSerial.hs @@ -3,7 +3,7 @@ module Main where import Control.Monad.Class.MonadSay (MonadSay(say)) import Data.Default.Class (Default(def)) import System.Hardware.Serialport (CommSpeed(..), SerialPortSettings(..)) -import Network.CAN (CANEndpoint) +import Network.CAN (CAN) import Network.SLCAN (Transport(..)) import qualified Control.Monad @@ -30,7 +30,7 @@ main = do act :: MonadSay m - => CANEndpoint m + => CAN m -> m () act can = do Network.CAN.send diff --git a/app/SLCANUDP.hs b/app/SLCANUDP.hs index 47267e2..fb92911 100644 --- a/app/SLCANUDP.hs +++ b/app/SLCANUDP.hs @@ -2,7 +2,7 @@ module Main where import Control.Monad.Class.MonadSay (MonadSay(say)) import Data.Default.Class (Default(def)) -import Network.CAN (CANEndpoint) +import Network.CAN (CAN) import Network.SLCAN (Transport(..)) import Network.Socket (AddrInfo(..), SocketType(Datagram)) @@ -45,7 +45,7 @@ main = do act :: MonadSay m - => CANEndpoint m + => CAN m -> m () act can = do Network.CAN.send diff --git a/cabal.project.local.ci b/cabal.project.local.ci index 16c31aa..e71265a 100644 --- a/cabal.project.local.ci +++ b/cabal.project.local.ci @@ -1 +1 @@ -flags: +build-apps +flags: +build-apps +build-readme diff --git a/network-can.cabal b/network-can.cabal index 91c03d4..6a8f0c3 100644 --- a/network-can.cabal +++ b/network-can.cabal @@ -25,6 +25,12 @@ flag build-apps description: Build example applications +flag build-readme + default: + False + description: + Build readme example + common commons ghc-options: -Wall -Wunused-packages default-language: Haskell2010 @@ -142,6 +148,19 @@ executable hslcanudp main-is: SLCANUDP.hs hs-source-dirs: app +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 location: https://github.com/DistRap/network-can diff --git a/src-slcan/Network/SLCAN.hs b/src-slcan/Network/SLCAN.hs index 976fcc2..7af0d27 100644 --- a/src-slcan/Network/SLCAN.hs +++ b/src-slcan/Network/SLCAN.hs @@ -17,7 +17,7 @@ import Control.Monad.Class.MonadThrow (Exception(..), MonadThrow(throwIO), final import Control.Monad.IO.Class (MonadIO(..)) import Network.Socket (Socket, SockAddr) -import Network.CAN (CANMessage, CANEndpoint(..)) +import Network.CAN (CANMessage, CAN(..)) import Network.SLCAN.Types import System.IO (Handle) @@ -137,7 +137,7 @@ withSLCAN ) => Transport -> SLCANConfig - -> (CANEndpoint m -> m a) + -> (CAN m -> m a) -> m a withSLCAN transport config act = do withSLCANTransport @@ -145,9 +145,9 @@ withSLCAN transport config act = do config $ \t -> act - CANEndpoint - { canEndpointSend = liftIO . sendCANMessage t - , canEndpointRecv = + CAN + { canSend = liftIO . sendCANMessage t + , canRecv = let recv = liftIO diff --git a/src-socketcan/Network/SocketCAN.hs b/src-socketcan/Network/SocketCAN.hs index 96d4ab2..e2a379b 100644 --- a/src-socketcan/Network/SocketCAN.hs +++ b/src-socketcan/Network/SocketCAN.hs @@ -9,7 +9,7 @@ module Network.SocketCAN , withSocketCAN ) where -import Network.CAN (CANMessage, CANEndpoint(..)) +import Network.CAN (CANMessage, CAN(..)) import Network.Socket (Socket) import Network.SocketCAN.Bindings (SockAddrCAN(..)) @@ -76,7 +76,7 @@ withSocketCAN , MonadThrow m ) => CANInterface - -> (CANEndpoint m -> m a) + -> (CAN m -> m a) -> m a withSocketCAN interface act = do mIdx <- @@ -90,7 +90,7 @@ withSocketCAN interface act = do idx $ \sock -> act - CANEndpoint - { canEndpointSend = liftIO . sendCANMessage sock - , canEndpointRecv = liftIO $ recvCANMessage sock + CAN + { canSend = liftIO . sendCANMessage sock + , canRecv = liftIO $ recvCANMessage sock } diff --git a/src/Network/CAN.hs b/src/Network/CAN.hs index eacfa62..b9138ee 100644 --- a/src/Network/CAN.hs +++ b/src/Network/CAN.hs @@ -1,5 +1,5 @@ module Network.CAN - ( CANEndpoint(..) + ( CAN(..) , send , recv , module Network.CAN.Types @@ -7,18 +7,18 @@ module Network.CAN import Network.CAN.Types -data CANEndpoint m = CANEndpoint - { canEndpointSend :: CANMessage -> m () - , canEndpointRecv :: m CANMessage +data CAN m = CAN + { canSend :: CANMessage -> m () + , canRecv :: m CANMessage } send - :: CANEndpoint m + :: CAN m -> CANMessage -> m () -send = canEndpointSend +send = canSend recv - :: CANEndpoint m + :: CAN m -> m CANMessage -recv = canEndpointRecv +recv = canRecv From 07a110d9d3caa83c26cb41f0428100dac35a3773 Mon Sep 17 00:00:00 2001 From: sorki Date: Wed, 29 Apr 2026 13:42:01 +0200 Subject: [PATCH 07/10] Add cabal.project --- cabal.project | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 cabal.project diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..78b421a --- /dev/null +++ b/cabal.project @@ -0,0 +1,3 @@ +multi-repl: true + +packages: . From d9da135036b3f8ad976a206c7fc5f4587e5c7234 Mon Sep 17 00:00:00 2001 From: sorki Date: Wed, 29 Apr 2026 13:45:45 +0200 Subject: [PATCH 08/10] data-default-class -> data-default --- app/CANBridge.hs | 2 +- app/SLCANSerial.hs | 2 +- app/SLCANUDP.hs | 2 +- network-can.cabal | 8 ++++---- src-slcan/Network/SLCAN/Types.hs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/CANBridge.hs b/app/CANBridge.hs index 8fd52ee..8ba8099 100644 --- a/app/CANBridge.hs +++ b/app/CANBridge.hs @@ -2,7 +2,7 @@ module Main where import Control.Monad.Class.MonadAsync (race_) -import Data.Default.Class (Default(def)) +import Data.Default (Default(def)) import Network.SLCAN (Transport(..)) import System.Hardware.Serialport (CommSpeed(..), SerialPortSettings(..)) diff --git a/app/SLCANSerial.hs b/app/SLCANSerial.hs index f56d017..f66c376 100644 --- a/app/SLCANSerial.hs +++ b/app/SLCANSerial.hs @@ -1,7 +1,7 @@ module Main where import Control.Monad.Class.MonadSay (MonadSay(say)) -import Data.Default.Class (Default(def)) +import Data.Default (Default(def)) import System.Hardware.Serialport (CommSpeed(..), SerialPortSettings(..)) import Network.CAN (CAN) import Network.SLCAN (Transport(..)) diff --git a/app/SLCANUDP.hs b/app/SLCANUDP.hs index fb92911..e01a3db 100644 --- a/app/SLCANUDP.hs +++ b/app/SLCANUDP.hs @@ -1,7 +1,7 @@ module Main where import Control.Monad.Class.MonadSay (MonadSay(say)) -import Data.Default.Class (Default(def)) +import Data.Default (Default(def)) import Network.CAN (CAN) import Network.SLCAN (Transport(..)) import Network.Socket (AddrInfo(..), SocketType(Datagram)) diff --git a/network-can.cabal b/network-can.cabal index 6a8f0c3..bf20cfa 100644 --- a/network-can.cabal +++ b/network-can.cabal @@ -60,7 +60,7 @@ library slcan , attoparsec >= 0.14 , bytestring , containers - , data-default-class + , data-default , io-classes , network >= 3.1 , network-can @@ -116,7 +116,7 @@ executable hcanbridge , network-can , network-can:slcan , network-can:socketcan - , data-default-class + , data-default , io-classes , serialport >= 0.5.5 main-is: CANBridge.hs @@ -129,7 +129,7 @@ executable hslcanserial build-depends: base >=4.7 && <5 , network-can , network-can:slcan - , data-default-class + , data-default , io-classes , serialport >= 0.5.5 main-is: SLCANSerial.hs @@ -143,7 +143,7 @@ executable hslcanudp , network , network-can , network-can:slcan - , data-default-class + , data-default , io-classes main-is: SLCANUDP.hs hs-source-dirs: app diff --git a/src-slcan/Network/SLCAN/Types.hs b/src-slcan/Network/SLCAN/Types.hs index 88b4877..01617ac 100644 --- a/src-slcan/Network/SLCAN/Types.hs +++ b/src-slcan/Network/SLCAN/Types.hs @@ -10,7 +10,7 @@ module Network.SLCAN.Types , SLCANConfig(..) ) where -import Data.Default.Class (Default(def)) +import Data.Default (Default(def)) import Data.Set (Set) import Data.Word (Word16) import Network.CAN.Types (CANMessage) From e4ccae4d6ac40143d35ce49050a212dbad8f4b23 Mon Sep 17 00:00:00 2001 From: sorki Date: Wed, 29 Apr 2026 13:46:41 +0200 Subject: [PATCH 09/10] Update ci --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e2c12a4..77863ea 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,9 +38,9 @@ jobs: strategy: matrix: cabal: - - '3.12' + - '3.16' ghc: - - '9.8.2' + - '9.8.4' os: - "ubuntu-latest" name: Haskell CI From c8b606aec8d7d21fe4e147b151af6e47fabf428d Mon Sep 17 00:00:00 2001 From: sorki Date: Wed, 29 Apr 2026 13:48:23 +0200 Subject: [PATCH 10/10] Release 0.2.0.0 --- CHANGELOG.md | 2 +- network-can.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20d252b..97f2541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Version [next](https://github.com/DistRap/network-can/compare/0.1.0.0...master) (2026-MM-DD) +# 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 diff --git a/network-can.cabal b/network-can.cabal index bf20cfa..6de0b11 100644 --- a/network-can.cabal +++ b/network-can.cabal @@ -1,6 +1,6 @@ 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