整合设备

This commit is contained in:
hsc 2025-11-20 13:42:13 +08:00
parent a25a4bc6ed
commit d2bf7ab4c0
15 changed files with 237 additions and 256 deletions

View File

@ -59,7 +59,12 @@ namespace BOB
containerRegistry.RegisterSingleton<StepRunning>();
containerRegistry.RegisterSingleton<Devices>();
}
protected override void OnExit(ExitEventArgs e)
{
var devices = Container.Resolve<Devices>();
devices.Dispose();
base.OnExit(e);
}
}
}

View File

@ -13,6 +13,9 @@ namespace BOB.Singleton
{
public class Devices
{
private CancellationTokenSource _appCancellationTokenSource = new();
public CancellationToken AppCancellationToken => _appCancellationTokenSource.Token;
public Dictionary<string, object> DeviceDic { get; private set; } = new Dictionary<string, object>();
private readonly ProxyGenerator _proxyGen = new ProxyGenerator();
@ -230,5 +233,11 @@ namespace BOB.Singleton
}
}
public void Dispose()
{
_appCancellationTokenSource.Cancel();
_appCancellationTokenSource.Dispose();
}
}
}

View File

@ -1,5 +1,6 @@
using BOB.Models;
using Common.PubEvent;
using Model;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@ -21,156 +22,30 @@ namespace BOB.ViewModels.Dialogs
get => _title;
set => SetProperty(ref _title, value);
}
private ObservableCollection<string> _types = ["串口", "Tcp", "ModbusRtu", "ModbusTcp", "CAN", "Udp"];
public ObservableCollection<string> Types
{
get => _types;
set => SetProperty(ref _types, value);
}
private string _Mode;
public string Mode
{
get => _Mode;
set => SetProperty(ref _Mode, value);
}
private bool _IsAdd;
public bool IsAdd
{
get => _IsAdd;
set => SetProperty(ref _IsAdd, value);
}
private ProgramModel _program;
public ProgramModel Program
{
get => _program;
set => SetProperty(ref _program, value);
}
private DeviceModel _Device;
public DeviceModel Device
private DeviceInfoModel _Device;
public DeviceInfoModel Device
{
get => _Device;
set => SetProperty(ref _Device, value);
}
private ObservableCollection<DeviceConnectSettingModel> _DeviceConnectSettings=new();
public ObservableCollection<DeviceConnectSettingModel> DeviceConnectSettings
{
get => _DeviceConnectSettings;
set => SetProperty(ref _DeviceConnectSettings, value);
}
#endregion
public DialogCloseListener RequestClose { get; set; }
private GlobalVariables _globalVariables;
private IEventAggregator _eventAggregator;
public ICommand CancelCommand { get; set; }
public ICommand SaveCommand { get; set; }
public ICommand SelectionChangedCommand { get; set; }
public DeviceSettingViewModel(GlobalVariables globalVariables, IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_globalVariables = globalVariables;
CancelCommand = new DelegateCommand(Cancel);
SaveCommand = new DelegateCommand(Save);
SelectionChangedCommand = new DelegateCommand(SelectionChanged);
}
private void SelectionChanged()
{
if (Device == null) return;
DeviceConnectSettings.Clear();
switch (Device!.Type)
{
case "Tcp":
case "ModbusTcp":
DeviceConnectSettings.Add(new()
{
Name = "IP地址",
Value = "127.0.0.1"
});
DeviceConnectSettings.Add(new()
{
Name = "端口号",
Value = "502"
});
break;
case "串口":
case "ModbusRtu":
DeviceConnectSettings.Add(new()
{
Name = "COM口",
Value = "COM1"
});
DeviceConnectSettings.Add(new()
{
Name = "波特率",
Value = "9600"
});
DeviceConnectSettings.Add(new()
{
Name = "数据位",
Value = "8"
});
DeviceConnectSettings.Add(new()
{
Name = "停止位",
Value = "1"
});
DeviceConnectSettings.Add(new()
{
Name = "奇偶",
Value = "无"
});
break;
case "Udp":
DeviceConnectSettings.Add(new()
{
Name = "远程IP地址",
Value = "127.0.0.1"
});
DeviceConnectSettings.Add(new()
{
Name = "远程端口号",
Value = "8080"
});
DeviceConnectSettings.Add(new()
{
Name = "本地端口号",
Value = "0"
});
break;
}
DeviceConnectSettings.Add(new()
{
Name = "读超时",
Value = "3000"
});
DeviceConnectSettings.Add(new()
{
Name = "写超时",
Value = "3000"
});
}
private void Save()
{
if (Mode == "ADD")
{
Program.Devices.Add(Device);
_globalVariables.SelectedDevice = Device;
}
else
{
var index = Program.Devices
.Select((x, i) => new { x, i })
.FirstOrDefault(p => p.x.ID == _globalVariables.SelectedDevice.ID)?.i;
if (index.HasValue)
{
Program.Devices[index.Value] = Device;
}
}
RequestClose.Invoke(ButtonResult.OK);
}
@ -192,20 +67,7 @@ namespace BOB.ViewModels.Dialogs
public void OnDialogOpened(IDialogParameters parameters)
{
_eventAggregator.GetEvent<OverlayEvent>().Publish(true);
Program = _globalVariables.Program;
Mode = parameters.GetValue<string>("Mode");
if(Mode == "ADD")
{
Device = new();
IsAdd = true;
Device.Type = "Tcp";
SelectionChanged();
}
else
{
Device = new DeviceModel(_globalVariables.SelectedDevice);
IsAdd = false;
}
}
#endregion
}

View File

@ -1,6 +1,11 @@
using BOB.Models;
using BOB.Singleton;
using Common.PubEvent;
using DeviceCommand.Base;
using DeviceCommand.Device;
using Logger;
using MaterialDesignThemes.Wpf;
using Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -16,6 +21,21 @@ namespace BOB.ViewModels
public class ParametersManagerViewModel:BindableBase
{
#region
private ObservableCollection<DeviceConfigModel> _DeviceList;
public ObservableCollection<DeviceConfigModel> DeviceList
{
get { return _DeviceList; }
set { SetProperty(ref _DeviceList,value); }
}
private ObservableCollection<DeviceInfoModel> _DeviceInfoModel;
public ObservableCollection<DeviceInfoModel> DeviceInfoModel
{
get { return _DeviceInfoModel; }
set { SetProperty(ref _DeviceInfoModel, value); }
}
public ProgramModel Program
{
get => _globalVariables.Program;
@ -40,85 +60,149 @@ namespace BOB.ViewModels
}
}
}
private DeviceModel _SelectedDevice;
public DeviceModel SelectedDevice
private DeviceInfoModel _SelectedDevice;
public DeviceInfoModel SelectedDevice
{
get => _SelectedDevice;
set
{
if (SetProperty(ref _SelectedDevice, value))
{
_globalVariables.SelectedDevice = value;
}
}
get { return _SelectedDevice; }
set { SetProperty(ref _SelectedDevice, value); }
}
#endregion
private GlobalVariables _globalVariables { get; set; }
private IDialogService _dialogService { get; set; }
private Devices _devices { get; set; }
private IEventAggregator _eventAggregator { get; set; }
public ICommand ParameterAddCommand { get; set; }
public ICommand ParameterEditCommand { get; set; }
public ICommand ParameterDeleteCommand { get; set; }
public ICommand DeviceAddCommand { get; set; }
public ICommand DeviceEditCommand { get; set; }
public ICommand DeviceDeleteCommand { get; set; }
public ICommand ReconnnectCommand { get; set; }
public ParametersManagerViewModel(GlobalVariables GlobalVariables,IDialogService dialogService,IEventAggregator eventAggregator)
public ParametersManagerViewModel(GlobalVariables GlobalVariables,IDialogService dialogService,IEventAggregator eventAggregator,IContainerProvider containerProvider)
{
_globalVariables = GlobalVariables;
_eventAggregator= eventAggregator;
_devices=containerProvider.Resolve<Devices>();
_dialogService = dialogService;
Program = _globalVariables.Program;
ParameterAddCommand = new DelegateCommand(ParameterAdd);
ParameterEditCommand = new DelegateCommand(ParameterEdit);
ParameterDeleteCommand = new DelegateCommand(ParameterDelete);
DeviceAddCommand = new DelegateCommand(DeviceAdd);
DeviceEditCommand = new DelegateCommand(DeviceEdit);
DeviceDeleteCommand = new DelegateCommand(DeviceDelete);
ReconnnectCommand = new DelegateCommand(Reconnnect);
DeviceList = new ObservableCollection<DeviceConfigModel>(SystemConfig.Instance.DeviceList);
InitData();
}
private void Reconnnect()
{
if(SelectedDevice!=null)
{
if (_devices.DeviceDic.TryGetValue(SelectedDevice.Remark, out var device) && device != null)
{
Task.Run(async () =>
{
switch (device)
{
case ITcp tcpDevice:
await tcpDevice.ConnectAsync();
break;
case ISerialPort serialDevice:
await serialDevice.ConnectAsync();
break;
case IModbusDevice modbusDevice:
await modbusDevice.ConnectAsync();
break;
}
});
}
}
}
private void InitData()
{
DeviceInfoModel = new ObservableCollection<DeviceInfoModel>();
for (int i = 0; i < DeviceList.Count; i++)
{
DeviceInfoModel.Add(new DeviceInfoModel
{
DeviceName = DeviceList[i].DeviceName,
Remark = DeviceList[i].Remark,
IsConnected = false,
IsEnabled= DeviceList[i].IsEnabled,
DeviceType= DeviceList[i].DeviceType,
CommunicationConfig= DeviceList[i].CommunicationConfig
});
}
var progress = new Progress<(int index, bool isConnected)>();
progress.ProgressChanged += (s, e) =>
{
DeviceInfoModel[e.index].IsConnected = e.isConnected;
};
var token = _devices.AppCancellationToken;
Task.Run(() => PollingConnection(progress, token));
}
private async Task PollingConnection(IProgress<(int index, bool isConnected)> progress, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
for (int i = 0; i < DeviceList.Count; i++)
{
bool isConnected = false;
if (_devices.DeviceDic.TryGetValue(DeviceList[i].Remark, out var device) && device != null)
{
switch (device)
{
case ITcp tcpDevice:
isConnected = tcpDevice.TcpClient?.Connected ?? false;
break;
case ISerialPort serialDevice:
isConnected = serialDevice.SerialPort?.IsOpen ?? false;
break;
case IModbusDevice modbusDevice:
isConnected = (modbusDevice.TcpClient?.Connected ?? false) || (modbusDevice.SerialPort?.IsOpen ?? false);
break;
}
}
progress.Report((i, isConnected));
}
await Task.Delay(200, token); // 支持取消
}
}
#region
private void DeviceDelete()
{
Program.Devices.Remove(SelectedDevice);
}
private void DeviceEdit()
{
if(SelectedDevice==null)
{
return;
}
var param = new DialogParameters
{
{ "Mode", SelectedDevice==null?"ADD":"Edit" }
{ "Devices", SelectedDevice }
};
_dialogService.ShowDialog("DeviceSetting", param, (r) =>
{
if (r.Result == ButtonResult.OK)
{
_eventAggregator.GetEvent<ParamsChangedEvent>().Publish();
}
else
{
}
});
}
private void DeviceAdd()
{
var param = new DialogParameters
{
{ "Mode", "ADD" }
};
_dialogService.ShowDialog("DeviceSetting", param, (r) =>
{
if (r.Result == ButtonResult.OK)
{
_eventAggregator.GetEvent<ParamsChangedEvent>().Publish();
}
else
{

View File

@ -56,31 +56,12 @@
<StackPanel Height="30"
Orientation="Horizontal"
Margin="7">
<Label Content="通讯协议类型"
VerticalAlignment="Bottom"
Width="85" />
<ComboBox ItemsSource="{Binding Types}"
SelectedItem="{Binding Device.Type,UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Bottom"
Width="120"
materialDesign:HintAssist.Hint="通讯协议类型"
IsEnabled="{Binding IsAdd}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding SelectionChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<StackPanel Height="30"
Orientation="Horizontal"
Margin="7">
<Label Content="设备描述"
<Label Content="是否启用"
Width="85"
VerticalAlignment="Bottom" />
<TextBox Text="{Binding Device.Description}"
<CheckBox IsChecked="{Binding IsEnable}"
VerticalAlignment="Bottom"
materialDesign:HintAssist.Hint="设备描述"
materialDesign:HintAssist.Hint="是否启用"
Width="120" />
</StackPanel>
<Label Content="连接参数"

View File

@ -70,7 +70,7 @@
<TabItem Header="设备">
<DataGrid Padding="10"
Background="Transparent"
ItemsSource="{Binding Program.Devices}"
ItemsSource="{Binding DeviceInfoModel}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
@ -90,12 +90,12 @@
<Setter Property="Fill"
Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding Connected}"
<DataTrigger Binding="{Binding IsConnected}"
Value="True">
<Setter Property="Fill"
Value="LimeGreen" />
</DataTrigger>
<DataTrigger Binding="{Binding Connected}"
<DataTrigger Binding="{Binding IsConnected}"
Value="False">
<Setter Property="Fill"
Value="Red" />
@ -109,25 +109,24 @@
</DataGridTemplateColumn>
<DataGridTextColumn Header="设备名称"
Binding="{Binding Name}" />
<DataGridTextColumn Header="备注"
Binding="{Binding Description}" />
</DataGrid.Columns>
Binding="{Binding DeviceName}" />
<DataGridTemplateColumn Header="是否启用">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsEnabled}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="新增"
Command="{Binding DeviceAddCommand}" />
<MenuItem Header="编辑"
Command="{Binding DeviceEditCommand}"
/>
<MenuItem Header="删除"
Foreground="Red"
Command="{Binding DeviceDeleteCommand}"
/>
<!--<MenuItem Header="编辑"
Command="{Binding DeviceEditCommand}" />-->
<MenuItem Header="重新连接"
Command="{Binding ReconnnectCommand}" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</TabItem>
</TabControl>

View File

@ -1,6 +1,9 @@
using System;
using NModbus;
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
@ -8,6 +11,9 @@ namespace DeviceCommand.Base
{
public interface IModbusDevice
{
public TcpClient TcpClient { get; set; }
public SerialPort SerialPort { get; set; }
public IModbusMaster Modbus { get; set; }
Task<bool> ConnectAsync(CancellationToken ct = default);
void Close();

View File

@ -7,6 +7,7 @@ namespace DeviceCommand.Base
{
public interface ISerialPort
{
public SerialPort SerialPort { get; set; }
Task<bool> ConnectAsync(CancellationToken ct = default);
void Close();

View File

@ -1,4 +1,5 @@
using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
@ -6,6 +7,7 @@ namespace DeviceCommand.Base
{
public interface ITcp
{
public TcpClient TcpClient { get; set; }
Task<bool> ConnectAsync(CancellationToken ct = default);
void Close();

View File

@ -2,6 +2,7 @@
using NModbus.Serial;
using System;
using System.IO.Ports;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
@ -17,8 +18,9 @@ namespace DeviceCommand.Base
public int ReadTimeout { get; private set; } = 3000;
public int WriteTimeout { get; private set; } = 3000;
public SerialPort SerialPort { get; private set; } = new SerialPort();
public IModbusMaster Modbus { get; private set; }
public SerialPort SerialPort { get; set; } = new SerialPort();
public TcpClient TcpClient { get; set; }
public IModbusMaster Modbus { get; set; }
private readonly SemaphoreSlim _commLock = new(1, 1);

View File

@ -1,5 +1,6 @@
using NModbus;
using System;
using System.IO.Ports;
using System.Net;
using System.Net.Sockets;
using System.Threading;
@ -13,8 +14,9 @@ namespace DeviceCommand.Base
public int Port { get; private set; } = 502;
public int SendTimeout { get; private set; } = 3000;
public int ReceiveTimeout { get; private set; } = 3000;
public TcpClient TcpClient { get; private set; } = new TcpClient();
public IModbusMaster Modbus { get; private set; }
public SerialPort SerialPort { get; set; }
public TcpClient TcpClient { get; set; } = new TcpClient();
public IModbusMaster Modbus { get; set; }
private readonly SemaphoreSlim _commLock = new(1, 1);

View File

@ -15,8 +15,8 @@ namespace DeviceCommand.Base
public Parity Parity { get; set; } = Parity.None;
public int ReadTimeout { get; set; } = 3000;
public int WriteTimeout { get; set; } = 3000;
public SerialPort _SerialPort { get; private set; } = new SerialPort();
private readonly SemaphoreSlim _commLock = new(1, 1);
public SerialPort SerialPort { get; set; } = new SerialPort();
private readonly SemaphoreSlim commLock = new(1, 1);
public void ConfigureDevice(string portName, int baudRate, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000)
{
@ -30,43 +30,43 @@ namespace DeviceCommand.Base
}
public async Task<bool> ConnectAsync(CancellationToken ct = default)
{
await _commLock.WaitAsync(ct);
await commLock.WaitAsync(ct);
try
{
if (_SerialPort.IsOpen) _SerialPort.Close();
if (SerialPort.IsOpen) SerialPort.Close();
_SerialPort.PortName = PortName;
_SerialPort.BaudRate = BaudRate;
_SerialPort.DataBits = DataBits;
_SerialPort.StopBits = StopBits;
_SerialPort.Parity = Parity;
_SerialPort.ReadTimeout = ReadTimeout;
_SerialPort.WriteTimeout = WriteTimeout;
SerialPort.PortName = PortName;
SerialPort.BaudRate = BaudRate;
SerialPort.DataBits = DataBits;
SerialPort.StopBits = StopBits;
SerialPort.Parity = Parity;
SerialPort.ReadTimeout = ReadTimeout;
SerialPort.WriteTimeout = WriteTimeout;
_SerialPort.Open();
SerialPort.Open();
return true;
}
finally
{
_commLock.Release();
commLock.Release();
}
}
public void Close()
{
if (_SerialPort.IsOpen) _SerialPort.Close();
if (SerialPort.IsOpen) SerialPort.Close();
}
public async Task SendAsync(string data, CancellationToken ct = default)
{
await _commLock.WaitAsync(ct);
await commLock.WaitAsync(ct);
try
{
if (!_SerialPort.IsOpen) return;
if (!SerialPort.IsOpen) return;
byte[] bytes = Encoding.UTF8.GetBytes(data);
var timeoutTask = Task.Delay(WriteTimeout > 0 ? WriteTimeout : Timeout.Infinite, ct);
var sendTask = Task.Run(() => _SerialPort.Write(bytes, 0, bytes.Length), ct);
var sendTask = Task.Run(() => SerialPort.Write(bytes, 0, bytes.Length), ct);
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
if (completedTask == timeoutTask) throw new TimeoutException($"写入操作在 {WriteTimeout} ms内未完成");
@ -74,16 +74,16 @@ namespace DeviceCommand.Base
}
finally
{
_commLock.Release();
commLock.Release();
}
}
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
{
await _commLock.WaitAsync(ct);
await commLock.WaitAsync(ct);
try
{
if (!_SerialPort.IsOpen) return null;
if (!SerialPort.IsOpen) return null;
delimiter ??= "\n";
var sb = new StringBuilder();
@ -91,9 +91,9 @@ namespace DeviceCommand.Base
while (!ct.IsCancellationRequested)
{
if (_SerialPort.BytesToRead > 0)
if (SerialPort.BytesToRead > 0)
{
int bytesRead = _SerialPort.Read(buffer, 0, buffer.Length);
int bytesRead = SerialPort.Read(buffer, 0, buffer.Length);
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
@ -112,7 +112,7 @@ namespace DeviceCommand.Base
}
finally
{
_commLock.Release();
commLock.Release();
}
}
}

View File

@ -14,7 +14,7 @@ namespace DeviceCommand.Base
public int Port { get; private set; } = 502;
public int SendTimeout { get; private set; } = 3000;
public int ReceiveTimeout { get; private set; } = 3000;
public TcpClient TcpClient { get; private set; } = new TcpClient();
public TcpClient TcpClient { get; set; } = new TcpClient();
private readonly SemaphoreSlim _commLock = new(1, 1);
public void ConfigureDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)

View File

@ -8,6 +8,14 @@ public class LoggingInterceptor : IInterceptor
{
string className = invocation.TargetType.Name;
string methodName = invocation.Method.Name;
// 排除 TcpClient, SerialPort, Modbus 的 getter
if (methodName.Contains("get_TcpClient") ||
methodName.Contains("get_SerialPort") ||
methodName.Contains("get_Modbus"))
{
invocation.Proceed(); // 不拦截
return;
}
LoggerHelper.InfoWithNotify($"调用 {className}.{methodName}() 开始");

20
Model/DeviceInfoModel.cs Normal file
View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
public class DeviceInfoModel
{
public string DeviceName { get; set; }
public string DeviceType { get; set; }
public string Remark { get; set; }
public bool IsEnabled { get; set; }
public bool IsConnected { get; set; }
public ICommunicationConfig CommunicationConfig { get; set; }
}
}