195 lines
4.6 KiB
Go
195 lines
4.6 KiB
Go
|
package modbus
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"log/slog"
|
||
|
"net"
|
||
|
"os"
|
||
|
"time"
|
||
|
|
||
|
"github.com/simonvetter/modbus"
|
||
|
)
|
||
|
|
||
|
type ConnectState int
|
||
|
|
||
|
const ()
|
||
|
|
||
|
type client struct {
|
||
|
url string
|
||
|
cli *modbus.ModbusClient
|
||
|
started bool // 是否启动
|
||
|
connected bool // 是否连接
|
||
|
cancel context.CancelFunc
|
||
|
}
|
||
|
|
||
|
func newClient(url string) (MasterClient, error) {
|
||
|
cli, err := modbus.NewClient(&modbus.ClientConfiguration{
|
||
|
URL: url,
|
||
|
Timeout: 1 * time.Second,
|
||
|
})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
c := &client{
|
||
|
url: url,
|
||
|
cli: cli,
|
||
|
connected: false,
|
||
|
}
|
||
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
go c.connManage(ctx)
|
||
|
c.cancel = cancel
|
||
|
return c, err
|
||
|
}
|
||
|
|
||
|
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 {
|
||
|
slog.Error("modbus客户端尝试连接失败", "url", c.url, "err", err)
|
||
|
} 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 {
|
||
|
return c.cli.Close()
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// 读请求执行
|
||
|
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)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// 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)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// 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)
|
||
|
})
|
||
|
}
|