Files

614 lines
24 KiB
C#
Raw Permalink Normal View History

2026-04-17 14:55:32 -04:00
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using DTS.Common.Base;
using DTS.Common.Classes;
using DTS.Common.Events;
using DTS.Common.Events.TTSImport;
using DTS.Common.Interface;
using DTS.Common.Interface.DataRecorders;
using DTS.Common.Interface.TestSetups.Imports.TTS.HardwareScan;
using DTS.Common.Interface.TestSetups.Imports.TTS.ReadFile;
using DTS.Common.Utilities.Logging;
using Prism.Events;
using Unity;
using DTS.Common.Interactivity;
using Prism.Regions;
using Prism.Commands;
using TTSImport.Model;
// ReSharper disable CheckNamespace
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable InconsistentNaming
namespace TTSImport
{
/// <summary>
/// this class handles Hardware Scan functionality
/// </summary>
[PartCreationPolicy(CreationPolicy.Shared)]
public class HardwareScanViewModel : IHardwareScanViewModel
{
/// <summary>
/// The Status and Progress bars
/// </summary>
public IStatusAndProgressBarView StatusAndProgressBarView { get; private set; }
/// <summary>
/// The Hardware Scan view
/// </summary>
public IHardwareScanView View { get; set; }
private IEventAggregator _eventAggregator { get; set; }
private IRegionManager _regionManager;
private IUnityContainer UnityContainer { get; set; }
public InteractionRequest<Notification> NotificationRequest { get; private set; }
public InteractionRequest<Confirmation> ConfirmationRequest { get; private set; }
/// <summary>
/// Creates a new instance of the TechnologyDomainEditViewModel.
/// </summary>
/// <param name="hardwareScanView">The Hardware Scan View.</param>
/// <param name="regionManager">The logical placeholder defined within the application's UI (in the shell or within views) into which views are displayed.</param>
/// <param name="eventAggregator">The EventAggregator which allows different components to publish/subscribe to events without being coupled to each other.</param>
/// <param name="unityContainer">The unityContainer.</param>
public HardwareScanViewModel(IHardwareScanView hardwareScanView, IRegionManager regionManager,
IEventAggregator eventAggregator, IUnityContainer unityContainer)
{
View = hardwareScanView;
View.DataContext = this;
NotificationRequest = new InteractionRequest<Notification>();
ConfirmationRequest = new InteractionRequest<Confirmation>();
_eventAggregator = eventAggregator;
UnityContainer = unityContainer;
_regionManager = regionManager;
_eventAggregator.GetEvent<RaiseNotification>().Subscribe(OnRaiseNotification);
_eventAggregator.GetEvent<BusyIndicatorChangeNotification>().Subscribe(OnBusyIndicatorNotification, ThreadOption.PublisherThread, true);
_eventAggregator.GetEvent<TTSImportReadFileStatusEvent>().Subscribe(OnReadFileFinished, ThreadOption.PublisherThread, true);
_eventAggregator.GetEvent<TTSImportHardwareScanFinishedEvent>().Subscribe(OnHardwareScanFinished, ThreadOption.PublisherThread, true);
_eventAggregator.GetEvent<AssignedChannelsChangedEvent>().Subscribe(OnAssignedChannelsChangedEvent, ThreadOption.PublisherThread, true);
}
#region Methods
public void Cleanup()
{
}
public Task CleanupAsync()
{
return Task.CompletedTask;
}
public void Initialize()
{
}
public void Initialize(object parameter)
{
}
public void Initialize(object parameter, object model)
{
}
public Task InitializeAsync()
{
StatusAndProgressBarView = GetStatusAndProgressBarView(this);
return Task.CompletedTask;
}
public Task InitializeAsync(object parameter)
{
return Task.CompletedTask;
}
public void Activated()
{
}
/// <summary>
/// Private Event handler for RaiseNotification event.
/// </summary>
private void OnBusyIndicatorNotification(bool eventArg)
{
IsBusy = eventArg;
}
/// <summary>
/// Private Event handler for RaiseNotification event.
/// </summary>
private void OnRaiseNotification(NotificationContentEventArgs eventArgsWithTitle)
{
// The NotificationRequest.Raise triggers the Invoke() method of the PopupWindowAction object to show the NotificationWindow window
// Notification object expects a NotificationContentEventArgsWithoutTitle object and a Title string.
var eventArgsWithoutTitle = new NotificationContentEventArgs(eventArgsWithTitle.Message, "",
eventArgsWithTitle.Image, string.Empty);
NotificationRequest.Raise(new Notification
{
Content = eventArgsWithoutTitle,
Title = eventArgsWithTitle.Title
});
}
private readonly StatusAndProgressBarEventArgs statusAndProgressBarEventArgs = new StatusAndProgressBarEventArgs();
/// <summary>
/// Sets the text, background color, progress value, and/or progress visibility
/// </summary>
/// <param name="status"></param>
public void SetStatus(string status)
{
switch (status)
{
case "Waiting": //change this to string resources but use the same one in DataPRO and here???
statusAndProgressBarEventArgs.StatusColor =
DTS.Common.BrushesAndColors.Brush_ApplicationStatus_Waiting.Color;
statusAndProgressBarEventArgs.ProgressBarVisibility = Visibility.Collapsed;
break;
case "Working":
statusAndProgressBarEventArgs.StatusColor =
DTS.Common.BrushesAndColors.Brush_ApplicationStatus_Busy.Color;
statusAndProgressBarEventArgs.ProgressValue = 0;
statusAndProgressBarEventArgs.ProgressBarVisibility = Visibility.Visible;
break;
case "Failed":
statusAndProgressBarEventArgs.StatusColor =
DTS.Common.BrushesAndColors.Brush_ApplicationStatus_Failed.Color;
statusAndProgressBarEventArgs.ProgressBarVisibility = Visibility.Collapsed;
break;
case "Done":
statusAndProgressBarEventArgs.StatusColor =
DTS.Common.BrushesAndColors.Brush_ApplicationStatus_Complete.Color;
statusAndProgressBarEventArgs.ProgressBarVisibility = Visibility.Collapsed;
break;
}
statusAndProgressBarEventArgs.StatusText = status;
statusAndProgressBarEventArgs.Requester = this;
_eventAggregator.GetEvent<StatusAndProgressBarEvent>().Publish(statusAndProgressBarEventArgs);
}
/// <summary>
/// Sets the progress value on the Status and Progress bar
/// </summary>
/// <param name="progress"></param>
public void SetProgress(double progress)
{
statusAndProgressBarEventArgs.ProgressValue = (int)progress;
statusAndProgressBarEventArgs.Requester = this;
_eventAggregator.GetEvent<StatusAndProgressBarEvent>().Publish(statusAndProgressBarEventArgs);
}
private IStatusAndProgressBarView GetStatusAndProgressBarView(IBaseViewModel parent)
{
var view = UnityContainer.Resolve<IStatusAndProgressBarView>();
var viewModel = UnityContainer.Resolve<IStatusAndProgressBarViewModel>();
view.DataContext = viewModel;
viewModel.Initialize(parent);
return view;
}
private void OnReadFileFinished(ReadFileStatusArg statusArg)
{
if (statusArg.Status)
{
_setup = statusArg.TTSSetup;
}
}
public void HardwareScan()
{
SetStatus(Resources.StringResources
.ImportTestSetup_PossibleStatus_Working); //use string resource (same both here and where passed)
var data = new WorkFunctionThreadData();
ThreadPool.QueueUserWorkItem(HardwareScanWorkThread, data);
}
void HardwareScanWorkThread(object obj)
{
ITTSSetup temp = null;
//Blank out the tables before re-scanning
HardwareRecords[0].Update(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
DasSummaryList = new List<IDASHardware>();
ChannelSummaryList = new List<ChannelSummary>();
_eventAggregator.GetEvent<TTSImportHardwareScanRunEvent>().Publish(temp); //temp not needed?
}
private void UpdateUnitCount(IDASHardware hardwareRecord, ref uint sps, ref uint spd, ref uint spt, ref uint ecm,
ref uint g5, ref uint rack)
{
if (hardwareRecord.SerialNumber.Length < 3)
{
return;
}
var firstThree = hardwareRecord.SerialNumber.Substring(0, 3).ToLower();
switch (firstThree)
{
case "spt": spt++; break;
case "spd": spd++; break;
case "sps": sps++; break;
case "spe": ecm++; break;
case "slt": spt++; break;
case "sld": spd++; break;
case "sle": ecm++; break;
case "sls": sps++; break;
case "sg5": g5++; break;
}
var firstTwo = hardwareRecord.SerialNumber.Substring(0, 2).ToLower();
switch (firstTwo)
{
case "dr": rack++; break;
case "lr": rack++; break;
case "5m": g5++; break;
}
}
/// <summary>
/// returns IP addresses for which SLICE2 modules have been found without a corresponding ECM/SLE
/// </summary>
/// <param name="hardwareRecords"></param>
/// <returns></returns>
private static IEnumerable<string> GetMissingECMIPAddresses(List<IDASHardware> hardwareRecords)
{
var foundECMIPAddresses = new HashSet<string>();
var missingECMIPAddresses = new List<string>();
foreach (var hardwareRecord in hardwareRecords)
{
if (!hardwareRecord.IsSLICEEthernetController) continue;
var ipAddress = hardwareRecord.Connection;
var index = ipAddress.IndexOf(':');
if (index > 0)
{
ipAddress = ipAddress.Substring(0, index);
}
foundECMIPAddresses.Add(ipAddress);
}
foreach (var hardwareRecord in hardwareRecords)
{
if (hardwareRecord.SerialNumber.Length < 3) { continue; }
var ipAddress = hardwareRecord.Connection.ToLower();
var index = ipAddress.IndexOf(':');
if (index > 0)
{
ipAddress = ipAddress.Substring(0, index);
}
if (foundECMIPAddresses.Contains(ipAddress)) { continue; }
if (missingECMIPAddresses.Contains(ipAddress)) { continue; }
var firstThree = hardwareRecord.SerialNumber.Substring(0, 3).ToLower();
switch (firstThree)
{
case "spt":
case "spd":
case "sps":
case "slt":
case "sld":
case "sls":
if (ipAddress.Contains("usb") || ipAddress.Length < 6) { continue; }
missingECMIPAddresses.Add(ipAddress);
break;
}
}
return missingECMIPAddresses;
}
/// <summary>
/// Fill in the table of DAS that are in the database and connected
/// </summary>
/// <param name="hardwareRecords"></param>
private void OnHardwareScanFinished(List<IDASHardware> hardwareRecords)
{
//Create mapping from IP address to ECM/SDB/Rack
var connectionToParent =
hardwareRecords.Where(hardwareRecord => hardwareRecord.IsSLICEEthernetController ||
hardwareRecord.IsTDASRack()).ToDictionary(hardwareRecord => hardwareRecord.Connection, hardwareRecord => hardwareRecord.SerialNumber);
var moduleRecords = new List<IDASHardware>();
uint analog = 0;
uint din = 0;
uint dout = 0;
uint squib = 0;
uint sps = 0;
uint spd = 0;
uint spt = 0;
uint ecm = 0;
uint g5 = 0;
uint rack = 0;
var missingECMIPS = GetMissingECMIPAddresses(hardwareRecords);
if (missingECMIPS.Any())
{
var prompt = $"{Resources.StringResources.MissingECMSWarning}\r\n";
prompt += string.Join("\r\n", missingECMIPS.ToArray());
var mreLocal = new ManualResetEvent(false);
APILogger.Log("MessageBox", prompt);
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
var window = Application.Current.MainWindow;
if (null == window)
{
return;
}
MessageBox.Show(window, prompt, Resources.StringResources.Warning,
MessageBoxButton.OK, MessageBoxImage.Warning);
mreLocal.Set();
}));
mreLocal.WaitOne();
}
foreach (var hardwareRecord in hardwareRecords)
{
//We don't want to list bridges, but we do want to list modules in a TDAS Rack, and DAS connected via ECM, SDB, and USB.
//Modules in a TDAS Rack have IsModule() == false.
//DAS connected via ECM or SDB have a hardwareRecord.Connection that's in the connectionToParent dictionary above.
//DAS connected via USB have a hardwareRecord.Connection == "USB".
if (hardwareRecord.IsModule() &&
!connectionToParent.ContainsKey(hardwareRecord.Connection) &&
hardwareRecord.Connection != "USB") continue;
UpdateUnitCount(hardwareRecord, ref sps, ref spd, ref spt, ref ecm, ref g5, ref rack);
//We want to display TDAS Racks, ECM, and SDBs in a separate row for their battery and
//voltage status but also as a parent of their connected DAS.
if (!hardwareRecord.IsSLICEEthernetController && !hardwareRecord.IsTDASRack())
{
//Display the DAS with its associated parent (TDAS Rack/ECM/SDB)
var parentDAS = connectionToParent.ContainsKey(hardwareRecord.Connection)
? connectionToParent[hardwareRecord.Connection]
: hardwareRecord.ParentDAS;
if (!string.IsNullOrWhiteSpace(parentDAS))
{
//DAS connected via USB have a blank ParentDAS
//When the hardwareRecord.ToString() override is implemented for all DAS types, the following
//can be modified to use it. Currently, only Slice DAS connected via ECM return "<parent>:<das>".
hardwareRecord.SerialNumberFamily = "[" + parentDAS + ":" + hardwareRecord.SerialNumber + "]";
}
else
{
hardwareRecord.SerialNumberFamily = hardwareRecord.SerialNumber;
}
UpdateChannelCount(hardwareRecord, ref analog, ref squib, ref din, ref dout);
}
else
{
hardwareRecord.SerialNumberFamily = hardwareRecord.SerialNumber;
if (hardwareRecord.IsTDASRack())
{
UpdateChannelCount(hardwareRecord, ref analog, ref squib, ref din, ref dout);
}
}
moduleRecords.Add(hardwareRecord);
}
HardwareRecords[0].Update(analog, squib, din, dout, ecm, sps, spt, spd, g5, rack);
DasSummaryList = moduleRecords;
SetStatus(hardwareRecords.Any()
? Resources.StringResources.ImportTestSetup_PossibleStatus_Done
: Resources.StringResources.ImportTestSetup_PossibleStatus_Failed);
}
/// <summary>
/// counts the number of analog/squib/digitalin/digitalout on given hardware and updates count
/// </summary>
/// <param name="hardwareRecord"></param>
/// <param name="analog"></param>
/// <param name="squib"></param>
/// <param name="din"></param>
/// <param name="dout"></param>
private static void UpdateChannelCount(IDASHardware hardwareRecord, ref uint analog, ref uint squib, ref uint din,
ref uint dout)
{
var channels = hardwareRecord.GetIHardwareChannels();
for (var i = 0; i < channels.Length; i++)
{
var ch = channels[i];
if (ch.IsAnalog)
{
analog++;
}
else if (ch.IsDigitalIn)
{
din++;
}
else if (ch.IsDigitalOut)
{
dout++;
}
else if (ch.IsSquib)
{
squib++;
i++;//skip the next squib
}
}
}
/// <summary>
/// Sets a global dictionary to be used by SetChannelSummaryList
/// </summary>
private void OnAssignedChannelsChangedEvent(ITTSSetup setup)
{
SetChannelSummaryList(_setup.Channels);
}
/// <summary>
/// Fill in the table of channels that were found in the Read File step
/// </summary>
/// <param name="channelRecords"></param>
public void SetChannelSummaryList(ITTSChannelRecord[] channelRecords)
{
const int Requested = 0;
const int Assigned = 1;
const int Unassigned = 2;
var tomChannels = new[] { 0, 0, 0 };
var digitalInChannels = new[] { 0, 0, 0 };
var analogChannels = new[] { 0, 0, 0 };
foreach (var channelRecord in channelRecords)
{
if (string.Equals(channelRecord.ChannelCode, TTSChannelRecord.NONE, StringComparison.CurrentCultureIgnoreCase)) continue;
if (channelRecord.IsSquib)
{
tomChannels[Requested] += 1;
if (channelRecord.HardwareChannel != null && channelRecord.HardwareChannel.IsSquib)
{
tomChannels[Assigned] += 1;
}
else
{
tomChannels[Unassigned] += 1;
}
}
else if (channelRecord.IsDigitalInput)
{
digitalInChannels[Requested] += 1;
if (channelRecord.HardwareChannel != null && channelRecord.HardwareChannel.IsDigitalIn)
{
digitalInChannels[Assigned] += 1;
}
else
{
digitalInChannels[Unassigned] += 1;
}
}
else if (!channelRecord.IsDigitalOutput)
{
//Must be analog
analogChannels[Requested] += 1;
if (channelRecord.HardwareChannel != null && channelRecord.HardwareChannel.IsAnalog)
{
analogChannels[Assigned] += 1;
}
else
{
analogChannels[Unassigned] += 1;
}
}
}
var temp = new List<ChannelSummary>();
var channel = new ChannelSummary
{
ChannelType = Resources.StringResources.Analog,
Requested = analogChannels[Requested],
Assigned = analogChannels[Assigned],
Unassigned = analogChannels[Unassigned],
};
temp.Add(channel);
channel = new ChannelSummary
{
ChannelType = Resources.StringResources.TOM,
Requested = tomChannels[Requested],
Assigned = tomChannels[Assigned],
Unassigned = tomChannels[Unassigned],
};
temp.Add(channel);
channel = new ChannelSummary
{
ChannelType = Resources.StringResources.DigitalIn,
Requested = digitalInChannels[Requested],
Assigned = digitalInChannels[Assigned],
Unassigned = digitalInChannels[Unassigned],
};
temp.Add(channel);
ChannelSummaryList = temp;
}
#endregion
#region Properties
private ITTSSetup _setup;
public bool IsDirty { get; private set; }
private bool _isBusy = false;
public bool IsBusy
{
get => _isBusy;
set
{
_isBusy = value;
OnPropertyChanged("IsBusy");
}
}
private bool _isMenuIncluded = false;
public bool IsMenuIncluded
{
get => _isMenuIncluded;
set
{
_isMenuIncluded = value;
OnPropertyChanged("IsMenuIncluded");
}
}
private bool _isNavigationIncluded = false;
public bool IsNavigationIncluded
{
get => _isNavigationIncluded;
set
{
_isNavigationIncluded = value;
OnPropertyChanged("IsNavigationIncluded");
}
}
private List<IDASHardware> _dasSummaryList = new List<IDASHardware>();
public List<IDASHardware> DasSummaryList
{
get => _dasSummaryList;
set
{
_dasSummaryList = value;
OnPropertyChanged("DasSummaryList");
}
}
private readonly IHardwareSummaryRecord[] _hardwareRecords = { new HardwareSummaryRecord() };
public IHardwareSummaryRecord[] HardwareRecords => _hardwareRecords;
private List<ChannelSummary> _channelSummaryList = new List<ChannelSummary>();
public List<ChannelSummary> ChannelSummaryList
{
get => _channelSummaryList;
set
{
_channelSummaryList = value;
OnPropertyChanged("ChannelSummaryList");
}
}
#endregion Properties
#region Commands
#endregion
///<summary>
///Occurs when a property value changes.
///</summary>
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}