上位机系统(Host Computer System)是工业自动化的中枢——下接PLC、运动控制卡、传感器,上承MES、ERP与操作人员。它不只是"画个界面连设备",而是要在实时性、可靠性、可维护性之间取得平衡。本文从通信层、数据层、控制层、UI层四个维度,给出可落地的架构与代码。
专业的上位机系统分为四层:
核心原则:UI不直接操作设备,设备不感知UI。所有交互通过服务接口与事件总线解耦。
无论底层是串口还是TCP,上层应看到统一的接口:
public interface IDeviceChannel : IDisposable {
bool IsConnected { get; }
Task ConnectAsync(CancellationToken ct);
Task<byte[]> SendAsync(byte[] request, CancellationToken ct);
event EventHandler<byte[]> DataReceived;
}串口实现带互斥锁与超时,避免并发写冲突:
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校验,上层业务代码完全无需改动。
设备断线是常态,必须自动重连并恢复状态:
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轮询。
任何运动控制都必须经过安全联锁与状态机校验。以下为升降机构控制示例:
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。
ViewModel订阅设备事件,通过 INotifyPropertyChanged 驱动界面更新:
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绑定简洁清晰:
<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>IDeviceChannel 可替换为模拟实现,离线验证状态机。总结:上位机系统的核心竞争力不在界面美观,而在通信稳定、状态可控、异常可回退。用统一接口屏蔽协议差异,用状态机约束设备操作,用MVVM解耦UI与逻辑,用联锁与审计守住安全底线。代码骨架可直接扩展至Modbus、OPC UA或SECS/GEM协议,满足工业现场要求。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。