zhangqian
2023-10-26 620d904921c76b6b29d1b69787da7a82121d4c31
读写plc支持modbusRTU
2个文件已添加
5个文件已修改
272 ■■■■ 已修改文件
go.mod 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
model/common/common.go 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pkg/plc/apacheplc4x/modbus.go 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pkg/plc/apacheplc4x/modbusrtu.go 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pkg/plc/modbusx/modbusrtu.go 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service/plc.go 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
test/plc_test.go 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
go.mod
@@ -16,7 +16,6 @@
    github.com/gofrs/uuid v4.4.0+incompatible
    github.com/gogo/protobuf v1.3.2
    github.com/gorilla/websocket v1.5.0
    github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4
    github.com/jinzhu/gorm v1.9.16
    github.com/mitchellh/mapstructure v1.5.0
    github.com/mojocn/base64Captcha v1.3.1
@@ -66,6 +65,7 @@
    github.com/gopacket/gopacket v1.1.1 // indirect
    github.com/hashicorp/hcl v1.0.0 // indirect
    github.com/icza/bitio v1.1.0 // indirect
    github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4 // indirect
    github.com/jinzhu/inflection v1.0.0 // indirect
    github.com/jinzhu/now v1.1.5 // indirect
    github.com/josharian/intern v1.0.0 // indirect
model/common/common.go
@@ -157,3 +157,11 @@
    DataType PullDataType `json:"dataType"` //要拉取的数据类型
    Data     interface{}  //返回的数据
}
type RTUConfig struct {
    BaudRate   int             `json:"baudRate"`                     //串口波特率, method = serial时 用
    SerialName string          `json:"serialName"`                   //串口名称,method = serial时 用
    DataBit    int             `gorm:"type:int(11)"  json:"dataBit"` //数据位,method = modbusRTU 用
    StopBit    int             `gorm:"type:int(11)"  json:"stopBit"` //停止位,method = modbusRTU 用
    Parity     constvar.Parity `gorm:"type:int(11)"  json:"parity"`  //校验方式,method = modbusRTU 用
}
pkg/plc/apacheplc4x/modbus.go
@@ -19,7 +19,7 @@
    // 创建一个上下文,并设置 3 秒超时
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    conn, err := newGetModbusConnection(ctx, ipAddr)
    conn, err := newModbusConnection(ctx, ipAddr)
    if err != nil {
        logx.Errorf("new modbus connection err: %v", err.Error())
        return nil, err
@@ -27,7 +27,7 @@
    return conn, nil
}
func newGetModbusConnection(ctx context.Context, ipAddr string) (plc4go.PlcConnection, error) {
func newModbusConnection(ctx context.Context, ipAddr string) (plc4go.PlcConnection, error) {
    // 创建驱动管理器
    driverManager := plc4go.NewPlcDriverManager()
    // 注册TCP传输
pkg/plc/apacheplc4x/modbusrtu.go
New file
@@ -0,0 +1,85 @@
package apacheplc4x
import (
    "apsClient/model/common"
    "apsClient/pkg/logx"
    "context"
    "errors"
    "fmt"
    plc4go "github.com/apache/plc4x/plc4go/pkg/api"
    apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
    "github.com/spf13/cast"
    "time"
)
func newModbusRTUConnection(c *common.RTUConfig) (plc4go.PlcConnection, error) {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
    // 创建一个新的 PLC 连接
    driverManager := plc4go.NewPlcDriverManager()
    connectionString := fmt.Sprintf("modbus-rtu:serial://%s?baudrate=%d&databits=%d&stopbits=%d&parity=%s", c.SerialName, c.BaudRate, c.DataBit, c.StopBit, c.Parity)
    connectionRequestChanel := driverManager.GetConnection(connectionString)
    // 等待连接响应,同时考虑上下文的超时
    select {
    case connectionResult := <-connectionRequestChanel:
        cancel()
        if err := connectionResult.GetErr(); err != nil {
            return nil, err
        }
        return connectionResult.GetConnection(), nil
    case <-ctx.Done():
        cancel()
        return nil, errors.New("connect plc by modbusRTU timeout")
    }
}
func ReadHoldingRegisterByRTU(c *common.RTUConfig, address, length int) ([]byte, error) {
    connection, err := newModbusRTUConnection(c)
    if err != nil {
        return nil, err
    }
    defer connection.Close()
    if length > 1 {
        return readHoldingRegisterList(connection, address, length)
    }
    return readHoldingRegisterSingle(connection, address)
}
func WriteHoldingRegisterByRTU(c *common.RTUConfig, address int, value any) (string, error) {
    connection, err := newModbusRTUConnection(c)
    if err != nil {
        return "", err
    }
    defer connection.Close()
    tag := fmt.Sprintf("tag:%v:w", address)
    var tagAddress string
    if cast.ToInt32(value) > 2<<16 {
        tagAddress = getTagAddress(address, 2)
    } else {
        tagAddress = getTagAddress(address, 1)
    }
    // 写模式
    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
}
pkg/plc/modbusx/modbusrtu.go
New file
@@ -0,0 +1,72 @@
package modbusx
import (
    "apsClient/constvar"
    "apsClient/model/common"
    "github.com/goburrow/modbus"
    "time"
)
func ReadByRTU(c *common.RTUConfig, address uint16, quantity uint16) (data []byte, err error) {
    address--
    cli, err := getModbusRTUConnection(c)
    if err != nil {
        return nil, err
    }
    data, err = cli.ReadHoldingRegisters(address, quantity)
    if err != nil {
        cli, err = getModbusRTUConnection(c)
        if err != nil {
            return nil, err
        }
        data, err = cli.ReadHoldingRegisters(address, quantity)
    }
    return
}
func WriteByRTU(c *common.RTUConfig, address uint16, value int) (err error) {
    address--
    var bytesVal []byte
    bytesVal = intToBytes(value)
    cli, err := getModbusRTUConnection(c)
    if err != nil {
        return err
    }
    _, err = cli.WriteMultipleRegisters(address, uint16(len(bytesVal)), bytesVal)
    if err != nil {
        cli, err = getModbusRTUConnection(c)
        if err != nil {
            return err
        }
        _, err = cli.WriteMultipleRegisters(address, uint16(len(bytesVal)), bytesVal)
    }
    return err
}
func getModbusRTUConnection(c *common.RTUConfig) (cli modbus.Client, err error) {
    // Modbus RTU/ASCII
    h := modbus.NewRTUClientHandler(c.SerialName)
    h.BaudRate = c.BaudRate
    h.DataBits = c.DataBit
    switch c.Parity {
    case constvar.ParityEven:
        h.Parity = "E"
    case constvar.ParityOdd:
        h.Parity = "O"
    case constvar.ParityNull:
        h.Parity = "N"
    }
    h.StopBits = c.StopBit
    h.SlaveId = 1
    h.Timeout = 5 * time.Second
    err = h.Connect()
    if err != nil {
        return nil, err
    }
    defer h.Close()
    cli = modbus.NewClient(h)
    return
}
service/plc.go
@@ -4,6 +4,7 @@
    "apsClient/conf"
    "apsClient/constvar"
    "apsClient/model"
    "apsClient/model/common"
    "apsClient/pkg/logx"
    "apsClient/pkg/plc"
    "apsClient/pkg/plc/apacheplc4x"
@@ -19,7 +20,6 @@
func PlcWrite(plcConfig *model.DevicePlc, fieldType constvar.PlcStartAddressType, channel int32, value interface{}) (err error) {
    var (
        startAddress int
        ipAddr       string
    )
    if plcConfig.CurrentTryTimes > plcConfig.MaxTryTimes {
@@ -32,26 +32,7 @@
            startAddress = pc.StartAddress
        }
    }
    if plcConfig.Method == constvar.PlcMethodModbusTCP {
        ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port)
        err = WriteHoldingRegister(ipAddr, startAddress, value)
        if err != nil {
            logx.Errorf("plc write failed, address: %v, value: %v, err: %v", startAddress, value, err.Error())
            plcConfig.CurrentErr = err
            return PlcWrite(plcConfig, fieldType, channel, value)
        }
        logx.Infof("plc write ok, address: %v, value: %v", startAddress, value)
    } else if plcConfig.Method == constvar.PlcMethodSerial {
        ipAddr = conf.Conf.Services.Serial
        if ipAddr == "" {
            return errors.New("conf.Conf.Services.Serial config not set yet")
        }
        label := fmt.Sprintf("D%d", startAddress)
        return plccom.WritePLC(plccom.DeviceTypeMitsubishi, ipAddr, label, cast.ToInt(value))
    }
    return
    return PlcWriteDirect(plcConfig, startAddress, value)
}
func PlcWriteDirect(plcConfig *model.DevicePlc, address int, value interface{}) (err error) {
@@ -61,7 +42,16 @@
    defer dealErr(err)
    if plcConfig.Method == constvar.PlcMethodModbusTCP {
        ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port)
        err = WriteHoldingRegister(ipAddr, address, value)
        err = WriteByModbusTCP(ipAddr, address, value)
        if err != nil {
            logx.Errorf("plc write failed, address: %v, value: %v, err: %v", address, value, err.Error())
            return err
        }
        logx.Infof("plc write ok, address: %v, value: %v", address, value)
    } else if plcConfig.Method == constvar.PlcMethodModbusRTU {
        ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port)
        err = WriteByModbusRTU(plcConfig, address, value)
        if err != nil {
            logx.Errorf("plc write failed, address: %v, value: %v, err: %v", address, value, err.Error())
@@ -84,12 +74,21 @@
        ipAddr string
    )
    defer dealErr(err)
    if plcConfig.Method == constvar.PlcMethodModbusTCP {
        ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port)
        value, err := ReadHoldingRegister(ipAddr, address, dataLength)
        if err != nil {
            return nil, err
    if plcConfig.Method == constvar.PlcMethodModbusTCP || plcConfig.Method == constvar.PlcMethodModbusRTU {
        var value []byte
        if plcConfig.Method == constvar.PlcMethodModbusTCP {
            ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port)
            value, err = ReadByModbusTCP(ipAddr, address, dataLength)
            if err != nil {
                return nil, err
            }
        } else {
            value, err = ReadByModbusRTU(plcConfig, address, dataLength)
            if err != nil {
                return nil, err
            }
        }
        switch valueType {
        case constvar.PlcStartAddressValueTypeString:
            return string(value), nil
@@ -118,7 +117,7 @@
    return
}
func ReadHoldingRegister(ipAddr string, address, length int) ([]byte, error) {
func ReadByModbusTCP(ipAddr string, address, length int) ([]byte, error) {
    if conf.Conf.PLC.Package == constvar.PlcPackageApache {
        newLength := length / 2
        if newLength == 0 {
@@ -140,7 +139,7 @@
    }
}
func WriteHoldingRegister(ipAddr string, address int, value any) (err error) {
func WriteByModbusTCP(ipAddr string, address int, value any) (err error) {
    if conf.Conf.PLC.Package == constvar.PlcPackageApache {
        _, err = apacheplc4x.WriteHoldingRegister(ipAddr, address, value)
        return err
@@ -156,15 +155,43 @@
    }
}
func ReadByModbusRTU(plcConfig *model.DevicePlc, address, length int) ([]byte, error) {
    rtuConfig := &common.RTUConfig{
        BaudRate:   plcConfig.BaudRate,
        SerialName: plcConfig.SerialName,
        DataBit:    plcConfig.DataBit,
        StopBit:    plcConfig.StopBit,
        Parity:     plcConfig.Parity,
    }
    if conf.Conf.PLC.Package == constvar.PlcPackageApache {
        newLength := length / 2
        if newLength == 0 {
            newLength = 1
        }
        return apacheplc4x.ReadHoldingRegisterByRTU(rtuConfig, address, newLength)
    } else {
        return modbusx.ReadByRTU(rtuConfig, uint16(address), uint16(length))
    }
}
func WriteByModbusRTU(plcConfig *model.DevicePlc, address int, value any) (err error) {
    rtuConfig := &common.RTUConfig{
        BaudRate:   plcConfig.BaudRate,
        SerialName: plcConfig.SerialName,
        DataBit:    plcConfig.DataBit,
        StopBit:    plcConfig.StopBit,
        Parity:     plcConfig.Parity,
    }
    if conf.Conf.PLC.Package == constvar.PlcPackageApache {
        _, err = apacheplc4x.WriteHoldingRegisterByRTU(rtuConfig, address, value)
        return err
    } else {
        return modbusx.WriteByRTU(rtuConfig, uint16(address), cast.ToInt(value))
    }
}
func PlcIsConnect() bool {
    return IsConnect()
    //if conf.Conf.PLC.Package == constvar.PlcPackageApache {
    //    return apacheplc4x.IsConnect()
    //} else if conf.Conf.PLC.Package == constvar.PlcPackageApacheLongConnection {
    //    return false
    //} else {
    //    return modbusx.IsConnect()
    //}
}
func dealErr(err error) {
test/plc_test.go
@@ -17,7 +17,7 @@
        log.Fatal(err)
    }
    var readValue int
    raw, err := service.ReadHoldingRegister(ipPort, address, 1)
    raw, err := service.ReadByModbusTCP(ipPort, address, 1)
    if err != nil {
        log.Fatal(err)
    }