simonvetter/modbus

repository·master·Indexed 19 days ago

https://github.com/simonvetter/modbus

A high-level Go implementation of the Modbus protocol providing both client and server components. It supports multiple transport layers including TCP, RTU (Serial), UDP, and TLS. The library allows interaction with Modbus devices using native Go types for reading and writing coils, discrete inputs, and registers (uint16, uint32, uint64, float32, float64). It includes a modbus-cli tool for probing, troubleshooting, and running automated sequences of Modbus operations.

Tokens
5.9K
Snippets
12
Records
27
Agent score
65%

What's inside simonvetter-modbus

  1. How the Modbus server component works

    master

    The library includes a server component that supports:

    • Modbus TCP (MBAP)
    • Modbus TCP over TLS (MBAPS / Modbus Security)

    Refer to examples/tcp_server.go and examples/tls_server.go for implementation details.

  2. Understand Modbus Request objects

    master

    When the RequestHandler methods are called, they receive request objects containing metadata about the client and the specific Modbus operation requested.

    Common fields across all request types:

    • ClientAddr: The source IP address of the client.
    • ClientRole: The client role extracted from the certificate (only available in tcp+tls mode).
    • UnitId: The requested Unit ID (Slave ID).
    • Addr: The base address requested.
    • Quantity: The number of consecutive items (coils/registers) requested.
  3. Configure custom logging for Modbus

    master

    The modbus package uses an internal logger type to handle diagnostic messages. While the logger type itself is unexported, you can control where logs are sent by providing a custom *log.Logger instance during the initialization of Modbus components (such as the Client or Server) that accept a logger parameter.

    If no custom logger is provided, the package defaults to writing logs directly to os.Stdout.

    Supported log levels:

    • Info / Infof: Informational messages.
    • Warning / Warningf: Warning messages.
    • Error / Errorf: Error messages.
    • Fatal / Fatalf: Error messages followed by an immediate os.Exit(1).
  4. Configure and start a Modbus server

    master

    Create a ModbusServer using NewServer by providing a ServerConfiguration and an implementation of the RequestHandler interface.

    Supported URL schemes:

    • tcp://[host]:[port]: Standard TCP Modbus.
    • tcp+tls://[host]:[port]: Modbus over TLS (requires TLSServerCert and TLSClientCAs to be set in the configuration).

    Once initialized, call .Start() to begin listening for connections and .Stop() to shut down the server and close active sessions.

    conf := &modbus.ServerConfiguration{
        URL:        "tcp://[::]:502",
        MaxClients: 10,
        Timeout:    120 * time.Second,
    }
    
    handler := &MyHandler{}
    server, err := modbus.NewServer(conf, handler)
    if err != nil {
        log.Fatal(err)
    }
    
    err = server.Start()
    if err != nil {
        log.Fatal(err)
    }
    
    // ... run server ...
    
    server.Stop()
  5. Use the modbus-cli tool

    master

    The modbus-cli is a command-line interface for interacting with Modbus devices, useful for probing, troubleshooting, and running automated sequences of Modbus operations. It supports various transports including Modbus RTU (serial), RTU over TCP/UDP, Modbus TCP, Modbus TCP over TLS, and Modbus TCP over UDP.

    Commands are provided as trailing arguments after the configuration flags. You can chain multiple commands together to create a sequence of operations.

    # Example: Read 6 holding registers at address 0x100 then set the coil at address 12 to true
    modbus-cli --target=tcp://somehost:502 --timeout=3s rh:uint16:0x100+5 wc:12:true
  6. Read data from Modbus registers

    master

    The client provides high-level methods to read various data types from registers. Use modbus.HOLDING_REGISTER or modbus.INPUT_REGISTER to specify the register type.

    Available read methods:

    • ReadRegister(address, regType): Returns a uint16.
    • ReadRegisters(address, count, regType): Returns a []uint16.
    • ReadUint32s(address, count, regType): Returns a []uint32.
    • ReadUint64(address, regType): Returns a uint64.
    • ReadBytes(address, count, regType): Returns a []byte.
    • ReadFloat32s(address, count, regType): Returns a []float32.
    // Read a single 16-bit holding register at address 100
    reg16, err := client.ReadRegister(100, modbus.HOLDING_REGISTER)
    
    // Read 4 consecutive 16-bit input registers starting at address 100
    reg16s, err := client.ReadRegisters(100, 4, modbus.INPUT_REGISTER)
    
    // Read 4 consecutive 16-bit input registers as 2 32-bit integers
    reg32s, err := client.ReadUint32s(100, 2, modbus.INPUT_REGISTER)
    
    // Read 4 consecutive 16-bit input registers as a single 64-bit integer
    reg64, err := client.ReadUint64(100, modbus.INPUT_REGISTER)
    
    // Read 4 consecutive 16-bit input registers as a slice of bytes
    regBs, err := client.ReadBytes(100, 8, modbus.INPUT_REGISTER)
    
    // Read 4 consecutive 16-bit input registers as 2 32-bit floats
    fl32s, err := client.ReadFloat32s(100, 2, modbus.INPUT_REGISTER)
  7. Configure and use a Modbus client

    master

    The modbus.NewClient function creates a client using a *modbus.ClientConfiguration. You must call client.Open() to establish the connection. You can call Open() multiple times on the same client instance if the first attempt fails.

    Supported transport schemes via the URL field:

    • tcp://hostname:port: Modbus TCP (MBAP)
    • udp://hostname:port: Modbus TCP over UDP
    • rtu:///dev/ttyUSB0: Modbus RTU (Serial)
    • rtuovertcp://hostname:port: RTU tunneled in TCP
    • rtuoverudp://hostname:port: RTU tunneled in UDP
    • tls://...: Modbus TCP over TLS (see examples/tls_client.go for details)
    import (
        "github.com/simonvetter/modbus"
        "time"
    )
    
    func main() {
        // Example: TCP Client
        client, err := modbus.NewClient(&modbus.ClientConfiguration{
            URL:      "tcp://hostname-or-ip-address:502",
            Timeout:  1 * time.Second,
        })
    
        // Example: RTU (Serial) Client
        client, err = modbus.NewClient(&modbus.ClientConfiguration{
            URL:      "rtu:///dev/ttyUSB0",
            Speed:    19200,
            DataBits: 8,
            Parity:   modbus.PARITY_NONE,
            StopBits: 2,
            Timeout:  300 * time.Millisecond,
        })
    
        if err != nil {
            // handle error
        }
    
        err = client.Open()
        if err != nil {
            // handle error
        }
        defer client.Close()
    }
  8. Write data to Modbus registers

    master

    Use the following methods to write data to the device:

    • WriteRegister(address, value): Writes a single uint16 value.
    • WriteFloat32s(address, values): Writes a slice of float32 values.
    • WriteBytes(address, data): Writes a slice of []byte.

    To change the target slave/unit ID, use client.SetUnitId(id).

    // Write -200 to 16-bit (holding) register 100, as a signed integer
    var s int16 = -200
    err := client.WriteRegister(100, uint16(s))
    
    // Switch to unit ID (a.k.a. slave ID) #4
    client.SetUnitId(4)
    
    // Write 3 floats to registers 100 to 105
    err = client.WriteFloat32s(100, []float32{3.14, 1.1, -783.22})
    
    // Write 8 bytes (4 consecutive modbus registers) to registers 10 through 13
    err = client.WriteBytes(10, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08})
  9. Configure byte encoding and endianness

    master

    By default, 16-bit integers are decoded as big-endian, and 32/64-bit values are big-endian with the high word first. You can change this behavior using client.SetEncoding(endianness, wordOrder).

    Supported constants:

    • modbus.LITTLE_ENDIAN / modbus.BIG_ENDIAN (for byte slices and 16-bit integers)
    • modbus.LOW_WORD_FIRST / modbus.HIGH_WORD_FIRST (affects 32/64-bit values)
    // Change the byte/word ordering to little endian, with the low word first
    client.SetEncoding(modbus.LITTLE_ENDIAN, modbus.LOW_WORD_FIRST)