diff --git a/BenchMovementModule/BenchMovementModule.cs b/BenchMovementModule/BenchMovementModule.cs index 6041442..8036819 100644 --- a/BenchMovementModule/BenchMovementModule.cs +++ b/BenchMovementModule/BenchMovementModule.cs @@ -1,5 +1,7 @@ using System.Reflection; using BenchMovementModule.Views; +using BenchMovementModule.Views.Dialogs; +using BenchMovementModule.ViewModels.Dialogs; namespace BenchMovementModule { @@ -14,6 +16,9 @@ namespace BenchMovementModule public void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation("BenchMovementView"); + + // 台架 TCP 连接配置弹窗 + containerRegistry.RegisterDialog("GantryConfigDialog"); } } } diff --git a/BenchMovementModule/HardwareDrive/GantryControlBase.cs b/BenchMovementModule/HardwareDrive/GantryControlBase.cs new file mode 100644 index 0000000..f18b94d --- /dev/null +++ b/BenchMovementModule/HardwareDrive/GantryControlBase.cs @@ -0,0 +1,548 @@ +using DeviceCommand.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Threading; + +namespace BenchMovementModule.HardwareDrive +{ + public abstract class GantryControlBase + { + protected readonly IModbusDevice _device; + protected readonly byte _slaveAddress; + + // ================================================================= + // ------------------ Coil / Bit 状态与控制 (位元件) ------------------ + // ================================================================= + protected const ushort ADDR_M_ESTOP = 20496; // 0x5010: 紧停 + protected const ushort ADDR_M_X_STOP = 41; // 0x29: X轴停止 + protected const ushort ADDR_M_Y_STOP = 42; // 0x2A: Y轴停止 + protected const ushort ADDR_M_Z_STOP = 43; // 0x2B: Z轴停止 + + protected const ushort ADDR_M_X_START = 30; // 0x1E: X轴指定位置启动 + protected const ushort ADDR_M_Y_START = 31; // 0x1F: Y轴指定位置启动 + protected const ushort ADDR_M_Z_START = 35; // 0x23: Z轴指定位置启动 + + protected const ushort ADDR_M_HOME_TRIGGER = 100; // 0x64: 回原点触发 + + // ================================================================= + // ------------------ 轴使能控制 (HM 寄存器) ------------------ + // ================================================================= + protected const ushort ADDR_HM_AXIS1_ENABLE = 49408; // 0xC100: 轴1使能 (X轴) + protected const ushort ADDR_HM_AXIS2_ENABLE = 49409; // 0xC101: 轴2使能 (Y轴) + protected const ushort ADDR_HM_AXIS3_ENABLE = 49410; // 0xC102: 轴3使能 (Z轴) + protected const ushort ADDR_HM_AXIS4_ENABLE = 49411; // 0xC103: 轴4使能 + protected const ushort ADDR_HM_AXIS5_ENABLE = 49412; // 0xC104: 轴5使能 + protected const ushort ADDR_HM_AXIS6_ENABLE = 49413; // 0xC105: 轴6使能 + + // ================================================================= + // ------------------ 速度与位置参数 (HD 32位数据寄存器) -------------- + // ================================================================= + protected const ushort ADDR_HD_HOME_POS_SPEED = 41288; // 0xA148: 原点定位速度 (32位) + + // 轴原点位置 + protected const ushort ADDR_HD_X_HOME_POS = 41198; // 0xA0EE: X轴原点位 (32位) + protected const ushort ADDR_HD_Z_HOME_POS = 41208; // 0xA0F8: Z轴原点位 (32位) + protected const ushort ADDR_HD_Y_HOME_POS = 41218; // 0xA102: Y轴原点位 (32位) + + // 轴运行目标位置 + protected const ushort ADDR_HD_Y_TARGET = 41228; // 0xA10C: Y轴指定位置 (32位) + protected const ushort ADDR_HD_Z_TARGET = 41238; // 0xA116: Z轴指定位置 (32位) + protected const ushort ADDR_HD_X_TARGET = 41248; // 0xA120: X轴指定位置 (32位) + + // 轴运行速度 + protected const ushort ADDR_HD_X_SPEED = 41318; // 0xA166: X轴速度 (32位) + protected const ushort ADDR_HD_Z_SPEED = 41328; // 0xA170: Z轴速度 (32位) + protected const ushort ADDR_HD_Y_SPEED = 41338; // 0xA17A: Y轴速度 (32位) + + // ================================================================= + // ------------------ 轴绝对位置(只读,相对原点)------------------ + // ================================================================= + protected const ushort ADDR_ABS_AXIS1_POS = 47232; // 轴1绝对位置 (32位) + protected const ushort ADDR_ABS_AXIS2_POS = 47236; // 轴2绝对位置 (32位) + protected const ushort ADDR_ABS_AXIS3_POS = 47240; // 轴3绝对位置 (32位) + protected const ushort ADDR_ABS_AXIS4_POS = 47244; // 轴4绝对位置 (32位) + protected const ushort ADDR_ABS_AXIS5_POS = 47248; // 轴5绝对位置 (32位) + protected const ushort ADDR_ABS_AXIS6_POS = 47252; // 轴6绝对位置 (32位) + + public bool IsConnected => _device?.IsConnected ?? false; + + protected GantryControlBase(IModbusDevice device, byte slaveAddress = 1) + { + _device = device ?? throw new ArgumentNullException(nameof(device)); + _slaveAddress = slaveAddress; + } + + public async Task ConnectAsync(CancellationToken ct = default) + { + return await _device.ConnectAsync(ct); + } + + public void Close() + { + _device.Close(); + } + + // ================================================================= + // ------------------ 核心控制逻辑与参数读写 ------------------------ + // ================================================================= + + /// + /// 轴使能控制 + /// + /// 轴编号 (1 - 6) + public async Task SetAxisEnableAsync(int axis, bool enable, CancellationToken ct = default) + { + ushort address = axis switch + { + 1 => ADDR_HM_AXIS1_ENABLE, + 2 => ADDR_HM_AXIS2_ENABLE, + 3 => ADDR_HM_AXIS3_ENABLE, + 4 => ADDR_HM_AXIS4_ENABLE, + 5 => ADDR_HM_AXIS5_ENABLE, + 6 => ADDR_HM_AXIS6_ENABLE, + _ => throw new ArgumentException("轴通道错误,仅支持 1 - 6", nameof(axis)) + }; + + await _device.WriteSingleCoilAsync(_slaveAddress, address, enable, ct); + } + + /// + /// 读取指定轴的使能状态 + /// + /// 1: X轴, 2: Y轴, 3: Z轴 + public async Task GetAxisEnableAsync(int axis, CancellationToken ct = default) + { + ushort address = axis switch + { + 1 => ADDR_HM_AXIS1_ENABLE, + 2 => ADDR_HM_AXIS2_ENABLE, + 3 => ADDR_HM_AXIS3_ENABLE, + _ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis)) + }; + + bool[] coils = await _device.ReadCoilsAsync(_slaveAddress, address, 1, ct); + return coils != null && coils.Length > 0 && coils[0]; + } + + /// + /// 同时设置 X/Y/Z 轴的原点位置(带各自独立的范围检查) + /// + /// X轴原点位置 (范围: 0 ~ 50000) + /// Y轴原点位置 (范围: 0 ~ 50000) + /// Z轴原点位置 (范围: 0 ~ 20000) + public async Task SetHomePositionAsync(int xPos, int yPos, int zPos, CancellationToken ct = default) + { + if (xPos < 0 || xPos > 50000) + throw new ArgumentOutOfRangeException(nameof(xPos), $"X轴原点设置值 [{xPos}] 超出合法范围 (0 - 50000)"); + + if (yPos < 0 || yPos > 50000) + throw new ArgumentOutOfRangeException(nameof(yPos), $"Y轴原点设置值 [{yPos}] 超出合法范围 (0 - 50000)"); + + if (zPos < 0 || zPos > 20000) + throw new ArgumentOutOfRangeException(nameof(zPos), $"Z轴原点设置值 [{zPos}] 超出合法范围 (0 - 20000)"); + + await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_X_HOME_POS, Int32ToUshorts(xPos), ct); + await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Y_HOME_POS, Int32ToUshorts(yPos), ct); + await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Z_HOME_POS, Int32ToUshorts(zPos), ct); + } + + /// + /// 批量查询当前 X、Y、Z 三轴设定的原点位置值 + /// + /// 返回包含三轴原点位置的元组 (X, Y, Z) + public async Task<(int X, int Y, int Z)> GetHomePositionAsync(CancellationToken ct = default) + { + // 41218(Y) - 41198(X) = 20,加Y轴自身的 2 个寄存器,共批量读取 22 个连续寄存器 + ushort[] registers = await _device.ReadHoldingRegistersAsync(_slaveAddress, ADDR_HD_X_HOME_POS, 22, ct); + + if (registers == null || registers.Length < 22) + throw new InvalidOperationException("从 PLC 读取三轴原点位置失败,返回数据长度不足。"); + + int xHomePos = UshortsToInt32(new ushort[] { registers[0], registers[1] }); + int zHomePos = UshortsToInt32(new ushort[] { registers[10], registers[11] }); + int yHomePos = UshortsToInt32(new ushort[] { registers[20], registers[21] }); + + return (xHomePos, yHomePos, zHomePos); + } + + /// + /// 设置回原点定位速度(带 0 - 100000 范围检查) + /// + public async Task SetHomePositionSpeedAsync(int speed, CancellationToken ct = default) + { + if (speed < 0 || speed > 100000) + throw new ArgumentOutOfRangeException(nameof(speed), $"回原点定位速度值 [{speed}] 超出合法范围 (0 - 100000)"); + + await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_HOME_POS_SPEED, Int32ToUshorts(speed), ct); + } + + /// + /// 查询当前设定的回原点定位速度 + /// + public async Task GetHomePositionSpeedAsync(CancellationToken ct = default) + { + ushort[] registers = await _device.ReadHoldingRegistersAsync(_slaveAddress, ADDR_HD_HOME_POS_SPEED, 2, ct); + + if (registers == null || registers.Length < 2) + throw new InvalidOperationException("从 PLC 读取回原点定位速度失败,返回数据长度不足。"); + + return UshortsToInt32(registers); + } + + /// + /// 设置指定轴的运行速度(带 0 - 100000 范围检查) + /// + /// 1: X轴, 2: Y轴, 3: Z轴 + public async Task SetAxisSpeedAsync(int axis, int speed, CancellationToken ct = default) + { + if (speed < 0 || speed > 100000) + throw new ArgumentOutOfRangeException(nameof(speed), $"轴速度设置值 [{speed}] 超出合法范围 (0 - 100000)"); + + ushort address = axis switch + { + 1 => ADDR_HD_X_SPEED, + 2 => ADDR_HD_Y_SPEED, + 3 => ADDR_HD_Z_SPEED, + _ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis)) + }; + + await _device.WriteMultipleRegistersAsync(_slaveAddress, address, Int32ToUshorts(speed), ct); + } + + /// + /// 读取指定轴当前设定的运行速度 + /// + public async Task GetAxisSpeedAsync(int axis, CancellationToken ct = default) + { + ushort address = axis switch + { + 1 => ADDR_HD_X_SPEED, + 2 => ADDR_HD_Y_SPEED, + 3 => ADDR_HD_Z_SPEED, + _ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis)) + }; + + ushort[] registers = await _device.ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct); + if (registers == null || registers.Length < 2) + throw new InvalidOperationException($"从 PLC 读取轴 {axis} 速度失败。"); + + return UshortsToInt32(registers); + } + + /// + /// 写入指定轴的目标位置(动态安全校验:目标位置不能超过 [轴最大物理极限 - 当前轴原点设置值]) + /// + public async Task SetAxisTargetPositionAsync(int axis, int targetPos, CancellationToken ct = default) + { + var (xHome, yHome, zHome) = await GetHomePositionAsync(ct); + + ushort targetAddress; + int maxLimit; + int currentHomePos; + + switch (axis) + { + case 1: + targetAddress = ADDR_HD_X_TARGET; + maxLimit = 50000; + currentHomePos = xHome; + break; + case 2: + targetAddress = ADDR_HD_Y_TARGET; + maxLimit = 50000; + currentHomePos = yHome; + break; + case 3: + targetAddress = ADDR_HD_Z_TARGET; + maxLimit = 20000; + currentHomePos = zHome; + break; + default: + throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis)); + } + + int allowedMaxLimit = maxLimit - currentHomePos; + + if (targetPos < 0 || targetPos > allowedMaxLimit) + { + throw new ArgumentOutOfRangeException( + nameof(targetPos), + $"轴 {axis} 目标位置 [{targetPos}] 不合法!当前原点为 [{currentHomePos}],允许的有效写入范围为: 0 - {allowedMaxLimit}" + ); + } + + await _device.WriteMultipleRegistersAsync(_slaveAddress, targetAddress, Int32ToUshorts(targetPos), ct); + } + + /// + /// 读取指定轴当前设定的目标指定位置 + /// + public async Task GetAxisTargetPositionAsync(int axis, CancellationToken ct = default) + { + ushort address = axis switch + { + 1 => ADDR_HD_X_TARGET, + 2 => ADDR_HD_Y_TARGET, + 3 => ADDR_HD_Z_TARGET, + _ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis)) + }; + + ushort[] registers = await _device.ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct); + + if (registers == null || registers.Length < 2) + throw new InvalidOperationException($"从 PLC 读取轴 {axis} 目标位置失败。"); + + return UshortsToInt32(registers); + } + + /// + /// 设置系统紧停锁定状态 (true: 锁定激活, false: 解除锁定) + /// + public async Task SetEStopStateAsync(bool state, CancellationToken ct = default) + { + await _device.WriteSingleCoilAsync(_slaveAddress, ADDR_M_ESTOP, state, ct); + } + + /// + /// 设置指定轴的停止控制状态 (true: 持续输出停止信号, false: 释放停止信号) + /// + public async Task SetStopStateAsync(int axis, bool state, CancellationToken ct = default) + { + ushort address = axis switch + { + 1 => ADDR_M_X_STOP, + 2 => ADDR_M_Y_STOP, + 3 => ADDR_M_Z_STOP, + _ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis)) + }; + + await _device.WriteSingleCoilAsync(_slaveAddress, address, state, ct); + } + + /// + /// 全局回零(安全校验版:先检查回原点速度是否为 0,为0则拒绝触发) + /// + public async Task HomeAsync(CancellationToken ct = default) + { + int currentSpeed = await GetHomePositionSpeedAsync(ct); + + if (currentSpeed == 0) + throw new InvalidOperationException("无法触发回原点!当前回原点定位速度为 0,请先设置合法的回零速度。"); + + await TriggerCoilAsync(ADDR_M_HOME_TRIGGER, ct); + } + + /// + /// 单轴指定位置启动(含速度判定、使能自动补齐、动作目标位置独立范围校验) + /// + /// 1: X轴, 2: Y轴, 3: Z轴 + public async Task StartSingleAxisAsync(int axis, CancellationToken ct = default) + { + ushort startAddress; + int maxLimit; + int currentHomePos; + + // 1. 获取基础参数与校验边界 + var (xHome, yHome, zHome) = await GetHomePositionAsync(ct); + switch (axis) + { + case 1: + startAddress = ADDR_M_X_START; + maxLimit = 50000; + currentHomePos = xHome; + break; + case 2: + startAddress = ADDR_M_Y_START; + maxLimit = 50000; + currentHomePos = yHome; + break; + case 3: + startAddress = ADDR_M_Z_START; + maxLimit = 20000; + currentHomePos = zHome; + break; + default: + throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis)); + } + + // 2. 检查动作轴的速度 + int currentSpeed = await GetAxisSpeedAsync(axis, ct); + if (currentSpeed == 0) + throw new InvalidOperationException($"轴 {axis} 无法启动!当前设定速度为 0。"); + + // 3. 检查动作轴使能,若为 false 则自动补写 true 激活驱动器 + bool isEnabled = await GetAxisEnableAsync(axis, ct); + if (!isEnabled) + { + await SetAxisEnableAsync(axis, true, ct); + await Task.Delay(100, ct); // 等待硬件继电器响应与伺服驱动就绪 + } + + // 4. 精准独立校验当前需要动作轴的目标定位是否越界 + int currentTarget = await GetAxisTargetPositionAsync(axis, ct); + int allowedMaxLimit = maxLimit - currentHomePos; + if (currentTarget < 0 || currentTarget > allowedMaxLimit) + { + throw new ArgumentOutOfRangeException($"轴 {axis} 无法启动!当前指定目标位置 [{currentTarget}] 超过了允许的最大安全极限值 [{allowedMaxLimit}]。"); + } + + // 5. 校验完成,边缘触发点动启动 + await TriggerCoilAsync(startAddress, ct); + } + + /// + /// 三轴同时启动(全开:严格校验全部运行轴速度、批量检测与补正1~6号轴使能、联动校验三轴目标范围极限) + /// + public async Task StartAllAxesAsync(CancellationToken ct = default) + { + // 1. 一次性读取三轴速度,严禁任何一轴速度为 0 + int xSpeed = await GetAxisSpeedAsync(1, ct); + int ySpeed = await GetAxisSpeedAsync(2, ct); + int zSpeed = await GetAxisSpeedAsync(3, ct); + + if (xSpeed == 0 || ySpeed == 0 || zSpeed == 0) + throw new InvalidOperationException($"无法全开启动!存在运行速度为 0 的轴。当前速度 -> X:{xSpeed}, Y:{ySpeed}, Z:{zSpeed}"); + + // 2. 批量读取 1 ~ 6 号轴连续的使能状态线圈,极大地优化网络性能 + bool[] enableStates = await _device.ReadCoilsAsync(_slaveAddress, ADDR_HM_AXIS1_ENABLE, 6, ct); + if (enableStates == null || enableStates.Length < 6) + throw new InvalidOperationException("从 PLC 读取 1~6 号轴使能状态失败。"); + + bool hasModifiedEnable = false; + ushort[] enableAddresses = { ADDR_HM_AXIS1_ENABLE, ADDR_HM_AXIS2_ENABLE, ADDR_HM_AXIS3_ENABLE, ADDR_HM_AXIS4_ENABLE, ADDR_HM_AXIS5_ENABLE, ADDR_HM_AXIS6_ENABLE }; + + for (int i = 0; i < 6; i++) + { + if (!enableStates[i]) + { + await _device.WriteSingleCoilAsync(_slaveAddress, enableAddresses[i], true, ct); + hasModifiedEnable = true; + } + } + + // 如果执行过使能补齐补齐写入,在循环外进行统一延时,保证PLC时序安全 + if (hasModifiedEnable) + await Task.Delay(100, ct); + + // 3. 获取三轴原点及当前设定的目标指定位置 + var (xHome, yHome, zHome) = await GetHomePositionAsync(ct); + + int xTarget = await GetAxisTargetPositionAsync(1, ct); + int yTarget = await GetAxisTargetPositionAsync(2, ct); + int zTarget = await GetAxisTargetPositionAsync(3, ct); + + // X轴安全范围联动校验 + int xMaxAllowed = 50000 - xHome; + if (xTarget < 0 || xTarget > xMaxAllowed) + throw new ArgumentOutOfRangeException(nameof(xTarget), $"X轴目标位置 [{xTarget}] 越界,当前原点下最大范围 0-{xMaxAllowed}"); + + // Y轴安全范围联动校验 + int yMaxAllowed = 50000 - yHome; + if (yTarget < 0 || yTarget > yMaxAllowed) + throw new ArgumentOutOfRangeException(nameof(yTarget), $"Y轴目标位置 [{yTarget}] 越界,当前原点下最大范围 0-{yMaxAllowed}"); + + // Z轴安全范围联动校验 + int zMaxAllowed = 20000 - zHome; + if (zTarget < 0 || zTarget > zMaxAllowed) + throw new ArgumentOutOfRangeException(nameof(zTarget), $"Z轴目标位置 [{zTarget}] 越界,当前原点下最大范围 0-{zMaxAllowed}"); + + // 4. 全部联动校验通过,三轴点动同步启动 + await _device.WriteSingleCoilAsync(_slaveAddress, ADDR_M_X_START, true, ct); + await _device.WriteSingleCoilAsync(_slaveAddress, ADDR_M_Y_START, true, ct); + await _device.WriteSingleCoilAsync(_slaveAddress, ADDR_M_Z_START, true, ct); + + await Task.Delay(100, ct); // 保持高电平状态以适配 PLC 扫描周期 + + await _device.WriteSingleCoilAsync(_slaveAddress, ADDR_M_X_START, false, ct); + await _device.WriteSingleCoilAsync(_slaveAddress, ADDR_M_Y_START, false, ct); + await _device.WriteSingleCoilAsync(_slaveAddress, ADDR_M_Z_START, false, ct); + } + + #region 内部高低字工具转化方法 + + /// + /// 触发点动脉冲信号(置 1 后,等待 100ms 自动置 0 释放) + /// + protected async Task TriggerCoilAsync(ushort address, CancellationToken ct = default) + { + await _device.WriteSingleCoilAsync(_slaveAddress, address, true, ct); + await Task.Delay(100, ct); + await _device.WriteSingleCoilAsync(_slaveAddress, address, false, ct); + } + + /// + /// 将 32 位整型数据转换为 Modbus 的 2 个 16 位无符号整数(低字在前 CDAB 格式) + /// + protected ushort[] Int32ToUshorts(int value) + { + ushort lowWord = (ushort)(value & 0xFFFF); + ushort highWord = (ushort)((value >> 16) & 0xFFFF); + return new ushort[] { lowWord, highWord }; + } + + /// + /// 将 Modbus 的 2 个 16 位无符号整数转换为 32 位整型(低字在前 CDAB 格式) + /// + protected int UshortsToInt32(ushort[] registers) + { + uint lowWord = registers[0]; + uint highWord = registers[1]; + return (int)((highWord << 16) | lowWord); + } + + #endregion + + #region 轴绝对位置读取(只读) + + /// + /// 读取指定轴的绝对位置(相对原点的实时位置,只读) + /// + /// 轴编号 (1-6) + public async Task GetAbsolutePositionAsync(int axis, CancellationToken ct = default) + { + ushort address = axis switch + { + 1 => ADDR_ABS_AXIS1_POS, + 2 => ADDR_ABS_AXIS2_POS, + 3 => ADDR_ABS_AXIS3_POS, + 4 => ADDR_ABS_AXIS4_POS, + 5 => ADDR_ABS_AXIS5_POS, + 6 => ADDR_ABS_AXIS6_POS, + _ => throw new ArgumentException("轴编号错误,仅支持 1-6", nameof(axis)) + }; + + ushort[] registers = await _device.ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct); + if (registers == null || registers.Length < 2) + throw new InvalidOperationException($"读取轴 {axis} 绝对位置失败。"); + + return UshortsToInt32(registers); + } + + /// + /// 批量读取 1-6 轴的绝对位置(一次性读取 22 个寄存器,高效) + /// + /// 返回 6 轴绝对位置元组 + public async Task<(int Axis1, int Axis2, int Axis3, int Axis4, int Axis5, int Axis6)> GetAllAbsolutePositionsAsync(CancellationToken ct = default) + { + // 从 47232 开始连续读取 22 个寄存器(覆盖 47232-47253) + ushort[] registers = await _device.ReadHoldingRegistersAsync(_slaveAddress, ADDR_ABS_AXIS1_POS, 22, ct); + if (registers == null || registers.Length < 22) + throw new InvalidOperationException("批量读取 6 轴绝对位置失败,返回数据长度不足。"); + + // 每轴间隔 4 个寄存器(2个数据 + 2个间隙),提取各轴数据 + int axis1 = UshortsToInt32(new ushort[] { registers[0], registers[1] }); + int axis2 = UshortsToInt32(new ushort[] { registers[4], registers[5] }); + int axis3 = UshortsToInt32(new ushort[] { registers[8], registers[9] }); + int axis4 = UshortsToInt32(new ushort[] { registers[12], registers[13] }); + int axis5 = UshortsToInt32(new ushort[] { registers[16], registers[17] }); + int axis6 = UshortsToInt32(new ushort[] { registers[20], registers[21] }); + + return (axis1, axis2, axis3, axis4, axis5, axis6); + } + + #endregion + } +} \ No newline at end of file diff --git a/BenchMovementModule/HardwareDrive/GantryControlTcp.cs b/BenchMovementModule/HardwareDrive/GantryControlTcp.cs new file mode 100644 index 0000000..dae9bd9 --- /dev/null +++ b/BenchMovementModule/HardwareDrive/GantryControlTcp.cs @@ -0,0 +1,27 @@ +using DeviceCommand.Base; +using Model.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BenchMovementModule.HardwareDrive +{ + public class GantryControlTcp : GantryControlBase + { + // 暴露出底层的 ModbusTcp 实例以便修改网络配置 + public ModbusTcp TcpDevice => (ModbusTcp)_device; + + public GantryControlTcp(TcpConfig config, byte slaveAddress = 1) + : base(new ModbusTcp(config), slaveAddress) + { + } + + public GantryControlTcp(string ipAddress, int port = 502, byte slaveAddress = 1) + : base(new ModbusTcp(), slaveAddress) + { + TcpDevice.ConfigureDevice(ipAddress, port); + } + } +} diff --git a/BenchMovementModule/ViewModels/BenchMovementViewModel.cs b/BenchMovementModule/ViewModels/BenchMovementViewModel.cs index 3772e97..21f87a7 100644 --- a/BenchMovementModule/ViewModels/BenchMovementViewModel.cs +++ b/BenchMovementModule/ViewModels/BenchMovementViewModel.cs @@ -1,413 +1,485 @@ using System; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Input; +using System.Windows.Threading; +using BenchMovementModule.HardwareDrive; +using Logger; using UIShare.ViewModelBase; namespace BenchMovementModule.ViewModels { public class BenchMovementViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable { - #region 属性与通知字段 + #region 属性 public bool KeepAlive => true; - // --- 1. 台架位置区 --- - private double _posX; - public double PosX + // ===== GantryControlTcp 实例 ===== + private GantryControlTcp? _gantry; + + // ===== 连接状态 ===== + private bool _isConnected; + public bool IsConnected { - get => _posX; - set => SetProperty(ref _posX, value); + get => _isConnected; + set => SetProperty(ref _isConnected, value); } - private double _posY; - public double PosY + private string _connectionStatus = "未连接"; + public string ConnectionStatus { - get => _posY; - set => SetProperty(ref _posY, value); + get => _connectionStatus; + set => SetProperty(ref _connectionStatus, value); } - private double _posZ; - public double PosZ - { - get => _posZ; - set => SetProperty(ref _posZ, value); - } + // ===== 6轴绝对位置显示(相对原点的实时位置,只读)===== + private int _absPos1; + public int AbsPos1 { get => _absPos1; set => SetProperty(ref _absPos1, value); } - // --- 2. 信号回读区 --- - private double _pos1X; - public double Pos1X - { - get => _pos1X; - set => SetProperty(ref _pos1X, value); - } + private int _absPos2; + public int AbsPos2 { get => _absPos2; set => SetProperty(ref _absPos2, value); } - private int _pos1Quality; - public int Pos1Quality - { - get => _pos1Quality; - set => SetProperty(ref _pos1Quality, value); - } + private int _absPos3; + public int AbsPos3 { get => _absPos3; set => SetProperty(ref _absPos3, value); } - private double _pos2X; - public double Pos2X - { - get => _pos2X; - set => SetProperty(ref _pos2X, value); - } + private int _absPos4; + public int AbsPos4 { get => _absPos4; set => SetProperty(ref _absPos4, value); } - private int _pos2Quality; - public int Pos2Quality - { - get => _pos2Quality; - set => SetProperty(ref _pos2Quality, value); - } + private int _absPos5; + public int AbsPos5 { get => _absPos5; set => SetProperty(ref _absPos5, value); } - private double _pos1Y; - public double Pos1Y - { - get => _pos1Y; - set => SetProperty(ref _pos1Y, value); - } + private int _absPos6; + public int AbsPos6 { get => _absPos6; set => SetProperty(ref _absPos6, value); } - private double _pos2Y; - public double Pos2Y - { - get => _pos2Y; - set => SetProperty(ref _pos2Y, value); - } + // ===== 寸动目标位置 ===== + private int _targetX; + public int TargetX { get => _targetX; set => SetProperty(ref _targetX, value); } - // --- 3. 扫描参数区 --- - private double _scanStepX = 10; - public double ScanStepX - { - get => _scanStepX; - set => SetProperty(ref _scanStepX, value); - } + private int _targetY; + public int TargetY { get => _targetY; set => SetProperty(ref _targetY, value); } - private double _scanStartX = -120; - public double ScanStartX - { - get => _scanStartX; - set => SetProperty(ref _scanStartX, value); - } + private int _targetZ; + public int TargetZ { get => _targetZ; set => SetProperty(ref _targetZ, value); } - private double _scanEndX = 120; - public double ScanEndX - { - get => _scanEndX; - set => SetProperty(ref _scanEndX, value); - } + // ===== 扫描参数 ===== + private int _scanStepX = 1000; + public int ScanStepX { get => _scanStepX; set => SetProperty(ref _scanStepX, value); } - private double _scanStepY = 10; - public double ScanStepY - { - get => _scanStepY; - set => SetProperty(ref _scanStepY, value); - } + private int _scanStartX; + public int ScanStartX { get => _scanStartX; set => SetProperty(ref _scanStartX, value); } - private double _scanStartY = -120; - public double ScanStartY - { - get => _scanStartY; - set => SetProperty(ref _scanStartY, value); - } + private int _scanEndX = 50000; + public int ScanEndX { get => _scanEndX; set => SetProperty(ref _scanEndX, value); } - private double _scanEndY = 120; - public double ScanEndY - { - get => _scanEndY; - set => SetProperty(ref _scanEndY, value); - } + private int _scanStepY = 1000; + public int ScanStepY { get => _scanStepY; set => SetProperty(ref _scanStepY, value); } - private double _scanStepZ = 1; - public double ScanStepZ - { - get => _scanStepZ; - set => SetProperty(ref _scanStepZ, value); - } + private int _scanStartY; + public int ScanStartY { get => _scanStartY; set => SetProperty(ref _scanStartY, value); } - private double _scanStartZ = 0; - public double ScanStartZ - { - get => _scanStartZ; - set => SetProperty(ref _scanStartZ, value); - } + private int _scanEndY = 50000; + public int ScanEndY { get => _scanEndY; set => SetProperty(ref _scanEndY, value); } - private double _scanEndZ = 0; - public double ScanEndZ - { - get => _scanEndZ; - set => SetProperty(ref _scanEndZ, value); - } + private int _scanStepZ = 100; + public int ScanStepZ { get => _scanStepZ; set => SetProperty(ref _scanStepZ, value); } - // --- 4. 寸动参数区 --- - private double _jogX; - public double JogX - { - get => _jogX; - set => SetProperty(ref _jogX, value); - } + private int _scanStartZ; + public int ScanStartZ { get => _scanStartZ; set => SetProperty(ref _scanStartZ, value); } - private double _jogY; - public double JogY - { - get => _jogY; - set => SetProperty(ref _jogY, value); - } + private int _scanEndZ = 20000; + public int ScanEndZ { get => _scanEndZ; set => SetProperty(ref _scanEndZ, value); } - private double _jogZ; - public double JogZ - { - get => _jogZ; - set => SetProperty(ref _jogZ, value); - } + // ===== 速度设定(脉冲)===== + private int _speedX = 5000; + public int SpeedX { get => _speedX; set => SetProperty(ref _speedX, value); } - // --- 5. 设置页:网络及PLC参数 --- - private string _plcIp = "192.168.0.30"; - public string PlcIp - { - get => _plcIp; - set => SetProperty(ref _plcIp, value); - } + private int _speedY = 5000; + public int SpeedY { get => _speedY; set => SetProperty(ref _speedY, value); } - private int _plcPort = 502; - public int PlcPort - { - get => _plcPort; - set => SetProperty(ref _plcPort, value); - } + private int _speedZ = 2000; + public int SpeedZ { get => _speedZ; set => SetProperty(ref _speedZ, value); } - // --- 6. 设置页:速度与零点设定 --- - private double _speedX; - public double SpeedX - { - get => _speedX; - set => SetProperty(ref _speedX, value); - } + private int _homeSpeed = 3000; + public int HomeSpeed { get => _homeSpeed; set => SetProperty(ref _homeSpeed, value); } - private double _speedY; - public double SpeedY - { - get => _speedY; - set => SetProperty(ref _speedY, value); - } + // ===== 零点设定 ===== + private int _homeX; + public int HomeX { get => _homeX; set => SetProperty(ref _homeX, value); } - private double _speedZ; - public double SpeedZ - { - get => _speedZ; - set => SetProperty(ref _speedZ, value); - } + private int _homeY; + public int HomeY { get => _homeY; set => SetProperty(ref _homeY, value); } - private double _zeroX; - public double ZeroX - { - get => _zeroX; - set => SetProperty(ref _zeroX, value); - } - - private double _zeroY; - public double ZeroY - { - get => _zeroY; - set => SetProperty(ref _zeroY, value); - } - - private double _zeroZ; - public double ZeroZ - { - get => _zeroZ; - set => SetProperty(ref _zeroZ, value); - } - - // --- 7. 设置页:CAN设定信号映射 --- - private int _canChannel = 0; - public int CanChannel - { - get => _canChannel; - set => SetProperty(ref _canChannel, value); - } - - private string _canMsgId = "0X0"; - public string CanMsgId - { - get => _canMsgId; - set => SetProperty(ref _canMsgId, value); - } - - private string _canMsgName = string.Empty; - public string CanMsgName - { - get => _canMsgName; - set => SetProperty(ref _canMsgName, value); - } - - private string _sigPos1X = string.Empty; - public string SigPos1X - { - get => _sigPos1X; - set => SetProperty(ref _sigPos1X, value); - } - - private string _sigPos2X = string.Empty; - public string SigPos2X - { - get => _sigPos2X; - set => SetProperty(ref _sigPos2X, value); - } - - private string _sigPos1Y = string.Empty; - public string SigPos1Y - { - get => _sigPos1Y; - set => SetProperty(ref _sigPos1Y, value); - } - - private string _sigPos2Y = string.Empty; - public string SigPos2Y - { - get => _sigPos2Y; - set => SetProperty(ref _sigPos2Y, value); - } - - private string _sigPos1Quality = string.Empty; - public string SigPos1Quality - { - get => _sigPos1Quality; - set => SetProperty(ref _sigPos1Quality, value); - } - - private string _sigPos2Quality = string.Empty; - public string SigPos2Quality - { - get => _sigPos2Quality; - set => SetProperty(ref _sigPos2Quality, value); - } + private int _homeZ; + public int HomeZ { get => _homeZ; set => SetProperty(ref _homeZ, value); } #endregion #region 命令 - // 扫描控制面板命令 - public ICommand StartScanCommand { get; } - public ICommand StopScanCommand { get; } - public ICommand GoHomeCommand { get; } - - // 寸动执行单轴移动 + public ICommand OpenConfigCommand { get; } + public ICommand ConnectCommand { get; } + public ICommand DisconnectCommand { get; } public ICommand MoveXCommand { get; } public ICommand MoveYCommand { get; } public ICommand MoveZCommand { get; } - - // 全局控制 + public ICommand MoveAllCommand { get; } + public ICommand GoHomeCommand { get; } public ICommand EmergencyStopCommand { get; } - - // 参数设置应用命令 - public ICommand ApplyNetworkCommand { get; } + public ICommand StopXCommand { get; } + public ICommand StopYCommand { get; } + public ICommand StopZCommand { get; } public ICommand SetSpeedCommand { get; } - public ICommand SetZeroCommand { get; } + public ICommand SetHomeCommand { get; } + public ICommand ReadStatusCommand { get; } #endregion #region 私有字段 - private readonly IContainerProvider _containerProvider; + private readonly DispatcherTimer _pollTimer; private bool _isInitiated = false; - private string _testStatus = string.Empty; #endregion public BenchMovementViewModel(IContainerProvider containerProvider) : base(containerProvider) { - _containerProvider = containerProvider; + // 创建 GantryControlTcp 实例(默认配置,用户可通过弹窗修改) + _gantry = new GantryControlTcp("192.168.0.30", 502); - // 初始化扫描控制命令绑定 - StartScanCommand = new DelegateCommand(OnStartScan); - StopScanCommand = new DelegateCommand(OnStopScan); - GoHomeCommand = new DelegateCommand(OnGoHome); - - // 初始化寸动命令绑定 + OpenConfigCommand = new DelegateCommand(OnOpenConfig); + ConnectCommand = new DelegateCommand(OnConnect); + DisconnectCommand = new DelegateCommand(OnDisconnect); MoveXCommand = new DelegateCommand(OnMoveX); MoveYCommand = new DelegateCommand(OnMoveY); MoveZCommand = new DelegateCommand(OnMoveZ); - - // 急停与设置保存 + MoveAllCommand = new DelegateCommand(OnMoveAll); + GoHomeCommand = new DelegateCommand(OnGoHome); EmergencyStopCommand = new DelegateCommand(OnEmergencyStop); - ApplyNetworkCommand = new DelegateCommand(OnApplyNetwork); + StopXCommand = new DelegateCommand(OnStopX); + StopYCommand = new DelegateCommand(OnStopY); + StopZCommand = new DelegateCommand(OnStopZ); SetSpeedCommand = new DelegateCommand(OnSetSpeed); - SetZeroCommand = new DelegateCommand(OnSetZero); + SetHomeCommand = new DelegateCommand(OnSetHome); + ReadStatusCommand = new DelegateCommand(OnReadStatus); + + // 位置轮询定时器(500ms) + _pollTimer = new DispatcherTimer(DispatcherPriority.Background) + { + Interval = TimeSpan.FromMilliseconds(500) + }; + _pollTimer.Tick += OnPollTick; } - #region 命令处理方法 + #region 命令处理 - private void OnStartScan() + private void OnOpenConfig() { - // TODO: 等协议到了处理开始扫描 + var param = new DialogParameters(); + param.Add("Gantry", _gantry); + _dialogService.ShowDialog("GantryConfigDialog", param, result => + { + UpdateConnectionStatus(); + }); } - private void OnStopScan() + private async void OnConnect() { - // TODO: 等协议到了处理停止扫描 + if (_gantry == null) return; + try + { + ConnectionStatus = "连接中..."; + bool ok = await _gantry.ConnectAsync(); + if (ok) + { + IsConnected = true; + ConnectionStatus = "已连接"; + _pollTimer.Start(); + await ReadDeviceParametersAsync(); + LoggerHelper.Info("[BenchMovement] 台架连接成功"); + } + else + { + ConnectionStatus = "连接失败"; + } + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] 连接失败: {ex.Message}"); + ConnectionStatus = $"连接失败: {ex.Message}"; + } + UpdateConnectionStatus(); } - private void OnGoHome() + private void OnDisconnect() { - // TODO: 等协议到了处理台架回原点逻辑 + if (_gantry == null) return; + try + { + _pollTimer.Stop(); + _gantry.Close(); + IsConnected = false; + ConnectionStatus = "未连接"; + LoggerHelper.Info("[BenchMovement] 台架已断开"); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] 断开失败: {ex.Message}"); + } + UpdateConnectionStatus(); } - private void OnMoveX() + private async void OnMoveX() { - // TODO: 执行单轴X向绝对位置寸动 + if (!EnsureConnected()) return; + try + { + await _gantry!.SetAxisTargetPositionAsync(1, TargetX); + await _gantry.StartSingleAxisAsync(1); + LoggerHelper.Info($"[BenchMovement] X轴移动到 {TargetX}"); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] X轴移动失败: {ex.Message}"); + ShowErrorMessageBox($"X轴移动失败: {ex.Message}", () => { }); + } } - private void OnMoveY() + private async void OnMoveY() { - // TODO: 执行单轴Y向绝对位置寸动 + if (!EnsureConnected()) return; + try + { + await _gantry!.SetAxisTargetPositionAsync(2, TargetY); + await _gantry.StartSingleAxisAsync(2); + LoggerHelper.Info($"[BenchMovement] Y轴移动到 {TargetY}"); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] Y轴移动失败: {ex.Message}"); + ShowErrorMessageBox($"Y轴移动失败: {ex.Message}", () => { }); + } } - private void OnMoveZ() + private async void OnMoveZ() { - // TODO: 执行单轴Z向绝对位置寸动 + if (!EnsureConnected()) return; + try + { + await _gantry!.SetAxisTargetPositionAsync(3, TargetZ); + await _gantry.StartSingleAxisAsync(3); + LoggerHelper.Info($"[BenchMovement] Z轴移动到 {TargetZ}"); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] Z轴移动失败: {ex.Message}"); + ShowErrorMessageBox($"Z轴移动失败: {ex.Message}", () => { }); + } } - private void OnEmergencyStop() + private async void OnMoveAll() { - // TODO: 全局急停高优先级报文/IO发送 + if (!EnsureConnected()) return; + try + { + await _gantry!.SetAxisTargetPositionAsync(1, TargetX); + await _gantry.SetAxisTargetPositionAsync(2, TargetY); + await _gantry.SetAxisTargetPositionAsync(3, TargetZ); + await _gantry.StartAllAxesAsync(); + LoggerHelper.Info($"[BenchMovement] 三轴同时移动: X={TargetX}, Y={TargetY}, Z={TargetZ}"); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] 三轴移动失败: {ex.Message}"); + ShowErrorMessageBox($"三轴移动失败: {ex.Message}", () => { }); + } } - private void OnApplyNetwork() + private async void OnGoHome() { - // TODO: 应用PLC的IP和端口网络连接配置 + if (!EnsureConnected()) return; + try + { + await _gantry!.HomeAsync(); + LoggerHelper.Info("[BenchMovement] 回原点已触发"); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] 回原点失败: {ex.Message}"); + ShowErrorMessageBox($"回原点失败: {ex.Message}", () => { }); + } } - private void OnSetSpeed() + private async void OnEmergencyStop() { - // TODO: 发送三轴移动速度设定参数 + if (!EnsureConnected()) return; + try + { + await _gantry!.SetEStopStateAsync(true); + LoggerHelper.Info("[BenchMovement] 急停已触发"); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] 急停失败: {ex.Message}"); + } } - private void OnSetZero() + private async void OnStopX() { - // TODO: 发送台架绝对位置零点校准设定 + if (!EnsureConnected()) return; + try + { + await _gantry!.SetStopStateAsync(1, true); + await Task.Delay(100); + await _gantry.SetStopStateAsync(1, false); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] X轴停止失败: {ex.Message}"); + } + } + + private async void OnStopY() + { + if (!EnsureConnected()) return; + try + { + await _gantry!.SetStopStateAsync(2, true); + await Task.Delay(100); + await _gantry.SetStopStateAsync(2, false); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] Y轴停止失败: {ex.Message}"); + } + } + + private async void OnStopZ() + { + if (!EnsureConnected()) return; + try + { + await _gantry!.SetStopStateAsync(3, true); + await Task.Delay(100); + await _gantry.SetStopStateAsync(3, false); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] Z轴停止失败: {ex.Message}"); + } + } + + private async void OnSetSpeed() + { + if (!EnsureConnected()) return; + try + { + await _gantry!.SetAxisSpeedAsync(1, SpeedX); + await _gantry.SetAxisSpeedAsync(2, SpeedY); + await _gantry.SetAxisSpeedAsync(3, SpeedZ); + await _gantry.SetHomePositionSpeedAsync(HomeSpeed); + LoggerHelper.Info($"[BenchMovement] 速度设定完成: X={SpeedX}, Y={SpeedY}, Z={SpeedZ}, 回零={HomeSpeed}"); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] 速度设定失败: {ex.Message}"); + ShowErrorMessageBox($"速度设定失败: {ex.Message}", () => { }); + } + } + + private async void OnSetHome() + { + if (!EnsureConnected()) return; + try + { + await _gantry!.SetHomePositionAsync(HomeX, HomeY, HomeZ); + LoggerHelper.Info($"[BenchMovement] 零点设定完成: X={HomeX}, Y={HomeY}, Z={HomeZ}"); + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] 零点设定失败: {ex.Message}"); + ShowErrorMessageBox($"零点设定失败: {ex.Message}", () => { }); + } + } + + private async void OnReadStatus() + { + await ReadDeviceParametersAsync(); } #endregion - #region 重写方法与生命周期导航 + #region 辅助方法 + + private bool EnsureConnected() + { + if (_gantry == null || !_gantry.IsConnected) + { + ShowErrorMessageBox("设备未连接,请先连接台架", () => { }); + return false; + } + return true; + } + + private void UpdateConnectionStatus() + { + IsConnected = _gantry?.IsConnected ?? false; + ConnectionStatus = IsConnected ? "已连接" : "未连接"; + } + + private async Task ReadDeviceParametersAsync() + { + if (_gantry == null || !_gantry.IsConnected) return; + try + { + var (x, y, z) = await _gantry.GetHomePositionAsync(); + HomeX = x; HomeY = y; HomeZ = z; + + SpeedX = await _gantry.GetAxisSpeedAsync(1); + SpeedY = await _gantry.GetAxisSpeedAsync(2); + SpeedZ = await _gantry.GetAxisSpeedAsync(3); + HomeSpeed = await _gantry.GetHomePositionSpeedAsync(); + + // 读取 6 轴绝对位置 + var positions = await _gantry.GetAllAbsolutePositionsAsync(); + AbsPos1 = positions.Axis1; AbsPos2 = positions.Axis2; AbsPos3 = positions.Axis3; + AbsPos4 = positions.Axis4; AbsPos5 = positions.Axis5; AbsPos6 = positions.Axis6; + } + catch (Exception ex) + { + LoggerHelper.Error($"[BenchMovement] 读取设备参数失败: {ex.Message}"); + } + } + + private async void OnPollTick(object? sender, EventArgs e) + { + if (_gantry == null || !_gantry.IsConnected) return; + try + { + // 批量读取 6 轴绝对位置(一次性通信,高效) + var pos = await _gantry.GetAllAbsolutePositionsAsync(); + AbsPos1 = pos.Axis1; AbsPos2 = pos.Axis2; AbsPos3 = pos.Axis3; + AbsPos4 = pos.Axis4; AbsPos5 = pos.Axis5; AbsPos6 = pos.Axis6; + } + catch { /* 忽略轮询错误 */ } + } + + #endregion + + #region 生命周期 public override void OnNavigatedTo(NavigationContext navigationContext) { base.OnNavigatedTo(navigationContext); - - if (!_isInitiated && navigationContext.Parameters.ContainsKey("Name")) + if (!_isInitiated) { - _testStatus = navigationContext.Parameters.GetValue("Name"); - - // TODO: 若后续需要从全局环境 Scope 字典中加载参数配置,可以在此处对齐获取: - // var scope = _globalInfo.ScopeDic[_testStatus]; - _isInitiated = true; } } public void Dispose() { - // 用于界面关闭或Region注销时的资源释放(例如解除事件订阅、断开临时Socket通道、清空通信缓冲) + _pollTimer.Stop(); + _gantry?.Close(); + _gantry?.TcpDevice?.Dispose(); } #endregion } -} \ No newline at end of file +} diff --git a/BenchMovementModule/ViewModels/Dialogs/GantryConfigDialogViewModel.cs b/BenchMovementModule/ViewModels/Dialogs/GantryConfigDialogViewModel.cs new file mode 100644 index 0000000..eb2d1f6 --- /dev/null +++ b/BenchMovementModule/ViewModels/Dialogs/GantryConfigDialogViewModel.cs @@ -0,0 +1,202 @@ +using System.Collections.ObjectModel; +using System.Windows.Input; +using BenchMovementModule.HardwareDrive; +using Logger; +using UIShare.ViewModelBase; + +namespace BenchMovementModule.ViewModels.Dialogs +{ + public class GantryConfigDialogViewModel : DialogViewModelBase + { + #region 属性 + + private string _title = "台架 TCP 连接配置"; + public string Title + { + get => _title; + set => SetProperty(ref _title, value); + } + + private string _ipAddress = "192.168.0.30"; + public string IpAddress + { + get => _ipAddress; + set => SetProperty(ref _ipAddress, value); + } + + private int _port = 502; + public int Port + { + get => _port; + set => SetProperty(ref _port, value); + } + + private int _sendTimeout = 3000; + public int SendTimeout + { + get => _sendTimeout; + set => SetProperty(ref _sendTimeout, value); + } + + private int _receiveTimeout = 3000; + public int ReceiveTimeout + { + get => _receiveTimeout; + set => SetProperty(ref _receiveTimeout, value); + } + + private bool _isConnected; + public bool IsConnected + { + get => _isConnected; + set => SetProperty(ref _isConnected, value); + } + + private string _connectionStatus = "未连接"; + public string ConnectionStatus + { + get => _connectionStatus; + set => SetProperty(ref _connectionStatus, value); + } + + private string _errorMessage = string.Empty; + public string ErrorMessage + { + get => _errorMessage; + set => SetProperty(ref _errorMessage, value); + } + + public ObservableCollection CommonPorts { get; } = new() + { + 502, 102, 80, 8080, 5020, 4840 + }; + + public ObservableCollection CommonTimeouts { get; } = new() + { + 500, 1000, 2000, 3000, 5000, 10000 + }; + + #endregion + + #region 命令 + public ICommand ConnectCommand { get; } + public ICommand DisconnectCommand { get; } + public ICommand CloseCommand { get; } + #endregion + + private GantryControlTcp? _gantry; + + public GantryConfigDialogViewModel(IContainerProvider containerProvider) : base(containerProvider) + { + ConnectCommand = new DelegateCommand(OnConnect); + DisconnectCommand = new DelegateCommand(OnDisconnect); + CloseCommand = new DelegateCommand(OnClose); + } + + private void ApplyConfigToDevice() + { + if (_gantry?.TcpDevice == null) return; + _gantry.TcpDevice.ConfigureDevice(IpAddress, Port, SendTimeout, ReceiveTimeout); + } + + private async void OnConnect() + { + ErrorMessage = string.Empty; + + if (string.IsNullOrWhiteSpace(IpAddress)) + { + ErrorMessage = "IP 地址不能为空"; + return; + } + if (!System.Net.IPAddress.TryParse(IpAddress, out _)) + { + ErrorMessage = "IP 地址格式不正确"; + return; + } + if (Port <= 0 || Port > 65535) + { + ErrorMessage = "端口范围应在 1 - 65535"; + return; + } + + try + { + ConnectionStatus = "连接中..."; + ApplyConfigToDevice(); + bool ok = await _gantry!.ConnectAsync(); + if (ok) + { + IsConnected = true; + ConnectionStatus = "已连接"; + LoggerHelper.Info($"[BenchMovement] 台架连接成功: {IpAddress}:{Port}"); + } + else + { + ConnectionStatus = "连接失败"; + ErrorMessage = "连接失败,请检查网络和设备状态"; + } + } + catch (Exception ex) + { + ConnectionStatus = "连接失败"; + ErrorMessage = $"连接异常: {ex.Message}"; + LoggerHelper.Error($"[BenchMovement] 连接异常: {ex.Message}"); + } + } + + private void OnDisconnect() + { + if (_gantry == null) return; + try + { + _gantry.Close(); + IsConnected = false; + ConnectionStatus = "未连接"; + ErrorMessage = string.Empty; + LoggerHelper.Info("[BenchMovement] 台架已断开"); + } + catch (Exception ex) + { + ErrorMessage = $"断开失败: {ex.Message}"; + LoggerHelper.Error($"[BenchMovement] 断开失败: {ex.Message}"); + } + } + + private void OnClose() + { + RequestClose.Invoke(ButtonResult.OK); + } + + #region Prism Dialog 规范 + + public override void OnDialogOpened(IDialogParameters parameters) + { + _eventAggregator.GetEvent().Publish(true); + + if (parameters.ContainsKey("Gantry")) + { + _gantry = parameters.GetValue("Gantry"); + + // 从现有设备读取当前配置 + if (_gantry?.TcpDevice != null) + { + IpAddress = _gantry.TcpDevice.IPAddress; + Port = _gantry.TcpDevice.Port; + SendTimeout = _gantry.TcpDevice.SendTimeout; + ReceiveTimeout = _gantry.TcpDevice.ReceiveTimeout; + } + + // 同步当前连接状态 + IsConnected = _gantry?.IsConnected ?? false; + ConnectionStatus = IsConnected ? "已连接" : "未连接"; + } + } + + public override void OnDialogClosed() + { + _eventAggregator.GetEvent().Publish(false); + } + + #endregion + } +} diff --git a/BenchMovementModule/Views/BenchMovementView.xaml b/BenchMovementModule/Views/BenchMovementView.xaml index 89f6c7b..d4aadf4 100644 --- a/BenchMovementModule/Views/BenchMovementView.xaml +++ b/BenchMovementModule/Views/BenchMovementView.xaml @@ -2,7 +2,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" xmlns:prism="http://prismlibrary.com/" @@ -44,7 +43,6 @@ - - + + - @@ -150,30 +167,90 @@ - - + + - - - + + - - + + + + + + + + + +