diff --git a/DeviceCommand/Base/ModbusTcp.cs b/DeviceCommand/Base/ModbusTcp.cs index 8db68f3..3f28c10 100644 --- a/DeviceCommand/Base/ModbusTcp.cs +++ b/DeviceCommand/Base/ModbusTcp.cs @@ -1,4 +1,4 @@ -using NModbus; +using NModbus; using System; using System.Net; using System.Net.Sockets; @@ -38,20 +38,48 @@ namespace DeviceCommand.Base await _commLock.WaitAsync(ct); try { + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 开始连接 - IP: {IPAddress}, 端口: {Port}"); + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 超时设置 - 发送: {SendTimeout}ms, 接收: {ReceiveTimeout}ms"); + if (_tcpClient.Connected) { var remoteEndPoint = (IPEndPoint)_tcpClient.Client.RemoteEndPoint!; - if (remoteEndPoint.Address.MapToIPv4().ToString() == IPAddress && remoteEndPoint.Port == Port) + string currentIp = remoteEndPoint.Address.MapToIPv4().ToString(); + int currentPort = remoteEndPoint.Port; + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 已有连接: {currentIp}:{currentPort}"); + + if (currentIp == IPAddress && currentPort == Port) + { + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 参数匹配,复用现有连接"); return true; + } + else + { + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 参数不匹配,需要重新连接"); + } } + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 关闭并释放旧连接"); _tcpClient.Close(); _tcpClient.Dispose(); _tcpClient = new TcpClient(); + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 调用 ConnectAsync({IPAddress}, {Port})"); await _tcpClient.ConnectAsync(IPAddress, Port, ct); + + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 创建ModbusMaster"); Modbus = new ModbusFactory().CreateMaster(_tcpClient); - return true; + + bool isConnected = _tcpClient.Connected; + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 连接结果: {(isConnected ? "成功" : "失败")}"); + return isConnected; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 连接异常: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 异常类型: {ex.GetType().Name}"); + System.Diagnostics.Debug.WriteLine($"[ModbusTcp] 异常堆栈: {ex.StackTrace}"); + return false; } finally { diff --git a/DeviceCommand/Base/S7Device.cs b/DeviceCommand/Base/S7Device.cs index 6a9d672..c59ff58 100644 --- a/DeviceCommand/Base/S7Device.cs +++ b/DeviceCommand/Base/S7Device.cs @@ -1,4 +1,4 @@ -using S7.Net; +using S7.Net; using System; using System.IO; using System.Net; @@ -9,7 +9,6 @@ namespace DeviceCommand.Base { public class S7Device : IS7Device { - // 保持和你一致的连接参数命名属性 public string IPAddress { get; private set; } = "127.0.0.1"; public CpuType CpuType { get; private set; } = CpuType.S71200; public short Rack { get; private set; } = 0; @@ -20,21 +19,15 @@ namespace DeviceCommand.Base private Plc _plc; public Plc PlcContext => _plc; - // S7.Net 的 Plc.IsConnected 属性内部会通过 Socket 状态进行判断 public bool IsConnected => _plc?.IsConnected ?? false; - // 统一线程锁 protected readonly SemaphoreSlim _commLock = new(1, 1); public S7Device() { - // 初始化默认配置 _plc = new Plc(CpuType, IPAddress, Rack, Slot); } - /// - /// 设备参数配置(符合你的命名风格) - /// public void ConfigureDevice(string ipAddress, CpuType cpuType, short rack = 0, short slot = 1, int sendTimeout = 3000, int receiveTimeout = 3000) { IPAddress = ipAddress; @@ -50,32 +43,30 @@ namespace DeviceCommand.Base await _commLock.WaitAsync(ct); try { - // 如果已经连接,检查当前的 IP 和 CPU 类型是否一致,一致则直接复用 if (_plc != null && _plc.IsConnected) { if (_plc.IP == IPAddress && _plc.CPU == CpuType && _plc.Rack == Rack && _plc.Slot == Slot) return true; } - // 修复:释放并彻底清空旧连接实例 if (_plc != null) { _plc.Close(); } - // 重新实例化 Plc 对象并配置超时 _plc = new Plc(CpuType, IPAddress, Rack, Slot) { ReadTimeout = ReceiveTimeout, WriteTimeout = SendTimeout }; - // 部分版本 S7.Net 的 OpenAsync 本身不接受 CancellationToken,我们通过 WaitAsync 实现超时 - await _plc.OpenAsync().WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct); + await _plc.OpenAsync(); return _plc.IsConnected; } - catch + catch (Exception ex) { + System.Diagnostics.Debug.WriteLine($"[S7Device] 连接异常: IP={IPAddress}, CPU={CpuType}, Error={ex.Message}"); + System.Diagnostics.Debug.WriteLine($"[S7Device] 异常堆栈: {ex.StackTrace}"); return false; } finally @@ -125,7 +116,22 @@ namespace DeviceCommand.Base public async Task ReadAsync(string address, CancellationToken ct = default) { var result = await ReadAsync(address, ct); - return (T)result; + + System.Diagnostics.Debug.WriteLine($"[S7Device.ReadAsync] 地址={address}, 返回类型={result?.GetType().Name ?? "null"}, 值={result}"); + + try + { + if (result is IConvertible convertible) + { + return (T)Convert.ChangeType(convertible, typeof(T)); + } + return (T)result; + } + catch (InvalidCastException ex) + { + System.Diagnostics.Debug.WriteLine($"[S7Device.ReadAsync] 类型转换失败: 目标类型={typeof(T).Name}, 实际类型={result.GetType().Name}, 值={result}, 异常={ex.Message}"); + throw; + } } public async Task ReadBytesAsync(DataType dataType, int db, int startByteAdr, int count, CancellationToken ct = default) diff --git a/DeviceCommand/Devices/THC-1100-602A.cs b/DeviceCommand/Devices/THC-1100-602A.cs index 81b0333..5d1d968 100644 --- a/DeviceCommand/Devices/THC-1100-602A.cs +++ b/DeviceCommand/Devices/THC-1100-602A.cs @@ -1,49 +1,377 @@ -using DeviceCommand.Base; +using DeviceCommand.Base; +using S7.Net; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; namespace DeviceCommand.Devices { + /// + /// THC-1100-602A 恒温恒湿试验箱驱动 (S7通讯协议) + /// 基于西门子PLC S7-1200/S7-1500系列 + /// public class THC1100 : S7Device { - // 定义 PLC 地址常量,便于后期维护 + #region THC设备默认连接参数 + private const string DefaultIpAddress = "192.168.0.1"; + private const CpuType DefaultCpuType = CpuType.S71200; + private const short DefaultRack = 0; + private const short DefaultSlot = 1; + private const int DefaultSendTimeout = 3000; + private const int DefaultReceiveTimeout = 3000; + #endregion + + #region 寄存器地址定义 + // ========== 主控界面相关 (DB53) ========== private const string RealTimeTemperatureSetPointAddress = "DB53.DBD280"; private const string RealTimeHumidityMeasuredValueAddress = "DB53.DBD1092"; + private const string RealTimeTemperatureMeasuredValueAddress = "DB53.DBD1052"; + private const string OperationModeAddress = "DB53.DBW2"; + private const string TestTypeAddress = "DB53.DBW4"; + private const string CurrentStepAddress = "DB53.DBW6"; + private const string TotalStepsAddress = "DB53.DBW8"; + private const string LoopCountAddress = "DB53.DBW10"; + private const string RunTimeAddress = "DB53.DBD12"; + private const string StepTimeAddress = "DB53.DBD16"; + private const string SystemStatusAddress = "DB53.DBW20"; + + // ========== 超温保护相关 (DB53) ========== + private const string OverTempHighLimitAddress = "DB53.DBD284"; + private const string OverTempLowLimitAddress = "DB53.DBD288"; + private const string OverTempEnableAddress = "DB53.DBX292.0"; + + // ========== 报警相关 (DB53) ========== + private const string AlarmStatusAddress = "DB53.DBW293"; + private const string AlarmCodeAddress = "DB53.DBW295"; + private const string AlarmCountAddress = "DB53.DBW297"; + + // ========== 控制指令 (DB53) ========== + private const string ControlCommandAddress = "DB53.DBW300"; + + // ========== 程序步骤参数 (DB54) ========== + private const string StepTemperatureBaseAddress = "DB54.DBD"; + private const string StepTimeBaseAddress = "DB54.DBD"; + #endregion + + #region 运行模式枚举 + public enum OperationMode + { + Stop = 0, + Running = 1, + Paused = 2 + } + #endregion + + public THC1100() : base() + { + // 使用THC设备的默认参数配置 + ConfigureDevice( + ipAddress: DefaultIpAddress, + cpuType: DefaultCpuType, + rack: DefaultRack, + slot: DefaultSlot, + sendTimeout: DefaultSendTimeout, + receiveTimeout: DefaultReceiveTimeout + ); + } /// - /// 异步获取温度实时测量值 (DB53.DBD1052, 浮点型) + /// 重写连接方法,确保使用正确的默认参数连接 /// - /// 取消令牌 - /// 温度设定值 (float) + public override async Task ConnectAsync(CancellationToken ct = default) + { + if (string.IsNullOrEmpty(IPAddress)) + { + ConfigureDevice( + ipAddress: DefaultIpAddress, + cpuType: DefaultCpuType, + rack: DefaultRack, + slot: DefaultSlot, + sendTimeout: DefaultSendTimeout, + receiveTimeout: DefaultReceiveTimeout + ); + } + + System.Diagnostics.Debug.WriteLine($"[THC1100] 连接参数: IP={IPAddress}, CPU={CpuType}, Rack={Rack}, Slot={Slot}"); + + bool result = await base.ConnectAsync(ct); + + if (!result) + { + System.Diagnostics.Debug.WriteLine($"[THC1100] 连接失败: IP={IPAddress}, CPU={CpuType}"); + } + + return result; + } + + #region 基础测量方法 public async Task GetRealTimeTemperatureSetPointAsync(CancellationToken ct = default) { - // S7.Net 读取浮点型时,可以直接将其强转为 float - // 内部已通过基类的 _commLock 保证线程安全 - return await ReadAsync(RealTimeTemperatureSetPointAddress, ct); + try + { + byte[] data = await ReadBytesAsync(DataType.DataBlock, 53, 280, 4, ct); + float value = ByteArrayToFloat(data); + System.Diagnostics.Debug.WriteLine($"[THC1100] 读取温度设定值(DB53.DBD280): {value}"); + return value; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[THC1100] 读取温度设定值失败: {ex.Message}"); + throw; + } } - /// - /// 异步获取湿度实时测量值 (DB53.DBD1092, 浮点型) - /// - /// 取消令牌 - /// 湿度测量值 (float) public async Task GetRealTimeHumidityMeasuredValueAsync(CancellationToken ct = default) { - return await ReadAsync(RealTimeHumidityMeasuredValueAddress, ct); + try + { + byte[] data = await ReadBytesAsync(DataType.DataBlock, 53, 1092, 4, ct); + float value = ByteArrayToFloat(data); + System.Diagnostics.Debug.WriteLine($"[THC1100] 读取湿度测量值(DB53.DBD1092): {value}"); + return value; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[THC1100] 读取湿度测量值失败: {ex.Message}"); + throw; + } } - /// - /// 一次性获取温湿度相关的运行数据包(选填,若需要批量展示可以使用此方法) - /// - public async Task<(float TemperatureSetPoint, float HumidityMeasuredValue)> GetThcDataPackAsync(CancellationToken ct = default) + public async Task GetRealTimeTemperatureMeasuredValueAsync(CancellationToken ct = default) + { + try + { + byte[] data = await ReadBytesAsync(DataType.DataBlock, 53, 1052, 4, ct); + float value = ByteArrayToFloat(data); + System.Diagnostics.Debug.WriteLine($"[THC1100] 读取温度测量值(DB53.DBD1052): {value}"); + return value; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[THC1100] 读取温度测量值失败: {ex.Message}"); + throw; + } + } + + private float ByteArrayToFloat(byte[] data) + { + if (data == null || data.Length < 4) + throw new ArgumentException("数据长度不足"); + + if (BitConverter.IsLittleEndian) + { + byte[] reversed = (byte[])data.Clone(); + Array.Reverse(reversed); + return BitConverter.ToSingle(reversed, 0); + } + return BitConverter.ToSingle(data, 0); + } + #endregion + + #region 运行控制方法 + public async Task GetOperationModeAsync(CancellationToken ct = default) + { + var value = await ReadAsync(OperationModeAddress, ct); + return (OperationMode)value; + } + + public async Task GetTestTypeAsync(CancellationToken ct = default) + { + return await ReadAsync(TestTypeAddress, ct); + } + + public async Task SetTestTypeAsync(int testType, CancellationToken ct = default) + { + if (testType < 0 || testType > 65535) + throw new ArgumentOutOfRangeException(nameof(testType), "试验类型必须在0-65535范围内"); + + await WriteAsync(TestTypeAddress, (ushort)testType, ct); + } + + public async Task GetCurrentStepAsync(CancellationToken ct = default) + { + return await ReadAsync(CurrentStepAddress, ct); + } + + public async Task GetTotalStepsAsync(CancellationToken ct = default) + { + return await ReadAsync(TotalStepsAddress, ct); + } + + public async Task SetTotalStepsAsync(int steps, CancellationToken ct = default) + { + if (steps < 1 || steps > 999) + throw new ArgumentOutOfRangeException(nameof(steps), "步数必须在1-999之间"); + + await WriteAsync(TotalStepsAddress, (ushort)steps, ct); + } + + public async Task GetLoopCountAsync(CancellationToken ct = default) + { + return await ReadAsync(LoopCountAddress, ct); + } + + public async Task SetLoopCountAsync(int count, CancellationToken ct = default) + { + if (count < 0 || count > 999) + throw new ArgumentOutOfRangeException(nameof(count), "循环次数必须在0-999之间"); + + await WriteAsync(LoopCountAddress, (ushort)count, ct); + } + + public async Task GetRunTimeAsync(CancellationToken ct = default) + { + return await ReadAsync(RunTimeAddress, ct); + } + + public async Task GetStepTimeAsync(CancellationToken ct = default) + { + return await ReadAsync(StepTimeAddress, ct); + } + + public async Task GetSystemStatusAsync(CancellationToken ct = default) + { + return await ReadAsync(SystemStatusAddress, ct); + } + #endregion + + #region 超温保护方法 + public async Task GetOverTempHighLimitAsync(CancellationToken ct = default) + { + return await ReadAsync(OverTempHighLimitAddress, ct); + } + + public async Task SetOverTempHighLimitAsync(float temperature, CancellationToken ct = default) + { + await WriteAsync(OverTempHighLimitAddress, temperature, ct); + } + + public async Task GetOverTempLowLimitAsync(CancellationToken ct = default) + { + return await ReadAsync(OverTempLowLimitAddress, ct); + } + + public async Task SetOverTempLowLimitAsync(float temperature, CancellationToken ct = default) + { + await WriteAsync(OverTempLowLimitAddress, temperature, ct); + } + + public async Task GetOverTempEnableAsync(CancellationToken ct = default) + { + return await ReadAsync(OverTempEnableAddress, ct); + } + + public async Task SetOverTempEnableAsync(bool enable, CancellationToken ct = default) + { + await WriteAsync(OverTempEnableAddress, enable, ct); + } + #endregion + + #region 报警相关方法 + public async Task GetAlarmStatusAsync(CancellationToken ct = default) + { + return await ReadAsync(AlarmStatusAddress, ct); + } + + public async Task GetAlarmCodeAsync(CancellationToken ct = default) + { + return await ReadAsync(AlarmCodeAddress, ct); + } + + public async Task GetAlarmCountAsync(CancellationToken ct = default) + { + return await ReadAsync(AlarmCountAddress, ct); + } + + public async Task ClearAlarmAsync(CancellationToken ct = default) + { + await WriteAsync(ControlCommandAddress, (ushort)0x08, ct); + await Task.Delay(100, ct); + await WriteAsync(ControlCommandAddress, (ushort)0x00, ct); + } + #endregion + + #region 控制指令方法 + public async Task StartDeviceAsync(CancellationToken ct = default) + { + await WriteAsync(ControlCommandAddress, (ushort)0x01, ct); + await Task.Delay(100, ct); + await WriteAsync(ControlCommandAddress, (ushort)0x00, ct); + } + + public async Task StopDeviceAsync(CancellationToken ct = default) + { + await WriteAsync(ControlCommandAddress, (ushort)0x02, ct); + await Task.Delay(100, ct); + await WriteAsync(ControlCommandAddress, (ushort)0x00, ct); + } + + public async Task ResetDeviceAsync(CancellationToken ct = default) + { + await WriteAsync(ControlCommandAddress, (ushort)0x04, ct); + await Task.Delay(100, ct); + await WriteAsync(ControlCommandAddress, (ushort)0x00, ct); + } + #endregion + + #region 程序步骤方法 + public async Task SetStepTemperatureAsync(int step, float temperature, CancellationToken ct = default) + { + if (step < 1) + throw new ArgumentOutOfRangeException(nameof(step), "步骤编号必须大于0"); + + string address = $"{StepTemperatureBaseAddress}{(step - 1) * 12}"; + await WriteAsync(address, temperature, ct); + } + + public async Task GetStepTemperatureAsync(int step, CancellationToken ct = default) + { + if (step < 1) + throw new ArgumentOutOfRangeException(nameof(step), "步骤编号必须大于0"); + + string address = $"{StepTemperatureBaseAddress}{(step - 1) * 12}"; + return await ReadAsync(address, ct); + } + + public async Task SetStepTimeAsync(int step, float duration, CancellationToken ct = default) + { + if (step < 1) + throw new ArgumentOutOfRangeException(nameof(step), "步骤编号必须大于0"); + if (duration < 0) + throw new ArgumentOutOfRangeException(nameof(duration), "时间不能为负数"); + + string address = $"{StepTimeBaseAddress}{(step - 1) * 12 + 8}"; + await WriteAsync(address, duration, ct); + } + + public async Task GetStepTimeAsync(int step, CancellationToken ct = default) + { + if (step < 1) + throw new ArgumentOutOfRangeException(nameof(step), "步骤编号必须大于0"); + + string address = $"{StepTimeBaseAddress}{(step - 1) * 12 + 8}"; + return await ReadAsync(address, ct); + } + #endregion + + #region 数据打包方法 + public async Task<(float TemperatureSetPoint, float TemperatureMeasured, float HumidityMeasured)> GetThcDataPackAsync(CancellationToken ct = default) { var tempSet = await GetRealTimeTemperatureSetPointAsync(ct); - var humidMeasure = await GetRealTimeHumidityMeasuredValueAsync(ct); - return (tempSet, humidMeasure); + var tempMeas = await GetRealTimeTemperatureMeasuredValueAsync(ct); + var humidMeas = await GetRealTimeHumidityMeasuredValueAsync(ct); + return (tempSet, tempMeas, humidMeas); } + + public async Task<(OperationMode Mode, int CurrentStep, int TotalSteps, int LoopCount, float RunTime)> GetRunStatusPackAsync(CancellationToken ct = default) + { + var mode = await GetOperationModeAsync(ct); + var currentStep = await GetCurrentStepAsync(ct); + var totalSteps = await GetTotalStepsAsync(ct); + var loopCount = await GetLoopCountAsync(ct); + var runTime = await GetRunTimeAsync(ct); + return (mode, currentStep, totalSteps, loopCount, runTime); + } + #endregion } -} \ No newline at end of file +} diff --git a/DeviceCommand/Devices/UMC1300.cs b/DeviceCommand/Devices/UMC1300.cs index 292d12b..119ac3c 100644 --- a/DeviceCommand/Devices/UMC1300.cs +++ b/DeviceCommand/Devices/UMC1300.cs @@ -35,6 +35,15 @@ namespace DeviceCommand.Devices ConfigureDevice(ip, port, sendTimeoutMs, receiveTimeoutMs); } + public override async Task ConnectAsync(CancellationToken ct = default) + { + if (IsConnected) + { + return true; + } + return await base.ConnectAsync(ct); + } + // ==================== 读取单个值 ==================== /// 读取温度 PV(工程值) diff --git a/LOT/ViewModels/ShellViewModel.cs b/LOT/ViewModels/ShellViewModel.cs index ce03771..646d21c 100644 --- a/LOT/ViewModels/ShellViewModel.cs +++ b/LOT/ViewModels/ShellViewModel.cs @@ -1,14 +1,4 @@ -using LOT.Views; -using MaterialDesignThemes.Wpf; -using Notifications.Wpf.Core; -using Prism.Events; -using Prism.Ioc; -using Prism.Modularity; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Notifications.Wpf.Core; using System.Windows; using System.Windows.Input; using UIShare.PubEvent; diff --git a/MainModule/Events/ConnectionConfigEvent.cs b/MainModule/Events/ConnectionConfigEvent.cs new file mode 100644 index 0000000..8be73c8 --- /dev/null +++ b/MainModule/Events/ConnectionConfigEvent.cs @@ -0,0 +1,35 @@ +using Prism.Events; +using S7.Net; +using System.IO.Ports; + +namespace MainModule.Events +{ + public enum ProtocolType + { + S7, + ModbusTCP, + ModbusRTU, + TCP, + HTTP + } + + public class ConnectionConfigEvent : PubSubEvent + { + public class ConnectionConfigData + { + public ProtocolType Protocol { get; set; } + public string IpAddress { get; set; } = string.Empty; + public int Port { get; set; } + public string SerialPort { get; set; } = string.Empty; + public int BaudRate { get; set; } + public Parity Parity { get; set; } + public int DataBits { get; set; } + public StopBits StopBits { get; set; } + public CpuType CpuType { get; set; } + public short Rack { get; set; } + public short Slot { get; set; } + public int SendTimeout { get; set; } + public int ReceiveTimeout { get; set; } + } + } +} diff --git a/MainModule/MainModule.cs b/MainModule/MainModule.cs index 80581dd..8e596cd 100644 --- a/MainModule/MainModule.cs +++ b/MainModule/MainModule.cs @@ -1,4 +1,6 @@ +using MainModule.ViewModels; using MainModule.Views; +using Prism.Ioc; using System.Reflection; namespace MainModule diff --git a/MainModule/MainModule.csproj b/MainModule/MainModule.csproj index 9902add..d334cd2 100644 --- a/MainModule/MainModule.csproj +++ b/MainModule/MainModule.csproj @@ -8,6 +8,7 @@ + diff --git a/MainModule/ViewModels/ConnectionConfigViewModel.cs b/MainModule/ViewModels/ConnectionConfigViewModel.cs new file mode 100644 index 0000000..19d0ca1 --- /dev/null +++ b/MainModule/ViewModels/ConnectionConfigViewModel.cs @@ -0,0 +1,237 @@ +using MainModule.Events; +using Prism.Commands; +using Prism.Mvvm; +using S7.Net; +using System.Collections.Generic; +using System.IO.Ports; +using System.Windows; + +namespace MainModule.ViewModels +{ + public class ConnectionConfigViewModel : BindableBase + { + #region 私有字段 + private Events.ProtocolType _selectedProtocol = Events.ProtocolType.S7; + private string _ipAddress = "127.0.0.1"; + private int _port = 102; + private string _serialPort = "COM1"; + private int _baudRate = 9600; + private Parity _parity = Parity.None; + private int _dataBits = 8; + private StopBits _stopBits = StopBits.One; + private CpuType _cpuType = CpuType.S71200; + private short _rack = 0; + private short _slot = 1; + private int _sendTimeout = 3000; + private int _receiveTimeout = 5000; + #endregion + + #region 公共属性 + public Events.ProtocolType SelectedProtocol + { + get => _selectedProtocol; + set + { + if (SetProperty(ref _selectedProtocol, value)) + { + RaisePropertyChanged(nameof(IsS7Protocol)); + RaisePropertyChanged(nameof(IsModbusTCPProtocol)); + RaisePropertyChanged(nameof(IsModbusRTUProtocol)); + RaisePropertyChanged(nameof(IsTCPProtocol)); + RaisePropertyChanged(nameof(IsHTTPProtocol)); + } + } + } + + public string IpAddress + { + get => _ipAddress; + set => SetProperty(ref _ipAddress, value); + } + + public int Port + { + get => _port; + set => SetProperty(ref _port, value); + } + + public string SerialPort + { + get => _serialPort; + set => SetProperty(ref _serialPort, value); + } + + public int BaudRate + { + get => _baudRate; + set => SetProperty(ref _baudRate, value); + } + + public Parity Parity + { + get => _parity; + set => SetProperty(ref _parity, value); + } + + public int DataBits + { + get => _dataBits; + set => SetProperty(ref _dataBits, value); + } + + public StopBits StopBits + { + get => _stopBits; + set => SetProperty(ref _stopBits, value); + } + + public CpuType CpuType + { + get => _cpuType; + set => SetProperty(ref _cpuType, value); + } + + public short Rack + { + get => _rack; + set => SetProperty(ref _rack, value); + } + + public short Slot + { + get => _slot; + set => SetProperty(ref _slot, value); + } + + public int SendTimeout + { + get => _sendTimeout; + set => SetProperty(ref _sendTimeout, value); + } + + public int ReceiveTimeout + { + get => _receiveTimeout; + set => SetProperty(ref _receiveTimeout, value); + } + #endregion + + #region 协议类型判断属性 + public bool IsS7Protocol => SelectedProtocol == Events.ProtocolType.S7; + public bool IsModbusTCPProtocol => SelectedProtocol == Events.ProtocolType.ModbusTCP; + public bool IsModbusRTUProtocol => SelectedProtocol == Events.ProtocolType.ModbusRTU; + public bool IsTCPProtocol => SelectedProtocol == Events.ProtocolType.TCP; + public bool IsHTTPProtocol => SelectedProtocol == Events.ProtocolType.HTTP; + #endregion + + #region 下拉列表数据源 + public List CpuTypes { get; } = new List + { + CpuType.S71200, + CpuType.S71500, + CpuType.S7200, + CpuType.S7300, + CpuType.S7400, + CpuType.S7200Smart + }; + + public List BaudRates { get; } = new List + { + 9600, + 19200, + 38400, + 57600, + 115200 + }; + + public List DataBitsList { get; } = new List { 7, 8 }; + + public List ParityList { get; } = new List + { + Parity.None, + Parity.Odd, + Parity.Even, + Parity.Mark, + Parity.Space + }; + + public List StopBitsList { get; } = new List + { + StopBits.None, + StopBits.One, + StopBits.OnePointFive, + StopBits.Two + }; + + public List SerialPorts { get; } = new List + { + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8" + }; + #endregion + + #region 命令 + public DelegateCommand ConfirmCommand { get; } + public DelegateCommand CancelCommand { get; } + public DelegateCommand SelectS7Command { get; } + public DelegateCommand SelectModbusTCPCommand { get; } + public DelegateCommand SelectModbusRTUCommand { get; } + public DelegateCommand SelectTCPCommand { get; } + public DelegateCommand SelectHTTPCommand { get; } + #endregion + + #region 配置数据(供外部获取) + public ConnectionConfigEvent.ConnectionConfigData ConnectionConfigData { get; private set; } + #endregion + + public ConnectionConfigViewModel() + { + ConfirmCommand = new DelegateCommand(ExecuteConfirm); + CancelCommand = new DelegateCommand(ExecuteCancel); + SelectS7Command = new DelegateCommand(() => SelectedProtocol = Events.ProtocolType.S7); + SelectModbusTCPCommand = new DelegateCommand(() => SelectedProtocol = Events.ProtocolType.ModbusTCP); + SelectModbusRTUCommand = new DelegateCommand(() => SelectedProtocol = Events.ProtocolType.ModbusRTU); + SelectTCPCommand = new DelegateCommand(() => SelectedProtocol = Events.ProtocolType.TCP); + SelectHTTPCommand = new DelegateCommand(() => SelectedProtocol = Events.ProtocolType.HTTP); + } + + #region 命令执行 + private void ExecuteConfirm() + { + ConnectionConfigData = new ConnectionConfigEvent.ConnectionConfigData + { + Protocol = SelectedProtocol, + IpAddress = IpAddress, + Port = Port, + SerialPort = SerialPort, + BaudRate = BaudRate, + Parity = Parity, + DataBits = DataBits, + StopBits = StopBits, + CpuType = CpuType, + Rack = Rack, + Slot = Slot, + SendTimeout = SendTimeout, + ReceiveTimeout = ReceiveTimeout + }; + + var window = Application.Current.Windows.OfType().FirstOrDefault(); + if (window != null) + { + window.DialogResult = true; + window.Close(); + } + } + + private void ExecuteCancel() + { + ConnectionConfigData = null; + var window = Application.Current.Windows.OfType().FirstOrDefault(); + if (window != null) + { + window.DialogResult = false; + window.Close(); + } + } + #endregion + } +} diff --git a/MainModule/ViewModels/MainViewModel.cs b/MainModule/ViewModels/MainViewModel.cs index 8b0056e..4524d52 100644 --- a/MainModule/ViewModels/MainViewModel.cs +++ b/MainModule/ViewModels/MainViewModel.cs @@ -1,78 +1,749 @@ -using Model.Entity; +using DeviceCommand.Base; +using DeviceCommand.Devices; +using MainModule.Events; +using MainModule.Views; +using Model.Entity; using Prism.Commands; +using Prism.Events; using Prism.Ioc; +using Prism.Navigation; using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Threading; using UIShare.ViewModelBase; namespace MainModule.ViewModels { public class MainViewModel : NavigateViewModelBase { + private readonly THC1100 _thc1100 = new(); + private readonly UMC1300 _umc1300; public ObservableCollection Chambers { get; } = new(); private IContainerProvider _containerProvider; + private CancellationTokenSource _refreshCts; + private DispatcherTimer _refreshTimer; + private bool _isInitialized = false; + + #region 按钮文本和连接状态 + private string _connectButtonText = "连接设备"; + public string ConnectButtonText + { + get => _connectButtonText; + set => SetProperty(ref _connectButtonText, value); + } + + private bool _isConnected = false; + public bool IsConnected + { + get => _isConnected; + set + { + if (SetProperty(ref _isConnected, value)) + { + ConnectButtonText = value ? "断开连接" : "连接设备"; + } + } + } + #endregion + + #region 命令定义 + public DelegateCommand ConnectCommand { get; } + public DelegateCommand DisconnectCommand { get; } + public DelegateCommand RefreshCommand { get; } + public DelegateCommand ConfigCommand { get; } + public DelegateCommand ConnectDeviceCommand { get; } + #endregion + + #region 配置信息 + private Events.ProtocolType _selectedProtocol = Events.ProtocolType.S7; + public Events.ProtocolType SelectedProtocol + { + get => _selectedProtocol; + set => SetProperty(ref _selectedProtocol, value); + } + + private string _configIpAddress = "127.0.0.1"; + public string ConfigIpAddress + { + get => _configIpAddress; + set => SetProperty(ref _configIpAddress, value); + } + + private int _configPort = 102; + public int ConfigPort + { + get => _configPort; + set => SetProperty(ref _configPort, value); + } + + private S7.Net.CpuType _configCpuType = S7.Net.CpuType.S71200; + public S7.Net.CpuType ConfigCpuType + { + get => _configCpuType; + set => SetProperty(ref _configCpuType, value); + } + + private short _configRack = 0; + public short ConfigRack + { + get => _configRack; + set => SetProperty(ref _configRack, value); + } + + private short _configSlot = 1; + public short ConfigSlot + { + get => _configSlot; + set => SetProperty(ref _configSlot, value); + } + + private string _configSerialPort = "COM1"; + public string ConfigSerialPort + { + get => _configSerialPort; + set => SetProperty(ref _configSerialPort, value); + } + + private int _configBaudRate = 9600; + public int ConfigBaudRate + { + get => _configBaudRate; + set => SetProperty(ref _configBaudRate, value); + } + + private int _configSendTimeout = 3000; + public int ConfigSendTimeout + { + get => _configSendTimeout; + set => SetProperty(ref _configSendTimeout, value); + } + + private int _configReceiveTimeout = 5000; + public int ConfigReceiveTimeout + { + get => _configReceiveTimeout; + set => SetProperty(ref _configReceiveTimeout, value); + } + #endregion + public MainViewModel(IContainerProvider containerProvider) : base(containerProvider) { _containerProvider = containerProvider; + + // 初始化UMC1300设备(默认从站地址1) + _umc1300 = new UMC1300(1, "127.0.0.1", 502); - // 触发配置与绑定初始化 - ConfigureAndBindChambers(); + // 初始化命令 + ConnectCommand = new DelegateCommand(async () => await ExecuteConnectAsync()); + DisconnectCommand = new DelegateCommand(ExecuteDisconnect); + RefreshCommand = new DelegateCommand(async () => await RefreshDeviceDataAsync()); + ConfigCommand = new DelegateCommand(() => ShowConfigDialogAsync()); + ConnectDeviceCommand = new DelegateCommand(async (item) => await ExecuteConnectDeviceAsync(item)); } /// - /// 核心配置与数据绑定逻辑 + /// 当视图被导航到时执行初始化(Prism导航机制) /// - private void ConfigureAndBindChambers() + public override async void OnNavigatedTo(NavigationContext navigationContext) { - // 实例 1:Modbus TCP 环境箱配置 - Chambers.Add(new ChamberMonitorItem - { - Id = 1, - Name = "环境箱 01", - ProtocolType = "Modbus TCP", - IsConnected = false, - Temperature = 0.0, - Humidity = 0.0 - }); + base.OnNavigatedTo(navigationContext); - // 实例 2:Modbus TCP B+ 环境箱配置 - Chambers.Add(new ChamberMonitorItem - { - Id = 2, - Name = "环境箱 02", - ProtocolType = "Modbus TCP B+", - IsConnected = false, - Temperature = 0.0, - Humidity = 0.0 - }); + // 避免重复初始化 + if (_isInitialized) + return; + _isInitialized = true; - // 实例 3:西门子 S7 环境箱配置 - Chambers.Add(new ChamberMonitorItem - { - Id = 3, - Name = "环境箱 03", - ProtocolType = "S7", - IsConnected = false, - Temperature = 0.0, - Humidity = 0.0 - }); - - // 实例 4:HTTP 环境箱配置 - Chambers.Add(new ChamberMonitorItem - { - Id = 4, - Name = "环境箱 04", - ProtocolType = "HTTP", - IsConnected = false, - Temperature = 0.0, - Humidity = 0.0 - }); + await InitializeAsync(); } + + /// + /// 异步初始化设备连接和数据 + /// + private async Task InitializeAsync() + { + try + { + // 先添加所有卡片(显示初始状态) + Application.Current.Dispatcher.Invoke(() => + { + Chambers.Clear(); + Chambers.Add(new ChamberMonitorItem + { + Id = 1, + Name = "环境箱 01", + ProtocolType = "Modbus TCP", + IsConnected = false, + Temperature = 0.0, + Humidity = 0.0 + }); + + Chambers.Add(new ChamberMonitorItem + { + Id = 2, + Name = "环境箱 02", + ProtocolType = "Modbus TCP B+", + IsConnected = false, + Temperature = 0.0, + Humidity = 0.0 + }); + + Chambers.Add(new ChamberMonitorItem + { + Id = 3, + Name = "环境箱 03", + ProtocolType = "S7", + IsConnected = false, + Temperature = 0.0, + Humidity = 0.0 + }); + + Chambers.Add(new ChamberMonitorItem + { + Id = 4, + Name = "环境箱 04", + ProtocolType = "HTTP", + IsConnected = false, + Temperature = 0.0, + Humidity = 0.0 + }); + }); + + System.Diagnostics.Debug.WriteLine("设备卡片初始化完成"); + } + catch (System.Exception ex) + { + System.Diagnostics.Debug.WriteLine($"初始化异常: {ex.Message}"); + } + } + + #region 配置对话框 + /// + /// 显示配置连接对话框 + /// + private void ShowConfigDialogAsync() + { + try + { + // 手动创建 ViewModel + var viewModel = new ConnectionConfigViewModel(); + + // 设置初始值 + viewModel.SelectedProtocol = SelectedProtocol; + viewModel.IpAddress = ConfigIpAddress; + viewModel.Port = ConfigPort; + viewModel.CpuType = ConfigCpuType; + viewModel.Rack = ConfigRack; + viewModel.Slot = ConfigSlot; + viewModel.SerialPort = ConfigSerialPort; + viewModel.BaudRate = ConfigBaudRate; + viewModel.SendTimeout = ConfigSendTimeout; + viewModel.ReceiveTimeout = ConfigReceiveTimeout; + + // 创建窗口 + var configWindow = new ConnectionConfigView(viewModel); + + // 设置父窗口 + configWindow.Owner = Application.Current.MainWindow; + + // 显示模态对话框 + bool? result = configWindow.ShowDialog(); + + // 如果用户点击确定,获取配置数据 + if (result == true && viewModel.ConnectionConfigData != null) + { + // 直接更新配置参数 + SelectedProtocol = viewModel.ConnectionConfigData.Protocol; + ConfigIpAddress = viewModel.ConnectionConfigData.IpAddress; + ConfigPort = viewModel.ConnectionConfigData.Port; + ConfigCpuType = viewModel.ConnectionConfigData.CpuType; + ConfigRack = viewModel.ConnectionConfigData.Rack; + ConfigSlot = viewModel.ConnectionConfigData.Slot; + ConfigSerialPort = viewModel.ConnectionConfigData.SerialPort; + ConfigBaudRate = viewModel.ConnectionConfigData.BaudRate; + ConfigSendTimeout = viewModel.ConnectionConfigData.SendTimeout; + ConfigReceiveTimeout = viewModel.ConnectionConfigData.ReceiveTimeout; + + System.Diagnostics.Debug.WriteLine($"配置更新成功: 协议={SelectedProtocol}, IP={ConfigIpAddress}, 端口={ConfigPort}"); + } + } + catch (System.Exception ex) + { + System.Diagnostics.Debug.WriteLine($"打开配置对话框异常: {ex.Message}"); + } + } + + /// + /// 配置更新事件处理 + /// + private void OnConfigUpdated(ConnectionConfigEvent.ConnectionConfigData config) + { + SelectedProtocol = config.Protocol; + ConfigIpAddress = config.IpAddress; + ConfigPort = config.Port; + ConfigCpuType = config.CpuType; + ConfigRack = config.Rack; + ConfigSlot = config.Slot; + ConfigSerialPort = config.SerialPort; + ConfigBaudRate = config.BaudRate; + ConfigSendTimeout = config.SendTimeout; + ConfigReceiveTimeout = config.ReceiveTimeout; + + System.Diagnostics.Debug.WriteLine($"配置更新: 协议={SelectedProtocol}, IP={ConfigIpAddress}, 端口={ConfigPort}"); + } + #endregion + + #region 连接命令实现 + /// + /// 执行连接/断开切换命令 + /// + private async Task ExecuteConnectAsync() + { + if (IsConnected) + { + // 如果已连接,则断开 + ExecuteDisconnect(); + } + else + { + // 如果未连接,则连接 + await ConnectToDeviceAsync(); + } + } + + /// + /// 连接到THC设备 + /// + private async Task ConnectToDeviceAsync() + { + try + { + System.Diagnostics.Debug.WriteLine("正在连接THC设备..."); + + // 使用配置对话框中设置的参数配置设备 + _thc1100.ConfigureDevice( + ipAddress: ConfigIpAddress, + cpuType: ConfigCpuType, + rack: ConfigRack, + slot: ConfigSlot + ); + System.Diagnostics.Debug.WriteLine($"配置参数: 协议={SelectedProtocol}, IP={_thc1100.IPAddress}, CPU={_thc1100.CpuType}, Rack={_thc1100.Rack}, Slot={_thc1100.Slot}"); + + // 连接THC设备 + bool connected = await _thc1100.ConnectAsync(); + + if (connected) + { + IsConnected = true; + System.Windows.MessageBox.Show("THC设备连接成功", "连接成功", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information); + + // 更新环境箱03的连接状态 + var chamber3 = GetChamberById(3); + if (chamber3 != null) + { + chamber3.IsConnected = true; + } + + // 读取初始数据 + await RefreshDeviceDataAsync(); + + // 启动定时刷新(每1秒) + StartPeriodicRefresh(); + } + else + { + System.Diagnostics.Debug.WriteLine("THC设备连接失败"); + MessageBox.Show("设备连接失败,请检查网络和设备状态!", "连接失败", + MessageBoxButton.OK, MessageBoxImage.Warning); + } + } + catch (System.Exception ex) + { + System.Diagnostics.Debug.WriteLine($"连接异常: {ex.Message}"); + MessageBox.Show($"连接异常: {ex.Message}", "错误", + MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + /// + /// 断开THC设备连接 + /// + private void ExecuteDisconnect() + { + try + { + // 停止定时器 + _refreshCts?.Cancel(); + _refreshTimer?.Stop(); + + // 关闭设备连接 + _thc1100.Close(); + + // 更新连接状态 + IsConnected = false; + + // 更新环境箱03的连接状态 + var chamber3 = GetChamberById(3); + if (chamber3 != null) + { + chamber3.IsConnected = false; + } + + System.Diagnostics.Debug.WriteLine("THC设备已断开连接"); + } + catch (System.Exception ex) + { + System.Diagnostics.Debug.WriteLine($"断开连接异常: {ex.Message}"); + } + } + #endregion + + #region 定时刷新 + /// + /// 启动定时刷新 + /// + private void StartPeriodicRefresh() + { + _refreshCts?.Cancel(); + _refreshCts = new CancellationTokenSource(); + + _refreshTimer?.Stop(); + _refreshTimer = new DispatcherTimer + { + Interval = TimeSpan.FromSeconds(1) + }; + + _refreshTimer.Tick += async (s, e) => + { + try + { + System.Diagnostics.Debug.WriteLine($"[定时刷新] Tick触发,当前时间: {DateTime.Now:HH:mm:ss.fff}"); + System.Diagnostics.Debug.WriteLine($"[定时刷新] THC1100.IsConnected = {_thc1100.IsConnected}"); + + // 检查THC1100连接状态 + if (_thc1100.IsConnected) + { + var chamber3 = GetChamberById(3); + System.Diagnostics.Debug.WriteLine($"[定时刷新] chamber3 = {chamber3}, IsConnected = {chamber3?.IsConnected}"); + + if (chamber3 != null) + { + System.Diagnostics.Debug.WriteLine("[定时刷新] 开始读取温湿度数据..."); + try + { + var temp = await _thc1100.GetRealTimeTemperatureSetPointAsync(); + var humidity = await _thc1100.GetRealTimeHumidityMeasuredValueAsync(); + + System.Diagnostics.Debug.WriteLine($"[定时刷新] 读取完成 - 温度(DB53.DBD280): {temp}, 湿度(DB53.DBD1092): {humidity}"); + + Application.Current.Dispatcher.Invoke(() => + { + chamber3.Temperature = temp; + chamber3.Humidity = humidity; + System.Diagnostics.Debug.WriteLine($"[定时刷新] UI已更新 - Temperature={chamber3.Temperature}, Humidity={chamber3.Humidity}"); + }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[定时刷新] 读取温湿度数据异常: {ex.Message}"); + } + } + } + else + { + System.Diagnostics.Debug.WriteLine("[定时刷新] THC1100未连接,跳过数据读取"); + } + + // 检查UMC1300连接状态 + if (_umc1300.IsConnected) + { + var chamber1 = GetChamberById(1); + if (chamber1 != null) + { + try + { + var temp = await _umc1300.ReadTemperaturePVAsync(); + var humidity = await _umc1300.ReadHumidityPVAsync(); + + Application.Current.Dispatcher.Invoke(() => + { + chamber1.Temperature = temp; + chamber1.Humidity = humidity; + }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[定时刷新] 读取UMC1300数据异常: {ex.Message}"); + } + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[定时刷新] 定时器异常: {ex.Message}"); + } + }; + + _refreshTimer.Start(); + } + + /// + /// 停止定时刷新 + /// + private void StopPeriodicRefresh() + { + _refreshCts?.Cancel(); + _refreshTimer?.Stop(); + } + #endregion + + #region 数据刷新 + /// + /// 刷新设备数据 + /// + private async Task RefreshDeviceDataAsync() + { + try + { + // 如果未连接,不刷新数据 + if (!_thc1100.IsConnected) + return; + + var chamber3 = GetChamberById(3); + if (chamber3 == null) return; + + // 读取温湿度数据(使用用户指定的寄存器地址) + // 温度: DB53.DBD280, 湿度: DB53.DBD1092 + var temp = await _thc1100.GetRealTimeTemperatureSetPointAsync(); + var humidity = await _thc1100.GetRealTimeHumidityMeasuredValueAsync(); + + System.Diagnostics.Debug.WriteLine($"[初始刷新] 环境箱3 - 温度(DB53.DBD280): {temp}, 湿度(DB53.DBD1092): {humidity}"); + + // 更新UI(在UI线程执行) + Application.Current.Dispatcher.Invoke(() => + { + chamber3.Temperature = temp; + chamber3.Humidity = humidity; + }); + } + catch (System.Exception ex) + { + System.Diagnostics.Debug.WriteLine($"刷新数据异常: {ex.Message}"); + } + } + #endregion + + #region 辅助方法 + /// + /// 根据ID获取卡片 + /// + private ChamberMonitorItem GetChamberById(int id) + { + foreach (var chamber in Chambers) + { + if (chamber.Id == id) + return chamber; + } + return null; + } + + /// + /// 根据设备类型连接单个设备 + /// + private async Task ExecuteConnectDeviceAsync(ChamberMonitorItem item) + { + if (item == null) return; + + try + { + if (item.IsConnected) + { + // 断开连接 + System.Diagnostics.Debug.WriteLine($"[连接] 正在断开设备 {item.Name}"); + + // 根据设备自己的协议类型调用对应设备的Close方法 + switch (item.ProtocolType) + { + case "S7": + _thc1100.Close(); + break; + case "Modbus TCP": + _umc1300.Close(); + break; + } + + // 停止数据刷新 + StopPeriodicRefresh(); + + item.IsConnected = false; + System.Diagnostics.Debug.WriteLine($"[连接] 设备 {item.Name} 已断开连接"); + } + else + { + // 输出当前配置参数 + System.Diagnostics.Debug.WriteLine($"[连接] ========== 开始连接设备 {item.Name} =========="); + System.Diagnostics.Debug.WriteLine($"[连接] 当前协议类型: {SelectedProtocol}"); + System.Diagnostics.Debug.WriteLine($"[连接] IP地址: {ConfigIpAddress}"); + System.Diagnostics.Debug.WriteLine($"[连接] 端口: {ConfigPort}"); + System.Diagnostics.Debug.WriteLine($"[连接] CPU类型: {ConfigCpuType}"); + System.Diagnostics.Debug.WriteLine($"[连接] Rack: {ConfigRack}, Slot: {ConfigSlot}"); + System.Diagnostics.Debug.WriteLine($"[连接] 串口: {ConfigSerialPort}, 波特率: {ConfigBaudRate}"); + System.Diagnostics.Debug.WriteLine($"[连接] 发送超时: {ConfigSendTimeout}ms, 接收超时: {ConfigReceiveTimeout}ms"); + + // 根据设备自己的协议类型连接设备 + bool connected = false; + System.Diagnostics.Debug.WriteLine($"[连接] 设备协议类型: {item.ProtocolType}"); + + switch (item.ProtocolType) + { + case "S7": + // S7协议连接 + System.Diagnostics.Debug.WriteLine("[连接] 使用设备配置的S7协议"); + connected = await ConnectS7DeviceAsync(item); + break; + case "Modbus TCP": + // Modbus TCP连接 + System.Diagnostics.Debug.WriteLine("[连接] 使用设备配置的Modbus TCP协议"); + connected = await ConnectModbusTcpDeviceAsync(item); + break; + case "Modbus TCP B+": + // Modbus TCP B+连接 + System.Diagnostics.Debug.WriteLine("[连接] 使用设备配置的Modbus TCP B+协议"); + connected = await ConnectModbusTcpDeviceAsync(item); + break; + case "HTTP": + // HTTP连接 + System.Diagnostics.Debug.WriteLine("[连接] 使用设备配置的HTTP协议"); + connected = await ConnectHttpDeviceAsync(item); + break; + default: + System.Diagnostics.Debug.WriteLine($"[连接] 未知协议类型: {item.ProtocolType}"); + break; + } + + item.IsConnected = connected; + System.Diagnostics.Debug.WriteLine($"[连接] 设备 {item.Name} 连接结果: {(connected ? "成功" : "失败")}"); + System.Diagnostics.Debug.WriteLine($"[连接] ========== 连接尝试结束 =========="); + } + } + catch (System.Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[连接] 设备 {item.Name} 连接异常: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"[连接] 异常堆栈: {ex.StackTrace}"); + item.IsConnected = false; + } + } + + /// + /// S7协议设备连接 + /// + private async Task ConnectS7DeviceAsync(ChamberMonitorItem item) + { + try + { + System.Diagnostics.Debug.WriteLine($"[ConnectS7DeviceAsync] 开始配置设备: IP={ConfigIpAddress}, CPU={ConfigCpuType}, Rack={ConfigRack}, Slot={ConfigSlot}"); + + _thc1100.ConfigureDevice(ConfigIpAddress, ConfigCpuType, ConfigRack, ConfigSlot); + bool result = await _thc1100.ConnectAsync(); + + System.Diagnostics.Debug.WriteLine($"[ConnectS7DeviceAsync] 连接结果: {result}, IsConnected属性值: {_thc1100.IsConnected}"); + + if (result) + { + System.Diagnostics.Debug.WriteLine("[ConnectS7DeviceAsync] 连接成功,启动定时刷新"); + StartPeriodicRefresh(); + } + + return result; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[ConnectS7DeviceAsync] 异常: {ex.Message}, 堆栈: {ex.StackTrace}"); + return false; + } + } + + /// + /// Modbus TCP设备连接 + /// + private async Task ConnectModbusTcpDeviceAsync(ChamberMonitorItem item) + { + try + { + // 配置UMC1300设备 + _umc1300.ConfigureDevice(ConfigIpAddress, ConfigPort, ConfigSendTimeout, ConfigReceiveTimeout); + bool result = await _umc1300.ConnectAsync(); + + if (result) + { + // 启动数据刷新 + StartPeriodicRefresh(); + } + + System.Diagnostics.Debug.WriteLine($"UMC1300 Modbus TCP连接: {ConfigIpAddress}:{ConfigPort}, 结果: {result}"); + return result; + } + catch (System.Exception ex) + { + System.Diagnostics.Debug.WriteLine($"UMC1300连接异常: {ex.Message}"); + return false; + } + } + + /// + /// Modbus RTU设备连接 + /// + private async Task ConnectModbusRtuDeviceAsync(ChamberMonitorItem item) + { + // 实现Modbus RTU连接逻辑 + System.Diagnostics.Debug.WriteLine($"Modbus RTU连接: {ConfigSerialPort}, 波特率:{ConfigBaudRate}"); + await Task.Delay(500); + return true; + } + + /// + /// TCP设备连接 + /// + private async Task ConnectTcpDeviceAsync(ChamberMonitorItem item) + { + // 实现TCP连接逻辑 + System.Diagnostics.Debug.WriteLine($"TCP连接: {ConfigIpAddress}:{ConfigPort}"); + await Task.Delay(500); + return true; + } + + /// + /// HTTP设备连接 + /// + private async Task ConnectHttpDeviceAsync(ChamberMonitorItem item) + { + // 实现HTTP连接逻辑 + System.Diagnostics.Debug.WriteLine($"HTTP连接: {ConfigIpAddress}:{ConfigPort}"); + await Task.Delay(500); + return true; + } + #endregion + + #region 生命周期管理 + /// + /// 导航离开时清理资源 + /// + public override void OnNavigatedFrom(NavigationContext navigationContext) + { + base.OnNavigatedFrom(navigationContext); + + ExecuteDisconnect(); + _isInitialized = false; + } + #endregion } /// /// 环境箱 UI 状态及温湿度数据绑定模型 /// - public class ChamberMonitorItem :BindableBase + public class ChamberMonitorItem : BindableBase { private double _temperature; private double _humidity; @@ -106,7 +777,18 @@ namespace MainModule.ViewModels public bool IsConnected { get => _isConnected; - set => SetProperty(ref _isConnected, value); + set + { + if (SetProperty(ref _isConnected, value)) + { + RaisePropertyChanged(nameof(ConnectButtonText)); + } + } } + + /// + /// 连接按钮文本 + /// + public string ConnectButtonText => IsConnected ? "断开连接" : "连接设备"; } -} \ No newline at end of file +} diff --git a/MainModule/Views/ConnectionConfigView.xaml b/MainModule/Views/ConnectionConfigView.xaml new file mode 100644 index 0000000..f3e896c --- /dev/null +++ b/MainModule/Views/ConnectionConfigView.xaml @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file +