| | |
| | | _ "apsClient/model/response" |
| | | "apsClient/pkg/contextx" |
| | | "apsClient/pkg/ecode" |
| | | "apsClient/pkg/logx" |
| | | "apsClient/pkg/plc" |
| | | "apsClient/service" |
| | | "encoding/binary" |
| | | "fmt" |
| | | "github.com/gin-gonic/gin" |
| | | "github.com/spf13/cast" |
| | | ) |
| | | |
| | | type PlcApi struct{} |
| | | |
| | | // GetProductProgress |
| | | // @Tags 获取动态数据 |
| | | // @Tags 生产数量 |
| | | // @Summary 获取生产进度 |
| | | // @Produce application/json |
| | | // @Success 200 {object} contextx.Response{data=response.ProductProgress} "成功" |
| | |
| | | return |
| | | } |
| | | |
| | | var startAddress int |
| | | var valueType string |
| | | var dataLength int |
| | | var ipAddr string |
| | | |
| | | for _, pc := range plcConfig.Details { |
| | | if pc.FieldName == constvar.PlcStartAddressTypeFinishNumber { |
| | | startAddress = pc.StartAddress |
| | | valueType = pc.Type |
| | | dataLength = pc.Length |
| | | } |
| | | ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port) |
| | | finishNumber, err := service.PlcRead(plcConfig, constvar.PlcStartAddressTypeFinishNumber) |
| | | if err != nil { |
| | | ctx.FailWithMsg(ecode.UnknownErr, "读取数据失败,请检查plc配置") |
| | | return |
| | | } |
| | | totalNumber, err := service.PlcRead(plcConfig, constvar.PlcStartAddressTypeTotalNumber) |
| | | if err != nil { |
| | | ctx.FailWithMsg(ecode.UnknownErr, "读取数据失败,请检查plc配置") |
| | | return |
| | | } |
| | | |
| | | resp := new(response.ProductProgress) |
| | | if startAddress == 0 || valueType == "" { |
| | | ctx.OkWithDetailed(resp) |
| | | logx.Warnf("请先配置PLC") |
| | | return |
| | | } |
| | | conn, err := plc.NewModbusConnection(ipAddr) |
| | | if err != nil { |
| | | ctx.OkWithDetailed(resp) |
| | | logx.Errorf("GetProductProgress 连接plc失败: %v", err.Error()) |
| | | return |
| | | } |
| | | defer conn.Close() |
| | | |
| | | rawData, err := plc.ReadHoldingRegister(conn, startAddress, dataLength) |
| | | if err != nil { |
| | | ctx.OkWithDetailed(resp) |
| | | logx.Errorf("GetProductProgress 获取plc数据失败: %v", err.Error()) |
| | | return |
| | | } |
| | | resp.FinishNumber = int(binary.BigEndian.Uint16(rawData)) |
| | | if err != nil { |
| | | ctx.OkWithDetailed(resp) |
| | | logx.Errorf("GetProductProgress 获取生产进度数据解析失败: %v, data: %v, valueType:%v", err.Error(), rawData, valueType) |
| | | return |
| | | } |
| | | resp.FinishNumber = cast.ToInt(finishNumber) |
| | | resp.TotalNumber = cast.ToInt(totalNumber) |
| | | ctx.OkWithDetailed(resp) |
| | | } |
| | | |
| | | // SetProductNumber |
| | | // @Tags 生产数量 |
| | | // @Summary 设置生产总量 |
| | | // @Produce application/json |
| | | // @Success 200 {object} contextx.Response{} "成功" |
| | | // @Router /v1/plc/setProductNumber [post] |
| | | func (slf *PlcApi) SetProductNumber(c *gin.Context) { |
| | | ctx, ok := contextx.NewContext(c, nil) |
| | | if !ok { |
| | | return |
| | | } |
| | | |
| | | taskData, code := service.NewTaskService().GetTask() |
| | | if code != ecode.OK { |
| | | ctx.Fail(code) |
| | | return |
| | | } |
| | | |
| | | if taskData.Order == nil { |
| | | ctx.FailWithMsg(ecode.UnknownErr, "当前没有待生产工单") |
| | | return |
| | | } |
| | | |
| | | plcConfig, code := service.NewDevicePlcService().GetDevicePlc() |
| | | if code != ecode.OK { |
| | | ctx.FailWithMsg(ecode.UnknownErr, "请先配置PLC") |
| | | return |
| | | } |
| | | |
| | | err := service.PlcWrite(plcConfig, constvar.PlcStartAddressTypeFinishNumber, taskData.Order.Amount.IntPart()) |
| | | if err != nil { |
| | | ctx.FailWithMsg(ecode.UnknownErr, "设置失败,请检查plc配置") |
| | | return |
| | | } |
| | | ctx.Ok() |
| | | } |
| | |
| | | NsqTopicProcessParamsResponse = "aps.%v.processParams.response" |
| | | ) |
| | | |
| | | type PlcStartAddressType string |
| | | type PlcStartAddressType int |
| | | |
| | | const ( |
| | | PlcStartAddressTypeFinishNumber = 1 |
| | | PlcStartAddressTypeTotalNumber = 2 |
| | | PlcStartAddressTypeFinishNumber PlcStartAddressType = 1 |
| | | PlcStartAddressTypeTotalNumber PlcStartAddressType = 2 |
| | | ) |
| | | |
| | | type PlcStartAddressValueType string |
| | | |
| | | const ( |
| | | PlcStartAddressValueTypeString PlcStartAddressValueType = "string" |
| | | PlcStartAddressValueTypeInt PlcStartAddressValueType = "int" |
| | | ) |
| | | |
| | | const ( |
| | |
| | | "application/json" |
| | | ], |
| | | "tags": [ |
| | | "获取动态数据" |
| | | "生产数量" |
| | | ], |
| | | "summary": "获取生产进度", |
| | | "responses": { |
| | |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | "/v1/plc/setProductNumber": { |
| | | "post": { |
| | | "produces": [ |
| | | "application/json" |
| | | ], |
| | | "tags": [ |
| | | "生产数量" |
| | | ], |
| | | "summary": "设置生产总量", |
| | | "responses": { |
| | | "200": { |
| | | "description": "成功", |
| | | "schema": { |
| | | "$ref": "#/definitions/contextx.Response" |
| | | } |
| | | } |
| | | } |
| | |
| | | "PlcMethodSerial" |
| | | ] |
| | | }, |
| | | "constvar.PlcStartAddressType": { |
| | | "type": "integer", |
| | | "enum": [ |
| | | 1, |
| | | 2 |
| | | ], |
| | | "x-enum-varnames": [ |
| | | "PlcStartAddressTypeFinishNumber", |
| | | "PlcStartAddressTypeTotalNumber" |
| | | ] |
| | | }, |
| | | "constvar.PlcStartAddressValueType": { |
| | | "type": "string", |
| | | "enum": [ |
| | | "string", |
| | | "int" |
| | | ], |
| | | "x-enum-varnames": [ |
| | | "PlcStartAddressValueTypeString", |
| | | "PlcStartAddressValueTypeInt" |
| | | ] |
| | | }, |
| | | "contextx.Response": { |
| | | "type": "object", |
| | | "properties": { |
| | |
| | | "properties": { |
| | | "fieldName": { |
| | | "description": "对应系统字段", |
| | | "type": "integer" |
| | | "allOf": [ |
| | | { |
| | | "$ref": "#/definitions/constvar.PlcStartAddressType" |
| | | } |
| | | ] |
| | | }, |
| | | "length": { |
| | | "description": "数据长度", |
| | |
| | | }, |
| | | "type": { |
| | | "description": "数据类型", |
| | | "type": "string" |
| | | "allOf": [ |
| | | { |
| | | "$ref": "#/definitions/constvar.PlcStartAddressValueType" |
| | | } |
| | | ] |
| | | } |
| | | } |
| | | }, |
| | |
| | | "properties": { |
| | | "finishNumber": { |
| | | "type": "integer" |
| | | }, |
| | | "totalNumber": { |
| | | "type": "integer" |
| | | } |
| | | } |
| | | }, |
| | |
| | | "application/json" |
| | | ], |
| | | "tags": [ |
| | | "获取动态数据" |
| | | "生产数量" |
| | | ], |
| | | "summary": "获取生产进度", |
| | | "responses": { |
| | |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | "/v1/plc/setProductNumber": { |
| | | "post": { |
| | | "produces": [ |
| | | "application/json" |
| | | ], |
| | | "tags": [ |
| | | "生产数量" |
| | | ], |
| | | "summary": "设置生产总量", |
| | | "responses": { |
| | | "200": { |
| | | "description": "成功", |
| | | "schema": { |
| | | "$ref": "#/definitions/contextx.Response" |
| | | } |
| | | } |
| | | } |
| | |
| | | "PlcMethodSerial" |
| | | ] |
| | | }, |
| | | "constvar.PlcStartAddressType": { |
| | | "type": "integer", |
| | | "enum": [ |
| | | 1, |
| | | 2 |
| | | ], |
| | | "x-enum-varnames": [ |
| | | "PlcStartAddressTypeFinishNumber", |
| | | "PlcStartAddressTypeTotalNumber" |
| | | ] |
| | | }, |
| | | "constvar.PlcStartAddressValueType": { |
| | | "type": "string", |
| | | "enum": [ |
| | | "string", |
| | | "int" |
| | | ], |
| | | "x-enum-varnames": [ |
| | | "PlcStartAddressValueTypeString", |
| | | "PlcStartAddressValueTypeInt" |
| | | ] |
| | | }, |
| | | "contextx.Response": { |
| | | "type": "object", |
| | | "properties": { |
| | |
| | | "properties": { |
| | | "fieldName": { |
| | | "description": "对应系统字段", |
| | | "type": "integer" |
| | | "allOf": [ |
| | | { |
| | | "$ref": "#/definitions/constvar.PlcStartAddressType" |
| | | } |
| | | ] |
| | | }, |
| | | "length": { |
| | | "description": "数据长度", |
| | |
| | | }, |
| | | "type": { |
| | | "description": "数据类型", |
| | | "type": "string" |
| | | "allOf": [ |
| | | { |
| | | "$ref": "#/definitions/constvar.PlcStartAddressValueType" |
| | | } |
| | | ] |
| | | } |
| | | } |
| | | }, |
| | |
| | | "properties": { |
| | | "finishNumber": { |
| | | "type": "integer" |
| | | }, |
| | | "totalNumber": { |
| | | "type": "integer" |
| | | } |
| | | } |
| | | }, |
| | |
| | | x-enum-varnames: |
| | | - PlcMethodModbusTCP |
| | | - PlcMethodSerial |
| | | constvar.PlcStartAddressType: |
| | | enum: |
| | | - 1 |
| | | - 2 |
| | | type: integer |
| | | x-enum-varnames: |
| | | - PlcStartAddressTypeFinishNumber |
| | | - PlcStartAddressTypeTotalNumber |
| | | constvar.PlcStartAddressValueType: |
| | | enum: |
| | | - string |
| | | - int |
| | | type: string |
| | | x-enum-varnames: |
| | | - PlcStartAddressValueTypeString |
| | | - PlcStartAddressValueTypeInt |
| | | contextx.Response: |
| | | properties: |
| | | code: |
| | |
| | | model.DevicePlcAddress: |
| | | properties: |
| | | fieldName: |
| | | allOf: |
| | | - $ref: '#/definitions/constvar.PlcStartAddressType' |
| | | description: 对应系统字段 |
| | | type: integer |
| | | length: |
| | | description: 数据长度 |
| | | type: integer |
| | |
| | | description: 数据起始地址 |
| | | type: integer |
| | | type: |
| | | allOf: |
| | | - $ref: '#/definitions/constvar.PlcStartAddressValueType' |
| | | description: 数据类型 |
| | | type: string |
| | | type: object |
| | | model.NetConfig: |
| | | properties: |
| | |
| | | properties: |
| | | finishNumber: |
| | | type: integer |
| | | totalNumber: |
| | | type: integer |
| | | type: object |
| | | response.TaskData: |
| | | properties: |
| | |
| | | type: object |
| | | summary: 获取生产进度 |
| | | tags: |
| | | - 获取动态数据 |
| | | - 生产数量 |
| | | /v1/plc/setProductNumber: |
| | | post: |
| | | produces: |
| | | - application/json |
| | | responses: |
| | | "200": |
| | | description: 成功 |
| | | schema: |
| | | $ref: '#/definitions/contextx.Response' |
| | | summary: 设置生产总量 |
| | | tags: |
| | | - 生产数量 |
| | | /v1/plcBrand/add: |
| | | post: |
| | | parameters: |
| | |
| | | } |
| | | |
| | | DevicePlcAddress struct { |
| | | StartAddress int `json:"startAddress"` // 数据起始地址 |
| | | Length int `json:"length"` // 数据长度 |
| | | Type string `json:"type"` // 数据类型 |
| | | FieldName int `json:"fieldName"` // 对应系统字段 |
| | | StartAddress int `json:"startAddress"` // 数据起始地址 |
| | | Length int `json:"length"` // 数据长度 |
| | | Type constvar.PlcStartAddressValueType `json:"type"` // 数据类型 |
| | | FieldName constvar.PlcStartAddressType `json:"fieldName"` // 对应系统字段 |
| | | } |
| | | |
| | | DevicePlcSearch struct { |
| | |
| | | |
| | | type ProductProgress struct { |
| | | FinishNumber int `json:"finishNumber"` |
| | | TotalNumber int `json:"totalNumber"` |
| | | } |
New file |
| | |
| | | package plc |
| | | |
| | | import ( |
| | | "encoding/hex" |
| | | "errors" |
| | | "fmt" |
| | | "io" |
| | | "strings" |
| | | "time" |
| | | |
| | | "github.com/jacobsa/go-serial/serial" |
| | | ) |
| | | |
| | | /* |
| | | 定义串口客户端 |
| | | */ |
| | | |
| | | type SerialClient struct { |
| | | readWriter io.ReadWriteCloser |
| | | OutBuffer chan string |
| | | PortName string |
| | | baudRate uint |
| | | } |
| | | |
| | | func NewSerialClient(portName string, baudRate uint) (*SerialClient, error) { |
| | | c := &SerialClient{ |
| | | readWriter: nil, |
| | | OutBuffer: make(chan string, 1024), |
| | | PortName: portName, |
| | | baudRate: baudRate, |
| | | } |
| | | r, err := c.open(portName, baudRate) |
| | | if err != nil { |
| | | return nil, err |
| | | } |
| | | |
| | | c.readWriter = r |
| | | return c, nil |
| | | } |
| | | |
| | | func (c *SerialClient) Start() { |
| | | for { |
| | | buf := make([]byte, 1024) |
| | | n, err := c.readWriter.Read(buf) |
| | | if err != nil { |
| | | if err != io.EOF { |
| | | //尝试重新打开 |
| | | r, err := c.open(c.PortName, c.baudRate) |
| | | if err != nil { |
| | | time.Sleep(2 * time.Second) |
| | | continue |
| | | } |
| | | c.readWriter = r |
| | | } |
| | | continue |
| | | } |
| | | |
| | | buf = buf[:n] |
| | | res := hex.EncodeToString(buf) |
| | | if strings.TrimSpace(res) == "" { |
| | | continue |
| | | } |
| | | fmt.Printf("Rx: %s\n", res) |
| | | c.OutBuffer <- res |
| | | } |
| | | } |
| | | |
| | | func (c *SerialClient) open(portName string, baudRate uint) (io.ReadWriteCloser, error) { |
| | | options := serial.OpenOptions{ |
| | | PortName: portName, |
| | | BaudRate: baudRate, |
| | | DataBits: 7, |
| | | StopBits: 1, |
| | | MinimumReadSize: 4, |
| | | ParityMode: serial.PARITY_EVEN, |
| | | } |
| | | |
| | | r, err := serial.Open(options) |
| | | if err != nil { |
| | | return nil, err |
| | | } |
| | | |
| | | c.readWriter = r |
| | | |
| | | return r, err |
| | | } |
| | | |
| | | func (c *SerialClient) Close() { |
| | | if c.readWriter != nil { |
| | | c.readWriter.Close() |
| | | } |
| | | } |
| | | |
| | | func (c *SerialClient) SendMsg(data string) (int, error) { |
| | | if c.readWriter == nil { |
| | | return 0, errors.New("serial port is closed") |
| | | } |
| | | |
| | | bytes, err := hex.DecodeString(data) |
| | | if err != nil { |
| | | return 0, err |
| | | } |
| | | |
| | | return c.readWriter.Write(bytes) |
| | | } |
| | | |
| | | func (c *SerialClient) ReadHoldRegister(addr string) (int, error) { |
| | | if c.readWriter == nil { |
| | | return 0, errors.New("serial port is closed") |
| | | } |
| | | |
| | | // 三菱fn系列串口协议 |
| | | // 02 表示开始 + 30 表示读数据 + 4位地址位 + 02 数据长度 + 03 停止 + 偶校验 |
| | | |
| | | // 先组装数据 |
| | | // 读取的地址 |
| | | dataBytes := append([]byte{0x30}, []byte(addr)...) |
| | | |
| | | // 读取的长度 |
| | | dataBytes = append(dataBytes, []byte("02")...) |
| | | |
| | | // 停止 |
| | | dataBytes = append(dataBytes, []byte{0x03}...) |
| | | |
| | | // 计算校验码, 30 + 地址编码 + 数据长度 + 03 , 十六进制结果取后两位. |
| | | var sum uint8 |
| | | for _, d := range dataBytes { |
| | | sum += d |
| | | } |
| | | |
| | | sumStr := fmt.Sprintf("%x", sum) |
| | | |
| | | fmt.Printf("sumStr: %s\n", sumStr) |
| | | |
| | | var checkCode string |
| | | if len(sumStr) < 2 { |
| | | checkCode = "0" + sumStr |
| | | } else { |
| | | checkCode = sumStr[len(sumStr)-2:] |
| | | } |
| | | |
| | | var bytes = []byte{0x02} |
| | | // 添加数据 |
| | | bytes = append(bytes, dataBytes...) |
| | | // 添加校验 |
| | | bytes = append(bytes, []byte(checkCode)...) |
| | | |
| | | fmt.Printf("Tx: %X \n", bytes) |
| | | |
| | | // 写 |
| | | if n, err := c.readWriter.Write(bytes); err != nil { |
| | | return n, err |
| | | } else { |
| | | fmt.Printf("Tx:len:%d\n", n) |
| | | } |
| | | |
| | | // 读 |
| | | buf := make([]byte, 1024) |
| | | n, err := c.readWriter.Read(buf) |
| | | if err != nil { |
| | | return n, err |
| | | } |
| | | |
| | | buf = buf[:n] |
| | | |
| | | fmt.Printf("Rx: %X\nRx len:%d", buf, n) |
| | | fmt.Printf("value: %d\n", buf[2:6]) |
| | | |
| | | return n, nil |
| | | } |
| | | |
| | | func (c *SerialClient) WriteHoldRegister(addr string) (int, error) { |
| | | if c.readWriter == nil { |
| | | return 0, errors.New("serial port is closed") |
| | | } |
| | | |
| | | // 三菱fn系列串口协议 |
| | | // 02 表示开始 + 31 表示写数据 + 4位地址位 + 02 数据长度 + 03 停止 + 偶校验 |
| | | |
| | | // 先组装数据 |
| | | // 读取的地址 |
| | | dataBytes := append([]byte{0x31}, []byte(addr)...) |
| | | |
| | | // 读取的长度 |
| | | dataBytes = append(dataBytes, []byte("02")...) |
| | | |
| | | // 停止 |
| | | dataBytes = append(dataBytes, []byte{0x03}...) |
| | | |
| | | // 计算校验码, 30 + 地址编码 + 数据长度 + 03 , 十六进制结果取后两位. |
| | | var sum uint8 |
| | | for _, d := range dataBytes { |
| | | sum += d |
| | | } |
| | | |
| | | sumStr := fmt.Sprintf("%x", sum) |
| | | |
| | | fmt.Printf("sumStr: %s\n", sumStr) |
| | | |
| | | var checkCode string |
| | | if len(sumStr) < 2 { |
| | | checkCode = "0" + sumStr |
| | | } else { |
| | | checkCode = sumStr[len(sumStr)-2:] |
| | | } |
| | | |
| | | var bytes = []byte{0x02} |
| | | // 添加数据 |
| | | bytes = append(bytes, dataBytes...) |
| | | // 添加校验 |
| | | bytes = append(bytes, []byte(checkCode)...) |
| | | |
| | | fmt.Printf("Tx: %X \n", bytes) |
| | | |
| | | // 写 |
| | | if n, err := c.readWriter.Write(bytes); err != nil { |
| | | return n, err |
| | | } else { |
| | | fmt.Printf("Tx:len:%d\n", n) |
| | | } |
| | | |
| | | // 读 |
| | | buf := make([]byte, 1024) |
| | | n, err := c.readWriter.Read(buf) |
| | | if err != nil { |
| | | return n, err |
| | | } |
| | | |
| | | buf = buf[:n] |
| | | |
| | | fmt.Printf("Rx: %X\nRx len:%d", buf, n) |
| | | fmt.Printf("value: %d\n", buf[2:6]) |
| | | |
| | | return n, nil |
| | | } |
| | |
| | | plcGroup := v1Group.Group("plc") |
| | | { |
| | | plcGroup.GET("productProgress", plcApi.GetProductProgress) // 获取网络配置 |
| | | plcGroup.POST("setProductNumber", plcApi.SetProductNumber) // 下发生产总量 |
| | | } |
| | | |
| | | InitPlcBrandRouter(v1Group) |
New file |
| | |
| | | package service |
| | | |
| | | import ( |
| | | "apsClient/constvar" |
| | | "apsClient/model" |
| | | "apsClient/pkg/logx" |
| | | "apsClient/pkg/plc" |
| | | "encoding/binary" |
| | | "errors" |
| | | "fmt" |
| | | ) |
| | | |
| | | func PlcRead(plcConfig *model.DevicePlc, fieldType constvar.PlcStartAddressType) (val interface{}, err error) { |
| | | var ( |
| | | startAddress int |
| | | valueType constvar.PlcStartAddressValueType |
| | | dataLength int |
| | | ipAddr string |
| | | ) |
| | | |
| | | for _, pc := range plcConfig.Details { |
| | | if pc.FieldName == fieldType { |
| | | startAddress = pc.StartAddress |
| | | valueType = pc.Type |
| | | dataLength = pc.Length |
| | | } |
| | | ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port) |
| | | } |
| | | |
| | | conn, err := plc.NewModbusConnection(ipAddr) |
| | | if err != nil { |
| | | logx.Errorf("GetProductProgress 连接plc失败: %v", err.Error()) |
| | | return |
| | | } |
| | | defer conn.Close() |
| | | |
| | | rawData, err := plc.ReadHoldingRegister(conn, startAddress, dataLength) |
| | | if err != nil { |
| | | logx.Errorf("GetProductProgress 获取plc数据失败: %v", err.Error()) |
| | | return |
| | | } |
| | | switch valueType { |
| | | case constvar.PlcStartAddressValueTypeString: |
| | | return string(rawData), nil |
| | | case constvar.PlcStartAddressValueTypeInt: |
| | | return int(binary.BigEndian.Uint16(rawData)), nil |
| | | } |
| | | return nil, errors.New("undefined value type") |
| | | } |
| | | |
| | | func PlcWrite(plcConfig *model.DevicePlc, fieldType constvar.PlcStartAddressType, value interface{}) (err error) { |
| | | var ( |
| | | startAddress int |
| | | ipAddr string |
| | | ) |
| | | |
| | | for _, pc := range plcConfig.Details { |
| | | if pc.FieldName == fieldType { |
| | | startAddress = pc.StartAddress |
| | | } |
| | | ipAddr = fmt.Sprintf("%s:%v", plcConfig.Address, plcConfig.Port) |
| | | } |
| | | |
| | | conn, err := plc.NewModbusConnection(ipAddr) |
| | | if err != nil { |
| | | logx.Errorf("GetProductProgress 连接plc失败: %v", err.Error()) |
| | | return |
| | | } |
| | | defer conn.Close() |
| | | |
| | | result, err := plc.WriteHoldingRegister(conn, startAddress, value) |
| | | if err != nil { |
| | | logx.Infof("plc write failed, address: %v, value: %v, err: %v", startAddress, value, err.Error()) |
| | | return |
| | | } |
| | | logx.Infof("plc write ok, address: %v, value: %v, result: %v", startAddress, value, result) |
| | | return |
| | | } |