modbus

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Mar 14, 2021 License: MIT Imports: 11 Imported by: 0

README

Modbus library - TCP/RTU and Client/Server interfaces

This code provides library access to Modbus devices, acting either as a client or as a server and supports RTU (serial) and TCP protocols (but not RTU-ASCII).

Note, the library supports access to all Modbus functions, and all but a few diagnostic sub-functions (cannot reset, or set to read-only when acting as a server).

As a client, you can call ALL functions on remote servers (discretes, coils, inputs, registers, files (including FIFO), and all diagnostic functions - counters, ids, logs, resets, etc.).

As a server, ALL data functions from clients are supported (discretes, coils, inputs, registers (including FIFO), files, and almost all diagnostoc functions - counters, ids, logs, counters & counter resets, but not device resets or read-only-mode)

Design

This code separates the various layers of the Modbus protocols - it defines a "modbus" as a communication channel that can have client and server units connected. In the form of a RTU bus the specification requires at most 1 client, and allows many servers. In the form of a TCP connection there is typically only 1 client and 1 server. Not that the server may in fact be a proxy to other units, and that single connection could proxy many units.

Both RTU and TCP protocol modbus setups are exposed as a single Modbbus interface - once a Modbus instance exists, it becomes transparent as to whether the physical implementation is RTU or TCP.

Connecting your code to the Modbus is a simple case of getting a Client if you want to command a remote unit, or registering a Server if you want to allow remote clients to command your code.

Notes about RTU

Establishing a serial connection requires pre-configured baud, parity, and stop-bit settings. In addition, the hardward-control DTR line may need to be set in order to initialize serial connections. The base modbus RTU specification indicates that 9600 baud, 8 bits, even parity, and 1 stop bit MUST be supported. This standard further suggests that support for "odd" and "no parity" should be implemented, but that when "no parity" support is used there should be 2 stop bits.

This library does not enforce any of these standard suggestions or requirements, it has no "default" settings, and as such "you" should ensure that the serial configuration is sane for a Modbus deployment.

Example RTU Client
mb, err := modbus.NewRTU("COM5", 9600, 'E', 1, true)
// ... error handling
client := mb.GetClient(5)

// ... do something with the client connection to remote UnitID 5
Example RTU Server
serialId := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
deviceInfo := []string{"ACME Corporation", "Widget Server", "v5.6.7", "http://github.com/rolfl/modbus"}
server, err := modbus.NewServer(serialId, deviceInfo)
// Register handlers for discretes, coils, inputs, registers & files

mb, err := modbus.NewRTU("COM5", 9600, 'E', 1, true)
// ... error handling
client := mb.SetServer(5, server)

// wait for the service to terminate

Note: This library depends on tarm/serial to drive the serial port, but that library does not currently have DTR support. DTR is required to talk to a number of USB-to-serial transceivers. The plan is to contribute back the DTR (and possibly RTS) support back to tarm/serial, but it needs to work on Linux first. For the moment, as per the tarm/serial license, the code has been copied in to this module, and modified to support DTR. See tarm/serial

The reason tarm/serial is the best for this library is because:

  • no cgo dependency (mikepb/go-serial) was considered, but compiling it is a real challenge
  • it is well established (lots of eyes on it)
  • it is relatively thin ..... not many added features
  • it supports read timeouts

Notes about TCP

In Modbus-TCP deployments the Server side is either:

  • a regular Modbus server - handling function commands as expecter
  • a "bridge" or "proxy" where it is a TCP frontend for multiple backend Modbus Servers

All Modbus servers are expected to have a unit ID between 1 and 247. If the remote system is a proxy/bridge then it will forward specific unit ids to the appropriate system behind the bridge. The TCP Guide states:

This field is used for routing purpose when addressing a device on a MODBUS+ or MODBUS serial line sub-network. In that case, the “Unit Identifier” carries the MODBUS slave address of the remote device: If the MODBUS server is connected to a MODBUS+ or MODBUS Serial Line sub-network and addressed through a bridge or a gateway, the MODBUS Unit identifier is necessary to identify the slave device connected on the subnetwork behind the bridge or the gateway. The destination IP address identifies the bridge itself and the bridge uses the MODBUS Unit identifier to forward the request to the right slave device. The MODBUS slave device addresses on serial line are assigned from 1 to 247 (decimal). Address 0 is used as broadcast address.

If the remote TCP device is a normal Modbus server, it often is configured to respond to ANY unitId, or the specific id 0xFF (which would normally be illegal). The official Modbus TCP guide states:

On TCP/IP, the MODBUS server is addressed using its IP address; therefore, the MODBUS Unit Identifier is useless. The value 0xFF has to be used.

When addressing a MODBUS server connected directly to a TCP/IP network, it’s recommended not using a significant MODBUS slave address in the “Unit Identifier” field. In the event of a re-allocation of the IP addresses within an automated system and if a IP address previously assigned to a MODBUS server is then assigned to a gateway, using a significant slave address may cause trouble because of a bad routing by the gateway. Using a nonsignificant slave address, the gateway will simply discard the MODBUS PDU with no trouble. 0xFF is recommended for the “Unit Identifier" as nonsignificant value.

Remark : The value 0 is also accepted to communicate directly to a MODBUS/TCP device.

That last Remark implies that the broadcast nature of address 0 is relinquished when the device is a TCP server.

In order to best support this somewhat ambiguous system, you can register a server on the Modbus instance using address 0xFF. This special address will cause that server instance to handle all requests regardless of the specified unitId UNLESS an explicit server has been set for the specific unit ID. In other words, registering a server for unit IDs 1, 2, 3 and 0xFF will have the "reasonable" consequence of units 1, 2, and 3 being handled by their respective server instances, and all other unit Ids being handled by the "wildcard" server 0xFF. When serving as a wildcard server 0xFF the server will ignore the broadcast-nature of unitID 0.

Example TCP Client
mb, err := modbus.NewTCP("server.example.com:502")
// error handling

sm := mb.GetClient(5)
// do something with client to remote unit 5
Example TCP Server

When using TCP as a protocol it is normal for the Modbus Server unit to be on the "passive" side of the TCP Socket (the client will establish the TCP socket, the server side waits for a client to connect). As a consequence, it's typical for the system acting as the Modbus Server to also manage a TCP Socket service. The code is thus a little more complicated...

serialId := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
deviceInfo := []string{"ACME Corporation", "Widget Server", "v5.6.7", "http://github.com/rolfl/modbus"}
server, err := modbus.NewServer(serialId, deviceInfo)
// Register handlers for discretes, coils, inputs, registers & files

// The `modbus.ServeAllUnits(server)` does what you expect.
tcpserv, err := modbus.NewTCPServer(":502", modbus.ServeAllUnits(server))
// Error handling

// wait for the network system to shutdown (typically wait "forever")
tcpserv.WaitClosed() 

With the above code, whenever a client connects to us as a service, we will establish the Mobdus protocol over the TCP socket, and then attach the supplied server as a listner for all UnitIds on the connection.

Client operations

When behaving as a client, the client instance is able to call functions on remote servers. All the remote functions are available using names that follow the Modbus specification. All calls require a timeout parameter - calls exceeding the timeout will fail with a timeout error.

All client calls return both a data value and an error. The data value is a Go representation of the specifications response payload, as a go struct. All responses are Stringers so you can print them out to get a sensible report of the response to a client call.

Example calls as a client are:

// get the server's ID - timeout after a second
serverId, err := client.ServerID(time.Second)
// error handling
fmt.Printf("The server's ID is %v", serverId)

// Write 3 coils starting from address 0 - within a second.
response, err := client.WriteMultipleCoils(0, []bool{true, false, true}, time.Second)
// error handling

Server operations

When behaving as a server, the server performs a number of functions for your convenience, and additionally it abstracts out a "cache" of the memory model of the device. The memory model is a "memory safe" implementation where both your code and the modbus library code can safely read/write to the cache from different go-routines. All reads/writes are gated with an atomic abstraction that provides locking to the memory.

Server-triggered commands

When the server is commanded by a client to read values (get discretes, coils, inputs, registers or files) the server will intiate an atomic read from the cache, and respond to the client. When commanded to write values (coils, registers, or files) the server will initiate an atomic transaction, and then with that atomic transaction it will request that the server handler process the mutation request. The handler call will include the current value of the memory cache, the intended write value, and it expects the return value to be the updated value to put in the cache.

Since the callback handler function also has the same atomic reference, it can query or update other parts of the memory cache as well in the same atomic action.

For example, if the following handler is registered with the server:

func updateCoils(server modbus.Server, atomic modbus.Atomic, address int, values []bool, current []bool) ([]bool, error) {
	return values, nil
}

server.RegisterCoils(50, updateCoils)

then when the server gets a request to write values to a set of coils, it will:

  1. establish an atomic lock on the server's cache
  2. read the current value(s) from the specified address (as many values are intended to be written)
  3. call the updateCoils function for the given server, with the same atomic reference, the address to write to, the values that are intended to be written to that address, and the values currently at that address (about to be overwritten)
  4. the updateCoils function can perform whatever operations are required to change the state appropriately (including using the atomic to read/write to other places in the cache).
  5. when complete, return the actual values to be stored in the cache (and if applicable, returned to the client)

See the documentation on the atomic reference for how to queue operations on the cache.

Non-Server operations

Not all systems are triggered by client requests only. It's typical for a system to have "background" tasks that read sensors, etc. and update discretes, inputs, and even coils and holding registers. For these non-server based memory cache updates, the code still needs to perform atomic operations on the server's memory cache (in order for client reads to read the correct values).

A typical process may be for a sensor to be read periodically, and to update a register in the cache. The code would look similar to:

// ... server exists

func updateServerSensorValue(val int) error {
	atomic := server.StartAtomic();
	defer atomic.Complete()
	// write a single value to holding register address 10
	return server.WriteHoldings(atomic, 10, []int{val})
}

Since single-operation atomic calls are common, a simpler version of the above would just be:

// ... server exists

func updateServerSensorValue(val int) error {
	// write a single value to holding register address 10
	return server.WriteHoldingsAtomic(10, []int{val})
}

Documentation

Overview

Package modbus provides interfaces to Modbus Clients and Servers via either TCP or RTU protocols.

Whether behaving as a Client or a Server, you need to establish a communication channel, either socket or serial based. The Modbus protocol is established on top of the communication channel. Once a Modbus instance is created, you can establish Client or Server instances on top of it. A client instance allows communication to a specific remote Server

Special note about Client instances: the Modbus documentation indicates that a "client" can talk to any of the servers on the modbus, but this code requires that for each remote server has a unique Client instance to communicate with it.

Establishing a Modbus communicationc channel using TCP is simple:

mb, _ := modbus.NewTCP("host.example.com:502")

The above establishes a TCP connection on the standard port 502. It is normal, but not required, for the system initiating the TCP to be the client. As a result, it would be normal if you wanted to communicate with the server at the unitID 5 to follow the above line with:

client := mb.GetClient(5)

With a client, you can perform all the standard Modbus functions against that server, for example, read 4 coils from address 0 with a timeout of 2 seconds:

    coils, _ := client.ReadCoils(0, 4, time.Second*2)
	fmt.Printf("The 4 coils are %v\n", coils)

Similar to TCP, establishing an RTU Modbus instance is relatively simple, though additional data is required:

mb, _ := modbus.NewRTU("COM5", 9600, 'E', 1, true)

The above establishes a serial communication channel on the serial port COM5 (windows) with 9600 baud, even parity, 1 stop bit and it also sets the serial DTR line (some systems, espeically USB-based serial protocol converters need this).

The `mb` Modbus instance returned from the NewRTU function behaves the same way as the `mb` returned from NewTCP. You can establish either/both a client presence or server presence on the Modbus. In this example we create a server at the UnitID of 5. Servers are more complicated than clients - we need to establish a pattern of behaviour that the server supports, including ID properties and more.

deviceInfo := []string{"My VendorName", "My ProductCode", "My MajorMinor", "My VendorURL", "My ProductName", ....}
server := []byte("MyServer ID")
server, _ := modbus.NewServer(serverId, deviceInfo)

mb.SetServer(5, server)

The above will establish a server that has no discretes, coils, inputs, registers, or files, but all the metadata and counter logic will still work fine. You need to register handlers for coils, etc in order for them to be functional: more detail about handlers is given in the Server documentaion.

The Modbus protocol relies heavily on 8-bit byte and 16-bit word values to communicate data. This library abstracts all the type conversion and relies on basic Go `int` values instead. Where converting to the valid Modbus type is not possible due to out-of-range values, a panic will be generated. The trade off for code complexity is significant. The public interface for all modbus operations is thus completely int and bool based. The only exception is the byte-array for serverIDs.

Index

Constants

View Source
const (
	// ParityNone is what is used to have no parity bit
	ParityNone = 'N'
	// ParityOdd is what is used to have one parity bit that is set to have an even number of 1 bits
	ParityOdd = 'O'
	// ParityEven is what is used to have one parity bit that is set to have an odd nuber of 1 bits
	ParityEven = 'E'
)
View Source
const (
	// StopBitsOne ensures a single stop bit after transmitting data.
	StopBitsOne = 1
	// StopBitsTwo ensures 2 stop bits after transmitting data.
	StopBitsTwo = 2
)

Variables

This section is empty.

Functions

func ServeAllUnits

func ServeAllUnits(server Server) map[int]Server

ServeAllUnits is a convenience function to map a Modbus Server instance on to all unitID addresses.

Types

type Atomic

type Atomic interface {
	// Complete indicates that all operations in the atomic set are queued. It returns when all operations have completed.
	Complete()
	// contains filtered or unexported methods
}

Atomic allows locked access to the server's internal cache of coil, discrete, input, holding, and file values. implementation in serverCache.go An Atomic instance is created by calling the StartAtomic() function on the Server

Do not Complete an atomic unless you started it. It's normal to `defer a.Complete()` immediately after starting it

atomic := server.StartAtomic()
defer atomic.Complete()

// do stuff using the atomic...

type BusDiagnostics

type BusDiagnostics struct {
	// Messages represents the number of valid messages received on this Modbus
	Messages int
	// CommErrors represents the number of failed receptions (invalid CRC, etc)
	CommErrors int
	// Exceptions represents the number of fail responses this Modbus instance has sent to clients
	Exceptions int
	// Overruns represents the number of incoming requests that were larger than the max Modbus payload size
	Overruns int
}

BusDiagnostics are values specific to the Modbus that summarize the bus status

type Client

type Client interface {
	// UnitID retrieves the remote unitID we are communicating with
	UnitID() int

	// ReadDiscretes reads read-only discrete values from the remote unit
	ReadDiscretes(from int, count int, tout time.Duration) (*X02xReadDiscretes, error)

	// ReadDiscretes reads coil values from the remote unit
	ReadCoils(from int, count int, tout time.Duration) (*X01xReadCoils, error)
	// WriteSingleCoil writes a single coil values to the remote unit
	WriteSingleCoil(address int, value bool, tout time.Duration) (*X05xWriteSingleCoil, error)
	// WriteMultipleCoils writes multiple coil values to the remote unit
	WriteMultipleCoils(address int, values []bool, tout time.Duration) (*X0FxWriteMultipleCoils, error)

	// ReadInputs reads multiple input values from the remote unit
	ReadInputs(from int, count int, tout time.Duration) (*X04xReadInputs, error)

	// ReadHoldings reads multipls holding register values from a remote unit
	ReadHoldings(from int, count int, tout time.Duration) (*X03xReadHolding, error)
	// WriteSingleHolding writes a single holding register to the remote unit
	WriteSingleHolding(from int, value int, tout time.Duration) (*X06xWriteSingleHolding, error)
	// WriteMultipleHoldings writes multiple holding registers to the remote unit
	WriteMultipleHoldings(address int, values []int, tout time.Duration) (*X10xWriteMultipleHoldings, error)
	// WriteReadMultipleHoldings initially writes one set of holding registers to the remote unit, then in the same
	// operation reads multiple values from the remote unit. The addresses being written and then read do not need to overlap
	WriteReadMultipleHoldings(read int, count int, write int, values []int, tout time.Duration) (*X17xWriteReadHoldings, error)
	// MaskWriteHolding applies an AND mask and an OR mask to a register on the remote unit. The logic is:
	// Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask))
	MaskWriteHolding(address int, andmask int, ormask int, tout time.Duration) (*X16xMaskWriteHolding, error)
	// Reads a variable number of values from the remote unit's holding register. At most 31 values can be retrieved
	// and the count of values depends on the value at the specified address (if the value at address is 3, it will return the three
	// values that are in address+1, address+2, address+3)
	ReadFIFOQueue(from int, tout time.Duration) (*X18xReadFIFOQueue, error)

	// ReadMultiFileRecords retrieves multiple sequences of File records from the remote unit
	ReadMultiFileRecords(requests []X14xReadRecordRequest, tout time.Duration) (*X14xReadMultiFileRecord, error)
	// ReadFileRecords retrieves a sequence of records from a file on a remote unit
	ReadFileRecords(file int, record int, length int, tout time.Duration) (*X14xReadFileRecordResult, error)
	// WriteMultiFileRecords writes sequences of records to multiple files on a remote unit
	WriteMultiFileRecords(requests []X15xWriteFileRecordRequest, tout time.Duration) (*X15xMultiWriteFileRecord, error)
	// WriteFileRecords writes a sequence of records to a single file on a remote unit
	WriteFileRecords(file int, record int, values []int, tout time.Duration) (*X15xWriteFileRecordResult, error)

	// ReadExceptionStatus returns the exception status register. The value is a bitmask of exception bits, but the meaning
	// of the set bits is device specific (no standard exists).
	ReadExceptionStatus(tout time.Duration) (*X07xReadExceptionStatus, error)
	// ServerID retrieves the ID of the remote unit. This is typically a unique value, but that is not guaranteed.
	ServerID(tout time.Duration) (*X11xServerID, error)
	// DiagnosticRegister retrieves the diagnostic sub-function 2 register. The value is device-specific.
	DiagnosticRegister(tout time.Duration) (*X08xDiagnosticRegister, error)
	// DiagnosticEcho responds with the exact same content that was sent.
	DiagnosticEcho(data []int, tout time.Duration) (*X08xDiagnosticEcho, error)
	// DiagnosticClear resets all counters and logs on the remote unit
	DiagnosticClear(tout time.Duration) error
	// DiagnosticCount retrieves a specific diagnostic counter from the remote unit. See the Diagnostic constants for valid
	// Diagnostic values.
	DiagnosticCount(counter Diagnostic, tout time.Duration) (*X08xDiagnosticCount, error)
	// DiagnosticOverrunClear resets the overrun counter
	DiagnosticOverrunClear(echo int, tout time.Duration) (*X08xDiagnosticOverrunClear, error)
	// CommEventCounter returns the number of "regular" operations on the remote unit. Regular operations access
	// discretes, coils, inputs, registers, and/or files
	CommEventCounter(tout time.Duration) (*X0BxCommEventCounter, error)
	// CommEventLog retrieves the basic details of the most recent 64 messages on the remote unit
	CommEventLog(tout time.Duration) (*X0CxCommEventLog, error)
	// DeviceIdentification retrieves all the remote unit's device labels.
	DeviceIdentification(tout time.Duration) (*X2BxDeviceIdentification, error)
	// DeviceIdentification retrieves a remote unit's specific device label.
	DeviceIdentificationObject(objectID int, tout time.Duration) (*X2BxDeviceIdentificationObject, error)
}

Client is able to drive a single modbus server (Send functions and get responses)

type Diagnostic

type Diagnostic uint16

Diagnostic is a type used to identify counters in the modbus diagnostics in client.DiagnosticCount(...)

const (
	BusMessages Diagnostic = 0x0B + iota
	BusCommErrors
	BusExceptionErrors
	ServerMessages
	ServerNoResponses
	ServerNAKs
	ServerBusies
	BusCharacterOverruns
)

Diagnostic constants for querying various counters on a Modbus Server

func (Diagnostic) String

func (d Diagnostic) String() string

type Error

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

Error is a custom type for Modbus errors

func IllegalAddressErrorF

func IllegalAddressErrorF(format string, args ...interface{}) *Error

IllegalAddressErrorF represents an invalid address - Modbus error code 2

func IllegalFunctionErrorF

func IllegalFunctionErrorF(format string, args ...interface{}) *Error

IllegalFunctionErrorF represents an invalid function code - Modbus error code 1

func IllegalValueErrorF

func IllegalValueErrorF(format string, args ...interface{}) *Error

IllegalValueErrorF represents an illegal data value - Modbus error code 3

func ServerBusyErrorF

func ServerBusyErrorF(format string, args ...interface{}) *Error

ServerBusyErrorF represents a condition in which the server is busy and cannot process the client request - Modbus error code 6

func ServerFailureErrorF

func ServerFailureErrorF(format string, args ...interface{}) *Error

ServerFailureErrorF represents an error that is not represented by the above types - Modbus error code 4

func (*Error) Code

func (err *Error) Code() uint8

Code is the Modbus code used to identify the type of modbus error

func (*Error) Error

func (err *Error) Error() string

type Modbus

type Modbus interface {
	//GetClient creates a control instance for communicating with a specific server on the remote side of the Modbus
	GetClient(unitID int) Client
	// SetServer establishes a server instance on the given unitId
	SetServer(unitID int, server Server)
	// Close closes the communication channel under the Modbus protocol
	Close() error
	// Diagnostics returns the current diagnostic counters for the Modbus channel
	Diagnostics() BusDiagnostics
	// contains filtered or unexported methods
}

Modbus is a half duplex (or possibly full duplex) mechanism for talking to remote units.

Both Modbus TCP and RTU can be described this way. In order to create a Modbus instance you need to initialize it using either the `modbus.NewTCPConn` or `modbus.NewRTU` constructors.

The Modbus instance can be used to get clients, add servers, or close the communication channel. In addition you can get the current diagnostic state of the channel.

func NewRTU

func NewRTU(device string, baud int, parity int, stopbits int, dtr bool) (Modbus, error)

NewRTU establishes a connection to a local COM port (windows) or serial device (others)

func NewTCP

func NewTCP(hostport string) (Modbus, error)

NewTCP establishes a connection to a remote IP and port using TCP then returns a Modbus instance on that TCP channel using NewTCPConn(connection)

e.g. NewTCP("192.168.1.10:502")

func NewTCPConn

func NewTCPConn(conn *net.TCPConn) (Modbus, error)

NewTCPConn establishes a Modbus transceiver based on a TCP connection

type Server

type Server interface {
	// Diagnostics returns the current diagnostic counts of the server instance
	Diagnostics() ServerDiagnostics

	// Busy will return true if a command is actively being handled
	Busy() bool

	// StartAtomic requests that access to the internal memory model/cache (coils, registers, discretes, inputs and files)
	// of the Server is granted. Only 1 transaction is active at a time, and is active until it is Completed.
	StartAtomic() Atomic

	// RegisterDiscretes indicates how many discretes to make available in the server memory model/cache
	RegisterDiscretes(count int)
	// ReadDiscretes performs a discrete read operation as part of an existing atomic operation from the memory model/cache
	ReadDiscretes(atomic Atomic, address int, count int) ([]bool, error)
	// ReadDiscretesAtomic performs an atomic ReadDiscretes
	ReadDiscretesAtomic(address int, count int) ([]bool, error)
	// WriteDiscretes performs a discrete write operation as part of an existing atomic operation to the memory model/cache
	WriteDiscretes(atomic Atomic, address int, values []bool) error
	// WriteDiscretesAtomic performs an atomic WriteDiscretes
	WriteDiscretesAtomic(address int, values []bool) error

	// RegisterCoils indicates how many coils to make available in the server memory model/cache, and which function to call
	// when a remote client attempts to update the coil settings
	RegisterCoils(count int, handler UpdateCoils)
	// ReadCoils performs a coil read operation as part of an existing atomic operation from the memory model/cache
	ReadCoils(atomic Atomic, address int, count int) ([]bool, error)
	// ReadCoilsAtomic performs an atomic ReadCoils
	ReadCoilsAtomic(address int, count int) ([]bool, error)
	// WriteCoils performs a coil write operation as part of an existing atomic operation to the memory model/cache
	WriteCoils(atomic Atomic, address int, values []bool) error
	// WriteCoilsAtomic performs an atomic WriteCoils
	WriteCoilsAtomic(address int, values []bool) error

	// RegisterInputs indicates how many inputs to make available in the server memory model/cache
	RegisterInputs(count int)
	// ReadInputs performs ain input read operation as part of an existing atomic operation from the memory model/cache
	ReadInputs(atomic Atomic, address int, count int) ([]int, error)
	// ReadInputsAtomic performs an atomic ReadInputs
	ReadInputsAtomic(address int, count int) ([]int, error)
	// WriteInputs performs an input write operation as part of an existing atomic operation to the memory model/cache
	WriteInputs(atomic Atomic, address int, values []int) error
	// WriteInputsAtomic performs an atomic WriteInputs
	WriteInputsAtomic(address int, values []int) error

	// RegisterHoldings indicates how many coils to make available in the server memory model/cache, and which function to call
	// when a remote client attempts to update the holding register values
	RegisterHoldings(count int, handler UpdateHoldings)
	// ReadHoldings performs a holding register read operation as part of an existing atomic operation from the memory model/cache
	ReadHoldings(atomic Atomic, address int, count int) ([]int, error)
	// ReadHoldingsAtomic performs an atomic ReadHoldings
	ReadHoldingsAtomic(address int, count int) ([]int, error)
	// WriteHoldings performs a holding register write operation as part of an existing atomic operation to the memory model/cache
	WriteHoldings(atomic Atomic, address int, values []int) error
	// WriteHoldingsAtomic performs an atomic WriteHoldings
	WriteHoldingsAtomic(address int, values []int) error

	// RegisterFiles indicates how many files to make available in the server memory model/cache, and which function to call
	// when a remote client attempts to update the file records
	RegisterFiles(count int, handler UpdateFile)
	// ReadFileRecords performs a file records read operation as part of an existing atomic operation from the memory model/cache
	ReadFileRecords(atomic Atomic, address int, offset int, count int) ([]int, error)
	// ReadFileRecordsAtomic performs an atomic ReadFileRecords
	ReadFileRecordsAtomic(address int, offset int, count int) ([]int, error)
	// WriteFileRecords performs a file records write operation as part of an existing atomic operation to the memory model/cache
	WriteFileRecords(atomic Atomic, address int, offset int, values []int) error
	// WriteFileRecordsAtomic performs an atomic WriteFileRecords
	WriteFileRecordsAtomic(address int, offset int, values []int) error
	// contains filtered or unexported methods
}

Server represents a system that can handle an incoming request from a remote client

func NewServer

func NewServer(id []byte, deviceInfo []string) (Server, error)

NewServer creates a Server instance that can be bound to a Modbus instance using modbus.SetServer(...).

type ServerDiagnostics

type ServerDiagnostics struct {
	Messages     int
	NoResponse   int
	ServerNAKs   int
	ServerBusy   int
	Register     int
	EventCounter int
}

ServerDiagnostics represents a summary of the server state.

type TCPServer

type TCPServer interface {
	io.Closer
	// WaitClosed will simply wait until the TCP server is closed. This is useful for creating
	// programs that don't exit until the listener is terminated.
	WaitClosed()
}

TCPServer represents a mechanism for receiving connections from remote clients. Note that this is not a Modbus server, but a TCP service, ready to accept connections and from each connection create a Modbus instance using NewTCPConn(...)

func NewTCPServer

func NewTCPServer(host string, servers map[int]Server) (TCPServer, error)

NewTCPServer establishes a listening socket to accept incoming TCP requests. Use ":{port}" style value to bind to all interfaces on the host. Use a specific local IP or local hostname to bind to just one interface.

Example bind to all interfaces: NewModbusTCPListener(":502", demux)

Example bind to just localhost: NewModbusTCPListener("localhost:502", demux)

Note that this function accepts a UnitID to Server mapping. Any connections to this server will be initialized with the supplied servers serving requests to the matching UnitID. It's normal for Modbus-TCP to have 1 server instance hosting ALL the UnitID addresses on the bus. The standard is to listen on UnitID 0xff. This is made more convenient with the ServeAllUnits(server) function.const

tcpserv, _ := modbus.NewTCPServer(":502", modbus.ServeAllUnits(server))

type UpdateCoils

type UpdateCoils func(server Server, atomic Atomic, address int, values []bool, current []bool) ([]bool, error)

UpdateCoils is a function called when coils are expected to be written by request from a remote client Do not Complete the atomic

type UpdateFile

type UpdateFile func(server Server, atomic Atomic, file int, address int, values []int, current []int) ([]int, error)

UpdateFile is a function called when files are expected to be written by request from a remote client Do not Complete the atomic

type UpdateHoldings

type UpdateHoldings func(server Server, atomic Atomic, address int, values []int, current []int) ([]int, error)

UpdateHoldings is a function called when holding registers are expected to be written by request from a remote client Do not Complete the atomic

type X01xReadCoils

type X01xReadCoils struct {
	Address int
	Coils   []bool
}

X01xReadCoils contains the results of reading coils from a remote server

func (X01xReadCoils) String

func (s X01xReadCoils) String() string

type X02xReadDiscretes

type X02xReadDiscretes struct {
	Address   int
	Discretes []bool
}

X02xReadDiscretes contains the results of reading discretes from a remote server

func (X02xReadDiscretes) String

func (s X02xReadDiscretes) String() string

type X03xReadHolding

type X03xReadHolding struct {
	Address int
	Values  []int
}

X03xReadHolding server response to a Read Multiple Holding Registers request

func (X03xReadHolding) String

func (s X03xReadHolding) String() string

type X04xReadInputs

type X04xReadInputs struct {
	Address int
	Values  []int
}

X04xReadInputs server response to a Read Multiple Inputs request

func (X04xReadInputs) String

func (s X04xReadInputs) String() string

type X05xWriteSingleCoil

type X05xWriteSingleCoil struct {
	Address int
	Value   bool
}

X05xWriteSingleCoil server response to a Write Single Coil request

func (X05xWriteSingleCoil) String

func (s X05xWriteSingleCoil) String() string

type X06xWriteSingleHolding

type X06xWriteSingleHolding struct {
	Address int
	Value   int
}

X06xWriteSingleHolding server response to a Read Multiple Holding Registers request

func (X06xWriteSingleHolding) String

func (s X06xWriteSingleHolding) String() string

type X07xReadExceptionStatus

type X07xReadExceptionStatus struct {
	ExceptionStatus int
}

X07xReadExceptionStatus server response to a ServerID function request

func (X07xReadExceptionStatus) String

func (s X07xReadExceptionStatus) String() string

type X08xDiagnosticCount

type X08xDiagnosticCount struct {
	Counter Diagnostic
	Count   int
}

X08xDiagnosticCount server response to a Diagnostic Counter function request

func (X08xDiagnosticCount) String

func (s X08xDiagnosticCount) String() string

type X08xDiagnosticEcho

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

X08xDiagnosticEcho server response to a Diagnostic Return Query data function request

func (X08xDiagnosticEcho) String

func (s X08xDiagnosticEcho) String() string

type X08xDiagnosticOverrunClear

type X08xDiagnosticOverrunClear struct {
	Echo int
}

X08xDiagnosticOverrunClear server response to a Diagnostic Overrun Clear data function request

func (X08xDiagnosticOverrunClear) String

type X08xDiagnosticRegister

type X08xDiagnosticRegister struct {
	Register int
}

X08xDiagnosticRegister server response to a Diagnostic Return Query data function request

func (X08xDiagnosticRegister) String

func (s X08xDiagnosticRegister) String() string

type X0BxCommEventCounter

type X0BxCommEventCounter struct {
	Busy       bool
	EventCount int
}

X0BxCommEventCounter server response to a Comm Event Counter function request

func (X0BxCommEventCounter) String

func (s X0BxCommEventCounter) String() string

type X0CxCommEventLog

type X0CxCommEventLog struct {
	Busy         bool
	EventCount   int
	MessageCount int
	Events       []int
}

X0CxCommEventLog server response to a Comm Event Counter function request

func (X0CxCommEventLog) String

func (s X0CxCommEventLog) String() string

type X0FxWriteMultipleCoils

type X0FxWriteMultipleCoils struct {
	Address int
	Count   int
}

X0FxWriteMultipleCoils server response to a Write Multiple Coil request

func (X0FxWriteMultipleCoils) String

func (s X0FxWriteMultipleCoils) String() string

type X10xWriteMultipleHoldings

type X10xWriteMultipleHoldings struct {
	Address int
	Count   int
}

X10xWriteMultipleHoldings server response to a Write Multiple Holding Registers request

func (X10xWriteMultipleHoldings) String

func (s X10xWriteMultipleHoldings) String() string

type X11xServerID

type X11xServerID struct {
	ServerID     []byte
	RunIndicator bool
}

X11xServerID server response to a ServerID function request

func (X11xServerID) String

func (s X11xServerID) String() string

type X14xReadFileRecordResult

type X14xReadFileRecordResult struct {
	File   int
	Record int
	Values []int
}

X14xReadFileRecordResult server response to a Read Multiple File Record request

func (X14xReadFileRecordResult) String

func (s X14xReadFileRecordResult) String() string

type X14xReadMultiFileRecord

type X14xReadMultiFileRecord struct {
	Records []X14xReadFileRecordResult
}

X14xReadMultiFileRecord server response to a Read Multiple File Record request

func (X14xReadMultiFileRecord) String

func (s X14xReadMultiFileRecord) String() string

type X14xReadRecordRequest

type X14xReadRecordRequest struct {
	File   int
	Record int
	Length int
}

X14xReadRecordRequest defines a record to read from a file. To be used by ReadMultiFileRecord

func (X14xReadRecordRequest) String

func (s X14xReadRecordRequest) String() string

type X15xMultiWriteFileRecord

type X15xMultiWriteFileRecord struct {
	Results []X15xWriteFileRecordResult
}

X15xMultiWriteFileRecord server response to Multiple Write File Records request

func (X15xMultiWriteFileRecord) String

func (s X15xMultiWriteFileRecord) String() string

type X15xWriteFileRecordRequest

type X15xWriteFileRecordRequest struct {
	File   int
	Record int
	Values []int
}

X15xWriteFileRecordRequest data to send in a multi-file-record-write

func (X15xWriteFileRecordRequest) String

type X15xWriteFileRecordResult

type X15xWriteFileRecordResult struct {
	File   int
	Record int
	Length int
}

X15xWriteFileRecordResult defines the response to the WriteMultiFileRecord function for just one of the file results

func (X15xWriteFileRecordResult) String

func (s X15xWriteFileRecordResult) String() string

type X16xMaskWriteHolding

type X16xMaskWriteHolding struct {
	Address int
	ANDMask int
	ORMask  int
}

X16xMaskWriteHolding server response to a Read Multiple Holding Registers request

func (X16xMaskWriteHolding) String

func (s X16xMaskWriteHolding) String() string

type X17xWriteReadHoldings

type X17xWriteReadHoldings struct {
	Address int
	Values  []int
}

X17xWriteReadHoldings server response to a Write/Read Multiple Holding Registers request

func (X17xWriteReadHoldings) String

func (s X17xWriteReadHoldings) String() string

type X18xReadFIFOQueue

type X18xReadFIFOQueue struct {
	Address int
	Values  []int
}

X18xReadFIFOQueue server response to a Read FIFO Queue request

func (X18xReadFIFOQueue) String

func (s X18xReadFIFOQueue) String() string

type X2BxDeviceIdentification

type X2BxDeviceIdentification struct {
	VendorName          string
	ProductCode         string
	MajorMinorVersion   string
	VendorURL           string
	ProductName         string
	ModelName           string
	UserApplicationName string
	Additional          []string
}

X2BxDeviceIdentification server response to a Device Identification function request

func (X2BxDeviceIdentification) String

func (s X2BxDeviceIdentification) String() string

type X2BxDeviceIdentificationObject

type X2BxDeviceIdentificationObject struct {
	ObjectID int
	Name     string
	Value    string
}

X2BxDeviceIdentificationObject server response to a Device Identification function request for a single Object

func (X2BxDeviceIdentificationObject) String

Directories

Path Synopsis
Goserial is a simple go package to allow you to read and write from the serial port as a stream of bytes.
Goserial is a simple go package to allow you to read and write from the serial port as a stream of bytes.

Jump to

Keyboard shortcuts

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