diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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.
diff --git a/System/Modbus.hsc b/System/Modbus.hsc
--- a/System/Modbus.hsc
+++ b/System/Modbus.hsc
@@ -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"
diff --git a/libmodbus.cabal b/libmodbus.cabal
--- a/libmodbus.cabal
+++ b/libmodbus.cabal
@@ -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
