447 lines
16 KiB
C#
447 lines
16 KiB
C#
using MainShell.Common;
|
||
using MainShell.Hardware;
|
||
using MainShell.DeviceMaintance.Model;
|
||
using MainShell.Log;
|
||
using MainShell.Motion;
|
||
using MaxwellFramework.Core.Interfaces;
|
||
using Stylet;
|
||
using StyletIoC;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using MwFramework.Device;
|
||
|
||
namespace MainShell.DeviceMaintance.ViewModel
|
||
{
|
||
public class LaserCompensationViewModel : DeviceMaintanceBaseViewModel, IPage
|
||
{
|
||
private const string ProjectPageName = "激光补偿";
|
||
private const int MinimumMoveTimeoutMilliseconds = 15000;
|
||
private const double MoveTimeoutScaleFactor = 3d;
|
||
private const int MoveTimeoutExtraMilliseconds = 2000;
|
||
|
||
private readonly SafeAxisMotion _safeAxisMotion;
|
||
private readonly AxisSpeedManager _axisSpeedManager;
|
||
private readonly HardwareManager _hardwareManager;
|
||
private CancellationTokenSource _cancellationTokenSource;
|
||
private bool _uiEnable = true;
|
||
private bool _isRunning;
|
||
private string _statusMessage;
|
||
private int _currentLoopIndex;
|
||
private int _currentPointIndex;
|
||
private double _currentTargetPosition;
|
||
private string _selectedAxisName;
|
||
|
||
public LaserCompensationViewModel(SafeAxisMotion safeAxisMotion, AxisSpeedManager axisSpeedManager, HardwareManager hardwareManager)
|
||
{
|
||
_safeAxisMotion = safeAxisMotion;
|
||
_axisSpeedManager = axisSpeedManager;
|
||
_hardwareManager = hardwareManager;
|
||
Setting = new LaserCompensationSetting();
|
||
Setting.LoadFromFile();
|
||
AxisNames = new System.Collections.ObjectModel.ObservableCollection<string>(BuildAxisNames());
|
||
if (!string.IsNullOrWhiteSpace(Setting.AxisName) && AxisNames.Contains(Setting.AxisName))
|
||
{
|
||
SelectedAxisName = Setting.AxisName;
|
||
}
|
||
else
|
||
{
|
||
SelectedAxisName = AxisNames.FirstOrDefault();
|
||
}
|
||
StatusMessage = "待机";
|
||
}
|
||
|
||
public string Name => "LaserCompensationMaint";
|
||
|
||
public LaserCompensationSetting Setting { get; }
|
||
|
||
public System.Collections.ObjectModel.ObservableCollection<string> AxisNames { get; }
|
||
|
||
public string SelectedAxisName
|
||
{
|
||
get { return _selectedAxisName; }
|
||
set
|
||
{
|
||
if (SetAndNotify(ref _selectedAxisName, value))
|
||
{
|
||
Setting.AxisName = value;
|
||
}
|
||
}
|
||
}
|
||
|
||
public bool UiEnable
|
||
{
|
||
get { return _uiEnable; }
|
||
set
|
||
{
|
||
if (SetAndNotify(ref _uiEnable, value))
|
||
{
|
||
OnUiStateChanged(value);
|
||
}
|
||
}
|
||
}
|
||
|
||
public bool IsRunning
|
||
{
|
||
get { return _isRunning; }
|
||
set { SetAndNotify(ref _isRunning, value); }
|
||
}
|
||
|
||
public string StatusMessage
|
||
{
|
||
get { return _statusMessage; }
|
||
set { SetAndNotify(ref _statusMessage, value); }
|
||
}
|
||
|
||
public int CurrentLoopIndex
|
||
{
|
||
get { return _currentLoopIndex; }
|
||
set { SetAndNotify(ref _currentLoopIndex, value); }
|
||
}
|
||
|
||
public int CurrentPointIndex
|
||
{
|
||
get { return _currentPointIndex; }
|
||
set { SetAndNotify(ref _currentPointIndex, value); }
|
||
}
|
||
|
||
public double CurrentTargetPosition
|
||
{
|
||
get { return _currentTargetPosition; }
|
||
set { SetAndNotify(ref _currentTargetPosition, value); }
|
||
}
|
||
|
||
public async Task StartAsync()
|
||
{
|
||
if (IsRunning)
|
||
{
|
||
return;
|
||
}
|
||
|
||
string validationMessage = ValidateSetting();
|
||
if (!string.IsNullOrWhiteSpace(validationMessage))
|
||
{
|
||
StatusMessage = validationMessage;
|
||
LocalizedMessageBox.Show(MessageKey.ParamInvalid, MessageKey.TitleError, System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
||
return;
|
||
}
|
||
|
||
_cancellationTokenSource?.Dispose();
|
||
_cancellationTokenSource = new CancellationTokenSource();
|
||
|
||
UiEnable = false;
|
||
IsRunning = true;
|
||
CurrentLoopIndex = 0;
|
||
CurrentPointIndex = 0;
|
||
CurrentTargetPosition = 0d;
|
||
Setting.AxisName = SelectedAxisName;
|
||
StatusMessage = "激光补偿运行中";
|
||
|
||
IoC.Get<IProjectManager>().EnablePageAndDisableOther(ProjectPageName);
|
||
BuildStartLogMessage().LogInfo();
|
||
|
||
try
|
||
{
|
||
CancellationToken cancellationToken = _cancellationTokenSource.Token;
|
||
await ExecuteCompensationAsync(cancellationToken).ConfigureAwait(true);
|
||
|
||
StatusMessage = "激光补偿执行完成";
|
||
BuildFinishLogMessage("完成").LogInfo();
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
StatusMessage = "激光补偿已停止";
|
||
BuildFinishLogMessage("已停止").LogInfo();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
StatusMessage = $"激光补偿异常:{ex.Message}";
|
||
LogManager.LogSysError(ex, true);
|
||
}
|
||
finally
|
||
{
|
||
IsRunning = false;
|
||
UiEnable = true;
|
||
_cancellationTokenSource?.Dispose();
|
||
_cancellationTokenSource = null;
|
||
IoC.Get<IProjectManager>().SwitchState();
|
||
}
|
||
}
|
||
|
||
public void Stop()
|
||
{
|
||
if (!IsRunning)
|
||
{
|
||
return;
|
||
}
|
||
|
||
StatusMessage = "正在停止激光补偿";
|
||
"激光补偿收到停止请求".LogInfo();
|
||
_cancellationTokenSource?.Cancel();
|
||
}
|
||
|
||
public void Save()
|
||
{
|
||
try
|
||
{
|
||
Setting.SaveToFile();
|
||
StatusMessage = "参数已保存";
|
||
"激光补偿参数已保存".LogInfo();
|
||
LocalizedMessageBox.Show(MessageKey.CommonSaveSucceeded, MessageKey.TitleInfo, System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
StatusMessage = $"参数保存失败:{ex.Message}";
|
||
LogManager.LogSysError(ex, true);
|
||
}
|
||
}
|
||
|
||
private async Task ExecuteCompensationAsync(CancellationToken cancellationToken)
|
||
{
|
||
IReadOnlyList<double> trajectory = BuildTrajectory();
|
||
|
||
ApplyAxisSpeedIfPossible();
|
||
|
||
for (int workIndex = 0; workIndex < Setting.WorkCount; workIndex++)
|
||
{
|
||
cancellationToken.ThrowIfCancellationRequested();
|
||
CurrentLoopIndex = workIndex + 1;
|
||
$"激光补偿开始第{CurrentLoopIndex}次循环,总点数={trajectory.Count}".LogInfo();
|
||
|
||
for (int pointIndex = 0; pointIndex < trajectory.Count; pointIndex++)
|
||
{
|
||
cancellationToken.ThrowIfCancellationRequested();
|
||
|
||
double targetPosition = trajectory[pointIndex];
|
||
CurrentPointIndex = pointIndex + 1;
|
||
CurrentTargetPosition = targetPosition;
|
||
StatusMessage = $"循环 {CurrentLoopIndex}/{Setting.WorkCount},点位 {CurrentPointIndex}/{trajectory.Count}";
|
||
|
||
int timeoutMilliseconds = CalculateMoveTimeoutMilliseconds(targetPosition, trajectory, pointIndex);
|
||
$"激光补偿移动开始:轴={Setting.AxisName},循环={CurrentLoopIndex},点位={CurrentPointIndex},目标={targetPosition:F4},超时={timeoutMilliseconds}ms".LogInfo();
|
||
|
||
MotionResult motionResult = await _safeAxisMotion.MoveAbsAsync(
|
||
Setting.AxisName,
|
||
targetPosition,
|
||
timeoutMilliseconds,
|
||
cancellationToken,
|
||
alarmId: null).ConfigureAwait(false);
|
||
|
||
motionResult.EnsureSuccess();
|
||
|
||
$"激光补偿移动完成:轴={Setting.AxisName},循环={CurrentLoopIndex},点位={CurrentPointIndex},目标={targetPosition:F4},到位={motionResult.EndPosition:F4}".LogInfo();
|
||
|
||
if (Setting.DwellTime > 0d)
|
||
{
|
||
int dwellMilliseconds = (int)Math.Ceiling(Setting.DwellTime * 1000d);
|
||
$"激光补偿延时采样:{dwellMilliseconds}ms".LogInfo();
|
||
await Task.Delay(dwellMilliseconds, cancellationToken).ConfigureAwait(false);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void ApplyAxisSpeedIfPossible()
|
||
{
|
||
try
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(Setting.AxisName) && Setting.Speed > 0d)
|
||
{
|
||
_axisSpeedManager.SetAxisSpeed(Setting.AxisName, Setting.Speed);
|
||
$"激光补偿轴速度已设置:轴={Setting.AxisName},Speed={Setting.Speed:F4}".LogInfo();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
$"激光补偿轴速度设置失败:{ex}".LogSysError();
|
||
throw;
|
||
}
|
||
}
|
||
|
||
private IReadOnlyList<double> BuildTrajectory()
|
||
{
|
||
List<double> measurePoints = Enumerable.Range(0, Setting.StepsCount)
|
||
.Select(index => Setting.StartPos + index * Setting.Steps)
|
||
.ToList();
|
||
|
||
double readyPosition = Setting.StartPos - Setting.JumpPos;
|
||
double overshootPosition = measurePoints[measurePoints.Count - 1] + Setting.JumpPos;
|
||
|
||
List<double> trajectory = new List<double>(measurePoints.Count * 2 + 3)
|
||
{
|
||
readyPosition
|
||
};
|
||
|
||
trajectory.AddRange(measurePoints);
|
||
trajectory.Add(overshootPosition);
|
||
trajectory.AddRange(measurePoints.AsEnumerable().Reverse());
|
||
trajectory.Add(readyPosition);
|
||
|
||
return trajectory;
|
||
}
|
||
|
||
private int CalculateMoveTimeoutMilliseconds(double targetPosition, IReadOnlyList<double> trajectory, int pointIndex)
|
||
{
|
||
IAxis axis = _hardwareManager.GetAxisByName(Setting.AxisName);
|
||
double startPosition = GetMoveStartPosition(axis, trajectory, pointIndex, targetPosition);
|
||
double distance = Math.Abs(targetPosition - startPosition);
|
||
double speed = ResolvePositiveMotionParameter(axis != null ? axis.Param.Velocity : 0d, Math.Abs(Setting.Speed));
|
||
if (speed <= 0d)
|
||
{
|
||
return MinimumMoveTimeoutMilliseconds;
|
||
}
|
||
|
||
double acceleration = ResolvePositiveMotionParameter(axis != null ? axis.Param.Accelerate : 0d, 0d);
|
||
double deceleration = ResolvePositiveMotionParameter(axis != null ? axis.Param.Decelerate : 0d, acceleration);
|
||
double expectedMilliseconds = CalculateExpectedMoveMilliseconds(distance, speed, acceleration, deceleration);
|
||
|
||
return Math.Max(MinimumMoveTimeoutMilliseconds, (int)Math.Ceiling(expectedMilliseconds * MoveTimeoutScaleFactor + MoveTimeoutExtraMilliseconds));
|
||
}
|
||
|
||
private static double GetMoveStartPosition(IAxis axis, IReadOnlyList<double> trajectory, int pointIndex, double targetPosition)
|
||
{
|
||
if (pointIndex > 0)
|
||
{
|
||
return trajectory[pointIndex - 1];
|
||
}
|
||
|
||
if (axis != null)
|
||
{
|
||
return axis.State != null ? axis.State.ActualPos : axis.GetPositionImmediate();
|
||
}
|
||
|
||
return targetPosition;
|
||
}
|
||
|
||
private static double ResolvePositiveMotionParameter(double primaryValue, double fallbackValue)
|
||
{
|
||
if (!double.IsNaN(primaryValue) && !double.IsInfinity(primaryValue) && primaryValue > 0d)
|
||
{
|
||
return primaryValue;
|
||
}
|
||
|
||
if (!double.IsNaN(fallbackValue) && !double.IsInfinity(fallbackValue) && fallbackValue > 0d)
|
||
{
|
||
return fallbackValue;
|
||
}
|
||
|
||
return 0d;
|
||
}
|
||
|
||
private static double CalculateExpectedMoveMilliseconds(double distance, double speed, double acceleration, double deceleration)
|
||
{
|
||
if (distance <= 0d)
|
||
{
|
||
return 0d;
|
||
}
|
||
|
||
if (speed <= 0d)
|
||
{
|
||
return 0d;
|
||
}
|
||
|
||
if (acceleration <= 0d || deceleration <= 0d)
|
||
{
|
||
return distance / speed * 1000d;
|
||
}
|
||
|
||
double accelerateDistance = speed * speed / (2d * acceleration);
|
||
double decelerateDistance = speed * speed / (2d * deceleration);
|
||
double criticalDistance = accelerateDistance + decelerateDistance;
|
||
double expectedSeconds;
|
||
|
||
if (distance >= criticalDistance)
|
||
{
|
||
expectedSeconds = speed / acceleration + speed / deceleration + (distance - criticalDistance) / speed;
|
||
}
|
||
else
|
||
{
|
||
double peakSpeed = Math.Sqrt(2d * distance * acceleration * deceleration / (acceleration + deceleration));
|
||
expectedSeconds = peakSpeed / acceleration + peakSpeed / deceleration;
|
||
}
|
||
|
||
return expectedSeconds * 1000d;
|
||
}
|
||
|
||
private string ValidateSetting()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(Setting.AxisName))
|
||
{
|
||
return "轴名称不能为空";
|
||
}
|
||
|
||
if (!AxisNames.Contains(Setting.AxisName))
|
||
{
|
||
return "请选择有效的轴名称";
|
||
}
|
||
|
||
if (Setting.StepsCount <= 0)
|
||
{
|
||
return "步距次数必须大于0";
|
||
}
|
||
|
||
if (Setting.WorkCount <= 0)
|
||
{
|
||
return "循环次数必须大于0";
|
||
}
|
||
|
||
if (Math.Abs(Setting.Steps) <= 0d)
|
||
{
|
||
return "步距必须非0";
|
||
}
|
||
|
||
if (Setting.JumpPos < 0d)
|
||
{
|
||
return "跃程不能小于0";
|
||
}
|
||
|
||
if (Setting.Speed <= 0d)
|
||
{
|
||
return "速度必须大于0";
|
||
}
|
||
|
||
if (Setting.DwellTime < 0d)
|
||
{
|
||
return "延时时间不能小于0";
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private string BuildStartLogMessage()
|
||
{
|
||
return $"激光补偿启动:轴={Setting.AxisName},StartPos={Setting.StartPos:F4},Steps={Setting.Steps:F4},StepsCount={Setting.StepsCount},JumpPos={Setting.JumpPos:F4},WorkCount={Setting.WorkCount},Speed={Setting.Speed:F4},DwellTime={Setting.DwellTime:F3}s";
|
||
}
|
||
|
||
private string BuildFinishLogMessage(string state)
|
||
{
|
||
return $"激光补偿结束:状态={state},轴={Setting.AxisName},已执行循环={CurrentLoopIndex},最后点位={CurrentPointIndex},最后目标={CurrentTargetPosition:F4}";
|
||
}
|
||
|
||
private IEnumerable<string> BuildAxisNames()
|
||
{
|
||
return new[]
|
||
{
|
||
GetAxisName(_hardwareManager?.Axis_PHS_X1, MainShell.Hardware.AxisName.Axis_PHS_X1),
|
||
GetAxisName(_hardwareManager?.Axis_PHS_X2, MainShell.Hardware.AxisName.Axis_PHS_X2),
|
||
GetAxisName(_hardwareManager?.Axis_PHS_Y1, MainShell.Hardware.AxisName.Axis_PHS_Y1),
|
||
GetAxisName(_hardwareManager?.Axis_WS_X3, MainShell.Hardware.AxisName.Axis_WS_X3),
|
||
GetAxisName(_hardwareManager?.Axis_Stage_Y3, MainShell.Hardware.AxisName.Axis_Stage_Y3),
|
||
GetAxisName(_hardwareManager?.Axis_WS_R, MainShell.Hardware.AxisName.Axis_WS_R),
|
||
}.Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToArray();
|
||
}
|
||
|
||
private static string GetAxisName(IAxis axis, string fallbackName)
|
||
{
|
||
if (axis == null || string.IsNullOrWhiteSpace(axis.Name))
|
||
{
|
||
return fallbackName;
|
||
}
|
||
|
||
return axis.Name;
|
||
}
|
||
}
|
||
}
|