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 }