diff --git a/Data/Entities/DbDevice.cs b/Data/Entities/DbDevice.cs index d3745d4..cd28670 100644 --- a/Data/Entities/DbDevice.cs +++ b/Data/Entities/DbDevice.cs @@ -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 /// public int Prot { get; set; } + /// + /// PLC的CPU类型。 + /// + [SugarColumn(ColumnDataType = "varchar(20)", IsNullable = true, SqlParameterDbType = typeof(EnumToStringConvert))] + public CpuType CpuType { get; set; } + + /// + /// PLC的机架号。 + /// + [SugarColumn(IsNullable = true)] + public short Rack { get; set; } + + /// + /// PLC的槽号。 + /// + /// [SugarColumn(IsNullable = true)] + public short Slot { get; set; } + /// /// 设备的通信协议类型。 /// diff --git a/Helper/SiemensHelper.cs b/Helper/SiemensHelper.cs index 15a13ca..333e44b 100644 --- a/Helper/SiemensHelper.cs +++ b/Helper/SiemensHelper.cs @@ -51,4 +51,20 @@ public static class SiemensHelper return "object"; } } + + /// + /// 将S7读取到的值转换为显示值 + /// + /// S7读取到的原始值 + /// 变量的数据类型 + /// 转换规则 + /// 显示值 + 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(); + } } \ No newline at end of file diff --git a/Models/Device.cs b/Models/Device.cs index edda0e3..bf99298 100644 --- a/Models/Device.cs +++ b/Models/Device.cs @@ -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; + /// + /// PLC的CPU类型。 + /// + [ObservableProperty] + private CpuType cpuType; + + /// + /// PLC的机架号。 + /// + [ObservableProperty] + private short rack; + + /// + /// PLC的槽号。 + /// + [ObservableProperty] + private short slot; + /// /// 设备的通信协议类型。 /// diff --git a/PMSWPF.csproj b/PMSWPF.csproj index 8e7d34a..728ad5f 100644 --- a/PMSWPF.csproj +++ b/PMSWPF.csproj @@ -23,6 +23,7 @@ + diff --git a/Services/S7BackgroundService.cs b/Services/S7BackgroundService.cs new file mode 100644 index 0000000..8d2af46 --- /dev/null +++ b/Services/S7BackgroundService.cs @@ -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 _logger; + private readonly DataServices _dataServices; + private readonly Dictionary _s7PlcClients = new Dictionary(); + private readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(1); // 轮询间隔 + + public S7BackgroundService(ILogger logger, DataServices dataServices) + { + _logger = logger; + _dataServices = dataServices; + _dataServices.OnDeviceListChanged += HandleDeviceListChanged; + } + + private void HandleDeviceListChanged(List 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); + } + } +} diff --git a/ViewModels/Dialogs/DeviceDialogViewModel.cs b/ViewModels/Dialogs/DeviceDialogViewModel.cs index 278e143..e7c6958 100644 --- a/ViewModels/Dialogs/DeviceDialogViewModel.cs +++ b/ViewModels/Dialogs/DeviceDialogViewModel.cs @@ -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() diff --git a/Views/Dialogs/DeviceDialog.xaml b/Views/Dialogs/DeviceDialog.xaml index 9e01efc..3a80ae4 100644 --- a/Views/Dialogs/DeviceDialog.xaml +++ b/Views/Dialogs/DeviceDialog.xaml @@ -97,7 +97,38 @@ - + + + + + + + + + + + + + + + + + +