首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >上位机系统:从设备通信到人机协同的工程实践

上位机系统:从设备通信到人机协同的工程实践

原创
作者头像
IT大佬 jzit-top
发布于 2026-10-12 11:38:37
发布于 2026-10-12 11:38:37
220
举报

上位机系统(Host Computer System)是工业自动化的中枢——下接PLC、运动控制卡、传感器,上承MES、ERP与操作人员。它不只是"画个界面连设备",而是要在实时性、可靠性、可维护性之间取得平衡。本文从通信层、数据层、控制层、UI层四个维度,给出可落地的架构与代码。

一、分层架构

专业的上位机系统分为四层:

  • 通信层:串口、TCP、Modbus、OPC UA,屏蔽协议差异。
  • 设备层:将物理设备抽象为服务,管理连接、心跳、重连。
  • 控制层:状态机驱动业务流程,处理联锁、回退、审计。
  • UI层:WPF + MVVM,实时展示状态,下发命令。

核心原则:UI不直接操作设备,设备不感知UI。所有交互通过服务接口与事件总线解耦。

二、通信层:统一接口与协议适配

无论底层是串口还是TCP,上层应看到统一的接口:

代码语言:javascript
复制
public interface IDeviceChannel : IDisposable {
    bool IsConnected { get; }
    Task ConnectAsync(CancellationToken ct);
    Task<byte[]> SendAsync(byte[] request, CancellationToken ct);
    event EventHandler<byte[]> DataReceived;
}

串口实现带互斥锁与超时,避免并发写冲突:

代码语言:javascript
复制
public class SerialChannel : IDeviceChannel {
    private readonly SerialPort _port;
    private readonly SemaphoreSlim _lock = new(1, 1);

    public SerialChannel(string portName, int baudRate = 115200) {
        _port = new SerialPort(portName, baudRate) {
            ReadTimeout = 2000, WriteTimeout = 2000
        };
        _port.DataReceived += (s, e) => {
            var buf = new byte[_port.BytesToRead];
            _port.Read(buf, 0, buf.Length);
            DataReceived?.Invoke(this, buf);
        };
    }

    public bool IsConnected => _port.IsOpen;
    public Task ConnectAsync(CancellationToken ct) {
        if (!_port.IsOpen) _port.Open();
        return Task.CompletedTask;
    }

    public async Task<byte[]> SendAsync(byte[] request, CancellationToken ct) {
        await _lock.WaitAsync(ct);
        try {
            _port.Write(request, 0, request.Length);
            await Task.Delay(50, ct);
            var len = _port.BytesToRead;
            var resp = new byte[len];
            _port.Read(resp, 0, len);
            return resp;
        } finally { _lock.Release(); }
    }

    public void Dispose() => _port?.Dispose();
}

Modbus RTU 只需在通道之上封装功能码与CRC校验,上层业务代码完全无需改动。

三、设备层:连接管理与心跳重连

设备断线是常态,必须自动重连并恢复状态:

代码语言:javascript
复制
public class DeviceService : IAsyncDisposable {
    private readonly IDeviceChannel _channel;
    private readonly ILogger _log;
    private CancellationTokenSource? _cts;
    private Task? _heartbeat;

    public bool Online { get; private set; }
    public event EventHandler<bool>? OnlineChanged;

    public DeviceService(IDeviceChannel channel, ILogger log) {
        _channel = channel; _log = log;
    }

    public async Task StartAsync() {
        _cts = new CancellationTokenSource();
        await _channel.ConnectAsync(_cts.Token);
        SetOnline(true);
        _heartbeat = Task.Run(() => HeartbeatLoop(_cts.Token));
    }

    private async Task HeartbeatLoop(CancellationToken ct) {
        while (!ct.IsCancellationRequested) {
            try {
                var resp = await _channel.SendAsync(new byte[] { 0x01, 0x03 }, ct);
                if (resp.Length == 0) throw new IOException("空响应");
            } catch (Exception ex) {
                _log.Warn("心跳失败,尝试重连: {0}", ex.Message);
                SetOnline(false);
                await Task.Delay(2000, ct);
                try { await _channel.ConnectAsync(ct); SetOnline(true); }
                catch { /* 继续重试 */ }
            }
            await Task.Delay(3000, ct);
        }
    }

    private void SetOnline(bool value) {
        if (Online == value) return;
        Online = value;
        OnlineChanged?.Invoke(this, value);
    }

    public async ValueTask DisposeAsync() {
        _cts?.Cancel();
        if (_heartbeat != null) await _heartbeat;
        _channel.Dispose();
    }
}

在线状态变化通过事件通知UI,避免UI轮询。

四、控制层:状态机与联锁

任何运动控制都必须经过安全联锁与状态机校验。以下为升降机构控制示例:

代码语言:javascript
复制
public enum LiftState { Idle, Moving, Positioned, Error }

public class LiftController {
    private readonly DeviceService _device;
    private readonly ISafetyInterlock _safety;
    private readonly ILogger _log;

    public LiftState State { get; private set; } = LiftState.Idle;
    public event EventHandler<LiftState>? StateChanged;

    public async Task MoveToAsync(int targetPos, CancellationToken ct) {
        if (State == LiftState.Moving)
            throw new InvalidOperationException("设备正在运动中");

        await _safety.EnsureSafeAsync(ct);   // 门锁、急停、真空度

        SetState(LiftState.Moving);
        try {
            var cmd = BuildMoveCommand(targetPos);
            var resp = await _device.SendAsync(cmd, ct);
            if (!CheckAck(resp)) throw new IOException("运动指令未确认");

            await WaitForPositionAsync(targetPos, ct);  // 轮询位置反馈
            SetState(LiftState.Positioned);
        } catch (OperationCanceledException) {
            await EmergencyStopAsync();
            SetState(LiftState.Idle);
            throw;
        } catch (Exception ex) {
            _log.Error(ex, "运动失败");
            await EmergencyStopAsync();
            SetState(LiftState.Error);
            throw;
        }
    }

    private void SetState(LiftState s) {
        State = s;
        StateChanged?.Invoke(this, s);
    }
}

关键点:任何运动前必须通过 ISafetyInterlock 检查,异常时立即急停,状态变更通过事件通知UI。

五、UI层:MVVM绑定与实时展示

ViewModel订阅设备事件,通过 INotifyPropertyChanged 驱动界面更新:

代码语言:javascript
复制
public class MainViewModel : INotifyPropertyChanged {
    private readonly DeviceService _device;
    private readonly LiftController _lift;

    public string ConnectionStatus =>
        _device.Online ? "已连接" : "断开";
    public string LiftStatus => _lift.State.ToString();

    public ICommand MoveCommand { get; }
    public ICommand StopCommand { get; }

    private CancellationTokenSource? _cts;

    public MainViewModel(DeviceService device, LiftController lift) {
        _device = device; _lift = lift;
        _device.OnlineChanged += (s, e) => OnPropertyChanged(nameof(ConnectionStatus));
        _lift.StateChanged += (s, e) => OnPropertyChanged(nameof(LiftStatus));

        MoveCommand = new RelayCommand<int>(async pos => {
            _cts = new CancellationTokenSource();
            try { await _lift.MoveToAsync(pos, _cts.Token); }
            catch (Exception ex) { /* 弹窗提示 */ }
        });
        StopCommand = new RelayCommand(_ => _cts?.Cancel());
    }

    public event PropertyChangedEventHandler? PropertyChanged;
    private void OnPropertyChanged(string name) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

XAML绑定简洁清晰:

代码语言:javascript
复制
<StackPanel>
    <TextBlock Text="{Binding ConnectionStatus}" FontSize="16"/>
    <TextBlock Text="{Binding LiftStatus}" FontSize="16"/>
    <Button Content="上升到100" Command="{Binding MoveCommand}"
            CommandParameter="100"/>
    <Button Content="停止" Command="{Binding StopCommand}"/>
</StackPanel>

六、工程要点

  1. 线程模型:设备IO全部异步,UI线程只更新绑定属性,杜绝阻塞。
  2. 幂等与审计:每次指令记录时间戳、参数、结果,支持追溯与回放。
  3. 安全联锁:联锁检查前置,异常立即急停,不做"尽力而为"。
  4. 可测试性:IDeviceChannel 可替换为模拟实现,离线验证状态机。
  5. 配置外置:端口、波特率、超时参数写入配置文件,部署不改代码。

总结:上位机系统的核心竞争力不在界面美观,而在通信稳定、状态可控、异常可回退。用统一接口屏蔽协议差异,用状态机约束设备操作,用MVVM解耦UI与逻辑,用联锁与审计守住安全底线。代码骨架可直接扩展至Modbus、OPC UA或SECS/GEM协议,满足工业现场要求。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、分层架构
  • 二、通信层:统一接口与协议适配
  • 三、设备层:连接管理与心跳重连
  • 四、控制层:状态机与联锁
  • 五、UI层:MVVM绑定与实时展示
  • 六、工程要点
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档