feat: 硬件虚拟化与CNC联动集成 - 运动控制/射线源模拟实现,CNC执行联动增强

This commit is contained in:
zhengxuan.zhang
2026-05-21 15:59:38 +08:00
parent 05c41a9a21
commit 43d0e7fa89
15 changed files with 1292 additions and 9 deletions
@@ -0,0 +1,81 @@
using System.Threading.Tasks;
using Xunit;
using XP.Hardware.MotionControl.Abstractions.Enums;
using XP.Hardware.MotionControl.Implementations;
namespace XplorePlane.Tests.Hardware
{
public class SimulatedLinearAxisTests
{
[Fact]
public async Task MoveToTarget_SetsStatusToMoving_ThenIdle()
{
// Arrange - use fast speed for quicker test
var axis = new SimulatedLinearAxis(AxisId.StageX, min: 0, max: 100, origin: 0, defaultSpeed: 200);
// Act
var result = axis.MoveToTarget(50);
// Assert - should be Moving immediately after call
Assert.True(result.Success);
Assert.Equal(AxisStatus.Moving, axis.Status);
// Wait for move to complete (50mm at 200mm/s = 250ms, add generous buffer)
await Task.Delay(1000);
Assert.Equal(AxisStatus.Idle, axis.Status);
Assert.Equal(50.0, axis.ActualPosition, precision: 1);
}
[Fact]
public void MoveToTarget_OutOfRange_ReturnsFail()
{
// Arrange
var axis = new SimulatedLinearAxis(AxisId.StageX, min: 0, max: 100, origin: 0);
// Act
var result = axis.MoveToTarget(150);
// Assert
Assert.False(result.Success);
Assert.NotNull(result.ErrorMessage);
}
[Fact]
public async Task Stop_DuringMove_KeepsCurrentPosition()
{
// Arrange - use slow speed so we can stop mid-move
var axis = new SimulatedLinearAxis(AxisId.StageX, min: 0, max: 100, origin: 0, defaultSpeed: 10);
// Act - start a long move (100mm at 10mm/s = 10s)
axis.MoveToTarget(100);
await Task.Delay(200); // Let it move a bit
axis.Stop();
// Assert
Assert.Equal(AxisStatus.Idle, axis.Status);
Assert.True(axis.ActualPosition > 0, "Position should have moved from 0");
Assert.True(axis.ActualPosition < 100, "Position should not have reached target");
}
[Fact]
public async Task Home_MovesToZero()
{
// Arrange - move to 50 first with fast speed
var axis = new SimulatedLinearAxis(AxisId.StageX, min: 0, max: 100, origin: 0, defaultSpeed: 200);
axis.MoveToTarget(50);
await Task.Delay(1000); // Wait for move to complete
Assert.Equal(50.0, axis.ActualPosition, precision: 1);
// Act
axis.Home();
await Task.Delay(1000); // Wait for home to complete
// Assert
Assert.Equal(0.0, axis.ActualPosition, precision: 1);
Assert.Equal(AxisStatus.Idle, axis.Status);
}
}
}
@@ -0,0 +1,65 @@
using System.Threading.Tasks;
using Xunit;
using XP.Hardware.MotionControl.Abstractions.Enums;
using XP.Hardware.MotionControl.Implementations;
namespace XplorePlane.Tests.Hardware
{
public class SimulatedRotaryAxisTests
{
[Fact]
public async Task MoveToTarget_SetsStatusToMoving_ThenIdle()
{
// Arrange - use fast speed for quicker test
var axis = new SimulatedRotaryAxis(
RotaryAxisId.DetectorSwing, minAngle: -180, maxAngle: 180, enabled: true, defaultSpeed: 200);
// Act
var result = axis.MoveToTarget(90);
// Assert - should be Moving immediately after call
Assert.True(result.Success);
Assert.Equal(AxisStatus.Moving, axis.Status);
// Wait for move to complete (90° at 200°/s = 450ms, add generous buffer)
await Task.Delay(1000);
Assert.Equal(AxisStatus.Idle, axis.Status);
Assert.Equal(90.0, axis.ActualAngle, precision: 1);
}
[Fact]
public void MoveToTarget_OutOfRange_ReturnsFail()
{
// Arrange
var axis = new SimulatedRotaryAxis(
RotaryAxisId.DetectorSwing, minAngle: -180, maxAngle: 180, enabled: true);
// Act
var result = axis.MoveToTarget(200);
// Assert
Assert.False(result.Success);
Assert.NotNull(result.ErrorMessage);
}
[Fact]
public async Task Stop_DuringMove_KeepsCurrentAngle()
{
// Arrange - use slow speed so we can stop mid-move
var axis = new SimulatedRotaryAxis(
RotaryAxisId.DetectorSwing, minAngle: -180, maxAngle: 180, enabled: true, defaultSpeed: 10);
// Act - start a long move (180° at 10°/s = 18s)
axis.MoveToTarget(180);
await Task.Delay(200); // Let it move a bit
axis.Stop();
// Assert
Assert.Equal(AxisStatus.Idle, axis.Status);
Assert.True(axis.ActualAngle > 0, "Angle should have moved from 0");
Assert.True(axis.ActualAngle < 180, "Angle should not have reached target");
}
}
}
@@ -0,0 +1,120 @@
using Moq;
using Prism.Events;
using Xunit;
using XP.Common.Logging.Interfaces;
using XP.Hardware.RaySource.Abstractions.Enums;
using XP.Hardware.RaySource.Abstractions.Events;
using XP.Hardware.RaySource.Implementations;
namespace XplorePlane.Tests.Hardware
{
public class SimulatedXRaySourceTests
{
private readonly Mock<IEventAggregator> _mockEventAggregator;
private readonly Mock<ILoggerService> _mockLogger;
private readonly Mock<RaySourceStatusChangedEvent> _mockStatusEvent;
private readonly SimulatedXRaySource _source;
public SimulatedXRaySourceTests()
{
_mockEventAggregator = new Mock<IEventAggregator>();
_mockLogger = new Mock<ILoggerService>();
_mockStatusEvent = new Mock<RaySourceStatusChangedEvent>();
// Setup logger to return itself for ForModule<T>()
_mockLogger.Setup(l => l.ForModule<SimulatedXRaySource>()).Returns(_mockLogger.Object);
// Setup event aggregator to return the mock event
_mockEventAggregator
.Setup(ea => ea.GetEvent<RaySourceStatusChangedEvent>())
.Returns(_mockStatusEvent.Object);
_source = new SimulatedXRaySource(_mockEventAggregator.Object, _mockLogger.Object);
}
[Fact]
public void Initialize_SetsInitializedTrue()
{
// Act
var initResult = _source.Initialize();
var connectResult = _source.ConnectVariables();
// Assert
Assert.True(initResult.Success);
Assert.True(connectResult.Success);
Assert.True(_source.IsConnected);
}
[Fact]
public void TurnOn_PublishesOpenedEvent()
{
// Arrange
_source.Initialize();
_source.ConnectVariables();
// Act
var result = _source.TurnOn();
// Assert
Assert.True(result.Success);
_mockStatusEvent.Verify(
e => e.Publish(RaySourceStatus.Opened),
Times.Once);
}
[Fact]
public void TurnOff_PublishesClosedEvent()
{
// Arrange
_source.Initialize();
_source.ConnectVariables();
_source.TurnOn();
// Act
var result = _source.TurnOff();
// Assert
Assert.True(result.Success);
_mockStatusEvent.Verify(
e => e.Publish(RaySourceStatus.Closed),
Times.Once);
}
[Fact]
public void ReadVoltage_ReturnsWithinTwoPercentOfSetValue()
{
// Arrange
_source.Initialize();
_source.ConnectVariables();
_source.SetVoltage(100f);
// Act & Assert - read multiple times to verify range
for (int i = 0; i < 20; i++)
{
var result = _source.ReadVoltage();
Assert.True(result.Success);
float voltage = result.GetFloat();
Assert.InRange(voltage, 98f, 102f);
}
}
[Fact]
public void CloseOff_ResetsState()
{
// Arrange
_source.Initialize();
_source.ConnectVariables();
_source.TurnOn();
Assert.True(_source.IsConnected);
// Act
var result = _source.CloseOff();
// Assert
Assert.True(result.Success);
Assert.False(_source.IsConnected);
}
}
}
@@ -37,6 +37,7 @@
<ItemGroup>
<ProjectReference Include="..\XP.Common\XP.Common.csproj" />
<ProjectReference Include="..\XP.Hardware.MotionControl\XP.Hardware.MotionControl.csproj" />
<ProjectReference Include="..\XP.Hardware.RaySource\XP.Hardware.RaySource.csproj" />
<ProjectReference Include="..\XplorePlane\XplorePlane.csproj" />
</ItemGroup>