modbus

package module
v0.0.0-...-8ec1b3d Latest Latest
Warning

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

Go to latest
Published: Apr 4, 2025 License: Apache-2.0 Imports: 18 Imported by: 0

README

Modbus TCP and RTU protocol client

License GoDoc Go Report Card Codecov

Modbus client (TCP/RTU) over TCP/UDP/Serial for Golang.

For questions use Github Discussions

Installation

go get github.com/aldas/go-modbus-client

Supported functions

  • FC1 - Read Coils (req/resp)
  • FC2 - Read Discrete Inputs (req/resp)
  • FC3 - Read Holding Registers (req/resp)
  • FC4 - Read Input Registers (req/resp)
  • FC5 - Write Single Coil (req/resp)
  • FC6 - Write Single Register (req/resp)
  • FC15 - Write Multiple Coils (req/resp)
  • FC16 - Write Multiple Registers (req/resp)
  • FC17 - Read Server ID (req/resp)
  • FC23 - Read / Write Multiple Registers (req/resp)

Goals

  • Packets separate from Client implementation
  • Client (TCP/UDP +RTU) separated from Modbus packets
  • Convenience methods to convert register data to/from different data types (with endianess/word order)
  • Builders to group multiple fields into request batches
  • Poller to request batches request and parse response to field values with long-running process.

Examples

Higher level API allows you to compose register requests out of arbitrary number of fields and extract those field values from response registers with convenience methods

Addresses without scheme (i.e. localhost:5020) are considered as TCP addresses. For UDP unicast use udp://localhost:5020.

b := modbus.NewRequestBuilder("tcp://localhost:5020", 1)

requests, _ := b.
    AddField(modbus.Field{Name: "test_do", Type: modbus.FieldTypeUint16, Address: 18}).
    AddField(modbus.Field{Name: "alarm_do_1", Type: modbus.FieldTypeInt64, Address: 19}).
    ReadHoldingRegistersTCP() // split added fields into multiple requests with suitable quantity size

client := modbus.NewTCPClient()
if err := client.Connect(context.Background(), "tcp://localhost:5020"); err != nil {
    return err
}
for _, req := range requests {
    resp, err := client.Do(context.Background(), req)
    if err != nil {
        return err
    }
    // extract response as packet.Registers instance to have access to convenience methods to 
    // extracting registers as different data types
    registers, _ := resp.(*packet.ReadHoldingRegistersResponseTCP).AsRegisters(req.StartAddress)
    alarmDo1, _ := registers.Int64(19)
    fmt.Printf("int64 @ address 19: %v", alarmDo1)
    
    // or extract values to FieldValue struct
    fields, _ := req.ExtractFields(resp, true)
    assert.Equal(t, uint16(1), fields[0].Value)
    assert.Equal(t, "alarm_do_1", fields[1].Field.Name)
}
Polling values with long-running process

See simple poller implementation cmd/modbus-poller/main.go.

func main() {
	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
	defer cancel()

	b := modbus.NewRequestBuilderWithConfig(modbus.BuilderDefaults{
		ServerAddress: "tcp://127.0.0.1:5022?invalid_addr=1000,12000-12100&read_timeout=2s",
		FunctionCode:  packet.FunctionReadHoldingRegisters, // fc3
		UnitID:        1,
		Protocol:      modbus.ProtocolTCP,
		Interval:      modbus.Duration(1 * time.Second), // send request every 1 second
	})

	batches, _ := b.
		AddField(modbus.Field{Name: "test_do", Type: modbus.FieldTypeUint16, Address: 18}).
		AddField(modbus.Field{Name: "alarm_do_1", Type: modbus.FieldTypeInt64, Address: 19}).
		Split() // split added fields into multiple requests with suitable quantity size

	p := poller.NewPoller(batches)

	go func() {
		for {
			select {
			case result := <-p.ResultChan:
				slog.Info("polled values", "values", result)
			case <-ctx.Done():
				return
			}
		}
	}()

	if err := p.Poll(ctx); err != nil {
		slog.Error("polling ended with failure", "err", err)
		return
	}
}
RTU over serial port

RTU examples to interact with serial port can be found from serial.md

Addresses without scheme (i.e. localhost:5020) are considered as TCP addresses. For UDP unicast use udp://localhost:5020.

Low level packets
client := modbus.NewTCPClientWithConfig(modbus.ClientConfig{
    WriteTimeout: 2 * time.Second,
    ReadTimeout:  2 * time.Second,
})
if err := client.Connect(context.Background(), "localhost:5020"); err != nil {
    return err
}
defer client.Close()
startAddress := uint16(10)
req, err := packet.NewReadHoldingRegistersRequestTCP(0, startAddress, 9)
if err != nil {
    return err
}

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.Do(ctx, req)
if err != nil {
    return err
}

registers, err := resp.(*packet.ReadHoldingRegistersResponseTCP).AsRegisters(startAddress)
if err != nil {
    return err
}
uint32Var, err := registers.Uint32(17) // extract uint32 value from register 17

To create single TCP packet use following methods. Use RTU suffix to create RTU packets.

import "github.com/aldas/go-modbus-client/packet"

req, err := packet.NewReadCoilsRequestTCP(0, 10, 9)
req, err := packet.NewReadDiscreteInputsRequestTCP(0, 10, 9)
req, err := packet.NewReadHoldingRegistersRequestTCP(0, 10, 9)
req, err := packet.NewReadInputRegistersRequestTCP(0, 10, 9)
req, err := packet.NewWriteSingleCoilRequestTCP(0, 10, true)
req, err := packet.NewWriteSingleRegisterRequestTCP(0, 10, []byte{0xCA, 0xFE})
req, err := packet.NewWriteMultipleCoilsRequestTCP(0, 10, []bool{true, false, true})
req, err := packet.NewReadServerIDRequestTCP(0)
req, err := packet.NewWriteMultipleRegistersRequestTCP(0, 10, []byte{0xCA, 0xFE, 0xBA, 0xBE})
Builder to group fields to packets
b := modbus.NewRequestBuilder("localhost:5020", 1)

requests, _ := b.
   AddField(modbus.Field{Name: "test_do", Type: modbus.FieldTypeUint16, Address: 18}).
   AddField(modbus.Field{Name: "alarm_do_1", Type: modbus.FieldTypeInt64, Address: 19}).
   ReadHoldingRegistersTCP() // split added fields into multiple requests with suitable quantity size

Changelog

See CHANGELOG.md

Tests

make check

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrClientNotConnected = &ClientError{Err: errors.New("client is not connected")}

ErrClientNotConnected is error indicating that Client has not yet connected to the modbus server

View Source
var ErrInvalidValue = errors.New("invalid value")

ErrInvalidValue is returned when extracted value for Field resulted invalid value (Field.Invalid).

View Source
var ErrPacketTooLong = &ClientError{Err: errors.New("received more bytes than valid Modbus packet size can be")}

ErrPacketTooLong is error indicating that modbus server sent amount of data that is bigger than any modbus packet could be

View Source
var ErrorFieldExtractHadError = errors.New("field extraction had an error. check FieldValue.Error for details")

ErrorFieldExtractHadError is returned when ExtractFields could not extract value from Field

Functions

func WithSerialHooks

func WithSerialHooks(hooks ClientHooks) func(c *SerialClient)

WithSerialHooks is option to set hooks for SerialClient

func WithSerialReadTimeout

func WithSerialReadTimeout(readTimeout time.Duration) func(c *SerialClient)

WithSerialReadTimeout is option to for setting total timeout for reading the whole packet

Types

type BField deprecated

type BField struct {
	Field
}

BField is distinct field be requested and extracted from response

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*BField) ByteOrder deprecated

func (f *BField) ByteOrder(byteOrder packet.ByteOrder) *BField

ByteOrder sets word and byte order for Field to be used when extracting values from response

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*BField) FunctionCode deprecated

func (f *BField) FunctionCode(functionCode uint8) *BField

FunctionCode sets FunctionCode for Field

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*BField) Name deprecated

func (f *BField) Name(name string) *BField

Name sets name/identifier for Field to be used to uniquely identify value when extracting values from response

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*BField) Protocol deprecated

func (f *BField) Protocol(protocol ProtocolType) *BField

Protocol sets Protocol for Field

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*BField) RequestInterval deprecated

func (f *BField) RequestInterval(requestInterval time.Duration) *BField

RequestInterval sets RequestInterval for Field

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*BField) ServerAddress deprecated

func (f *BField) ServerAddress(serverAddress string) *BField

ServerAddress sets modbus server address for Field. Usage `[network://]host:port`

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*BField) UnitID deprecated

func (f *BField) UnitID(unitID uint8) *BField

UnitID sets UnitID for Field

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

type Builder

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

Builder helps to group extractable field values of different types into modbus requests with minimal amount of separate requests produced

func NewRequestBuilder

func NewRequestBuilder(serverAddress string, unitID uint8) *Builder

NewRequestBuilder creates new instance of Builder

func NewRequestBuilderWithConfig

func NewRequestBuilderWithConfig(config BuilderDefaults) *Builder

NewRequestBuilderWithConfig creates new instance of Builder with given defaults. Arguments can be left empty and ServerAddress+UnitID provided for each field separately

func (*Builder) Add deprecated

func (b *Builder) Add(field *BField) *Builder

Add adds field into Builder

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) AddAll

func (b *Builder) AddAll(fields Fields) *Builder

AddAll adds field into Builder. AddAll does not set ServerAddress and UnitID values.

func (*Builder) AddField

func (b *Builder) AddField(field Field) *Builder

AddField adds field into Builder

func (*Builder) Bit deprecated

func (b *Builder) Bit(registerAddress uint16, bit uint8) *BField

Bit add bit (0-15) field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Byte deprecated

func (b *Builder) Byte(registerAddress uint16, fromHighByte bool) *BField

Byte add byte field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Bytes deprecated

func (b *Builder) Bytes(registerAddress uint16, byteLength uint8) *BField

Bytes add raw bytes field to Builder to be requested and extracted. byteLength is length in bytes (1 register is 2 bytes)

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Coil deprecated

func (b *Builder) Coil(address uint16) *BField

Coil adds discrete/coil field to Builder to be requested and extracted by FC1/FC2.

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Float32 deprecated

func (b *Builder) Float32(registerAddress uint16) *BField

Float32 add float32 field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Float64 deprecated

func (b *Builder) Float64(registerAddress uint16) *BField

Float64 add float64 field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Int16 deprecated

func (b *Builder) Int16(registerAddress uint16) *BField

Int16 add int16 field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Int32 deprecated

func (b *Builder) Int32(registerAddress uint16) *BField

Int32 add int32 field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Int64 deprecated

func (b *Builder) Int64(registerAddress uint16) *BField

Int64 add int64 field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Int8 deprecated

func (b *Builder) Int8(registerAddress uint16, fromHighByte bool) *BField

Int8 add int8 field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) ReadCoilsRTU

func (b *Builder) ReadCoilsRTU() ([]BuilderRequest, error)

ReadCoilsRTU combines fields into RTU Read Coils (FC1) requests

func (*Builder) ReadCoilsTCP

func (b *Builder) ReadCoilsTCP() ([]BuilderRequest, error)

ReadCoilsTCP combines fields into TCP Read Coils (FC1) requests

func (*Builder) ReadDiscreteInputsRTU

func (b *Builder) ReadDiscreteInputsRTU() ([]BuilderRequest, error)

ReadDiscreteInputsRTU combines fields into RTU Read Discrete Inputs (FC2) requests

func (*Builder) ReadDiscreteInputsTCP

func (b *Builder) ReadDiscreteInputsTCP() ([]BuilderRequest, error)

ReadDiscreteInputsTCP combines fields into TCP Read Discrete Inputs (FC2) requests

func (*Builder) ReadHoldingRegistersRTU

func (b *Builder) ReadHoldingRegistersRTU() ([]BuilderRequest, error)

ReadHoldingRegistersRTU combines fields into RTU Read Holding Registers (FC3) requests

func (*Builder) ReadHoldingRegistersTCP

func (b *Builder) ReadHoldingRegistersTCP() ([]BuilderRequest, error)

ReadHoldingRegistersTCP combines fields into TCP Read Holding Registers (FC3) requests

func (*Builder) ReadInputRegistersRTU

func (b *Builder) ReadInputRegistersRTU() ([]BuilderRequest, error)

ReadInputRegistersRTU combines fields into RTU Read Input Registers (FC4) requests

func (*Builder) ReadInputRegistersTCP

func (b *Builder) ReadInputRegistersTCP() ([]BuilderRequest, error)

ReadInputRegistersTCP combines fields into TCP Read Input Registers (FC4) requests

func (*Builder) Split

func (b *Builder) Split() ([]BuilderRequest, error)

Split combines fields into requests by their ServerAddress+FunctionCode+UnitID+Protocol+RequestInterval

func (*Builder) String deprecated

func (b *Builder) String(registerAddress uint16, length uint8) *BField

String add string field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Uint16 deprecated

func (b *Builder) Uint16(registerAddress uint16) *BField

Uint16 add uint16 field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Uint32 deprecated

func (b *Builder) Uint32(registerAddress uint16) *BField

Uint32 add uint32 field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Uint64 deprecated

func (b *Builder) Uint64(registerAddress uint16) *BField

Uint64 add uint64 field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

func (*Builder) Uint8 deprecated

func (b *Builder) Uint8(registerAddress uint16, fromHighByte bool) *BField

Uint8 add uint8 field to Builder to be requested and extracted

Deprecated: use `modbus.Field` struct and methods `Builder.AddAll(fields Fields)` and `Builder.AddField(field Field)`

type BuilderDefaults

type BuilderDefaults struct {
	// Should be formatted as url.URL scheme `[scheme:][//[userinfo@]host][/]path[?query]`
	// Example:
	// * `127.0.0.1:502` (library defaults to `tcp` as scheme)
	// * `udp://127.0.0.1:502`
	// * `/dev/ttyS0?BaudRate=4800`
	// * `file:///dev/ttyUSB?BaudRate=4800`
	ServerAddress string `json:"server_address" mapstructure:"server_address"`
	FunctionCode  uint8  `json:"function_code" mapstructure:"function_code"`
	// UnitID default value for added field. Note: if non zero is set as default it will overwrite zero on Field.
	UnitID   uint8        `json:"unit_id" mapstructure:"unit_id"`
	Protocol ProtocolType `json:"protocol" mapstructure:"protocol"`
	Interval Duration     `json:"interval" mapstructure:"interval"`
}

BuilderDefaults holds Builder default values for adding/creating Fields

type BuilderRequest

type BuilderRequest struct {
	packet.Request

	// ServerAddress is modbus server address where request should be sent
	ServerAddress string
	// UnitID is unit identifier of modbus slave device
	UnitID uint8
	// StartAddress is start register address for request
	StartAddress uint16
	// Quantity is amount of registers/coils to return with request
	Quantity uint16

	Protocol        ProtocolType
	RequestInterval time.Duration

	// Fields is slice of field use to construct the request and to be extracted from response
	Fields Fields
}

BuilderRequest helps to connect requested fields to responses

func (BuilderRequest) AsRegisters

func (r BuilderRequest) AsRegisters(response RegistersResponse) (*packet.Registers, error)

AsRegisters returns response data as Register to more convenient access

func (BuilderRequest) ExtractFields

func (r BuilderRequest) ExtractFields(response packet.Response, continueOnExtractionErrors bool) ([]FieldValue, error)

ExtractFields extracts Field values from given response. When continueOnExtractionErrors is true and error occurs during extraction, this method does not end but continues to extract all Fields and returns ErrorFieldExtractHadError at the end. To distinguish errors check FieldValue.Error field.

type Client

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

Client provides mechanisms to send requests to modbus server over network connection

func NewClient

func NewClient(conf ClientConfig) *Client

NewClient creates new instance of Modbus Client with given configuration options

func NewRTUClient

func NewRTUClient() *Client

NewRTUClient creates new instance of Modbus Client for Modbus RTU protocol

func NewRTUClientWithConfig

func NewRTUClientWithConfig(conf ClientConfig) *Client

NewRTUClientWithConfig creates new instance of Modbus Client for Modbus RTU protocol with given configuration options

func NewTCPClient

func NewTCPClient() *Client

NewTCPClient creates new instance of Modbus Client for Modbus TCP protocol

func NewTCPClientWithConfig

func NewTCPClientWithConfig(conf ClientConfig) *Client

NewTCPClientWithConfig creates new instance of Modbus Client for Modbus TCP protocol with given configuration options

func (*Client) Close

func (c *Client) Close() error

Close closes network connection to Modbus server

func (*Client) Connect

func (c *Client) Connect(ctx context.Context, address string) error

Connect opens network connection to Client to server. Context lifetime is only meant for this call. ctx is to be used for to cancel connection attempt.

`address` should be formatted as url.URL scheme `[scheme:][//[userinfo@]host][/]path[?query]` Example: * `127.0.0.1:502` (library defaults to `tcp` as scheme) * `udp://127.0.0.1:502` * `/dev/ttyS0?BaudRate=4800` * `file:///dev/ttyUSB?BaudRate=4800`

func (*Client) Do

func (c *Client) Do(ctx context.Context, req packet.Request) (packet.Response, error)

Do sends given Modbus request to modbus server and returns parsed Response. ctx is to be used for to cancel connection attempt. On modbus exception nil is returned as response and error wraps value of type packet.ErrorResponseTCP or packet.ErrorResponseRTU User errors.Is and errors.As to check if error wraps packet.ErrorResponseTCP or packet.ErrorResponseRTU

type ClientConfig

type ClientConfig struct {
	// WriteTimeout is total amount of time writing the request can take after client returns error
	WriteTimeout time.Duration
	// ReadTimeout is total amount of time reading the response can take before client returns error
	ReadTimeout time.Duration

	DialContextFunc     func(ctx context.Context, address string) (net.Conn, error)
	AsProtocolErrorFunc func(data []byte) error
	ParseResponseFunc   func(data []byte) (packet.Response, error)

	Hooks ClientHooks
}

ClientConfig is configuration for Client

type ClientError

type ClientError struct {
	Err error
}

ClientError indicates errors returned by Client that network related and are possibly retryable

func (*ClientError) Error

func (e *ClientError) Error() string

Error returns contained error message

func (*ClientError) Unwrap

func (e *ClientError) Unwrap() error

Unwrap allows unwrapping errors with errors.Is and errors.As

type ClientHooks

type ClientHooks interface {
	BeforeWrite(toWrite []byte)
	AfterEachRead(received []byte, n int, err error)
	BeforeParse(received []byte)
}

ClientHooks allows to log bytes send/received by client. NB: Do not modify given slice - it is not a copy.

type CoilsResponse

type CoilsResponse interface {
	packet.Response
	IsCoilSet(startAddress uint16, coilAddress uint16) (bool, error)
}

CoilsResponse is marker interface for responses returning coil/discrete data

type Duration

type Duration time.Duration

A Duration represents the elapsed time between two instants. This library type extends time.Duration with JSON marshalling and unmarshalling support to/from string. i.e. "1s" unmarshalled to `1 * time.Second`

func (Duration) MarshalJSON

func (d Duration) MarshalJSON() ([]byte, error)

MarshalJSON converts Duration to JSON bytes

func (*Duration) UnmarshalJSON

func (d *Duration) UnmarshalJSON(raw []byte) error

UnmarshalJSON converts raw bytes from JSON to Duration

type Field

type Field struct {
	Name string `json:"name" mapstructure:"name"`

	// ServerAddress is Modbus server location as URL.
	// URL: `scheme://host:port` or file `/dev/ttyS0?BaudRate=4800`
	// Query parameters are used to pass information about network connection or how to split
	// fields into request batches.
	//
	// Splitter logic know following query parameters:
	// - `max_quantity_per_request` maximum quantity (uint16) that request can have. How many
	//		registers/coils requests will be limited to.
	// - `invalid_addr=70,100-120` - addresses or address range that splitter will avoid to include in request
	//		when creating batches.
	ServerAddress   string       `json:"server_address" mapstructure:"server_address"`
	FunctionCode    uint8        `json:"function_code" mapstructure:"function_code"`
	UnitID          uint8        `json:"unit_id" mapstructure:"unit_id"`
	Protocol        ProtocolType `json:"protocol" mapstructure:"protocol"`
	RequestInterval Duration     `json:"request_interval" mapstructure:"request_interval"`

	// Address of the register (first register of that data type) or discrete/coil address in modbus.
	// Addresses are 0-based.
	Address uint16    `json:"address" mapstructure:"address"`
	Type    FieldType `json:"type" mapstructure:"type"`

	// Only relevant to register function fields
	Bit uint8 `json:"bit" mapstructure:"bit"`

	// FromHighByte is for single byte data types stored in single register (e.g. byte,uint8,int8)
	//
	// In Modbus (which uses big-endian format), the most significant byte is
	// sent first and is therefore considered the 0th byte. The least significant byte
	// is sent second and is considered the 1st byte.
	//
	// Modbus register with value `0x1234`.
	//  - 0x12 is High Byte, 0th byte
	//  - 0x34 is Low byte, is the 1st byte
	FromHighByte bool `json:"from_high_byte" mapstructure:"from_high_byte"`

	// Length is length of string and raw bytes data types.
	Length uint8 `json:"length" mapstructure:"length"`

	ByteOrder packet.ByteOrder `json:"byte_order" mapstructure:"byte_order"`

	// Invalid that represents not existent value in modbus. Given value (presented in hex) when encountered is converted to ErrInvalidValue error.
	// for example your energy meter ac power is uint32 value of which `0xffffffff` should be treated as error/invalid value.
	//
	// Usually invalid value is largest unsigned or smallest signed value per data type. Example:
	// - uint8 	= 0xff (255)
	// - int8 	= 0x80 (-127)
	// - uint16 = 0xff, 0xff (65535)
	// - int16	= 0x80, 0x00 (-32768)
	// - uint32 = 0xff, 0xff, 0xff, 0xff (4294967295)
	// - int32	= 0x80, 0x0, 0x0, 0x0 (-2147483648)
	// - uint64 = 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff (18446744073709551615)
	// - int64	= 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 (-9223372036854775808)
	// - float32 is same as uint32
	// - float64 is same as uint64
	// - bit, boolean - can not have invalid values
	Invalid Invalid `json:"invalid,omitempty" mapstructure:"invalid"`
}

Field is distinct field be requested and extracted from response Tag `mapstructure` allows you to marshal https://github.com/spf13/viper supported configuration format to the Field

func (*Field) CheckInvalid

func (f *Field) CheckInvalid(registers *packet.Registers) error

CheckInvalid compares Invalid value to bytes in fields registers. When raw data in response equal to Invalid the ErrInvalidValue error is returned. Nil return value means no problems occurred.

func (*Field) ExtractFrom

func (f *Field) ExtractFrom(registers *packet.Registers) (interface{}, error)

ExtractFrom extracts field value from given registers data

func (*Field) MarshalBytes

func (f *Field) MarshalBytes(value any) ([]byte, error)

MarshalBytes converts given value to suitable Modbus bytes (in big endian) that can be used as request data. This method will truncate/limit value to max/min value of field being marshalled. In case field type is Float32 or Float64 providing bigger int (e.g. uint64) conversion will have loss of precision

Accepted types for given value are: - bool - uint8,int8,byte - uint16,int16 - uint32,int32 - uint64,int64 - float32,float64 - string (Note: raw utf8 bytes. If you need ASCII, convert the string before) - []byte

func (*Field) Validate

func (f *Field) Validate() error

Validate checks if Field is values are correctly filled

type FieldType

type FieldType uint8

FieldType is enum type for data types that Field can represent

const (
	// FieldTypeBit represents single bit out 16 bit register. Use `Field.Bit` (0-15) to indicate which bit is meant.
	FieldTypeBit FieldType = 1
	// FieldTypeByte represents single byte of 16 bit, 2 byte, single register. Use `Field.FromHighByte` to indicate is high or low byte is meant.
	FieldTypeByte FieldType = 2
	// FieldTypeUint8 represents uint8 value of 2 byte, single register. Use `Field.FromHighByte` to indicate is high or low byte value is meant.
	FieldTypeUint8 FieldType = 3
	// FieldTypeInt8 represents int8 value of 2 byte, single register. Use `Field.FromHighByte` to indicate is high or low byte value is meant.
	FieldTypeInt8 FieldType = 4
	// FieldTypeUint16 represents single register (16 bit) as uint16 value
	FieldTypeUint16 FieldType = 5
	// FieldTypeInt16 represents single register (16 bit) as int16 value
	FieldTypeInt16 FieldType = 6
	// FieldTypeUint32 represents 2 registers (32 bit) as uint32 value. Use `Field.ByteOrder` to indicate byte and word order of register data.
	FieldTypeUint32 FieldType = 7
	// FieldTypeInt32 represents 2 registers (32 bit) as int32 value. Use `Field.ByteOrder` to indicate byte and word order of register data.
	FieldTypeInt32 FieldType = 8
	// FieldTypeUint64 represents 4 registers (64 bit) as uint64 value. Use `Field.ByteOrder` to indicate byte and word order of register data.
	FieldTypeUint64 FieldType = 9
	// FieldTypeInt64 represents 4 registers (64 bit) as int64 value. Use `Field.ByteOrder` to indicate byte and word order of register data.
	FieldTypeInt64 FieldType = 10
	// FieldTypeFloat32 represents 2 registers (32 bit) as float32 value. Use `Field.ByteOrder` to indicate byte and word order of register data.
	FieldTypeFloat32 FieldType = 11
	// FieldTypeFloat64 represents 4 registers (64 bit) as float64 value. Use `Field.ByteOrder` to indicate byte and word order of register data.
	FieldTypeFloat64 FieldType = 12
	// FieldTypeString represents N registers as string value. Use `Field.Length` to length of string.
	FieldTypeString FieldType = 13

	// FieldTypeCoil represents single discrete/coil value (used by FC1/FC2).
	FieldTypeCoil FieldType = 14
	// FieldTypeRawBytes represents N registers contents as byte slice.
	FieldTypeRawBytes FieldType = 15
)

func ParseFieldType

func ParseFieldType(raw string) (FieldType, error)

ParseFieldType parses given string to FieldType

func (*FieldType) UnmarshalJSON

func (ft *FieldType) UnmarshalJSON(raw []byte) error

UnmarshalJSON converts raw bytes from JSON to FieldType

type FieldValue

type FieldValue struct {
	Field Field

	// Value contains extracted value
	// possible types:
	// * bool
	// * byte
	// * []byte
	// * uint[8/16/32/64]
	// * int[8/16/32/64]
	// * float[32/64]
	// * string
	Value any

	// Error contains error that occurred during extracting field from response.
	// In case Field.Invalid was set and response data contained it the Error is set to modbus.ErrInvalidValue
	Error error
}

FieldValue is concrete value extracted from register data using field data type and byte order

type Fields

type Fields []Field

Fields is slice of Field instances

type Flusher

type Flusher interface {
	Flush() error
}

Flusher is interface for flushing unread/unwritten data from serial port buffer

type Invalid

type Invalid []byte

Invalid that represents not existent value in modbus. Given value (presented in hex) when encountered is converted to ErrInvalidValue error. for example your energy meter ac power is uint32 value of which `0xffffffff` should be treated as error/invalid value.

func (Invalid) MarshalJSON

func (i Invalid) MarshalJSON() ([]byte, error)

MarshalJSON converts Invalid to JSON bytes

func (*Invalid) UnmarshalJSON

func (i *Invalid) UnmarshalJSON(b []byte) error

UnmarshalJSON converts raw bytes from JSON to Invalid

type ProtocolType

type ProtocolType uint8

ProtocolType represents which Modbus encoding is being used.

const (

	// ProtocolTCP represents Modbus TCP encoding which could be transferred over TCP/UDP connection.
	ProtocolTCP ProtocolType = 1
	// ProtocolRTU represents Modbus RTU encoding with a Cyclic-Redundant Checksum. Could be transferred
	// over TCP/UDP and serial connection.
	ProtocolRTU ProtocolType = 2
)

func (*ProtocolType) UnmarshalJSON

func (pt *ProtocolType) UnmarshalJSON(raw []byte) error

UnmarshalJSON converts raw bytes from JSON to Invalid

type RegistersResponse

type RegistersResponse interface {
	packet.Response
	AsRegisters(requestStartAddress uint16) (*packet.Registers, error)
}

RegistersResponse is marker interface for responses returning register data

type SerialClient

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

SerialClient provides mechanisms to send requests to modbus server over serial port

func NewSerialClient

func NewSerialClient(serialPort io.ReadWriteCloser, opts ...SerialClientOptionFunc) *SerialClient

NewSerialClient creates new instance of Modbus SerialClient for Modbus RTU protocol

func (*SerialClient) Close

func (c *SerialClient) Close() error

Close closes serial connection to the device

func (*SerialClient) Do

Do sends given Modbus request to modbus server and returns parsed Response. ctx is to be used for to cancel connection attempt. On modbus exception nil is returned as response and error wraps value of type packet.ErrorResponseRTU User errors.Is and errors.As to check if error wraps packet.ErrorResponseRTU

type SerialClientOptionFunc

type SerialClientOptionFunc func(c *SerialClient)

SerialClientOptionFunc is options type for NewSerialClient function

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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