| | |
| | | "apsClient/pkg/convertx" |
| | | "apsClient/pkg/ecode" |
| | | "apsClient/pkg/logx" |
| | | "apsClient/pkg/plc" |
| | | "apsClient/service" |
| | | "apsClient/service/plc_address" |
| | | "errors" |
| | |
| | | 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) |
| | | 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, result: %v", k, v, result) |
| | | } |
| | | } else { |
| | | logx.Errorf("miss param address, k:%v, v:%v", k, v) |
| | | logx.Infof("plc write ok: key: %v, value: %v", k, v) |
| | | } |
| | | } |
| | | if failedNumbers >= 1 { //写入plc失败, 重试 |
| | |
| | | "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", |
| | |
| | | |
| | | Services struct { |
| | | ApsServer string |
| | | Serial string |
| | | } |
| | | |
| | | nsqConf struct { |
| | |
| | | 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("......................................................") |
| | | } |
| | |
| | | mu sync.Mutex |
| | | } |
| | | |
| | | func NewPlcConnectionManager() *ConnectionManager { |
| | | func newPlcConnectionManager() *ConnectionManager { |
| | | return &ConnectionManager{ |
| | | connections: make(map[string]plc4go.PlcConnection), |
| | | } |
| | |
| | | return conn, ok |
| | | } |
| | | |
| | | var connectionManager = NewPlcConnectionManager() |
| | | var connectionManager = newPlcConnectionManager() |
| | | |
| | | func (cm *ConnectionManager) AddConnection(address string, connection plc4go.PlcConnection) { |
| | | cm.mu.Lock() |
| | |
| | | // 创建一个上下文,并设置 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 |
| | | } |
| | |
| | | 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传输 |
| | |
| | | } |
| | | } |
| | | |
| | | 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) |
| | | |
| | |
| | | |
| | | } |
| | | |
| | | 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) |
| | | |
| | |
| | | |
| | | 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) { |
New file |
| | |
| | | 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:] |
| | | } |
New file |
| | |
| | | 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 |
| | | } |
| | |
| | | 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) { |
| | |
| | | dataLength = pc.Length |
| | | } |
| | | } |
| | | |
| | | if plcConfig.Method == constvar.PlcMethodModbusTCP { |
| | | 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 |
| | | return nil, err |
| | | } |
| | | |
| | | rawData, err := plc.ReadHoldingRegister(conn, startAddress, dataLength) |
| | | if err != nil { |
| | | logx.Errorf("PlcRead 获取plc数据失败: %v", err.Error()) |
| | | return |
| | | return nil, err |
| | | } |
| | | switch valueType { |
| | | case constvar.PlcStartAddressValueTypeString: |
| | |
| | | } |
| | | } |
| | | 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("interface type not support") |
| | | } |
| | | |
| | | func PlcWrite(plcConfig *model.DevicePlc, fieldType constvar.PlcStartAddressType, value interface{}) (err error) { |
| | |
| | | startAddress = pc.StartAddress |
| | | } |
| | | } |
| | | |
| | | if plcConfig.Method == constvar.PlcMethodModbusTCP { |
| | | ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port) |
| | | conn, err := plc.GetModbusConnection(ipAddr) |
| | | if err != nil { |
| | |
| | | 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)) |
| | | } |
| | | 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 |
| | | } |