package function import ( "bytes" "encoding/binary" "encoding/json" "fmt" "os" "strconv" "strings" "github.com/sigurn/crc16" "go.uber.org/zap" ) var ( gCrc16Table = crc16.MakeTable(crc16.CRC16_MODBUS) // 选择 Modbus ) /************** 快照文件结构体 **************/ type SegmentOfCIBuffer struct { Len uint16 `json:"len"` Crc16 uint16 `json:"crc16"` TimeStamp uint32 `json:"timeStamp"` } type EquipmentSn struct { Len int32 `json:"len"` Data string `json:"data"` } type HwfAlarmDataItem struct { AlarmCode uint16 `json:"alarmCode"` //故障编码 Level uint8 `json:"level"` //故障等级 NeedSave uint8 `json:"needSave"` //该故障是否需要保存 TimeStart uint32 `json:"timeStart"` //该故障的开始时间 } type AlarmList struct { Count int32 `json:"count"` DataItem []HwfAlarmDataItem `json:"dataItem"` } type SnapXid struct { IdType uint8 `json:"idType"` // PID : 1, VID : 0 DataType uint8 `json:"dataType"` // 0-UINT32 1-INT32 2-FLOAT 3-PCHAR ID uint16 `json:"id"` // 在PID和VID配置表中对应的id, 可基于此找到对应的PID/VID } type XidDataType interface { uint32 | int32 | float32 | string } type XidSegmentHeader struct { Xid SnapXid `json:"xid"` Len int32 `json:"len"` // DATA 的长度 } type XidSegment[T XidDataType] struct { Header XidSegmentHeader `json:"header"` Data T `json:"data"` // DATA 取决于 SnapXid.DataType } type XidSegmentType interface { XidSegment[uint32] | XidSegment[int32] | XidSegment[float32] | XidSegment[string] } type XidSegmentIf interface { GetXid() any CreateXid(v any) error } func (x *XidSegment[T]) GetXid() any { return x } func (x *XidSegment[T]) CreateXid(v any) error { val, ok := v.(XidSegment[T]) if !ok { return fmt.Errorf("invalid type: expected %T", x.Data) } *x = val return nil } type InfoSegmentPackage struct { Header SegmentOfCIBuffer `json:"header"` HeadStr [4]byte `json:"headerStr"` // "HEAD" GateSn EquipmentSn `json:"aGateSn"` PowerSn EquipmentSn `json:"aPowerSn"` Alarm AlarmList `json:"alarm"` } type XidSegmentPackage struct { Header SegmentOfCIBuffer `json:"header"` XidList []XidSegmentIf `json:"xidList"` } type SnapFilePackage struct { Info InfoSegmentPackage `json:"info"` Xid XidSegmentPackage `json:"xid"` } /************** 快照JSON数据结构体 **************/ type AlarmCodeInfo struct { AlarmCode uint16 `json:"alarmCode"` //故障编码 Level uint8 `json:"level"` //故障等级 TimeStart uint32 `json:"timeStart"` //该故障的开始时间 } type AlarmInfoJson struct { Total int32 `json:"total"` AlarmList []AlarmCodeInfo `json:"alarmList"` } type XID[T XidDataType] struct { ID uint16 `json:"id"` Type string `json:"type"` // "PID" or "VID" Macro *string `json:"macro"` // 代码中的宏名称 Brief *string `json:"brief"` // 简介 Data T `json:"data"` } type XidIf interface { GetXid() any CreateXid(v any) error } func (x *XID[T]) GetXid() any { return x } func (x *XID[T]) CreateXid(v any) error { val, ok := v.(XID[T]) if !ok { return fmt.Errorf("invalid type: expected %T", x.Data) } *x = val return nil } type SnapInfoJson struct { XidList []XidIf `json:"xidList"` } type SnapJson struct { TimeStamp uint32 `json:"timeStamp"` SiteSn string `json:"aGateSn"` PowerSn []string `json:"aPowerSn"` AlarmInfo AlarmInfoJson `json:"alarmInfo"` SnapInfo SnapInfoJson `json:"snapInfo"` } /************** 快照JSON数据结构体(Unmarshal) **************/ type StringAny string func (s *StringAny) UnmarshalJSON(b []byte) error { // null 处理 if string(b) == "null" { *s = "" return nil } // 字符串:"123" if len(b) > 0 && b[0] == '"' { var str string if err := json.Unmarshal(b, &str); err != nil { return err } *s = StringAny(str) return nil } // 数值:123 / 123.45 var num json.Number dec := json.NewDecoder(bytes.NewReader(b)) dec.UseNumber() if err := dec.Decode(&num); err == nil { *s = StringAny(num.String()) return nil } return fmt.Errorf("StringAny: unsupported value %s", string(b)) } type UnmarshalXID struct { ID uint16 `json:"id"` Type string `json:"type"` // "PID" or "VID" Macro *string `json:"macro"` // 代码中的宏名称 Brief *string `json:"brief"` // 简介 Data StringAny `json:"data"` // number or string -> string } type SnapInfoUnmarshalJson struct { XidList []UnmarshalXID `json:"xidList"` } type SnapUnmarshalJson struct { TimeStamp uint32 `json:"timeStamp"` SiteSn string `json:"aGateSn"` PowerSn []string `json:"aPowerSn"` AlarmInfo AlarmInfoJson `json:"alarmInfo"` SnapInfo SnapInfoUnmarshalJson `json:"snapInfo"` } /************** 解析数据结构体 **************/ type ParserAlarmSnapshot struct { FilePath string `json:"filePath"` FilePackage []SnapFilePackage `json:"filePackage"` SnapJson []SnapJson `json:"snapJson"` } type SegmentDataType struct { Header SegmentOfCIBuffer Data []byte } type SegmentType struct { InfoSegment SegmentDataType XidSegment SegmentDataType BinData []byte } // 创建ParserAlarmSnapshot结果体必须使用函数NewParserAlarmSnapshot func NewParserAlarmSnapshot(filePath string) *ParserAlarmSnapshot { return &ParserAlarmSnapshot{ FilePath: filePath, } } func (parser *ParserAlarmSnapshot) ParserSnapFile() error { binData, err := os.ReadFile(parser.FilePath) if err != nil { zap.L().Error("snap文件读取失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return err } var newBinData []byte // 寻找第一个有效 "HEAD" 的位置,生成新的连续数据段 pRemainingData := binData startIdx := int(0) for { idx := bytes.Index(pRemainingData, []byte("HEAD")) if idx == -1 { fmt.Println("未找到 HEAD 结束") break } else if idx < 8 { // 数据开头不完整,跳到下一个数据 pRemainingData = pRemainingData[idx+4:] startIdx = startIdx + idx + 4 continue } // 计算前8个字节的开始位置 start := idx - 8 startIdx = startIdx + start // 检查有效性 _, err := parser.checkSnapData(pRemainingData[start:]) if err != nil { fmt.Println("第一个数据段无效") continue } pHeaderData := binData[:startIdx] pRemainingData = pRemainingData[start:] // 重新拼接循环数据 newBinData = make([]byte, len(binData)) copy(newBinData, pRemainingData) copy(newBinData[len(pRemainingData):], pHeaderData) break } // 声明一个切片,用来存放多个 Segment var segmentList []*SegmentType pNewBinData := newBinData pBinIdx := startIdx // 生成数据段列表 for { // 检查 HEAD 头 if !bytes.Equal(pNewBinData[8:8+4], []byte("HEAD")) { // 往下寻找新的 HEAD 头 idx := bytes.Index(pNewBinData, []byte("HEAD")) if idx == -1 { fmt.Println("未找到 HEAD 结束") break } else if idx < 8 { // 数据开头不完整,跳到下一个数据 pNewBinData = pNewBinData[idx+4:] pBinIdx = pBinIdx + idx + 4 continue } pNewBinData = pNewBinData[idx-8:] pBinIdx = pBinIdx + idx - 8 } // 检查有效性 segData, err := parser.checkSnapData(pNewBinData) if err != nil { fmt.Printf("数据段格式错误 index: %d\n", pBinIdx) // 数据开头不完整,跳到下一个数据 pNewBinData = pNewBinData[8+4:] pBinIdx = pBinIdx + 8 + 4 continue } segmentList = append(segmentList, segData) pNewBinData = pNewBinData[len(segData.BinData):] pBinIdx = pBinIdx + len(segData.BinData) if len(pNewBinData) == 0 { // 数据刚好处理完,无剩余无效数据 break } } // 处理每个数据段 for _, pData := range segmentList { var filePackage SnapFilePackage var snapJson SnapJson if err := parser.parserInfoSegment(&filePackage, &snapJson, pData.InfoSegment); err != nil { return err } if err := parser.parserXidSegment(&filePackage, &snapJson, pData.XidSegment); err != nil { return err } parser.FilePackage = append(parser.FilePackage, filePackage) parser.SnapJson = append(parser.SnapJson, snapJson) } return nil } // 检验数据的正确性和完整性 // 1、校验两段SegmentOfCIBuffer的CRC // 2、同时检查数据长度是否完整 func (parser *ParserAlarmSnapshot) checkSnapData(binData []byte) (*SegmentType, error) { // 处理数据段一 var infoHeader SegmentOfCIBuffer headerSize := binary.Size(infoHeader) if len(binData) < headerSize { zap.L().Error("snap文件长度异常", zap.String("FilePath", parser.FilePath)) return nil, fmt.Errorf("snap文件长度异常") } headerRaw := binData[:headerSize] // 头部数据 headerReader := bytes.NewReader(headerRaw) if err := binary.Read(headerReader, binary.LittleEndian, &infoHeader); err != nil { zap.L().Error("snap文件读取转换失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return nil, err } // 获取数据长度 infoBufferLen := int(infoHeader.Len) if len(binData[headerSize:]) < infoBufferLen { zap.L().Error("snap文件长度异常", zap.String("FilePath", parser.FilePath)) return nil, fmt.Errorf("snap文件长度异常") } // Info Buffer pInfoBufferRaw := binData[headerSize:] pInfoBufferRaw = pInfoBufferRaw[:infoBufferLen] // 校验CRC bufferCrc := infoHeader.Crc16 calcCrc := crc16.Checksum(pInfoBufferRaw, gCrc16Table) if bufferCrc != calcCrc { zap.L().Error("snap文件CRC校验失败", zap.String("FilePath", parser.FilePath)) return nil, fmt.Errorf("snap文件CRC校验失败") } pInfoBufferRaw = pInfoBufferRaw[4:] // 排除 “HEAD” 头 // 处理数据段二 var xidHeader SegmentOfCIBuffer headerSize = binary.Size(xidHeader) secBinData := binData[headerSize+infoBufferLen:] if len(secBinData) < headerSize { zap.L().Error("snap文件长度异常", zap.String("FilePath", parser.FilePath)) return nil, fmt.Errorf("snap文件长度异常") } headerRaw = secBinData[:headerSize] // 头部数据 headerReader = bytes.NewReader(headerRaw) if err := binary.Read(headerReader, binary.LittleEndian, &xidHeader); err != nil { zap.L().Error("snap文件读取转换失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return nil, err } // 获取数据长度 xidBufferLen := int(xidHeader.Len) if len(secBinData[headerSize:]) < xidBufferLen { zap.L().Error("snap文件长度异常", zap.String("FilePath", parser.FilePath)) return nil, fmt.Errorf("snap文件长度异常") } // Xid Buffer pXidBufferRaw := secBinData[headerSize:] pXidBufferRaw = pXidBufferRaw[:xidBufferLen] // 校验CRC bufferCrc = xidHeader.Crc16 calcCrc = crc16.Checksum(pXidBufferRaw, gCrc16Table) if bufferCrc != calcCrc { zap.L().Error("snap文件CRC校验失败", zap.String("FilePath", parser.FilePath)) return nil, fmt.Errorf("snap文件CRC校验失败") } // 计算数据总长度 dataLen := headerSize + infoBufferLen + headerSize + xidBufferLen return &SegmentType{ InfoSegment: SegmentDataType{ Header: infoHeader, Data: pInfoBufferRaw, }, XidSegment: SegmentDataType{ Header: xidHeader, Data: pXidBufferRaw, }, BinData: binData[:dataLen], }, nil } func (parser *ParserAlarmSnapshot) parserInfoSegment(pkg *SnapFilePackage, snapJson *SnapJson, segData SegmentDataType) error { header := segData.Header pkg.Info.Header = header // 填充JSON数据 snapJson.TimeStamp = header.TimeStamp pInfoBufferRaw := segData.Data // Info Buffer // 写入数据头标签 HEAD copy(pkg.Info.HeadStr[:], "HEAD") equipmentSnLenReader := bytes.NewReader(pInfoBufferRaw[:binary.Size(pkg.Info.GateSn.Len)]) if err := binary.Read(equipmentSnLenReader, binary.LittleEndian, &pkg.Info.GateSn.Len); err != nil { zap.L().Error("snap文件读取设备aGate SN长度失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return err } if pkg.Info.GateSn.Len < 0 { zap.L().Error("snap文件读取设备aGate SN长度小于0", zap.String("len", strconv.Itoa(int(pkg.Info.GateSn.Len))), zap.String("FilePath", parser.FilePath)) return fmt.Errorf("snap文件读取设备aGate SN长度小于0") } pInfoBufferRaw = pInfoBufferRaw[binary.Size(pkg.Info.GateSn.Len):] // 提取aGate SN equipmentSnDataRaw := pInfoBufferRaw[:pkg.Info.GateSn.Len] pkg.Info.GateSn.Data = string(equipmentSnDataRaw) snapJson.SiteSn = string(equipmentSnDataRaw) pInfoBufferRaw = pInfoBufferRaw[pkg.Info.GateSn.Len:] equipmentSnLenReader = bytes.NewReader(pInfoBufferRaw[:binary.Size(pkg.Info.PowerSn.Len)]) if err := binary.Read(equipmentSnLenReader, binary.LittleEndian, &pkg.Info.PowerSn.Len); err != nil { zap.L().Error("snap文件读取设备aPower SN长度失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return err } if pkg.Info.PowerSn.Len < 0 { zap.L().Error("snap文件读取设备aPower SN长度小于0", zap.String("len", strconv.Itoa(int(pkg.Info.PowerSn.Len))), zap.String("FilePath", parser.FilePath)) return fmt.Errorf("snap文件读取设备aPower SN长度小于0") } pInfoBufferRaw = pInfoBufferRaw[binary.Size(pkg.Info.PowerSn.Len):] // 提取aPower SN equipmentSnDataRaw = pInfoBufferRaw[:pkg.Info.PowerSn.Len] pkg.Info.PowerSn.Data = string(equipmentSnDataRaw) snapJson.PowerSn = strings.Split(string(equipmentSnDataRaw), ",") pInfoBufferRaw = pInfoBufferRaw[pkg.Info.PowerSn.Len:] alarmCountReader := bytes.NewReader(pInfoBufferRaw[:binary.Size(pkg.Info.Alarm.Count)]) if err := binary.Read(alarmCountReader, binary.LittleEndian, &pkg.Info.Alarm.Count); err != nil { zap.L().Error("snap文件读取告警数量失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return err } snapJson.AlarmInfo.Total = pkg.Info.Alarm.Count pInfoBufferRaw = pInfoBufferRaw[binary.Size(pkg.Info.Alarm.Count):] for i := 0; i < int(pkg.Info.Alarm.Count); i++ { var alarmDataItem HwfAlarmDataItem alarmDataItemReader := bytes.NewBuffer(pInfoBufferRaw[:binary.Size(alarmDataItem)]) if err := binary.Read(alarmDataItemReader, binary.LittleEndian, &alarmDataItem); err != nil { zap.L().Error("snap文件读取告警项失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return err } pkg.Info.Alarm.DataItem = append(pkg.Info.Alarm.DataItem, alarmDataItem) snapJson.AlarmInfo.AlarmList = append(snapJson.AlarmInfo.AlarmList, AlarmCodeInfo{ AlarmCode: alarmDataItem.AlarmCode, Level: alarmDataItem.Level, TimeStart: alarmDataItem.TimeStart, }) pInfoBufferRaw = pInfoBufferRaw[binary.Size(alarmDataItem):] } return nil } func (parser *ParserAlarmSnapshot) parserXidSegment(pkg *SnapFilePackage, snapJson *SnapJson, segData SegmentDataType) error { header := segData.Header pkg.Xid.Header = header pXidBufferRaw := segData.Data // Xid Buffer for len(pXidBufferRaw) > 0 { var xidHeader XidSegmentHeader snapXidHeaderReader := bytes.NewReader(pXidBufferRaw[:binary.Size(xidHeader)]) if err := binary.Read(snapXidHeaderReader, binary.LittleEndian, &xidHeader); err != nil { zap.L().Error("snap文件读取XID数据头失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return err } pXidBufferRaw = pXidBufferRaw[binary.Size(xidHeader):] var IdTypeStr string switch xidHeader.Xid.IdType { case 0: IdTypeStr = "VID" case 1: IdTypeStr = "PID" default: zap.L().Error("snap文件读取XID数据ID类型错误", zap.String("id_type", strconv.Itoa(int(xidHeader.Xid.IdType))), zap.String("FilePath", parser.FilePath)) return fmt.Errorf("snap文件读取XID数据ID类型错误") } switch xidHeader.Xid.DataType { case 0: var xid XidSegment[uint32] xid.Header = xidHeader var data uint32 if xidHeader.Len != int32(binary.Size(data)) { zap.L().Error("snap文件读取XID数据长度错误", zap.String("type", strconv.Itoa(int(xidHeader.Xid.DataType))), zap.String("len", strconv.Itoa(int(xidHeader.Len))), zap.String("FilePath", parser.FilePath)) return fmt.Errorf("snap文件读取XID数据长度错误") } dataReader := bytes.NewReader(pXidBufferRaw[:binary.Size(data)]) if err := binary.Read(dataReader, binary.LittleEndian, &data); err != nil { zap.L().Error("snap文件读取XID数据失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return err } xid.Data = data pkg.Xid.XidList = append(pkg.Xid.XidList, &xid) // 赋值到JSON数据 snapJson.SnapInfo.XidList = append(snapJson.SnapInfo.XidList, &XID[uint32]{ ID: xidHeader.Xid.ID, Type: IdTypeStr, Data: data, }) case 1: var xid XidSegment[int32] xid.Header = xidHeader var data int32 if xidHeader.Len != int32(binary.Size(data)) { zap.L().Error("snap文件读取XID数据长度错误", zap.String("type", strconv.Itoa(int(xidHeader.Xid.DataType))), zap.String("len", strconv.Itoa(int(xidHeader.Len))), zap.String("FilePath", parser.FilePath)) return fmt.Errorf("snap文件读取XID数据长度错误") } dataReader := bytes.NewReader(pXidBufferRaw[:binary.Size(data)]) if err := binary.Read(dataReader, binary.LittleEndian, &data); err != nil { zap.L().Error("snap文件读取XID数据失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return err } xid.Data = data pkg.Xid.XidList = append(pkg.Xid.XidList, &xid) // 赋值到JSON数据 snapJson.SnapInfo.XidList = append(snapJson.SnapInfo.XidList, &XID[int32]{ ID: xidHeader.Xid.ID, Type: IdTypeStr, Data: data, }) case 2: var xid XidSegment[float32] xid.Header = xidHeader var data float32 if xidHeader.Len != int32(binary.Size(data)) { zap.L().Error("snap文件读取XID数据长度错误", zap.String("type", strconv.Itoa(int(xidHeader.Xid.DataType))), zap.String("len", strconv.Itoa(int(xidHeader.Len))), zap.String("FilePath", parser.FilePath)) return fmt.Errorf("snap文件读取XID数据长度错误") } dataReader := bytes.NewReader(pXidBufferRaw[:binary.Size(data)]) if err := binary.Read(dataReader, binary.LittleEndian, &data); err != nil { zap.L().Error("snap文件读取XID数据失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return err } xid.Data = data pkg.Xid.XidList = append(pkg.Xid.XidList, &xid) // 赋值到JSON数据 snapJson.SnapInfo.XidList = append(snapJson.SnapInfo.XidList, &XID[float32]{ ID: xidHeader.Xid.ID, Type: IdTypeStr, Data: data, }) case 3: var xid XidSegment[string] xid.Header = xidHeader strBytes := make([]byte, xidHeader.Len) dataReader := bytes.NewReader(pXidBufferRaw[:xidHeader.Len]) if err := binary.Read(dataReader, binary.LittleEndian, &strBytes); err != nil { zap.L().Error("snap文件读取XID数据失败", zap.String("err", err.Error()), zap.String("FilePath", parser.FilePath)) return err } xid.Data = string(strBytes) pkg.Xid.XidList = append(pkg.Xid.XidList, &xid) // 赋值到JSON数据 snapJson.SnapInfo.XidList = append(snapJson.SnapInfo.XidList, &XID[string]{ ID: xidHeader.Xid.ID, Type: IdTypeStr, Data: xid.Data, }) default: zap.L().Error("snap文件读取XID类型错误", zap.String("type", strconv.Itoa(int(xidHeader.Xid.DataType))), zap.String("FilePath", parser.FilePath)) return fmt.Errorf("snap文件读取XID类型错误") } pXidBufferRaw = pXidBufferRaw[xidHeader.Len:] } return nil }