mbserver

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 19, 2026 License: MIT Imports: 11 Imported by: 0

README

mbserver

Go Version Go Reference

一个用 Go 语言实现的 Modbus 服务器(从站),支持 TCP 和 RTU(串行)协议。

功能特性

  • 完整的 Modbus 协议支持(功能码 1、2、3、4、5、6、15、16)
  • 支持 TCP、TLS 和 RTU(串行)传输层
  • 可自定义的内存寄存器(线圈、离散输入、保持寄存器、输入寄存器)
  • 可扩展的函数处理器(支持自定义功能码)
  • 线程安全,并发处理请求
  • 优雅关闭

安装

go get github.com/novawatcher-io/mbserver

快速开始

以下是一个简单的 TCP 服务器示例:

package main

import (
    "context"
    "flag"
    "log/slog"
    "os/signal"
    "syscall"

    "github.com/novawatcher-io/mbserver"
)

var addr = flag.String("addr", ":8080", "TCP address to listen on")

func main() {
    flag.Parse()

    ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)

    s := mbserver.NewServer()

    err := s.ListenTCP(*addr)
    if err != nil {
        slog.Error("listen tcp err", "err", err)
        return
    }

    defer s.Shutdown()

    go s.Start()

    <-ctx.Done()
}

运行该程序将启动一个监听 :8080 的 Modbus TCP 服务器。

使用方法

创建服务器
s := mbserver.NewServer()
使用自定义寄存器

默认情况下,服务器使用内存寄存器(每个区域 65536 个地址)。你可以提供自己的寄存器实现:

type MyRegister struct {
    // 实现 mbserver.Register 接口
}

mr := &MyRegister{}
s := mbserver.NewServer(mbserver.WithRegister(mr))
自定义函数处理器

你可以为特定的功能码注册自定义处理函数:

s := mbserver.NewServer(mbserver.WithRegisterFunction(0x41, myCustomFunction))
监听 TCP
err := s.ListenTCP(":502")
if err != nil {
    // 处理错误
}
监听串行端口(RTU)
import "github.com/goburrow/serial"

config := &serial.Config{
    Address:  "/dev/ttyUSB0",
    BaudRate: 9600,
    DataBits: 8,
    StopBits: 1,
    Parity:   "N",
}
err := s.ListenRTU(config)
if err != nil {
    // 处理错误
}
监听 TLS(安全 TCP)
import "crypto/tls"

tlsConfig := &tls.Config{
    // 配置 TLS 证书和密钥
}
err := s.ListenTLS(":802", tlsConfig)
if err != nil {
    // 处理错误
}
启动服务器
go s.Start()
关闭服务器
s.Shutdown()

API 文档

完整的 API 文档请参阅 pkg.go.dev/github.com/novawatcher-io/mbserver

示例

更多示例请查看 cmd/ 目录和测试文件。

贡献

欢迎提交 Issue 和 Pull Request。

许可证

本项目基于 MIT 许可证开源,详见 LICENSE 文件。

Documentation

Overview

Package mbserver implements a Modbus server (slave).

Example

Start a Modbus server and use a client to write to and read from the serer.

// Start the server.
serv := NewServer()
err := serv.ListenTCP("127.0.0.1:1502")
if err != nil {
	log.Fatalln(err)
	return
}
defer serv.Shutdown()
go serv.Start()

// Wait for the server to start
time.Sleep(1 * time.Millisecond)

// Connect a client.
handler := modbus.NewTCPClientHandler("localhost:1502")
err = handler.Connect()
if err != nil {
	log.Fatalln(err)
	return
}
defer handler.Close()
client := modbus.NewClient(handler)

// Write some registers.
_, err = client.WriteMultipleRegisters(0, 3, []byte{0, 3, 0, 4, 0, 5})
if err != nil {
	log.Fatalln(err)
}

// Read those registers back.
results, err := client.ReadHoldingRegisters(0, 3)
if err != nil {
	log.Fatalln(err)
}
fmt.Printf("results %v\n", results)
Output:
results [0 3 0 4 0 5]

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrNilFrame indicates the provided frame is nil.
	ErrNilFrame = errors.New("mbserver: nil frame")
	// ErrServerClosed indicates the server is shutting down.
	ErrServerClosed = errors.New("mbserver: server closed")
	// ErrSessionClosed indicates the TCP/TLS session is already closed.
	ErrSessionClosed = errors.New("mbserver: session closed")
	// ErrSessionNotFound indicates the connection is not managed by the server.
	ErrSessionNotFound = errors.New("mbserver: session not found")
)

Functions

func BytesToUint16

func BytesToUint16(bytes []byte) []uint16

BytesToUint16 converts a big endian array of bytes to an array of unit16s

func SetDataWithRegisterAndNumber

func SetDataWithRegisterAndNumber(frame Framer, register, number uint16)

SetDataWithRegisterAndNumber sets the RTUFrame Data byte field to hold a register and number of registers

func SetDataWithRegisterAndNumberAndBytes

func SetDataWithRegisterAndNumberAndBytes(frame Framer, register, number uint16, bytes []byte)

SetDataWithRegisterAndNumberAndBytes sets the TCPFrame Data byte field to hold a register and number of registers and coil bytes

func SetDataWithRegisterAndNumberAndValues

func SetDataWithRegisterAndNumberAndValues(frame Framer, register, number uint16, values []uint16)

SetDataWithRegisterAndNumberAndValues sets the TCPFrame Data byte field to hold a register and number of registers and values

func Uint16ToBytes

func Uint16ToBytes(values []uint16) []byte

Uint16ToBytes converts an array of uint16s to a big endian array of bytes

Types

type Exception

type Exception uint8

Exception codes.

const (
	// Success operation successful.
	Success Exception = iota

	// IllegalFunction function code received in the query is not recognized or allowed by slave.
	IllegalFunction

	// IllegalDataAddress data address of some or all the required entities are not allowed or do not exist in slave.
	IllegalDataAddress

	// IllegalDataValue value is not accepted by slave.
	IllegalDataValue

	// SlaveDeviceFailure Unrecoverable error occurred while slave was attempting to perform requested action.
	SlaveDeviceFailure

	// AcknowledgeSlave has accepted request and is processing it, but a long duration of time is required. This response is returned to prevent a timeout error from occurring in the master. Master can next issue a Poll Program Complete message to determine whether processing is completed.
	AcknowledgeSlave

	// SlaveDeviceBusy is engaged in processing a long-duration command. Master should retry later.
	SlaveDeviceBusy

	// NegativeAcknowledge Slave cannot perform the programming functions. Master should request diagnostic or error information from slave.
	NegativeAcknowledge

	// MemoryParityError Slave detected a parity error in memory. Master can retry the request, but service may be required on the slave device.
	MemoryParityError

	// GatewayPathUnavailable Specialized for Modbus gateways. Indicates a misconfigured gateway.
	GatewayPathUnavailable Exception = 10

	// GatewayTargetDeviceFailedToRespond Specialized for Modbus gateways. Sent when slave fails to respond.
	GatewayTargetDeviceFailedToRespond Exception = 11
)

func GetException

func GetException(frame Framer) (exception Exception)

GetException returns the Modbus exception or Success (indicating not exception).

func (Exception) Error

func (e Exception) Error() string

func (Exception) String

func (i Exception) String() string

type Framer

type Framer interface {
	Bytes() []byte
	Copy() Framer
	GetData() []byte
	GetFunction() uint8
	SetException(Exception)
	SetData(data []byte)
}

Framer is the interface that wraps Modbus frames.

type Function

type Function func(Register, Framer) ([]byte, Exception)

type MemRegister

type MemRegister struct {
	Coils          []bool
	DiscreteInputs []bool

	HoldingRegisters []uint16
	InputRegisters   []uint16
}

func NewMemRegister

func NewMemRegister() *MemRegister

func (*MemRegister) ReadCoils

func (r *MemRegister) ReadCoils(start, count int) ([]bool, Exception)

func (*MemRegister) ReadDiscreteInputs

func (r *MemRegister) ReadDiscreteInputs(start, count int) ([]bool, Exception)

func (*MemRegister) ReadHoldingRegisters

func (r *MemRegister) ReadHoldingRegisters(start, count int) ([]uint16, Exception)

func (*MemRegister) ReadInputRegisters

func (r *MemRegister) ReadInputRegisters(start, count int) ([]uint16, Exception)

func (*MemRegister) WriteMultipleCoils

func (r *MemRegister) WriteMultipleCoils(start int, values []bool) Exception

func (*MemRegister) WriteMultipleRegisters

func (r *MemRegister) WriteMultipleRegisters(start int, values []uint16) Exception

func (*MemRegister) WriteSingleCoil

func (r *MemRegister) WriteSingleCoil(start int, value bool) Exception

func (*MemRegister) WriteSingleRegister

func (r *MemRegister) WriteSingleRegister(start int, value uint16) Exception

type OptionFunc

type OptionFunc func(s *Server)

OptionFunc is a function type used to configure options for the Server.

func WithOnClose

func WithOnClose(onClose func(net.Conn)) OptionFunc

WithOnClose registers a callback invoked when a TCP/TLS connection is about to close.

func WithOnConnect

func WithOnConnect(onConnect func(net.Conn)) OptionFunc

WithOnConnect registers a callback invoked after a TCP/TLS connection is accepted.

func WithRegister

func WithRegister(register Register) OptionFunc

WithRegister sets the memory register for the server.

func WithRegisterFunction

func WithRegisterFunction(funcCode uint8, function Function) OptionFunc

WithRegisterFunction registers a custom function handler for a specific function code. Parameter funcCode is the function code, and function is the custom handler for that code.

Example

Override the default ReadDiscreteInputs function.

// Override ReadDiscreteInputs function.
wf := WithRegisterFunction(2, func(r Register, frame Framer) ([]byte, Exception) {
	register, numRegs := registerAddressAndNumber(frame)
	// Check the request is within the allocated memory
	if register+numRegs > 65535 {
		return []byte{}, IllegalDataAddress
	}
	dataSize := numRegs / 8
	if (numRegs % 8) != 0 {
		dataSize++
	}
	data := make([]byte, 1+dataSize)
	data[0] = byte(dataSize)

	discreteInputs, exception := r.ReadDiscreteInputs(register, numRegs)
	if exception != Success {
		return []byte{}, exception
	}

	for i, value := range discreteInputs {
		if value {
			shift := uint(i) % 8
			data[1+i/8] |= byte(1 << shift)
		}
	}
	return data, Success
})

wr := WithRegister(&MemRegister{
	DiscreteInputs: []bool{
		true, true, true, true, true, true, true, true,
		true, true, true, true, true, true, true, true,
	},
})

serv := NewServer(wf, wr)

// Start the server.
err := serv.ListenTCP("localhost:4321")
if err != nil {
	log.Fatalln(err)
	return
}
defer serv.Shutdown()
go serv.Start()

// Wait for the server to start
time.Sleep(1 * time.Millisecond)

// Connect a client.
handler := modbus.NewTCPClientHandler("localhost:4321")
err = handler.Connect()
if err != nil {
	log.Fatalln(err)
}
defer handler.Close()
client := modbus.NewClient(handler)

// Read discrete inputs.
results, err := client.ReadDiscreteInputs(0, 16)
if err != nil {
	log.Fatalln(err)
}

fmt.Printf("results %v\n", results)
Output:
results [255 255]

type RTUFrame

type RTUFrame struct {
	Data     []byte
	CRC      uint16
	Address  uint8
	Function uint8
}

RTUFrame is the Modbus TCP frame.

func NewRTUFrame

func NewRTUFrame(packet []byte) (*RTUFrame, error)

NewRTUFrame converts a packet to a Modbus TCP frame.

func (*RTUFrame) Bytes

func (frame *RTUFrame) Bytes() []byte

Bytes returns the Modbus byte stream based on the RTUFrame fields

func (*RTUFrame) Copy

func (frame *RTUFrame) Copy() Framer

Copy the RTUFrame.

func (*RTUFrame) GetData

func (frame *RTUFrame) GetData() []byte

GetData returns the RTUFrame Data byte field.

func (*RTUFrame) GetFunction

func (frame *RTUFrame) GetFunction() uint8

GetFunction returns the Modbus function code.

func (*RTUFrame) SetData

func (frame *RTUFrame) SetData(data []byte)

SetData sets the RTUFrame Data byte field and updates the frame length accordingly.

func (*RTUFrame) SetException

func (frame *RTUFrame) SetException(exception Exception)

SetException sets the Modbus exception code in the frame.

type Register

type Register interface {
	ReadCoils(int, int) ([]bool, Exception)
	ReadDiscreteInputs(int, int) ([]bool, Exception)
	ReadHoldingRegisters(int, int) ([]uint16, Exception)
	ReadInputRegisters(int, int) ([]uint16, Exception)

	WriteSingleCoil(int, bool) Exception
	WriteSingleRegister(int, uint16) Exception
	WriteMultipleCoils(int, []bool) Exception
	WriteMultipleRegisters(int, []uint16) Exception
}

type Request

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

Request contains the connection and Modbus frame.

type Server

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

Server is a Modbus slave with allocated memory for discrete inputs, coils, etc.

func NewServer

func NewServer(opts ...OptionFunc) *Server

NewServer creates a new Modbus server (slave).

func (*Server) ListenRTU

func (s *Server) ListenRTU(serialConfig *serial.Config) (err error)

ListenRTU starts the Modbus server listening to a serial device. For example: err := s.ListenRTU(&serial.Config{Address: "/dev/ttyUSB0"})

func (*Server) ListenTCP

func (s *Server) ListenTCP(addressPort string) (err error)

ListenTCP starts the Modbus server listening on "address:port".

func (*Server) ListenTLS

func (s *Server) ListenTLS(addressPort string, config *tls.Config) (err error)

ListenTLS starts the Modbus server listening on "address:port".

func (*Server) Send

func (s *Server) Send(conn net.Conn, frame Framer) error

Send enqueues a Modbus frame to a managed TCP/TLS connection. The frame is serialized into the Modbus TCP wire format and sent through the session writer.

func (*Server) Session

func (s *Server) Session(conn net.Conn) (*Session, bool)

Session returns the managed TCP/TLS session for a connection accepted by the server.

func (*Server) Shutdown

func (s *Server) Shutdown()

Shutdown stops listening to TCP/IP ports and closes serial ports.

func (*Server) Start

func (s *Server) Start()

Start the service

type Session

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

Session represents a managed TCP/TLS connection.

func (*Session) Close

func (s *Session) Close() error

Close closes the session and unregisters it from the server.

func (*Session) Conn

func (s *Session) Conn() net.Conn

Conn returns the underlying network connection.

func (*Session) Send

func (s *Session) Send(frame Framer) error

Send enqueues a Modbus frame to the session writer.

type TCPFrame

type TCPFrame struct {
	TransactionIdentifier uint16
	ProtocolIdentifier    uint16
	Length                uint16
	Device                uint8
	Function              uint8
	Data                  []byte
}

TCPFrame is the Modbus TCP frame.

func NewTCPFrame

func NewTCPFrame(packet []byte) (*TCPFrame, error)

NewTCPFrame converts a packet to a Modbus TCP frame.

func (*TCPFrame) Bytes

func (frame *TCPFrame) Bytes() []byte

Bytes returns the Modbus byte stream based on the TCPFrame fields

func (*TCPFrame) Copy

func (frame *TCPFrame) Copy() Framer

Copy the TCPFrame.

func (*TCPFrame) GetData

func (frame *TCPFrame) GetData() []byte

GetData returns the TCPFrame Data byte field.

func (*TCPFrame) GetFunction

func (frame *TCPFrame) GetFunction() uint8

GetFunction returns the Modbus function code.

func (*TCPFrame) SetData

func (frame *TCPFrame) SetData(data []byte)

SetData sets the TCPFrame Data byte field and updates the frame length accordingly.

func (*TCPFrame) SetException

func (frame *TCPFrame) SetException(exception Exception)

SetException sets the Modbus exception code in the frame.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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