添加S7后台服务和添加PLC所需要的属性
This commit is contained in:
@@ -2,6 +2,7 @@ using PMSWPF.Enums;
|
||||
using SqlSugar;
|
||||
using SqlSugar.DbConvert;
|
||||
using ProtocolType = PMSWPF.Enums.ProtocolType;
|
||||
using S7.Net; // Add this using directive
|
||||
|
||||
namespace PMSWPF.Data.Entities;
|
||||
|
||||
@@ -54,6 +55,24 @@ public class DbDevice
|
||||
/// </summary>
|
||||
public int Prot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PLC的CPU类型。
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "varchar(20)", IsNullable = true, SqlParameterDbType = typeof(EnumToStringConvert))]
|
||||
public CpuType CpuType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PLC的机架号。
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public short Rack { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PLC的槽号。
|
||||
/// </summary>
|
||||
/// [SugarColumn(IsNullable = true)]
|
||||
public short Slot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备的通信协议类型。
|
||||
/// </summary>
|
||||
|
||||
@@ -51,4 +51,20 @@ public static class SiemensHelper
|
||||
return "object";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将S7读取到的值转换为显示值
|
||||
/// </summary>
|
||||
/// <param name="value">S7读取到的原始值</param>
|
||||
/// <param name="dataType">变量的数据类型</param>
|
||||
/// <param name="conversion">转换规则</param>
|
||||
/// <returns>显示值</returns>
|
||||
public static string ConvertS7Value(object value, string dataType, string conversion)
|
||||
{
|
||||
if (value == null) return string.Empty;
|
||||
|
||||
// For now, a simple conversion to string. More complex logic can be added here.
|
||||
// Based on dataType and conversion, you might parse, format, or apply formulas.
|
||||
return value.ToString();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using PMSWPF.Enums;
|
||||
using SqlSugar;
|
||||
using SqlSugar.DbConvert;
|
||||
using S7.Net; // Add this using directive
|
||||
|
||||
namespace PMSWPF.Models;
|
||||
|
||||
@@ -64,6 +65,24 @@ public partial class Device : ObservableObject
|
||||
[ObservableProperty]
|
||||
private int prot;
|
||||
|
||||
/// <summary>
|
||||
/// PLC的CPU类型。
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private CpuType cpuType;
|
||||
|
||||
/// <summary>
|
||||
/// PLC的机架号。
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private short rack;
|
||||
|
||||
/// <summary>
|
||||
/// PLC的槽号。
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private short slot;
|
||||
|
||||
/// <summary>
|
||||
/// 设备的通信协议类型。
|
||||
/// </summary>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<PackageReference Include="NLog" Version="6.0.0" />
|
||||
<PackageReference Include="NLog.Database" Version="6.0.0" />
|
||||
<PackageReference Include="NPOI" Version="2.7.4" />
|
||||
<PackageReference Include="S7netplus" Version="0.20.0" />
|
||||
<PackageReference Include="SqlSugarCore.MySql" Version="5.1.4.178" />
|
||||
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.193" />
|
||||
</ItemGroup>
|
||||
|
||||
184
Services/S7BackgroundService.cs
Normal file
184
Services/S7BackgroundService.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using S7.Net;
|
||||
using PMSWPF.Models;
|
||||
using PMSWPF.Enums;
|
||||
using PMSWPF.Helper;
|
||||
|
||||
namespace PMSWPF.Services
|
||||
{
|
||||
public class S7BackgroundService : BackgroundService
|
||||
{
|
||||
private readonly ILogger<S7BackgroundService> _logger;
|
||||
private readonly DataServices _dataServices;
|
||||
private readonly Dictionary<int, Plc> _s7PlcClients = new Dictionary<int, Plc>();
|
||||
private readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(1); // 轮询间隔
|
||||
|
||||
public S7BackgroundService(ILogger<S7BackgroundService> logger, DataServices dataServices)
|
||||
{
|
||||
_logger = logger;
|
||||
_dataServices = dataServices;
|
||||
_dataServices.OnDeviceListChanged += HandleDeviceListChanged;
|
||||
}
|
||||
|
||||
private void HandleDeviceListChanged(List<Device> devices)
|
||||
{
|
||||
// 当设备列表变化时,更新PLC客户端
|
||||
// 这里需要更复杂的逻辑来处理连接的关闭和新连接的建立
|
||||
// 简单起见,这里只做日志记录
|
||||
_logger.LogInformation("Device list changed. S7 clients might need to be reinitialized.");
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation("S7 Background Service is starting.");
|
||||
|
||||
stoppingToken.Register(() => _logger.LogInformation("S7 Background Service is stopping."));
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogDebug("S7 Background Service is doing background work.");
|
||||
|
||||
await PollS7Devices(stoppingToken);
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(_pollingInterval, stoppingToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// When the stopping token is canceled, a TaskCanceledException is thrown.
|
||||
// We should catch it to exit gracefully.
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("S7 Background Service has stopped.");
|
||||
}
|
||||
|
||||
private async Task PollS7Devices(CancellationToken stoppingToken)
|
||||
{
|
||||
var s7Devices = _dataServices.Devices?.Where(d => d.ProtocolType == ProtocolType.S7 && d.IsActive).ToList();
|
||||
|
||||
if (s7Devices == null || !s7Devices.Any())
|
||||
{
|
||||
_logger.LogDebug("No active S7 devices found to poll.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var device in s7Devices)
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested) return;
|
||||
|
||||
if (!_s7PlcClients.ContainsKey(device.Id))
|
||||
{
|
||||
// Initialize Plc client for the device
|
||||
try
|
||||
{
|
||||
var plc = new Plc(device.CpuType, device.Ip, (short)device.Prot, device.Rack, device.Slot);
|
||||
await plc.OpenAsync();
|
||||
_s7PlcClients[device.Id] = plc;
|
||||
_logger.LogInformation($"Connected to S7 PLC: {device.Name} ({device.Ip})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Failed to connect to S7 PLC: {device.Name} ({device.Ip})");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var plcClient = _s7PlcClients[device.Id];
|
||||
if (!plcClient.IsConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
await plcClient.OpenAsync();
|
||||
_logger.LogInformation($"Reconnected to S7 PLC: {device.Name} ({device.Ip})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Failed to reconnect to S7 PLC: {device.Name} ({device.Ip})");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter variables for the current device and S7 protocol
|
||||
var s7Variables = device.VariableTables
|
||||
?.SelectMany(vt => vt.DataVariables)
|
||||
.Where(vd => vd.ProtocolType == ProtocolType.S7 && vd.IsActive)
|
||||
.ToList();
|
||||
|
||||
if (s7Variables == null || !s7Variables.Any())
|
||||
{
|
||||
_logger.LogDebug($"No active S7 variables found for device: {device.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Batch read variables
|
||||
var addressesToRead = s7Variables.Select(vd => vd.S7Address).ToList();
|
||||
if (!addressesToRead.Any()) continue;
|
||||
|
||||
try
|
||||
{
|
||||
// S7.Net.Plus library supports ReadMultiple, but it's more complex for different data types.
|
||||
// For simplicity, we'll read them one by one for now, or use a more advanced batch read if all are same type.
|
||||
// A more robust solution would involve grouping by data block and type.
|
||||
|
||||
// Example of reading multiple items (assuming all are of type DWord for simplicity)
|
||||
// This part needs to be refined based on actual variable types and addresses
|
||||
// var dataItems = addressesToRead.Select(addr => new DataItem { DataType = DataType.DataBlock, VarType = VarType.DWord, StringLength = 1, DB = 1, StartByteAdr = 0 }).ToList();
|
||||
// plcClient.ReadMultiple(dataItems);
|
||||
|
||||
foreach (var variable in s7Variables)
|
||||
{
|
||||
if (stoppingToken.IsCancellationRequested) return;
|
||||
try
|
||||
{
|
||||
// This is a simplified read. In a real scenario, you'd parse S7Address
|
||||
// to get DataType, DB, StartByteAdr, BitAdr, etc.
|
||||
// For now, assuming S7Address is directly readable by Read method (e.g., "DB1.DBW0")
|
||||
var value = await plcClient.ReadAsync(variable.S7Address);
|
||||
if (value != null)
|
||||
{
|
||||
// Update the variable's DataValue and DisplayValue
|
||||
variable.DataValue = value.ToString();
|
||||
variable.DisplayValue = SiemensHelper.ConvertS7Value(value, variable.DataType, variable.Converstion);
|
||||
_logger.LogDebug($"Read {variable.Name}: {variable.DataValue}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Failed to read variable {variable.Name} from {device.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error during batch read for device {device.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task StopAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation("S7 Background Service is stopping.");
|
||||
|
||||
// Close all active PLC connections
|
||||
foreach (var plcClient in _s7PlcClients.Values)
|
||||
{
|
||||
if (plcClient.IsConnected)
|
||||
{
|
||||
plcClient.Close();
|
||||
_logger.LogInformation($"Closed S7 PLC connection: {plcClient.IP}");
|
||||
}
|
||||
}
|
||||
_s7PlcClients.Clear();
|
||||
|
||||
await base.StopAsync(stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using PMSWPF.Models;
|
||||
using S7.Net; // Add this using directive
|
||||
|
||||
namespace PMSWPF.ViewModels.Dialogs;
|
||||
|
||||
@@ -8,6 +9,13 @@ public partial class DeviceDialogViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private Device _device;
|
||||
partial void OnDeviceChanged(Device value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Device ProtocolType changed to: {value.ProtocolType}");
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty] private string title ;
|
||||
[ObservableProperty] private string primaryButContent ;
|
||||
@@ -17,6 +25,9 @@ public partial class DeviceDialogViewModel : ObservableObject
|
||||
_device = device;
|
||||
}
|
||||
|
||||
// Add a property to expose CpuType enum values for ComboBox
|
||||
public Array CpuTypes => Enum.GetValues(typeof(CpuType));
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public void AddDevice()
|
||||
|
||||
@@ -97,7 +97,38 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<!-- 通讯协议-->
|
||||
|
||||
<!-- S7 Specific Properties -->
|
||||
<StackPanel x:Name="S7PropertiesPanel" Visibility="Visible" Background="LightGray">
|
||||
<StackPanel.Style>
|
||||
<Style TargetType="StackPanel">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Device.ProtocolType}"
|
||||
Value="{x:Static en:ProtocolType.S7}">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</StackPanel.Style>
|
||||
<!-- CpuType -->
|
||||
<TextBlock Text="CPU 类型"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource TextBlockSubTitle}" />
|
||||
<ComboBox SelectedItem="{Binding Device.CpuType}"
|
||||
ItemsSource="{Binding CpuTypes}" />
|
||||
|
||||
<!-- Rack -->
|
||||
<TextBlock Text="机架号"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource TextBlockSubTitle}" />
|
||||
<TextBox Text="{Binding Device.Rack, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<!-- Slot -->
|
||||
<TextBlock Text="槽号"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource TextBlockSubTitle}" />
|
||||
<TextBox Text="{Binding Device.Slot, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox FontSize="16"
|
||||
Content="是否启用"
|
||||
|
||||
@@ -2,15 +2,28 @@
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using PMSWPF.Models;
|
||||
using PMSWPF.ViewModels.Dialogs;
|
||||
using NLog; // Add NLog using directive
|
||||
|
||||
namespace PMSWPF.Views.Dialogs;
|
||||
|
||||
public partial class DeviceDialog
|
||||
{
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); // Add Logger
|
||||
|
||||
public DeviceDialog(DeviceDialogViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
|
||||
// Log the ProtocolType value
|
||||
if (viewModel.Device != null)
|
||||
{
|
||||
Logger.Info($"DeviceDialog opened. Device ProtocolType: {viewModel.Device.ProtocolType}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info("DeviceDialog opened. Device is null.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user