modbus

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2020 License: MIT Imports: 11 Imported by: 0

README

Go modbus stack

Description

This package is a go implementation of the modbus protocol. It aims to provide a simple-to-use, high-level API to interact with modbus devices using native Go types.

Both client and server components are available, using either TCP or RTU (serial) as transports.

The client also supports RTU over TCP to allow the use of remote serial ports or cheap TCP to serial bridges.

A CLI client is available in cmd/modbus-cli.go and can be built with

$ go build -o modbus-cli cmd/modbus-cli.go
$ ./modbus-cli --help

Getting started

$ go get github.com/simonvetter/modbus

Using the client

import (
    "github.com/simonvetter/modbus"
)

func main() {
    var client  *modbus.ModbusClient
    var err      error

    // for a TCP endpoint
    client, err = modbus.NewClient(&modbus.ClientConfiguration{
        URL:      "tcp://hostname-or-ip-address:502",
        Timeout:  1 * time.Second,
    })

    // for an RTU (serial) device/bus
    client, err = modbus.NewClient(&modbus.ClientConfiguration{
        URL:      "rtu:///dev/ttyUSB0",
        Speed:    9600,                    // default
        DataBits: 8,                       // default, optional
        Parity:   modbus.PARITY_NONE,      // default, optional
        StopBits: 2,                       // default if no parity, optional
        Timeout:  300 * time.Millisecond,
    })

    // for an RTU over TCP device/bus (remote serial port or
    // simple TCP-to-serial bridge)
    client, err = modbus.NewClient(&modbus.ClientConfiguration{
        URL:      "rtuovertcp://hostname-or-ip-address:502",
        Timeout:  1 * time.Second,
    })

    // read a single 16-bit holding register at address 100
    var reg16   uint16
    reg16, err  = client.ReadRegister(100, modbus.HOLDING_REGISTER)
    if err != nil {
      // error out
    } else {
      // use value
      fmt.Printf("value: %v", reg16)        // as unsigned integer
      fmt.Printf("value: %v", int16(reg16)) // as signed integer
    }

    // read 4 consecutive 16-bit input registers starting at address 100
    var reg16s  []uint16
    reg16s, err = client.ReadRegisters(100, 4, modbus.INPUT_REGISTER)

    // read the same 4 consecutive 16-bit input registers as 2 32-bit registers
    var reg32s  []uint32
    reg32s, err = client.ReadUint32s(100, 2, modbus.INPUT_REGISTER)

    // read the same 4 consecutive 16-bit registers as a single 64-bit register
    var reg64	uint64
    reg64, err	= client.ReadUint64(100, modbus.INPUT_REGISTER)

    // by default, 16-bit registers are decoded as big-endian and 32-bit registers
    // as big-endian with the high word first.
    // change the byte/word ordering of subsequent requests to little endian, with
    // the low word first (note that the second argument only affects 32-bit registers)
    client.SetEncoding(modbus.LITTLE_ENDIAN, modbus.LOW_WORD_FIRST)

    // read the same 4 consecutive 16-bit input registers as 2 32-bit floats
    var fl32s   []float32
    fl32s, err  = client.ReadFloat32s(100, 2, modbus.INPUT_REGISTER)

    // write -200 to 16-bit (holding) register 100, as a signed integer
    var s int16 = -200
    err         = client.WriteRegister(100, uint16(s))

    // Switch to unit ID (a.k.a. slave ID) #4
    client.SetUnitId(4)

    // write 3 floats to registers 100 to 105
    err         = client.WriteFloat32s(100, []float32{
	3.14,
        1.1,
	-783.22,
    })

    // close the TCP connection/serial port
    client.Close()
}

Using the server component

See examples/tcp_server.go for an example.

Supported function codes, golang object types and endianness/word ordering

Function codes:

  • Read coils (0x01)
  • Read discrete inputs (0x02)
  • Read holding registers (0x03)
  • Read input registers (0x04)
  • Write single coil (0x05)
  • Write single register (0x06)
  • Write multiple coils (0x0f)
  • Write multiple registers (0x10)

Go object types:

  • Booleans (coils and discrete inputs)
  • Signed/Unisgned 16-bit integers (input and holding registers)
  • Signed/Unsigned 32-bit integers (input and holding registers)
  • 32-bit floating point numbers (input and holding registers)
  • Signed/Unsigned 64-bit integers (input and holding registers)
  • 64-bit floating point numbers (input and holding registers)

Byte encoding/endianness/word ordering:

  • Little and Big endian for 16-bit integers
  • Little and Big endian, with and without word swap for 32 and 64-bit integers and floating point numbers.

TODO (in no particular order)

  • Add more tests
  • Add diagnostics register support
  • Add fifo register support
  • Add file register support

Dependencies

License

MIT.

Documentation

Index

Constants

View Source
const (
	PARITY_NONE uint = 0
	PARITY_EVEN uint = 1
	PARITY_ODD  uint = 2

	HOLDING_REGISTER RegType = 0
	INPUT_REGISTER   RegType = 1

	// endianness of 16-bit registers
	BIG_ENDIAN    Endianness = 1
	LITTLE_ENDIAN Endianness = 2

	// word order of 32-bit registers
	HIGH_WORD_FIRST WordOrder = 1
	LOW_WORD_FIRST  WordOrder = 2
)
View Source
const (
	// coils
	FC_READ_COILS           uint8 = 0x01
	FC_WRITE_SINGLE_COIL    uint8 = 0x05
	FC_WRITE_MULTIPLE_COILS uint8 = 0x0f

	// discrete inputs
	FC_READ_DISCRETE_INPUTS uint8 = 0x02

	// 16-bit input/holding registers
	FC_READ_HOLDING_REGISTERS       uint8 = 0x03
	FC_READ_INPUT_REGISTERS         uint8 = 0x04
	FC_WRITE_SINGLE_REGISTER        uint8 = 0x06
	FC_WRITE_MULTIPLE_REGISTERS     uint8 = 0x10
	FC_MASK_WRITE_REGISTER          uint8 = 0x16
	FC_READ_WRITE_MULTILE_REGISTERS uint8 = 0x17
	FC_READ_FIFO_QUEUE              uint8 = 0x18

	// file access
	FC_READ_FILE_RECORD  uint8 = 0x14
	FC_WRITE_FILE_RECORD uint8 = 0x15

	// exception codes
	EX_ILLEGAL_FUNCTION            uint8 = 0x01
	EX_ILLEGAL_DATA_ADDRESS        uint8 = 0x02
	EX_ILLEGAL_DATA_VALUE          uint8 = 0x03
	EX_SERVER_DEVICE_FAILURE       uint8 = 0x04
	EX_ACKNOWLEDGE                 uint8 = 0x05
	EX_SERVER_DEVICE_BUSY          uint8 = 0x06
	EX_MEMORY_PARITY_ERROR         uint8 = 0x08
	EX_GW_PATH_UNAVAILABLE         uint8 = 0x0a
	EX_GW_TARGET_FAILED_TO_RESPOND uint8 = 0x0b
)
View Source
const (
	RTU_TRANSPORT          transportType = 1
	RTU_OVER_TCP_TRANSPORT transportType = 2
	TCP_TRANSPORT          transportType = 3
)

Variables

View Source
var (
	ErrConfigurationError      error = errors.New("configuration error")
	ErrRequestTimedOut         error = errors.New("request timed out")
	ErrIllegalFunction         error = errors.New("illegal function")
	ErrIllegalDataAddress      error = errors.New("illegal data address")
	ErrIllegalDataValue        error = errors.New("illegal data value")
	ErrServerDeviceFailure     error = errors.New("server device failure")
	ErrAcknowledge             error = errors.New("request acknowledged")
	ErrServerDeviceBusy        error = errors.New("server device busy")
	ErrMemoryParityError       error = errors.New("memory parity error")
	ErrGWPathUnavailable       error = errors.New("gateway path unavailable")
	ErrGWTargetFailedToRespond error = errors.New("gateway target device failed to respond")
	ErrBadCRC                  error = errors.New("bad crc")
	ErrShortFrame              error = errors.New("short frame")
	ErrProtocolError           error = errors.New("protocol error")
	ErrBadUnitId               error = errors.New("bad unit id")
	ErrBadTransactionId        error = errors.New("bad transaction id")
	ErrUnknownProtocolId       error = errors.New("unknown protocol identifier")
	ErrUnexpectedParameters    error = errors.New("unexpected parameters")
)

Functions

This section is empty.

Types

type ClientConfiguration

type ClientConfiguration struct {
	URL      string
	Speed    uint
	DataBits uint
	Parity   uint
	StopBits uint
	Timeout  time.Duration
}

type CoilsRequest

type CoilsRequest struct {
	ClientAddr string // the source (client) IP address
	UnitId     uint8  // the requested unit id (slave id)
	Addr       uint16 // the base coil address requested
	Quantity   uint16 // the number of consecutive coils covered by this request
	// (first address: Addr, last address: Addr + Quantity - 1)
	IsWrite bool   // true if the request is a write, false if a read
	Args    []bool // a slice of bool values of the coils to be set, ordered

}

Request object passed to the coil handler.

type DiscreteInputsRequest

type DiscreteInputsRequest struct {
	ClientAddr string // the source (client) IP address
	UnitId     uint8  // the requested unit id (slave id)
	Addr       uint16 // the base discrete input address requested
	Quantity   uint16 // the number of consecutive discrete inputs

}

Request object passed to the discrete input handler.

type Endianness

type Endianness uint

type HoldingRegistersRequest

type HoldingRegistersRequest struct {
	ClientAddr string   // the source (client) IP address
	UnitId     uint8    // the requested unit id (slave id)
	Addr       uint16   // the base register address requested
	Quantity   uint16   // the number of consecutive registers covered by this request
	IsWrite    bool     // true if the request is a write, false if a read
	Args       []uint16 // a slice of register values to be set, ordered from

}

Request object passed to the holding register handler.

type InputRegistersRequest

type InputRegistersRequest struct {
	ClientAddr string // the source (client) IP address
	UnitId     uint8  // the requested unit id (slave id)
	Addr       uint16 // the base register address requested
	Quantity   uint16 // the number of consecutive registers covered by this request
}

Request object passed to the input register handler.

type ModbusClient

type ModbusClient struct {
	// contains filtered or unexported fields
}

func NewClient

func NewClient(conf *ClientConfiguration) (mc *ModbusClient, err error)

func (*ModbusClient) Bind

func (mc *ModbusClient) Bind(sock net.Conn) error

Bind to socket connection instead of creating new connection

func (*ModbusClient) Close

func (mc *ModbusClient) Close() (err error)

Closes the underlying transport.

func (*ModbusClient) Open

func (mc *ModbusClient) Open() (err error)

Opens the underlying transport (tcp socket or serial line).

func (*ModbusClient) ReadCoil

func (mc *ModbusClient) ReadCoil(addr uint16) (value bool, err error)

Reads a single coil (function code 01).

func (*ModbusClient) ReadCoils

func (mc *ModbusClient) ReadCoils(addr uint16, quantity uint16) (values []bool, err error)

Reads multiple coils (function code 01).

func (*ModbusClient) ReadDiscreteInput

func (mc *ModbusClient) ReadDiscreteInput(addr uint16) (value bool, err error)

Reads a single discrete input (function code 02).

func (*ModbusClient) ReadDiscreteInputs

func (mc *ModbusClient) ReadDiscreteInputs(addr uint16, quantity uint16) (values []bool, err error)

Reads multiple discrete inputs (function code 02).

func (*ModbusClient) ReadFloat32

func (mc *ModbusClient) ReadFloat32(addr uint16, regType RegType) (value float32, err error)

Reads a single 32-bit float register.

func (*ModbusClient) ReadFloat32s

func (mc *ModbusClient) ReadFloat32s(addr uint16, quantity uint16, regType RegType) (values []float32, err error)

Reads multiple 32-bit float registers.

func (*ModbusClient) ReadFloat64

func (mc *ModbusClient) ReadFloat64(addr uint16, regType RegType) (value float64, err error)

Reads a single 64-bit float register.

func (*ModbusClient) ReadFloat64s

func (mc *ModbusClient) ReadFloat64s(addr uint16, quantity uint16, regType RegType) (values []float64, err error)

Reads multiple 64-bit float registers.

func (*ModbusClient) ReadRegister

func (mc *ModbusClient) ReadRegister(addr uint16, regType RegType) (value uint16, err error)

Reads a single 16-bit register (function code 03 or 04).

func (*ModbusClient) ReadRegisters

func (mc *ModbusClient) ReadRegisters(addr uint16, quantity uint16, regType RegType) (values []uint16, err error)

Reads multiple 16-bit registers (function code 03 or 04).

func (*ModbusClient) ReadUint32

func (mc *ModbusClient) ReadUint32(addr uint16, regType RegType) (value uint32, err error)

Reads a single 32-bit register.

func (*ModbusClient) ReadUint32s

func (mc *ModbusClient) ReadUint32s(addr uint16, quantity uint16, regType RegType) (values []uint32, err error)

Reads multiple 32-bit registers.

func (*ModbusClient) ReadUint64

func (mc *ModbusClient) ReadUint64(addr uint16, regType RegType) (value uint64, err error)

Reads a single 64-bit register.

func (*ModbusClient) ReadUint64s

func (mc *ModbusClient) ReadUint64s(addr uint16, quantity uint16, regType RegType) (values []uint64, err error)

Reads multiple 64-bit registers.

func (*ModbusClient) SetEncoding

func (mc *ModbusClient) SetEncoding(endianness Endianness, wordOrder WordOrder) (err error)

Sets the encoding (endianness and word ordering) of subsequent requests.

func (*ModbusClient) SetUnitId

func (mc *ModbusClient) SetUnitId(id uint8) (err error)

Sets the unit id of subsequent requests.

func (*ModbusClient) WriteCoil

func (mc *ModbusClient) WriteCoil(addr uint16, value bool) (err error)

Writes a single coil (function code 05)

func (*ModbusClient) WriteCoils

func (mc *ModbusClient) WriteCoils(addr uint16, values []bool) (err error)

Writes multiple coils (function code 15)

func (*ModbusClient) WriteFloat32

func (mc *ModbusClient) WriteFloat32(addr uint16, value float32) (err error)

Writes a single 32-bit float register.

func (*ModbusClient) WriteFloat32s

func (mc *ModbusClient) WriteFloat32s(addr uint16, values []float32) (err error)

Writes multiple 32-bit float registers.

func (*ModbusClient) WriteFloat64

func (mc *ModbusClient) WriteFloat64(addr uint16, value float64) (err error)

Writes a single 64-bit float register.

func (*ModbusClient) WriteFloat64s

func (mc *ModbusClient) WriteFloat64s(addr uint16, values []float64) (err error)

Writes multiple 64-bit float registers.

func (*ModbusClient) WriteRegister

func (mc *ModbusClient) WriteRegister(addr uint16, value uint16) (err error)

Writes a single 16-bit register (function code 06).

func (*ModbusClient) WriteRegisters

func (mc *ModbusClient) WriteRegisters(addr uint16, values []uint16) (err error)

Writes multiple 16-bit registers (function code 16).

func (*ModbusClient) WriteUint32

func (mc *ModbusClient) WriteUint32(addr uint16, value uint32) (err error)

Writes a single 32-bit register.

func (*ModbusClient) WriteUint32s

func (mc *ModbusClient) WriteUint32s(addr uint16, values []uint32) (err error)

Writes multiple 32-bit registers.

func (*ModbusClient) WriteUint64

func (mc *ModbusClient) WriteUint64(addr uint16, value uint64) (err error)

Writes a single 64-bit register.

func (*ModbusClient) WriteUint64s

func (mc *ModbusClient) WriteUint64s(addr uint16, values []uint64) (err error)

Writes multiple 64-bit registers.

type ModbusServer

type ModbusServer struct {
	// contains filtered or unexported fields
}

Modbus server object.

func NewServer

func NewServer(conf *ServerConfiguration, reqHandler RequestHandler) (
	ms *ModbusServer, err error)

Returns a new modbus server. reqHandler should be a user-provided handler object satisfying the RequestHandler interface.

func (*ModbusServer) Start

func (ms *ModbusServer) Start() (err error)

Starts accepting client connections.

func (*ModbusServer) Stop

func (ms *ModbusServer) Stop() (err error)

Stops accepting new client connections and closes any active session.

type RegType

type RegType uint

type RequestHandler

type RequestHandler interface {
	// HandleCoils handles the read coils (0x01), write single coil (0x05)
	// and write multiple coils (0x0f) function codes.
	// A CoilsRequest object is passed to the handler (see above).
	//
	// Expected return values:
	// - res:	a slice of bools containing the coil values to be sent to back
	//		to the client (only sent for reads),
	// - err:	either nil if no error occurred, a modbus error (see
	//		mapErrorToExceptionCode() in modbus.go for a complete list),
	//		or any other error.
	//		If nil, a positive modbus response is sent back to the client
	//		along with the returned data.
	//		If non-nil, a negative modbus response is sent back, with the
	//		exception code set depending on the error
	//		(again, see mapErrorToExceptionCode()).
	HandleCoils(req *CoilsRequest) (res []bool, err error)

	// HandleDiscreteInputs handles the read discrete inputs (0x02) function code.
	// A DiscreteInputsRequest oibject is passed to the handler (see above).
	//
	// Expected return values:
	// - res:	a slice of bools containing the discrete input values to be
	//		sent back to the client,
	// - err:	either nil if no error occurred, a modbus error (see
	//		mapErrorToExceptionCode() in modbus.go for a complete list),
	//		or any other error.
	HandleDiscreteInputs(req *DiscreteInputsRequest) (res []bool, err error)

	// HandleHoldingRegisters handles the read holding registers (0x03),
	// write single register (0x06) and write multiple registers (0x10).
	// A HoldingRegistersRequest object is passed to the handler (see above).
	//
	// Expected return values:
	// - res:	a slice of uint16 containing the register values to be sent
	//		to back to the client (only sent for reads),
	// - err:	either nil if no error occurred, a modbus error (see
	//		mapErrorToExceptionCode() in modbus.go for a complete list),
	//		or any other error.
	HandleHoldingRegisters(req *HoldingRegistersRequest) (res []uint16, err error)

	// HandleInputRegisters handles the read input registers (0x04) function code.
	// An InputRegistersRequest object is passed to the handler (see above).
	//
	// Expected return values:
	// - res:	a slice of uint16 containing the register values to be sent
	//		back to the client,
	// - err:	either nil if no error occurred, a modbus error (see
	//		mapErrorToExceptionCode() in modbus.go for a complete list),
	//		or any other error.
	HandleInputRegisters(req *InputRegistersRequest) (res []uint16, err error)
}

The RequestHandler interface should be implemented by the handler object passed to NewServer (see reqHandler in NewServer()). After decoding and validating an incoming request, the server will invoke the appropriate handler function, depending on the function code of the request.

type ServerConfiguration

type ServerConfiguration struct {
	URL     string        // where to listen at e.g. tcp://[::]:502
	Timeout time.Duration // idle session timeout (client connection will be
	// closed if idle for this long)
	MaxClients uint // maximum number of concurrent client connections

	Speed           uint
	DataBits        uint
	Parity          uint
	StopBits        uint
	AcceptedUnitIds []uint8
}

Server configuration object.

type WordOrder

type WordOrder uint

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL