Files
DP44/DataPRO/IService/.svn/pristine/f6/f60403e7748b18604b5c96e640b66eb769baffe9.svn-base

722 lines
31 KiB
Plaintext
Raw Normal View History

2026-04-17 14:55:32 -04:00
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;
}
}
}