Modbus client (TCP/RTU) over TCP/UDP/Serial for Golang.
- Modbus TCP/IP specification: http://www.modbus.org/specs.php
- Modbus TCP/IP and RTU simpler description: http://www.simplymodbus.ca/TCP.htm
For questions use Github Discussions
go get github.com/aldas/go-modbus-client
- 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)
- 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
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.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(), "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(18)
fmt.Printf("int64 @ address 18: %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)
}
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
.
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})
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
See CHANGELOG.md
make check