722 lines
31 KiB
C#
722 lines
31 KiB
C#
|
|
using DTS.Common.Interface.Channels;
|
|||
|
|
using DTS.Common.Interface.DASFactory;
|
|||
|
|
using DTS.Common.Interface.DASFactory.Config;
|
|||
|
|
using DTS.Common.Interface.Sensors;
|
|||
|
|
using DTS.Common.Interface.StatusAndProgressBar;
|
|||
|
|
using DTS.Common.Utilities.Logging;
|
|||
|
|
using DTS.DASLib.Service.StateMachine.StatusAndParameters.Configure;
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Threading;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
|
|||
|
|
namespace DTS.DASLib.Service.StateMachine
|
|||
|
|
{
|
|||
|
|
public class ConfigureStatusInformation : IStatusInfo
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// all possible status that can be relayed to consumers
|
|||
|
|
/// </summary>
|
|||
|
|
public enum StatusValues
|
|||
|
|
{
|
|||
|
|
ApplyingConfiguration,
|
|||
|
|
AutoResolvingChannels,
|
|||
|
|
ManuallyResolvedChannels,
|
|||
|
|
Completed,
|
|||
|
|
Cancelling,
|
|||
|
|
Cancelled,
|
|||
|
|
ChannelOutOfPosition,
|
|||
|
|
NoChannelsAssigned,
|
|||
|
|
AllChannelsResolved,
|
|||
|
|
EIDNotFound,
|
|||
|
|
ApplyConfigFailed,
|
|||
|
|
AppliedConfiguration,
|
|||
|
|
PrepareForDiagnostics,
|
|||
|
|
PrepareForDiagnosticsFailed,
|
|||
|
|
PrepareForDiagnosticsSuccess,
|
|||
|
|
LowPower,
|
|||
|
|
LowPowerSuccess,
|
|||
|
|
LowPowerFailure
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// no channels are currently assigned
|
|||
|
|
/// </summary>
|
|||
|
|
public bool NoChannelsAssigned { get; internal set; } = true;
|
|||
|
|
/// <summary>
|
|||
|
|
/// all channels to be resolved are resolved
|
|||
|
|
/// </summary>
|
|||
|
|
public bool AllChannelsResolved { get; internal set; } = false;
|
|||
|
|
/// <summary>
|
|||
|
|
/// one or more channels are out of position with respect to how
|
|||
|
|
/// they requested to be positioned (they are on a different channel than they were specified to be on)
|
|||
|
|
/// </summary>
|
|||
|
|
public bool ChannelsOutOfPosition { get; internal set; } = false;
|
|||
|
|
/// <summary>
|
|||
|
|
/// all units that were to be configured were configured
|
|||
|
|
/// </summary>
|
|||
|
|
public bool HaveAppliedConfigAllUnits { get; set; } = false;
|
|||
|
|
/// <summary>
|
|||
|
|
/// signals a cancel request
|
|||
|
|
/// </summary>
|
|||
|
|
public ManualResetEvent CancelEvent = new ManualResetEvent(false);
|
|||
|
|
/// <summary>
|
|||
|
|
/// signals when work is finished
|
|||
|
|
/// </summary>
|
|||
|
|
public ManualResetEvent DoneEvent = new ManualResetEvent(false);
|
|||
|
|
/// <summary>
|
|||
|
|
/// action to take on completion of work
|
|||
|
|
/// </summary>
|
|||
|
|
public ActionCompleteDelegate CompleteAction { get; set; }
|
|||
|
|
/// <summary>
|
|||
|
|
/// action to take on progress notification
|
|||
|
|
/// </summary>
|
|||
|
|
public SetProgressValueDelegate ProgressAction { get; set; }
|
|||
|
|
/// <summary>
|
|||
|
|
/// action to take on a status notification
|
|||
|
|
/// </summary>
|
|||
|
|
public StatusIntDelegate StatusAction { get; set; }
|
|||
|
|
/// <summary>
|
|||
|
|
/// action to take on extended status notification
|
|||
|
|
/// </summary>
|
|||
|
|
public StatusExIntDelegate StatusExAction { get; set; }
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// holds references to all units which were successfully configured
|
|||
|
|
/// </summary>
|
|||
|
|
public IDASCommunication[] UnitsConfigured { get; set; } = new IDASCommunication[0];
|
|||
|
|
/// <summary>
|
|||
|
|
/// task for turning excitation on/off, used for monitoring task or interrupting
|
|||
|
|
/// </summary>
|
|||
|
|
private Task _ExcitationTask = null;
|
|||
|
|
/// <summary>
|
|||
|
|
/// task for applying configuration, used for monitoring task or interrupting
|
|||
|
|
/// </summary>
|
|||
|
|
private Task _ApplyConfigTask = null;
|
|||
|
|
/// <summary>
|
|||
|
|
/// used to canel running task
|
|||
|
|
/// returns when task is cancelled or completed
|
|||
|
|
/// </summary>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
public async Task Cancel()
|
|||
|
|
{
|
|||
|
|
CancelEvent.Set();
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.Cancelling);
|
|||
|
|
//simulate time spent waiting for cancel to be acknowledged
|
|||
|
|
Thread.Sleep(100);
|
|||
|
|
DoneEvent.WaitOne();
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.Cancelled);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// turns on power (if requested and appropriate)
|
|||
|
|
/// </summary>
|
|||
|
|
private void PrepareForDiagnostics()
|
|||
|
|
{
|
|||
|
|
var param = States.Instance.Configure.Status.ConfigureParameters;
|
|||
|
|
var status = States.Instance.Configure.Status.ConfigureStatus;
|
|||
|
|
var global = States.Instance.Configure.Status.GlobalStatusInformation;
|
|||
|
|
var dasFactory = States.Instance.Configure.DASFactory;
|
|||
|
|
|
|||
|
|
//make sure we have some units to turn on, if not fail task
|
|||
|
|
if (!status.UnitsConfigured.Any())
|
|||
|
|
{
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.PrepareForDiagnosticsFailed);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ProgressAction?.Invoke(0D);
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.PrepareForDiagnostics);
|
|||
|
|
|
|||
|
|
//record if any units fail to turn on excitation
|
|||
|
|
var bPassed = true;
|
|||
|
|
var mre = new ManualResetEvent(false);
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
using (var diagnosticsService = new DiagnosticsService())
|
|||
|
|
{
|
|||
|
|
var units = status.UnitsConfigured.ToList();
|
|||
|
|
diagnosticsService.PrepareForDiagnostics(units,
|
|||
|
|
PrePostResults.PreEventDiagnosticsResult,
|
|||
|
|
param.SampleRateLookup,
|
|||
|
|
param.AAFRateLookup,
|
|||
|
|
(ServiceBase.CallbackData data) =>
|
|||
|
|
{
|
|||
|
|
switch (data.Status)
|
|||
|
|
{
|
|||
|
|
case ServiceBase.CallbackData.CallbackStatus.Success:
|
|||
|
|
StatusExAction?.Invoke((int)StatusValues.PrepareForDiagnosticsSuccess, data.Target);
|
|||
|
|
global.AddUnitAtHighPower(data.Target);
|
|||
|
|
break;
|
|||
|
|
case ServiceBase.CallbackData.CallbackStatus.Failure:
|
|||
|
|
bPassed = false;
|
|||
|
|
StatusExAction?.Invoke((int)StatusValues.PrepareForDiagnosticsFailed, data.Target);
|
|||
|
|
break;
|
|||
|
|
case ServiceBase.CallbackData.CallbackStatus.AllFinished:
|
|||
|
|
mre.Set();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
units);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
bPassed = false;
|
|||
|
|
throw ex;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var timeWaited = 0;
|
|||
|
|
while (!mre.WaitOne(PREPARE_SPIN_TIME, false))
|
|||
|
|
{
|
|||
|
|
timeWaited += PREPARE_SPIN_TIME;
|
|||
|
|
ProgressAction?.Invoke(100D * timeWaited / EXPECTED_PREPARE_TIME);
|
|||
|
|
}
|
|||
|
|
if (bPassed)
|
|||
|
|
{
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.PrepareForDiagnosticsSuccess);
|
|||
|
|
States.Instance.ConfigureStart.Status.GlobalStatusInformation.ExcitationOn = true;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.PrepareForDiagnosticsFailed);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private const int PREPARE_SPIN_TIME = 200;
|
|||
|
|
private const int EXPECTED_PREPARE_TIME = 8000;
|
|||
|
|
/// <summary>
|
|||
|
|
/// lock used to control thread access to shared resources
|
|||
|
|
/// [like UnitsConfigured]
|
|||
|
|
/// </summary>
|
|||
|
|
private static readonly object MyLock = new object();
|
|||
|
|
private void AddConfiguredDevice(IDASCommunication device)
|
|||
|
|
{
|
|||
|
|
lock (MyLock)
|
|||
|
|
{
|
|||
|
|
if (UnitsConfigured.Contains(device)) { return; }
|
|||
|
|
var list = new List<IDASCommunication>();
|
|||
|
|
list.AddRange(UnitsConfigured);
|
|||
|
|
list.Add(device);
|
|||
|
|
UnitsConfigured = list.ToArray();
|
|||
|
|
}
|
|||
|
|
StatusExAction?.Invoke((int)StatusValues.AppliedConfiguration, device);
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// Turns off excitation, returns immediately
|
|||
|
|
/// </summary>
|
|||
|
|
public void TurnOffExcitation()
|
|||
|
|
{
|
|||
|
|
var param = States.Instance.ConfigureStart.Status.ConfigureParameters;
|
|||
|
|
//reset the flag that got us here
|
|||
|
|
param.TurnOffExcitation = false;
|
|||
|
|
var status = States.Instance.ConfigureStart.Status.ConfigureStatus;
|
|||
|
|
var dasFactory = States.Instance.ConfigureStart.DASFactory;
|
|||
|
|
|
|||
|
|
_ExcitationTask = Task.Run(() =>
|
|||
|
|
{
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.LowPower);
|
|||
|
|
var units = dasFactory.GetDASList();
|
|||
|
|
var anyFailed = false;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var mre = new ManualResetEvent(false);
|
|||
|
|
using (var service = new ArmingService())
|
|||
|
|
{
|
|||
|
|
service.EnterLowPowerMode(units, (ServiceBase.CallbackData data) =>
|
|||
|
|
{
|
|||
|
|
switch (data.Status)
|
|||
|
|
{
|
|||
|
|
case ServiceBase.CallbackData.CallbackStatus.Success:
|
|||
|
|
StatusExAction?.Invoke((int)StatusValues.LowPowerSuccess, data.Target);
|
|||
|
|
break;
|
|||
|
|
case ServiceBase.CallbackData.CallbackStatus.AllFinished:
|
|||
|
|
mre.Set();
|
|||
|
|
break;
|
|||
|
|
case ServiceBase.CallbackData.CallbackStatus.Failure:
|
|||
|
|
anyFailed = true;
|
|||
|
|
StatusExAction?.Invoke((int)StatusValues.LowPowerFailure, data.Target);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}, units);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
mre.WaitOne();
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
APILogger.Log(ex);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//if any failed to turn off record the error
|
|||
|
|
//if all turned off, then set the global status param that excitation is now known off
|
|||
|
|
if (anyFailed)
|
|||
|
|
{
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.LowPowerFailure);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
States.Instance.ConfigureStart.Status.GlobalStatusInformation.ExcitationOn = false;
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.LowPowerSuccess);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.Completed);
|
|||
|
|
CompleteAction?.Invoke();
|
|||
|
|
DoneEvent.Set();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// starts ApplyConfig process, returns immediately
|
|||
|
|
/// </summary>
|
|||
|
|
public void ApplyConfig()
|
|||
|
|
{
|
|||
|
|
DoneEvent.Reset();
|
|||
|
|
var param = States.Instance.ConfigureStart.Status.ConfigureParameters;
|
|||
|
|
if (param.TurnOffExcitation)
|
|||
|
|
{
|
|||
|
|
TurnOffExcitation();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
var unitsToConfigure = param.UnitsToConfigure.ToList();
|
|||
|
|
var dasFactory = States.Instance.ConfigureStart.DASFactory;
|
|||
|
|
|
|||
|
|
_ApplyConfigTask = Task.Run(() =>
|
|||
|
|
{
|
|||
|
|
//the user could just be turning on power, in which case the configuration is already set
|
|||
|
|
//if the flag for set configuration is set, then set the configuration, otherwise don't
|
|||
|
|
if (param.SetConfiguration)
|
|||
|
|
{
|
|||
|
|
HaveAppliedConfigAllUnits = false;
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.ApplyingConfiguration);
|
|||
|
|
|
|||
|
|
//check if we are supposed to configure a unit, but it's not available
|
|||
|
|
//mark it failed if the unit is not available
|
|||
|
|
var units = dasFactory.GetDASList();
|
|||
|
|
foreach (var unit in unitsToConfigure)
|
|||
|
|
{
|
|||
|
|
if (!units.Contains(unit))
|
|||
|
|
{
|
|||
|
|
unitsToConfigure.Remove(unit);
|
|||
|
|
StatusExAction?.Invoke((int)StatusValues.ApplyConfigFailed, unit);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var mre = new ManualResetEvent(false);
|
|||
|
|
if (unitsToConfigure.Any())
|
|||
|
|
{
|
|||
|
|
using (var configService = new ConfigurationService())
|
|||
|
|
{
|
|||
|
|
configService.SetConfiguration(unitsToConfigure, param.DoStrictCheck, param.EventConfig,
|
|||
|
|
(data) =>
|
|||
|
|
{
|
|||
|
|
switch (data.Status)
|
|||
|
|
{
|
|||
|
|
case ServiceBase.CallbackData.CallbackStatus.AllFinished:
|
|||
|
|
mre.Set();
|
|||
|
|
break;
|
|||
|
|
case ServiceBase.CallbackData.CallbackStatus.Progress:
|
|||
|
|
ProgressAction?.Invoke(data.ProgressValue);
|
|||
|
|
break;
|
|||
|
|
case ServiceBase.CallbackData.CallbackStatus.Failure:
|
|||
|
|
StatusExAction?.Invoke((int)StatusValues.ApplyConfigFailed, data.Target,
|
|||
|
|
data.ErrorMessage, data.ErrorException);
|
|||
|
|
break;
|
|||
|
|
case ServiceBase.CallbackData.CallbackStatus.Success:
|
|||
|
|
AddConfiguredDevice(data.Target);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}, unitsToConfigure,
|
|||
|
|
param.ErrorRequiringActionAction,
|
|||
|
|
param.DummyConfig,
|
|||
|
|
param.MaxAAF,
|
|||
|
|
param.ConfigureDigitalOutputs,
|
|||
|
|
param.TurnOffAAFRealtime,
|
|||
|
|
param.DSPFilterType,
|
|||
|
|
param.DiscardDiagnostics);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
mre.WaitOne();
|
|||
|
|
//check that all the units that should have been configured were
|
|||
|
|
var allConfigured = Array.TrueForAll(param.UnitsToConfigure, d => UnitsConfigured.Contains(d));
|
|||
|
|
HaveAppliedConfigAllUnits = allConfigured;
|
|||
|
|
}
|
|||
|
|
//finally turn on power UNLESS the user has specified not to (most likely by pressing "Low Power" button)
|
|||
|
|
//but also potentially by a property setting
|
|||
|
|
if (!CancelEvent.WaitOne(1, false))
|
|||
|
|
{
|
|||
|
|
if (param.PrepareForDiagnostics && !param.SkipTurnOnPower)
|
|||
|
|
{
|
|||
|
|
PrepareForDiagnostics();
|
|||
|
|
}
|
|||
|
|
CompleteAction?.Invoke();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
StatusAction?.Invoke((int)StatusValues.Completed);
|
|||
|
|
DoneEvent.Set();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// holds channels that are not resolved for one reason or another
|
|||
|
|
/// </summary>
|
|||
|
|
private readonly List<GroupChannelWithMeta> _unresolvedChannels = new List<GroupChannelWithMeta>();
|
|||
|
|
/// <summary>
|
|||
|
|
/// holds channels that are now resolved/assigned
|
|||
|
|
/// </summary>
|
|||
|
|
private readonly List<GroupChannelWithMeta> _resolvedChannels = new List<GroupChannelWithMeta>();
|
|||
|
|
|
|||
|
|
private bool IsUnresolved(IGroupChannel ch)
|
|||
|
|
{
|
|||
|
|
return _unresolvedChannels.Exists(channel => channel.Channel == ch);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool IsResolved(IGroupChannel ch)
|
|||
|
|
{
|
|||
|
|
return _resolvedChannels.Exists(channel => channel.Channel == ch);
|
|||
|
|
}
|
|||
|
|
private void AddResolvedChannel(IGroupChannel channel,
|
|||
|
|
IDASChannel hwChannel,
|
|||
|
|
bool eidOutOfPlace,
|
|||
|
|
ConfigureStatusParameters param,
|
|||
|
|
IDictionary<IDASChannel, IDASCommunication> lookupChannelToDAS,
|
|||
|
|
ISensorData sd,
|
|||
|
|
bool missingEID)
|
|||
|
|
{
|
|||
|
|
//remove any possible duplicates
|
|||
|
|
_resolvedChannels.RemoveAll(ch => ch.Channel == channel);
|
|||
|
|
_unresolvedChannels.RemoveAll(ch => ch.Channel == channel);
|
|||
|
|
|
|||
|
|
var meta = new GroupChannelWithMeta()
|
|||
|
|
{
|
|||
|
|
Channel = channel,
|
|||
|
|
ChannelConflict = false,
|
|||
|
|
ConflictingChannel = null,
|
|||
|
|
DASChannel = hwChannel,
|
|||
|
|
EIDOutOfPlace = eidOutOfPlace,
|
|||
|
|
HWChannelIncompatible = false,
|
|||
|
|
MissingID = missingEID,
|
|||
|
|
MissingSensor = false
|
|||
|
|
};
|
|||
|
|
_resolvedChannels.Add(meta);
|
|||
|
|
|
|||
|
|
//set the hardware in the group if needed
|
|||
|
|
var includedHardware = channel.Group.IncludedHardware.ToList();
|
|||
|
|
var hwId = param.GetDatabaseIdAction(lookupChannelToDAS[hwChannel]);
|
|||
|
|
if (!includedHardware.Contains(hwId))
|
|||
|
|
{
|
|||
|
|
includedHardware.Add(hwId);
|
|||
|
|
}
|
|||
|
|
channel.Group.SetIncludedHardware(includedHardware.ToArray());
|
|||
|
|
|
|||
|
|
//set the calibration
|
|||
|
|
if (hwChannel is AnalogInputDASChannel aic)
|
|||
|
|
{
|
|||
|
|
foreach (var e in sd.SupportedExcitation)
|
|||
|
|
{
|
|||
|
|
if (!aic.IsSupported(e))
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var sc = param.GetCalibrationAction(sd, e);
|
|||
|
|
if (null == sc) { continue; }
|
|||
|
|
param.SetSensorCalibrationAction(sd, sc);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
private void AddUnresolvedChannel(IGroupChannel ch,
|
|||
|
|
bool bChannelConflict,
|
|||
|
|
IGroupChannel conflictingChannel,
|
|||
|
|
bool EIDOutOfPlace,
|
|||
|
|
bool hwChannelIncompatible,
|
|||
|
|
bool hwNotFound,
|
|||
|
|
bool missingId,
|
|||
|
|
bool missingSensor)
|
|||
|
|
{
|
|||
|
|
//remove any possible duplicates
|
|||
|
|
_resolvedChannels.RemoveAll(channel => channel.Channel == ch);
|
|||
|
|
_unresolvedChannels.RemoveAll(channel => channel.Channel == ch);
|
|||
|
|
|
|||
|
|
var meta = new GroupChannelWithMeta()
|
|||
|
|
{
|
|||
|
|
Channel = ch,
|
|||
|
|
ChannelConflict = bChannelConflict,
|
|||
|
|
ConflictingChannel = conflictingChannel,
|
|||
|
|
DASChannel = null,
|
|||
|
|
EIDOutOfPlace = EIDOutOfPlace,
|
|||
|
|
HWChannelIncompatible = hwChannelIncompatible,
|
|||
|
|
HWNotFound = hwNotFound,
|
|||
|
|
MissingID = missingId,
|
|||
|
|
MissingSensor = missingSensor
|
|||
|
|
};
|
|||
|
|
_unresolvedChannels.Add(meta);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool IsHWChannelResolved(IDASChannel channel)
|
|||
|
|
{
|
|||
|
|
return _resolvedChannels.Exists(ch => ch.DASChannel == channel);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void ManuallyUnresolveChannel(IGroupChannel channel)
|
|||
|
|
{
|
|||
|
|
if (!IsResolved(channel))
|
|||
|
|
{
|
|||
|
|
throw new InvalidAssignmentException(channel, InvalidAssignmentException.Reasons.ChannelNotAssigned);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var resolvedChannel = _resolvedChannels.First(ch => ch.Channel == channel);
|
|||
|
|
if (resolvedChannel.AssignedByEID)
|
|||
|
|
{
|
|||
|
|
throw new InvalidAssignmentException(channel, InvalidAssignmentException.Reasons.EID_Locked);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
AddUnresolvedChannel(channel, false, null, false, false, resolvedChannel.HWNotFound, resolvedChannel.MissingID,
|
|||
|
|
false);
|
|||
|
|
}
|
|||
|
|
public void ManuallyResolveChannel(IGroupChannel channel, IDASChannel hardwareChannel)
|
|||
|
|
{
|
|||
|
|
var param = States.Instance.ConfigureStart.Status.ConfigureParameters;
|
|||
|
|
GetIdToHardwareLookup(param, out var lookupIDToHardware, out var lookupChannelToDAS,
|
|||
|
|
out var lookupDAStoChannel);
|
|||
|
|
if (channel.IsBlank())
|
|||
|
|
{
|
|||
|
|
throw new InvalidAssignmentException(channel, InvalidAssignmentException.Reasons.BlankChannel);
|
|||
|
|
}
|
|||
|
|
if (channel.IsDisabled)
|
|||
|
|
{
|
|||
|
|
throw new InvalidAssignmentException(channel, InvalidAssignmentException.Reasons.ChannelDisabled);
|
|||
|
|
}
|
|||
|
|
if (channel.SensorId < 0)
|
|||
|
|
{
|
|||
|
|
throw new InvalidAssignmentException(channel, InvalidAssignmentException.Reasons.NoSensor);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var sd = param.GetSensorAction(channel);
|
|||
|
|
if (null == sd)
|
|||
|
|
{
|
|||
|
|
throw new InvalidAssignmentException(channel, InvalidAssignmentException.Reasons.SensorNotFound);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var compatible = IsSensorHwCompatible(hardwareChannel, sd);
|
|||
|
|
if (SensorCompatiblilityResponse.Compatible != compatible)
|
|||
|
|
{
|
|||
|
|
throw new InvalidAssignmentException(channel, InvalidAssignmentException.Reasons.IncompatibleHardware);
|
|||
|
|
}
|
|||
|
|
//alright, looks ok ... unless there's something on that channel already ...
|
|||
|
|
if (IsHWChannelResolved(hardwareChannel))
|
|||
|
|
{
|
|||
|
|
throw new InvalidAssignmentException(channel, InvalidAssignmentException.Reasons.ChannelAlreadyAssigned);
|
|||
|
|
}
|
|||
|
|
//ok, well make sure it's we didn't find the EID for the channel already on a channel, if so ... it's locked on
|
|||
|
|
//that channel
|
|||
|
|
bool bFoundEID = false;
|
|||
|
|
if (IsResolved(channel))
|
|||
|
|
{
|
|||
|
|
var resolvedChannel = _resolvedChannels.First(ch => ch.Channel == channel);
|
|||
|
|
if (resolvedChannel.AssignedByEID)
|
|||
|
|
{
|
|||
|
|
if (resolvedChannel.DASChannel != hardwareChannel)
|
|||
|
|
{
|
|||
|
|
throw new InvalidAssignmentException(channel, InvalidAssignmentException.Reasons.EID_Locked);
|
|||
|
|
}
|
|||
|
|
//nothing to do ...
|
|||
|
|
bFoundEID = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//determine if we should have found an EID and didn't
|
|||
|
|
var bMissingEID = !string.IsNullOrWhiteSpace(sd.EID) && !bFoundEID;
|
|||
|
|
//if we are missing an ID, and aren't allowed to assign to a channel without an id, then don't allow it
|
|||
|
|
if (bMissingEID && !param.AllowSensorIdToBlankChannel)
|
|||
|
|
{
|
|||
|
|
throw new InvalidAssignmentException(channel, InvalidAssignmentException.Reasons.EIDRequiredAndMissing);
|
|||
|
|
}
|
|||
|
|
//add as a resolved channel
|
|||
|
|
AddResolvedChannel(channel, hardwareChannel, false, param, lookupChannelToDAS, sd, bMissingEID);
|
|||
|
|
AllChannelsResolved = _unresolvedChannels.Any();
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// thrown by manually resolve/unresolve functions
|
|||
|
|
/// </summary>
|
|||
|
|
public class InvalidAssignmentException : Exception
|
|||
|
|
{
|
|||
|
|
public enum Reasons
|
|||
|
|
{
|
|||
|
|
BlankChannel,
|
|||
|
|
NoSensor,
|
|||
|
|
ChannelDisabled,
|
|||
|
|
SensorNotFound,
|
|||
|
|
IncompatibleHardware,
|
|||
|
|
ChannelAlreadyAssigned,
|
|||
|
|
EID_Locked, //sensor is already locked by EID to a different channel
|
|||
|
|
EIDRequiredAndMissing,
|
|||
|
|
ChannelNotAssigned
|
|||
|
|
}
|
|||
|
|
public IGroupChannel GroupChannel { get; private set; }
|
|||
|
|
public Reasons Reason { get; private set; }
|
|||
|
|
|
|||
|
|
public InvalidAssignmentException(IGroupChannel channel, Reasons reason)
|
|||
|
|
: base($"{reason.ToString()} - {channel}")
|
|||
|
|
{
|
|||
|
|
Reason = reason;
|
|||
|
|
GroupChannel = channel;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// sensor is not compatible with hardware, but maybe in the future we'll want to know why
|
|||
|
|
/// even though right now it's just "hw incompatible"
|
|||
|
|
/// </summary>
|
|||
|
|
internal enum SensorCompatiblilityResponse
|
|||
|
|
{
|
|||
|
|
Compatible,
|
|||
|
|
DigitalInputNotSupportedOnHWChannel,
|
|||
|
|
DigitalInputModeNotSupportedOnHWChannel,
|
|||
|
|
BridgeModeNotSupportedOnHWChannel,
|
|||
|
|
SquibFireModeNotSupportedOnHWChannel,
|
|||
|
|
NoSupportedExcitationOnHWChannel
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// check if sensor can run on the channel in question
|
|||
|
|
/// checks excitation, bridge, sensor type, etc
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="hwid"></param>
|
|||
|
|
/// <param name="sd"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private SensorCompatiblilityResponse IsSensorHwCompatible(IDASChannel hwChannel, ISensorData sd)
|
|||
|
|
{
|
|||
|
|
switch (hwChannel)
|
|||
|
|
{
|
|||
|
|
case AnalogInputDASChannel aic:
|
|||
|
|
if (aic.SupportedBridges.Contains(sd.Bridge))
|
|||
|
|
{
|
|||
|
|
if (aic.DigitalInputChannel)
|
|||
|
|
{
|
|||
|
|
if (!sd.IsDigitalInput())
|
|||
|
|
{
|
|||
|
|
//hardware is digital, sensor is not, it's not compatible
|
|||
|
|
return SensorCompatiblilityResponse.DigitalInputNotSupportedOnHWChannel;
|
|||
|
|
}
|
|||
|
|
else if (!aic.SupportedDigitalInputModes.Contains(sd.InputMode))
|
|||
|
|
{
|
|||
|
|
//sensor and hardware are digital, but the input mode is not supported
|
|||
|
|
return SensorCompatiblilityResponse.DigitalInputModeNotSupportedOnHWChannel;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
var param = States.Instance.ConfigureStart.Status.ConfigureParameters;
|
|||
|
|
var excitationPassed = (from e in sd.SupportedExcitation
|
|||
|
|
where aic.SupportedExcitation.Contains(e)
|
|||
|
|
select param.GetCalibrationAction(sd, e)).Any(sc => null != sc);
|
|||
|
|
if (!excitationPassed)
|
|||
|
|
{
|
|||
|
|
//there's no supported excitation match
|
|||
|
|
return SensorCompatiblilityResponse.NoSupportedExcitationOnHWChannel;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
//the hardware does not support the bridge mode of the sensor ...
|
|||
|
|
return SensorCompatiblilityResponse.BridgeModeNotSupportedOnHWChannel;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
break;
|
|||
|
|
case OutputSquibChannel _:
|
|||
|
|
var squibChannel = (OutputSquibChannel)hwChannel;
|
|||
|
|
if (!sd.IsSquib())
|
|||
|
|
{
|
|||
|
|
//hardware channel is squib, sensor is not
|
|||
|
|
return SensorCompatiblilityResponse.BridgeModeNotSupportedOnHWChannel;
|
|||
|
|
}
|
|||
|
|
else if (!squibChannel.SupportedSquibFireModes.Contains(sd.SquibFireMode))
|
|||
|
|
{
|
|||
|
|
//hardware and sensor are squib, but fire mode not supported by hardware
|
|||
|
|
return SensorCompatiblilityResponse.SquibFireModeNotSupportedOnHWChannel;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
break;
|
|||
|
|
case OutputTOMDigitalChannel _:
|
|||
|
|
if (!sd.IsDigitalOutput())
|
|||
|
|
{
|
|||
|
|
//hardware channel is digital output, sensor is not
|
|||
|
|
return SensorCompatiblilityResponse.BridgeModeNotSupportedOnHWChannel;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
return SensorCompatiblilityResponse.Compatible;
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// returns sensor EID to hardware channel mapping as determined from connected units
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="param"></param>
|
|||
|
|
/// <param name="lookupChannelToDAS">hardware channel to IDASCommunication</param>
|
|||
|
|
/// <param name="lookupIDToHardware">eid to hardware channel</param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private void GetIdToHardwareLookup(ConfigureStatusParameters param,
|
|||
|
|
out IDictionary<string, IDASChannel> lookupIDToHardware,
|
|||
|
|
out IDictionary<IDASChannel, IDASCommunication> lookupChannelToDAS,
|
|||
|
|
out IDictionary<int, IDictionary<int, IDASChannel>> lookupDASToChannel
|
|||
|
|
)
|
|||
|
|
{
|
|||
|
|
lookupIDToHardware = new Dictionary<string, IDASChannel>();
|
|||
|
|
lookupChannelToDAS = new Dictionary<IDASChannel, IDASCommunication>();
|
|||
|
|
lookupDASToChannel = new Dictionary<int, IDictionary<int, IDASChannel>>();
|
|||
|
|
|
|||
|
|
foreach (var unit in param.UnitsToConfigure)
|
|||
|
|
{
|
|||
|
|
//why is there no config data?
|
|||
|
|
if (null == unit.ConfigData)
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var dasid = param.GetDatabaseIdAction(unit);
|
|||
|
|
if (!lookupDASToChannel.ContainsKey(dasid))
|
|||
|
|
{
|
|||
|
|
lookupDASToChannel.Add(dasid, new Dictionary<int, IDASChannel>());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var module in unit.ConfigData.Modules)
|
|||
|
|
{
|
|||
|
|
foreach (var channel in module.Channels)
|
|||
|
|
{
|
|||
|
|
lookupChannelToDAS[channel] = unit;
|
|||
|
|
lookupDASToChannel[dasid][channel.Number] = channel;
|
|||
|
|
if (null != channel.IDs && channel.IDs.Length > 0)
|
|||
|
|
{
|
|||
|
|
var id = channel.IDs[0].ID;
|
|||
|
|
if (!string.IsNullOrWhiteSpace(id))
|
|||
|
|
{
|
|||
|
|
lookupIDToHardware[id] = channel;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// resets all status to defaults
|
|||
|
|
/// </summary>
|
|||
|
|
public void Reset()
|
|||
|
|
{
|
|||
|
|
NoChannelsAssigned = true;
|
|||
|
|
AllChannelsResolved = false;
|
|||
|
|
ChannelsOutOfPosition = false;
|
|||
|
|
HaveAppliedConfigAllUnits = false;
|
|||
|
|
StatusAction = null;
|
|||
|
|
CompleteAction = null;
|
|||
|
|
ProgressAction = null;
|
|||
|
|
UnitsConfigured = new IDASCommunication[0];
|
|||
|
|
_unresolvedChannels.Clear();
|
|||
|
|
_resolvedChannels.Clear();
|
|||
|
|
_ExcitationTask = null;
|
|||
|
|
_ApplyConfigTask = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|