From d4272e1b692515af9c47799a658e395703d13555 Mon Sep 17 00:00:00 2001
From: zhangqian <zhangqian@123.com>
Date: 星期四, 07 九月 2023 21:22:49 +0800
Subject: [PATCH] 支持串口读写
---
service/plc.go | 127 +++++++++++++----
pkg/plc/plc4x.go | 16 +-
conf/config.go | 2
conf/apsClient.json | 3
pkg/plccom/plccom.go | 114 ++++++++++++++++
pkg/plccom/mitsubishi/protocol.go | 113 ++++++++++++++++
api/v1/task.go | 28 +--
7 files changed, 344 insertions(+), 59 deletions(-)
diff --git a/api/v1/task.go b/api/v1/task.go
index 74287d5..7fb3b50 100644
--- a/api/v1/task.go
+++ b/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澶辫触, 閲嶈瘯
diff --git a/conf/apsClient.json b/conf/apsClient.json
index ed4e16c..f93e770 100644
--- a/conf/apsClient.json
+++ b/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",
diff --git a/conf/config.go b/conf/config.go
index 65b12ca..bf7ffbd 100644
--- a/conf/config.go
+++ b/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("......................................................")
}
diff --git a/pkg/plc/plc4x.go b/pkg/plc/plc4x.go
index 7e386e6..529c516 100644
--- a/pkg/plc/plc4x.go
+++ b/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) {
diff --git a/pkg/plccom/mitsubishi/protocol.go b/pkg/plccom/mitsubishi/protocol.go
new file mode 100644
index 0000000..17ba649
--- /dev/null
+++ b/pkg/plccom/mitsubishi/protocol.go
@@ -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 瑙f瀽浠庤澶囪鍙栫殑鍊�
+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 瑙f瀽鍐欏叆璁惧鐨勭粨鏋�
+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:]
+}
diff --git a/pkg/plccom/plccom.go b/pkg/plccom/plccom.go
new file mode 100644
index 0000000..1396a1a
--- /dev/null
+++ b/pkg/plccom/plccom.go
@@ -0,0 +1,114 @@
+package plccom
+
+import (
+ "apsClient/pkg/logx"
+ "apsClient/pkg/plccom/mitsubishi"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+)
+
+// SerialProtocol 鏄覆鍙i�氫俊鍗忚鐨勯�氱敤鎺ュ彛
+type SerialProtocol interface {
+ // ConvertLabelToAddress 灏嗚澶囩壒瀹氱殑鏍囩杞崲涓哄瘎瀛樺櫒鍦板潃
+ ConvertLabelToAddress(label string) string
+
+ // MakeReadProtocol 鍒涘缓璇诲彇鏁版嵁鐨勫崗璁�
+ MakeReadProtocol(addr string) []byte
+
+ // MakeWriteProtocol 鍒涘缓鍐欏叆鏁版嵁鐨勫崗璁�
+ MakeWriteProtocol(addr string, value int) []byte
+
+ // ParseReadValue 瑙f瀽浠庤澶囪鍙栫殑鍊�
+ ParseReadValue(data []byte) int
+
+ // ParseWriteValue 瑙f瀽鍐欏叆璁惧鐨勭粨鏋滐紝骞惰繑鍥炵粨鏋滃拰鏄惁鎴愬姛
+ 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
+}
diff --git a/service/plc.go b/service/plc.go
index 090b4bc..f2bbd0b 100644
--- a/service/plc.go
+++ b/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
}
--
Gitblit v1.8.0