添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
@@ -0,0 +1,355 @@
|
||||
using MaxwellFramework.Core.Attributes;
|
||||
using MainShell.Hardware;
|
||||
using MwFramework.Device;
|
||||
using System;
|
||||
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
public interface IAcsAddressAccessService
|
||||
{
|
||||
bool IsReady { get; }
|
||||
|
||||
object Read(AcsAddressDefinition addressDefinition);
|
||||
|
||||
object Read(AcsAddressDefinition addressDefinition, int index1, int index2);
|
||||
|
||||
object ReadRange(AcsAddressDefinition addressDefinition, int from1, int to1, int from2, int to2);
|
||||
|
||||
T Read<T>(AcsAddressDefinition addressDefinition);
|
||||
|
||||
bool Write(AcsAddressDefinition addressDefinition, object value);
|
||||
|
||||
bool Write(AcsAddressDefinition addressDefinition, object value, int index1, int index2);
|
||||
|
||||
bool WriteRange(AcsAddressDefinition addressDefinition, object value, int from1, int to1, int from2, int to2);
|
||||
}
|
||||
|
||||
[Singleton]
|
||||
public sealed class AcsAddressAccessService : IAcsAddressAccessService
|
||||
{
|
||||
private const int DefaultBufferId = -1;
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
|
||||
public AcsAddressAccessService(HardwareManager hardwareManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
}
|
||||
|
||||
public bool IsReady
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hardwareManager.AcsCard != null
|
||||
&& _hardwareManager.AcsCard.Controller != null
|
||||
&& _hardwareManager.AcsCard.IsOpen;
|
||||
}
|
||||
}
|
||||
|
||||
public object Read(AcsAddressDefinition addressDefinition)
|
||||
{
|
||||
AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
|
||||
IMotionController controller = GetController();
|
||||
string variableName = ResolveVariableName(validatedAddress);
|
||||
bool result= controller.ReadVariableBuffer(DefaultBufferId, variableName,out object val);
|
||||
if (!result)
|
||||
{
|
||||
throw CreateReadWriteException("Read", validatedAddress);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public object Read(AcsAddressDefinition addressDefinition, int index1, int index2)
|
||||
{
|
||||
AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
|
||||
ValidateIndex(index1, nameof(index1));
|
||||
ValidateIndex(index2, nameof(index2));
|
||||
IMotionController controller = GetController();
|
||||
string variableName = ResolveVariableName(validatedAddress);
|
||||
bool result = controller.ReadVariableBuffer(DefaultBufferId, variableName, out object val, index1, index2);
|
||||
if (!result)
|
||||
{
|
||||
throw CreateReadWriteException("Read", validatedAddress);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public object ReadRange(AcsAddressDefinition addressDefinition, int from1, int to1, int from2, int to2)
|
||||
{
|
||||
AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
|
||||
ValidateRange(from1, to1, nameof(from1), nameof(to1));
|
||||
ValidateRange(from2, to2, nameof(from2), nameof(to2));
|
||||
|
||||
IMotionController controller = GetController();
|
||||
string variableName = ResolveVariableName(validatedAddress);
|
||||
object value;
|
||||
bool result = controller.ReadVariableBuffer(DefaultBufferId, variableName, out value, from1, to1, from2, to2);
|
||||
if (!result)
|
||||
{
|
||||
throw CreateReadWriteException("Read", validatedAddress);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public T Read<T>(AcsAddressDefinition addressDefinition)
|
||||
{
|
||||
AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
|
||||
ValidateTypeCompatibility(validatedAddress, typeof(T));
|
||||
object value = Read(validatedAddress);
|
||||
return ConvertValue<T>(value, validatedAddress);
|
||||
}
|
||||
|
||||
public bool Write(AcsAddressDefinition addressDefinition, object value)
|
||||
{
|
||||
AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
|
||||
ValidateWriteValue(validatedAddress, value);
|
||||
IMotionController controller = GetController();
|
||||
string variableName = ResolveVariableName(validatedAddress);
|
||||
bool result = controller.WriteVariableBuffer(DefaultBufferId, variableName, value);
|
||||
if (!result)
|
||||
{
|
||||
throw CreateReadWriteException("Write", validatedAddress);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Write(AcsAddressDefinition addressDefinition, object value, int index1, int index2)
|
||||
{
|
||||
AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
|
||||
ValidateWriteValue(validatedAddress, value);
|
||||
ValidateIndex(index1, nameof(index1));
|
||||
ValidateIndex(index2, nameof(index2));
|
||||
IMotionController controller = GetController();
|
||||
string variableName = ResolveVariableName(validatedAddress);
|
||||
bool result = controller.WriteVariableBuffer(DefaultBufferId, variableName, value, index1, index2);
|
||||
if (!result)
|
||||
{
|
||||
throw CreateReadWriteException("Write", validatedAddress);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool WriteRange(AcsAddressDefinition addressDefinition, object value, int from1, int to1, int from2, int to2)
|
||||
{
|
||||
AcsAddressDefinition validatedAddress = ValidateAddress(addressDefinition);
|
||||
ValidateWriteValue(validatedAddress, value);
|
||||
ValidateRange(from1, to1, nameof(from1), nameof(to1));
|
||||
ValidateRange(from2, to2, nameof(from2), nameof(to2));
|
||||
IMotionController controller = GetController();
|
||||
string variableName = ResolveVariableName(validatedAddress);
|
||||
bool result = controller.WriteVariableBuffer(DefaultBufferId, variableName, value, from1, to1, from2, to2);
|
||||
if (!result)
|
||||
{
|
||||
throw CreateReadWriteException("Write", validatedAddress);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IMotionController GetController()
|
||||
{
|
||||
if (!IsReady)
|
||||
{
|
||||
throw new InvalidOperationException("ACS controller is not ready. Ensure AcsCard is initialized and opened.");
|
||||
}
|
||||
|
||||
return _hardwareManager.AcsCard.Controller;
|
||||
}
|
||||
|
||||
private static AcsAddressDefinition ValidateAddress(AcsAddressDefinition addressDefinition)
|
||||
{
|
||||
if (addressDefinition == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(addressDefinition));
|
||||
}
|
||||
|
||||
if (!addressDefinition.HasNumericAddress && !addressDefinition.HasSymbolicAddress)
|
||||
{
|
||||
throw new ArgumentException("ACS address definition does not contain a usable address.", nameof(addressDefinition));
|
||||
}
|
||||
|
||||
return addressDefinition;
|
||||
}
|
||||
|
||||
private static string ResolveVariableName(AcsAddressDefinition addressDefinition)
|
||||
{
|
||||
if (addressDefinition.HasSymbolicAddress)
|
||||
{
|
||||
return addressDefinition.SymbolicAddress;
|
||||
}
|
||||
|
||||
return addressDefinition.Address.Value.ToString();
|
||||
}
|
||||
|
||||
private static void ValidateTypeCompatibility(AcsAddressDefinition addressDefinition, Type targetType)
|
||||
{
|
||||
if (targetType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
}
|
||||
|
||||
Type nonNullableTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
|
||||
if (nonNullableTargetType == typeof(object))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (addressDefinition.DataType == nonNullableTargetType)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (addressDefinition.IsInteger && IsIntegerType(nonNullableTargetType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (addressDefinition.IsFloatingPoint && IsFloatingPointType(nonNullableTargetType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Address {addressDefinition.Name} is defined as {addressDefinition.DataType.Name} and cannot be read as {nonNullableTargetType.Name}.");
|
||||
}
|
||||
|
||||
private static T ConvertValue<T>(object value, AcsAddressDefinition addressDefinition)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
|
||||
if (value is T typedValue)
|
||||
{
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
Type targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
|
||||
try
|
||||
{
|
||||
if (targetType == typeof(bool))
|
||||
{
|
||||
return (T)(object)ConvertToBoolean(value);
|
||||
}
|
||||
|
||||
object convertedValue = Convert.ChangeType(value, targetType);
|
||||
return (T)convertedValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidCastException($"The value of address {addressDefinition.Name} cannot be converted to {targetType.Name}.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ConvertToBoolean(object value)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return boolValue;
|
||||
}
|
||||
|
||||
if (value is string stringValue)
|
||||
{
|
||||
if (string.Equals(stringValue, "1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(stringValue, "0", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return Convert.ToDouble(value) != 0d;
|
||||
}
|
||||
|
||||
private static void ValidateWriteValue(AcsAddressDefinition addressDefinition, object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (addressDefinition.Length > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Type valueType = value.GetType();
|
||||
if (addressDefinition.DataType.IsAssignableFrom(valueType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (addressDefinition.IsBoolean && IsBooleanCompatibleType(valueType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (addressDefinition.IsInteger && IsIntegerType(valueType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (addressDefinition.IsFloatingPoint && (IsFloatingPointType(valueType) || IsIntegerType(valueType)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Address {addressDefinition.Name} is defined as {addressDefinition.DataType.Name}, but the current value type is {valueType.Name}.");
|
||||
}
|
||||
|
||||
private static bool IsBooleanCompatibleType(Type type)
|
||||
{
|
||||
return type == typeof(bool) || IsIntegerType(type);
|
||||
}
|
||||
|
||||
private static bool IsIntegerType(Type type)
|
||||
{
|
||||
return type == typeof(byte)
|
||||
|| type == typeof(sbyte)
|
||||
|| type == typeof(short)
|
||||
|| type == typeof(ushort)
|
||||
|| type == typeof(int)
|
||||
|| type == typeof(uint)
|
||||
|| type == typeof(long)
|
||||
|| type == typeof(ulong);
|
||||
}
|
||||
|
||||
private static bool IsFloatingPointType(Type type)
|
||||
{
|
||||
return type == typeof(float)
|
||||
|| type == typeof(double)
|
||||
|| type == typeof(decimal);
|
||||
}
|
||||
|
||||
private static void ValidateIndex(int index, string parameterName)
|
||||
{
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(parameterName, index, "ACS index cannot be less than 0.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateRange(int from, int to, string fromName, string toName)
|
||||
{
|
||||
ValidateIndex(from, fromName);
|
||||
ValidateIndex(to, toName);
|
||||
if (to < from)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {toName} cannot be less than {fromName}.", toName);
|
||||
}
|
||||
}
|
||||
|
||||
private static Exception CreateReadWriteException(string operationName, AcsAddressDefinition addressDefinition)
|
||||
{
|
||||
string addressText = addressDefinition.HasSymbolicAddress
|
||||
? addressDefinition.SymbolicAddress
|
||||
: addressDefinition.Address.Value.ToString();
|
||||
return new InvalidOperationException($"{operationName} ACS address failed: {addressDefinition.Name}, address={addressText}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
using System;
|
||||
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents one ACS controller address definition.
|
||||
/// Used to describe read/write address, data type, length, and note.
|
||||
/// </summary>
|
||||
public sealed class AcsAddressDefinition
|
||||
{
|
||||
public AcsAddressDefinition(string name, int address, Type dataType, int length = 1, string description = "")
|
||||
: this(name, dataType, length, description, address, null)
|
||||
{
|
||||
}
|
||||
|
||||
public AcsAddressDefinition(string name, string symbolicAddress, Type dataType, int length = 1, string description = "")
|
||||
: this(name, dataType, length, description, null, symbolicAddress)
|
||||
{
|
||||
}
|
||||
|
||||
private AcsAddressDefinition(string name, Type dataType, int length, string description, int? address, string symbolicAddress)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("ACS address name cannot be empty.", nameof(name));
|
||||
}
|
||||
|
||||
if (dataType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dataType));
|
||||
}
|
||||
|
||||
if (length <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(length), length, "ACS address length must be greater than 0.");
|
||||
}
|
||||
|
||||
if (address.HasValue && address.Value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(address), address, "ACS address cannot be less than 0.");
|
||||
}
|
||||
|
||||
if (!address.HasValue && string.IsNullOrWhiteSpace(symbolicAddress))
|
||||
{
|
||||
throw new ArgumentException("ACS address cannot be empty.", nameof(symbolicAddress));
|
||||
}
|
||||
|
||||
Name = name;
|
||||
Address = address;
|
||||
SymbolicAddress = symbolicAddress ?? string.Empty;
|
||||
DataType = dataType;
|
||||
Length = length;
|
||||
Description = description ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Semantic name, for example ServoOn or ActualPosition.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Numeric address in the ACS controller.
|
||||
/// </summary>
|
||||
public int? Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Symbolic address in the ACS controller, for example AP_SAPos1.
|
||||
/// </summary>
|
||||
public string SymbolicAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Data type mapped to this address.
|
||||
/// </summary>
|
||||
public Type DataType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Continuous address length. Default is 1.
|
||||
/// </summary>
|
||||
public int Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Address description for maintenance and diagnostics.
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
public bool HasNumericAddress
|
||||
{
|
||||
get { return Address.HasValue; }
|
||||
}
|
||||
|
||||
public bool HasSymbolicAddress
|
||||
{
|
||||
get { return !string.IsNullOrWhiteSpace(SymbolicAddress); }
|
||||
}
|
||||
|
||||
public bool IsBoolean
|
||||
{
|
||||
get { return DataType == typeof(bool); }
|
||||
}
|
||||
|
||||
public bool IsInteger
|
||||
{
|
||||
get { return DataType == typeof(short) || DataType == typeof(ushort) || DataType == typeof(int) || DataType == typeof(uint) || DataType == typeof(long) || DataType == typeof(ulong); }
|
||||
}
|
||||
|
||||
public bool IsFloatingPoint
|
||||
{
|
||||
get { return DataType == typeof(float) || DataType == typeof(double) || DataType == typeof(decimal); }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (HasNumericAddress)
|
||||
{
|
||||
return $"{Name} [Address={Address.Value}, Type={DataType.Name}, Length={Length}]";
|
||||
}
|
||||
|
||||
return $"{Name} [Symbol={SymbolicAddress}, Type={DataType.Name}, Length={Length}]";
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateBool(string name, int address, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, address, typeof(bool), 1, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateBool(string name, int address, int length, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, address, typeof(bool), length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateInt32(string name, int address, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, address, typeof(int), 1, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateInt32(string name, int address, int length, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, address, typeof(int), length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateDouble(string name, int address, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, address, typeof(double), 1, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateDouble(string name, int address, int length, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, address, typeof(double), length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateSymbol(string name, string symbolicAddress, Type dataType, int length = 1, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, symbolicAddress, dataType, length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateSymbol(string name, Type dataType, int length = 1, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, name, dataType, length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateBoolSymbol(string name, string symbolicAddress, int length = 1, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, symbolicAddress, typeof(bool), length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateBoolSymbol(string name, int length = 1, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, name, typeof(bool), length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateInt32Symbol(string name, string symbolicAddress, int length = 1, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, symbolicAddress, typeof(int), length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateInt32Symbol(string name, int length = 1, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, name, typeof(int), length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateDoubleSymbol(string name, string symbolicAddress, int length = 1, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, symbolicAddress, typeof(double), length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateDoubleSymbol(string name, int length = 1, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, name, typeof(double), length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateString(string name, int address, int length, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, address, typeof(string), length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateStringSymbol(string name, string symbolicAddress, int length, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, symbolicAddress, typeof(string), length, description);
|
||||
}
|
||||
|
||||
public static AcsAddressDefinition CreateStringSymbol(string name, int length, string description = "")
|
||||
{
|
||||
return new AcsAddressDefinition(name, name, typeof(string), length, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using MaxwellFramework.Core.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
[Singleton]
|
||||
public sealed class AcsAddressRepository
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly Dictionary<string, AcsBondingCommunicationAdress> _bondingCommunicationDataCache = new Dictionary<string, AcsBondingCommunicationAdress>(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, AcsBondingLogAdress> _bondingLogDataCache = new Dictionary<string, AcsBondingLogAdress>(StringComparer.Ordinal);
|
||||
private readonly Lazy<AcsCommonAdress> _commonData = new Lazy<AcsCommonAdress>(() => new AcsCommonAdress());
|
||||
private readonly Lazy<AcsInterferometerCompensationAdress> _interferometerCompensationData = new Lazy<AcsInterferometerCompensationAdress>(() => new AcsInterferometerCompensationAdress());
|
||||
private readonly Lazy<AcsHomeOffsetAdress> _homeOffsetData = new Lazy<AcsHomeOffsetAdress>(() => new AcsHomeOffsetAdress());
|
||||
private readonly Lazy<AcsHomeSettingAdress> _homeSettingData = new Lazy<AcsHomeSettingAdress>(() => new AcsHomeSettingAdress());
|
||||
private readonly Lazy<AcsBondingAdress> _newBondingData = new Lazy<AcsBondingAdress>(() => new AcsBondingAdress());
|
||||
|
||||
public AcsBondingCommunicationAdress GetBondingCommunicationData(string addressSuffix)
|
||||
{
|
||||
string normalizedSuffix = NormalizeAddressSuffix(addressSuffix);
|
||||
AcsBondingCommunicationAdress communicationData;
|
||||
if (_bondingCommunicationDataCache.TryGetValue(normalizedSuffix, out communicationData))
|
||||
{
|
||||
return communicationData;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_bondingCommunicationDataCache.TryGetValue(normalizedSuffix, out communicationData))
|
||||
{
|
||||
return communicationData;
|
||||
}
|
||||
|
||||
communicationData = new AcsBondingCommunicationAdress(normalizedSuffix);
|
||||
_bondingCommunicationDataCache[normalizedSuffix] = communicationData;
|
||||
return communicationData;
|
||||
}
|
||||
}
|
||||
|
||||
public AcsBondingLogAdress GetBondingLogData(string addressSuffix)
|
||||
{
|
||||
string normalizedSuffix = NormalizeAddressSuffix(addressSuffix);
|
||||
AcsBondingLogAdress logData;
|
||||
if (_bondingLogDataCache.TryGetValue(normalizedSuffix, out logData))
|
||||
{
|
||||
return logData;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_bondingLogDataCache.TryGetValue(normalizedSuffix, out logData))
|
||||
{
|
||||
return logData;
|
||||
}
|
||||
|
||||
logData = new AcsBondingLogAdress(normalizedSuffix);
|
||||
_bondingLogDataCache[normalizedSuffix] = logData;
|
||||
return logData;
|
||||
}
|
||||
}
|
||||
|
||||
public AcsInterferometerCompensationAdress GetInterferometerCompensationData()
|
||||
{
|
||||
return _interferometerCompensationData.Value;
|
||||
}
|
||||
|
||||
public AcsCommonAdress GetCommonData()
|
||||
{
|
||||
return _commonData.Value;
|
||||
}
|
||||
|
||||
public AcsHomeOffsetAdress GetHomeOffsetData()
|
||||
{
|
||||
return _homeOffsetData.Value;
|
||||
}
|
||||
|
||||
public AcsHomeSettingAdress GetHomeSettingData()
|
||||
{
|
||||
return _homeSettingData.Value;
|
||||
}
|
||||
|
||||
public AcsBondingAdress GetNewBondingData()
|
||||
{
|
||||
return _newBondingData.Value;
|
||||
}
|
||||
|
||||
private static string NormalizeAddressSuffix(string addressSuffix)
|
||||
{
|
||||
if (addressSuffix == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(addressSuffix));
|
||||
}
|
||||
|
||||
string normalizedSuffix = addressSuffix.Trim();
|
||||
if (normalizedSuffix.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("ACS address suffix cannot be empty.", nameof(addressSuffix));
|
||||
}
|
||||
|
||||
return normalizedSuffix;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
public sealed class AcsBondingAdress
|
||||
{
|
||||
public const string SumIndexName = "SumIndex";
|
||||
public const string PA_BondSumRowName = "PA_BondSumRow";
|
||||
public const string PA_DataSendFinishName = "PA_DataSendFinish";
|
||||
public const string PA_FBBondStopName = "PA_FBBondStop";
|
||||
public const string AP_FBBondingFinishName = "AP_FBBondingFinish";
|
||||
public const string PA_HomeAxisName = "PA_HomeAxis";
|
||||
public const string PHSCurrentRowName = "PHSCurrentRow";
|
||||
public const string AP_LogIndexAName = "AP_LogIndexA";
|
||||
public const string AP_LogIndexBName = "AP_LogIndexB";
|
||||
|
||||
public const string AP_LogIndexASymbol = "AP_LogIndexPerA";
|
||||
public const string AP_LogIndexBSymbol = "AP_LogIndexPerB";
|
||||
|
||||
public AcsBondingAdress()
|
||||
{
|
||||
SumIndex = AcsAddressDefinition.CreateInt32Symbol(SumIndexName, 1, "当前固晶计数。");
|
||||
PA_BondSumRow = AcsAddressDefinition.CreateInt32Symbol(PA_BondSumRowName, 1, "固晶总行数。");
|
||||
PA_DataSendFinish = AcsAddressDefinition.CreateInt32Symbol(PA_DataSendFinishName, 1, "数据发送完成标志。");
|
||||
PA_FBBondStop = AcsAddressDefinition.CreateInt32Symbol(PA_FBBondStopName, 1, "固晶停止标志,1 表示停止。");
|
||||
AP_FBBondingFinish = AcsAddressDefinition.CreateInt32Symbol(AP_FBBondingFinishName, 1, "固晶完成状态,0 表示进行中,1 表示完成,-1 表示默认值。");
|
||||
PA_HomeAxis = AcsAddressDefinition.CreateInt32Symbol(PA_HomeAxisName, 1, "回零目标轴号。");
|
||||
PHSCurrentRow = AcsAddressDefinition.CreateInt32Symbol(PHSCurrentRowName, 1, "当前固晶行索引。");
|
||||
AP_LogIndexA = AcsAddressDefinition.CreateInt32Symbol(AP_LogIndexAName, AP_LogIndexASymbol, 1, "A 区固晶日志计数。");
|
||||
AP_LogIndexB = AcsAddressDefinition.CreateInt32Symbol(AP_LogIndexBName, AP_LogIndexBSymbol, 1, "B 区固晶日志计数。");
|
||||
}
|
||||
|
||||
public AcsAddressDefinition SumIndex { get; }
|
||||
|
||||
public AcsAddressDefinition PA_BondSumRow { get; }
|
||||
|
||||
public AcsAddressDefinition PA_DataSendFinish { get; }
|
||||
|
||||
public AcsAddressDefinition PA_FBBondStop { get; }
|
||||
|
||||
public AcsAddressDefinition AP_FBBondingFinish { get; }
|
||||
|
||||
public AcsAddressDefinition PA_HomeAxis { get; }
|
||||
|
||||
public AcsAddressDefinition PHSCurrentRow { get; }
|
||||
|
||||
public AcsAddressDefinition AP_LogIndexA { get; }
|
||||
|
||||
public AcsAddressDefinition AP_LogIndexB { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
public sealed class AcsBondingCommunicationAdress
|
||||
{
|
||||
public const string PA_X1PosArrayName = "PA_X1Pos_Array";
|
||||
public const string PA_X2PosArrayName = "PA_X2Pos_Array";
|
||||
public const string PA_Y1PosArrayName = "PA_Y1Pos_Array";
|
||||
public const string PA_Y2PosArrayName = "PA_Y2Pos_Array";
|
||||
public const string PA_RowSumName = "PA_RowSum";
|
||||
public const string PA_RowPointCountArrayName = "PA_RowPointCount_Array";
|
||||
public const string PA_CZIsBondingArrayName = "PA_CZIsBonding_Array";
|
||||
public const string PA_BigChangeRowArrayName = "PA_BigChangeRow_Array";
|
||||
public const string AP_FBStartName = "AP_FBStart";
|
||||
public const string PA_BonDataSendFinishName = "PA_BonDataSendFinish";
|
||||
public const string PA_BonNumberName = "PA_BonNumber";
|
||||
public const string PA_BonEndName = "PA_BonEnd";
|
||||
public const string PA_PadPitchName = "PA_PadPitch";
|
||||
public const string PA_CZoffsetName = "PA_CZoffset";
|
||||
|
||||
public const string PA_X1FBPosSymbol = "PA_X1FBPos";
|
||||
public const string PA_X2FBPosSymbol = "PA_X2FBPos";
|
||||
public const string PA_Y1FBPosSymbol = "PA_Y1FBPos";
|
||||
public const string PA_Y2FBPosSymbol = "PA_Y2FBPos";
|
||||
public const string PA_RowSumSymbol = "PA_RowSum";
|
||||
public const string PA_RowPointCountSymbol = "PA_RowPointCount";
|
||||
public const string PA_CZIsBondingSymbol = "PA_CZIsBonding";
|
||||
public const string PA_BigChangeRowSymbol = "PA_BigChangeRow";
|
||||
public const string AP_FBStartSymbol = "AP_FBStart";
|
||||
public const string PA_BonDataSendFinishSymbol = "PA_BonDataSendFinish";
|
||||
public const string PA_BonNumberSymbol = "PA_BonNumber";
|
||||
public const string PA_BonEndSymbol = "PA_BonEnd";
|
||||
public const string PA_PadPitchSymbol = "PA_PadPitch";
|
||||
public const string PA_CZoffsetSymbol = "PA_CZoffset";
|
||||
|
||||
public AcsBondingCommunicationAdress(string addressSuffix)
|
||||
{
|
||||
if (addressSuffix == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(addressSuffix));
|
||||
}
|
||||
|
||||
PA_X1Pos_Array = AcsAddressDefinition.CreateDoubleSymbol(PA_X1PosArrayName, PA_X1FBPosSymbol + addressSuffix, 10000, "A 区 X1 位置数组,长度为 10000。");
|
||||
PA_X2Pos_Array = AcsAddressDefinition.CreateDoubleSymbol(PA_X2PosArrayName, PA_X2FBPosSymbol + addressSuffix, 10000, "A 区 X2 位置数组,长度为 10000。");
|
||||
PA_Y1Pos_Array = AcsAddressDefinition.CreateDoubleSymbol(PA_Y1PosArrayName, PA_Y1FBPosSymbol + addressSuffix, 10000, "A 区 Y1 位置数组,长度为 10000。");
|
||||
PA_Y2Pos_Array = AcsAddressDefinition.CreateDoubleSymbol(PA_Y2PosArrayName, PA_Y2FBPosSymbol + addressSuffix, 10000, "A 区 Y2 位置数组,长度为 10000。");
|
||||
PA_RowSum = AcsAddressDefinition.CreateInt32Symbol(PA_RowSumName, PA_RowSumSymbol + addressSuffix, 1, "A 区已下发的固晶行数。");
|
||||
PA_RowPointCount_Array = AcsAddressDefinition.CreateInt32Symbol(PA_RowPointCountArrayName, PA_RowPointCountSymbol + addressSuffix, 10000, "A 区每行固晶点数数组。");
|
||||
PA_CZIsBonding_Array = AcsAddressDefinition.CreateBoolSymbol(PA_CZIsBondingArrayName, PA_CZIsBondingSymbol + addressSuffix, 10000, "A 区各点是否处于固晶状态。");
|
||||
PA_BigChangeRow_Array = AcsAddressDefinition.CreateBoolSymbol(PA_BigChangeRowArrayName, PA_BigChangeRowSymbol + addressSuffix, 10000, "A 区大跨行标记数组。");
|
||||
AP_FBStart = AcsAddressDefinition.CreateInt32Symbol(AP_FBStartName, AP_FBStartSymbol + addressSuffix, 1, "A 区固晶启动标志,1 表示启动。");
|
||||
PA_BonDataSendFinish = AcsAddressDefinition.CreateInt32Symbol(PA_BonDataSendFinishName, PA_BonDataSendFinishSymbol + addressSuffix, 1, "补固数据发送完成标志。");
|
||||
PA_BonNumber = AcsAddressDefinition.CreateInt32Symbol(PA_BonNumberName, PA_BonNumberSymbol + addressSuffix, 1, "各区补固次数。");
|
||||
PA_BonEnd = AcsAddressDefinition.CreateInt32Symbol(PA_BonEndName, PA_BonEndSymbol + addressSuffix, 1, "补固结束标志。");
|
||||
PA_PadPitch = AcsAddressDefinition.CreateDoubleSymbol(PA_PadPitchName, PA_PadPitchSymbol + addressSuffix, 1, "焊盘间距参数。");
|
||||
PA_CZoffset = AcsAddressDefinition.CreateDoubleSymbol(PA_CZoffsetName, PA_CZoffsetSymbol + addressSuffix, 1, "CZ 偏移量。");
|
||||
}
|
||||
|
||||
public AcsAddressDefinition PA_X1Pos_Array { get; }
|
||||
|
||||
public AcsAddressDefinition PA_X2Pos_Array { get; }
|
||||
|
||||
public AcsAddressDefinition PA_Y1Pos_Array { get; }
|
||||
|
||||
public AcsAddressDefinition PA_Y2Pos_Array { get; }
|
||||
|
||||
public AcsAddressDefinition PA_RowSum { get; }
|
||||
|
||||
public AcsAddressDefinition PA_RowPointCount_Array { get; }
|
||||
|
||||
public AcsAddressDefinition PA_CZIsBonding_Array { get; }
|
||||
|
||||
public AcsAddressDefinition PA_BigChangeRow_Array { get; }
|
||||
|
||||
public AcsAddressDefinition AP_FBStart { get; }
|
||||
|
||||
public AcsAddressDefinition PA_BonDataSendFinish { get; }
|
||||
|
||||
public AcsAddressDefinition PA_BonNumber { get; }
|
||||
|
||||
public AcsAddressDefinition PA_BonEnd { get; }
|
||||
|
||||
public AcsAddressDefinition PA_PadPitch { get; }
|
||||
|
||||
public AcsAddressDefinition PA_CZoffset { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
public sealed class AcsBondingLogAdress
|
||||
{
|
||||
public const string AP_SAPosName = "AP_SAPos";
|
||||
public const string AP_SFPosName = "AP_SFPos";
|
||||
public const string AP_SAvelName = "AP_SAvel";
|
||||
public const string AP_SFvelName = "AP_SFvel";
|
||||
public const string AP_SPeName = "AP_SPe";
|
||||
public const string AP_EFPosName = "AP_EFPos";
|
||||
public const string AP_EAvelName = "AP_EAvel";
|
||||
public const string AP_EAPosName = "AP_EAPos";
|
||||
public const string AP_EFvelName = "AP_EFvel";
|
||||
public const string AP_EPeName = "AP_EPe";
|
||||
|
||||
public AcsBondingLogAdress(string addressSuffix)
|
||||
{
|
||||
if (addressSuffix == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(addressSuffix));
|
||||
}
|
||||
|
||||
AP_SAPos = AcsAddressDefinition.CreateDoubleSymbol(AP_SAPosName, AP_SAPosName + addressSuffix, 1, "固晶起始 A 位置日志。");
|
||||
AP_SFPos = AcsAddressDefinition.CreateDoubleSymbol(AP_SFPosName, AP_SFPosName + addressSuffix, 1, "固晶起始 F 位置日志。");
|
||||
AP_SAvel = AcsAddressDefinition.CreateDoubleSymbol(AP_SAvelName, AP_SAvelName + addressSuffix, 1, "固晶起始 A 速度日志。");
|
||||
AP_SFvel = AcsAddressDefinition.CreateDoubleSymbol(AP_SFvelName, AP_SFvelName + addressSuffix, 1, "固晶起始 F 速度日志。");
|
||||
AP_SPe = AcsAddressDefinition.CreateDoubleSymbol(AP_SPeName, AP_SPeName + addressSuffix, 1, "固晶起始误差日志。");
|
||||
AP_EFPos = AcsAddressDefinition.CreateDoubleSymbol(AP_EFPosName, AP_EFPosName + addressSuffix, 1, "固晶结束 F 位置日志。");
|
||||
AP_EAvel = AcsAddressDefinition.CreateDoubleSymbol(AP_EAvelName, AP_EAvelName + addressSuffix, 1, "固晶结束 A 速度日志。");
|
||||
AP_EAPos = AcsAddressDefinition.CreateDoubleSymbol(AP_EAPosName, AP_EAPosName + addressSuffix, 1, "固晶结束 A 位置日志。");
|
||||
AP_EFvel = AcsAddressDefinition.CreateDoubleSymbol(AP_EFvelName, AP_EFvelName + addressSuffix, 1, "固晶结束 F 速度日志。");
|
||||
AP_EPe = AcsAddressDefinition.CreateDoubleSymbol(AP_EPeName, AP_EPeName + addressSuffix, 1, "固晶结束误差日志。");
|
||||
}
|
||||
|
||||
public AcsAddressDefinition AP_SAPos { get; }
|
||||
|
||||
public AcsAddressDefinition AP_SFPos { get; }
|
||||
|
||||
public AcsAddressDefinition AP_SAvel { get; }
|
||||
|
||||
public AcsAddressDefinition AP_SFvel { get; }
|
||||
|
||||
public AcsAddressDefinition AP_SPe { get; }
|
||||
|
||||
public AcsAddressDefinition AP_EFPos { get; }
|
||||
|
||||
public AcsAddressDefinition AP_EAvel { get; }
|
||||
|
||||
public AcsAddressDefinition AP_EAPos { get; }
|
||||
|
||||
public AcsAddressDefinition AP_EFvel { get; }
|
||||
|
||||
public AcsAddressDefinition AP_EPe { get; }
|
||||
|
||||
public IReadOnlyList<AcsAddressDefinition> GetAddresses()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
AP_SAPos,
|
||||
AP_SFPos,
|
||||
AP_SAvel,
|
||||
AP_SFvel,
|
||||
AP_SPe,
|
||||
AP_EFPos,
|
||||
AP_EAvel,
|
||||
AP_EAPos,
|
||||
AP_EFvel,
|
||||
AP_EPe
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
public sealed class AcsCommonAdress
|
||||
{
|
||||
public const string AP_ComSupMotionResName = "AP_ComSupMotionRes";
|
||||
public const string PA_ComSupMotionTypeName = "PA_ComSupMotionType";
|
||||
public const string PA_ChangeParaIndexName = "PA_ChangeParaIndex";
|
||||
|
||||
public const string AP_ComSupMotionResSymbol = "AP_ComSupMotionRes";
|
||||
public const string PA_ComSupMotionTypeSymbol = "PA_ComSupMotionType";
|
||||
public const string PA_ChangeParaIndexSymbol = "PA_ChangeParaIndex";
|
||||
|
||||
public AcsCommonAdress()
|
||||
{
|
||||
AP_ComSupMotionRes = AcsAddressDefinition.CreateInt32Symbol(AP_ComSupMotionResName, AP_ComSupMotionResSymbol, 1, "Result flag for ACS common communication command.");
|
||||
PA_ComSupMotionType = AcsAddressDefinition.CreateInt32Symbol(PA_ComSupMotionTypeName, PA_ComSupMotionTypeSymbol, 1, "Communication action type such as data set, home, alarm clear, or PID sync.");
|
||||
PA_ChangeParaIndex = AcsAddressDefinition.CreateInt32Symbol(PA_ChangeParaIndexName, PA_ChangeParaIndexSymbol, 1, "PID parameter index. 0=fast bonding PID, 1=fly-shot PID.");
|
||||
}
|
||||
|
||||
public AcsAddressDefinition AP_ComSupMotionRes { get; }
|
||||
|
||||
public AcsAddressDefinition PA_ComSupMotionType { get; }
|
||||
|
||||
public AcsAddressDefinition PA_ChangeParaIndex { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
public sealed class AcsHomeOffsetAdress
|
||||
{
|
||||
private const int AxisIndexedArrayLength = 32;
|
||||
|
||||
public const string PA_HomeOffsetName = "PA_HomeOffset";
|
||||
public const string PA_HomeOffsetSymbol = "PA_HomeOffset";
|
||||
|
||||
public AcsHomeOffsetAdress()
|
||||
{
|
||||
PA_HomeOffset = AcsAddressDefinition.CreateDoubleSymbol(PA_HomeOffsetName, PA_HomeOffsetSymbol, AxisIndexedArrayLength, "Homing offset array, indexed by axis number.");
|
||||
}
|
||||
|
||||
public AcsAddressDefinition PA_HomeOffset { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
public sealed class AcsHomeSettingAdress
|
||||
{
|
||||
private const int AxisIndexedArrayLength = 32;
|
||||
|
||||
public const string PA_HomeModeName = "PA_HomeMode";
|
||||
public const string PA_HomeVelName = "PA_HomeVel";
|
||||
public const string PA_HomeOrderName = "PA_HomeOrder";
|
||||
|
||||
public AcsHomeSettingAdress()
|
||||
{
|
||||
PA_HomeMode = AcsAddressDefinition.CreateInt32Symbol(PA_HomeModeName, AxisIndexedArrayLength, "回零模式数组,按轴号索引<E7B4A2><E5BC95>?");
|
||||
PA_HomeVel = AcsAddressDefinition.CreateDoubleSymbol(PA_HomeVelName, AxisIndexedArrayLength, "回零速度数组,按轴号索引<E7B4A2><E5BC95>?");
|
||||
PA_HomeOrder = AcsAddressDefinition.CreateInt32Symbol(PA_HomeOrderName, AxisIndexedArrayLength, "回零顺序数组,按轴号索引<E7B4A2><E5BC95>?");
|
||||
}
|
||||
|
||||
public AcsAddressDefinition PA_HomeMode { get; }
|
||||
|
||||
public AcsAddressDefinition PA_HomeVel { get; }
|
||||
|
||||
public AcsAddressDefinition PA_HomeOrder { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
public sealed class AcsInterferometerCompensationAdress
|
||||
{
|
||||
private const int AxisIndexedArrayLength = 32;
|
||||
|
||||
public const string PA_CorrectionX1Name = "PA_Correction_X1";
|
||||
public const string PA_CorrectionY1Name = "PA_Correction_Y1";
|
||||
public const string PA_CorrectionX2Name = "PA_Correction_X2";
|
||||
public const string PA_CorrectionY2Name = "PA_Correction_Y2";
|
||||
public const string PA_StartPosName = "PA_StartPos";
|
||||
public const string PA_StepLenName = "PA_Step_len";
|
||||
public const string PA_CorrectionOpenName = "PA_CorrectionOpen";
|
||||
|
||||
public const string PA_CorrectionX1Symbol = "PA_Correction_X1";
|
||||
public const string PA_CorrectionY1Symbol = "PA_Correction_Y1";
|
||||
public const string PA_CorrectionX2Symbol = "PA_Correction_X2";
|
||||
public const string PA_CorrectionY2Symbol = "PA_Correction_Y2";
|
||||
public const string PA_StartPosSymbol = "PA_StartPos";
|
||||
public const string PA_StepLenSymbol = "PA_StepLen";
|
||||
public const string PA_CorrectionOpenSymbol = "PA_CorrectionOpen";
|
||||
|
||||
public AcsInterferometerCompensationAdress()
|
||||
{
|
||||
PA_Correction_X1 = AcsAddressDefinition.CreateDoubleSymbol(PA_CorrectionX1Name, PA_CorrectionX1Symbol, AxisIndexedArrayLength, "Interferometer compensation array for X1 axis.");
|
||||
PA_Correction_Y1 = AcsAddressDefinition.CreateDoubleSymbol(PA_CorrectionY1Name, PA_CorrectionY1Symbol, AxisIndexedArrayLength, "Interferometer compensation array for Y1 axis.");
|
||||
PA_Correction_X2 = AcsAddressDefinition.CreateDoubleSymbol(PA_CorrectionX2Name, PA_CorrectionX2Symbol, AxisIndexedArrayLength, "Interferometer compensation array for X2 axis.");
|
||||
PA_Correction_Y2 = AcsAddressDefinition.CreateDoubleSymbol(PA_CorrectionY2Name, PA_CorrectionY2Symbol, AxisIndexedArrayLength, "Interferometer compensation array for Y2 axis.");
|
||||
PA_StartPos = AcsAddressDefinition.CreateDoubleSymbol(PA_StartPosName, PA_StartPosSymbol, AxisIndexedArrayLength, "Compensation start position array, indexed by axis number.");
|
||||
PA_Step_len = AcsAddressDefinition.CreateDoubleSymbol(PA_StepLenName, PA_StepLenSymbol, AxisIndexedArrayLength, "Compensation step length array, indexed by axis number.");
|
||||
PA_CorrectionOpen = AcsAddressDefinition.CreateBoolSymbol(PA_CorrectionOpenName, PA_CorrectionOpenSymbol, AxisIndexedArrayLength, "Whether interferometer compensation is enabled per axis.");
|
||||
}
|
||||
|
||||
public AcsAddressDefinition PA_Correction_X1 { get; }
|
||||
|
||||
public AcsAddressDefinition PA_Correction_Y1 { get; }
|
||||
|
||||
public AcsAddressDefinition PA_Correction_X2 { get; }
|
||||
|
||||
public AcsAddressDefinition PA_Correction_Y2 { get; }
|
||||
|
||||
public AcsAddressDefinition PA_StartPos { get; }
|
||||
|
||||
public AcsAddressDefinition PA_Step_len { get; }
|
||||
|
||||
public AcsAddressDefinition PA_CorrectionOpen { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
public class CorrectionItem
|
||||
{
|
||||
public string AxisName { get; set; }
|
||||
|
||||
public int AxisIndex { get; set; }
|
||||
|
||||
public double StepLength { get; set; }
|
||||
|
||||
public double StartPos { get; set; }
|
||||
|
||||
public ObservableCollection<CorrectionData> CorrectionDatas { get; set; }
|
||||
|
||||
public bool Enable { get; set; }
|
||||
|
||||
public int HomeMode { get; set; }
|
||||
|
||||
public double HomeSpeed { get; set; }
|
||||
|
||||
public int HomeOrder { get; set; }
|
||||
}
|
||||
|
||||
public class CorrectionData
|
||||
{
|
||||
public double ScaleData { get; set; }
|
||||
|
||||
public double ScaleNegativeData { get; set; }
|
||||
|
||||
public int Index { get; set; }
|
||||
|
||||
public double StepData { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.PageCalib.OriginCalib.Model;
|
||||
using MaxwellFramework.Core.Attributes;
|
||||
using MwFramework.Device;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MainShell.Hardware.Acs
|
||||
{
|
||||
public interface IAcsStartupInitializationService
|
||||
{
|
||||
bool IsInitialized { get; }
|
||||
|
||||
void Initialize();
|
||||
|
||||
void WriteOriginCalibrationHomeOffsets();
|
||||
}
|
||||
|
||||
public sealed class AcsStartupInitializationService : IAcsStartupInitializationService
|
||||
{
|
||||
private const int CorrectionArrayLength = 100;
|
||||
private const int StartupPollingIntervalMilliseconds = 100;
|
||||
private const int StartupTimeoutMilliseconds = 10000;
|
||||
private const string InterferometerConfigRelativePath = @"Configuration\Card\Acs\Interferometer.xml";
|
||||
private const string WorkspaceInterferometerConfigRelativePath = @"Bin\Configuration\Card\Acs\Interferometer.xml";
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly IAcsAddressAccessService _acsAddressAccessService;
|
||||
private readonly AcsAddressRepository _acsAddressRepository;
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private bool _isInitialized;
|
||||
|
||||
public AcsStartupInitializationService(
|
||||
IAcsAddressAccessService acsAddressAccessService,
|
||||
AcsAddressRepository acsAddressRepository,
|
||||
HardwareManager hardwareManager)
|
||||
{
|
||||
_acsAddressAccessService = acsAddressAccessService ?? throw new ArgumentNullException(nameof(acsAddressAccessService));
|
||||
_acsAddressRepository = acsAddressRepository ?? throw new ArgumentNullException(nameof(acsAddressRepository));
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
}
|
||||
|
||||
public bool IsInitialized
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isInitialized;
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
WriteInterferometerStartupParameters();
|
||||
WriteOriginCalibrationHomeOffsetsCore();
|
||||
_isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteOriginCalibrationHomeOffsets()
|
||||
{
|
||||
WriteOriginCalibrationHomeOffsetsCore();
|
||||
}
|
||||
|
||||
private static void ValidateAxisIndex(int axisIndex, int axisCount)
|
||||
{
|
||||
if (axisIndex < 0 || axisIndex >= axisCount)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(axisIndex), axisIndex, "Axis index is outside the loaded ACS startup parameter range.");
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteInterferometerStartupParameters()
|
||||
{
|
||||
ObservableCollection<CorrectionItem> corrections = LoadInterferometerCorrections();
|
||||
AcsCommonAdress commonData = _acsAddressRepository.GetCommonData();
|
||||
AcsInterferometerCompensationAdress interferometerData = _acsAddressRepository.GetInterferometerCompensationData();
|
||||
AcsHomeSettingAdress homeSettingData = _acsAddressRepository.GetHomeSettingData();
|
||||
|
||||
int axisCount = homeSettingData.PA_HomeMode.Length;
|
||||
double[] startPositions = new double[axisCount];
|
||||
double[] stepLengths = new double[axisCount];
|
||||
int[] correctionOpens = new int[axisCount];
|
||||
int[] homeModes = new int[axisCount];
|
||||
double[] homeVelocities = new double[axisCount];
|
||||
int[] homeOrders = new int[axisCount];
|
||||
|
||||
foreach (CorrectionItem correctionItem in corrections)
|
||||
{
|
||||
if (correctionItem == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ValidateAxisIndex(correctionItem.AxisIndex, axisCount);
|
||||
|
||||
double[] correctionValues = BuildCorrectionArray(correctionItem);
|
||||
startPositions[correctionItem.AxisIndex] = correctionItem.StartPos;
|
||||
stepLengths[correctionItem.AxisIndex] = correctionItem.StepLength;
|
||||
correctionOpens[correctionItem.AxisIndex] = correctionItem.Enable ? 1 : 0;
|
||||
homeModes[correctionItem.AxisIndex] = correctionItem.HomeMode;
|
||||
homeVelocities[correctionItem.AxisIndex] = correctionItem.HomeSpeed;
|
||||
homeOrders[correctionItem.AxisIndex] = correctionItem.HomeOrder;
|
||||
|
||||
AcsAddressDefinition correctionBufferAddress;
|
||||
if (TryGetCorrectionBufferAddress(correctionItem.AxisName, interferometerData, out correctionBufferAddress))
|
||||
{
|
||||
_acsAddressAccessService.Write(correctionBufferAddress, correctionValues);
|
||||
}
|
||||
}
|
||||
|
||||
_acsAddressAccessService.Write(interferometerData.PA_StartPos, startPositions);
|
||||
_acsAddressAccessService.Write(interferometerData.PA_Step_len, stepLengths);
|
||||
_acsAddressAccessService.Write(interferometerData.PA_CorrectionOpen, correctionOpens);
|
||||
_acsAddressAccessService.Write(homeSettingData.PA_HomeMode, homeModes);
|
||||
_acsAddressAccessService.Write(homeSettingData.PA_HomeVel, homeVelocities);
|
||||
_acsAddressAccessService.Write(homeSettingData.PA_HomeOrder, homeOrders);
|
||||
_acsAddressAccessService.Write(commonData.PA_ComSupMotionType, (int)ComSupMotionType.Interfere);
|
||||
|
||||
WaitForStartupWriteResult(commonData);
|
||||
}
|
||||
|
||||
private ObservableCollection<CorrectionItem> LoadInterferometerCorrections()
|
||||
{
|
||||
string configPath = ResolveInterferometerConfigPath();
|
||||
XmlSerializer xmlSerializer = new XmlSerializer(typeof(ObservableCollection<CorrectionItem>));
|
||||
|
||||
using (FileStream fileStream = File.OpenRead(configPath))
|
||||
{
|
||||
ObservableCollection<CorrectionItem> corrections = xmlSerializer.Deserialize(fileStream) as ObservableCollection<CorrectionItem>;
|
||||
if (corrections == null)
|
||||
{
|
||||
throw new InvalidOperationException("The interferometer configuration could not be deserialized.");
|
||||
}
|
||||
|
||||
return corrections;
|
||||
}
|
||||
}
|
||||
|
||||
private static double[] BuildCorrectionArray(CorrectionItem correctionItem)
|
||||
{
|
||||
double[] correctionValues = new double[CorrectionArrayLength];
|
||||
if (correctionItem.CorrectionDatas == null)
|
||||
{
|
||||
return correctionValues;
|
||||
}
|
||||
|
||||
int count = Math.Min(correctionItem.CorrectionDatas.Count, correctionValues.Length);
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
CorrectionData correctionData = correctionItem.CorrectionDatas[index];
|
||||
if (correctionData == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
correctionValues[index] = correctionData.ScaleData;
|
||||
}
|
||||
|
||||
return correctionValues;
|
||||
}
|
||||
|
||||
private void WriteOriginCalibrationHomeOffsetsCore()
|
||||
{
|
||||
OriginCalibSetting setting = OriginCalibSetting.LoadOrCreate();
|
||||
AcsHomeOffsetAdress homeOffsetData = _acsAddressRepository.GetHomeOffsetData();
|
||||
double[] homeOffsets = new double[homeOffsetData.PA_HomeOffset.Length];
|
||||
|
||||
WriteHomeOffset(homeOffsets, _hardwareManager.Axis_X1, GetCalibrationOffset(setting, OriginCalibModuleNames.GantryX1Y1, AxisName.Axis_PHS_X1));
|
||||
WriteHomeOffset(homeOffsets, _hardwareManager.Axis_Y1, GetCalibrationOffset(setting, OriginCalibModuleNames.GantryX1Y1, AxisName.Axis_PHS_Y1));
|
||||
WriteHomeOffset(homeOffsets, _hardwareManager.Axis_X2, GetCalibrationOffset(setting, OriginCalibModuleNames.GantryX2, AxisName.Axis_PHS_X2));
|
||||
WriteHomeOffset(homeOffsets, _hardwareManager.Axis_Stage_Y3, GetCalibrationOffset(setting, OriginCalibModuleNames.StageY2, AxisName.Axis_Stage_Y3));
|
||||
WriteHomeOffset(homeOffsets, _hardwareManager.Axis_WS_X3, GetCalibrationOffset(setting, OriginCalibModuleNames.WSX3, AxisName.Axis_WS_X3));
|
||||
|
||||
_acsAddressAccessService.Write(homeOffsetData.PA_HomeOffset, homeOffsets);
|
||||
}
|
||||
|
||||
private static double GetCalibrationOffset(OriginCalibSetting setting, string moduleName, string axisName)
|
||||
{
|
||||
if (setting == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(setting));
|
||||
}
|
||||
|
||||
OriginCalibModuleItem module = setting.Modules.Find(item =>
|
||||
item != null && string.Equals(item.ModuleName, moduleName, StringComparison.OrdinalIgnoreCase));
|
||||
if (module == null || module.CalibrationAxes == null)
|
||||
{
|
||||
return 0d;
|
||||
}
|
||||
|
||||
CalibrationAxisItem calibrationAxis = module.CalibrationAxes.Find(item =>
|
||||
item != null && string.Equals(item.AxisName, axisName, StringComparison.OrdinalIgnoreCase));
|
||||
if (calibrationAxis == null)
|
||||
{
|
||||
return 0d;
|
||||
}
|
||||
|
||||
return calibrationAxis.Offset;
|
||||
}
|
||||
|
||||
private static void WriteHomeOffset(double[] homeOffsets, IAxis axis, double offset)
|
||||
{
|
||||
if (homeOffsets == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(homeOffsets));
|
||||
}
|
||||
|
||||
if (axis == null)
|
||||
{
|
||||
throw new InvalidOperationException("The target axis for origin calibration home offset was not found.");
|
||||
}
|
||||
|
||||
int axisIndex = axis.AxisIndex;
|
||||
ValidateAxisIndex(axisIndex, homeOffsets.Length);
|
||||
homeOffsets[axisIndex] = offset;
|
||||
}
|
||||
|
||||
private void WaitForStartupWriteResult(AcsCommonAdress commonData)
|
||||
{
|
||||
if (commonData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(commonData));
|
||||
}
|
||||
|
||||
DateTime deadline = DateTime.Now.AddMilliseconds(StartupTimeoutMilliseconds);
|
||||
int result = 0;
|
||||
|
||||
while (DateTime.Now <= deadline)
|
||||
{
|
||||
object rawResult = _acsAddressAccessService.Read(commonData.AP_ComSupMotionRes);
|
||||
if (rawResult != null)
|
||||
{
|
||||
result = Convert.ToInt32(rawResult);
|
||||
if (result == -1)
|
||||
{
|
||||
throw new InvalidOperationException("Writing interferometer startup parameters to ACS failed.");
|
||||
}
|
||||
|
||||
if (result == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(StartupPollingIntervalMilliseconds);
|
||||
}
|
||||
|
||||
throw new TimeoutException("Writing interferometer startup parameters to ACS timed out.");
|
||||
}
|
||||
|
||||
private static string ResolveInterferometerConfigPath()
|
||||
{
|
||||
List<string> candidatePaths = new List<string>
|
||||
{
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, InterferometerConfigRelativePath),
|
||||
Path.Combine(Environment.CurrentDirectory, WorkspaceInterferometerConfigRelativePath),
|
||||
Path.Combine(Environment.CurrentDirectory, InterferometerConfigRelativePath)
|
||||
};
|
||||
|
||||
foreach (string candidatePath in candidatePaths)
|
||||
{
|
||||
if (File.Exists(candidatePath))
|
||||
{
|
||||
return candidatePath;
|
||||
}
|
||||
}
|
||||
|
||||
throw new FileNotFoundException("The interferometer startup configuration file was not found.");
|
||||
}
|
||||
|
||||
private static bool TryGetCorrectionBufferAddress(
|
||||
string axisName,
|
||||
AcsInterferometerCompensationAdress interferometerData,
|
||||
out AcsAddressDefinition correctionBufferAddress)
|
||||
{
|
||||
if (interferometerData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(interferometerData));
|
||||
}
|
||||
|
||||
correctionBufferAddress = null;
|
||||
if (string.IsNullOrWhiteSpace(axisName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string normalizedAxisName = axisName.Trim().ToUpperInvariant();
|
||||
switch (normalizedAxisName)
|
||||
{
|
||||
case "X1":
|
||||
case "X11":
|
||||
correctionBufferAddress = interferometerData.PA_Correction_X1;
|
||||
return true;
|
||||
case "Y1":
|
||||
case "Y11":
|
||||
correctionBufferAddress = interferometerData.PA_Correction_Y1;
|
||||
return true;
|
||||
case "X2":
|
||||
case "X21":
|
||||
correctionBufferAddress = interferometerData.PA_Correction_X2;
|
||||
return true;
|
||||
case "Y2":
|
||||
case "Y21":
|
||||
correctionBufferAddress = interferometerData.PA_Correction_Y2;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static int ResolveInterfereMotionTypeValue()
|
||||
{
|
||||
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
foreach (Assembly assembly in assemblies)
|
||||
{
|
||||
Type[] types;
|
||||
try
|
||||
{
|
||||
types = assembly.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
types = ex.Types;
|
||||
}
|
||||
|
||||
foreach (Type type in types)
|
||||
{
|
||||
if (type == null || !type.IsEnum || !string.Equals(type.Name, "ComSupMotionType", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Enum.IsDefined(type, "Interfere"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
object enumValue = Enum.Parse(type, "Interfere");
|
||||
return Convert.ToInt32(enumValue);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The ComSupMotionType.Interfere enumeration value could not be resolved.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
using MainShell.EventArgsFolder;
|
||||
using MainShell.Log;
|
||||
using MainShell.Motion;
|
||||
using MaxwellFramework.Core.Attributes;
|
||||
using MwFramework.Device;
|
||||
using MwFramework.Device.Delta;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Hardware
|
||||
{
|
||||
[Singleton]
|
||||
public sealed class AxisHomeService : IHandle<OriginCalibDataChangedEventArgs>
|
||||
{
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly SafeAxisMotion _safeAxisMotion;
|
||||
private readonly IDeltaAxisExtend _deltaAxisExtend;
|
||||
private readonly Dictionary<string, double> _latestOriginCalibOffsets = new Dictionary<string, double>(StringComparer.Ordinal);
|
||||
|
||||
public IReadOnlyDictionary<string, double> LatestOriginCalibOffsets
|
||||
{
|
||||
get
|
||||
{
|
||||
return _latestOriginCalibOffsets;
|
||||
}
|
||||
}
|
||||
|
||||
public AxisHomeService(HardwareManager hardwareManager, SafeAxisMotion safeAxisMotion, IEventAggregator eventAggregator)
|
||||
{
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_safeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
|
||||
if (eventAggregator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(eventAggregator));
|
||||
}
|
||||
|
||||
eventAggregator.Subscribe(this);
|
||||
_deltaAxisExtend=_hardwareManager.TdCard as IDeltaAxisExtend;
|
||||
SetHomeDoneEvent(AxisName.Axis_PHS_Z1);
|
||||
SetHomeDoneEvent(AxisName.Axis_PHS_Z3);
|
||||
SetHomeDoneEvent(AxisName.Axis_PHS_Z4);
|
||||
SetHomeDoneEvent(AxisName.Axis_PHS_Z5);
|
||||
SetHomeDoneEvent(AxisName.Axis_PHS_Z6);
|
||||
}
|
||||
|
||||
private void SetHomeDoneEvent(string axisName)
|
||||
{
|
||||
var axis = ResolveAxis(axisName);
|
||||
if (axis != null && axis is IDeltaAxisEvent deltaAxis)
|
||||
{
|
||||
deltaAxis.HomeFinished -= DeltaAxis_HomeFinished;
|
||||
deltaAxis.HomeFinished += DeltaAxis_HomeFinished;
|
||||
}
|
||||
}
|
||||
|
||||
private void DeltaAxis_HomeFinished(IAxis arg1, bool arg2)
|
||||
{
|
||||
if(arg2)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void SetHomeOffset(IAxis axis, double offset)
|
||||
{
|
||||
if (_deltaAxisExtend != null)
|
||||
{
|
||||
_deltaAxisExtend.SetAxisCurrentPosition(axis, offset);
|
||||
}
|
||||
}
|
||||
|
||||
public void Home(string axisName)
|
||||
{
|
||||
Home(axisName, CancellationToken.None);
|
||||
}
|
||||
|
||||
public void Home(string axisName, CancellationToken cancellationToken)
|
||||
{
|
||||
HomeAsync(axisName, MotionController.DefaultTimeoutMilliseconds, cancellationToken).GetAwaiter().GetResult().EnsureSuccess();
|
||||
}
|
||||
|
||||
public void Home(IAxis axis)
|
||||
{
|
||||
Home(axis, CancellationToken.None);
|
||||
}
|
||||
|
||||
public void Home(IAxis axis, CancellationToken cancellationToken)
|
||||
{
|
||||
HomeAsync(axis, MotionController.DefaultTimeoutMilliseconds, cancellationToken).GetAwaiter().GetResult().EnsureSuccess();
|
||||
}
|
||||
|
||||
public void Home(params string[] axisNames)
|
||||
{
|
||||
Home(CancellationToken.None, axisNames);
|
||||
}
|
||||
|
||||
public void Home(CancellationToken cancellationToken, params string[] axisNames)
|
||||
{
|
||||
HomeAsync(MotionController.DefaultTimeoutMilliseconds, cancellationToken, axisNames).GetAwaiter().GetResult().EnsureSuccess();
|
||||
}
|
||||
|
||||
public async Task<MotionResult> HomeAsync(string axisName, int timeoutMilliseconds = MotionController.DefaultTimeoutMilliseconds, CancellationToken cancellationToken = default(CancellationToken), int? alarmId = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(axisName))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(axisName));
|
||||
}
|
||||
|
||||
IAxis axis = ResolveAxis(axisName);
|
||||
return await HomeAsync(axis, timeoutMilliseconds, cancellationToken, alarmId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<MotionResult> HomeAsync(IAxis axis, int timeoutMilliseconds = MotionController.DefaultTimeoutMilliseconds, CancellationToken cancellationToken = default(CancellationToken), int? alarmId = null)
|
||||
{
|
||||
IAxis resolvedAxis = axis ?? throw new ArgumentNullException(nameof(axis));
|
||||
string axisName = resolvedAxis.Name ?? string.Empty;
|
||||
DateTime startedAt = DateTime.Now;
|
||||
string.Format("Axis home started. Axis:{0}", axisName).LogInfo();
|
||||
|
||||
try
|
||||
{
|
||||
MotionResult result = await _safeAxisMotion.HomeAsync(resolvedAxis, timeoutMilliseconds, cancellationToken, alarmId).ConfigureAwait(false);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
string.Format("Axis home completed. Axis:{0} Duration:{1}ms", axisName, (DateTime.Now - startedAt).TotalMilliseconds.ToString("F0")).LogInfo();
|
||||
}
|
||||
else if (result.Cancelled)
|
||||
{
|
||||
string.Format("Axis home cancelled. Axis:{0}", axisName).LogInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
string failureMessage = string.IsNullOrWhiteSpace(result.Message) ? "Unknown axis home failure." : result.Message;
|
||||
string.Format("Axis home failed. Axis:{0} Error:{1}", axisName, failureMessage).LogSysError();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string.Format("Axis home exception. Axis:{0} Error:{1}", axisName, ex.Message).LogSysError();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<MotionBatchResult> HomeAsync(params string[] axisNames)
|
||||
{
|
||||
return HomeAsync(MotionController.DefaultTimeoutMilliseconds, CancellationToken.None, axisNames);
|
||||
}
|
||||
|
||||
public Task<MotionBatchResult> HomeAsync(CancellationToken cancellationToken, params string[] axisNames)
|
||||
{
|
||||
return HomeAsync(MotionController.DefaultTimeoutMilliseconds, cancellationToken, axisNames);
|
||||
}
|
||||
|
||||
public async Task<MotionBatchResult> HomeAsync(int timeoutMilliseconds, CancellationToken cancellationToken, params string[] axisNames)
|
||||
{
|
||||
string[] normalizedAxisNames = NormalizeAxisNames(axisNames);
|
||||
if (normalizedAxisNames.Length == 0)
|
||||
{
|
||||
return new MotionBatchResult(Array.Empty<MotionResult>());
|
||||
}
|
||||
|
||||
IAxis[] axes = normalizedAxisNames.Select(ResolveAxis).ToArray();
|
||||
return await HomeAsync(timeoutMilliseconds, cancellationToken, axes).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<MotionBatchResult> HomeAsync(params IAxis[] axes)
|
||||
{
|
||||
return HomeAsync(MotionController.DefaultTimeoutMilliseconds, CancellationToken.None, axes);
|
||||
}
|
||||
|
||||
public Task<MotionBatchResult> HomeAsync(CancellationToken cancellationToken, params IAxis[] axes)
|
||||
{
|
||||
return HomeAsync(MotionController.DefaultTimeoutMilliseconds, cancellationToken, axes);
|
||||
}
|
||||
|
||||
public async Task<MotionBatchResult> HomeAsync(int timeoutMilliseconds, CancellationToken cancellationToken, params IAxis[] axes)
|
||||
{
|
||||
IAxis[] normalizedAxes = NormalizeAxes(axes);
|
||||
if (normalizedAxes.Length == 0)
|
||||
{
|
||||
return new MotionBatchResult(Array.Empty<MotionResult>());
|
||||
}
|
||||
|
||||
List<MotionResult> results = new List<MotionResult>(normalizedAxes.Length);
|
||||
foreach (IAxis axis in normalizedAxes)
|
||||
{
|
||||
MotionResult result = await HomeAsync(axis, timeoutMilliseconds, cancellationToken).ConfigureAwait(false);
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
return new MotionBatchResult(results);
|
||||
}
|
||||
|
||||
public Task<MotionBatchResult> SafeHomeAsync(params string[] axisNames)
|
||||
{
|
||||
return SafeHomeAsync(CancellationToken.None, axisNames);
|
||||
}
|
||||
|
||||
public async Task<MotionBatchResult> SafeHomeAsync(CancellationToken cancellationToken, params string[] axisNames)
|
||||
{
|
||||
string[] normalizedAxisNames = NormalizeAxisNames(axisNames);
|
||||
if (normalizedAxisNames.Length == 0)
|
||||
{
|
||||
return new MotionBatchResult(Array.Empty<MotionResult>());
|
||||
}
|
||||
|
||||
string.Format("Batch axis home started. Axes:{0}", string.Join(", ", normalizedAxisNames)).LogInfo();
|
||||
|
||||
try
|
||||
{
|
||||
MotionBatchResult result = await _safeAxisMotion.SafeHomeAsync(cancellationToken, normalizedAxisNames).ConfigureAwait(false);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
string.Format("Batch axis home completed. Axes:{0}", string.Join(", ", normalizedAxisNames)).LogInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<string> failedAxes = result.Results.Where(x => x != null && !x.Succeeded).Select(x => x.AxisName ?? string.Empty);
|
||||
string.Format("Batch axis home failed. Axes:{0}", string.Join(", ", failedAxes)).LogSysError();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string.Format("Batch axis home exception. Axes:{0} Error:{1}", string.Join(", ", normalizedAxisNames), ex.Message).LogSysError();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public IAxis GetAxis(string axisName)
|
||||
{
|
||||
return ResolveAxis(axisName);
|
||||
}
|
||||
|
||||
public bool TryGetOriginCalibOffset(string axisName, out double offset)
|
||||
{
|
||||
offset = 0;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(axisName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _latestOriginCalibOffsets.TryGetValue(axisName, out offset);
|
||||
}
|
||||
|
||||
private IAxis ResolveAxis(string axisName)
|
||||
{
|
||||
IAxis axis = _hardwareManager.GetAxisByName(axisName);
|
||||
if (axis == null)
|
||||
{
|
||||
throw new ArgumentException(string.Format("Axis with name {0} was not found.", axisName), nameof(axisName));
|
||||
}
|
||||
|
||||
return axis;
|
||||
}
|
||||
|
||||
private static string[] NormalizeAxisNames(IEnumerable<string> axisNames)
|
||||
{
|
||||
return (axisNames ?? Enumerable.Empty<string>())
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static IAxis[] NormalizeAxes(IEnumerable<IAxis> axes)
|
||||
{
|
||||
return (axes ?? Enumerable.Empty<IAxis>())
|
||||
.Where(x => x != null)
|
||||
.GroupBy(x => x.Name ?? string.Empty, StringComparer.Ordinal)
|
||||
.Select(x => x.First())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public void Handle(OriginCalibDataChangedEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_latestOriginCalibOffsets.Clear();
|
||||
|
||||
if (eventArgs.Axes != null)
|
||||
{
|
||||
foreach (OriginCalibAxisOffsetChangedItem axis in eventArgs.Axes)
|
||||
{
|
||||
if (axis == null || string.IsNullOrWhiteSpace(axis.AxisName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_latestOriginCalibOffsets[axis.AxisName] = axis.Offset;
|
||||
}
|
||||
}
|
||||
|
||||
string.Format(
|
||||
"Origin calibration data changed received. AxisCount:{0}",
|
||||
_latestOriginCalibOffsets.Count).LogInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
using MainShell.DeviceMaintance.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Hardware
|
||||
{
|
||||
public interface IDeviceCylinderService
|
||||
{
|
||||
IReadOnlyList<CylinderDefinition> Definitions { get; }
|
||||
bool TryGetDefinition(string name, out CylinderDefinition definition);
|
||||
bool TryExtend(string name);
|
||||
bool TryExtend(string name, out string failureReason);
|
||||
bool TryRetract(string name);
|
||||
bool TryRetract(string name, out string failureReason);
|
||||
Task<bool> TryExtendAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> TryRetractAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<CylinderExecutionResult> TryExtendAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<CylinderExecutionResult> TryRetractAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
|
||||
public class DeviceCylinderService : IDeviceCylinderService
|
||||
{
|
||||
private readonly IDeviceIoMonitorService _ioMonitorService;
|
||||
private readonly Dictionary<string, CylinderDefinition> _definitionsByName;
|
||||
private static readonly StringComparer PointReferenceComparer = StringComparer.OrdinalIgnoreCase;
|
||||
private static readonly TimeSpan ActiveRefreshTimeout = TimeSpan.FromMilliseconds(120);
|
||||
private static readonly TimeSpan FeedbackRetryInterval = TimeSpan.FromMilliseconds(50);
|
||||
|
||||
public IReadOnlyList<CylinderDefinition> Definitions { get; }
|
||||
|
||||
public DeviceCylinderService(IDeviceIoMonitorService ioMonitorService)
|
||||
{
|
||||
_ioMonitorService = ioMonitorService;
|
||||
Definitions = CylinderDefinitionLoader.LoadDefinitions();
|
||||
_definitionsByName = Definitions.ToDictionary(x => x.Name, x => x, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public bool TryGetDefinition(string name, out CylinderDefinition definition)
|
||||
{
|
||||
return _definitionsByName.TryGetValue(name, out definition);
|
||||
}
|
||||
|
||||
public bool TryExtend(string name)
|
||||
{
|
||||
string failureReason;
|
||||
return TryExtend(name, out failureReason);
|
||||
}
|
||||
|
||||
public bool TryExtend(string name, out string failureReason)
|
||||
{
|
||||
CylinderDefinition definition;
|
||||
if (!TryGetDefinition(name, out definition))
|
||||
{
|
||||
failureReason = "未找到对应气缸定义。";
|
||||
return false;
|
||||
}
|
||||
|
||||
return Execute(definition, true, out failureReason);
|
||||
}
|
||||
|
||||
public bool TryRetract(string name)
|
||||
{
|
||||
string failureReason;
|
||||
return TryRetract(name, out failureReason);
|
||||
}
|
||||
|
||||
public bool TryRetract(string name, out string failureReason)
|
||||
{
|
||||
CylinderDefinition definition;
|
||||
if (!TryGetDefinition(name, out definition))
|
||||
{
|
||||
failureReason = "未找到对应气缸定义。";
|
||||
return false;
|
||||
}
|
||||
|
||||
return Execute(definition, false, out failureReason);
|
||||
}
|
||||
|
||||
public async Task<bool> TryExtendAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var result = await TryExtendAndWaitResultAsync(name, timeout, cancellationToken);
|
||||
return result.Success;
|
||||
}
|
||||
|
||||
public async Task<bool> TryRetractAndWaitAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var result = await TryRetractAndWaitResultAsync(name, timeout, cancellationToken);
|
||||
return result.Success;
|
||||
}
|
||||
|
||||
public async Task<CylinderExecutionResult> TryExtendAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
CylinderDefinition definition;
|
||||
string failureReason;
|
||||
if (!TryGetDefinition(name, out definition))
|
||||
{
|
||||
return CreateFailureResult("未找到对应气缸定义。");
|
||||
}
|
||||
|
||||
if (!Execute(definition, true, out failureReason))
|
||||
{
|
||||
return CreateFailureResult(failureReason);
|
||||
}
|
||||
|
||||
return await WaitForFeedbackResultAsync(definition.ExtendedFeedbackPoints, definition.RetractedFeedbackPoints, timeout, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<CylinderExecutionResult> TryRetractAndWaitResultAsync(string name, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
CylinderDefinition definition;
|
||||
string failureReason;
|
||||
if (!TryGetDefinition(name, out definition))
|
||||
{
|
||||
return CreateFailureResult("未找到对应气缸定义。");
|
||||
}
|
||||
|
||||
if (!Execute(definition, false, out failureReason))
|
||||
{
|
||||
return CreateFailureResult(failureReason);
|
||||
}
|
||||
|
||||
return await WaitForFeedbackResultAsync(definition.RetractedFeedbackPoints, definition.ExtendedFeedbackPoints, timeout, cancellationToken);
|
||||
}
|
||||
|
||||
private bool Execute(CylinderDefinition definition, bool extend, out string failureReason)
|
||||
{
|
||||
if (!TryValidateConditions(definition, extend, out failureReason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<DeviceIoOutputWriteRequest> requests;
|
||||
if (!TryBuildWriteRequests(definition, extend, out requests, out failureReason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string failurePointReference;
|
||||
var result = _ioMonitorService.TrySetOutputStates(requests, out failurePointReference);
|
||||
failureReason = result
|
||||
? string.Empty
|
||||
: string.IsNullOrWhiteSpace(failurePointReference)
|
||||
? "输出控制执行失败。"
|
||||
: $"输出控制执行失败:{failurePointReference}";
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool TryValidateConditions(CylinderDefinition definition, bool extend, out string failureReason)
|
||||
{
|
||||
failureReason = string.Empty;
|
||||
var conditions = extend ? definition.ExtendConditions : definition.RetractConditions;
|
||||
if (conditions == null || conditions.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_ioMonitorService.TryRefreshNow(ActiveRefreshTimeout);
|
||||
|
||||
foreach (var condition in conditions)
|
||||
{
|
||||
DeviceIoPointState point;
|
||||
if (!TryGetPoint(condition.PointReference, out point))
|
||||
{
|
||||
failureReason = !string.IsNullOrWhiteSpace(condition.Message)
|
||||
? condition.Message
|
||||
: $"条件 IO {condition.PointReference} 状态不可用。";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (point.Value != condition.ExpectedState)
|
||||
{
|
||||
failureReason = !string.IsNullOrWhiteSpace(condition.Message)
|
||||
? condition.Message
|
||||
: $"条件 IO {point.Name} 未满足,要求 {(condition.ExpectedState ? "ON" : "OFF")}。";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<CylinderExecutionResult> WaitForFeedbackResultAsync(IReadOnlyList<string> requiredOnPoints, IReadOnlyList<string> requiredOffPoints, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
var normalizedOnPoints = NormalizePointReferences(requiredOnPoints);
|
||||
var normalizedOffPoints = NormalizePointReferences(requiredOffPoints);
|
||||
if (normalizedOnPoints.Count == 0 && normalizedOffPoints.Count == 0)
|
||||
{
|
||||
return CreateSuccessResult();
|
||||
}
|
||||
|
||||
var start = DateTime.UtcNow;
|
||||
|
||||
while (DateTime.UtcNow - start < timeout)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var remaining = timeout - (DateTime.UtcNow - start);
|
||||
var refreshTimeout = remaining < ActiveRefreshTimeout ? remaining : ActiveRefreshTimeout;
|
||||
if (refreshTimeout > TimeSpan.Zero)
|
||||
{
|
||||
await _ioMonitorService.TryRefreshNowAsync(refreshTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
var onReady = normalizedOnPoints.All(IsPointOn);
|
||||
var offReady = normalizedOffPoints.All(pointReference => !IsPointOn(pointReference));
|
||||
if (onReady && offReady)
|
||||
{
|
||||
return CreateSuccessResult();
|
||||
}
|
||||
|
||||
await Task.Delay(FeedbackRetryInterval, cancellationToken);
|
||||
}
|
||||
|
||||
return CreateFeedbackTimeoutResult(normalizedOnPoints, normalizedOffPoints);
|
||||
}
|
||||
|
||||
private bool TryBuildWriteRequests(CylinderDefinition definition, bool extend, out List<DeviceIoOutputWriteRequest> requests, out string failureReason)
|
||||
{
|
||||
requests = new List<DeviceIoOutputWriteRequest>();
|
||||
failureReason = string.Empty;
|
||||
|
||||
var desiredStates = new Dictionary<string, bool>(PointReferenceComparer);
|
||||
if (!AddOutputRequests(desiredStates, definition.ExtendOutputPoints, extend, out failureReason) ||
|
||||
!AddOutputRequests(desiredStates, definition.RetractOutputPoints, !extend, out failureReason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
requests.AddRange(desiredStates.Select(x => new DeviceIoOutputWriteRequest
|
||||
{
|
||||
PointReference = x.Key,
|
||||
OutputOn = x.Value
|
||||
}));
|
||||
|
||||
if (requests.Count > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
failureReason = "未配置输出控制点。";
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AddOutputRequests(IDictionary<string, bool> desiredStates, IEnumerable<string> pointReferences, bool outputOn, out string failureReason)
|
||||
{
|
||||
failureReason = string.Empty;
|
||||
if (pointReferences == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var pointReference in pointReferences.Where(x => !string.IsNullOrWhiteSpace(x)))
|
||||
{
|
||||
bool existingState;
|
||||
if (desiredStates.TryGetValue(pointReference, out existingState))
|
||||
{
|
||||
if (existingState != outputOn)
|
||||
{
|
||||
failureReason = $"气缸输出配置冲突:{pointReference} 同时要求为 {(existingState ? "ON" : "OFF")} 和 {(outputOn ? "ON" : "OFF")}。";
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
desiredStates[pointReference] = outputOn;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<string> NormalizePointReferences(IEnumerable<string> pointReferences)
|
||||
{
|
||||
return (pointReferences ?? Enumerable.Empty<string>())
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Distinct(PointReferenceComparer)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private bool TryGetPoint(string pointReference, out DeviceIoPointState point)
|
||||
{
|
||||
return _ioMonitorService.TryGetPoint(pointReference, out point)
|
||||
|| _ioMonitorService.TryGetPointByPointKey(pointReference, out point);
|
||||
}
|
||||
|
||||
private bool IsPointOn(string pointReference)
|
||||
{
|
||||
return _ioMonitorService.IsPointOn(pointReference)
|
||||
|| _ioMonitorService.IsPointOnByPointKey(pointReference);
|
||||
}
|
||||
|
||||
private CylinderExecutionResult CreateFeedbackTimeoutResult(IReadOnlyList<string> requiredOnPoints, IReadOnlyList<string> requiredOffPoints)
|
||||
{
|
||||
var missingOnPoints = requiredOnPoints.Where(pointReference => !IsPointOn(pointReference)).ToList();
|
||||
var missingOffPoints = requiredOffPoints.Where(IsPointOn).ToList();
|
||||
var messages = new List<string>();
|
||||
if (missingOnPoints.Count > 0)
|
||||
{
|
||||
messages.Add("以下反馈未到位(期望ON):" + string.Join(",", missingOnPoints.Select(GetPointDisplayName)));
|
||||
}
|
||||
|
||||
if (missingOffPoints.Count > 0)
|
||||
{
|
||||
messages.Add("以下反馈未复位(期望OFF):" + string.Join(",", missingOffPoints.Select(GetPointDisplayName)));
|
||||
}
|
||||
|
||||
var failurePoints = missingOnPoints.Concat(missingOffPoints).Distinct(PointReferenceComparer).ToList();
|
||||
return new CylinderExecutionResult
|
||||
{
|
||||
Success = false,
|
||||
FailureReason = messages.Count > 0 ? string.Join(";", messages) : "等待气缸反馈超时。",
|
||||
FailurePointReferences = failurePoints
|
||||
};
|
||||
}
|
||||
|
||||
private string GetPointDisplayName(string pointReference)
|
||||
{
|
||||
DeviceIoPointState point;
|
||||
return TryGetPoint(pointReference, out point) ? point.Name : pointReference;
|
||||
}
|
||||
|
||||
private static CylinderExecutionResult CreateSuccessResult()
|
||||
{
|
||||
return new CylinderExecutionResult
|
||||
{
|
||||
Success = true,
|
||||
FailureReason = string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private static CylinderExecutionResult CreateFailureResult(string failureReason)
|
||||
{
|
||||
return new CylinderExecutionResult
|
||||
{
|
||||
Success = false,
|
||||
FailureReason = failureReason ?? string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,950 @@
|
||||
using MainShell.EventArgsFolder;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Hardware
|
||||
{
|
||||
public interface IDeviceIoMonitorService
|
||||
{
|
||||
event EventHandler<DeviceIoChangedEventArgs> IoChanged;
|
||||
IReadOnlyList<DeviceIoPointDefinition> Definitions { get; }
|
||||
IReadOnlyDictionary<string, List<DeviceIoPointDefinition>> DefinitionsByModule { get; }
|
||||
IReadOnlyDictionary<string, DeviceIoPointState> LatestPoints { get; }
|
||||
bool IsOnline { get; }
|
||||
bool TryGetPoint(int id, out DeviceIoPointState point);
|
||||
bool TryGetPoint(string name, out DeviceIoPointState point);
|
||||
bool TryGetPointByPointKey(string pointKey, out DeviceIoPointState point);
|
||||
bool IsPointOn(int id);
|
||||
bool IsPointOn(string name);
|
||||
bool IsPointOnByPointKey(string pointKey);
|
||||
Task<bool> WaitForPointStateAsync(int id, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> WaitForPointStateAsync(string name, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> WaitForPointStateByPointKeyAsync(string pointKey, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
IReadOnlyList<DeviceIoPointState> GetPointsByModule(string module);
|
||||
void Start();
|
||||
void Stop();
|
||||
void RequestRefresh();
|
||||
bool TryRefreshNow(TimeSpan timeout);
|
||||
Task<bool> TryRefreshNowAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
bool TrySetOutputState(int id, bool outputOn);
|
||||
bool TrySetOutputState(string name, bool outputOn);
|
||||
bool TrySetOutputStateByPointKey(string pointKey, bool outputOn);
|
||||
bool TrySetOutputStates(IReadOnlyList<DeviceIoOutputWriteRequest> requests, out string failurePointReference);
|
||||
bool TrySetOutputStatesByPointKey(IReadOnlyDictionary<string, bool> outputStates, out string failurePointKey);
|
||||
}
|
||||
|
||||
public class DeviceIoMonitorService : IDeviceIoMonitorService
|
||||
{
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly AutoResetEvent _refreshSignal = new AutoResetEvent(false);
|
||||
private readonly TimeSpan _pollInterval = TimeSpan.FromMilliseconds(100);
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly List<DeviceIoPointDefinition> _definitions = new List<DeviceIoPointDefinition>();
|
||||
private readonly Dictionary<string, List<DeviceIoPointDefinition>> _definitionsByModule = new Dictionary<string, List<DeviceIoPointDefinition>>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, DeviceIoPointDefinition> _definitionsByName = new Dictionary<string, DeviceIoPointDefinition>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, DeviceIoPointDefinition> _definitionsByPointKey = new Dictionary<string, DeviceIoPointDefinition>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly object _refreshWaitersSyncRoot = new object();
|
||||
private readonly List<TaskCompletionSource<bool>> _refreshWaiters = new List<TaskCompletionSource<bool>>();
|
||||
private const string DeviceIoConfigFileName = "DeviceIoPoints.csv";
|
||||
|
||||
private readonly Dictionary<string, DeviceIoPointState> _latestPoints = new Dictionary<string, DeviceIoPointState>(StringComparer.OrdinalIgnoreCase);
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
public event EventHandler<DeviceIoChangedEventArgs> IoChanged;
|
||||
|
||||
public IReadOnlyList<DeviceIoPointDefinition> Definitions => _definitions;
|
||||
public IReadOnlyDictionary<string, List<DeviceIoPointDefinition>> DefinitionsByModule => _definitionsByModule;
|
||||
public IReadOnlyDictionary<string, DeviceIoPointState> LatestPoints => GetLatestPointsSnapshot();
|
||||
public bool IsOnline => _hardwareManager.TdCard != null || _hardwareManager.AcsCard != null;
|
||||
|
||||
|
||||
public DeviceIoMonitorService(HardwareManager hardwareManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager;
|
||||
LoadIoPointDefinitions();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_cts != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LoadIoPointDefinitions();
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
_refreshSignal.WaitOne(_pollInterval);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
IList<DeviceIoPointState> readPoints;
|
||||
if (!TryReadIoPoints(out readPoints) || readPoints == null || readPoints.Count == 0)
|
||||
{
|
||||
CompleteRefreshWaiters(false);
|
||||
await Task.Yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
var changed = CollectChangedPoints(readPoints);
|
||||
CompleteRefreshWaiters(true);
|
||||
if (changed.Count == 0)
|
||||
{
|
||||
await Task.Yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
var handler = IoChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
var changedCopy = changed.Select(x => x.Clone()).ToList();
|
||||
var allCopy = GetLatestPointsSnapshot();
|
||||
Execute.OnUIThread(() => handler(this, new DeviceIoChangedEventArgs(changedCopy, allCopy)));
|
||||
}
|
||||
|
||||
await Task.Yield();
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_cts == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cts.Cancel();
|
||||
_refreshSignal.Set();
|
||||
CompleteRefreshWaiters(false);
|
||||
_cts.Dispose();
|
||||
_cts = null;
|
||||
}
|
||||
|
||||
public void RequestRefresh()
|
||||
{
|
||||
_refreshSignal.Set();
|
||||
}
|
||||
|
||||
public bool TryRefreshNow(TimeSpan timeout)
|
||||
{
|
||||
return TryRefreshNowAsync(timeout).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<bool> TryRefreshNowAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
Start();
|
||||
|
||||
var waiter = new TaskCompletionSource<bool>();
|
||||
CancellationTokenRegistration registration = default(CancellationTokenRegistration);
|
||||
lock (_refreshWaitersSyncRoot)
|
||||
{
|
||||
_refreshWaiters.Add(waiter);
|
||||
}
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
registration = cancellationToken.Register(() => waiter.TrySetCanceled());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RequestRefresh();
|
||||
|
||||
if (timeout <= TimeSpan.Zero)
|
||||
{
|
||||
timeout = _pollInterval;
|
||||
}
|
||||
|
||||
var completedTask = await Task.WhenAny(waiter.Task, Task.Delay(timeout, cancellationToken));
|
||||
if (completedTask != waiter.Task)
|
||||
{
|
||||
RemoveRefreshWaiter(waiter);
|
||||
return false;
|
||||
}
|
||||
|
||||
return await waiter.Task;
|
||||
}
|
||||
finally
|
||||
{
|
||||
registration.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TrySetOutputState(int id, bool outputOn)
|
||||
{
|
||||
var definition = ResolveUniqueDefinitionById(id, IoPointType.Output);
|
||||
if (definition == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = _hardwareManager.TryWriteIoPointValue(definition, outputOn);
|
||||
if (result)
|
||||
{
|
||||
RequestRefresh();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TrySetOutputStateByPointKey(string pointKey, bool outputOn)
|
||||
{
|
||||
DeviceIoPointDefinition definition;
|
||||
if (!_definitionsByPointKey.TryGetValue(pointKey ?? string.Empty, out definition) || definition.Type != IoPointType.Output || !definition.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = _hardwareManager.TryWriteIoPointValue(definition, outputOn);
|
||||
if (result)
|
||||
{
|
||||
RequestRefresh();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TrySetOutputStates(IReadOnlyList<DeviceIoOutputWriteRequest> requests, out string failurePointReference)
|
||||
{
|
||||
failurePointReference = string.Empty;
|
||||
if (requests == null || requests.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var definitions = new List<Tuple<DeviceIoPointDefinition, bool, string>>();
|
||||
foreach (var request in requests)
|
||||
{
|
||||
if (request == null || string.IsNullOrWhiteSpace(request.PointReference))
|
||||
{
|
||||
failurePointReference = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
DeviceIoPointDefinition definition;
|
||||
if (!TryResolveOutputDefinition(request.PointReference, out definition))
|
||||
{
|
||||
failurePointReference = request.PointReference;
|
||||
return false;
|
||||
}
|
||||
|
||||
definitions.Add(Tuple.Create(definition, request.OutputOn, request.PointReference));
|
||||
}
|
||||
|
||||
foreach (var item in definitions)
|
||||
{
|
||||
if (!_hardwareManager.TryWriteIoPointValue(item.Item1, item.Item2))
|
||||
{
|
||||
failurePointReference = item.Item3;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RequestRefresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySetOutputStatesByPointKey(IReadOnlyDictionary<string, bool> outputStates, out string failurePointKey)
|
||||
{
|
||||
failurePointKey = string.Empty;
|
||||
if (outputStates == null || outputStates.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var definitions = new List<Tuple<DeviceIoPointDefinition, bool, string>>();
|
||||
foreach (var item in outputStates)
|
||||
{
|
||||
DeviceIoPointDefinition definition;
|
||||
if (!_definitionsByPointKey.TryGetValue(item.Key ?? string.Empty, out definition) || definition.Type != IoPointType.Output || !definition.Enabled)
|
||||
{
|
||||
failurePointKey = item.Key;
|
||||
return false;
|
||||
}
|
||||
|
||||
definitions.Add(Tuple.Create(definition, item.Value, item.Key));
|
||||
}
|
||||
|
||||
foreach (var item in definitions)
|
||||
{
|
||||
if (!_hardwareManager.TryWriteIoPointValue(item.Item1, item.Item2))
|
||||
{
|
||||
failurePointKey = item.Item3;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RequestRefresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySetOutputState(string name, bool outputOn)
|
||||
{
|
||||
DeviceIoPointDefinition definition;
|
||||
if (!_definitionsByName.TryGetValue(name ?? string.Empty, out definition) || definition.Type != IoPointType.Output || !definition.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = _hardwareManager.TryWriteIoPointValue(definition, outputOn);
|
||||
if (result)
|
||||
{
|
||||
RequestRefresh();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TryGetPoint(int id, out DeviceIoPointState point)
|
||||
{
|
||||
var definition = ResolveUniqueDefinitionById(id, null);
|
||||
if (definition == null)
|
||||
{
|
||||
point = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_latestPoints.TryGetValue(definition.PointKey, out point))
|
||||
{
|
||||
point = point.Clone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
point = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetPointByPointKey(string pointKey, out DeviceIoPointState point)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_latestPoints.TryGetValue(pointKey ?? string.Empty, out point))
|
||||
{
|
||||
point = point.Clone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
point = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsPointOn(int id)
|
||||
{
|
||||
DeviceIoPointState point;
|
||||
return TryGetPoint(id, out point) && point.Value;
|
||||
}
|
||||
|
||||
public bool IsPointOn(string name)
|
||||
{
|
||||
DeviceIoPointState point;
|
||||
return TryGetPoint(name, out point) && point.Value;
|
||||
}
|
||||
|
||||
public bool IsPointOnByPointKey(string pointKey)
|
||||
{
|
||||
DeviceIoPointState point;
|
||||
return TryGetPointByPointKey(pointKey, out point) && point.Value;
|
||||
}
|
||||
|
||||
public async Task<bool> WaitForPointStateAsync(int id, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
while (DateTime.UtcNow - start < timeout)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var remaining = timeout - (DateTime.UtcNow - start);
|
||||
var refreshTimeout = remaining < _pollInterval ? remaining : _pollInterval;
|
||||
if (refreshTimeout > TimeSpan.Zero)
|
||||
{
|
||||
await TryRefreshNowAsync(refreshTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
if (IsPointOn(id) == expectedState)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
await Task.Delay(50, cancellationToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> WaitForPointStateByPointKeyAsync(string pointKey, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
while (DateTime.UtcNow - start < timeout)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var remaining = timeout - (DateTime.UtcNow - start);
|
||||
var refreshTimeout = remaining < _pollInterval ? remaining : _pollInterval;
|
||||
if (refreshTimeout > TimeSpan.Zero)
|
||||
{
|
||||
await TryRefreshNowAsync(refreshTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
if (IsPointOnByPointKey(pointKey) == expectedState)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
await Task.Delay(50, cancellationToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> WaitForPointStateAsync(string name, bool expectedState, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
while (DateTime.UtcNow - start < timeout)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var remaining = timeout - (DateTime.UtcNow - start);
|
||||
var refreshTimeout = remaining < _pollInterval ? remaining : _pollInterval;
|
||||
if (refreshTimeout > TimeSpan.Zero)
|
||||
{
|
||||
await TryRefreshNowAsync(refreshTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
if (IsPointOn(name) == expectedState)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
await Task.Delay(50, cancellationToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetPoint(string name, out DeviceIoPointState point)
|
||||
{
|
||||
DeviceIoPointDefinition definition;
|
||||
if (!_definitionsByName.TryGetValue(name ?? string.Empty, out definition) && !_definitionsByPointKey.TryGetValue(name ?? string.Empty, out definition))
|
||||
{
|
||||
point = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_latestPoints.TryGetValue(definition.PointKey, out point))
|
||||
{
|
||||
point = point.Clone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
point = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IReadOnlyList<DeviceIoPointState> GetPointsByModule(string module)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _latestPoints.Values
|
||||
.Where(x => string.Equals(x.Module, module, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(x => x.Clone())
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryReadIoPoints(out IList<DeviceIoPointState> points)
|
||||
{
|
||||
points = new List<DeviceIoPointState>();
|
||||
|
||||
if (Definitions == null || Definitions.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary<string, bool> readValues;
|
||||
bool hasBatchValues = _hardwareManager.TryReadIoPointValues(Definitions, out readValues);
|
||||
|
||||
foreach (DeviceIoPointDefinition definition in Definitions)
|
||||
{
|
||||
bool value;
|
||||
if (!definition.Enabled)
|
||||
{
|
||||
value = definition.DefaultValue;
|
||||
}
|
||||
else if (hasBatchValues && readValues != null && readValues.TryGetValue(definition.PointKey, out value))
|
||||
{
|
||||
}
|
||||
else if (!_hardwareManager.TryReadIoPointValue(definition, out value))
|
||||
{
|
||||
value = definition.DefaultValue;
|
||||
}
|
||||
|
||||
DeviceIoPointState state;
|
||||
if (definition.Type == IoPointType.Output)
|
||||
{
|
||||
state = new DeviceOutputPointState
|
||||
{
|
||||
Id = definition.Id,
|
||||
PointKey = definition.PointKey,
|
||||
Module = definition.Module,
|
||||
Name = definition.Name,
|
||||
Card = definition.Card,
|
||||
StationNo = definition.StationNo,
|
||||
LineNo = definition.LineNo,
|
||||
Value = value,
|
||||
IsInverse = definition.IsInverse,
|
||||
CommandValue = value
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
state = new DeviceInputPointState
|
||||
{
|
||||
Id = definition.Id,
|
||||
PointKey = definition.PointKey,
|
||||
Module = definition.Module,
|
||||
Name = definition.Name,
|
||||
Card = definition.Card,
|
||||
StationNo = definition.StationNo,
|
||||
LineNo = definition.LineNo,
|
||||
Value = value,
|
||||
IsInverse = definition.IsInverse
|
||||
};
|
||||
}
|
||||
|
||||
points.Add(state);
|
||||
}
|
||||
|
||||
return points.Count > 0;
|
||||
}
|
||||
|
||||
private void LoadIoPointDefinitions()
|
||||
{
|
||||
_definitions.Clear();
|
||||
_definitionsByModule.Clear();
|
||||
_definitionsByName.Clear();
|
||||
_definitionsByPointKey.Clear();
|
||||
|
||||
var pointKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var path = ResolveConfigPath();
|
||||
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = File.ReadAllLines(path, Encoding.UTF8);
|
||||
for (var i = 0; i < lines.Length; i++)
|
||||
{
|
||||
var raw = lines[i];
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var line = raw.Trim();
|
||||
if (line.StartsWith("#"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == 0 && line.IndexOf("Id", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var cols = line.Split(',');
|
||||
if (cols.Length < 11)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int id;
|
||||
if (!int.TryParse(cols[0].Trim(), out id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var moduleIndex = 1;
|
||||
var nameIndex = 2;
|
||||
var typeIndex = 3;
|
||||
var cardIndex = 4;
|
||||
var stationIndex = 5;
|
||||
var indexColumnIndex = 6;
|
||||
var subIndexColumnIndex = 7;
|
||||
var lineNoIndex = 8;
|
||||
var enabledIndex = 9;
|
||||
var defaultValueIndex = 10;
|
||||
var isInverseIndex = cols.Length >= 12 ? 11 : -1;
|
||||
var descriptionIndex = isInverseIndex >= 0 ? 12 : 11;
|
||||
|
||||
var name = cols[nameIndex].Trim();
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var module = cols[moduleIndex].Trim();
|
||||
if (string.IsNullOrWhiteSpace(module))
|
||||
{
|
||||
module = "Default";
|
||||
}
|
||||
|
||||
var type = ParseIoPointType(cols[typeIndex]);
|
||||
var card = ParseIoCardType(cols[cardIndex]);
|
||||
var stationNo = ParseInt(cols[stationIndex]);
|
||||
var index = ParseInt(cols[indexColumnIndex]);
|
||||
var subIndex = ParseInt(cols[subIndexColumnIndex]);
|
||||
var lineNo = cols[lineNoIndex].Trim();
|
||||
var pointKey = DeviceIoPointDefinition.BuildPointKey(type, card, stationNo, index, subIndex, lineNo);
|
||||
|
||||
if (!pointKeys.Add(pointKey) || !names.Add(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var definition = new DeviceIoPointDefinition
|
||||
{
|
||||
Id = id,
|
||||
PointKey = pointKey,
|
||||
Module = module,
|
||||
Name = name,
|
||||
Type = type,
|
||||
Card = card,
|
||||
StationNo = stationNo,
|
||||
Index = index,
|
||||
SubIndex = subIndex,
|
||||
LineNo = lineNo,
|
||||
Enabled = ParseBoolean(cols[enabledIndex], true),
|
||||
DefaultValue = ParseBoolean(cols[defaultValueIndex], false),
|
||||
IsInverse = isInverseIndex >= 0 && isInverseIndex < cols.Length && ParseBoolean(cols[isInverseIndex], false),
|
||||
Description = cols.Length > descriptionIndex ? string.Join(",", cols.Skip(descriptionIndex)).Trim() : string.Empty
|
||||
};
|
||||
|
||||
_definitions.Add(definition);
|
||||
_definitionsByName[definition.Name] = definition;
|
||||
_definitionsByPointKey[definition.PointKey] = definition;
|
||||
|
||||
List<DeviceIoPointDefinition> moduleDefinitions;
|
||||
if (!_definitionsByModule.TryGetValue(module, out moduleDefinitions))
|
||||
{
|
||||
moduleDefinitions = new List<DeviceIoPointDefinition>();
|
||||
_definitionsByModule[module] = moduleDefinitions;
|
||||
}
|
||||
|
||||
moduleDefinitions.Add(definition);
|
||||
}
|
||||
}
|
||||
|
||||
private static int ParseInt(string raw)
|
||||
{
|
||||
int value;
|
||||
return int.TryParse((raw ?? string.Empty).Trim(), out value) ? value : 0;
|
||||
}
|
||||
|
||||
private static IoPointType ParseIoPointType(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
return IoPointType.Input;
|
||||
}
|
||||
|
||||
var text = raw.Trim();
|
||||
if (string.Equals(text, "Output", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "OUT", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "DO", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "O", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return IoPointType.Output;
|
||||
}
|
||||
|
||||
return IoPointType.Input;
|
||||
}
|
||||
|
||||
private static IoCardType ParseIoCardType(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
return IoCardType.Td;
|
||||
}
|
||||
|
||||
var text = raw.Trim();
|
||||
if (string.Equals(text, "Acs", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "ACS", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return IoCardType.Acs;
|
||||
}
|
||||
|
||||
return IoCardType.Td;
|
||||
}
|
||||
|
||||
private static bool ParseBoolean(string raw, bool defaultValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var text = raw.Trim();
|
||||
if (string.Equals(text, "1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "true", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "yes", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "y", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(text, "0", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "false", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "no", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(text, "n", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private DeviceIoPointDefinition ResolveUniqueDefinitionById(int id, IoPointType? pointType)
|
||||
{
|
||||
var matches = _definitions
|
||||
.Where(x => x.Id == id && (!pointType.HasValue || x.Type == pointType.Value))
|
||||
.Take(2)
|
||||
.ToList();
|
||||
|
||||
return matches.Count == 1 ? matches[0] : null;
|
||||
}
|
||||
|
||||
private void CompleteRefreshWaiters(bool result)
|
||||
{
|
||||
List<TaskCompletionSource<bool>> waiters;
|
||||
lock (_refreshWaitersSyncRoot)
|
||||
{
|
||||
if (_refreshWaiters.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
waiters = _refreshWaiters.ToList();
|
||||
_refreshWaiters.Clear();
|
||||
}
|
||||
|
||||
foreach (var waiter in waiters)
|
||||
{
|
||||
waiter.TrySetResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveRefreshWaiter(TaskCompletionSource<bool> waiter)
|
||||
{
|
||||
lock (_refreshWaitersSyncRoot)
|
||||
{
|
||||
_refreshWaiters.Remove(waiter);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryResolveOutputDefinition(string pointReference, out DeviceIoPointDefinition definition)
|
||||
{
|
||||
definition = null;
|
||||
if (string.IsNullOrWhiteSpace(pointReference))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_definitionsByPointKey.TryGetValue(pointReference, out definition))
|
||||
{
|
||||
return definition.Type == IoPointType.Output && definition.Enabled;
|
||||
}
|
||||
|
||||
if (_definitionsByName.TryGetValue(pointReference, out definition))
|
||||
{
|
||||
return definition.Type == IoPointType.Output && definition.Enabled;
|
||||
}
|
||||
|
||||
int id;
|
||||
if (int.TryParse(pointReference, out id))
|
||||
{
|
||||
definition = ResolveUniqueDefinitionById(id, IoPointType.Output);
|
||||
return definition != null && definition.Enabled;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string ResolveConfigPath()
|
||||
{
|
||||
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
var localPath = Path.Combine(baseDir, "Configuration", DeviceIoConfigFileName);
|
||||
var sourcePath = ResolveRootConfigPath(baseDir);
|
||||
|
||||
if (TrySyncConfigToLocal(sourcePath, localPath))
|
||||
{
|
||||
return localPath;
|
||||
}
|
||||
|
||||
return File.Exists(localPath) ? localPath : null;
|
||||
}
|
||||
|
||||
private static string ResolveRootConfigPath(string baseDir)
|
||||
{
|
||||
var current = new DirectoryInfo(baseDir);
|
||||
while (current != null)
|
||||
{
|
||||
var rootConfigDir = Path.Combine(current.FullName, "Configuration");
|
||||
if (Directory.Exists(rootConfigDir) && Directory.Exists(Path.Combine(current.FullName, "MainShell")))
|
||||
{
|
||||
var path = Path.Combine(rootConfigDir, DeviceIoConfigFileName);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool TrySyncConfigToLocal(string sourcePath, string localPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourcePath) || !File.Exists(sourcePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(Path.GetFullPath(sourcePath), Path.GetFullPath(localPath), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var localDir = Path.GetDirectoryName(localPath);
|
||||
if (!string.IsNullOrWhiteSpace(localDir))
|
||||
{
|
||||
Directory.CreateDirectory(localDir);
|
||||
}
|
||||
|
||||
if (!File.Exists(localPath) || File.GetLastWriteTimeUtc(localPath) < File.GetLastWriteTimeUtc(sourcePath))
|
||||
{
|
||||
File.Copy(sourcePath, localPath, true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<DeviceIoPointState> CollectChangedPoints(IList<DeviceIoPointState> readPoints)
|
||||
{
|
||||
var changed = new List<DeviceIoPointState>();
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_latestPoints.Count == 0)
|
||||
{
|
||||
foreach (var point in readPoints)
|
||||
{
|
||||
if (point == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_latestPoints[point.PointKey] = PreparePoint(point.Clone());
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
foreach (var point in readPoints)
|
||||
{
|
||||
if (point == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_latestPoints.TryGetValue(point.PointKey, out DeviceIoPointState old))
|
||||
{
|
||||
_latestPoints[point.PointKey] = PreparePoint(point.Clone());
|
||||
changed.Add(PreparePoint(point.Clone()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (HasPointChanged(old, point))
|
||||
{
|
||||
_latestPoints[point.PointKey] = PreparePoint(point.Clone());
|
||||
changed.Add(PreparePoint(point.Clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<string, DeviceIoPointState> GetLatestPointsSnapshot()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _latestPoints.ToDictionary(x => x.Key, x => PreparePoint(x.Value.Clone()));
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceIoPointState PreparePoint(DeviceIoPointState point)
|
||||
{
|
||||
var outputPoint = point as DeviceOutputPointState;
|
||||
DeviceIoPointDefinition definition;
|
||||
_definitionsByPointKey.TryGetValue(point.PointKey ?? string.Empty, out definition);
|
||||
if (outputPoint != null && definition != null && definition.Enabled)
|
||||
{
|
||||
outputPoint.BindWriteAction(outputOn => TrySetOutputState(definition.Name, outputOn));
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
private static bool HasPointChanged(DeviceIoPointState oldPoint, DeviceIoPointState newPoint)
|
||||
{
|
||||
if (oldPoint == null || newPoint == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (oldPoint.Value != newPoint.Value ||
|
||||
oldPoint.PointType != newPoint.PointType ||
|
||||
!string.Equals(oldPoint.PointKey, newPoint.PointKey, StringComparison.OrdinalIgnoreCase) ||
|
||||
!string.Equals(oldPoint.Name, newPoint.Name, StringComparison.OrdinalIgnoreCase) ||
|
||||
!string.Equals(oldPoint.Module, newPoint.Module, StringComparison.OrdinalIgnoreCase) ||
|
||||
oldPoint.Card != newPoint.Card)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var oldOutput = oldPoint as DeviceOutputPointState;
|
||||
var newOutput = newPoint as DeviceOutputPointState;
|
||||
if (oldOutput != null && newOutput != null)
|
||||
{
|
||||
return oldOutput.CommandValue != newOutput.CommandValue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MainShell.Hardware
|
||||
{
|
||||
public enum IoPointType
|
||||
{
|
||||
Input = 0,
|
||||
Output = 1
|
||||
}
|
||||
|
||||
public enum IoCardType
|
||||
{
|
||||
Td = 0,
|
||||
Acs = 1
|
||||
}
|
||||
|
||||
public class DeviceIoPointDefinition
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string PointKey { get; set; }
|
||||
public string Module { get; set; }
|
||||
public string Name { get; set; }
|
||||
public IoPointType Type { get; set; }
|
||||
public IoCardType Card { get; set; }
|
||||
public int StationNo { get; set; }
|
||||
public int Index { get; set; }
|
||||
public int SubIndex { get; set; }
|
||||
public string LineNo { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
public bool DefaultValue { get; set; }
|
||||
public bool IsInverse { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public static string BuildPointKey(IoPointType type, IoCardType card, int stationNo, string lineNo)
|
||||
{
|
||||
return BuildPointKey(type, card, stationNo, 0, 0, lineNo);
|
||||
}
|
||||
|
||||
public static string BuildPointKey(IoPointType type, IoCardType card, int stationNo, int index, int subIndex, string lineNo)
|
||||
{
|
||||
return string.Format(
|
||||
"{0}:{1}:{2}:{3}:{4}:{5}",
|
||||
type,
|
||||
card,
|
||||
stationNo,
|
||||
index,
|
||||
subIndex,
|
||||
(lineNo ?? string.Empty).Trim().ToUpperInvariant());
|
||||
}
|
||||
}
|
||||
|
||||
public class DeviceIoOutputWriteRequest
|
||||
{
|
||||
public string PointReference { get; set; }
|
||||
public bool OutputOn { get; set; }
|
||||
}
|
||||
|
||||
public abstract class DeviceIoPointState
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string PointKey { get; set; }
|
||||
public string Module { get; set; }
|
||||
public string Name { get; set; }
|
||||
public IoCardType Card { get; set; }
|
||||
public int StationNo { get; set; }
|
||||
public string LineNo { get; set; }
|
||||
public bool Value { get; set; }
|
||||
public bool IsInverse { get; set; }
|
||||
public abstract IoPointType PointType { get; }
|
||||
public abstract DeviceIoPointState Clone();
|
||||
}
|
||||
|
||||
public class DeviceInputPointState : DeviceIoPointState
|
||||
{
|
||||
public override IoPointType PointType => IoPointType.Input;
|
||||
|
||||
public override DeviceIoPointState Clone()
|
||||
{
|
||||
return new DeviceInputPointState
|
||||
{
|
||||
Id = Id,
|
||||
PointKey = PointKey,
|
||||
Module = Module,
|
||||
Name = Name,
|
||||
Card = Card,
|
||||
StationNo = StationNo,
|
||||
LineNo = LineNo,
|
||||
Value = Value,
|
||||
IsInverse = IsInverse
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class DeviceOutputPointState : DeviceIoPointState
|
||||
{
|
||||
private Func<bool, bool> _writeAction;
|
||||
|
||||
public bool CommandValue { get; set; }
|
||||
public bool CanWrite => _writeAction != null;
|
||||
|
||||
public override IoPointType PointType => IoPointType.Output;
|
||||
|
||||
public void BindWriteAction(Func<bool, bool> writeAction)
|
||||
{
|
||||
_writeAction = writeAction;
|
||||
}
|
||||
|
||||
public bool TrySetOutput(bool outputOn)
|
||||
{
|
||||
if (_writeAction == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = _writeAction(outputOn);
|
||||
if (result)
|
||||
{
|
||||
CommandValue = outputOn;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TryTurnOn()
|
||||
{
|
||||
return TrySetOutput(true);
|
||||
}
|
||||
|
||||
public bool TryTurnOff()
|
||||
{
|
||||
return TrySetOutput(false);
|
||||
}
|
||||
|
||||
public override DeviceIoPointState Clone()
|
||||
{
|
||||
var clone = new DeviceOutputPointState
|
||||
{
|
||||
Id = Id,
|
||||
PointKey = PointKey,
|
||||
Module = Module,
|
||||
Name = Name,
|
||||
Card = Card,
|
||||
StationNo = StationNo,
|
||||
LineNo = LineNo,
|
||||
Value = Value,
|
||||
CommandValue = CommandValue,
|
||||
IsInverse = IsInverse
|
||||
};
|
||||
|
||||
clone.BindWriteAction(_writeAction);
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
public class IoModuleSnapshot
|
||||
{
|
||||
public string Module { get; set; }
|
||||
public IReadOnlyList<DeviceIoPointState> Points { get; set; } = new List<DeviceIoPointState>();
|
||||
}
|
||||
|
||||
public class DeviceIoSnapshot
|
||||
{
|
||||
public bool IsValid { get; set; }
|
||||
public DateTime Timestamp { get; set; } = DateTime.Now;
|
||||
public IReadOnlyDictionary<string, DeviceIoPointState> AllPoints { get; set; } = new Dictionary<string, DeviceIoPointState>();
|
||||
|
||||
public IReadOnlyList<IoModuleSnapshot> Modules
|
||||
{
|
||||
get
|
||||
{
|
||||
return AllPoints.Values
|
||||
.GroupBy(x => string.IsNullOrWhiteSpace(x.Module) ? "Default" : x.Module)
|
||||
.Select(g => new IoModuleSnapshot
|
||||
{
|
||||
Module = g.Key,
|
||||
Points = g.Select(x => x.Clone()).ToList()
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
namespace MainShell.Hardware
|
||||
{
|
||||
public static class DeviceIoNames
|
||||
{
|
||||
public static class WZ
|
||||
{
|
||||
public const string StartButton = "PG.WZ.StartButton";
|
||||
public const string StopButton = "PG.WZ.StopButton";
|
||||
public const string ResetButton = "PG.WZ.ResetButton";
|
||||
public const string EmergencyStopButton = "PG.WZ.EmergencyStopButton";
|
||||
public const string BlueButton = "PG.WZ.BlueButton";
|
||||
public const string SafetyDoor1Closed = "PG.WZ.SafetyDoor1Closed";
|
||||
public const string SafetyDoor2Closed = "PG.WZ.SafetyDoor2Closed";
|
||||
public const string SafetyDoor3Closed = "PG.WZ.SafetyDoor3Closed";
|
||||
public const string SafetyDoor4Closed = "PG.WZ.SafetyDoor4Closed";
|
||||
public const string SecurityDoorShield = "PG.WZ.SecurityDoorShield";
|
||||
public const string StartButtonLight = "PG.WZ.StartButtonLight";
|
||||
public const string StopButtonLight = "PG.WZ.StopButtonLight";
|
||||
public const string ResetButtonLight = "PG.WZ.ResetButtonLight";
|
||||
public const string BlueButtonLight = "PG.WZ.BlueButtonLight";
|
||||
public const string WorkLight1 = "PG.WZ.WorkLight1";
|
||||
public const string StackLightRed = "PG.WZ.StackLightRed";
|
||||
public const string StackLightYellow = "PG.WZ.StackLightYellow";
|
||||
public const string StackLightGreen = "PG.WZ.StackLightGreen";
|
||||
public const string StackLightBlue = "PG.WZ.StackLightBlue";
|
||||
public const string BuzzerAct = "PG.WZ.BuzzerAct";
|
||||
public const string SafetyDoor1Open = "PG.WZ.SafetyDoor1Open";
|
||||
public const string SafetyDoor2Open = "PG.WZ.SafetyDoor2Open";
|
||||
public const string SafetyDoor3Open = "PG.WZ.SafetyDoor3Open";
|
||||
public const string SafetyDoor4Open = "PG.WZ.SafetyDoor4Open";
|
||||
}
|
||||
|
||||
public static class Stage
|
||||
{
|
||||
public const string ToolSetterSignal = "PG.Stage.ToolSetterSignal";
|
||||
public const string SubstrateInPlace = "PG.Stage.SubstrateInPlace";
|
||||
public const string LifterCyl1WorkPos = "PG.Stage.LifterCyl1WorkPos";
|
||||
public const string LifterCyl1HomePos = "PG.Stage.LifterCyl1HomePos";
|
||||
public const string LifterCyl2WorkPos = "PG.Stage.LifterCyl2WorkPos";
|
||||
public const string LifterCyl2HomePos = "PG.Stage.LifterCyl2HomePos";
|
||||
public const string LifterCyl1Act = "PG.Stage.LifterCyl1Act";
|
||||
public const string LifterCyl2Act = "PG.Stage.LifterCyl2Act";
|
||||
public const string BreakingTheVacuum1 = "PG.Stage.BreakingTheVacuum1";
|
||||
public const string BreakingTheVacuum2 = "PG.Stage.BreakingTheVacuum2";
|
||||
public const string BreakingTheVacuum3 = "PG.Stage.BreakingTheVacuum3";
|
||||
public const string BreakingTheVacuum4 = "PG.Stage.BreakingTheVacuum4";
|
||||
public const string Vacuum1 = "PG.Stage.Vacuum1";
|
||||
public const string Vacuum2 = "PG.Stage.Vacuum2";
|
||||
public const string Vacuum3 = "PG.Stage.Vacuum3";
|
||||
public const string Vacuum4 = "PG.Stage.Vacuum4";
|
||||
}
|
||||
|
||||
public static class WS
|
||||
{
|
||||
public const string ThetaClampCyl1WorkPos = "PG.WS.ThetaClampCyl1WorkPos";
|
||||
public const string ThetaClampCyl1HomePos = "PG.WS.ThetaClampCyl1HomePos";
|
||||
public const string ThetaClampCyl2WorkPos = "PG.WS.ThetaClampCyl2WorkPos";
|
||||
public const string ThetaClampCyl2HomePos = "PG.WS.ThetaClampCyl2HomePos";
|
||||
public const string ThetaClampCyl3WorkPos = "PG.WS.ThetaClampCyl3WorkPos";
|
||||
public const string ThetaClampCyl3HomePos = "PG.WS.ThetaClampCyl3HomePos";
|
||||
public const string ThetaClampCyl4WorkPos = "PG.WS.ThetaClampCyl4WorkPos";
|
||||
public const string ThetaClampCyl4HomePos = "PG.WS.ThetaClampCyl4HomePos";
|
||||
public const string ThetaTrayPresentSensor1 = "PG.WS.ThetaTrayPresentSensor1";
|
||||
public const string ThetaTrayPresentSensor2 = "PG.WS.ThetaTrayPresentSensor2";
|
||||
public const string ThetaTrayPresentSensor3 = "PG.WS.ThetaTrayPresentSensor3";
|
||||
public const string ThetaTrayPresentSensor4 = "PG.WS.ThetaTrayPresentSensor4";
|
||||
public const string BondHead1InPlace = "PG.WS.BondHead1InPlace";
|
||||
public const string BondHead2InPlace = "PG.WS.BondHead2InPlace";
|
||||
public const string BondHead3InPlace = "PG.WS.BondHead3InPlace";
|
||||
public const string BondHead4InPlace = "PG.WS.BondHead4InPlace";
|
||||
public const string ThetaClampCylAct = "PG.WS.ThetaClampCylAct";
|
||||
public const string ThetaBrakePressCylAct = "PG.WS.ThetaBrakePressCylAct";
|
||||
}
|
||||
|
||||
public static class WEU
|
||||
{
|
||||
public const string Z11MaterialSensor1 = "PG.WEU.Z11MaterialSensor1";
|
||||
public const string Z11MaterialSensor2 = "PG.WEU.Z11MaterialSensor2";
|
||||
public const string Z12MaterialSensor3 = "PG.WEU.Z12MaterialSensor3";
|
||||
public const string Z12MaterialSensor4 = "PG.WEU.Z12MaterialSensor4";
|
||||
public const string IonBarAct1 = "PG.WEU.IonBarAct1";
|
||||
public const string IonBarAct2 = "PG.WEU.IonBarAct2";
|
||||
public const string TrayBreakVacuum1 = "PG.WEU.TrayBreakVacuum1";
|
||||
public const string TrayBreakVacuum2 = "PG.WEU.TrayBreakVacuum2";
|
||||
public const string TrayVacuum1 = "PG.WEU.TrayVacuum1";
|
||||
public const string TrayVacuum2 = "PG.WEU.TrayVacuum2";
|
||||
}
|
||||
|
||||
public static class CV
|
||||
{
|
||||
public const string UpperInletSensor = "PG.CV.UpperInletSensor";
|
||||
public const string UpperInPlaceSensor = "PG.CV.UpperInPlaceSensor";
|
||||
public const string LowerOutletSensor = "PG.CV.LowerOutletSensor";
|
||||
public const string LowerInPlaceSensor = "PG.CV.LowerInPlaceSensor";
|
||||
public const string IonWindRodAlarmDetection = "PG.CV.IonWindRodAlarmDetection";
|
||||
public const string IonBarAct = "PG.CV.IonBarAct";
|
||||
}
|
||||
|
||||
public static class General
|
||||
{
|
||||
public const string PhaseLossSensor = "PG.General.PhaseLossSensor";
|
||||
public const string SafetyCircuitStatus1 = "PG.General.SafetyCircuitStatus1";
|
||||
public const string SafetyCircuitStatus2 = "PG.General.SafetyCircuitStatus2";
|
||||
}
|
||||
|
||||
public static class EFEM
|
||||
{
|
||||
public const string IonWindRodAlarmDetection1 = "PG.EFEM.IonWindRodAlarmDetection1";
|
||||
public const string IonWindRodAlarmDetection2 = "PG.EFEM.IonWindRodAlarmDetection2";
|
||||
public const string WorkLight1 = "PG.EFEM.WorkLight1";
|
||||
}
|
||||
|
||||
public static class AxisZ
|
||||
{
|
||||
public const string Z1GuideRailBrake = "PG.AxisZ.Z1GuideRailBrake";
|
||||
public const string Z3CameraAirCooling = "PG.AxisZ.Z3CameraAirCooling";
|
||||
public const string Z5CameraAirCooling = "PG.AxisZ.Z5CameraAirCooling";
|
||||
public const string Z10CameraAirCooling = "PG.AxisZ.Z10CameraAirCooling";
|
||||
public const string Z2BondHeadAirCooling = "PG.AxisZ.Z2BondHeadAirCooling";
|
||||
public const string Z2BondHeadBreakVacuum = "PG.AxisZ.Z2BondHeadBreakVacuum";
|
||||
public const string Z2BondHeadVacuum = "PG.AxisZ.Z2BondHeadVacuum";
|
||||
}
|
||||
|
||||
public static class SubstrateLoad
|
||||
{
|
||||
public const string SensorIn = "LG.SubstrateLoad.SensorIn";
|
||||
public const string TrackMotorRun = "LG.SubstrateLoad.TrackMotorRun";
|
||||
public const string HomeChecked = "LG.SubstrateLoad.HomeChecked";
|
||||
public const string VacuumOk = "LG.SubstrateLoad.VacuumOk";
|
||||
}
|
||||
|
||||
public static class ChipLoad
|
||||
{
|
||||
public const string SensorIn = "LG.ChipLoad.SensorIn";
|
||||
public const string TrackMotorRun = "LG.ChipLoad.TrackMotorRun";
|
||||
public const string HomeChecked = "LG.ChipLoad.HomeChecked";
|
||||
public const string VacuumOk = "LG.ChipLoad.VacuumOk";
|
||||
}
|
||||
|
||||
public static class Cylinder
|
||||
{
|
||||
public const string ClampExtended = "LG.Cylinder.Clamp.Extended";
|
||||
public const string ClampRetracted = "LG.Cylinder.Clamp.Retracted";
|
||||
public const string StopperExtended = "LG.Cylinder.Stopper.Extended";
|
||||
public const string StopperRetracted = "LG.Cylinder.Stopper.Retracted";
|
||||
public const string PressExtended = "LG.Cylinder.Press.Extended";
|
||||
public const string PressRetracted = "LG.Cylinder.Press.Retracted";
|
||||
public const string PressSafeSensor = "LG.Cylinder.Press.SafeSensor";
|
||||
public const string MultiExtended = "LG.Cylinder.Multi.Extended";
|
||||
public const string MultiRetracted = "LG.Cylinder.Multi.Retracted";
|
||||
}
|
||||
}
|
||||
|
||||
public static class DeviceIoIds
|
||||
{
|
||||
public static class WZ
|
||||
{
|
||||
public const int StartButton = 0;
|
||||
public const int StopButton = 1;
|
||||
public const int ResetButton = 2;
|
||||
public const int EmergencyStopButton = 3;
|
||||
public const int BlueButton = 4;
|
||||
public const int SafetyDoor1Closed = 4;
|
||||
public const int SafetyDoor2Closed = 5;
|
||||
public const int SafetyDoor3Closed = 6;
|
||||
public const int SafetyDoor4Closed = 7;
|
||||
public const int SecurityDoorShield = 8;
|
||||
public const int StartButtonLight = 0;
|
||||
public const int StopButtonLight = 1;
|
||||
public const int ResetButtonLight = 2;
|
||||
public const int BlueButtonLight = 3;
|
||||
public const int WorkLight1 = 4;
|
||||
public const int StackLightRed = 8;
|
||||
public const int StackLightYellow = 9;
|
||||
public const int StackLightGreen = 10;
|
||||
public const int StackLightBlue = 11;
|
||||
public const int BuzzerAct = 12;
|
||||
public const int SafetyDoor1Open = 1;
|
||||
public const int SafetyDoor2Open = 2;
|
||||
public const int SafetyDoor3Open = 3;
|
||||
public const int SafetyDoor4Open = 4;
|
||||
}
|
||||
|
||||
public static class Stage
|
||||
{
|
||||
public const int ToolSetterSignal = 5;
|
||||
public const int SubstrateInPlace = 6;
|
||||
public const int LifterCyl1WorkPos = 7;
|
||||
public const int LifterCyl1HomePos = 8;
|
||||
public const int LifterCyl2WorkPos = 9;
|
||||
public const int LifterCyl2HomePos = 10;
|
||||
public const int LifterCyl1Act = 10;
|
||||
public const int LifterCyl2Act = 11;
|
||||
public const int BreakingTheVacuum1 = 0;
|
||||
public const int BreakingTheVacuum2 = 1;
|
||||
public const int BreakingTheVacuum3 = 2;
|
||||
public const int BreakingTheVacuum4 = 3;
|
||||
public const int Vacuum1 = 7;
|
||||
public const int Vacuum2 = 8;
|
||||
public const int Vacuum3 = 9;
|
||||
public const int Vacuum4 = 10;
|
||||
}
|
||||
|
||||
public static class WS
|
||||
{
|
||||
public const int ThetaClampCyl1WorkPos = 11;
|
||||
public const int ThetaClampCyl1HomePos = 12;
|
||||
public const int ThetaClampCyl2WorkPos = 13;
|
||||
public const int ThetaClampCyl2HomePos = 14;
|
||||
public const int ThetaClampCyl3WorkPos = 15;
|
||||
public const int ThetaClampCyl3HomePos = 0;
|
||||
public const int ThetaClampCyl4WorkPos = 1;
|
||||
public const int ThetaClampCyl4HomePos = 2;
|
||||
public const int ThetaTrayPresentSensor1 = 3;
|
||||
public const int ThetaTrayPresentSensor2 = 4;
|
||||
public const int ThetaTrayPresentSensor3 = 5;
|
||||
public const int ThetaTrayPresentSensor4 = 6;
|
||||
public const int BondHead1InPlace = 7;
|
||||
public const int BondHead2InPlace = 8;
|
||||
public const int BondHead3InPlace = 9;
|
||||
public const int BondHead4InPlace = 10;
|
||||
public const int ThetaClampCylAct = 8;
|
||||
public const int ThetaBrakePressCylAct = 9;
|
||||
}
|
||||
|
||||
public static class WEU
|
||||
{
|
||||
public const int Z11MaterialSensor1 = 11;
|
||||
public const int Z11MaterialSensor2 = 12;
|
||||
public const int Z12MaterialSensor3 = 13;
|
||||
public const int Z12MaterialSensor4 = 14;
|
||||
public const int IonBarAct1 = 6;
|
||||
public const int IonBarAct2 = 7;
|
||||
public const int TrayBreakVacuum1 = 4;
|
||||
public const int TrayBreakVacuum2 = 5;
|
||||
public const int TrayVacuum1 = 11;
|
||||
public const int TrayVacuum2 = 12;
|
||||
}
|
||||
|
||||
public static class CV
|
||||
{
|
||||
public const int UpperInletSensor = 0;
|
||||
public const int UpperInPlaceSensor = 1;
|
||||
public const int LowerOutletSensor = 2;
|
||||
public const int LowerInPlaceSensor = 3;
|
||||
public const int IonWindRodAlarmDetection = 13;
|
||||
public const int IonBarAct = 5;
|
||||
}
|
||||
|
||||
public static class General
|
||||
{
|
||||
public const int PhaseLossSensor = 10;
|
||||
public const int SafetyCircuitStatus1 = 11;
|
||||
public const int SafetyCircuitStatus2 = 12;
|
||||
}
|
||||
|
||||
public static class EFEM
|
||||
{
|
||||
public const int IonWindRodAlarmDetection1 = 14;
|
||||
public const int IonWindRodAlarmDetection2 = 15;
|
||||
public const int WorkLight1 = 6;
|
||||
}
|
||||
|
||||
public static class AxisZ
|
||||
{
|
||||
public const int Z1GuideRailBrake = 0;
|
||||
public const int Z3CameraAirCooling = 1;
|
||||
public const int Z5CameraAirCooling = 2;
|
||||
public const int Z10CameraAirCooling = 3;
|
||||
public const int Z2BondHeadAirCooling = 4;
|
||||
public const int Z2BondHeadBreakVacuum = 6;
|
||||
public const int Z2BondHeadVacuum = 13;
|
||||
}
|
||||
|
||||
public static class SubstrateLoad
|
||||
{
|
||||
public const int SensorIn = 1001;
|
||||
public const int TrackMotorRun = 1002;
|
||||
public const int HomeChecked = 1003;
|
||||
public const int VacuumOk = 1004;
|
||||
}
|
||||
|
||||
public static class ChipLoad
|
||||
{
|
||||
public const int SensorIn = 1101;
|
||||
public const int TrackMotorRun = 1102;
|
||||
public const int HomeChecked = 1103;
|
||||
public const int VacuumOk = 1104;
|
||||
}
|
||||
|
||||
public static class Cylinder
|
||||
{
|
||||
public const int ClampExtendCmd = 1701;
|
||||
public const int ClampExtended = 1702;
|
||||
public const int ClampRetracted = 1703;
|
||||
public const int StopperExtendCmd = 1711;
|
||||
public const int StopperRetractCmd = 1712;
|
||||
public const int StopperExtended = 1713;
|
||||
public const int StopperRetracted = 1714;
|
||||
public const int PressExtendCmd = 1721;
|
||||
public const int PressExtended = 1722;
|
||||
public const int PressRetracted = 1723;
|
||||
public const int PressSafeSensor = 1724;
|
||||
public const int MultiOutput1 = 1731;
|
||||
public const int MultiOutput2 = 1732;
|
||||
public const int MultiOutput3 = 1733;
|
||||
public const int MultiOutput4 = 1734;
|
||||
public const int MultiExtended = 1735;
|
||||
public const int MultiRetracted = 1736;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DeviceCylinderNames
|
||||
{
|
||||
public static class WS
|
||||
{
|
||||
public const string ThetaClamp = "WS θ夹紧气缸";
|
||||
public const string ThetaBrakePress = "WS θ刹车压紧气缸";
|
||||
}
|
||||
|
||||
public static class Stage
|
||||
{
|
||||
public const string LifterSync = "Stage 顶升同步气缸";
|
||||
}
|
||||
|
||||
public const string Clamp = "夹爪气缸";
|
||||
public const string Stopper = "挡停气缸";
|
||||
public const string Press = "压料气缸";
|
||||
public const string Multi = "多联气缸";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using MainShell.EventArgsFolder;
|
||||
using MainShell.Log;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using MwFramework.Device;
|
||||
|
||||
namespace MainShell.Hardware
|
||||
{
|
||||
public class DiastimeterPollingService : IDisposable
|
||||
{
|
||||
private const int DefaultIntervalMilliseconds = 200;
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly object _syncRoot = new object();
|
||||
private Timer _timer;
|
||||
private bool _isRunning;
|
||||
private bool _isReading;
|
||||
private int _intervalMilliseconds;
|
||||
|
||||
public DiastimeterPollingService(HardwareManager hardwareManager, IEventAggregator eventAggregator)
|
||||
{
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_intervalMilliseconds = DefaultIntervalMilliseconds;
|
||||
}
|
||||
|
||||
public bool IsRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _isRunning;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int IntervalMilliseconds
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _intervalMilliseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Start(DefaultIntervalMilliseconds);
|
||||
}
|
||||
|
||||
public void Start(int intervalMilliseconds)
|
||||
{
|
||||
if (intervalMilliseconds <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(intervalMilliseconds));
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_intervalMilliseconds = intervalMilliseconds;
|
||||
if (_timer == null)
|
||||
{
|
||||
_timer = new Timer(ReadSample, null, Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
|
||||
_timer.Change(0, _intervalMilliseconds);
|
||||
_isRunning = true;
|
||||
}
|
||||
|
||||
$"激光测距仪轮询启动,周期={intervalMilliseconds}ms".LogInfo();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_timer != null)
|
||||
{
|
||||
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
"激光测距仪轮询停止。".LogInfo();
|
||||
}
|
||||
|
||||
private void ReadSample(object state)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (!_isRunning || _isReading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isReading = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IDiastimeter diastimeter = _hardwareManager.Diastimeter;
|
||||
if (diastimeter == null)
|
||||
{
|
||||
PublishInvalidSample("激光测距仪未初始化。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!diastimeter.IsConnected)
|
||||
{
|
||||
PublishInvalidSample("激光测距仪未连接。");
|
||||
return;
|
||||
}
|
||||
|
||||
double distance;
|
||||
diastimeter.GetLastSample(out distance);
|
||||
if (double.IsNaN(distance) || double.IsInfinity(distance))
|
||||
{
|
||||
PublishInvalidSample("激光测距仪反馈数据无效。");
|
||||
return;
|
||||
}
|
||||
|
||||
_eventAggregator.PublishOnUIThread(new DiastimeterSampleChangedEventArgs(distance, DateTime.Now, true, string.Empty));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
$"激光测距仪轮询读取失败:{ex.Message}".LogSysError();
|
||||
PublishInvalidSample(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_isReading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishInvalidSample(string message)
|
||||
{
|
||||
_eventAggregator.PublishOnUIThread(new DiastimeterSampleChangedEventArgs(0d, DateTime.Now, false, message));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_timer != null)
|
||||
{
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace MainShell.Hardware
|
||||
{
|
||||
public static class HardwareAlarmIds
|
||||
{
|
||||
public const int DeviceNotReady = 15000;
|
||||
|
||||
public const int InterlockBlocked = 15001;
|
||||
|
||||
public const int IoStateInvalid = 15002;
|
||||
|
||||
public const int GenericRuntimeFailure = 15003;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Hardware
|
||||
{
|
||||
public interface ISubstrateLoadDeviceService
|
||||
{
|
||||
bool IsSubstrateAtLoadPosition();
|
||||
bool IsSubstrateLoadHomeChecked();
|
||||
bool IsSubstrateLoadVacuumOk();
|
||||
bool TrySetSubstrateLoadTrackMotor(bool isRunning);
|
||||
Task<bool> WaitSubstrateAtLoadPositionAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> WaitSubstrateLoadHomeCheckedAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> WaitSubstrateLoadVacuumOkAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
bool TryExtendClamp();
|
||||
bool TryRetractClamp();
|
||||
bool TryExtendStopper();
|
||||
bool TryRetractStopper();
|
||||
bool TryPressDown();
|
||||
bool TryReleasePress();
|
||||
Task<bool> TryExtendClampAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> TryRetractClampAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> TryExtendStopperAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> TryRetractStopperAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> TryPressDownAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task<bool> TryReleasePressAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
|
||||
public class SubstrateLoadDeviceService : ISubstrateLoadDeviceService
|
||||
{
|
||||
private readonly IDeviceIoMonitorService _ioMonitorService;
|
||||
private readonly IDeviceCylinderService _cylinderService;
|
||||
|
||||
public SubstrateLoadDeviceService(IDeviceIoMonitorService ioMonitorService, IDeviceCylinderService cylinderService)
|
||||
{
|
||||
_ioMonitorService = ioMonitorService;
|
||||
_cylinderService = cylinderService;
|
||||
}
|
||||
|
||||
public bool IsSubstrateAtLoadPosition()
|
||||
{
|
||||
return _ioMonitorService.IsPointOn(DeviceIoNames.SubstrateLoad.SensorIn);
|
||||
}
|
||||
|
||||
public bool IsSubstrateLoadHomeChecked()
|
||||
{
|
||||
return _ioMonitorService.IsPointOn(DeviceIoNames.SubstrateLoad.HomeChecked);
|
||||
}
|
||||
|
||||
public bool IsSubstrateLoadVacuumOk()
|
||||
{
|
||||
return _ioMonitorService.IsPointOn(DeviceIoNames.SubstrateLoad.VacuumOk);
|
||||
}
|
||||
|
||||
public bool TrySetSubstrateLoadTrackMotor(bool isRunning)
|
||||
{
|
||||
return _ioMonitorService.TrySetOutputState(DeviceIoIds.SubstrateLoad.TrackMotorRun, isRunning);
|
||||
}
|
||||
|
||||
public Task<bool> WaitSubstrateAtLoadPositionAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return _ioMonitorService.WaitForPointStateAsync(DeviceIoNames.SubstrateLoad.SensorIn, true, timeout, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<bool> WaitSubstrateLoadHomeCheckedAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return _ioMonitorService.WaitForPointStateAsync(DeviceIoNames.SubstrateLoad.HomeChecked, true, timeout, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<bool> WaitSubstrateLoadVacuumOkAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return _ioMonitorService.WaitForPointStateAsync(DeviceIoNames.SubstrateLoad.VacuumOk, true, timeout, cancellationToken);
|
||||
}
|
||||
|
||||
public bool TryExtendClamp()
|
||||
{
|
||||
return _cylinderService.TryExtend(DeviceCylinderNames.Clamp);
|
||||
}
|
||||
|
||||
public bool TryRetractClamp()
|
||||
{
|
||||
return _cylinderService.TryRetract(DeviceCylinderNames.Clamp);
|
||||
}
|
||||
|
||||
public bool TryExtendStopper()
|
||||
{
|
||||
return _cylinderService.TryExtend(DeviceCylinderNames.Stopper);
|
||||
}
|
||||
|
||||
public bool TryRetractStopper()
|
||||
{
|
||||
return _cylinderService.TryRetract(DeviceCylinderNames.Stopper);
|
||||
}
|
||||
|
||||
public bool TryPressDown()
|
||||
{
|
||||
return _cylinderService.TryExtend(DeviceCylinderNames.Press);
|
||||
}
|
||||
|
||||
public bool TryReleasePress()
|
||||
{
|
||||
return _cylinderService.TryRetract(DeviceCylinderNames.Press);
|
||||
}
|
||||
|
||||
public Task<bool> TryExtendClampAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return _cylinderService.TryExtendAndWaitAsync(DeviceCylinderNames.Clamp, timeout, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<bool> TryRetractClampAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return _cylinderService.TryRetractAndWaitAsync(DeviceCylinderNames.Clamp, timeout, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<bool> TryExtendStopperAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return _cylinderService.TryExtendAndWaitAsync(DeviceCylinderNames.Stopper, timeout, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<bool> TryRetractStopperAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return _cylinderService.TryRetractAndWaitAsync(DeviceCylinderNames.Stopper, timeout, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<bool> TryPressDownAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return _cylinderService.TryExtendAndWaitAsync(DeviceCylinderNames.Press, timeout, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<bool> TryReleasePressAndWaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return _cylinderService.TryRetractAndWaitAsync(DeviceCylinderNames.Press, timeout, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user