modbus

package module
v0.0.0-...-0f56c99 Latest Latest
Warning

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

Go to latest
Published: May 9, 2026 License: MIT Imports: 4 Imported by: 0

README

sup/modbus

Go Reference Test License

sup/modbus is a high-reliability Modbus client implementation for Go, built on top of the sup actor library. It provides a thread-safe, supervised, and observable way to interact with Modbus devices over TCP, RTU, or ASCII.

Why this exists?

Modbus is a single-threaded protocol by nature. If two different parts of your application try to access the same Serial or TCP port simultaneously, you get collisions, corrupted data, or "resource busy" errors.

This library solves that by treating the Modbus connection as an Actor. All requests are queued in a mailbox and processed sequentially by the actor loop, ensuring hardware access is perfectly serialized.

Features

  • Actor-Based Concurrency: Thread-safe access to hardware. Multiple goroutines can call the actor safely; the actor handles the queue.
  • Supervised Lifecycle: Designed to run under a sup.Supervisor. If the connection drops (EOF/Timeout), the actor returns a fatal error, allowing the supervisor to handle reconnection.
  • Protocol Support: Supports Modbus TCP, RTU (RS485/RS232), and ASCII.
  • Industrial Timing: Support for RS485 RTS delays and custom inter-frame "quiet time" requirements.

Quick start

package main

import (
    "context"
    "fmt"
    "time"

		"github.com/webermarci/sup"
    "github.com/webermarci/sup-modbus"
)

func main() {
	actor := modbus.NewActor("actor",
		modbus.TCP, 
		"192.168.1.50:502", 
		1, // Slave ID
		modbus.WithTimeout(2 * time.Second),
	)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
    
	supervisor := sup.NewSupervisor("root",
		sup.WithActor(actor),
		sup.WithPolicy(sup.Transient),
		sup.WithRestartDelay(time.Second),
		sup.WithRestartLimit(5, 10 * time.Second),
	)
    
	supervisor.Run(ctx)

	res, err := client.ReadHoldingRegisters(100, 2)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Register Data: %X\n", res)
}

Reactive Bus Integration

sup-modbus works seamlessly with the sup/bus package. This allows you to turn Modbus registers into high-level Signals, Mirrors, and Triggers.

Polling Registers (Signals)

Use bus.Signal to poll a register at a specific interval. The signal will notify subscribers only when the value actually changes.

// Create a raw byte signal from the Modbus actor
temperatureRaw := bus.NewSignal("signal", func() ([]byte, error) {
	return actor.ReadHoldingRegisters(100, 1)
}).WithInterval(1 * time.Second)

// Use a Mirror to decode the bytes into a float64
temperature := bus.NewMirror(func() float64 {
	data := temperatureRaw.Read()
	if len(data) < 2 { return 0 }
	return float64(binary.BigEndian.Uint16(data)) / 10.0
})
Writing to Coils (Triggers)

Use bus.Trigger to create a write-only command path for your hardware.

// Create a trigger for a fan relay
fanSwitch := bus.NewTrigger("trigger", func(on bool) error {
	val := uint16(0x0000)
	if on { 
		val = 0xFF00 
	}
	_, err := actor.WriteSingleCoil(5, val)
	return err
})

// Writing is now decoupled from Modbus specifics
fanSwitch.Write(true)
Automated Logic

By combining these, you can create reactive control loops that are completely decoupled from the Modbus protocol logic.

// Simple logic using a Mirror
isTooHot := bus.NewMirror(func() bool {
	return temperature.Read() > 28.5
})

// Bridge the signal to the trigger
go func() {
	for range tempRaw.Subscribe(ctx) {
		if isTooHot.Read() {
			fanSwitch.Write(true)
    }
	}
}()

Using with a Supervisor

The modbus.Actor implements the sup.Actor interface. It should be managed by a supervisor to handle connection drops or hardware timeouts.

supervisor := sup.NewSupervisor("root",
	sup.WithActors(actor, tempSignal), // Signals are also actors!
	sup.WithPolicy(sup.Transient),
	sup.WithRestartDelay(time.Second),
)

supervisor.Run(ctx)

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Actor

type Actor struct {
	*sup.BaseActor
	// contains filtered or unexported fields
}

Actor is an actor that handles Modbus communication using the specified protocol and configuration.

It processes Modbus requests sequentially and can be configured with various options such as mailbox size, timeouts, serial settings, and an optional observer for monitoring requests and responses.

func NewActor

func NewActor(name string, protocol ModbusProtocol, address string, slaveId byte, opts ...ActorOption) *Actor

NewActor creates a new Actor with the specified protocol, address, slave ID, and optional configuration options.

func (*Actor) MaskWriteRegister

func (a *Actor) MaskWriteRegister(address, andMask, orMask uint16) ([]byte, error)

func (*Actor) ReadCoils

func (a *Actor) ReadCoils(address, quantity uint16) ([]byte, error)

func (*Actor) ReadDiscreteInputs

func (a *Actor) ReadDiscreteInputs(address, quantity uint16) ([]byte, error)

func (*Actor) ReadFIFOQueue

func (a *Actor) ReadFIFOQueue(address uint16) ([]byte, error)

func (*Actor) ReadHoldingRegisters

func (a *Actor) ReadHoldingRegisters(address, quantity uint16) ([]byte, error)

func (*Actor) ReadInputRegisters

func (a *Actor) ReadInputRegisters(address, quantity uint16) ([]byte, error)

func (*Actor) ReadWriteMultipleRegisters

func (a *Actor) ReadWriteMultipleRegisters(readAddress, readQuantity, writeAddress, writeQuantity uint16, value []byte) ([]byte, error)

func (*Actor) Run

func (a *Actor) Run(ctx context.Context) error

Run starts the Actor and processes incoming requests. It establishes a connection to the Modbus device based on the configured protocol and handles requests sequentially. The actor will continue running until the context is canceled or an unrecoverable error occurs.

func (*Actor) WriteMultipleCoils

func (a *Actor) WriteMultipleCoils(address, quantity uint16, value []byte) ([]byte, error)

func (*Actor) WriteMultipleRegisters

func (a *Actor) WriteMultipleRegisters(address, quantity uint16, value []byte) ([]byte, error)

func (*Actor) WriteSingleCoil

func (a *Actor) WriteSingleCoil(address, value uint16) ([]byte, error)

func (*Actor) WriteSingleRegister

func (a *Actor) WriteSingleRegister(address, value uint16) ([]byte, error)

type ActorOption

type ActorOption func(*Actor)

ActorOption defines a function type for configuring the Actor. It allows for flexible configuration of the actor's behavior and settings when creating a new instance.

func WithInboxSize

func WithInboxSize(size int) ActorOption

WithInboxSize sets the size of the actor's inbox. A larger inbox allows for more concurrent requests to be queued, but may increase memory usage. The default mailbox size is 64.

func WithOnRequest

func WithOnRequest(handler func(functionCode byte, slaveId byte, address uint16, quantity uint16)) ActorOption

WithOnRequest allows the caller to provide a callback function that will be invoked before a Modbus request is executed. This can be used for logging, metrics collection, or other side effects related to outgoing Modbus requests.

func WithOnResponse

func WithOnResponse(handler func(functionCode byte, slaveId byte, res []byte, err error, duration time.Duration)) ActorOption

WithOnResponse allows the caller to provide a callback function that will be invoked after a Modbus response is received. This can be used for logging, metrics collection, or other side effects related to incoming Modbus responses, including any errors that may have occurred.

func WithOnStart

func WithOnStart(handler func(protocol ModbusProtocol, address string, slaveId byte)) ActorOption

WithOnStart allows the caller to provide a callback function that will be invoked when the Actor starts and establishes a connection to the Modbus device. This can be used for logging, metrics, or other side effects related to the actor's startup.

func WithRS485Config

func WithRS485Config(enabled bool, delayRts time.Duration, delayCustom time.Duration) ActorOption

WithRS485Config configures RS485 settings for RTU protocol. If enabled, it allows setting delays before and after sending data to accommodate RS485 transceiver timing requirements.

func WithSerialConfig

func WithSerialConfig(baud int, dataBits int, stopBits int, parity string) ActorOption

WithSerialConfig configures serial communication settings for RTU and ASCII protocols. It allows setting the baud rate, data bits, stop bits, and parity. These settings are essential for establishing a proper serial connection with Modbus devices.

func WithTimeout

func WithTimeout(timeout time.Duration) ActorOption

WithTimeout sets the timeout duration for Modbus requests. This timeout applies to all requests made by the Actor and determines how long it will wait for a response before considering the request failed.

type ModbusProtocol

type ModbusProtocol int

ModbusProtocol defines the type of Modbus protocol to use (TCP, RTU, ASCII).

const (
	TCP ModbusProtocol = iota
	RTU
	ASCII
)

Jump to

Keyboard shortcuts

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