Modbus客户端及常用功能接口及实现

Modbus客户端管理实现
This commit is contained in:
walker 2023-12-06 17:07:52 +08:00
commit a774387776
9 changed files with 410 additions and 0 deletions

27
.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
.idea/
.vscode
.air.toml
.DS_Store
output/
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
# Go workspace file
/pom.xml

13
README.md Normal file
View File

@ -0,0 +1,13 @@
# iot-gateway(Internet of Things)物联网网关模块
用于提供各种硬件协议实现及应用抽象,将各种协议最终转换为 MQTT 协议实现应用功能
# 硬件协议
## Modbus(实现中)
功能路线:
- Modbus TCP 客户端接口及实现,支持自动重连 --- 已完成
- Modbus 客户端管理实现 --- 已完成
- Modbus TCP 常用功能接口实现 --- 已完成

7
go.mod Normal file
View File

@ -0,0 +1,7 @@
module joylink.club/iot
go 1.21
require github.com/simonvetter/modbus v1.6.0
require github.com/goburrow/serial v0.1.0 // indirect

4
go.sum Normal file
View File

@ -0,0 +1,4 @@
github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA=
github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA=
github.com/simonvetter/modbus v1.6.0 h1:RDHJevtc7LDIVoHAbhDun8fy+QwnGe+ZU+sLm9ZZzjc=
github.com/simonvetter/modbus v1.6.0/go.mod h1:hh90ZaTaPLcK2REj6/fpTbiV0J6S7GWmd8q+GVRObPw=

56
main.go Normal file
View File

@ -0,0 +1,56 @@
package main
import (
"fmt"
"time"
"joylink.club/iot/modbus"
)
func main() {
cli, err := modbus.NewClient("tcp://localhost:502")
if err != nil {
panic(err)
}
cli.Start()
// vb := false
for i := 0; i < 10; i++ {
time.Sleep(time.Second * 2)
// read a single 16-bit holding register at address 100
if i%2 == 0 {
// vb = !vb
// data := []bool{vb, !vb, vb, !vb, vb, !vb, vb, !vb}
// err := cli.WriteCoils(0, data)
// if err != nil {
// // error out
// fmt.Printf("第%d次写错误: %s\n", i, err)
// } else {
// fmt.Printf("第%d次写成功, value: %v\n", i, vb)
// }
data := []uint16{uint16(i + 1), uint16(i + 2), uint16(i + 3), uint16(i + 4), uint16(i + 5), uint16(i + 6), uint16(i + 7), uint16(i + 8)}
err := cli.WriteRegisters(0, data)
if err != nil {
// error out
fmt.Printf("第%d次写错误: %s\n", i, err)
} else {
fmt.Printf("第%d次写成功, value: %v\n", i, data)
}
} else {
// bs, err := cli.ReadCoil(0, 10)
// if err != nil {
// // error out
// fmt.Printf("第%d次读错误: %s\n", i, err)
// } else {
// fmt.Printf("第%d次读成功, value: %v\n", i, bs)
// }
bs, err := cli.ReadHoldingRegister(0, 10)
if err != nil {
// error out
fmt.Printf("第%d次读错误: %s\n", i, err)
} else {
fmt.Printf("第%d次读成功, value: %v\n", i, bs)
}
}
}
}

33
modbus/api.go Normal file
View File

@ -0,0 +1,33 @@
package modbus
// 在modbus协议中主机正常是客户端
type MasterClient interface {
// 启动
Start() error
// 停止
Stop() error
// 关闭,销毁
Close() error
// 是否连接
IsConnected() bool
// 读线圈,位操作,功能码为0x01
ReadCoil(addr uint16, quantity uint16) ([]bool, error)
// 读一个线圈
ReadCoilBit(addr uint16) (bool, error)
// 读离散输入,位操作功能码为0x02
ReadDiscreteInput(addr uint16, quantity uint16) ([]bool, error)
// 读保持寄存器,字节操作功能码为0x03
ReadHoldingRegister(addr uint16, quantity uint16) ([]uint16, error)
// 读一个保持寄存器
ReadHoldingRegisterUint16(addr uint16) (uint16, error)
// 读输入寄存器,字节操作功能码为0x04
ReadInputRegister(addr uint16, quantity uint16) ([]uint16, error)
// 写单个线圈,功能码为0x05
WriteCoil(addr uint16, value bool) error
// 写多个线圈,功能码为0x0F
WriteCoils(addr uint16, values []bool) error
// 写单个保持寄存器,功能码为0x06
WriteRegister(addr uint16, value uint16) error
// 写多个保持寄存器,功能码为0x10
WriteRegisters(addr uint16, values []uint16) error
}

194
modbus/client.go Normal file
View File

@ -0,0 +1,194 @@
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)
})
}

23
modbus/function_code.go Normal file
View File

@ -0,0 +1,23 @@
package modbus
// 功能码
type FunctionCode int
const (
// 读线圈
FCReadCoil FunctionCode = 0x01
// 读离散输入
FCReadDiscreteInput FunctionCode = 0x02
// 读多个寄存器
FCReadHoldingRegister FunctionCode = 0x03
// 读输入寄存器
FCReadInputRegister FunctionCode = 0x04
// 写单个线圈
FCWriteSingleCoil FunctionCode = 0x05
// 写单个寄存器
FCWriteSingleRegister FunctionCode = 0x06
// 写多个线圈
FCWriteMultipleCoil FunctionCode = 0x0F
// 写多个寄存器
FCWriteMultipleRegister FunctionCode = 0x10
)

53
modbus/manage.go Normal file
View File

@ -0,0 +1,53 @@
package modbus
import "sync"
// modbus客户端管理
type modbusManager struct {
lock sync.Mutex
clientMap map[string]MasterClient
}
var manager = modbusManager{
clientMap: make(map[string]MasterClient),
}
// 获取modbus客户端
func GetClient(url string) (MasterClient, bool) {
manager.lock.Lock()
defer manager.lock.Unlock()
client, ok := manager.clientMap[url]
return client, ok
}
// 新建客户端
func NewClient(url string) (MasterClient, error) {
manager.lock.Lock()
defer manager.lock.Unlock()
client, err := newClient(url)
if err != nil {
return nil, err
}
manager.clientMap[url] = client
return client, err
}
// 获取或新建客户端
func GetOrInitClient(url string) (MasterClient, error) {
client, ok := GetClient(url)
if ok {
return client, nil
}
return NewClient(url)
}
// 删除客户端
func DeleteClient(url string) {
manager.lock.Lock()
defer manager.lock.Unlock()
c := manager.clientMap[url]
if c != nil {
c.Close()
delete(manager.clientMap, url)
}
}