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
|
||
}
|