// Package faultRecordFile 故障录波数据解析(纯函数):cmd=0 的 hexData、cmd=2 导出的 failuredata/waveformdata 及压缩包。 // // 字节结构依据 docs/fault-record/FaultFilter_MQTT协议文档.md 5.1.1、5.2, // 并按 docs/fault-record/protocol-specification.md 8A 的样本验证规则补充。 package faultRecordFile import ( "bytes" "encoding/binary" "fmt" proto "fwh-toolbox-server/function/FaultRecordProtocol" ) // byteReader 顺序读取,越界时记录错误并返回零值 type byteReader struct { b []byte p int err error } func (r *byteReader) take(n int, field string) []byte { if r.err != nil { return make([]byte, n) } if r.p+n > len(r.b) { r.err = fmt.Errorf("data too short at %s (offset %d, need %d, have %d)", field, r.p, n, len(r.b)-r.p) return make([]byte, n) } v := r.b[r.p : r.p+n] r.p += n return v } func (r *byteReader) u8(field string) uint8 { return r.take(1, field)[0] } func (r *byteReader) u16(field string) uint16 { return binary.BigEndian.Uint16(r.take(2, field)) } func (r *byteReader) u32(field string) uint32 { return binary.BigEndian.Uint32(r.take(4, field)) } func (r *byteReader) time(field string) proto.DeviceTime { return proto.ParseDeviceTime(r.take(7, field)) } // cString 取第一个 NUL 之前的内容 func cString(b []byte) string { if i := bytes.IndexByte(b, 0); i >= 0 { b = b[:i] } return string(b) } /************** 公共结构 **************/ // BasicInfo 设备故障录波功能基本信息(code=2,16 字节) type BasicInfo struct { CanProtocolVersion uint8 `json:"canProtocolVersion"` StorageFormat uint8 `json:"storageFormat"` ParamListVersion string `json:"paramListVersion"` // 4 字节十六进制 MaxSampleFrequency uint16 `json:"maxSampleFrequency"` SampleFrequency uint16 `json:"sampleFrequency"` TotalPoints uint16 `json:"totalPoints"` PrePoints uint16 `json:"prePoints"` PostPoints uint16 `json:"postPoints"` } func readBasicInfo(r *byteReader) BasicInfo { return BasicInfo{ CanProtocolVersion: r.u8("canProtocolVersion"), StorageFormat: r.u8("storageFormat"), ParamListVersion: fmt.Sprintf("%08X", r.u32("paramListVersion")), MaxSampleFrequency: r.u16("maxSampleFrequency"), SampleFrequency: r.u16("sampleFrequency"), TotalPoints: r.u16("totalPoints"), PrePoints: r.u16("prePoints"), PostPoints: r.u16("postPoints"), } } // SnapshotChannel 快照通道:含义代码(F14),code=4 中带故障时刻的原始值 type SnapshotChannel struct { Meaning uint16 `json:"meaning"` Value *uint16 `json:"value,omitempty"` } // ConfigurableChannel 可配通道:类型 2B + 数据 4B。 // 类型低字节与设置 id=6 的类型字节格式一致:高 4 位为设置方式(0=默认方式 1=代码方式),低 4 位为地址类型; // 两种方式的数据都按 4 字节原样保留在 Address(代码方式时前 2 字节为参数代码)。 type ConfigurableChannel struct { Type uint16 `json:"type"` Mode uint8 `json:"mode"` AddrType uint8 `json:"addrType"` TypeName string `json:"typeName"` Address uint32 `json:"address"` } // ChannelGroups 快照 / 采样 / 可配快照 / 可配采样 四组通道(code=1 与 failuredata 共用) type ChannelGroups struct { SnapshotTotal uint8 `json:"snapshotTotal"` SnapshotUsed uint8 `json:"snapshotUsed"` Snapshots []SnapshotChannel `json:"snapshots"` SampleTotal uint8 `json:"sampleTotal"` SampleUsed uint8 `json:"sampleUsed"` SampleMeanings []uint16 `json:"sampleMeanings"` ConfigSnapTotal uint8 `json:"configSnapTotal"` ConfigSnapUsed uint8 `json:"configSnapUsed"` ConfigSnapshots []ConfigurableChannel `json:"configSnapshots"` ConfigSampleTotal uint8 `json:"configSampleTotal"` ConfigSampleUsed uint8 `json:"configSampleUsed"` ConfigSamples []ConfigurableChannel `json:"configSamples"` } func readChannelGroups(r *byteReader, snapshotWithValue bool) ChannelGroups { var g ChannelGroups g.SnapshotTotal = r.u8("snapshotTotal") g.SnapshotUsed = r.u8("snapshotUsed") g.Snapshots = make([]SnapshotChannel, 0, g.SnapshotTotal) for i := 0; i < int(g.SnapshotTotal); i++ { c := SnapshotChannel{Meaning: r.u16("snapshotMeaning")} if snapshotWithValue { v := r.u16("snapshotValue") c.Value = &v } g.Snapshots = append(g.Snapshots, c) } g.SampleTotal = r.u8("sampleTotal") g.SampleUsed = r.u8("sampleUsed") g.SampleMeanings = make([]uint16, 0, g.SampleTotal) for i := 0; i < int(g.SampleTotal); i++ { g.SampleMeanings = append(g.SampleMeanings, r.u16("sampleMeaning")) } g.ConfigSnapTotal = r.u8("configSnapTotal") g.ConfigSnapUsed = r.u8("configSnapUsed") g.ConfigSnapshots = readConfigurable(r, int(g.ConfigSnapTotal)) g.ConfigSampleTotal = r.u8("configSampleTotal") g.ConfigSampleUsed = r.u8("configSampleUsed") g.ConfigSamples = readConfigurable(r, int(g.ConfigSampleTotal)) return g } func readConfigurable(r *byteReader, n int) []ConfigurableChannel { list := make([]ConfigurableChannel, 0, n) for i := 0; i < n; i++ { t := r.u16("configType") mode, addrType := uint8(t)>>4, uint8(t)&0x0F list = append(list, ConfigurableChannel{ Type: t, Mode: mode, AddrType: addrType, TypeName: proto.ChannelAddrTypeNames[addrType], Address: r.u32("configAddress"), }) } return list }