modbus

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2018 License: MIT Imports: 10 Imported by: 0

README

Threadsafe Modbus Client Library

GoDoc Build Status Coverage Status Go Report Card

This Go package implements a Modbus Client (i.e. a master) that can be used concurrently in multiple goroutines.

Supported Protocols

  • RTU
  • ASCII
  • TCP

Supported Queries

  • Read Coils
  • Read Discrete Inputs
  • Read Holding Registers
  • Read Input Registers
  • Write Single Coil
  • Write Single Register
  • Write Multiple Coils
  • Write Multiple Registers
  • Mask Write Register

Example

Initialize a ConnectionSettings struct. Set the Mode, Host, Timeout, and Baud if the Mode is ModeRTU or ModeASCII. When using ModeTCP the Host is the fully qualified domain name or ip address and port number. The port number in the Host string is required. When using ModeRTU or ModeASCII, the Baud rate is required and the Host string is the full path to the serial device or the name of the COM port if on Windows. For all modes, the Timeout cannot be zero.

csTCP := ConnectionSettings{
        Mode: ModeTCP,
        Host: "192.168.1.121:502",
        Timeout: 500 * time.Millisecond,
}
csASCII := ConnectionSettings{
        Mode: ModeASCII,
        Host: "/dev/ttyS1",
        Baud: 9600,
        Timeout: 500 * time.Millisecond,
}
csRTU := ConnectionSettings{
        Mode: ModeRTU,
        Host: "/dev/ttyUSB0",
        Baud: 115200,
        Timeout: 500 * time.Millisecond,
}

GetClientHandle returns a ClientHandle object which can be used to concurrently send Query objects to the underlying client. This starts the client with the given ConnectionSettings if it's not already running.

ch, err := modbus.GetClientHandle(csTCP)
if nil != err {
        fmt.Println(err)
        return
}
q, _ := ReadCoils(0,0,16)
data, err := ch.Send(q)

Multiple ClientHandles can be acquired or the same ClientHandle can be copied and reused in multiple goroutines. The ConnectionSettings must match exactly if a client is already running with the same Host string.

cs := csTCP
ch1, err := modbus.GetClientHandle(cs) // Returns another ClientHandle for the same client
if nil != err {
        fmt.Println(err)
        return
}
cs.Timeout = 1000
_, err := modbus.GetClientHandle(cs) // Returns error since the Timeout was changed
if nil != err {
        fmt.Println(err)
        return
}

Create a Query using one of the function code initializers.

readCoils, err := ReadCoils(0, 0, 5) // SlaveID, Address, Quantity
if nil != err {
        fmt.Println(err)
        return
}
data, err := ch.Send(readCoils)

You can edit and reuse the Query, say to change the SlaveID.

readCoils.SlaveID = 1
data, err := ch.Send(readCoils)

Alternatively you can manually initialize a Query struct and call IsValid() on the Query to make sure that it is well formed.

readDiscreteInputs := Query{
        FunctionCode: FunctionReadDiscreteInputs,
        SlaveID: 1,
        Address: 3,
        Quantity: 4,
}
writeMultitpleRegisters := Query{
        FunctionCode: FunctionWriteMultipleRegisters,
        Address:      1,
        Quantity:     2,
        Values:       []uint16{0x8081, 500},
}

if valid, err := readDiscreteInputs.IsValid(); !valid {
        fmt.Println(err)
        return
}
if valid, err := writeMultipleRegisters.IsValid(); !valid {
        fmt.Println(err)
        return
}

When you are finished using the ClientHandle, call its Close() method. The underlying client is closed and all associated goroutines will return once all open ClientHandles are closed. Keep in mind that if you are sharing a ClientHandle between multiple goroutines, and one call Close, that ClientHandle will fail to send any further Queries.

Documentation

Overview

Package modbus implements a threadsafe modbus library.

Getting Started

Start by initializing a valid ConnectionSettings object and passing it to GetClientHandle. If successful, the error will be <nil> and you can use ClientHandle.Send(Query) to transmit Queries.

Concurrent Access

The ClientHandle can be used in multiple goroutines concurrently as long as ClientHandle.Close() has yet to be called. To allow for each goroutine to call ClientHandle.Close() asynchronously, multiple ClientHandles for the same ConnectionSettings can be acquired using repeated calls to GetClientHandle. The clients are hashed by their ConnectionSettings.Host string, and ConnectionSettings must match exactly for multiple ClientHandles to be returned.

Cleanup

After all ClientHandles for a given client with the corresponding ConnectionSettings have been closed, the client is automatically shutdown.

Synchronous Access

The RTU, ASCII and TCP Packagers implement the modbus protocol and can be used directly to send Queries. This is intended to allow more flexible use of the underlying modbus library, such as in simple programs that don't require concurrency.

Index

Constants

View Source
const (
	MaxRTUSize   = 512
	MaxASCIISize = 512
	MaxTCPSize   = 260
)

MaxRTUSize MaxASCIISize and MaxTCPSize define the maximum allowable number of byes in a single Modbus packet.

View Source
const (
	FunctionReadCoils              FunctionCode = 0x01
	FunctionReadDiscreteInputs                  = 0x02
	FunctionReadHoldingRegisters                = 0x03
	FunctionReadInputRegisters                  = 0x04
	FunctionWriteSingleCoil                     = 0x05
	FunctionWriteSingleRegister                 = 0x06
	FunctionWriteMultipleCoils                  = 0x0F
	FunctionWriteMultipleRegisters              = 0x10
	FunctionMaskWriteRegister                   = 0x16
)

Modbus Function Codes

Variables

View Source
var FunctionCodes = map[string]FunctionCode{}

FunctionCodes maps FunctionCodes by their FunctionName, i.e. the inverse of the FunctionNames map

View Source
var FunctionNames = map[FunctionCode]string{
	FunctionReadCoils:              "ReadCoils",
	FunctionReadDiscreteInputs:     "ReadDiscreteInputs",
	FunctionReadHoldingRegisters:   "ReadHoldingRegisters",
	FunctionReadInputRegisters:     "ReadInputRegisters",
	FunctionWriteSingleCoil:        "WriteSingleCoil",
	FunctionWriteSingleRegister:    "WriteSingleRegister",
	FunctionWriteMultipleCoils:     "WriteMultipleCoils",
	FunctionWriteMultipleRegisters: "WriteMultipleRegisters",
	FunctionMaskWriteRegister:      "MaskWriteRegister",
}

FunctionNames maps function name strings by their Function Code

View Source
var ModeByName = map[string]Mode{}

ModeByName maps ModeNames to their Mode, i.e. the inverse of ModeNames.

View Source
var ModeNames = map[Mode]string{
	ModeTCP:   "TCP",
	ModeRTU:   "RTU",
	ModeASCII: "ASCII",
}

ModeNames maps Mode to a string description

Functions

This section is empty.

Types

type ASCIIPackager

type ASCIIPackager struct {
	*serial.Port
	// contains filtered or unexported fields
}

ASCIIPackager implements the Packager interface for Modbus ASCII.

func NewASCIIPackager

func NewASCIIPackager(c ConnectionSettings) (*ASCIIPackager, error)

NewASCIIPackager returns a new, ready to use ASCIIPackager with the given ConnectionSettings.

func (*ASCIIPackager) Send

func (pkgr *ASCIIPackager) Send(q Query) ([]byte, error)

Send sends the Query and returns the result or and error code.

func (*ASCIIPackager) SetDebug

func (ps *ASCIIPackager) SetDebug(debug bool)

type ClientHandle

type ClientHandle interface {
	// Send sends the Query to the underlying client for transmission and
	// waits for the response data.
	Send(q Query) ([]byte, error)
	// Close closes the ClientHandle. Once all ClientHandles for a given Client
	// have been closed, the Client will shutdown.
	Close() error
	// GetConnectionSettings returns the ConnectionSettings for the client
	// associated with this ClientHandle.
	GetConnectionSettings() ConnectionSettings
}

ClientHandle provides a handle for sending Queries to a Client.

func GetClientHandle

func GetClientHandle(cs ConnectionSettings) (ClientHandle, error)

GetClientHandle returns a new ClientHandle for a client with the given ConnectionSettings. ConnectionSettings with the same Host string must also match exactly for a new ClientHandle to be returned. The client is shutdown after all ClientHandles have been Closed. After a client for a given Host has been shutdown it can be reopened with different ConnectionSettings.

type ConnectionSettings

type ConnectionSettings struct {
	Mode
	Host    string
	Baud    uint
	Timeout time.Duration
	Debug   bool
}

ConnectionSettings holds all connection settings. For ModeTCP the Host is the FQDN or IP address AND the port number. For ModeRTU and ModeASCII the Host string holds the full path to the serial device (Linux) or the name of the COM port (Windows) and BaudRate must be specified. The Timeout is the response timeout for the the underlying connection.

type FunctionCode

type FunctionCode byte

FunctionCode is the modbus function code type.

type Mode

type Mode byte

Mode is the modbus connection mode type.

const (
	ModeTCP Mode = iota
	ModeRTU
	ModeASCII
)

The available modbus connection modes.

type Packager

type Packager interface {
	Send(q Query) ([]byte, error)
	Transporter
	SetDebug(debug bool)
}

Packager generates the raw bytes of a Modbus packet for a given Query, transmits the Query on the underlying Transporter interface, and returns and parses the response data. A Packager is implemented for the three modbus Modes: ASCIIPackager, RTUPackager and TCPPackager.

func NewPackager

func NewPackager(cs ConnectionSettings) (Packager, error)

NewPackager returns a Packager according to the cs.Mode.

type Query

type Query struct {
	FunctionCode
	SlaveID  byte
	Address  uint16
	Quantity uint16
	Values   []uint16
}

Query contains the necessary data for a Packager to construct and execute a Modbus query.

func MaskWriteRegister

func MaskWriteRegister(slaveID byte, address, andMask, orMask uint16) (Query, error)

MaskWriteRegister constructs a MaskWriteRegister Query object.

func ReadCoils

func ReadCoils(slaveID byte, address, quantity uint16) (Query, error)

ReadCoils constructs a ReadCoils Query object.

func ReadDiscreteInputs

func ReadDiscreteInputs(slaveID byte, address, quantity uint16) (Query, error)

ReadDiscreteInputs constructs a ReadDiscreteInputs Query object.

func ReadHoldingRegisters

func ReadHoldingRegisters(slaveID byte, address, quantity uint16) (Query, error)

ReadHoldingRegisters constructs a ReadHoldingRegisters Query object.

func ReadInputRegisters

func ReadInputRegisters(slaveID byte, address, quantity uint16) (Query, error)

ReadInputRegisters constructs a ReadInputRegisters Query object.

func ReadQuery

func ReadQuery(slaveID byte, fCode FunctionCode,
	address, quantity uint16) (Query, error)

ReadQuery constructs a Query where isReadFunction(fCode) is true.

func WriteMultipleCoils

func WriteMultipleCoils(slaveID byte, address, quantity uint16,
	value []uint16) (Query, error)

WriteMultipleCoils constructs a WriteMultipleCoils Query object.

func WriteMultipleQuery

func WriteMultipleQuery(slaveID byte, fCode FunctionCode,
	address, quantity uint16, values []uint16) (Query, error)

WriteMultipleQuery constructs a Query where isWriteMultipleFunction(fCode) is true.

func WriteMultipleRegisters

func WriteMultipleRegisters(slaveID byte, address, quantity uint16,
	value []uint16) (Query, error)

WriteMultipleRegisters constructs a WriteMultipleRegisters Query object.

func WriteSingleCoil

func WriteSingleCoil(slaveID byte, address uint16, value bool) (Query, error)

WriteSingleCoil constructs a WriteSingleCoil Query object.

func WriteSingleQuery

func WriteSingleQuery(slaveID byte, fCode FunctionCode,
	address, value uint16) (Query, error)

WriteSingleQuery constructs a Query where isWriteSingleFunction(fCode) is true.

func WriteSingleRegister

func WriteSingleRegister(slaveID byte, address, value uint16) (Query, error)

WriteSingleRegister constructs a WriteSingleRegister Query object.

func (Query) IsValid

func (q Query) IsValid() (bool, error)

IsValid returns a bool representing whether the Query is well constructed with a supported FunctionCode and appropriate Quantity and len(Values). If the query is invalid, IsValid returns false, and an error describing the reason for not passing. Otherwise it returns true, nil.

type RTUPackager

type RTUPackager struct {
	*serial.Port
	// contains filtered or unexported fields
}

RTUPackager implements the Packager interface for Modbus RTU.

func NewRTUPackager

func NewRTUPackager(c ConnectionSettings) (*RTUPackager, error)

NewRTUPackager returns a new, ready to use RTUPackager with the given ConnectionSettings.

func (*RTUPackager) Send

func (pkgr *RTUPackager) Send(q Query) ([]byte, error)

Send sends the Query and returns the result or and error code.

func (*RTUPackager) SetDebug

func (ps *RTUPackager) SetDebug(debug bool)

type TCPPackager

type TCPPackager struct {
	net.Conn
	// contains filtered or unexported fields
}

TCPPackager implements the Packager interface for Modbus TCP.

func NewTCPPackager

func NewTCPPackager(c ConnectionSettings) (*TCPPackager, error)

NewTCPPackager returns a new, ready to use TCPPackager with the given ConnectionSettings.

func (*TCPPackager) Send

func (pkgr *TCPPackager) Send(q Query) ([]byte, error)

Send sends the Query and returns the result or and error code.

func (*TCPPackager) SetDebug

func (ps *TCPPackager) SetDebug(debug bool)

type Transporter

type Transporter interface {
	Write([]byte) (int, error)
	Read([]byte) (int, error)
	Close() error
}

Transporter is the underlying connection interface. This is used to store either a TCP connection or a serial/comm port.

Jump to

Keyboard shortcuts

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