【轻量化开发】C# 上位机工具实战:快速调试与数据导出功能实现

一、引言

在工业调试、设备研发、产线维护等场景中,开发者经常需要一款轻量、启动快、专注核心功能的上位机工具,而不是功能臃肿的大型监控系统。

本文将手把手教你使用 C# + WPF 开发一款轻量化上位机调试工具,核心功能包括:

  • 快速串口 / Modbus TCP 调试
  • 实时数据监控与指令收发
  • 一键数据导出(Excel / CSV)
  • 日志记录与历史回放

该工具启动速度快(<1秒)、内存占用低,非常适合研发调试、现场维护和临时数据采集场景。

二、系统架构设计

设计原则

  • 轻量化:单项目、无复杂框架、功能聚焦
  • 高响应:异步通信 + 响应式UI
  • 易扩展:支持后续增加 Modbus RTU/TCP、OPC UA 等协议

核心模块

  • 通信模块(SerialPort + Modbus)
  • 调试控制台模块
  • 实时数据监控模块
  • 数据导出模块
  • 日志系统

三、项目搭建与核心实现

1. 项目创建与NuGet包

推荐NuGet包

  • CommunityToolkit.Mvvm(MVVM)
  • ClosedXML(Excel导出,推荐)
  • Serilog + Serilog.Sinks.File(日志)
  • OxyPlot.Wpf(可选实时曲线)
2. 轻量级串口调试实现
public partial class MainViewModel : ObservableObject
{
    private SerialPort? _serialPort;

    [ObservableProperty] private string portName = "COM3";
    [ObservableProperty] private int baudRate = 9600;
    [ObservableProperty] private string sendCommand = "";
    [ObservableProperty] private string receiveLog = "";

    [RelayCommand]
    private void OpenPort()
    {
        try
        {
            _serialPort = new SerialPort(PortName, BaudRate, Parity.None, 8, StopBits.One);
            _serialPort.DataReceived += SerialPort_DataReceived;
            _serialPort.Open();
            Log.Information("串口 {Port} 打开成功", PortName);
        }
        catch (Exception ex)
        {
            Log.Error(ex, "串口打开失败");
        }
    }

    private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        string data = _serialPort?.ReadExisting() ?? "";
        ReceiveLog += $"[{DateTime.Now:HH:mm:ss}] {data}\n";
    }

    [RelayCommand]
    private void Send()
    {
        if (_serialPort?.IsOpen == true && !string.IsNullOrEmpty(SendCommand))
        {
            _serialPort.WriteLine(SendCommand);
            Log.Information("发送: {Cmd}", SendCommand);
        }
    }
}
3. Modbus TCP 快速调试
public class ModbusDebugService
{
    private ModbusTcpMaster? _master;

    public async Task ConnectAsync(string ip, int port = 502)
    {
        _master = new ModbusTcpMaster(ip, port);
        await _master.ConnectAsync();
    }

    public async Task<ushort[]> ReadRegistersAsync(ushort address, ushort count)
    {
        return await _master!.ReadHoldingRegistersAsync(1, address, count);
    }
}
4. 数据导出功能(核心)
[RelayCommand]
private void ExportToExcel()
{
    try
    {
        using var workbook = new XLWorkbook();
        var worksheet = workbook.Worksheets.Add("采集数据");

        // 写入表头
        worksheet.Cell(1, 1).Value = "时间";
        worksheet.Cell(1, 2).Value = "设备ID";
        worksheet.Cell(1, 3).Value = "参数";
        worksheet.Cell(1, 4).Value = "数值";

        // 写入数据(示例)
        var dataList = _dataService.GetCollectedData();
        for (int i = 0; i < dataList.Count; i++)
        {
            var row = i + 2;
            worksheet.Cell(row, 1).Value = dataList[i].Timestamp;
            worksheet.Cell(row, 2).Value = dataList[i].DeviceId;
            worksheet.Cell(row, 3).Value = dataList[i].Parameter;
            worksheet.Cell(row, 4).Value = dataList[i].Value;
        }

        var fileName = $"DebugData_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";
        workbook.SaveAs(fileName);

        Log.Information("数据已导出:{FileName}", fileName);
        MessageBox.Show("导出成功!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
    }
    catch (Exception ex)
    {
        Log.Error(ex, "导出失败");
    }
}

CSV导出(更轻量):

private void ExportToCsv()
{
    var csv = new StringBuilder();
    csv.AppendLine("时间,设备ID,参数,数值");

    foreach (var item in _dataList)
        csv.AppendLine($"{item.Timestamp},{item.DeviceId},{item.Parameter},{item.Value}");

    File.WriteAllText($"Data_{DateTime.Now:yyyyMMddHHmmss}.csv", csv.ToString());
}

五、界面设计建议(轻量化WPF)

  • 使用 TabControl 分页:串口调试 | Modbus TCP | 数据监控 | 历史记录
  • 左侧配置区 + 右侧日志/数据区
  • 状态栏显示当前连接状态

六、生产级增强

  • 使用 ValueTask 优化高频采集
  • Serilog 异步日志记录
  • 支持配置文件保存常用设备参数
  • 添加“自动重连”功能

七、部署与打包

使用 .NET 9 Native AOT 单文件发布:

dotnet publish -c Release -r win-x64 --self-contained true /p:PublishAot=true

最终生成单个exe文件,体积小、启动快,非常适合U盘携带调试。


结语:一款优秀的轻量化上位机工具,核心不在于功能多,而在于够快、够稳、够实用。通过本文的方案,你可以快速打造出一款适用于研发、调试、维护场景的C#上位机工具,大幅提升工作效率。

本工具可作为基础框架,后续轻松扩展为完整监控系统。


需要我继续补充以下任意部分吗?

  1. 完整 MainWindow.xaml + Dashboard 布局代码
  2. 串口 + Modbus 双协议切换 完整实现
  3. 实时数据网格 + 自动刷新 示例
  4. 整个项目的 App.xaml.cs 初始化代码

我对之前的代码示例进行了全面结构优化,重点提升可维护性、可扩展性、可测试性工业级规范


一、优化后的工程代码结构(推荐)

IndustrialMonitor.WPF/
├── IndustrialMonitor/
│   ├── App.xaml
│   ├── App.xaml.cs                          # 程序启动入口 + DI 配置
│   │
│   ├── Common/                              # 通用基础
│   │   ├── Constants.cs
│   │   ├── Extensions/
│   │   └── Helpers/
│   │
│   ├── Core/                                # 领域核心
│   │   ├── Models/                          # 实体、DTO
│   │   ├── Enums/
│   │   └── Events/                          # Prism 或自定义事件
│   │
│   ├── Interfaces/                          # 接口层(重要!)
│   │   ├── IDeviceService.cs
│   │   ├── IModbusService.cs
│   │   ├── IAlarmService.cs
│   │   ├── ILinkageService.cs
│   │   └── IExportService.cs
│   │
│   ├── Services/                            # 实现层
│   │   ├── Device/
│   │   ├── Modbus/
│   │   │   ├── ModbusTcpService.cs
│   │   │   └── ModbusRtuService.cs
│   │   ├── LinkageService.cs
│   │   ├── AlarmService.cs
│   │   ├── ExportService.cs
│   │   └── LoggingService.cs
│   │
│   ├── ViewModels/                          # MVVM
│   │   ├── MainViewModel.cs
│   │   ├── DashboardViewModel.cs
│   │   └── LinkageRuleViewModel.cs
│   │
│   ├── Views/                               # 界面
│   │   ├── DashboardView.xaml
│   │   └── LinkageRuleEditorView.xaml
│   │
│   └── Configuration/                       # 配置管理
│       └── AppSettings.cs
│
├── IndustrialMonitor.Core/                  # 可复用核心库(推荐)
└── IndustrialMonitor.Tests/

二、优化后的核心代码示例

1. 接口定义(Interfaces/IModbusService.cs)
public interface IModbusService : IAsyncDisposable
{
    Task<bool> ConnectAsync(ModbusConfig config);
    Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count);
    Task WriteSingleRegisterAsync(ushort address, ushort value);
    Task<bool> IsConnected { get; }
    
    event EventHandler<ModbusDataReceivedEventArgs>? DataReceived;
}
2. 服务实现(推荐使用依赖注入)
public class ModbusTcpService : IModbusService
{
    private ModbusTcpMaster? _master;
    private readonly ILogger _logger;
    private readonly IOptions<ModbusConfig> _config;

    public ModbusTcpService(ILogger<ModbusTcpService> logger, IOptions<ModbusConfig> config)
    {
        _logger = logger;
        _config = config;
    }

    public async Task<bool> ConnectAsync(ModbusConfig config)
    {
        try
        {
            _master = new ModbusTcpMaster(config.IpAddress, config.Port);
            await _master.ConnectAsync();
            _logger.Information("Modbus TCP 连接成功 {Ip}:{Port}", config.IpAddress, config.Port);
            return true;
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Modbus TCP 连接失败");
            return false;
        }
    }

    public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count)
    {
        if (_master == null || !_master.IsConnected)
            throw new InvalidOperationException("Modbus 未连接");

        return await _master.ReadHoldingRegistersAsync(1, startAddress, count);
    }

    public async ValueTask DisposeAsync()
    {
        _master?.Dispose();
    }
}
3. ViewModel 优化(使用 CommunityToolkit.Mvvm)
public partial class DashboardViewModel : ObservableObject
{
    private readonly IModbusService _modbusService;
    private readonly IExportService _exportService;

    [ObservableProperty] private bool isConnected;
    [ObservableProperty] private string statusText = "已断开";

    public DashboardViewModel(IModbusService modbusService, IExportService exportService)
    {
        _modbusService = modbusService;
        _exportService = exportService;
    }

    [RelayCommand]
    private async Task ConnectAsync()
    {
        IsConnected = await _modbusService.ConnectAsync(new ModbusConfig { /*...*/ });
        StatusText = IsConnected ? "已连接" : "连接失败";
    }

    [RelayCommand]
    private async Task ExportDataAsync()
    {
        await _exportService.ExportToExcelAsync(_currentData);
    }
}
4. 依赖注入配置(App.xaml.cs)
public partial class App : Application
{
    public App()
    {
        Services = ConfigureServices();
        SerilogConfig.Initialize();
    }

    public static IServiceProvider Services { get; private set; } = null!;

    private static IServiceProvider ConfigureServices()
    {
        var services = new ServiceCollection();

        // 配置
        services.Configure<ModbusConfig>(options => { /* 从appsettings加载 */ });

        // 服务注册
        services.AddSingleton<IModbusService, ModbusTcpService>();
        services.AddSingleton<IExportService, ExportService>();
        services.AddSingleton<ILinkageService, LinkageService>();

        // ViewModels
        services.AddTransient<DashboardViewModel>();
        services.AddTransient<LinkageRuleViewModel>();

        return services.BuildServiceProvider();
    }
}

优化总结

优化点 优化前 优化后 收益
结构 全部写在一个文件 分层清晰 可维护性大幅提升
依赖管理 new 直接实例化 依赖注入 + 接口 易于单元测试和扩展
日志 Console.WriteLine Serilog 结构化日志 生产级可追溯
异步 混用同步 全面异步 + ValueTask 高并发下更稳定
配置 硬编码 IOptions + 配置文件 支持不同环境切换

需要我继续优化以下任意部分吗?

  1. 完整 ExportService(Excel + CSV)
  2. 串口粘包解析器 的优化最终版
  3. 报警中心完整实现
  4. 整个项目的 App.xaml.cs + MainWindow 初始化代码
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐