modbus

package module
v0.0.0-...-53cfe52 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2021 License: ISC Imports: 8 Imported by: 0

README

Optimizing Modbus client

Go Reference Go Report Card License

This is a proof-of-concept Modbus master library that operates in batches of requests, based on goburrow/modbus.

The client itself is a refactored extract from a bachelor's thesis on high-delay connection communication optimization methods, where Modbus RTU would go over a connection with very high latency.

Optimization methods

The client uses the properties of Modbus functions 3/16 (Read Holding Registers / Write Multiple Holding Registers), specifically their ability to operate on multiple registers at once. If registers + quantity are closely connected (e.g. requests with register 82, quantity 2, and register 84 will be connected), they can be merged into a single request.

For write requests, the master can also use the previous values to only send the requests containing differentiating values to the slave.

Example

package main

import (
    "fmt"

    goburrow "github.com/goburrow/modbus"
    "github.com/tdemin/opmodbus/types"
    modbus "github.com/tdemin/opmodbus"
)

func main() {
    handler := goburrow.NewTCPClientHandler("localhost:502")
    defer handler.Close()
    client := modbus.NewClient(handler)
    // client will batch these three reads into a single request
    // with function 3, register 82 and quantity 6
    results, err := client.BatchRead([]modbus.Read{
        ReadFloat{82},
        ReadFloat{84},
        ReadFloat{86},
    })
    if err != nil {
        panic(err)
    }
    for register, value := range results {
        fmt.Printf("%v: %v", register, value)
    }
    // client will batch these two writes into a single request
    // with function 16, register 82, quantity 4, and merged data from
    // both requests
    if client.BatchWrite([]modbus.Write{
        WriteFloat{82, 0.4},
        WriteFloat{84, 0.6},
    }, nil); err != nil {
        panic(err)
    }
    // with differential optimization against old values
    if client.BatchWrite([]modbus.Write{
        WriteFloat{82, 0.3},
        WriteFloat{84, 0.7},
    }, results); err != nil {
        panic(err)
    }
}

// implements read/write operation interfaces
type ReadFloat struct {
    register uint16
}
func (r ReadFloat) Register() uint16 {
    return r.register
}
func (ReadFloat) Type() types.Type {
    // opmodbus has pre-built types for float32 and uint16
    return types.Float32CDABType
}

type WriteFloat struct {
    register uint16
    value float32
}
func (w WriteFloat) Register() uint16 {
    return w.register
}
func (w WriteFloat) Value() types.Value {
    return types.Float32CDAB(w.value)
}

Caveats

The client is currently only capable of using functions 3/16 for read/write operations.

The optimization premise is based on the assumption that the Modbus slave is capable of having its registers grouped one after other in PLC configuration. This has proven to be not true for some PLCs like OVEN PLC-63. You should verify that your PLC works in such a configuration first.

Differential optimization should only be used under two conditions:

  1. You know the registers you set will never change between BatchWrite invocations (otherwise invoking BatchWrite involves a risk of skipping required updates).
  2. The registers to be set aren't going in the straight order right after another (otherwise skipping differential optimization is more likely to save time).

Copying

See COPYING.

Documentation

Overview

Package modbus implements a proof-of-concept thread-safe Modbus client that operates on batches of requests.

The client is designed to work on high-latency connections where an average delay between a Modbus request and the response to it gets up to a few seconds by using Modbus functions 3 and 16 to combine requests which can be merged into one. See doc of BatchRead/BatchWrite for details.

The client can also optimize batch writes by difference against an older set of values. See doc of BatchWrite for details.

Algorithm

1. For write operations: if differential optimization is enabled, exclude all operations whose register and value match the older written data.

2. Sort operations by register number in ascending order.

3. If out of two consecutive operations A and B the following condition holds true:

A.register + A.quantity = B.register

and the total quantity after merge does not exceed 2047 for reads and 123 for writes, merge operations.

Index

Constants

This section is empty.

Variables

View Source
var ErrTooManyRegisters = errors.New("too many registers in an operation")

ErrTooManyRegisters is returned when a number of registers exceeds 123 for writes, and 2047 for reads.

Functions

This section is empty.

Types

type Client

type Client struct {
	modbus.Client
	modbus.ClientHandler
	// contains filtered or unexported fields
}

Client is an optimizing Modbus client that operates on chains of requests. It can only execute functions 3 and 16.

Client is only thread-safe if Client and ClientHandler are untouched.

func NewClient

func NewClient(handler modbus.ClientHandler) *Client

NewClient builds a Modbus client from ClientHandler.

func (*Client) BatchRead

func (c *Client) BatchRead(ops []Read) (Registers, error)

BatchRead optimizes a batch of read operations, performs them with function 3 and returns a map of Modbus registers with their corresponding values.

See package documentation for the optimization algoritm.

func (*Client) BatchWrite

func (c *Client) BatchWrite(ops []Write, oldData Registers) error

BatchWrite optimizes a batch of write operations, performs them with function 16 and returns on the first error encountered.

If oldData is not nil, BatchWrite will perform differential optimization. See package documentation for the optimization algorithm.

Only use differential optimization if it is well-known that the slave registers values never change between BatchWrite invocations.

func (*Client) Read

func (c *Client) Read(register uint16, t types.Type) (types.Value, error)

Read reads a single value from one or more Modbus registers with function 3 and converts it to Value. The number of Modbus registers is automatically picked based on provided type.

func (*Client) Write

func (c *Client) Write(register uint16, value types.Value) error

Write writes a single value to one or more Modbus registers with function 16. The number of Modbus registers is automatically picked based on value size.

type Read

type Read interface {
	Register() uint16
	Type() types.Type
}

Read represents Modbus function 3 call for a single value.

type Registers

type Registers map[uint16]types.Value

Registers holds a mapping of a Modbus registers set to their values.

type Write

type Write interface {
	Register() uint16
	Value() types.Value
}

Write represents Modbus function 16 call for a single value.

Directories

Path Synopsis
internal
Package types provides the most common data types for use with Modbus.
Package types provides the most common data types for use with Modbus.

Jump to

Keyboard shortcuts

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