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