package faultRecordFile import ( "archive/zip" "bytes" "encoding/csv" "fmt" "io" "strconv" ) var utf8Bom = []byte{0xEF, 0xBB, 0xBF} // 便于 Excel 识别中文 // ChannelLabel 通道表头:序号 + 含义代码(可配通道附类型和地址) func ChannelLabel(ch WaveformChannel) string { if ch.Configurable != nil { return fmt.Sprintf("ch%d 可配%s", ch.Number, ConfigurableDesc(*ch.Configurable)) } return fmt.Sprintf("ch%d 0x%04X", ch.Number, ch.Meaning) } // ConfigurableDesc 可配通道说明:默认方式为“类型@地址”,代码方式为“代码 0xXXXX” func ConfigurableDesc(c ConfigurableChannel) string { if c.Mode == 1 { return fmt.Sprintf("代码0x%04X", c.Address>>16) } return fmt.Sprintf("%s@0x%X", c.TypeName, c.Address) } // WriteWaveformCSV 每行一个采样点:点序号、相对故障时刻毫秒、各通道原始值 func WriteWaveformCSV(w io.Writer, rec Record) error { if _, err := w.Write(utf8Bom); err != nil { return err } cw := csv.NewWriter(w) wf := rec.Waveform header := []string{"point", "time_ms"} for _, ch := range wf.Channels { header = append(header, ChannelLabel(ch)) } if err := cw.Write(header); err != nil { return err } row := make([]string, len(header)) for k := 0; k < wf.Points; k++ { row[0] = strconv.Itoa(k) row[1] = strconv.FormatFloat(wf.TimeOffsetMs(k), 'f', 3, 64) for c, ch := range wf.Channels { row[c+2] = strconv.Itoa(int(ch.Values[k])) } if err := cw.Write(row); err != nil { return err } } cw.Flush() return cw.Error() } // WriteSummaryCSV 基本信息、版本、触发源及各组通道 func WriteSummaryCSV(w io.Writer, rec Record) error { if _, err := w.Write(utf8Bom); err != nil { return err } cw := csv.NewWriter(w) f := rec.Failure rows := [][]string{ {"项目", "值"}, {"批次", rec.Batch}, {"设备 SN", rec.Sn}, {"记录序号", strconv.Itoa(rec.RecordIndex)}, {"发生时间(设备时间)", f.Time}, {"触发源", fmt.Sprintf("0x%04X %s", f.TriggerSource, f.TriggerSourceName)}, {"CAN 协议版本", strconv.Itoa(int(f.Basic.CanProtocolVersion))}, {"存储格式版本", strconv.Itoa(int(f.Basic.StorageFormat))}, {"参数列表版本", f.Basic.ParamListVersion}, {"最大采样频率 Hz", strconv.Itoa(int(f.Basic.MaxSampleFrequency))}, {"采样频率 Hz", strconv.Itoa(int(f.Basic.SampleFrequency))}, {"采样点总数", strconv.Itoa(int(f.Basic.TotalPoints))}, {"故障前采样点", strconv.Itoa(int(f.Basic.PrePoints))}, {"故障后采样点", strconv.Itoa(int(f.Basic.PostPoints))}, {"DCDC 版本", f.DcdcVersion}, {"INV 版本", f.InvVersion}, {"FPGA 版本", f.FpgaVersion}, {}, {"快照通道", "含义代码", "原始值"}, } for i, s := range f.Snapshots { v := "" if s.Value != nil { v = strconv.Itoa(int(*s.Value)) } rows = append(rows, []string{strconv.Itoa(i), fmt.Sprintf("0x%04X", s.Meaning), v}) } rows = append(rows, []string{}, []string{"采样通道", "含义代码"}) for i, m := range f.SampleMeanings { rows = append(rows, []string{strconv.Itoa(i), fmt.Sprintf("0x%04X", m)}) } rows = append(rows, []string{}, []string{"可配快照通道", "配置"}) for i, c := range f.ConfigSnapshots { rows = append(rows, []string{strconv.Itoa(i), ConfigurableDesc(c)}) } rows = append(rows, []string{}, []string{"可配采样通道", "配置"}) for i, c := range f.ConfigSamples { rows = append(rows, []string{strconv.Itoa(i), ConfigurableDesc(c)}) } if err := cw.WriteAll(rows); err != nil { return err } return cw.Error() } // BuildCSVZip 每条记录生成 waveform 和 summary 两个 CSV,打包为 zip func BuildCSVZip(records []Record) ([]byte, error) { var buf bytes.Buffer zw := zip.NewWriter(&buf) for _, rec := range records { prefix := fmt.Sprintf("%s_%s_index%d", rec.Batch, rec.Sn, rec.RecordIndex) for _, item := range []struct { name string write func(io.Writer, Record) error }{ {prefix + "_waveform.csv", WriteWaveformCSV}, {prefix + "_summary.csv", WriteSummaryCSV}, } { fw, err := zw.Create(item.name) if err != nil { return nil, err } if err := item.write(fw, rec); err != nil { return nil, err } } } if err := zw.Close(); err != nil { return nil, err } return buf.Bytes(), nil }