C#合并CAN ASC文件:实现与优化

news2025/6/9 1:02:25

C#合并CAN ASC文件:实现与优化

在汽车电子和工业控制领域,CAN(Controller Area Network)总线是一种广泛使用的通信协议。CAN ASC(American Standard Code)文件则是记录CAN总线通信数据的标准格式,常用于数据分析和故障排查。当需要处理多个时间段的CAN数据时,合并多个ASC文件就成为了必要操作。本文将介绍如何使用C#实现CAN ASC文件的合并功能。

一、ASC文件格式简介

CAN ASC文件通常包含以下部分:

  • 文件头部:包含日期、时间戳类型等信息
  • 数据记录:每条记录包含时间戳、CAN ID、数据长度和数据内容

一个典型的ASC文件示例:

date Wed May 29 10:30:00 am 2025
base hex  timestamps absolute
   0.000000 123             Tx   d 8 01 02 03 04 05 06 07 08  Channel: 1
   1.500000 456             Rx   d 8 11 12 13 14 15 16 17 18  Channel: 1

二、合并CAN ASC文件的挑战

合并多个ASC文件看似简单,但实际上需要考虑以下几个关键问题:

    1. 时间戳处理:如何将不同文件中的相对时间戳合并为统一的时间线
    1. 文件顺序:按什么顺序合并文件才能保证时间连续性
    1. 数据一致性:合并后的数据行数是否正确
    1. 重复合并检查:避免重复合并相同的文件

三、C#实现CAN ASC文件合并器

下面是一个完整的C#实现,它能够处理多个ASC文件的合并,并解决上述挑战:

/// <summary>
/// 报文合并
/// </summary>
public class AscMerger
{
    /// <summary>
    /// 源文件夹路径,存储需要合并的 ASC 文件
    /// </summary>
    private readonly string sourcePath;
    /// <summary>
    /// 目标文件夹路径,用于存储合并后的 ASC 文件
    /// </summary>
    private readonly string destinationPath;

    public AscMerger(string sourcePath, string destinationPath)
    {
        // 设置源文件夹路径
        this.sourcePath = sourcePath;

        // 设置目标文件夹路径
        this.destinationPath = destinationPath;
    }

    public (string filePath, ErrorCode errorCode) Merge()
    {
        try
        {
            if (!Directory.Exists(this.sourcePath))
            {
                return ("", ErrorCode.SourceFolderNotFound);
            }
            if (!Directory.Exists(this.destinationPath))
            {
                return ("", ErrorCode.DestinationFolderNotFound);
            }

            var files = this.GetFiles();
            if (!files.Any())
            {
                return ("", ErrorCode.AscFilesNotFound);
            }

            var messages = files.Select(f => this.ReadFile(f)).ToList();
            var headerTime = this.CalculateHeaderTime(messages);
            var headerTimestamp = ToTimestamp(headerTime);
            var combinedMessages = this.CombineMessages(messages, headerTimestamp);

            // 验证文件已存在
            var targetName = $"{this.ToFileString(headerTime)}.asc";
            if (files.Any(f => f.Name == targetName))
            {
                return ("", ErrorCode.AlreadyMerged);
            }

            // 验证行数
            var lineCount = messages.Select(m => m.Skip(2).Count()).Sum();
            if (lineCount != combinedMessages.Count)
            {
                return ("", ErrorCode.Inconsistent);
            }

            // 保存
            var target = Path.Combine(this.destinationPath, targetName);
            if (!this.SaveDestinationFile(headerTime, combinedMessages, target))
            {
                return ("", ErrorCode.FileWriteError);
            }

            return (target, ErrorCode.None);
        }
        catch (Exception e)
        {
            Console.WriteLine($"{e.Message}\r\n{e.StackTrace}");
            return ("", ErrorCode.FileReadError);
        }
    }

    private List<FileInfo> GetFiles()
    {
        return new DirectoryInfo(this.sourcePath).GetFiles("*.asc").ToList();
    }

    private List<string> ReadFile(FileInfo f)
    {
        using (var sr = new StreamReader(f.FullName))
        {
            var list = new List<string>();
            var content = "";
            while (!string.IsNullOrWhiteSpace((content = sr.ReadLine())))
            {
                list.Add(content);
            }
            return list;
        }
    }

    private DateTime CalculateHeaderTime(List<List<string>> messages)
    {
        return messages.Where(m => m.Any())
            .Select(m => m.Take(1).First())
            .Select(m => this.FromCanHeaderString(m))
            .Min();
    }

    private DateTime FromCanHeaderString(string s)
    {
        s = s.Replace("date ", "").Replace("am", "AM").Replace("pm", "PM");
        var format = "ddd MMM dd hh:mm:ss tt yyyy";
        var culture = CultureInfo.CreateSpecificCulture("en-US");

        return DateTime.ParseExact(s, format, culture);
    }

    private double ToTimestamp(DateTime d)
    {
        if (d == DateTime.MinValue)
        {
            return 0;
        }

        return new DateTimeOffset(d).ToUnixTimeMilliseconds() / 1000.0;
    }

    private List<string> CombineMessages(List<List<string>> messages, double headerTimestamp)
    {
        return (
            from dict in
                from m in messages
                select this.CalculateTimestamp(m)
            from kp in dict
            orderby kp.Item1
            select this.ReplaceTimestamp(kp.Item2, headerTimestamp, kp.Item1)
        ).ToList();
    }

    private List<(double, string)> CalculateTimestamp(List<string> messages)
    {
        if (messages == null || messages.Count < 2)
        {
            return new List<(double, string)>();
        }

        var header = messages.Take(2).ToList();
        var time = this.FromCanHeaderString(header[0]);
        var timestamp = ToTimestamp(time);

        return messages.Skip(2).Select(m => (this.ExtractTimestamp(m), m))
            .Select(m => (timestamp + m.Item1, m.Item2))
            .ToList();
    }

    private double ExtractTimestamp(string s)
    {
        var reg = new Regex("^\\d{1,}.\\d{6}");
        var match = reg.Match(s);
        return double.Parse(match.Value);
    }

    private string ReplaceTimestamp(string s, double baseTimestamp, double timestamp)
    {
        return Regex.Replace(s, "^\\d{1,}.\\d{6}", (timestamp - baseTimestamp).ToString("0.000000"));
    }

    private bool SaveDestinationFile(DateTime headerTime, List<string> messages, string path)
    {
        using (var sw = new StreamWriter(path))
        {
            sw.WriteLine($"date {this.ToCanHeaderString(headerTime)}");
            sw.WriteLine("base hex timestamps absolute");
            sw.Flush();

            foreach (var m in messages)
            {
                sw.WriteLine(m);
            }
            return true;
        }
    }

    private string ToFileString(DateTime time)
    {
        return $"{time:yyyyMMdd_HHmmss}";
    }

    public string ToCanHeaderString(DateTime d)
    {
        var format = "ddd MMM dd hh:mm:ss tt yyyy";
        var culture = CultureInfo.CreateSpecificCulture("en-US");
        return d.ToString(format, culture).Replace("AM", "am").Replace("PM", "pm");
    }
}


public enum ErrorCode
{
    None = 0,
    SourceFolderNotFound = 1,
    DestinationFolderNotFound = 2,
    AscFilesNotFound = 3,
    FileReadError = 4,
    FileWriteError = 5,
    Inconsistent = 6,
    AlreadyMerged = 7
}

四、代码解析

这个ASC文件合并器主要包含以下几个核心功能:

    1. 初始化与路径验证:通过构造函数接收源文件夹和目标文件夹路径,并在合并前验证这些路径是否存在。
    1. 文件读取ReadFile方法负责读取单个ASC文件的内容,将其存储为字符串列表。
    1. 时间戳处理
    • CalculateHeaderTime方法确定所有文件中最早的时间戳
    • ExtractTimestamp方法从每条记录中提取相对时间戳
    • ReplaceTimestamp方法将所有时间戳转换为相对于合并后文件开始时间的相对时间
    1. 文件合并CombineMessages方法将所有文件的内容按时间顺序合并,并处理时间戳转换。
    1. 数据验证:在合并前后进行数据验证,确保合并过程中没有数据丢失。
    1. 错误处理:使用枚举类型ErrorCode处理各种可能的错误情况,确保程序的健壮性。

五、使用示例

下面是如何使用这个合并器的简单示例:

static void Main(string[] args)
{
    string sourcePath = @"C:\CAN\Source";
    string destinationPath = @"C:\CAN\Destination";
    
    var merger = new AscMerger(sourcePath, destinationPath);
    var result = merger.Merge();
    
    if (result.errorCode == ErrorCode.None)
    {
        Console.WriteLine($"合并成功!文件保存至: {result.filePath}");
    }
    else
    {
        Console.WriteLine($"合并失败!错误码: {result.errorCode}");
    }
}

六、性能优化建议

对于处理大量或大型ASC文件的情况,可以考虑以下优化:

  1. 使用并行处理来加速文件读取
  2. 实现流式处理,避免将整个文件加载到内存中
  3. 添加进度报告功能,让用户了解合并进度
  4. 增加文件过滤功能,只合并特定时间段的文件

通过这种方式实现的CAN ASC文件合并器,不仅能够正确处理时间戳问题,还提供了完善的错误处理机制,确保合并过程的可靠性和数据的完整性。无论是用于汽车诊断、工业自动化还是其他CAN总线应用场景,这个工具都能帮助工程师更高效地处理和分析CAN数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2404782.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[TIP] Ubuntu 22.04 配置多个版本的 GCC 环境

问题背景 在 Ubuntu 22.04 中安装 VMware 虚拟机时&#xff0c;提示缺少 VMMON 和 VMNET 模块 编译这两个模块需要 GCC 的版本大于 12.3.0&#xff0c;而 Ubuntu 22.04 自带的 GCC 版本为 11.4.0 因此需要安装对应的 GCC 版本&#xff0c;但为了不影响其他程序&#xff0c;需…

如何思考?分析篇

现代人每天刷 100 条信息&#xff0c;却难静下心读 10 页书。 前言&#xff1a; 我一直把思考当作一件生活中和工作中最为重要的事情。但是我发现当我想写一篇跟思考有关的文章时&#xff0c;却难以下手。因为思考是一件非常复杂的事情&#xff0c;用文字描述十分的困难。 读书…

Redis:Hash数据类型

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Redis &#x1f525; Hash哈希 &#x1f433; ⼏乎所有的主流编程语⾔都提供了哈希&#xff08;hash&#xff09;类型&#xff0c;它们的叫法可能是哈希、字典、关联数组、映射。在Redis中&#…

快捷键的记录

下面对应的ATL数字 ATL4 显示编译输出 CTRL B 编译 CTRLR 运行exe 菜单栏 ALTF ALTE ALTB ALTD ALTH

Python读取阿里法拍网的html+解决登录cookie

效果图 import time from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from lxml import etreedef get_taobao_auct…

electron-vite串口通信

一、构建项目后&#xff0c;安装“串口通信库” npm install serialport二、设置 npm install --save-dev electron-rebuild ./node_modules/.bin/electron-rebuild 注意&#xff1a;如果执行报错以下问题 1、未配置python变量 2、没有Microsoft Visual Studio BuildTools 3…

中山大学美团港科大提出首个音频驱动多人对话视频生成MultiTalk,输入一个音频和提示,即可生成对应唇部、音频交互视频。

由中山大学、美团、香港科技大学联合提出的MultiTalk是一个用于音频驱动的多人对话视频生成的新框架。给定一个多流音频输入和一个提示&#xff0c;MultiTalk 会生成一个包含提示所对应的交互的视频&#xff0c;其唇部动作与音频保持一致。 相关链接 论文&#xff1a;https://a…

redis分片集群架构

主从集群解决高并发&#xff0c;哨兵解决高可用问题。但是任然有两个问题没有解决&#xff1a;1海量数据存储问题&#xff1b;2高并发写的问题&#xff08;如果服务中有大量写的请求&#xff09; 那就可以采用分片集群架构解决这些问题 分片集群特征 分片集群中有多个master…

关于物联网的基础知识(一)

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于物联网的基础知识&#xff08;一&a…

电脑商城--用户注册登录

用户注册 1 用户-创建数据表 1.使用use命令先选中store数据库。 USE store; 2.在store数据库中创建t_user用户数据表。 CREATE TABLE t_user (uid INT AUTO_INCREMENT COMMENT 用户id,username VARCHAR(20) NOT NULL UNIQUE COMMENT 用户名,password CHAR(32) NOT NULL COMME…

什么是梯度磁场

梯度磁场是叠加在均匀主磁场&#xff08;如MRI中的静磁场B₀&#xff09;上的一种特殊磁场&#xff0c;其强度会沿着特定方向&#xff08;如X、Y或Z轴&#xff09;呈线性变化。这种磁场在磁共振成像和粒子控制等领域发挥着关键作用&#xff0c;主要用于实现空间位置的精确编码和…

从零开始的python学习(七)P102+P103+P104+P105+P106+P107

本文章记录观看B站python教程学习笔记和实践感悟&#xff0c;视频链接&#xff1a;【花了2万多买的Python教程全套&#xff0c;现在分享给大家&#xff0c;入门到精通(Python全栈开发教程)】 https://www.bilibili.com/video/BV1wD4y1o7AS/?p6&share_sourcecopy_web&v…

Linux--进程的调度

1.进程切换 CPU上下⽂切换&#xff1a;其实际含义是任务切换, 或者CPU寄存器切换。当多任务内核决定运⾏另外的任务时, 它保存正在运⾏任务的当前状态, 也就是CPU寄存器中的全部内容。这些内容被保存在任务⾃⼰的堆栈中, ⼊栈⼯作完成后就把下⼀个将要运⾏的任务的当前状况从该…

VmWare Ubuntu22.04 搭建DPDK 20.11.1

一、开发环境 Ubuntu 版本 二、增加虚拟机的网卡 给虚拟机增加1个网卡,加上原来的网卡,一共2个 网络适配器作为 ssh 连接的网卡,网络适配器2作为 DPDK 运行的网卡。 三、NAT模式简介 这里待补充,网上都是那一张图,看不懂 四、使网卡名称从0开始命名 进入管理员权限 s…

selenium-自动更新谷歌浏览器驱动

1、简介 selenium最初是一个自动化测试工具&#xff0c;而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题&#xff0c;因为有些网页数据是通过JavaScript动态加载的。selenium本质是通过驱动浏览器&#xff0c;完全模拟浏览器的操作&#xff0c;比如输入…

Docker容器部署elasticsearch8.*与Kibana8.*版本使用filebeat采集日志

第 1 步&#xff1a;使用 Docker Compose 部署 Elasticsearch 和 Kibana 首先&#xff0c;我们需要创建一个 docker-compose.yml 文件来定义和运行 Elasticsearch 和 Kibana 服务。这种方式可以轻松管理两个容器的配置和网络。 创建 docker-compose.yml 文件 在一个新的文件夹…

OpenCV CUDA模块图像处理------双边滤波的GPU版本函数bilateralFilter()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该函数在 GPU 上执行双边滤波操作&#xff0c;是一种非线性平滑滤波器&#xff0c;能够在 保留边缘的同时去除噪声。 函数原型 void cv::cuda:…

华为手机开机卡在Huawei界面不动怎么办?

遇到华为手机卡在启动界面&#xff08;如HUAWEI Logo界面&#xff09;的情况&#xff0c;可依次尝试以下解决方案&#xff0c;按操作复杂度和风险由低到高排序&#xff1a; &#x1f527; 一、强制重启&#xff08;优先尝试&#xff09; 1.通用方法‌ 长按 ‌电源键 音量下键‌…

go语言map扩容

map是什么&#xff1f; ​在Go语言中&#xff0c;map是一种内置的无序key/value键值对的集合&#xff0c;可以根据key在O(1)的时间复杂度内取到value&#xff0c;有点类似于数组或者切片结构&#xff0c;可以把数组看作是一种特殊的map&#xff0c;数组的key为数组的下标&…

5.3 Spring Boot整合JPA

本文详细介绍了如何在Spring Boot项目中整合Spring JPA&#xff0c;实现对数据库的高效操作。首先&#xff0c;创建Spring Boot项目并添加必要的依赖&#xff0c;如Druid数据源。接着&#xff0c;配置数据源属性&#xff0c;创建实体类Comment和Article&#xff0c;并使用JPA注…