using MainShell.Common; using MainShell.Hardware; using MainShell.Log; using MainShell.Parameter; using MaxwellFramework.Core.Attributes; using System; using System.Collections.Generic; using System.Linq; using static MainShell.ParaSetting.Model.SpeedSetting; namespace MainShell.Motion { public enum AxisSpeedProfile { Low, Medium, High, Custom } public sealed class AxisMotionProfile { public AxisMotionProfile(double velocity, double acceleration, double deceleration, double jerk) { Velocity = velocity; Acceleration = acceleration; Deceleration = deceleration; Jerk = jerk; } public double Velocity { get; } public double Acceleration { get; } public double Deceleration { get; } public double Jerk { get; } } [Singleton] public class AxisSpeedManager { private readonly GlobalParameterContext _globalParam; private readonly HardwareManager _hardware; public AxisSpeedManager(GlobalParameterContext globalParam, HardwareManager hardware) { _globalParam = globalParam ?? throw new ArgumentNullException(nameof(globalParam)); _hardware = hardware ?? throw new ArgumentNullException(nameof(hardware)); } public void SetAxisSpeed(string axisName, double speed) { var speedItem = GetRequiredSpeedTypeItem(axisName); var profile = CreateCustomProfile(speedItem, speed); ApplyAxisMotionProfile(axisName, profile, AxisSpeedProfile.Custom, null); } public void SetAxisSpeed(string axisName, AxisSpeedProfile profile) { var speedItem = GetRequiredSpeedTypeItem(axisName); var motionProfile = CreateProfile(speedItem, profile); ApplyAxisMotionProfile(axisName, motionProfile, profile, speedItem); } public void ApplySpeedSettings() { var speedItems = _globalParam?.SpeedParaSysSetting?.SpeedTypeItemCollection?.SpeedTypeItemList; if (speedItems == null || speedItems.Count == 0) { "Axis speed settings skipped because no speed configuration is available.".LogInfo(); return; } var currentProfile = MapCurrentSpeedTypeText(_globalParam.SpeedParaSysSetting.CurrentSpeedType.ToString()); foreach (var speedItem in speedItems) { if (speedItem == null || string.IsNullOrWhiteSpace(speedItem.AxisName)) { continue; } var profile = CreateProfile(speedItem, currentProfile); ApplyAxisMotionProfile(speedItem.AxisName, profile, currentProfile, speedItem); } } private void ApplyAxisMotionProfile(string axisName, AxisMotionProfile profile, AxisSpeedProfile profileType, SpeedTypeItem speedItem) { if (string.IsNullOrWhiteSpace(axisName)) { throw new ArgumentNullException(nameof(axisName)); } var axis = _hardware.GetAxisByName(axisName); if (axis == null) { throw new ArgumentException(string.Format("Axis with name {0} not found.", axisName), nameof(axisName)); } ValidateMotionProfile(profile, axisName); axis.Param.Velocity = profile.Velocity; axis.Param.Accelerate = profile.Acceleration; axis.Param.Decelerate = profile.Deceleration; axis.Param.Jerk = profile.Jerk; try { axis.IssueParam(); LogProfileApplied(axisName, profile, profileType, speedItem); } catch (Exception ex) { string.Format("Axis:{0} speed profile apply failed. Profile:{1} Error:{2}", axisName, profileType, ex.Message).LogSysError(); throw; } } private SpeedTypeItem GetRequiredSpeedTypeItem(string axisName) { if (string.IsNullOrWhiteSpace(axisName)) { throw new ArgumentNullException(nameof(axisName)); } var speedItem = _globalParam?.SpeedParaSysSetting?.SpeedTypeItemCollection?.SpeedTypeItemList? .FirstOrDefault(x => x != null && string.Equals(x.AxisName, axisName, StringComparison.OrdinalIgnoreCase)); if (speedItem == null) { throw new InvalidOperationException(string.Format("No speed configuration found for axis {0}.", axisName)); } return speedItem; } private static AxisMotionProfile CreateCustomProfile(SpeedTypeItem speedItem, double speed) { ValidatePositiveFinite(speed, nameof(speed), speedItem.AxisName); ValidatePositiveFinite(speedItem.Acc, nameof(speedItem.Acc), speedItem.AxisName); ValidatePositiveFinite(speedItem.Dec, nameof(speedItem.Dec), speedItem.AxisName); ValidatePositiveFinite(speedItem.Jerk, nameof(speedItem.Jerk), speedItem.AxisName); ValidatePositiveFinite(speedItem.Speed, nameof(speedItem.Speed), speedItem.AxisName); var scale = speed / speedItem.Speed; return new AxisMotionProfile( speed, speedItem.Acc * scale, speedItem.Dec * scale, speedItem.Jerk * scale); } private static AxisMotionProfile CreateProfile(SpeedTypeItem speedItem, AxisSpeedProfile profile) { if (speedItem == null) { throw new ArgumentNullException(nameof(speedItem)); } ValidatePositiveFinite(speedItem.Speed, nameof(speedItem.Speed), speedItem.AxisName); ValidatePositiveFinite(speedItem.Acc, nameof(speedItem.Acc), speedItem.AxisName); ValidatePositiveFinite(speedItem.Dec, nameof(speedItem.Dec), speedItem.AxisName); ValidatePositiveFinite(speedItem.Jerk, nameof(speedItem.Jerk), speedItem.AxisName); var percent = ResolveProfilePercent(speedItem, profile); var scale = percent / 100.0d; return new AxisMotionProfile( speedItem.Speed * scale, speedItem.Acc * scale, speedItem.Dec * scale, speedItem.Jerk * scale); } private static double ResolveProfilePercent(SpeedTypeItem speedItem, AxisSpeedProfile profile) { switch (profile) { case AxisSpeedProfile.Low: return ValidatePercent(speedItem.LowPercent, nameof(speedItem.LowPercent), speedItem.AxisName); case AxisSpeedProfile.Medium: return ValidatePercent(speedItem.MedianPercent, nameof(speedItem.MedianPercent), speedItem.AxisName); case AxisSpeedProfile.High: return ValidatePercent(speedItem.HighPercent, nameof(speedItem.HighPercent), speedItem.AxisName); default: return 100.0d; } } private static AxisSpeedProfile MapCurrentSpeedTypeText(string currentSpeedTypeText) { if (string.Equals(currentSpeedTypeText, "High", StringComparison.OrdinalIgnoreCase)) { return AxisSpeedProfile.High; } if (string.Equals(currentSpeedTypeText, "Median", StringComparison.OrdinalIgnoreCase) || string.Equals(currentSpeedTypeText, "Medium", StringComparison.OrdinalIgnoreCase)) { return AxisSpeedProfile.Medium; } return AxisSpeedProfile.Low; } private static void ValidateMotionProfile(AxisMotionProfile profile, string axisName) { if (profile == null) { throw new ArgumentNullException(nameof(profile)); } ValidatePositiveFinite(profile.Velocity, nameof(profile.Velocity), axisName); ValidatePositiveFinite(profile.Acceleration, nameof(profile.Acceleration), axisName); ValidatePositiveFinite(profile.Deceleration, nameof(profile.Deceleration), axisName); ValidatePositiveFinite(profile.Jerk, nameof(profile.Jerk), axisName); } private static void ValidatePositiveFinite(double value, string propertyName, string axisName) { if (double.IsNaN(value) || double.IsInfinity(value) || value <= 0d) { throw new ArgumentOutOfRangeException(propertyName, string.Format("Axis {0} has invalid motion parameter {1}: {2}.", axisName, propertyName, value)); } } private static double ValidatePercent(double value, string propertyName, string axisName) { if (double.IsNaN(value) || double.IsInfinity(value) || value <= 0d || value > 100d) { throw new ArgumentOutOfRangeException(propertyName, string.Format("Axis {0} has invalid speed profile percent {1}: {2}.", axisName, propertyName, value)); } return value; } private static void LogProfileApplied(string axisName, AxisMotionProfile profile, AxisSpeedProfile profileType, SpeedTypeItem speedItem) { var source = speedItem != null ? "ConfiguredProfile" : "CustomScaledProfile"; string.Format( "Axis:{0} speed profile applied. Source:{1} Profile:{2} Velocity:{3:F3} Acc:{4:F3} Dec:{5:F3} Jerk:{6:F3}", axisName, source, profileType, profile.Velocity, profile.Acceleration, profile.Deceleration, profile.Jerk).LogInfo(); } } }