package faultRecordProtocol import ( "encoding/binary" "encoding/hex" "fmt" "strings" ) // 可配采样通道地址类型,FaultFilter 文档 5.4.1 id=6;名称统一首字母大写 var ChannelAddrTypeNames = map[uint8]string{ 0x0: "Empty", 0x1: "Uint16", 0x2: "Int16", 0x3: "Float32", 0x4: "Uint32_HWord", 0x5: "Uint32_LWord", 0x6: "Uint32", } // DecodeHex 解码十六进制字符串(允许空格) func DecodeHex(s string) ([]byte, error) { s = strings.ReplaceAll(strings.TrimSpace(s), " ", "") if len(s)%2 != 0 { return nil, fmt.Errorf("hex length must be even") } return hex.DecodeString(s) } // ValidateSetVal 按功能码校验 setVal 的字节长度和取值 func ValidateSetVal(id int, setVal string) error { b, err := DecodeHex(setVal) if err != nil { return fmt.Errorf("id=%d setVal invalid: %w", id, err) } switch id { case 1, 3: if len(b) != 0 { return fmt.Errorf("id=%d takes no parameter", id) } case 2: if len(b) < 1 || (len(b)-1)%2 != 0 { return fmt.Errorf("id=2 setVal must be 1 byte count + n × 2 bytes") } case 4: if len(b) != 7 { return fmt.Errorf("id=4 setVal must be 7 bytes") } case 5: if len(b) != 2 { return fmt.Errorf("id=5 setVal must be 2 bytes") } case 6: if len(b) != 6 { return fmt.Errorf("id=6 setVal must be 6 bytes") } case 7: if len(b) != 1 || b[0] > 1 { return fmt.Errorf("id=7 setVal must be 00 or 01") } default: return fmt.Errorf("invalid function code %d", id) } return nil } // ConfigChannel id=6 可配采样通道配置 type ConfigChannel struct { Index uint8 `json:"index"` Mode uint8 `json:"mode"` // 0=默认方式(地址),1=代码方式 AddrType uint8 `json:"addrType"` // 默认方式下的地址类型 TypeName string `json:"typeName,omitempty"` Address uint32 `json:"address,omitempty"` // 默认方式 Code uint16 `json:"code,omitempty"` // 代码方式 } // DecodeIdData 解读 cmd=3 返回的 idData(结构参照对应功能码的 setVal)。 // 无法解读时返回 nil,调用方只显示原始值。 func DecodeIdData(id int, idData string) any { b, err := DecodeHex(idData) if err != nil { return nil } switch id { case 2: if len(b) < 1 || (len(b)-1)%2 != 0 { return nil } channels := make([]string, 0, (len(b)-1)/2) for i := 1; i+1 < len(b); i += 2 { channels = append(channels, fmt.Sprintf("0x%04X", binary.BigEndian.Uint16(b[i:]))) } return map[string]any{"count": b[0], "channels": channels} case 4: if len(b) != 7 { return nil } return map[string]any{"deviceTime": ParseDeviceTime(b).String()} case 5: if len(b) != 2 { return nil } return map[string]any{"frequencyHz": binary.BigEndian.Uint16(b)} case 6: if len(b) != 6 { return nil } c := ConfigChannel{Index: b[0], Mode: b[1] >> 4, AddrType: b[1] & 0x0F} if c.Mode == 0 { c.TypeName = ChannelAddrTypeNames[c.AddrType] c.Address = binary.BigEndian.Uint32(b[2:]) } else { c.Code = binary.BigEndian.Uint16(b[2:]) } return c case 7: if len(b) != 1 { return nil } return map[string]any{"enabled": b[0] == 1} } return nil } // DeviceTime 设备时间(7 字节:年 2B 大端、月、日、时、分、秒),按设备时间原样表示,不做时区换算 type DeviceTime struct { Year uint16 `json:"year"` Month uint8 `json:"month"` Day uint8 `json:"day"` Hour uint8 `json:"hour"` Min uint8 `json:"min"` Sec uint8 `json:"sec"` } func ParseDeviceTime(b []byte) DeviceTime { return DeviceTime{ Year: binary.BigEndian.Uint16(b[0:2]), Month: b[2], Day: b[3], Hour: b[4], Min: b[5], Sec: b[6], } } func (t DeviceTime) String() string { return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", t.Year, t.Month, t.Day, t.Hour, t.Min, t.Sec) }