feat: replace iot adapter with mqtt command pipeline

This commit is contained in:
2026-08-03 13:18:11 +08:00
parent 6ed417c8c1
commit fefdba470a
48 changed files with 1620 additions and 338 deletions

View File

@@ -0,0 +1,155 @@
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
}