156 lines
4.7 KiB
Go
156 lines
4.7 KiB
Go
|
|
package protocol
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/binary"
|
|||
|
|
"fmt"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
MainBasicInfo = byte(0x01)
|
|||
|
|
MainRuntime = byte(0x20)
|
|||
|
|
MainQuery = byte(0x30)
|
|||
|
|
MainSetting = byte(0x40)
|
|||
|
|
MainRealtime = byte(0x50)
|
|||
|
|
MainHistorical = byte(0x60)
|
|||
|
|
SubValve = byte(0x0A)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// DataBlock 是主标识下的一个子标识数据块。
|
|||
|
|
type DataBlock struct {
|
|||
|
|
SubID byte
|
|||
|
|
Data []byte
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type RealtimeData struct {
|
|||
|
|
Sensors []SensorReading `json:"sensors,omitempty"`
|
|||
|
|
Events []EventReading `json:"events,omitempty"`
|
|||
|
|
IO []IOReading `json:"io,omitempty"`
|
|||
|
|
Parameters []ParameterReading `json:"parameters,omitempty"`
|
|||
|
|
}
|
|||
|
|
type SensorReading struct {
|
|||
|
|
Number uint32 `json:"number"`
|
|||
|
|
SensorType byte `json:"sensor_type"`
|
|||
|
|
Object uint16 `json:"object"`
|
|||
|
|
Value int16 `json:"value"`
|
|||
|
|
Unit byte `json:"unit"`
|
|||
|
|
Decimal byte `json:"decimal"`
|
|||
|
|
Status byte `json:"status"`
|
|||
|
|
}
|
|||
|
|
type EventReading struct {
|
|||
|
|
Type uint16 `json:"type"`
|
|||
|
|
Number uint32 `json:"number"`
|
|||
|
|
Value int16 `json:"value"`
|
|||
|
|
}
|
|||
|
|
type IOReading struct {
|
|||
|
|
Type uint16 `json:"type"`
|
|||
|
|
Number uint32 `json:"number"`
|
|||
|
|
Status byte `json:"status"`
|
|||
|
|
}
|
|||
|
|
type ParameterReading struct {
|
|||
|
|
Code byte `json:"code"`
|
|||
|
|
Value int16 `json:"value"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// EncodePayload 按“主标识、块数量、子标识、定长数据”编码。
|
|||
|
|
// 由于厂商协议没有携带块长度,本函数用于已知命令;上行解析由业务标识专用解析器完成。
|
|||
|
|
func EncodePayload(mainID byte, blocks ...DataBlock) ([]byte, error) {
|
|||
|
|
if len(blocks) > 255 {
|
|||
|
|
return nil, fmt.Errorf("数据块数量超过 255")
|
|||
|
|
}
|
|||
|
|
result := []byte{mainID, byte(len(blocks))}
|
|||
|
|
for _, block := range blocks {
|
|||
|
|
result = append(result, block.SubID)
|
|||
|
|
result = append(result, block.Data...)
|
|||
|
|
}
|
|||
|
|
result = append(result, 0x00)
|
|||
|
|
return result, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ValveCommand 编码 0x40/0x0A 电磁阀控制;0x01 关闭,0xA0 开启。
|
|||
|
|
func ValveCommand(controller, loop, component byte, open bool) ([]byte, error) {
|
|||
|
|
action := byte(0x01)
|
|||
|
|
if open {
|
|||
|
|
action = 0xA0
|
|||
|
|
}
|
|||
|
|
return EncodePayload(MainSetting, DataBlock{SubID: SubValve, Data: []byte{controller, loop, component, action}})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DecodeRealtime 解析协议 0x50 的传感器、事件、IO 和 AI 阀参数定长数据块。
|
|||
|
|
func DecodeRealtime(payload []byte) (RealtimeData, error) {
|
|||
|
|
var result RealtimeData
|
|||
|
|
if len(payload) < 3 || payload[0] != MainRealtime {
|
|||
|
|
return result, fmt.Errorf("不是实时数据包")
|
|||
|
|
}
|
|||
|
|
offset := 2
|
|||
|
|
for blockIndex := 0; blockIndex < int(payload[1]); blockIndex++ {
|
|||
|
|
if offset >= len(payload) {
|
|||
|
|
return result, fmt.Errorf("实时数据块被截断")
|
|||
|
|
}
|
|||
|
|
subID := payload[offset]
|
|||
|
|
offset++
|
|||
|
|
switch subID {
|
|||
|
|
case 0x01:
|
|||
|
|
count, next, err := readCount(payload, offset, 12)
|
|||
|
|
if err != nil {
|
|||
|
|
return result, err
|
|||
|
|
}
|
|||
|
|
offset = next
|
|||
|
|
for range count {
|
|||
|
|
item := payload[offset : offset+12]
|
|||
|
|
result.Sensors = append(result.Sensors, SensorReading{Number: binary.BigEndian.Uint32(item[0:4]), SensorType: item[4], Object: binary.BigEndian.Uint16(item[5:7]), Value: int16(binary.BigEndian.Uint16(item[7:9])), Unit: item[9], Decimal: item[10], Status: item[11]})
|
|||
|
|
offset += 12
|
|||
|
|
}
|
|||
|
|
case 0x02:
|
|||
|
|
count, next, err := readCount(payload, offset, 8)
|
|||
|
|
if err != nil {
|
|||
|
|
return result, err
|
|||
|
|
}
|
|||
|
|
offset = next
|
|||
|
|
for range count {
|
|||
|
|
item := payload[offset : offset+8]
|
|||
|
|
result.Events = append(result.Events, EventReading{Type: binary.BigEndian.Uint16(item[0:2]), Number: binary.BigEndian.Uint32(item[2:6]), Value: int16(binary.BigEndian.Uint16(item[6:8]))})
|
|||
|
|
offset += 8
|
|||
|
|
}
|
|||
|
|
case 0x03:
|
|||
|
|
count, next, err := readCount(payload, offset, 7)
|
|||
|
|
if err != nil {
|
|||
|
|
return result, err
|
|||
|
|
}
|
|||
|
|
offset = next
|
|||
|
|
for range count {
|
|||
|
|
item := payload[offset : offset+7]
|
|||
|
|
result.IO = append(result.IO, IOReading{Type: binary.BigEndian.Uint16(item[0:2]), Number: binary.BigEndian.Uint32(item[2:6]), Status: item[6]})
|
|||
|
|
offset += 7
|
|||
|
|
}
|
|||
|
|
case 0x04:
|
|||
|
|
if offset >= len(payload) {
|
|||
|
|
return result, fmt.Errorf("参数块被截断")
|
|||
|
|
}
|
|||
|
|
count := int(payload[offset])
|
|||
|
|
offset++
|
|||
|
|
if offset+count*3 > len(payload) {
|
|||
|
|
return result, fmt.Errorf("参数块长度无效")
|
|||
|
|
}
|
|||
|
|
for range count {
|
|||
|
|
result.Parameters = append(result.Parameters, ParameterReading{Code: payload[offset], Value: int16(binary.BigEndian.Uint16(payload[offset+1 : offset+3]))})
|
|||
|
|
offset += 3
|
|||
|
|
}
|
|||
|
|
default:
|
|||
|
|
return result, fmt.Errorf("未知实时数据子标识 0x%02X", subID)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return result, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func readCount(payload []byte, offset, recordSize int) (int, int, error) {
|
|||
|
|
if offset+2 > len(payload) {
|
|||
|
|
return 0, offset, fmt.Errorf("数据块数量被截断")
|
|||
|
|
}
|
|||
|
|
count := int(binary.BigEndian.Uint16(payload[offset : offset+2]))
|
|||
|
|
offset += 2
|
|||
|
|
if offset+count*recordSize > len(payload) {
|
|||
|
|
return 0, offset, fmt.Errorf("数据块记录长度无效")
|
|||
|
|
}
|
|||
|
|
return count, offset, nil
|
|||
|
|
}
|