台架驱动

This commit is contained in:
hsc
2026-07-17 17:36:08 +08:00
parent c4ec348217
commit 32d62de6f8
10 changed files with 1494 additions and 626 deletions

View File

@@ -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>("BenchMovementView");
// 台架 TCP 连接配置弹窗
containerRegistry.RegisterDialog<GantryConfigDialogView, GantryConfigDialogViewModel>("GantryConfigDialog");
}
}
}

View File

@@ -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<bool> ConnectAsync(CancellationToken ct = default)
{
return await _device.ConnectAsync(ct);
}
public void Close()
{
_device.Close();
}
// =================================================================
// ------------------ 核心控制逻辑与参数读写 ------------------------
// =================================================================
/// <summary>
/// 轴使能控制
/// </summary>
/// <param name="axis">轴编号 (1 - 6)</param>
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);
}
/// <summary>
/// 读取指定轴的使能状态
/// </summary>
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
public async Task<bool> 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];
}
/// <summary>
/// 同时设置 X/Y/Z 轴的原点位置(带各自独立的范围检查)
/// </summary>
/// <param name="xPos">X轴原点位置 (范围: 0 ~ 50000)</param>
/// <param name="yPos">Y轴原点位置 (范围: 0 ~ 50000)</param>
/// <param name="zPos">Z轴原点位置 (范围: 0 ~ 20000)</param>
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);
}
/// <summary>
/// 批量查询当前 X、Y、Z 三轴设定的原点位置值
/// </summary>
/// <returns>返回包含三轴原点位置的元组 (X, Y, Z)</returns>
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);
}
/// <summary>
/// 设置回原点定位速度(带 0 - 100000 范围检查)
/// </summary>
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);
}
/// <summary>
/// 查询当前设定的回原点定位速度
/// </summary>
public async Task<int> 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);
}
/// <summary>
/// 设置指定轴的运行速度(带 0 - 100000 范围检查)
/// </summary>
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
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);
}
/// <summary>
/// 读取指定轴当前设定的运行速度
/// </summary>
public async Task<int> 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);
}
/// <summary>
/// 写入指定轴的目标位置(动态安全校验:目标位置不能超过 [轴最大物理极限 - 当前轴原点设置值]
/// </summary>
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);
}
/// <summary>
/// 读取指定轴当前设定的目标指定位置
/// </summary>
public async Task<int> 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);
}
/// <summary>
/// 设置系统紧停锁定状态 (true: 锁定激活, false: 解除锁定)
/// </summary>
public async Task SetEStopStateAsync(bool state, CancellationToken ct = default)
{
await _device.WriteSingleCoilAsync(_slaveAddress, ADDR_M_ESTOP, state, ct);
}
/// <summary>
/// 设置指定轴的停止控制状态 (true: 持续输出停止信号, false: 释放停止信号)
/// </summary>
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);
}
/// <summary>
/// 全局回零(安全校验版:先检查回原点速度是否为 0为0则拒绝触发
/// </summary>
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);
}
/// <summary>
/// 单轴指定位置启动(含速度判定、使能自动补齐、动作目标位置独立范围校验)
/// </summary>
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
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);
}
/// <summary>
/// 三轴同时启动全开严格校验全部运行轴速度、批量检测与补正1~6号轴使能、联动校验三轴目标范围极限
/// </summary>
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
/// <summary>
/// 触发点动脉冲信号(置 1 后,等待 100ms 自动置 0 释放)
/// </summary>
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);
}
/// <summary>
/// 将 32 位整型数据转换为 Modbus 的 2 个 16 位无符号整数(低字在前 CDAB 格式)
/// </summary>
protected ushort[] Int32ToUshorts(int value)
{
ushort lowWord = (ushort)(value & 0xFFFF);
ushort highWord = (ushort)((value >> 16) & 0xFFFF);
return new ushort[] { lowWord, highWord };
}
/// <summary>
/// 将 Modbus 的 2 个 16 位无符号整数转换为 32 位整型(低字在前 CDAB 格式)
/// </summary>
protected int UshortsToInt32(ushort[] registers)
{
uint lowWord = registers[0];
uint highWord = registers[1];
return (int)((highWord << 16) | lowWord);
}
#endregion
#region
/// <summary>
/// 读取指定轴的绝对位置(相对原点的实时位置,只读)
/// </summary>
/// <param name="axis">轴编号 (1-6)</param>
public async Task<int> 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);
}
/// <summary>
/// 批量读取 1-6 轴的绝对位置(一次性读取 22 个寄存器,高效)
/// </summary>
/// <returns>返回 6 轴绝对位置元组</returns>
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
}
}

View File

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

View File

@@ -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<string>("Name");
// TODO: 若后续需要从全局环境 Scope 字典中加载参数配置,可以在此处对齐获取:
// var scope = _globalInfo.ScopeDic[_testStatus];
_isInitiated = true;
}
}
public void Dispose()
{
// 用于界面关闭或Region注销时的资源释放例如解除事件订阅、断开临时Socket通道、清空通信缓冲
_pollTimer.Stop();
_gantry?.Close();
_gantry?.TcpDevice?.Dispose();
}
#endregion
}
}
}

View File

@@ -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<int> CommonPorts { get; } = new()
{
502, 102, 80, 8080, 5020, 4840
};
public ObservableCollection<int> 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<UIShare.PubEvent.OverlayEvent>().Publish(true);
if (parameters.ContainsKey("Gantry"))
{
_gantry = parameters.GetValue<GantryControlTcp>("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<UIShare.PubEvent.OverlayEvent>().Publish(false);
}
#endregion
}
}

View File

@@ -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 @@
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
<!-- 极具机械/按钮交互感的现代工业 Button 样式 -->
<Style TargetType="Button">
<Setter Property="Background" Value="#F5F5F5"/>
<Setter Property="Foreground" Value="#333333"/>
@@ -57,20 +55,18 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="btnBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
<Border x:Name="btnBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="3">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<!-- 鼠标悬停:变亮并加深边框 -->
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="btnBorder" Property="Background" Value="#EAEAEA"/>
<Setter TargetName="btnBorder" Property="BorderBrush" Value="#4A90E2"/>
</Trigger>
<!-- 鼠标按下:明显的下凹和变暗交互感 -->
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="btnBorder" Property="Background" Value="#DCDCDC"/>
<Setter TargetName="btnBorder" Property="BorderBrush" Value="#2A6099"/>
@@ -81,7 +77,6 @@
</Setter>
</Style>
<!-- 针对特殊控制按钮单独定制的样式 -->
<Style TargetType="Button" x:Key="StopButtonStyle" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="#FFF0F0"/>
<Setter Property="Foreground" Value="#C00000"/>
@@ -128,6 +123,29 @@
</Setter>
</Style>
<Style TargetType="Button" x:Key="ConnectButtonStyle" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="#5CB85C"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#4CAE4C"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="btnBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" CornerRadius="3">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="btnBorder" Property="Background" Value="#449D44"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="btnBorder" Property="Background" Value="#3D8B3D"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="GroupBox">
<Setter Property="BorderBrush" Value="#D0D0D0"/>
<Setter Property="BorderThickness" Value="1"/>
@@ -136,7 +154,6 @@
</Style>
</UserControl.Resources>
<!-- 浅灰、偏白色工业背景 -->
<Grid Background="#F0F2F5">
<TabControl Background="Transparent" BorderThickness="0">
<TabControl.Resources>
@@ -150,30 +167,90 @@
</Style>
</TabControl.Resources>
<!-- ======================= 1. 主页标签页 ======================= -->
<TabItem Header="标签">
<!-- ======================= 1. 控制面板 ======================= -->
<TabItem Header="控制">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<!-- 左上:台架位置 & 信号回读 -->
<Grid Grid.Row="0" Grid.Column="0">
<!-- 顶部:连接状态条 -->
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Background="White" BorderBrush="#D0D0D0" BorderThickness="1" CornerRadius="3" Padding="15,8" Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<Ellipse Width="14" Height="14" Margin="0,0,8,0">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="#999999"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Fill" Value="#5CB85C"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock Text="{Binding ConnectionStatus}" FontSize="16" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,20,0"/>
<Button Content="配置连接" Width="100" Margin="0" Command="{Binding OpenConfigCommand}"/>
<Button Content="连接" Width="80" Margin="8,0,0,0" Style="{StaticResource ConnectButtonStyle}" Command="{Binding ConnectCommand}"/>
<Button Content="断开" Width="80" Margin="8,0,0,0" Style="{StaticResource StopButtonStyle}" Command="{Binding DisconnectCommand}"/>
<Button Content="刷新状态" Width="90" Margin="8,0,0,0" Command="{Binding ReadStatusCommand}"/>
</StackPanel>
</Border>
<!-- 左中:位置显示 & 寸动目标 -->
<Grid Grid.Row="1" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1.5*"/>
</Grid.ColumnDefinitions>
<!-- 台架位置 -->
<!-- 6轴绝对位置 -->
<GroupBox Grid.Column="0">
<GroupBox.Header>
<TextBlock Text="台架位置" Style="{StaticResource HeaderStyle}"/>
<TextBlock Text="绝对位置(只读)" Style="{StaticResource HeaderStyle}"/>
</GroupBox.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="轴1" Style="{StaticResource LabelStyle}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding AbsPos1}" Style="{StaticResource ValueStyle}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="轴2" Style="{StaticResource LabelStyle}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding AbsPos2}" Style="{StaticResource ValueStyle}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="轴3" Style="{StaticResource LabelStyle}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding AbsPos3}" Style="{StaticResource ValueStyle}"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="轴4" Style="{StaticResource LabelStyle}"/>
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding AbsPos4}" Style="{StaticResource ValueStyle}"/>
<TextBlock Grid.Row="4" Grid.Column="0" Text="轴5" Style="{StaticResource LabelStyle}"/>
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding AbsPos5}" Style="{StaticResource ValueStyle}"/>
<TextBlock Grid.Row="5" Grid.Column="0" Text="轴6" Style="{StaticResource LabelStyle}"/>
<TextBlock Grid.Row="5" Grid.Column="1" Text="{Binding AbsPos6}" Style="{StaticResource ValueStyle}"/>
</Grid>
</GroupBox>
<!-- 寸动目标位置 -->
<GroupBox Grid.Column="1">
<GroupBox.Header>
<TextBlock Text="寸动目标" Style="{StaticResource HeaderStyle}"/>
</GroupBox.Header>
<Grid>
<Grid.RowDefinitions>
@@ -184,350 +261,135 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="X (mm)" Style="{StaticResource LabelStyle}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding PosX}" Style="{StaticResource ValueStyle}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Y (mm)" Style="{StaticResource LabelStyle}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding PosY}" Style="{StaticResource ValueStyle}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Z (mm)" Style="{StaticResource LabelStyle}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding PosZ}" Style="{StaticResource ValueStyle}"/>
</Grid>
</GroupBox>
<!-- 信号回读 -->
<GroupBox Grid.Column="1">
<GroupBox.Header>
<TextBlock Text="信号回读" Style="{StaticResource HeaderStyle}"/>
</GroupBox.Header>
<UniformGrid Columns="2">
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Text="POS1_X(mm): " Style="{StaticResource LabelStyle}"/>
<TextBlock Text="{Binding Pos1X}" Foreground="#0056B3" FontSize="18" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Text="POS1_Quality: " Style="{StaticResource LabelStyle}"/>
<TextBlock Text="{Binding Pos1Quality}" Foreground="#0056B3" FontSize="18" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Text="POS2_X(mm): " Style="{StaticResource LabelStyle}"/>
<TextBlock Text="{Binding Pos2X}" Foreground="#0056B3" FontSize="18" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Text="POS2_Quality: " Style="{StaticResource LabelStyle}"/>
<TextBlock Text="{Binding Pos2Quality}" Foreground="#0056B3" FontSize="18" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Text="POS1_Y(mm): " Style="{StaticResource LabelStyle}"/>
<TextBlock Text="{Binding Pos1Y}" Foreground="#0056B3" FontSize="18" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Text="POS2_Y(mm): " Style="{StaticResource LabelStyle}"/>
<TextBlock Text="{Binding Pos2Y}" Foreground="#0056B3" FontSize="18" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</UniformGrid>
</GroupBox>
</Grid>
<!-- 左中:扫描参数 -->
<GroupBox Grid.Row="1" Grid.Column="0">
<GroupBox.Header>
<TextBlock Text="扫描参数" Style="{StaticResource HeaderStyle}"/>
</GroupBox.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- X轴 -->
<TextBlock Grid.Row="0" Grid.Column="0" Text="X移动步长(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding ScanStepX}" Margin="5"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="X起始位置(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="0" Grid.Column="3" Text="{Binding ScanStartX}" Margin="5"/>
<TextBlock Grid.Row="0" Grid.Column="4" Text="X终止位置(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="0" Grid.Column="5" Text="{Binding ScanEndX}" Margin="5"/>
<!-- Y轴 -->
<TextBlock Grid.Row="1" Grid.Column="0" Text="Y移动步长(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding ScanStepY}" Margin="5"/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="Y起始位置(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="1" Grid.Column="3" Text="{Binding ScanStartY}" Margin="5"/>
<TextBlock Grid.Row="1" Grid.Column="4" Text="Y终止位置(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="1" Grid.Column="5" Text="{Binding ScanEndY}" Margin="5"/>
<!-- Z轴 -->
<TextBlock Grid.Row="2" Grid.Column="0" Text="Z移动步长(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding ScanStepZ}" Margin="5"/>
<TextBlock Grid.Row="2" Grid.Column="2" Text="Z起始位置(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="2" Grid.Column="3" Text="{Binding ScanStartZ}" Margin="5"/>
<TextBlock Grid.Row="2" Grid.Column="4" Text="Z终止位置(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="2" Grid.Column="5" Text="{Binding ScanEndZ}" Margin="5"/>
</Grid>
</GroupBox>
<!-- 左下:寸动参数与雷达位置图 -->
<Grid Grid.Row="2" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<!-- 寸动参数 -->
<GroupBox Grid.Column="0">
<GroupBox.Header>
<TextBlock Text="寸动参数" Style="{StaticResource HeaderStyle}"/>
</GroupBox.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="X移动位置(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding JogX}" Margin="5"/>
<TextBlock Grid.Row="0" Grid.Column="0" Text="X目标" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding TargetX}" Margin="5"/>
<Button Grid.Row="0" Grid.Column="2" Content="移动" Width="60" Margin="5,0,5,0" Command="{Binding MoveXCommand}"/>
<Button Grid.Row="0" Grid.Column="3" Content="停止" Width="60" Margin="0,0,5,0" Style="{StaticResource StopButtonStyle}" Command="{Binding StopXCommand}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Y移动位置(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding JogY}" Margin="5"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Y目标" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding TargetY}" Margin="5"/>
<Button Grid.Row="1" Grid.Column="2" Content="移动" Width="60" Margin="5,0,5,0" Command="{Binding MoveYCommand}"/>
<Button Grid.Row="1" Grid.Column="3" Content="停止" Width="60" Margin="0,0,5,0" Style="{StaticResource StopButtonStyle}" Command="{Binding StopYCommand}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Z移动位置(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding JogZ}" Margin="5"/>
</Grid>
</GroupBox>
<!-- 坐标示意图 -->
<GroupBox Grid.Column="1">
<GroupBox.Header>
<TextBlock Text="台架现在位置" Style="{StaticResource HeaderStyle}"/>
</GroupBox.Header>
<Grid Margin="5" Background="White">
<!-- 十字坐标轴 -->
<Line X1="0" Y1="50" X2="200" Y2="50" Stroke="#B0B0B0" StrokeThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" Width="200" Height="100"/>
<Line X1="100" Y1="0" X2="100" Y2="100" Stroke="#B0B0B0" StrokeThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" Width="200" Height="100"/>
<!-- 轴向文本标签 -->
<TextBlock Text="X+" HorizontalAlignment="Center" VerticalAlignment="Top" Foreground="#666666" Margin="0,2,0,0"/>
<TextBlock Text="X-" HorizontalAlignment="Center" VerticalAlignment="Bottom" Foreground="#666666" Margin="0,0,0,2"/>
<TextBlock Text="Y+" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="#666666" Margin="2,0,0,0"/>
<TextBlock Text="Y-" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="#666666" Margin="0,0,2,0"/>
<!-- 台架模拟中心点 -->
<Border Width="65" Height="35" Background="#E9ECEF" BorderBrush="#999999" BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" CornerRadius="2">
<TextBlock Text="台架位置" Foreground="#333333" FontSize="11" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Z目标" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding TargetZ}" Margin="5"/>
<Button Grid.Row="2" Grid.Column="2" Content="移动" Width="60" Margin="5,0,5,0" Command="{Binding MoveZCommand}"/>
<Button Grid.Row="2" Grid.Column="3" Content="停止" Width="60" Margin="0,0,5,0" Style="{StaticResource StopButtonStyle}" Command="{Binding StopZCommand}"/>
</Grid>
</GroupBox>
</Grid>
<!-- 右侧:控制面板除去Spacing增加Margin防粘连 -->
<Grid Grid.Row="0" Grid.RowSpan="3" Grid.Column="1" Margin="6,6,0,6">
<!-- 右侧:控制面板 -->
<Grid Grid.Row="1" Grid.Column="1" Margin="6,6,0,6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 扫描路径操作 -->
<StackPanel Grid.Row="0">
<Button Content="▶ 开始扫描" Command="{Binding StartScanCommand}"/>
<Button Content="■ 停止扫描" Style="{StaticResource StopButtonStyle}" Command="{Binding StopScanCommand}"/>
<Button Content="↩ 回原点" Command="{Binding GoHomeCommand}"/>
<TextBlock Text="扫描路径" Style="{StaticResource LabelStyle}" Margin="0,10,0,4"/>
<ComboBox Height="30" Background="White" Foreground="#333333" SelectedIndex="0" Margin="0,0,0,8" materialDesign:HintAssist.Hint="">
<ComboBoxItem Content="配置文件1.xml"/>
</ComboBox>
<Button Content="⬆ 三轴同动" Command="{Binding MoveAllCommand}"/>
</StackPanel>
<!-- 寸动单轴移动按钮组 -->
<Grid Grid.Row="1">
<StackPanel VerticalAlignment="Center">
<Button Content="↕ X 移动" Command="{Binding MoveXCommand}"/>
<Button Content="↔ Y 移动" Command="{Binding MoveYCommand}"/>
<Button Content="↕ Z 移动" Command="{Binding MoveZCommand}"/>
</StackPanel>
</Grid>
<StackPanel Grid.Row="1" VerticalAlignment="Center">
<TextBlock Text="单轴控制" Style="{StaticResource LabelStyle}" HorizontalAlignment="Center" Margin="0,10,0,10"/>
<Button Content="↕ X 移动" Command="{Binding MoveXCommand}"/>
<Button Content="↔ Y 移动" Command="{Binding MoveYCommand}"/>
<Button Content="↕ Z 移动" Command="{Binding MoveZCommand}"/>
</StackPanel>
<!-- 急停 -->
<Button Grid.Row="2" Content="❌ 急 停" Style="{StaticResource EmergencyButtonStyle}" FontWeight="Bold" FontSize="18" Height="55" Margin="0" Command="{Binding EmergencyStopCommand}"/>
</Grid>
</Grid>
</TabItem>
<!-- ======================= 2. 设置标签页 ======================= -->
<!-- ======================= 2. 设置 ======================= -->
<TabItem Header="设置">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="10">
<!-- 台架接口配置 -->
<!-- 移动速度设定(脉冲) -->
<GroupBox>
<GroupBox.Header>
<TextBlock Text="台架接口" Style="{StaticResource HeaderStyle}"/>
<TextBlock Text="移动速度设定" Style="{StaticResource HeaderStyle}"/>
</GroupBox.Header>
<StackPanel Orientation="Horizontal" Margin="5">
<StackPanel Orientation="Horizontal" Margin="0,0,15,0">
<TextBlock Text="IP: " Style="{StaticResource LabelStyle}"/>
<TextBox Width="150" Text="{Binding PlcIp}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,15,0">
<TextBlock Text="端口: " Style="{StaticResource LabelStyle}"/>
<TextBox Width="80" Text="{Binding PlcPort}"/>
</StackPanel>
<Button Content="应 用" Width="90" Margin="0" Command="{Binding ApplyNetworkCommand}"/>
<StackPanel Margin="5">
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="X速度(脉冲)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding SpeedX}"/>
</Grid>
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Y速度(脉冲)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding SpeedY}"/>
</Grid>
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Z速度(脉冲)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding SpeedZ}"/>
</Grid>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="回零速度(脉冲)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding HomeSpeed}"/>
</Grid>
<Button Content="速度设定" Width="110" HorizontalAlignment="Left" Margin="0" Command="{Binding SetSpeedCommand}"/>
</StackPanel>
</GroupBox>
<!-- 移动速度与零点设定 -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<!-- 移动速度设定 -->
<GroupBox Grid.Column="0">
<GroupBox.Header>
<TextBlock Text="移动速度设定" Style="{StaticResource HeaderStyle}"/>
</GroupBox.Header>
<StackPanel Margin="5">
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="X移动速度(mm/s)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding SpeedX}"/>
</Grid>
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Y移动速度(mm/s)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding SpeedY}"/>
</Grid>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Z移动速度(mm/s)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding SpeedZ}"/>
</Grid>
<Button Content="速度设定" Width="110" HorizontalAlignment="Left" Margin="0" Command="{Binding SetSpeedCommand}"/>
</StackPanel>
</GroupBox>
<!-- 零点设定 -->
<GroupBox Grid.Column="1">
<GroupBox.Header>
<TextBlock Text="零点设定" Style="{StaticResource HeaderStyle}"/>
</GroupBox.Header>
<StackPanel Margin="5">
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="X零点(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding ZeroX}"/>
</Grid>
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Y零点(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding ZeroY}"/>
</Grid>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Z零点(mm)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding ZeroZ}"/>
</Grid>
<Button Content="零点设定" Width="110" HorizontalAlignment="Left" Margin="0" Command="{Binding SetZeroCommand}"/>
</StackPanel>
</GroupBox>
</Grid>
<!-- CAN设定 -->
<!-- 零点设定 -->
<GroupBox>
<GroupBox.Header>
<TextBlock Text="CAN设定" Style="{StaticResource HeaderStyle}"/>
<TextBlock Text="零点设定" Style="{StaticResource HeaderStyle}"/>
</GroupBox.Header>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170"/>
<ColumnDefinition Width="280"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="140"/>
</Grid.ColumnDefinitions>
<!-- 通道与模式 -->
<TextBlock Grid.Row="0" Grid.Column="0" Text="应用程序CAN通道" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding CanChannel}" Margin="3"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="模式" Style="{StaticResource LabelStyle}" HorizontalAlignment="Center"/>
<ComboBox Grid.Row="0" Grid.Column="3" Height="28" Background="White" Foreground="#333333" SelectedIndex="0" Margin="3" materialDesign:HintAssist.Hint="">
<ComboBoxItem Content="正常模式"/>
</ComboBox>
<!-- 报文ID -->
<TextBlock Grid.Row="1" Grid.Column="0" Text="报文ID(16进制)" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding CanMsgId}" Margin="3"/>
<!-- 信号名称列表 -->
<TextBlock Grid.Row="2" Grid.Column="0" Text="报文名称" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" Text="{Binding CanMsgName}" Margin="3"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="POS1_X信号名称" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="3" Text="{Binding SigPos1X}" Margin="3"/>
<TextBlock Grid.Row="4" Grid.Column="0" Text="POS2_X信号名称" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="3" Text="{Binding SigPos2X}" Margin="3"/>
<TextBlock Grid.Row="5" Grid.Column="0" Text="POS1_Y信号名称" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="3" Text="{Binding SigPos1Y}" Margin="3"/>
<TextBlock Grid.Row="6" Grid.Column="0" Text="POS2_Y信号名称" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="3" Text="{Binding SigPos2Y}" Margin="3"/>
<TextBlock Grid.Row="7" Grid.Column="0" Text="POS1_Quality信号名称" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="7" Grid.Column="1" Grid.ColumnSpan="3" Text="{Binding SigPos1Quality}" Margin="3"/>
<TextBlock Grid.Row="8" Grid.Column="0" Text="POS2_Quality信号名称" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Row="8" Grid.Column="1" Grid.ColumnSpan="3" Text="{Binding SigPos2Quality}" Margin="3"/>
</Grid>
<StackPanel Margin="5">
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="X零点" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding HomeX}"/>
</Grid>
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Y零点" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding HomeY}"/>
</Grid>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Z零点" Style="{StaticResource LabelStyle}"/>
<TextBox Grid.Column="1" Text="{Binding HomeZ}"/>
</Grid>
<Button Content="零点设定" Width="110" HorizontalAlignment="Left" Margin="0" Command="{Binding SetHomeCommand}"/>
</StackPanel>
</GroupBox>
</StackPanel>
</ScrollViewer>
</TabItem>
</TabControl>
</Grid>
</UserControl>
</UserControl>

View File

@@ -0,0 +1,140 @@
<UserControl x:Class="BenchMovementModule.Views.Dialogs.GantryConfigDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
mc:Ignorable="d"
xmlns:prism="http://prismlibrary.com/"
Background="White"
prism:ViewModelLocator.AutoWireViewModel="True"
Width="440"
Height="380">
<prism:Dialog.WindowStyle>
<Style BasedOn="{StaticResource DialogUserManageStyle}"
TargetType="Window" />
</prism:Dialog.WindowStyle>
<UserControl.Resources>
<converters:StringToVisibilityConverter x:Key="StringToVisibility"/>
</UserControl.Resources>
<GroupBox Padding="12,8,12,8"
helpers:WindowDragHelper.EnableWindowDrag="True">
<GroupBox.Header>
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="*" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Title}"
Foreground="White"
VerticalAlignment="Center"
Margin="5,0,10,0" />
</Grid>
</GroupBox.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 连接配置表单 -->
<Grid Grid.Row="0" Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Text="IP 地址:"
VerticalAlignment="Center" Margin="0,6"/>
<TextBox Grid.Row="0" Grid.Column="1" Margin="0,6"
materialDesign:HintAssist.Hint=""
Text="{Binding IpAddress, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Row="1" Grid.Column="0"
Text="端口:"
VerticalAlignment="Center" Margin="0,6"/>
<ComboBox Grid.Row="1" Grid.Column="1" Margin="0,6"
IsEditable="True"
materialDesign:HintAssist.Hint=""
ItemsSource="{Binding CommonPorts}"
Text="{Binding Port, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Row="2" Grid.Column="0"
Text="发送超时(ms)"
VerticalAlignment="Center" Margin="0,6"/>
<ComboBox Grid.Row="2" Grid.Column="1" Margin="0,6"
IsEditable="True"
materialDesign:HintAssist.Hint=""
ItemsSource="{Binding CommonTimeouts}"
Text="{Binding SendTimeout, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Row="3" Grid.Column="0"
Text="接收超时(ms)"
VerticalAlignment="Center" Margin="0,6"/>
<ComboBox Grid.Row="3" Grid.Column="1" Margin="0,6"
IsEditable="True"
materialDesign:HintAssist.Hint=""
ItemsSource="{Binding CommonTimeouts}"
Text="{Binding ReceiveTimeout, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
<!-- 连接状态 -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,8,0,0">
<Ellipse Width="12" Height="12" Margin="0,0,6,0">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="#999999"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Fill" Value="#5CB85C"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock Text="{Binding ConnectionStatus}" FontSize="14" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
<!-- 错误提示 -->
<TextBlock Grid.Row="2"
Margin="0,8,0,0"
Foreground="#D32F2F"
TextWrapping="Wrap"
Text="{Binding ErrorMessage}"
Visibility="{Binding ErrorMessage, Converter={StaticResource StringToVisibility}}"/>
<!-- 按钮区 -->
<StackPanel Grid.Row="3"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="0,12,0,0">
<Button Content="连接台架"
Width="100" Padding="0,4"
Margin="0,0,10,0"
Command="{Binding ConnectCommand}"/>
<Button Content="断开台架"
Width="100" Padding="0,4"
Margin="0,0,10,0"
Command="{Binding DisconnectCommand}"/>
<Button Content="关闭"
Width="80" Padding="0,4"
IsDefault="True"
Command="{Binding CloseCommand}"/>
</StackPanel>
</Grid>
</GroupBox>
</UserControl>

View File

@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace BenchMovementModule.Views.Dialogs
{
public partial class GantryConfigDialogView : UserControl
{
public GantryConfigDialogView()
{
InitializeComponent();
}
}
}

View File

@@ -28,7 +28,7 @@ namespace UIShare.ViewModelBase
dialogParams.Add("Message", Message);
dialogParams.Add("Icon", "info");
dialogParams.Add("ShowOk", true);
_dialogService.ShowDialog("MessageBoxView", dialogParams, result =>
_dialogService.ShowDialog("MessageBox", dialogParams, result =>
{
callback();
});
@@ -41,7 +41,7 @@ namespace UIShare.ViewModelBase
dialogParams.Add("Message", Message);
dialogParams.Add("Icon", "info");
dialogParams.Add("ShowOk", true);
_dialogService.ShowDialog("MessageBoxView", dialogParams, result =>
_dialogService.ShowDialog("MessageBox", dialogParams, result =>
{
callback();
});

View File

@@ -34,7 +34,7 @@ namespace UIShare.ViewModelBase
dialogParams.Add("Message", Message);
dialogParams.Add("Icon", "info");
dialogParams.Add("ShowOk", true);
_dialogService.ShowDialog("MessageBoxView", dialogParams, result =>
_dialogService.ShowDialog("MessageBox", dialogParams, result =>
{
callback();
});
@@ -46,7 +46,7 @@ namespace UIShare.ViewModelBase
dialogParams.Add("Message", Message);
dialogParams.Add("Icon", "info");
dialogParams.Add("ShowOk", true);
_dialogService.ShowDialog("MessageBoxView", dialogParams, result =>
_dialogService.ShowDialog("MessageBox", dialogParams, result =>
{
callback();
});