2023-12-06 17:07:52 +08:00
|
|
|
package modbus
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"log/slog"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/simonvetter/modbus"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ConnectState int
|
|
|
|
|
|
|
|
const ()
|
|
|
|
|
2023-12-11 14:26:31 +08:00
|
|
|
type ClientConfig struct {
|
|
|
|
Url string
|
|
|
|
Timeout uint32 // 超时时间,单位ms
|
|
|
|
}
|
|
|
|
|
2023-12-06 17:07:52 +08:00
|
|
|
type client struct {
|
|
|
|
url string
|
|
|
|
cli *modbus.ModbusClient
|
|
|
|
started bool // 是否启动
|
|
|
|
connected bool // 是否连接
|
|
|
|
cancel context.CancelFunc
|
|
|
|
}
|
|
|
|
|
2023-12-11 14:26:31 +08:00
|
|
|
func newClient(conf *ClientConfig) (MasterClient, error) {
|
2023-12-06 17:07:52 +08:00
|
|
|
cli, err := modbus.NewClient(&modbus.ClientConfiguration{
|
2023-12-11 14:26:31 +08:00
|
|
|
URL: conf.Url,
|
|
|
|
Timeout: time.Duration(conf.Timeout) * time.Millisecond,
|
2023-12-06 17:07:52 +08:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
c := &client{
|
2023-12-11 14:26:31 +08:00
|
|
|
url: conf.Url,
|
2023-12-06 17:07:52 +08:00
|
|
|
cli: cli,
|
|
|
|
connected: false,
|
|
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
go c.connManage(ctx)
|
|
|
|
c.cancel = cancel
|
|
|
|
return c, err
|
|
|
|
}
|
|
|
|
|
2023-12-08 18:08:15 +08:00
|
|
|
func (c *client) SetUnitId(id uint8) error {
|
|
|
|
return c.cli.SetUnitId(id)
|
|
|
|
}
|
|
|
|
|
2023-12-11 17:28:52 +08:00
|
|
|
func (c *client) SetEndianness(endianness Endianness) error {
|
|
|
|
switch endianness {
|
|
|
|
case BigEndian:
|
|
|
|
return c.cli.SetEncoding(modbus.BIG_ENDIAN, modbus.HIGH_WORD_FIRST)
|
|
|
|
case LittleEndian:
|
|
|
|
return c.cli.SetEncoding(modbus.LITTLE_ENDIAN, modbus.HIGH_WORD_FIRST)
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unknown endianness value %v", endianness)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-06 17:07:52 +08:00
|
|
|
func (c *client) Start() error {
|
|
|
|
c.started = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) Stop() error {
|
|
|
|
c.started = false
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 客户端连接管理
|
|
|
|
func (c *client) connManage(ctx context.Context) {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
slog.Info("modbus客户端连接管理线程退出", "url", c.url)
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
if c.started && !c.connected { // 已经启动, 尝试重连
|
|
|
|
err := c.cli.Open()
|
|
|
|
if err != nil {
|
2023-12-11 17:28:52 +08:00
|
|
|
slog.Error("modbus客户端连接失败", "url", c.url, "err", err)
|
2023-12-06 17:07:52 +08:00
|
|
|
} else {
|
|
|
|
c.connected = true
|
|
|
|
slog.Info("modbus客户端连接成功", "url", c.url)
|
|
|
|
}
|
|
|
|
} else if !c.started && c.connected { // 未启动,尝试关闭
|
|
|
|
err := c.cli.Close()
|
|
|
|
if err != nil {
|
|
|
|
slog.Error("modbus客户端关闭错误", "url", c.url, "err", err)
|
|
|
|
}
|
|
|
|
c.connected = false
|
|
|
|
slog.Info("modbus客户端关闭", "url", c.url)
|
|
|
|
}
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func (c *client) IsConnected() bool {
|
|
|
|
return c.connected
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close implements MasterClient.
|
|
|
|
func (c *client) Close() error {
|
|
|
|
c.cancel()
|
|
|
|
if c.connected {
|
2023-12-08 18:08:15 +08:00
|
|
|
c.connected = false
|
2023-12-06 17:07:52 +08:00
|
|
|
}
|
2023-12-08 18:08:15 +08:00
|
|
|
return c.cli.Close()
|
2023-12-06 17:07:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 读请求执行
|
|
|
|
func readExecute[T any](c *client, req func() (T, error)) (T, error) {
|
|
|
|
if !c.started {
|
|
|
|
return *new(T), fmt.Errorf("modbus客户端未启动")
|
|
|
|
}
|
|
|
|
if !c.connected {
|
|
|
|
return *new(T), fmt.Errorf("modbus客户端未连接或连接断开")
|
|
|
|
}
|
|
|
|
res, err := req()
|
|
|
|
if err != nil {
|
|
|
|
if newErr, ok := err.(*net.OpError); ok {
|
|
|
|
if se, ok := newErr.Err.(*os.SyscallError); ok {
|
|
|
|
c.connected = false
|
|
|
|
return res, se
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 写请求执行
|
|
|
|
func writeExecute(c *client, req func() error) error {
|
|
|
|
if !c.started {
|
|
|
|
return fmt.Errorf("modbus客户端未启动")
|
|
|
|
}
|
|
|
|
if !c.connected {
|
|
|
|
return fmt.Errorf("modbus客户端未连接或连接断开")
|
|
|
|
}
|
|
|
|
return req()
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadCoil implements MasterClient.
|
|
|
|
func (c *client) ReadCoil(addr uint16, quantity uint16) ([]bool, error) {
|
|
|
|
return readExecute[[]bool](c, func() ([]bool, error) {
|
|
|
|
return c.cli.ReadCoils(addr, quantity)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) ReadCoilBit(addr uint16) (bool, error) {
|
|
|
|
return readExecute[bool](c, func() (bool, error) {
|
|
|
|
return c.cli.ReadCoil(addr)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadDiscreteInput implements MasterClient.
|
|
|
|
func (c *client) ReadDiscreteInput(addr uint16, quantity uint16) ([]bool, error) {
|
|
|
|
return readExecute[[]bool](c, func() ([]bool, error) {
|
|
|
|
return c.cli.ReadDiscreteInputs(addr, quantity)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadHoldingRegister implements MasterClient.
|
|
|
|
func (c *client) ReadHoldingRegister(addr uint16, quantity uint16) ([]uint16, error) {
|
|
|
|
return readExecute[[]uint16](c, func() ([]uint16, error) {
|
|
|
|
return c.cli.ReadRegisters(addr, quantity, modbus.HOLDING_REGISTER)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-12-08 18:08:15 +08:00
|
|
|
func (c *client) ReadHoldingRegisterBytes(addr uint16, quantity uint16) ([]byte, error) {
|
|
|
|
return readExecute[[]byte](c, func() ([]byte, error) {
|
|
|
|
return c.cli.ReadBytes(addr, quantity, modbus.HOLDING_REGISTER)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-12-06 17:07:52 +08:00
|
|
|
// ReadHoldingRegisterUint16 implements MasterClient.
|
|
|
|
func (c *client) ReadHoldingRegisterUint16(addr uint16) (uint16, error) {
|
|
|
|
return readExecute[uint16](c, func() (uint16, error) {
|
|
|
|
return c.cli.ReadRegister(addr, modbus.HOLDING_REGISTER)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadInputRegister implements MasterClient.
|
|
|
|
func (c *client) ReadInputRegister(addr uint16, quantity uint16) ([]uint16, error) {
|
|
|
|
return readExecute[[]uint16](c, func() ([]uint16, error) {
|
|
|
|
return c.cli.ReadRegisters(addr, quantity, modbus.INPUT_REGISTER)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-12-08 18:08:15 +08:00
|
|
|
func (c *client) ReadInputRegisterBytes(addr uint16, quantity uint16) ([]byte, error) {
|
|
|
|
return readExecute[[]byte](c, func() ([]byte, error) {
|
|
|
|
return c.cli.ReadBytes(addr, quantity, modbus.INPUT_REGISTER)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-12-06 17:07:52 +08:00
|
|
|
// WriteCoil implements MasterClient.
|
|
|
|
func (c *client) WriteCoil(addr uint16, value bool) error {
|
|
|
|
return writeExecute(c, func() error {
|
|
|
|
return c.cli.WriteCoil(addr, value)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteCoils implements MasterClient.
|
|
|
|
func (c *client) WriteCoils(addr uint16, values []bool) error {
|
|
|
|
return writeExecute(c, func() error {
|
|
|
|
return c.cli.WriteCoils(addr, values)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteRegister implements MasterClient.
|
|
|
|
func (c *client) WriteRegister(addr uint16, value uint16) error {
|
|
|
|
return writeExecute(c, func() error {
|
|
|
|
return c.cli.WriteRegister(addr, value)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteRegisters implements MasterClient.
|
|
|
|
func (c *client) WriteRegisters(addr uint16, values []uint16) error {
|
|
|
|
return writeExecute(c, func() error {
|
|
|
|
return c.cli.WriteRegisters(addr, values)
|
|
|
|
})
|
|
|
|
}
|
2023-12-08 18:08:15 +08:00
|
|
|
|
|
|
|
func (c *client) WriteRegisterBytes(addr uint16, values []byte) error {
|
|
|
|
return writeExecute(c, func() error {
|
|
|
|
return c.cli.WriteBytes(addr, values)
|
|
|
|
})
|
|
|
|
}
|