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