添加S7后台服务和添加PLC所需要的属性
This commit is contained in:
@@ -2,6 +2,7 @@ using PMSWPF.Enums;
|
|||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using SqlSugar.DbConvert;
|
using SqlSugar.DbConvert;
|
||||||
using ProtocolType = PMSWPF.Enums.ProtocolType;
|
using ProtocolType = PMSWPF.Enums.ProtocolType;
|
||||||
|
using S7.Net; // Add this using directive
|
||||||
|
|
||||||
namespace PMSWPF.Data.Entities;
|
namespace PMSWPF.Data.Entities;
|
||||||
|
|
||||||
@@ -54,6 +55,24 @@ public class DbDevice
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int Prot { get; set; }
|
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>
|
||||||
/// 设备的通信协议类型。
|
/// 设备的通信协议类型。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -51,4 +51,20 @@ public static class SiemensHelper
|
|||||||
return "object";
|
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 PMSWPF.Enums;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using SqlSugar.DbConvert;
|
using SqlSugar.DbConvert;
|
||||||
|
using S7.Net; // Add this using directive
|
||||||
|
|
||||||
namespace PMSWPF.Models;
|
namespace PMSWPF.Models;
|
||||||
|
|
||||||
@@ -64,6 +65,24 @@ public partial class Device : ObservableObject
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private int prot;
|
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>
|
||||||
/// 设备的通信协议类型。
|
/// 设备的通信协议类型。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
<PackageReference Include="NLog" Version="6.0.0" />
|
<PackageReference Include="NLog" Version="6.0.0" />
|
||||||
<PackageReference Include="NLog.Database" Version="6.0.0" />
|
<PackageReference Include="NLog.Database" Version="6.0.0" />
|
||||||
<PackageReference Include="NPOI" Version="2.7.4" />
|
<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="SqlSugarCore.MySql" Version="5.1.4.178" />
|
||||||
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.193" />
|
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.193" />
|
||||||
</ItemGroup>
|
</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.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using PMSWPF.Models;
|
using PMSWPF.Models;
|
||||||
|
using S7.Net; // Add this using directive
|
||||||
|
|
||||||
namespace PMSWPF.ViewModels.Dialogs;
|
namespace PMSWPF.ViewModels.Dialogs;
|
||||||
|
|
||||||
@@ -8,6 +9,13 @@ public partial class DeviceDialogViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Device _device;
|
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 title ;
|
||||||
[ObservableProperty] private string primaryButContent ;
|
[ObservableProperty] private string primaryButContent ;
|
||||||
@@ -17,6 +25,9 @@ public partial class DeviceDialogViewModel : ObservableObject
|
|||||||
_device = device;
|
_device = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a property to expose CpuType enum values for ComboBox
|
||||||
|
public Array CpuTypes => Enum.GetValues(typeof(CpuType));
|
||||||
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public void AddDevice()
|
public void AddDevice()
|
||||||
|
|||||||
@@ -97,7 +97,38 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ComboBox.ItemTemplate>
|
</ComboBox.ItemTemplate>
|
||||||
</ComboBox>
|
</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"
|
<CheckBox FontSize="16"
|
||||||
Content="是否启用"
|
Content="是否启用"
|
||||||
|
|||||||
@@ -2,15 +2,28 @@
|
|||||||
using iNKORE.UI.WPF.Modern.Controls;
|
using iNKORE.UI.WPF.Modern.Controls;
|
||||||
using PMSWPF.Models;
|
using PMSWPF.Models;
|
||||||
using PMSWPF.ViewModels.Dialogs;
|
using PMSWPF.ViewModels.Dialogs;
|
||||||
|
using NLog; // Add NLog using directive
|
||||||
|
|
||||||
namespace PMSWPF.Views.Dialogs;
|
namespace PMSWPF.Views.Dialogs;
|
||||||
|
|
||||||
public partial class DeviceDialog
|
public partial class DeviceDialog
|
||||||
{
|
{
|
||||||
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); // Add Logger
|
||||||
|
|
||||||
public DeviceDialog(DeviceDialogViewModel viewModel)
|
public DeviceDialog(DeviceDialogViewModel viewModel)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContext = viewModel;
|
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