Modbus客户端及常用功能接口及实现
Modbus客户端管理实现
This commit is contained in:
commit
a774387776
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal 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
13
README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# iot-gateway(Internet of Things)物联网网关模块
|
||||||
|
|
||||||
|
用于提供各种硬件协议实现及应用抽象,将各种协议最终转换为 MQTT 协议实现应用功能
|
||||||
|
|
||||||
|
# 硬件协议
|
||||||
|
|
||||||
|
## Modbus(实现中)
|
||||||
|
|
||||||
|
功能路线:
|
||||||
|
|
||||||
|
- Modbus TCP 客户端接口及实现,支持自动重连 --- 已完成
|
||||||
|
- Modbus 客户端管理实现 --- 已完成
|
||||||
|
- Modbus TCP 常用功能接口实现 --- 已完成
|
7
go.mod
Normal file
7
go.mod
Normal 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
4
go.sum
Normal 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
56
main.go
Normal 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
33
modbus/api.go
Normal 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
194
modbus/client.go
Normal 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
23
modbus/function_code.go
Normal 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
53
modbus/manage.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user