libmodbus 1.0.0 → 1.1.0
raw patch · 3 files changed
+178/−72 lines, 3 filesdep +bytestring
Dependencies added: bytestring
Files
- CHANGELOG +16/−0
- System/Modbus.hsc +159/−70
- libmodbus.cabal +3/−2
CHANGELOG view
@@ -1,3 +1,19 @@+haskell-libmodbus (1.1.0) unstable; urgency=medium++ * Added a full working example of reading data from an epsolar charge+ controller and using the binary library to parse the data.+ * Added a RegisterData type class, which allows register accessors+ to use more useful data types than a mutable vector, eg a ByteString.+ (API change)+ * Added a BitData type class, which allows bit readers to return data+ in a more useful form than a mutable vector, eg a list of Bool.+ (API change)+ * Register and bit accessors no longer return the number of values+ read/written, but instead throw an IO error if the wrong amount is+ somehow read/written. (API change)++ -- Joey Hess <id@joeyh.name> Wed, 21 Aug 2019 11:52:43 -0400+ haskell-libmodbus (1.0.0) unstable; urgency=medium * Initial release.
System/Modbus.hsc view
@@ -1,6 +1,9 @@ {- | Haskell bindings to the C modbus library https://libmodbus.org/ -} -{-# LANGUAGE ForeignFunctionInterface, GeneralizedNewtypeDeriving, CPP #-}+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleInstances #-} module System.Modbus ( -- * Equivilance to the C library@@ -17,23 +20,47 @@ -- This module has been tested with version 3.1.4 of the C library. -- It may also work with other versions. - -- * Quick example+ -- * Example -- - -- | This example dumps some of the registers of an Epever solar - -- charge controller.+ -- | This example reads some of the registers of an Epever solar+ -- charge controller. It shows how `Data.Binary` can be used+ -- to decode the modbus registers into a haskell data structure. -- -- > import System.Modbus- -- > import qualified Data.Vector.Storable as V+ -- > import Data.Binary.Get+ -- > -- > main = do -- > mb <- new_rtu "/dev/ttyS1" (Baud 115200) ParityNone (DataBits 8) (StopBits 1)- -- > set_slave mb (DeviceAddress 1)- -- > connect mb- -- > regs <- mkRegisterVector 10- -- > read_input_registers mb (Addr 0x3100) regs- -- > print =<< V.freeze regs+ -- > set_slave mb (DeviceAddress 1)+ -- > connect mb+ -- > regs <- mkRegisterVector 5+ -- > b <- read_input_registers mb (Addr 0x3100) regs+ -- > print $ runGet getEpever b+ -- > + -- > data Epever = Epever+ -- > { pv_array_voltage :: Float+ -- > , pv_array_current :: Float+ -- > , pv_array_power :: Float+ -- > , battery_voltage :: Float+ -- > } deriving (Show)+ -- > + -- > getEpever :: Get Epever+ -- > getEpever = Epever+ -- > <$> epeverfloat -- register 0x3100+ -- > <*> epeverfloat -- register 0x3101+ -- > <*> epeverfloat2 -- register 0x3102 (low) and 0x3103 (high)+ -- > <*> epeverfloat -- register 0x3104+ -- > where+ -- > epeverfloat = decimals 2 <$> getWord16host+ -- > epeverfloat2 = do+ -- > l <- getWord16host+ -- > h <- getWord16host+ -- > return (decimals 2 (l + h*2^16))+ -- > decimals n v = fromIntegral v / (10^n) - -- * Contexts+ -- * Core data types Context,+ Addr(..), -- * RTU Context Baud(..),@@ -73,9 +100,9 @@ set_response_timeout, -- * Accessing registers- Addr(..), RegisterVector, mkRegisterVector,+ RegisterData(..), read_registers, read_input_registers, write_registers,@@ -85,6 +112,7 @@ -- * Accessing bits/coils BitVector, mkBitVector,+ BitData(..), Bit, boolBit, bitBool,@@ -100,7 +128,11 @@ import Foreign.C import Data.Char import Data.Default-import qualified Data.Vector.Storable.Mutable as V+import qualified Data.Vector.Storable.Mutable as VM+import qualified Data.Vector.Storable as V+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import GHC.IO.Exception foreign import ccall unsafe "modbus.h &modbus_close" modbus_close :: FunPtr (Ptr () -> IO ())@@ -187,16 +219,19 @@ :: Storable t => Context -> Addr- -> V.IOVector t+ -> VM.IOVector t -> (Ptr () -> Int -> Int -> Ptr t -> IO Int) -> String- -> IO Int+ -> IO () accessVector h (Addr addr) v action actionname = withContext h $ \ctx -> do- let (fptr, nb) = V.unsafeToForeignPtr0 v+ let (fptr, nb) = VM.unsafeToForeignPtr0 v r <- withForeignPtr fptr $ action ctx addr nb if r == -1 then throwErrno actionname- else return r+ else if r /= nb+ then ioError $ IOError Nothing OtherError+ actionname "short read/write" Nothing Nothing+ else return () -- | A modbus device context. --@@ -441,43 +476,75 @@ newtype Addr = Addr Int deriving (Show, Eq) --- | A vector that is used to read or write registers of a modbus device.------ Use functions from `Data.Vector.Storable.Mutable` to access and modify--- the values stored in the vector.-type RegisterVector = V.IOVector Word16+-- | A mutable vector that is used as a buffer when reading or writing +-- registers of a modbus device.+type RegisterVector = VM.IOVector Word16 --- | Allocates a vector holding the specified number of registers.+-- | Allocates a vector holding the contents of a specified number+-- of registers. ----- The registers are initialized to 0 to start.+-- The values are initialized to 0 to start. mkRegisterVector :: Int -> IO RegisterVector-mkRegisterVector sz = V.replicate sz 0- +mkRegisterVector sz = VM.replicate sz 0++-- | Types that can hold modbus register data.+-- +-- Of these, `RegisterVector` is the most efficient as it avoids+-- allocating new memory on each read or write. But it can be more useful+-- to get a ByteString and use a library such as cereal or binary to+-- parse the contents of the modbus registers.+class RegisterData t where+ fromRegisterVector :: RegisterVector -> IO t+ toRegisterVector :: t -> IO RegisterVector++instance RegisterData RegisterVector where+ fromRegisterVector = pure+ toRegisterVector = pure++instance RegisterData (V.Vector Word16) where+ fromRegisterVector = V.freeze+ toRegisterVector = V.thaw++instance RegisterData B.ByteString where+ fromRegisterVector v =+ B.pack . V.toList . castbytes <$> fromRegisterVector v+ where+ -- Simply interpret the vector as bytes.+ castbytes :: V.Vector Word16 -> V.Vector Word8+ castbytes = V.unsafeCast+ toRegisterVector =+ toRegisterVector . castbytes . V.fromList . B.unpack+ where+ -- If there are an odd number of bytes, the last+ -- byte will be omitted.+ castbytes :: V.Vector Word8 -> V.Vector Word16+ castbytes = V.unsafeCast++instance RegisterData L.ByteString where+ fromRegisterVector v = L.fromStrict <$> fromRegisterVector v+ toRegisterVector = toRegisterVector . L.toStrict+ -- | Reads the holding registers from the modbus device, starting at--- the Addr. The RegisterVector is modified to contain the values read.------ Returns the number of registers that were read.-read_registers :: Context -> Addr -> RegisterVector -> IO Int-read_registers h addr v = +-- the Addr, into the RegisterVector buffer.+read_registers :: RegisterData t => Context -> Addr -> RegisterVector -> IO t+read_registers h addr v = do accessVector h addr v modbus_read_registers "modbus_read_registers"+ fromRegisterVector v -- | Reads the input registers from the modbus device, starting at--- the Addr. The RegisterVector is modified to contain the values read.------ Returns the number of registers that were read.-read_input_registers :: Context -> Addr -> RegisterVector -> IO Int-read_input_registers h addr v = +-- the Addr, into the RegisterVector buffer.+read_input_registers :: RegisterData t => Context -> Addr -> RegisterVector -> IO t+read_input_registers h addr v = do accessVector h addr v modbus_read_input_registers "modbus_read_input_registers"+ fromRegisterVector v -- | Writes the registers to the modbus device, starting at -- the Addr.------ Returns the number of written registers.-write_registers :: Context -> Addr -> RegisterVector -> IO Int+write_registers :: Context -> Addr -> RegisterVector -> IO () write_registers h addr v = accessVector h addr v modbus_write_registers@@ -500,25 +567,34 @@ -- ^ address to read from -> RegisterVector -- ^ data to read- -> IO Int+ -> IO () write_and_read_registers h (Addr write_addr) write_v (Addr read_addr) read_v = withContext h $ \ctx -> do- let (write_fptr, write_nb) = V.unsafeToForeignPtr0 write_v- let (read_fptr, read_nb) = V.unsafeToForeignPtr0 read_v+ let (write_fptr, write_nb) = VM.unsafeToForeignPtr0 write_v+ let (read_fptr, read_nb) = VM.unsafeToForeignPtr0 read_v r <- withForeignPtr write_fptr $ \write_ptr -> withForeignPtr read_fptr $ \read_ptr -> modbus_write_and_read_registers ctx write_addr write_nb write_ptr read_addr read_nb read_ptr if r == -1- then throwErrno "modbus_write_and_read_registers"- else return r+ then throwErrno actionname+ else if r /= read_nb+ then ioError $ IOError Nothing OtherError+ actionname "short read" Nothing Nothing+ else return ()+ where+ actionname = "modbus_write_and_read_registers" --- | A vector that is used to read or write bits of a modbus device.+-- | A mutable vector that is used as a buffer when reading or writing +-- bits (coils) of a modbus device.+type BitVector = VM.IOVector Bit++-- | Allocates a vector holding the specified number of bits. ----- Use functions from `Data.Vector.Storable.Mutable` to access and modify--- the values stored in the vector.-type BitVector = V.IOVector Bit+-- The bits are set to start.+mkBitVector :: Int -> IO BitVector+mkBitVector sz = VM.replicate sz #const (TRUE) type Bit = Word8 @@ -529,35 +605,48 @@ bitBool True = #const (TRUE) bitBool False = #const (FALSE) --- | Allocates a vector holding the specified number of bits.------ The bits are initialized to true to start.-mkBitVector :: Int -> IO BitVector-mkBitVector sz = V.replicate sz #const (TRUE)+-- | Types that can hold modbus bit data.+-- +-- Of these, `BitVector` is the most efficient as it avoids+-- allocating new memory on each read or write. But it can be easier+-- to use a Vector of Bool.+class BitData t where+ fromBitVector :: BitVector -> IO t+ toBitVector :: t -> IO BitVector +instance BitData BitVector where+ fromBitVector = pure+ toBitVector = pure++instance BitData (V.Vector Word8) where+ fromBitVector = V.freeze+ toBitVector = V.thaw++instance BitData (V.Vector Bool) where+ fromBitVector v = V.map boolBit <$> fromBitVector v+ toBitVector = toBitVector . V.map bitBool+ -- | Reads the bits (coils) from the modbus device, starting at--- the Addr. The BitVector is modified to contain the values read.------ Returns the number of bits that were read.-read_bits :: Context -> Addr -> BitVector -> IO Int-read_bits h addr v = accessVector h addr v- modbus_read_bits- "modbus_read_bits"+-- the Addr, into the BitVector.+read_bits :: BitData t => Context -> Addr -> BitVector -> IO t+read_bits h addr v = do+ accessVector h addr v+ modbus_read_bits+ "modbus_read_bits"+ fromBitVector v -- | Reads the input bits from the modbus device, starting at--- the Addr. The BitVector is modified to contain the values read.------ Returns the number of bits that were read.-read_input_bits :: Context -> Addr -> BitVector -> IO Int-read_input_bits h addr v = accessVector h addr v- modbus_read_input_bits- "modbus_read_input_bits"+-- the Addr, into the BitVector.+read_input_bits :: BitData t => Context -> Addr -> BitVector -> IO t+read_input_bits h addr v = do+ accessVector h addr v+ modbus_read_input_bits+ "modbus_read_input_bits"+ fromBitVector v -- | Writes the bits (coils) of the modbus device, starting at -- the Addr.------ Returns the number of written bits.-write_bits :: Context -> Addr -> BitVector -> IO Int+write_bits :: Context -> Addr -> BitVector -> IO () write_bits h addr v = accessVector h addr v modbus_write_bits "modbus_write_bits"
libmodbus.cabal view
@@ -1,5 +1,5 @@ Name: libmodbus-Version: 1.0.0+Version: 1.1.0 Cabal-Version: >= 1.8 License: BSD2 Maintainer: Joey Hess <id@joeyh.name>@@ -25,7 +25,8 @@ Build-Depends: base >= 4.5 && < 5, vector >= 0.12 && <= 0.13, - data-default >= 0.7 && <= 0.8+ data-default >= 0.7 && <= 0.8,+ bytestring >= 0.10 && <= 0.11 source-repository head type: git