前阵子帮天津武清的一家汽车零部件厂救急,差点被三条产线的运维和工人围堵在中控室。

这家工厂的三条产线,用的设备协议完全不统一:焊装线的西门子S7-1200/1500用Modbus TCP,涂装线的ABB机器人+欧姆龙温控器用OPC UA,总装线的AGV+汇川PLC用CANopen。之前的方案是三条线三套上位机、三个数据库、三个WinForms客户端、三套MES对接接口,问题多到离谱:

  • 工人巡检要切三个窗口,总装线AGV报警要等10秒才能看到,差点撞坏物料架;
  • MES对接写了三套代码,前前后后折腾了一个月,还经常出现数据时序错乱;
  • 运维要维护三套Windows环境,备份要分三次,每次折腾2小时;
  • 后来要加质检线的Profinet设备,又要从头写一套上位机,工期要一个月。

最严重的一次,焊装线的紧急避障指令被普通采集任务阻塞,延迟了500ms才下发,AGV差点撞坏200多万的KUKA焊接机器人。

后来我花了两周时间,重构了这套四层解耦的多协议统一上位机架构,一套代码搞定了所有设备,MES对接只要一个标准API,运维只要维护一套Docker容器,工人只要一个浏览器就能全产线巡检。上线半年,零宕机、零数据丢失,顺利通过了主机厂的生产追溯审计。

今天把这套完整的架构设计、核心代码、踩坑实录、ARM+AOT适配方案全部分享出来,都是工业现场摸爬滚打出来的干货,新手看完能少走3个月弯路,老手看完也能直接落地复用。


一、先搞懂:这套架构解决什么问题?

很多新手一上来就想做“万能上位机”,结果越做越乱。先明确这套架构的适用边界,不吹不黑,只解决工业现场最痛的问题。

1.1 核心解决的痛点

  • 多协议设备碎片化:不用再为Modbus/OPC UA/CAN分别写一套上位机,一套代码全兼容;
  • 跨平台部署难:全链路支持.NET 9 AOT编译,Windows x64、Linux ARM64工控机都能跑,单文件部署,不用装.NET运行时;
  • 高可用能力弱:自带心跳检测、自动重连、离线缓存、指令优先级调度,断网不丢数据,紧急指令不阻塞;
  • 对接扩展难:统一RESTful API,MES/ERP/云平台只要对接一套接口,新增设备只要改配置,不用改核心代码;
  • 用户体验差:Blazor Server跨平台Web UI,一个浏览器搞定全产线巡检,不用装多个客户端。

1.2 适用场景

  • 单工厂1-10条产线、100-5000点位的中小规模工业数字化改造;
  • 多协议设备共存的产线升级、旧设备改造;
  • 成本敏感的项目:ARM工控机替代x86,成本直降70%;
  • 有跨平台、远程巡检需求的场景;
  • 有生产数据追溯、合规审计要求的汽车、食品、医药行业。

1.3 不适用场景

  • 超大型工厂万点级SCADA系统(推荐用商用SCADA平台);
  • 毫秒级硬实时运动控制场景(推荐用专用软PLC)。

二、架构总览:四层解耦设计,全链路AOT友好

这套架构我前后迭代了3个版本,最终落地的是四层分层设计,完全遵循开闭原则——新增协议/设备只要加一个适配器,不用改一行核心业务代码,全链路选用AOT友好的依赖组件,无动态代码、无反射滥用。

┌─────────────────────────────────────────────────────────────────────────────────────┐
│ 【应用交互层】Blazor Server跨平台UI、WinForms桌面端、MES/ERP RESTful对接、MQTT上云 │
└───────────────────────────────────────┬─────────────────────────────────────────────┘
                                        │
┌───────────────────────────────────────▼─────────────────────────────────────────────┐
│ 【接口服务层】统一RESTful API、SignalR实时数据推送、身份认证、权限控制、操作审计   │
└───────────────────────────────────────┬─────────────────────────────────────────────┘
                                        │
┌───────────────────────────────────────▼─────────────────────────────────────────────┐
│ 【核心服务层】数据处理引擎、规则/报警引擎、指令优先级调度、离线缓存、时序数据存储 │
└───────────────────────────────────────┬─────────────────────────────────────────────┘
                                        │
┌───────────────────────────────────────▼─────────────────────────────────────────────┐
│ 【设备接入层】统一通信接口、Modbus/OPC UA/CANopen适配器、设备生命周期管理、心跳检测 │
└─────────────────────────────────────────────────────────────────────────────────────┘

核心设计思路只有一个:把所有协议差异封装在最底层,上层业务完全不用关心底层用的是什么协议。就像我们用手机充电,不用关心墙里是火电还是水电,只要插上充电线就能用。


三、分层详细设计(含可直接落地的核心代码)

3.1 设备接入层:架构的灵魂,多协议统一适配

这一层是整个架构的核心,用适配器模式+工厂模式,让所有协议适配器都实现同一个IIndustrialDevice统一通信接口。上层业务不用管底层是Modbus还是OPC UA,只要调用这一套接口,就能实现所有设备的读写。

3.1.1 AOT适配第一原则

工业场景现在越来越流行ARM+AOT部署,所以设计之初就定下了铁律:

  • 禁用dynamic动态类型,所有数据类型显式声明;
  • 禁用非必要反射,仅在配置反序列化时使用,且显式配置AOT裁剪保留;
  • 所有第三方库必须经过AOT兼容验证;
  • 协议相关配置完全外置,新增设备不用改代码。
3.1.2 统一通信接口定义(全协议通用)

不管是什么协议,本质上都是「连接设备、读数据、写数据、断开连接」,所以我们把这些通用能力抽象成一个统一接口,所有协议适配器都必须实现这个接口。

namespace MultiProtocolHmi.DeviceAdapter
{
    // 设备状态枚举
    public enum DeviceStatus
    {
        Disconnected,
        Connecting,
        Connected,
        Fault
    }

    // 数据点质量枚举(兼容OPC UA,Modbus/CAN默认Good)
    public enum DataQuality
    {
        Good,
        Bad,
        Uncertain
    }

    // 数据点类型枚举(覆盖所有工业协议常用类型)
    public enum DataType
    {
        Bool,
        Int16,
        UInt16,
        Int32,
        UInt32,
        Float,
        Double,
        String
    }

    // 统一数据点模型:所有协议的采集数据都转换成这个模型
    public class DeviceDataPoint
    {
        public string DeviceId { get; set; } = string.Empty;
        public string PointId { get; set; } = string.Empty;
        public string PointName { get; set; } = string.Empty;
        public DataType DataType { get; set; }
        public object? Value { get; set; }
        public DateTime TimestampUtc { get; set; }
        public DataQuality Quality { get; set; }
    }

    // 统一控制指令模型:所有协议的写入指令都用这个模型
    public class DeviceControlCommand
    {
        public string CommandId { get; set; } = Guid.NewGuid().ToString("N");
        public string DeviceId { get; set; } = string.Empty;
        public string PointId { get; set; } = string.Empty;
        public object? TargetValue { get; set; }
        public byte Priority { get; set; } = 100; // 0最高,255最低
        public int TimeoutMs { get; set; } = 2000;
        public int MaxRetryCount { get; set; } = 2;
        public int CurrentRetryCount { get; set; } = 0;
        public string Operator { get; set; } = string.Empty;
    }

    // 统一通信接口:所有协议适配器必须实现
    public interface IIndustrialDevice : IDisposable
    {
        // 设备基础信息
        string DeviceId { get; }
        string DeviceName { get; }
        DeviceStatus Status { get; }
        
        // 事件通知:上层业务通过事件接收状态变化和数据更新
        event EventHandler<DeviceStatus>? OnStatusChanged;
        event EventHandler<DeviceDataPoint>? OnDataPointChanged;

        // 通用方法
        Task ConnectAsync(CancellationToken cancellationToken = default);
        Task DisconnectAsync(CancellationToken cancellationToken = default);
        Task<DeviceDataPoint?> ReadSinglePointAsync(string pointId, CancellationToken cancellationToken = default);
        Task<List<DeviceDataPoint>> ReadMultiPointsAsync(List<string> pointIds, CancellationToken cancellationToken = default);
        Task<bool> WriteSinglePointAsync(DeviceControlCommand command, CancellationToken cancellationToken = default);
    }
}
3.1.3 适配器工厂+配置外置:新增设备不用改代码

我们把所有协议相关的配置都放在appsettings.json里,新增/修改设备只要改配置,不用动一行核心代码,完全适配工厂快速迭代的需求。

适配器工厂代码

namespace MultiProtocolHmi.DeviceAdapter
{
    // 协议类型枚举
    public enum ProtocolType
    {
        ModbusTcp,
        ModbusRtu,
        OpcUa,
        CanOpen
    }

    // 设备配置模型
    public class DeviceConfig
    {
        public string DeviceId { get; set; } = string.Empty;
        public string DeviceName { get; set; } = string.Empty;
        public ProtocolType ProtocolType { get; set; }
        public string ProtocolConfigJson { get; set; } = string.Empty;
        public string DataPointsConfigJson { get; set; } = string.Empty;
        public int HeartbeatIntervalMs { get; set; } = 1000;
        public int MaxReconnectCount { get; set; } = 10;
    }

    // 适配器工厂:根据配置自动创建对应的协议适配器
    public static class DeviceAdapterFactory
    {
        public static IIndustrialDevice CreateAdapter(DeviceConfig config, ILogger logger)
        {
            return config.ProtocolType switch
            {
                ProtocolType.ModbusTcp => new ModbusTcpAdapter(config, logger),
                ProtocolType.ModbusRtu => new ModbusRtuAdapter(config, logger),
                ProtocolType.OpcUa => new OpcUaAdapter(config, logger),
                ProtocolType.CanOpen => new CanOpenAdapter(config, logger),
                _ => throw new NotSupportedException($"不支持的协议类型:{config.ProtocolType}")
            };
        }
    }
}

配置文件示例(appsettings.json)

{
  "DeviceConfigs": [
    {
      "DeviceId": "WeldLine_PLC01",
      "DeviceName": "焊装线S7-1200 PLC",
      "ProtocolType": "ModbusTcp",
      "ProtocolConfigJson": "{\"IpAddress\":\"192.168.0.10\",\"Port\":502,\"UnitId\":1}",
      "DataPointsConfigJson": "[{\"PointId\":\"AI0_WeldTemp\",\"PointName\":\"焊枪温度\",\"DataType\":\"Float\",\"FunctionCode\":3,\"Address\":0,\"Coefficient\":0.1}]",
      "HeartbeatIntervalMs": 1000,
      "MaxReconnectCount": 10
    },
    {
      "DeviceId": "PaintLine_Robot01",
      "DeviceName": "涂装线ABB机器人",
      "ProtocolType": "OpcUa",
      "ProtocolConfigJson": "{\"EndpointUrl\":\"opc.tcp://192.168.0.20:4840\",\"UseSecurity\":false}",
      "DataPointsConfigJson": "[{\"PointId\":\"AxisX_Pos\",\"PointName\":\"X轴位置\",\"DataType\":\"Float\",\"NodeId\":\"ns=2;s=Robot1.AxisX.Position\"}]",
      "HeartbeatIntervalMs": 500,
      "MaxReconnectCount": 10
    }
  ]
}
3.1.4 三大协议适配器选型与实现

所有适配器都经过AOT兼容验证,工业现场稳定运行6个月以上:

  • Modbus TCP/RTU适配器:基于FluentModbus实现,比NModbus更轻量,AOT友好,自带心跳检测、自动重连、批量读写优化;
  • OPC UA适配器:基于OPC Foundation官方标准库实现,已验证支持AOT,禁用动态类型,完全适配统一接口;
  • CANopen适配器:基于System.Device.Gpio+SocketCAN(Linux)/PCAN Basic(Windows)实现,支持AOT编译,适配ARM工控机。
【踩坑实录】适配器模式的天坑

当年第一版设计的时候,踩了一个90%的开发者都会踩的坑:适配器模式用了个寂寞,业务层还是要关心底层协议

一开始的代码里,业务层写了大量的if (adapter is ModbusTcpAdapter)if (adapter is OpcUaAdapter)的判断,后来加CANopen适配器,又要加一堆判断,业务层代码从1000行涨到3000行,完全没法维护。

解决方法:把所有协议相关的细节(比如Modbus的UnitId、OPC UA的NodeId、CANopen的节点ID)全部封装到适配器内部,通过配置文件外置,业务层只需要调用统一接口,完全不用关心底层是什么协议。


3.2 核心服务层:工业级能力封装,与协议完全解耦

这一层完全不关心底层用什么协议,只基于统一接口实现工业场景必备的核心能力,所有功能可配置、可扩展。

服务名称 核心职责 工业场景价值
设备生命周期管理服务 设备批量连接/断开、状态监控、心跳检测、自动重连 设备断线3秒内感知,10秒内自动重连,无需人工干预
数据采集与处理服务 批量数据采集、数据解析、滤波、差分上报、时序对齐 1000点采集延迟≤50ms,数据时序一致,无脏数据
指令优先级调度服务 基于PriorityQueue实现,高优先级指令插队执行、超时控制、重试机制 紧急停止指令延迟≤10ms,不会被普通采集阻塞
规则与报警引擎 本地阈值判断、报警触发/确认/清除、分级推送、持久化 异常报警100ms内触发,无需等待云端处理,避免生产事故
离线缓存服务 基于SQLite实现本地持久化缓存,断网缓存、联网补发 断网72小时数据零丢失,满足生产追溯合规要求
时序数据存储服务 基于InfluxDB OSS实现,工业时序数据专用存储 7天历史数据查询≤200ms,比MySQL快100倍

这里重点讲工业场景最核心的指令优先级调度服务,当年焊装线差点出事故,就是因为没有优先级调度,紧急指令被普通采集阻塞。

指令优先级调度核心代码
namespace MultiProtocolHmi.CoreServices
{
    public interface ICommandSchedulerService
    {
        Task EnqueueCommandAsync(DeviceControlCommand command, CancellationToken cancellationToken = default);
        Task StartAsync(CancellationToken cancellationToken = default);
        Task StopAsync(CancellationToken cancellationToken = default);
    }

    public class CommandSchedulerService : ICommandSchedulerService
    {
        // .NET 6+ 自带的PriorityQueue是最小堆,优先级数值越小,优先级越高
        private readonly PriorityQueue<DeviceControlCommand, byte> _commandQueue = new();
        private readonly IServiceProvider _serviceProvider;
        private readonly ILogger<CommandSchedulerService> _logger;
        private readonly object _lock = new();
        private bool _isRunning;
        private Task? _processTask;
        private readonly CancellationTokenSource _cts = new();

        public CommandSchedulerService(IServiceProvider serviceProvider, ILogger<CommandSchedulerService> logger)
        {
            _serviceProvider = serviceProvider;
            _logger = logger;
        }

        // 指令入队
        public Task EnqueueCommandAsync(DeviceControlCommand command, CancellationToken cancellationToken = default)
        {
            lock (_lock)
            {
                _commandQueue.Enqueue(command, command.Priority);
                _logger.LogInformation($"指令已入队,ID:{command.CommandId},优先级:{command.Priority},目标设备:{command.DeviceId}");
            }
            return Task.CompletedTask;
        }

        // 启动调度服务
        public Task StartAsync(CancellationToken cancellationToken = default)
        {
            if (_isRunning)
                return Task.CompletedTask;

            _isRunning = true;
            _processTask = ProcessCommandLoopAsync(_cts.Token);
            _logger.LogInformation("指令调度服务已启动");
            return Task.CompletedTask;
        }

        // 指令处理循环
        private async Task ProcessCommandLoopAsync(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested && _isRunning)
            {
                DeviceControlCommand? command = null;
                lock (_lock)
                {
                    // 每次取队列里优先级最高的指令
                    if (_commandQueue.TryDequeue(out command, out _))
                    {
                        command = command;
                    }
                }

                if (command != null)
                {
                    await ExecuteCommandAsync(command, cancellationToken);
                }
                else
                {
                    // 队列为空,等待10ms,避免CPU空转
                    await Task.Delay(10, cancellationToken);
                }
            }
        }

        // 执行指令
        private async Task ExecuteCommandAsync(DeviceControlCommand command, CancellationToken cancellationToken)
        {
            try
            {
                _logger.LogInformation($"开始执行指令,ID:{command.CommandId},目标:{command.DeviceId}.{command.PointId}");
                // 超时控制
                using var timeoutCts = new CancellationTokenSource(command.TimeoutMs);
                using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);

                // 获取对应设备适配器
                var deviceManager = _serviceProvider.GetRequiredService<IDeviceManagerService>();
                var device = deviceManager.GetDeviceById(command.DeviceId);
                if (device == null || device.Status != DeviceStatus.Connected)
                {
                    _logger.LogError($"指令执行失败,设备不在线,ID:{command.CommandId}");
                    return;
                }

                // 执行写入:完全不用关心底层协议
                var result = await device.WriteSinglePointAsync(command, linkedCts.Token);
                if (result)
                {
                    _logger.LogInformation($"指令执行成功,ID:{command.CommandId}");
                    // 记录操作审计日志
                    var auditService = _serviceProvider.GetRequiredService<IAuditService>();
                    await auditService.RecordOperationAsync(command, result, cancellationToken);
                }
                else
                {
                    _logger.LogError($"指令执行失败,ID:{command.CommandId}");
                    // 重试机制
                    if (command.CurrentRetryCount < command.MaxRetryCount)
                    {
                        command.CurrentRetryCount++;
                        await EnqueueCommandAsync(command, cancellationToken);
                    }
                }
            }
            catch (OperationCanceledException)
            {
                _logger.LogError($"指令执行超时,ID:{command.CommandId}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"指令执行异常,ID:{command.CommandId}");
            }
        }

        // 停止调度服务
        public async Task StopAsync(CancellationToken cancellationToken = default)
        {
            if (!_isRunning)
                return;

            _isRunning = false;
            _cts.Cancel();
            if (_processTask != null)
                await _processTask;
            _cts.Dispose();
            _logger.LogInformation("指令调度服务已停止");
        }
    }
}

3.3 接口服务层:统一对接入口,全场景覆盖

这一层为上层应用提供统一的对接入口,完全与底层协议解耦,MES/ERP/第三方平台只要对接这一套接口,不用关心底层设备用的是什么协议。

  • RESTful API:提供设备管理、数据查询、指令下发、报警管理的标准HTTP接口,MES对接零成本;
  • SignalR实时推送:实时推送设备状态、数据变化、报警信息,Web UI延迟≤100ms;
  • MQTT上云接口:支持将数据推送到阿里云/腾讯云/华为云IoT平台,满足集团级远程监控需求;
  • 身份认证与权限控制:基于JWT实现身份认证,基于RBAC实现权限控制,满足等保2.0要求;
  • 操作审计:所有控制指令、配置修改全链路留痕,可追溯、可审计。

3.4 应用交互层:跨平台全场景覆盖

这一层面向最终用户,提供多端适配的交互能力,彻底解决之前多客户端切换的痛点:

  1. Blazor Server跨平台Web UI:一套代码适配Windows/Linux/macOS/移动端,工人用浏览器就能访问,无需安装客户端,跨车间巡检零门槛;
  2. WinForms桌面端:适配传统中控室场景,高性能、低延迟,满足关键产线本地控制需求;
  3. 移动端H5:适配手机/平板,支持报警推送、远程巡检、紧急停机,管理人员随时随地掌握生产状态。

四、.NET 9 AOT全链路适配+ARM工控机部署

这是这套架构最大的亮点之一:全链路支持AOT编译,单文件部署到ARM工控机,成本直降70%,冷启动≤200ms。

4.1 AOT适配核心配置

项目文件MultiProtocolHmi.csproj的核心配置,全链路AOT兼容,单文件体积≤30MB,冷启动≤200ms:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <!-- AOT核心配置 -->
    <PublishAot>true</PublishAot>
    <PublishSingleFile>true</PublishSingleFile>
    <SelfContained>true</SelfContained>
    <TrimMode>link</TrimMode>
    <OptimizationPreference>Size</OptimizationPreference>
    <InvariantGlobalization>true</InvariantGlobalization>
    <SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
    <!-- 目标运行时:ARM64 Linux,适配RK3588/树莓派 -->
    <RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
  </PropertyGroup>

  <ItemGroup>
    <!-- 全部经过AOT兼容验证的依赖包 -->
    <PackageReference Include="FluentModbus" Version="5.1.0" />
    <PackageReference Include="OPCFoundation.NetStandard.Opc.Ua" Version="1.5.374.1" />
    <PackageReference Include="InfluxDB.Client" Version="2.7.0" />
    <PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
    <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
    <PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
    <PackageReference Include="System.Device.Gpio" Version="3.0.0" />
  </ItemGroup>

  <!-- AOT裁剪保留配置 -->
  <ItemGroup>
    <TrimmerRootAssembly Include="MultiProtocolHmi" />
  </ItemGroup>
</Project>

4.2 编译命令

# ARM64 Linux 单文件AOT发布
dotnet publish -c Release -r linux-arm64 --self-contained true -p:PublishAot=true -p:PublishSingleFile=true

# 可选:upx压缩,体积从30MB降到10MB以内
upx --best --lzma MultiProtocolHmi

4.3 ARM工控机部署步骤

  1. 硬件准备:瑞芯微RK3588/树莓派4B/5,安装Debian 12/Ubuntu 22.04 ARM64系统;
  2. 环境配置
    # 安装串口/CAN依赖
    sudo apt update && sudo apt install -y libssl-dev can-utils udev
    # 配置串口权限
    sudo usermod -aG dialout $USER
    # 配置CAN权限
    sudo usermod -aG netdev $USER
    
  3. 程序部署:将编译好的单文件拷贝到ARM工控机,添加执行权限:
    chmod +x MultiProtocolHmi
    # 后台运行
    nohup ./MultiProtocolHmi > hmi.log 2>&1 &
    # 配置systemd开机自启(可选)
    
  4. 访问测试:浏览器访问http://ARM工控机IP:5000,即可打开Web UI。
【踩坑实录】AOT适配的5个天坑
  1. OPC UA库动态类型问题:一开始用了dynamic处理NodeId,导致AOT编译失败,后来全部换成object+显式类型转换,完美解决;
  2. 第三方库不支持AOT:一开始用了某国产CAN卡的SDK,不支持AOT,后来换成了开源的SocketCAN/PCAN Basic,完美适配;
  3. JSON反序列化裁剪警告:配置文件反序列化时,AOT裁剪会丢失类型信息,后来用JsonSerializable显式声明序列化类型,解决了问题;
  4. Linux串口权限问题:ARM工控机上普通用户没有串口权限,程序运行报错,通过udev规则固定权限,解决了问题;
  5. Blazor Server AOT兼容问题:.NET 8之前的Blazor Server对AOT支持不好,升级到.NET 9后,完美支持AOT编译。

五、落地效果:天津工厂实测数据

这套架构在天津武清的汽车零部件厂落地后,效果远超客户预期,实测数据如下:

测试项 实测结果 工业场景价值
1000点数据采集延迟 ≤50ms 实时监控无卡顿
紧急控制指令响应延迟 ≤10ms 紧急停机无延迟,避免生产事故
AOT程序冷启动时间 180ms 工控机开机后快速就绪
单文件体积(upx压缩) 8MB 部署便捷,低配置工控机无压力
内存占用(空闲) 22MB 长时间运行无内存泄漏
断网数据补发能力 72小时数据零丢失 满足生产追溯合规要求
设备断线重连时间 ≤10s 无需人工干预,自动恢复

最终落地收益

  1. 运维成本降低90%:从3套系统维护变成1套代码维护,运维时间从每周8小时降到每周30分钟;
  2. MES对接成本降低80%:从3套接口开发变成1套标准API对接,开发时间从1个月降到3天;
  3. 硬件成本降低70%:从3台x86工控机(3000元/台)变成1台ARM工控机(900元/台),硬件成本从9000元降到2500元;
  4. 系统稳定性拉满:连续运行6个月无宕机、无数据丢失,顺利通过主机厂的生产追溯审计。

六、新手3天上手计划

很多新手看完会觉得“架构太复杂,我学不会”,其实完全不用怕,我给你整理了3天上手计划,循序渐进,新手也能快速落地:

  • 第一天:搭建开发环境,实现Modbus TCP适配器,跑通单个PLC的采集和写入,熟悉统一接口的设计思路;
  • 第二天:实现指令优先级调度、离线缓存核心服务,跑通Blazor Server Web UI,实现实时数据显示和指令下发;
  • 第三天:完成AOT编译,部署到ARM工控机,跑通全流程,实现多设备统一管理。

最后说几句

工业上位机的核心,从来不是炫技用多少新技术,而是稳定、可靠、好用、好维护。这套架构没有用什么高大上的技术,只是用最经典的设计模式,解决了工业现场最痛的问题。

不用再为不同的协议写N套代码,不用再维护N套环境,不用再担心紧急指令被阻塞,不用再怕断网丢数据。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐