zhangqian
2023-09-07 d4272e1b692515af9c47799a658e395703d13555
支持串口读写
2个文件已添加
5个文件已修改
403 ■■■■ 已修改文件
api/v1/task.go 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
conf/apsClient.json 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
conf/config.go 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pkg/plc/plc4x.go 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pkg/plccom/mitsubishi/protocol.go 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pkg/plccom/plccom.go 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service/plc.go 127 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
api/v1/task.go
@@ -11,7 +11,6 @@
    "apsClient/pkg/convertx"
    "apsClient/pkg/ecode"
    "apsClient/pkg/logx"
    "apsClient/pkg/plc"
    "apsClient/service"
    "apsClient/service/plc_address"
    "errors"
@@ -266,27 +265,24 @@
    if plcConfig.CurrentTryTimes > plcConfig.MaxTryTimes {
        return plcConfig.CurrentErr
    }
    conn, err := plc.GetModbusConnection(fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port))
    if err != nil {
        return errors.New(fmt.Sprintf("连接plc失败: %v", err.Error()))
    }
    if plcConfig.CurrentTryTimes == 0 {
        logx.Info("----------------开始下发工艺参数-----------------")
    }
    var failedNumbers int
    for k, v := range paramsMap {
        if address, ok := plc_address.Get(k); ok {
            result, err := plc.WriteHoldingRegister(conn, address, v)
            if err != nil {
                plcConfig.CurrentErr = err
                failedNumbers++
                logx.Errorf("plc write err:%v, address: %v, key: %v value: %v", err.Error(), address, k, v)
            } else {
                delete(paramsMap, k)
                logx.Infof("plc write ok: key: %v, value: %v, result: %v", k, v, result)
            }
        } else {
        address, ok := plc_address.Get(k)
        if !ok {
            logx.Errorf("miss param address, k:%v, v:%v", k, v)
            continue
        }
        err := service.PlcWriteDirect(plcConfig, address, v)
        if err != nil {
            plcConfig.CurrentErr = err
            failedNumbers++
            logx.Errorf("plc write err:%v, address: %v, key: %v value: %v", err.Error(), address, k, v)
        } else {
            delete(paramsMap, k)
            logx.Infof("plc write ok: key: %v, value: %v", k, v)
        }
    }
    if failedNumbers >= 1 { //写入plc失败, 重试
conf/apsClient.json
@@ -20,7 +20,8 @@
    "connMaxIdleTimeSecond": 3600
  },
  "Services":{
    "apsServer": "http://192.168.20.119:9081"
    "apsServer": "http://192.168.20.119:9081",
    "serial": "http://192.168.20.126:9987"
  },
  "nsqConf": {
    "NodeId": "wangpengfei",
conf/config.go
@@ -58,6 +58,7 @@
    Services struct {
        ApsServer string
        Serial    string
    }
    nsqConf struct {
@@ -128,5 +129,6 @@
    log.Printf("   System:                %+v", Conf.System)
    log.Printf("   Log:                   %+v", Conf.Log)
    log.Printf("   plc :                  %+v", Conf.PLC)
    log.Printf("   services :                  %+v", Conf.Services)
    log.Println("......................................................")
}
pkg/plc/plc4x.go
@@ -19,7 +19,7 @@
    mu          sync.Mutex
}
func NewPlcConnectionManager() *ConnectionManager {
func newPlcConnectionManager() *ConnectionManager {
    return &ConnectionManager{
        connections: make(map[string]plc4go.PlcConnection),
    }
@@ -33,7 +33,7 @@
    return conn, ok
}
var connectionManager = NewPlcConnectionManager()
var connectionManager = newPlcConnectionManager()
func (cm *ConnectionManager) AddConnection(address string, connection plc4go.PlcConnection) {
    cm.mu.Lock()
@@ -50,7 +50,7 @@
    // 创建一个上下文,并设置 3 秒超时
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()
    conn, err := NewGetModbusConnection(ctx, ipAddr)
    conn, err := newGetModbusConnection(ctx, ipAddr)
    if err != nil {
        return nil, err
    }
@@ -58,7 +58,7 @@
    return conn, nil
}
func NewGetModbusConnection(ctx context.Context, ipAddr string) (plc4go.PlcConnection, error) {
func newGetModbusConnection(ctx context.Context, ipAddr string) (plc4go.PlcConnection, error) {
    // 创建驱动管理器
    driverManager := plc4go.NewPlcDriverManager()
    // 注册TCP传输
@@ -83,7 +83,7 @@
    }
}
func ReadHoldingRegisterSingle(connection plc4go.PlcConnection, address int) ([]byte, error) {
func readHoldingRegisterSingle(connection plc4go.PlcConnection, address int) ([]byte, error) {
    tag := fmt.Sprintf("tag:%v", address)
    tagAddress := fmt.Sprintf("holding-register:%d:UINT", address)
@@ -113,7 +113,7 @@
}
func ReadHoldingRegisterList(connection plc4go.PlcConnection, address, length int) ([]byte, error) {
func readHoldingRegisterList(connection plc4go.PlcConnection, address, length int) ([]byte, error) {
    tag := fmt.Sprintf("tag:%v:%v", address, length)
    tagAddress := fmt.Sprintf("holding-register:%d:UINT[%d]", address, length)
@@ -150,10 +150,10 @@
func ReadHoldingRegister(connection plc4go.PlcConnection, address, length int) ([]byte, error) {
    if length > 1 {
        return ReadHoldingRegisterList(connection, address, length)
        return readHoldingRegisterList(connection, address, length)
    }
    return ReadHoldingRegisterSingle(connection, address)
    return readHoldingRegisterSingle(connection, address)
}
func WriteHoldingRegister(connection plc4go.PlcConnection, address int, value any) (string, error) {
pkg/plccom/mitsubishi/protocol.go
New file
@@ -0,0 +1,113 @@
package mitsubishi
import (
    "fmt"
    "strconv"
    "unicode"
)
// ProtocolMitsubishi 实现了 SerialProtocol 接口,用于三菱设备的串口通信
type ProtocolMitsubishi struct{}
// ConvertLabelToAddress 将设备特定的标签转换为寄存器地址
func (p *ProtocolMitsubishi) ConvertLabelToAddress(label string) string {
    if label[0] != 'D' {
        return ""
    }
    num := label[1:]
    for _, c := range num {
        if !unicode.IsDigit(c) {
            return ""
        }
    }
    var val int
    fmt.Sscanf(num, "%X", &val)
    vaddr := 0x1000 + val*2
    return fmt.Sprintf("%X", vaddr)
}
// MakeReadProtocol 创建读取数据的协议
func (p *ProtocolMitsubishi) MakeReadProtocol(addr string) []byte {
    // 02 表示开始 + 30 表示读数据 + 4位地址位 + 02 数据长度 + 03 停止 + 偶校验
    pro := []byte{0x30}
    pro = append(pro, []byte(addr)...)
    pro = append(pro, []byte("02")...)
    pro = append(pro, []byte{0x03}...)
    crc := crc(pro)
    proto := []byte{0x02}
    proto = append(proto, pro...)
    proto = append(proto, []byte(crc)...)
    return proto
}
// MakeWriteProtocol 创建写入数据的协议
func (p *ProtocolMitsubishi) MakeWriteProtocol(addr string, value int) []byte {
    valueStr := fmt.Sprintf("000%X", value)
    val := valueStr[len(valueStr)-2:]
    val += valueStr[len(valueStr)-4 : len(valueStr)-2]
    // 02 表示开始 + 31 表示写数据 + 4位地址位 + 02 数据长度 + 03 停止 + 偶校验
    pro := []byte{0x31}
    pro = append(pro, []byte(addr)...)
    pro = append(pro, []byte("02")...)
    pro = append(pro, val...)
    pro = append(pro, []byte{0x03}...)
    crc := crc(pro)
    proto := []byte{0x02}
    proto = append(proto, pro...)
    proto = append(proto, []byte(crc)...)
    return proto
}
// ParseReadValue 解析从设备读取的值
func (p *ProtocolMitsubishi) ParseReadValue(data []byte) int {
    if len(data) < 6 {
        return -1
    }
    vhex := data[1:5]
    v := vhex[2:]
    v = append(v, vhex[0:2]...)
    vstr := string(v)
    var bint int64
    bint, _ = strconv.ParseInt(vstr, 16, 32)
    return int(bint)
}
// ParseWriteValue 解析写入设备的结果
func (p *ProtocolMitsubishi) ParseWriteValue(data []byte) (v int, ok bool) {
    if len(data) == 0 {
        return -1, false
    }
    v = int(data[0])
    if v == 0x15 {
        // 失败
        return v, false
    }
    if v == 0x06 {
        // 成功
        return v, ok
    }
    return v, false
}
// crc 取后两位0x值
func crc(data []byte) string {
    // 计算校验码, 30 + 地址编码 + 数据长度 + 03 , 十六进制结果取后两位.
    var sum uint8
    for _, d := range data {
        sum += d
    }
    sumStr := fmt.Sprintf("0%X", sum)
    return sumStr[len(sumStr)-2:]
}
pkg/plccom/plccom.go
New file
@@ -0,0 +1,114 @@
package plccom
import (
    "apsClient/pkg/logx"
    "apsClient/pkg/plccom/mitsubishi"
    "encoding/base64"
    "errors"
    "fmt"
    "io"
    "net/http"
    "strings"
)
// SerialProtocol 是串口通信协议的通用接口
type SerialProtocol interface {
    // ConvertLabelToAddress 将设备特定的标签转换为寄存器地址
    ConvertLabelToAddress(label string) string
    // MakeReadProtocol 创建读取数据的协议
    MakeReadProtocol(addr string) []byte
    // MakeWriteProtocol 创建写入数据的协议
    MakeWriteProtocol(addr string, value int) []byte
    // ParseReadValue 解析从设备读取的值
    ParseReadValue(data []byte) int
    // ParseWriteValue 解析写入设备的结果,并返回结果和是否成功
    ParseWriteValue(data []byte) (int, bool)
}
const (
    DeviceTypeMitsubishi = "mitsubishi"
)
// LoadProtocol 根据配置加载不同的协议实现
func LoadProtocol(deviceType string) (SerialProtocol, error) {
    var protocol SerialProtocol
    switch deviceType {
    case DeviceTypeMitsubishi:
        protocol = &mitsubishi.ProtocolMitsubishi{} // 使用三菱设备协议
    // case "OtherDevice":
    //     protocol = &OtherDeviceProtocol{} // 使用其他设备的协议
    default:
        return nil, fmt.Errorf("unsupported device type: %s", deviceType)
    }
    return protocol, nil
}
func ReadPLC(deviceType, url, label string) (val int, err error) {
    protocol, err := LoadProtocol(deviceType)
    if err != nil {
        return 0, err
    }
    addr := protocol.ConvertLabelToAddress(label)
    proto := protocol.MakeReadProtocol(addr)
    bp := base64.StdEncoding.EncodeToString(proto)
    resp, err := http.Get(url + bp)
    if err != nil {
        logx.Errorf("ReadPLC http get failed:%v", err)
        return 0, err
    }
    body := readBody(resp.Body)
    data, err := base64.StdEncoding.DecodeString(string(body))
    if err != nil {
        logx.Errorf("ReadPLC base64.StdEncoding.DecodeString failed:%v", err)
        return 0, err
    }
    val = protocol.ParseReadValue(data)
    fmt.Println("read PLC:", val)
    return val, nil
}
func readBody(r io.ReadCloser) []byte {
    body, e := io.ReadAll(r)
    defer r.Close()
    if e != nil {
        fmt.Println("ReadAll resp body error:", e)
        return nil
    }
    return body
}
func WritePLC(deviceType, url, label string, val int) error {
    protocol, err := LoadProtocol(deviceType)
    if err != nil {
        return err
    }
    addr := protocol.ConvertLabelToAddress(label)
    proto := protocol.MakeWriteProtocol(addr, val)
    bp := base64.StdEncoding.EncodeToString(proto)
    resp, err := http.Post(url, "text/plain", strings.NewReader(bp))
    if err != nil {
        logx.Errorf("write plc http post failed:%v", err)
        return err
    }
    body := readBody(resp.Body)
    data, err := base64.StdEncoding.DecodeString(string(body))
    if err != nil {
        logx.Errorf("write plc base64 StdEncoding DecodeString:%v", err)
        return err
    }
    val, ok := protocol.ParseWriteValue(data)
    if !ok {
        logx.Errorf("serial write failed, response:%v", val)
        return errors.New("failed")
    }
    logx.Errorf("serial write success, response:%v", val)
    return nil
}
service/plc.go
@@ -1,13 +1,16 @@
package service
import (
    "apsClient/conf"
    "apsClient/constvar"
    "apsClient/model"
    "apsClient/pkg/logx"
    "apsClient/pkg/plc"
    "apsClient/pkg/plccom"
    "encoding/binary"
    "errors"
    "fmt"
    "github.com/spf13/cast"
)
func PlcRead(plcConfig *model.DevicePlc, fieldType constvar.PlcStartAddressType) (val interface{}, err error) {
@@ -25,31 +28,42 @@
            dataLength = pc.Length
        }
    }
    ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port)
    conn, err := plc.GetModbusConnection(ipAddr)
    if err != nil {
        logx.Errorf("PlcRead 连接plc失败: %v", err.Error())
        return
    }
    if plcConfig.Method == constvar.PlcMethodModbusTCP {
        ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port)
    rawData, err := plc.ReadHoldingRegister(conn, startAddress, dataLength)
    if err != nil {
        logx.Errorf("PlcRead 获取plc数据失败: %v", err.Error())
        return
    }
    switch valueType {
    case constvar.PlcStartAddressValueTypeString:
        return string(rawData), nil
    case constvar.PlcStartAddressValueTypeInt:
        if len(rawData) == 2 {
            return int(binary.BigEndian.Uint16(rawData)), nil
        } else {
            logx.Errorf("plc read get an unknown int value: %v, address:%v", rawData, startAddress)
            return nil, errors.New(fmt.Sprintf("unknown int value:%v", rawData))
        conn, err := plc.GetModbusConnection(ipAddr)
        if err != nil {
            logx.Errorf("PlcRead 连接plc失败: %v", err.Error())
            return nil, err
        }
        rawData, err := plc.ReadHoldingRegister(conn, startAddress, dataLength)
        if err != nil {
            logx.Errorf("PlcRead 获取plc数据失败: %v", err.Error())
            return nil, err
        }
        switch valueType {
        case constvar.PlcStartAddressValueTypeString:
            return string(rawData), nil
        case constvar.PlcStartAddressValueTypeInt:
            if len(rawData) == 2 {
                return int(binary.BigEndian.Uint16(rawData)), nil
            } else {
                logx.Errorf("plc read get an unknown int value: %v, address:%v", rawData, startAddress)
                return nil, errors.New(fmt.Sprintf("unknown int value:%v", rawData))
            }
        }
        return nil, errors.New("undefined value type")
    } else if plcConfig.Method == constvar.PlcMethodSerial {
        ipAddr = conf.Conf.Services.Serial
        if ipAddr == "" {
            return nil, errors.New("conf.Conf.Services.Serial config not set yet")
        }
        label := fmt.Sprintf("D%d", startAddress)
        return plccom.ReadPLC(plccom.DeviceTypeMitsubishi, ipAddr, label)
    }
    return nil, errors.New("undefined value type")
    return nil, errors.New("interface type not support")
}
func PlcWrite(plcConfig *model.DevicePlc, fieldType constvar.PlcStartAddressType, value interface{}) (err error) {
@@ -68,20 +82,65 @@
            startAddress = pc.StartAddress
        }
    }
    ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port)
    conn, err := plc.GetModbusConnection(ipAddr)
    if err != nil {
        logx.Errorf("plc write failed, 连接plc失败: %v", err.Error())
        plcConfig.CurrentErr = err
        return PlcWrite(plcConfig, fieldType, value)
    }
    result, err := plc.WriteHoldingRegister(conn, 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, value)
    if plcConfig.Method == constvar.PlcMethodModbusTCP {
        ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port)
        conn, err := plc.GetModbusConnection(ipAddr)
        if err != nil {
            logx.Errorf("plc write failed, 连接plc失败: %v", err.Error())
            plcConfig.CurrentErr = err
            return PlcWrite(plcConfig, fieldType, value)
        }
        result, err := plc.WriteHoldingRegister(conn, 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, value)
        }
        logx.Infof("plc write ok, address: %v, value: %v, result: %v", startAddress, value, result)
    } 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))
    }
    logx.Infof("plc write ok, address: %v, value: %v, result: %v", startAddress, value, result)
    return
}
func PlcWriteDirect(plcConfig *model.DevicePlc, address int, value interface{}) (err error) {
    var (
        ipAddr string
    )
    if plcConfig.CurrentTryTimes > plcConfig.MaxTryTimes {
        return plcConfig.CurrentErr
    }
    plcConfig.CurrentTryTimes++
    if plcConfig.Method == constvar.PlcMethodModbusTCP {
        ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port)
        conn, err := plc.GetModbusConnection(ipAddr)
        if err != nil {
            logx.Errorf("plc write failed, 连接plc失败: %v", err.Error())
            plcConfig.CurrentErr = err
            return PlcWriteDirect(plcConfig, address, value)
        }
        result, err := plc.WriteHoldingRegister(conn, address, value)
        if err != nil {
            logx.Errorf("plc write failed, address: %v, value: %v, err: %v", address, value, err.Error())
            plcConfig.CurrentErr = err
            return PlcWriteDirect(plcConfig, address, value)
        }
        logx.Infof("plc write ok, address: %v, value: %v, result: %v", address, value, result)
    } 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", address)
        return plccom.WritePLC(plccom.DeviceTypeMitsubishi, ipAddr, label, cast.ToInt(value))
    }
    return
}