添加S7后台服务和添加PLC所需要的属性

This commit is contained in:
2025-07-05 01:31:44 +08:00
parent 69b75ef228
commit 404501cc17
8 changed files with 295 additions and 1 deletions

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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>

View File

@@ -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>

View 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);
}
}
}

View File

@@ -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()

View File

@@ -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="是否启用"

View File

@@ -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.");
}
}
}