modbus

package module
v1.6.6 Latest Latest
Warning

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

Go to latest
Published: Dec 8, 2024 License: MIT Imports: 16 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.

The client supports the following modes:

  • modbus RTU (serial, over both RS-232 and RS-485),
  • modbus TCP (a.k.a. MBAP),
  • modbus TCP over TLS (a.k.a. MBAPS or Modbus Security),
  • modbus TCP over UDP (a.k.a. MBAP over UDP),
  • modbus RTU over TCP (RTU tunneled in TCP for use with e.g. remote serial ports or cheap TCP to serial bridges),
  • modbus RTU over UDP (RTU tunneled in UDP).

Please note that UDP transports are not part of the Modbus specification. Some devices expect MBAP (modbus TCP) framing in UDP packets while others use RTU frames instead. The client support both so if unsure, try with both udp:// and rtuoverudp:// schemes.

The server supports:

  • modbus TCP (a.k.a. MBAP),
  • modbus TCP over TLS (a.k.a. MBAPS or Modbus Security).

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
    // (see examples/tls_client.go for TLS usage and options)
    client, err = modbus.NewClient(&modbus.ClientConfiguration{
        URL:      "tcp://hostname-or-ip-address:502",
        Timeout:  1 * time.Second,
    })
    // note: use udp:// for modbus TCP over UDP

    // for an RTU (serial) device/bus
    client, err = modbus.NewClient(&modbus.ClientConfiguration{
        URL:      "rtu:///dev/ttyUSB0",
        Speed:    19200,                   // 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",
        Speed:    19200,                   // serial link speed
        Timeout:  1 * time.Second,
    })
    // note: use rtuoverudp:// for modbus RTU over UDP

    if err != nil {
        // error out if client creation failed
    }

    // now that the client is created and configured, attempt to connect
    err = client.Open()
    if err != nil {
        // error out if we failed to connect/open the device
        // note: multiple Open() attempts can be made on the same client until
        // the connection succeeds (i.e. err == nil), calling the constructor again
        // is unnecessary.
        // likewise, a client can be opened and closed as many times as needed.
    }

    // 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 integers
    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 integer
    var reg64   uint64
    reg64, err  = client.ReadUint64(100, modbus.INPUT_REGISTER)

    // read the same 4 consecutive 16-bit registers as a slice of bytes
    var regBs   []byte
    regBs, err  = client.ReadBytes(100, 8, modbus.INPUT_REGISTER)

    // by default, 16-bit integers are decoded as big-endian and 32/64-bit values 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/64-bit values)
    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,
    })

    // write 0x0102030405060708 to 16-bit (holding) registers 10 through 13
    // (8 bytes i.e. 4 consecutive modbus registers)
    err         = client.WriteBytes(10, []byte{
        0x01, 0x02, 0x03, 0x04,
        0x05, 0x06, 0x07, 0x08,
    })

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

Using the server component

See:

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)
  • Bytes (input and holding registers)
  • 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 byte slices and 16-bit integers
  • Little and Big endian, with and without word swap for 32 and 64-bit integers and floating point numbers.

Logging

Both client and server objects will log to stdout by default. This behavior can be overriden by passing a log.Logger object through the Logger property of ClientConfiguration/ServerConfiguration.

TODO (in no particular order)

  • Add RTU (serial) support to the server
  • Add more tests
  • Add diagnostics register support
  • Add fifo register support
  • Add file register support

Dependencies

License

MIT.

Documentation

Index

Constants

View Source
const (
	// HoldingRegister is a writable 16 bit register.
	HoldingRegister RegisterType = 0
	// InputRegister is a read-only 16 bit register.
	InputRegister RegisterType = 1

	// BigEndian means that the most significant bit is first, this is the default.
	BigEndian Endianness = 1
	// LittleEndian means that the least significant bit is first.
	LittleEndian Endianness = 2

	// HighWordFirst means that the most significant register is first, this is the default.
	HighWordFirst WordOrder = 1
	// LowWordFirst means that the least significant register is first.
	LowWordFirst WordOrder = 2
)

Variables

This section is empty.

Functions

func LoadCertPool

func LoadCertPool(filePath string) (cp *x509.CertPool, err error)

LoadCertPool loads a certificate store from a file into a CertPool object.

func WithEndianess added in v1.6.5

func WithEndianess(endianness Endianness) func(*Client)

WithEndianess is an option that can be passed to the Read and Write functions

func WithUnitID added in v1.6.5

func WithUnitID(unitID uint8) func(*Client)

WithUnitID is an option that can be passed to the Read and Write functions

func WithWordOrder added in v1.6.5

func WithWordOrder(wordOrder WordOrder) func(*Client)

WithWordOrder is an option that can be passed to the Read and Write functions

Types

type Client added in v1.6.5

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

Client is the actual client for a Modbus transport.

func NewClient

func NewClient(conf *Configuration) (mc *Client, err error)

NewClient creates, configures and returns a modbus client object.

func (*Client) Close added in v1.6.5

func (mc *Client) Close() error

Close closes the underlying transport.

func (*Client) Open added in v1.6.5

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

Open opens the underlying transport (network socket or serial line).

func (*Client) ReadBytes added in v1.6.5

func (mc *Client) ReadBytes(addr uint16, quantity uint16, regType RegisterType, options ...func(*Client)) (values []byte, err error)

ReadBytes reads one or multiple 16-bit registers (function code 03 or 04) as bytes. A per-register byteswap is performed if endianness is set to LittleEndian.

func (*Client) ReadCoil added in v1.6.5

func (mc *Client) ReadCoil(addr uint16, options ...func(*Client)) (value bool, err error)

ReadCoil reads a single coil (function code 01).

func (*Client) ReadCoils added in v1.6.5

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

ReadCoils reads multiple coils (function code 01).

func (*Client) ReadDiscreteInput added in v1.6.5

func (mc *Client) ReadDiscreteInput(addr uint16, options ...func(*Client)) (value bool, err error)

ReadDiscreteInput reads a single discrete input (function code 02).

func (*Client) ReadDiscreteInputs added in v1.6.5

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

ReadDiscreteInputs reads multiple discrete inputs (function code 02).

func (*Client) ReadFloat32 added in v1.6.5

func (mc *Client) ReadFloat32(addr uint16, regType RegisterType, options ...func(*Client)) (value float32, err error)

ReadFloat32 reads a single 32-bit float register.

func (*Client) ReadFloat32s added in v1.6.5

func (mc *Client) ReadFloat32s(addr uint16, quantity uint16, regType RegisterType, options ...func(*Client)) (values []float32, err error)

ReadFloat32s reads multiple 32-bit float registers.

func (*Client) ReadFloat64 added in v1.6.5

func (mc *Client) ReadFloat64(addr uint16, regType RegisterType, options ...func(*Client)) (value float64, err error)

ReadFloat64 reads a single 64-bit float register.

func (*Client) ReadFloat64s added in v1.6.5

func (mc *Client) ReadFloat64s(addr uint16, quantity uint16, regType RegisterType, options ...func(*Client)) (values []float64, err error)

ReadFloat64s reads multiple 64-bit float registers.

func (*Client) ReadRawBytes added in v1.6.5

func (mc *Client) ReadRawBytes(addr uint16, quantity uint16, regType RegisterType, options ...func(*Client)) (values []byte, err error)

ReadRawBytes reads one or multiple 16-bit registers (function code 03 or 04) as bytes. No byte or word reordering is performed: bytes are returned exactly as they come off the wire, allowing the caller to handle encoding/endianness/word order manually.

func (*Client) ReadRegister added in v1.6.5

func (mc *Client) ReadRegister(addr uint16, regType RegisterType, options ...func(*Client)) (value uint16, err error)

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

func (*Client) ReadRegisters added in v1.6.5

func (mc *Client) ReadRegisters(addr uint16, quantity uint16, regType RegisterType, options ...func(*Client)) (values []uint16, err error)

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

func (*Client) ReadUint32 added in v1.6.5

func (mc *Client) ReadUint32(addr uint16, regType RegisterType, options ...func(*Client)) (value uint32, err error)

ReadUint32 reads a single 32-bit register.

func (*Client) ReadUint32s added in v1.6.5

func (mc *Client) ReadUint32s(addr uint16, quantity uint16, regType RegisterType, options ...func(*Client)) (values []uint32, err error)

ReadUint32s reads multiple 32-bit registers.

func (*Client) ReadUint64 added in v1.6.5

func (mc *Client) ReadUint64(addr uint16, regType RegisterType, options ...func(*Client)) (value uint64, err error)

ReadUint64 reads a single 64-bit register.

func (*Client) ReadUint64s added in v1.6.5

func (mc *Client) ReadUint64s(addr uint16, quantity uint16, regType RegisterType, options ...func(*Client)) (values []uint64, err error)

ReadUint64s reads multiple 64-bit registers.

func (*Client) SetEncoding added in v1.6.5

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

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

func (*Client) SetUnitID added in v1.6.5

func (mc *Client) SetUnitID(id uint8) (err error)

SetUnitID sets the unit id of subsequent requests.

func (*Client) WriteBytes added in v1.6.5

func (mc *Client) WriteBytes(addr uint16, values []byte, options ...func(*Client)) (err error)

WriteBytes writes the given slice of bytes to 16-bit registers starting at addr. A per-register byteswap is performed if endianness is set to LittleEndian. Odd byte quantities are padded with a null byte to fall on 16-bit register boundaries.

func (*Client) WriteCoil added in v1.6.5

func (mc *Client) WriteCoil(addr uint16, value bool, options ...func(*Client)) (err error)

WriteCoil writes a single coil (function code 05).

func (*Client) WriteCoils added in v1.6.5

func (mc *Client) WriteCoils(addr uint16, values []bool, options ...func(*Client)) (err error)

WriteCoils writes multiple coils (function code 15).

func (*Client) WriteFloat32 added in v1.6.5

func (mc *Client) WriteFloat32(addr uint16, value float32, options ...func(*Client)) (err error)

WriteFloat32 writes a single 32-bit float register.

func (*Client) WriteFloat32s added in v1.6.5

func (mc *Client) WriteFloat32s(addr uint16, values []float32, options ...func(*Client)) (err error)

WriteFloat32s writes multiple 32-bit float registers.

func (*Client) WriteFloat64 added in v1.6.5

func (mc *Client) WriteFloat64(addr uint16, value float64, options ...func(*Client)) (err error)

WriteFloat64 writes a single 64-bit float register.

func (*Client) WriteFloat64s added in v1.6.5

func (mc *Client) WriteFloat64s(addr uint16, values []float64, options ...func(*Client)) (err error)

WriteFloat64s writes multiple 64-bit float registers.

func (*Client) WriteRawBytes added in v1.6.5

func (mc *Client) WriteRawBytes(addr uint16, values []byte, options ...func(*Client)) (err error)

WriteRawBytes writes the given slice of bytes to 16-bit registers starting at addr. No byte or word reordering is performed: bytes are pushed to the wire as-is, allowing the caller to handle encoding/endianness/word order manually. Odd byte quantities are padded with a null byte to fall on 16-bit register boundaries.

func (*Client) WriteRegister added in v1.6.5

func (mc *Client) WriteRegister(addr uint16, value uint16, options ...func(*Client)) (err error)

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

func (*Client) WriteRegisters added in v1.6.5

func (mc *Client) WriteRegisters(addr uint16, values []uint16, options ...func(*Client)) (err error)

WriteRegisters writes multiple 16-bit registers (function code 16).

func (*Client) WriteUint32 added in v1.6.5

func (mc *Client) WriteUint32(addr uint16, value uint32, options ...func(*Client)) (err error)

WriteUint32 writes a single 32-bit register.

func (*Client) WriteUint32s added in v1.6.5

func (mc *Client) WriteUint32s(addr uint16, values []uint32, options ...func(*Client)) (err error)

WriteUint32s writes multiple 32-bit registers.

func (*Client) WriteUint64 added in v1.6.5

func (mc *Client) WriteUint64(addr uint16, value uint64, options ...func(*Client)) (err error)

WriteUint64 writes a single 64-bit register.

func (*Client) WriteUint64s added in v1.6.5

func (mc *Client) WriteUint64s(addr uint16, values []uint64, options ...func(*Client)) (err error)

WriteUint64s writes multiple 64-bit registers.

type CoilsRequest

type CoilsRequest struct {
	ClientAddr string // the source (client) IP address
	ClientRole string // the client role as encoded in the client certificate (tcp+tls only)
	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

}

CoilsRequest is an object passed to the coil handler.

type Configuration added in v1.6.5

type Configuration struct {
	// URL sets the client mode and target location in the form
	// <mode>://<serial device or host:port> e.g. tcp://plc:502.
	URL string
	// Speed sets the serial link speed (in bps, rtu only).
	Speed int
	// DataBits sets the number of bits per serial character (rtu only).
	DataBits int
	// Parity sets the serial link parity mode (rtu only).
	Parity serial.Parity
	// StopBits sets the number of serial stop bits (rtu only).
	StopBits serial.StopBits
	// Timeout sets the request timeout value.
	Timeout time.Duration
	// TLSClientCert sets the client-side TLS key pair (tcp+tls only).
	TLSClientCert *tls.Certificate
	// TLSRootCAs sets the list of CA certificates used to authenticate
	// the server (tcp+tls only). Leaf (i.e. server) certificates can also
	// be used in case of self-signed certs, or if cert pinning is required.
	TLSRootCAs *x509.CertPool
	// Logger provides a custom sink for log messages.
	// If nil, messages will be written to stdout.
	Logger *log.Logger
}

Configuration stores the configuration needed to create a Modbus client.

type DiscreteInputsRequest

type DiscreteInputsRequest struct {
	ClientAddr string // the source (client) IP address
	ClientRole string // the client role as encoded in the client certificate (tcp+tls only)
	UnitID     uint8  // the requested unit id (slave id)
	Addr       uint16 // the base discrete input address requested
	Quantity   uint16 // the number of consecutive discrete inputs covered by this request
}

DiscreteInputsRequest is an object passed to the discrete input handler.

type Endianness

type Endianness uint

Endianness is either BigEndian or LittleEndian.

type Error

type Error string

Error for modbus errors

const (
	ErrConfigurationError       Error = "configuration error"
	ErrRequestTimedOut          Error = "request timed out"
	ErrIllegalFunction          Error = "illegal function"
	ErrIllegalDataAddress       Error = "illegal data address"
	ErrIllegalDataValue         Error = "illegal data value"
	ErrServerDeviceFailure      Error = "server device failure"
	ErrAcknowledge              Error = "request acknowledged"
	ErrServerDeviceBusy         Error = "server device busy"
	ErrMemoryParityError        Error = "memory parity error"
	ErrGWPathUnavailable        Error = "gateway path unavailable"
	ErrGWTargetFailedToRespond  Error = "gateway target device failed to respond"
	ErrBadCRC                   Error = "bad crc"
	ErrShortFrame               Error = "short frame"
	ErrProtocolError            Error = "protocol error"
	ErrBadUnitID                Error = "bad unit id"
	ErrBadTransactionID         Error = "bad transaction id"
	ErrUnknownProtocolID        Error = "unknown protocol identifier"
	ErrUnexpectedParameters     Error = "unexpected parameters"
	ErrTransportIsAlreadyOpen   Error = "transport is already open"
	ErrTransportIsAlreadyClosed Error = "transport is already closed"
)

All Modbus errors.

func (Error) Error

func (me Error) Error() (s string)

Error implements the error interface.

type HoldingRegistersRequest

type HoldingRegistersRequest struct {
	ClientAddr string   // the source (client) IP address
	ClientRole string   // the client role as encoded in the client certificate (tcp+tls only)
	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

}

HoldingRegistersRequest is an object passed to the holding register handler.

type InputRegistersRequest

type InputRegistersRequest struct {
	ClientAddr string // the source (client) IP address
	ClientRole string // the client role as encoded in the client certificate (tcp+tls only)
	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
}

InputRegistersRequest is an object passed to the input register handler.

type RegisterType added in v1.6.5

type RegisterType uint

RegisterType is either HoldingRegister or InputRegister.

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 Server added in v1.6.5

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

Server object.

func NewServer

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

NewServer creates a new modbus server.

func (*Server) Start added in v1.6.5

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

Start accepting client connections.

func (*Server) Stop added in v1.6.5

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

Stop accepting new client connections and closes any active session.

type ServerConfiguration

type ServerConfiguration struct {
	// URL defines where to listen at e.g. tcp://[::]:502
	URL string
	// Timeout sets the idle session timeout (client connections will
	// be closed if idle for this long)
	Timeout time.Duration
	// MaxClients sets the maximum number of concurrent client connections
	MaxClients uint
	// TLSServerCert sets the server-side TLS key pair (tcp+tls only)
	TLSServerCert *tls.Certificate
	// TLSClientCAs sets the list of CA certificates used to authenticate
	// client connections (tcp+tls only). Leaf (i.e. client) certificates can
	// also be used in case of self-signed certs, or if cert pinning is required.
	TLSClientCAs *x509.CertPool
	// Logger provides a custom sink for log messages.
	// If nil, messages will be written to stdout.
	Logger *log.Logger
}

ServerConfiguration object

type WordOrder

type WordOrder uint

WordOrder is either HighWordFirst of LowWordFirst.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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