packages feed

libmodbus 1.0.0 → 1.1.0

raw patch · 3 files changed

+178/−72 lines, 3 filesdep +bytestring

Dependencies added: bytestring

Files

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