Skip to content

aldas/go-modbus-client

Repository files navigation

Modbus TCP and RTU protocol client

License GoDoc Go Report Card Codecov

Modbus client (TCP/RTU) over TCP/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/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

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

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

requests, _ := b.Add(b.Uint16(18).UnitID(0).Name("test_do")).
    Add(b.Int64(18).Name("alarm_do_1").UnitID(0)).
    ReadHoldingRegistersTCP() // split added fields into multiple requests with suitable quantity size

client := modbus.NewTCPClient()
if err := client.Connect(context.Background(), "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(18)
    fmt.Printf("int64 @ address 18: %v", alarmDo1)
    
    // or extract values to FieldValue struct
    fields, _ := req.ExtractFields(resp.(modbus.RegistersResponse), true)
    assert.Equal(t, uint16(1), fields[0].Value)
    assert.Equal(t, "alarm_do_1", fields[1].Field.Name)
}

RTU over serial port

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

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.Add(b.Int64(18).UnitID(0).Name("test_do")).
   Add(b.Int64(18).Name("alarm_do_1").UnitID(0)).
   ReadHoldingRegistersTCP() // split added fields into multiple requests with suitable quantity size

Changelog

See CHANGELOG.md

Tests

make check