package apacheplc4x
|
|
import (
|
"apsClient/pkg/logx"
|
"context"
|
"errors"
|
"fmt"
|
plc4go "github.com/apache/plc4x/plc4go/pkg/api"
|
"github.com/apache/plc4x/plc4go/pkg/api/drivers"
|
apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
|
"github.com/apache/plc4x/plc4go/pkg/api/transports"
|
"sync/atomic"
|
"time"
|
)
|
|
var driverManager plc4go.PlcDriverManager
|
|
func init() {
|
// 创建驱动管理器
|
driverManager = plc4go.NewPlcDriverManager()
|
// 注册TCP传输
|
transports.RegisterTcpTransport(driverManager)
|
// 注册串口传输
|
transports.RegisterSerialTransport(driverManager)
|
// 注册驱动
|
drivers.RegisterModbusTcpDriver(driverManager)
|
drivers.RegisterModbusRtuDriver(driverManager)
|
}
|
|
func GetModbusConnection(ipAddr string) (plc4go.PlcConnection, error) {
|
// 创建一个上下文,并设置 3 秒超时
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
defer cancel()
|
conn, err := newModbusTCPConnection(ctx, ipAddr)
|
if err != nil {
|
logx.Errorf("new modbus connection err: %v", err.Error())
|
return nil, err
|
}
|
return conn, nil
|
}
|
func newModbusTCPConnection(ctx context.Context, ipAddr string) (plc4go.PlcConnection, error) {
|
// 通过TCP连接PLC设备
|
connectionString := fmt.Sprintf("modbus-tcp://%s", ipAddr)
|
connectionRequestChanel := driverManager.GetConnection(connectionString)
|
// 等待连接响应,同时考虑上下文的超时
|
select {
|
case connectionResult := <-connectionRequestChanel:
|
if err := connectionResult.GetErr(); err != nil {
|
return nil, err
|
}
|
return connectionResult.GetConnection(), nil
|
case <-ctx.Done():
|
return nil, ctx.Err()
|
}
|
}
|
|
func readHoldingRegisterSingle(connection plc4go.PlcConnection, address int, intType string) ([]byte, error) {
|
tag := fmt.Sprintf("tag:%v", address)
|
tagAddress := getTagAddress(address, 1, intType)
|
|
// 读模式
|
readRequest, err := connection.ReadRequestBuilder().AddTagAddress(tag, tagAddress).Build()
|
if err != nil {
|
logx.Errorf("preparing read-request:%s\n", err.Error())
|
return nil, err
|
}
|
|
// 执行
|
readResult := <-readRequest.Execute()
|
if err := readResult.GetErr(); err != nil {
|
logx.Errorf("execting read-request:%s\n", err.Error())
|
return nil, err
|
}
|
|
// 判断响应码是否正确
|
if readResult.GetResponse().GetResponseCode(tag) != apiModel.PlcResponseCode_OK {
|
logx.Errorf("error an non-ok return code: %s", readResult.GetResponse().GetResponseCode(tag).GetName())
|
return nil, nil
|
}
|
|
value := readResult.GetResponse().GetValue(tag)
|
|
return value.GetRaw(), err
|
|
}
|
|
func readHoldingRegisterList(connection plc4go.PlcConnection, address, length int, intType string) ([]byte, error) {
|
tag := fmt.Sprintf("tag:%v:%v", address, length)
|
tagAddress := getTagAddress(address, length, intType)
|
|
// 读模式
|
readRequest, err := connection.ReadRequestBuilder().AddTagAddress(tag, tagAddress).Build()
|
if err != nil {
|
logx.Errorf("preparing read-request:%s\n", err.Error())
|
return nil, err
|
}
|
|
// 执行
|
readResult := <-readRequest.Execute()
|
if err := readResult.GetErr(); err != nil {
|
logx.Errorf("plc4x execute read-request:%s\n", err.Error())
|
return nil, err
|
}
|
|
// 判断响应码是否正确
|
if readResult.GetResponse().GetResponseCode(tag) != apiModel.PlcResponseCode_OK {
|
logx.Errorf("plc4x response error code: %s", readResult.GetResponse().GetResponseCode(tag).GetName())
|
return nil, errors.New("error code: " + readResult.GetResponse().GetResponseCode(tag).GetName())
|
}
|
|
value := readResult.GetResponse().GetValue(tag)
|
|
var result []byte
|
|
for _, val := range value.GetList() {
|
result = append(result, val.GetRaw()...)
|
}
|
|
return result, nil
|
}
|
|
func ReadHoldingRegister(ipAddr string, address, length int, intType string) ([]byte, error) {
|
connection, err := GetModbusConnection(ipAddr)
|
dealErr(err, ipAddr)
|
if err != nil {
|
return nil, err
|
}
|
defer connection.Close()
|
if length > 1 {
|
return readHoldingRegisterList(connection, address, length, intType)
|
}
|
|
return readHoldingRegisterSingle(connection, address, intType)
|
}
|
|
func getTagAddress(address int, length int, intType string) string {
|
if intType == "" {
|
intType = "DINT"
|
}
|
if length == 1 {
|
return fmt.Sprintf("holding-register:%d:%v", address, intType)
|
} else {
|
return fmt.Sprintf("holding-register:%d:%v[%d]", address, intType, length)
|
}
|
}
|
|
func WriteHoldingRegister(ipAddr string, address int, value any, intType string) (string, error) {
|
connection, err := GetModbusConnection(ipAddr)
|
dealErr(err, ipAddr)
|
if err != nil {
|
return "", err
|
}
|
defer connection.Close()
|
tag := fmt.Sprintf("tag:%v:w", address)
|
var tagAddress string
|
tagAddress = getTagAddress(address, 1, intType)
|
|
// 写模式
|
writeRequest, err := connection.WriteRequestBuilder().AddTagAddress(tag, tagAddress, value).Build()
|
if err != nil {
|
logx.Errorf("plc4x preparing read-request:%s\n", err.Error())
|
return "", err
|
}
|
|
// 执行
|
writeResult := <-writeRequest.Execute()
|
if err := writeResult.GetErr(); err != nil {
|
logx.Errorf("plc4x execute write-request:%s\n", err.Error())
|
return "", err
|
}
|
|
// 判断响应码是否正确
|
if writeResult.GetResponse().GetResponseCode(tag) != apiModel.PlcResponseCode_OK {
|
logx.Errorf("plc4x response error code: %s", writeResult.GetResponse().GetResponseCode(tag).GetName())
|
return "", errors.New("error code: " + writeResult.GetResponse().GetResponseCode(tag).GetName())
|
}
|
|
result := writeResult.GetResponse().String()
|
|
return result, nil
|
}
|
|
func dealErr(err error, ipAddr string) {
|
if err != nil {
|
FailureRemainingOpportunitiesDecr() //减少失败剩余机会
|
} else {
|
FailureRemainingOpportunitiesReset() //重置失败剩余机会
|
}
|
}
|
|
var connectionStatus atomic.Bool
|
|
var failureRemainingOpportunities atomic.Int64
|
|
const (
|
defaultFailureRemainingOpportunities = 20
|
)
|
|
func IsConnect() bool {
|
return connectionStatus.Load()
|
}
|
|
func FailureRemainingOpportunitiesDecr() {
|
newValue := failureRemainingOpportunities.Add(-1)
|
if newValue <= 0 {
|
connectionStatus.Store(false)
|
}
|
return
|
}
|
|
func FailureRemainingOpportunitiesReset() {
|
if failureRemainingOpportunities.Load() < defaultFailureRemainingOpportunities {
|
failureRemainingOpportunities.Store(defaultFailureRemainingOpportunities)
|
}
|
if connectionStatus.Load() == false {
|
connectionStatus.Store(true)
|
}
|
return
|
}
|