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