init
This commit is contained in:
@@ -0,0 +1,622 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using DTS.Common.Events;
|
||||
using DTS.Common.Events.TTSImport;
|
||||
using DTS.Common.Interface.DataRecorders;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS.ReadFile;
|
||||
using DTS.Common.Utilities.Logging;
|
||||
using TTSImport.Resources;
|
||||
using Application = System.Windows.Application;
|
||||
using DASChannel = TTSImport.Model.DASChannel;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
using DTS.Common.DAS.Concepts;
|
||||
using DTS.Common.Enums;
|
||||
using TTSImport.Model;
|
||||
using Prism.Events;
|
||||
using Unity;
|
||||
using DTS.Common.Interactivity;
|
||||
using Prism.Regions;
|
||||
using Prism.Commands;
|
||||
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace TTSImport
|
||||
{
|
||||
/// <summary>
|
||||
/// this class handles Level Trigger edit/create functionality
|
||||
/// </summary>
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class AnalogChannelsViewModel : IAnalogChannelsViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The Hardware Scan view
|
||||
/// </summary>
|
||||
public IAnalogChannelsView View { get; set; }
|
||||
|
||||
private IEventAggregator _eventAggregator { get; }
|
||||
private IRegionManager _regionManager;
|
||||
private IUnityContainer UnityContainer { get; }
|
||||
|
||||
public InteractionRequest<Notification> NotificationRequest { get; }
|
||||
public InteractionRequest<Confirmation> ConfirmationRequest { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
public void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
#region constructors and initializers
|
||||
/// <summary>
|
||||
/// Creates a new instance of the TechnologyDomainEditViewModel.
|
||||
/// </summary>
|
||||
/// <param name="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 AnalogChannelsViewModel(IAnalogChannelsView view, IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator, IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
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<AssignedChannelsChangedEvent>().Subscribe(OnAssignedChannelsChangedEvent,
|
||||
ThreadOption.PublisherThread, true);
|
||||
_eventAggregator.GetEvent<TTSImportHardwareScanFinishedEvent>()
|
||||
.Subscribe(OnHardwareScanComplete, ThreadOption.PublisherThread, true);
|
||||
_eventAggregator.GetEvent<EIDMappingEvent>()
|
||||
.Subscribe(OnEIDComplete, ThreadOption.PublisherThread, true);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
private void OnEIDComplete(IDictionary<string, string> sensorIdToChannelId)
|
||||
{
|
||||
var channelIdToSensorId = new Dictionary<string, string>();
|
||||
using (var e = sensorIdToChannelId.GetEnumerator())
|
||||
{
|
||||
while (e.MoveNext())
|
||||
{
|
||||
channelIdToSensorId[e.Current.Value] = e.Current.Key;
|
||||
}
|
||||
}
|
||||
_hardwareChannelIdToSensorId = channelIdToSensorId;
|
||||
}
|
||||
|
||||
public string Validate()
|
||||
{
|
||||
var bEmptyChannelCodes = false;
|
||||
|
||||
foreach (var channel in DASChannels)
|
||||
{
|
||||
if (null == channel.Channel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(channel.ToyotaCode))
|
||||
{
|
||||
bEmptyChannelCodes = true;
|
||||
}
|
||||
}
|
||||
return bEmptyChannelCodes ? StringResources.EmptyChannelCodeWarning : string.Empty;
|
||||
}
|
||||
private void OnHardwareScanComplete(List<IDASHardware> hardware)
|
||||
{
|
||||
_hardware = hardware;
|
||||
}
|
||||
|
||||
private void OnAssignedChannelsChangedEvent(ITTSSetup setup)
|
||||
{
|
||||
if (!Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
OnAssignedChannelsChangedEvent(setup);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
_setup = setup;
|
||||
if (null == _hardware || null == _setup) { return; }
|
||||
var channels = new ObservableCollection<DASChannel>();
|
||||
var remainingChannels = new ObservableCollection<ITTSChannelRecord>();
|
||||
|
||||
var channelIdToDASChannel = new Dictionary<string, DASChannel>();
|
||||
foreach (var das in _hardware)
|
||||
{
|
||||
if (das.IsSLICEEthernetController) { continue; }
|
||||
var ichannels = das.GetIHardwareChannels();
|
||||
foreach (var ch in ichannels)
|
||||
{
|
||||
if (!ch.IsAnalog) { continue; }
|
||||
var newChannel = new DASChannel(ch);
|
||||
if (_hardwareChannelIdToSensorId.ContainsKey(ch.GetId()))
|
||||
{
|
||||
newChannel.EID = _hardwareChannelIdToSensorId[ch.GetId()];
|
||||
}
|
||||
channels.Add(newChannel);
|
||||
channelIdToDASChannel[newChannel.HardwareChannel.GetId()] = newChannel;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var channelRecord in _setup.Channels)
|
||||
{
|
||||
if (channelRecord.IsEmptyRecord) { continue; }
|
||||
if (channelRecord.IsDigitalInput) { continue; }
|
||||
if (channelRecord.IsDigitalOutput) { continue; }
|
||||
if (channelRecord.IsSquib) { continue; }
|
||||
if (!channelRecord.IsChannelCodeValid) { continue; }
|
||||
if (channelRecord.ChannelCode == TTSChannelRecord.NONE) { continue; }
|
||||
if (null != channelRecord.HardwareChannel)
|
||||
{
|
||||
var key = channelRecord.HardwareChannel.GetId();
|
||||
if (channelIdToDASChannel.ContainsKey(key))
|
||||
{
|
||||
channelIdToDASChannel[key].SetITTSChannelRecord(channelRecord);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
remainingChannels.Add(channelRecord);
|
||||
}
|
||||
}
|
||||
|
||||
DASChannels = channels;
|
||||
RemainingChannels = remainingChannels;
|
||||
OnPropertyChanged("DASChannels");
|
||||
OnPropertyChanged("RemainingChannels");
|
||||
}
|
||||
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()
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
private ITTSSetup _setup;
|
||||
private IList<IDASHardware> _hardware;
|
||||
private IDictionary<string, string> _hardwareChannelIdToSensorId = new Dictionary<string, string>();
|
||||
public bool IsDirty { get; private set; }
|
||||
private bool _isBusy;
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set
|
||||
{
|
||||
_isBusy = value;
|
||||
OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isMenuIncluded;
|
||||
|
||||
public bool IsMenuIncluded
|
||||
{
|
||||
get => _isMenuIncluded;
|
||||
set
|
||||
{
|
||||
_isMenuIncluded = value;
|
||||
OnPropertyChanged("IsMenuIncluded");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isNavigationIncluded;
|
||||
|
||||
public bool IsNavigationIncluded
|
||||
{
|
||||
get => _isNavigationIncluded;
|
||||
set
|
||||
{
|
||||
_isNavigationIncluded = value;
|
||||
OnPropertyChanged("IsNavigationIncluded");
|
||||
}
|
||||
}
|
||||
|
||||
public bool AssignEnabled { get; set; }
|
||||
public bool RemoveEnabled { get; set; }
|
||||
public bool EnableOrDisableEnabled { get; set; }
|
||||
public ObservableCollection<DASChannel> DASChannels { get; set; } = new ObservableCollection<DASChannel>();
|
||||
public ObservableCollection<ITTSChannelRecord> RemainingChannels { get; set; } =
|
||||
new ObservableCollection<ITTSChannelRecord>();
|
||||
|
||||
private ITTSChannelRecord _selectedRemainingChannel;
|
||||
public ITTSChannelRecord SelectedRemainingChannel
|
||||
{
|
||||
get => _selectedRemainingChannel;
|
||||
set
|
||||
{
|
||||
_selectedRemainingChannel = value;
|
||||
if (null == _selectedRemainingChannel || null == SelectedDASChannel) return;
|
||||
AssignEnabled = true;
|
||||
OnPropertyChanged("AssignEnabled");
|
||||
}
|
||||
}
|
||||
private DASChannel _selectedDASChannel;
|
||||
public DASChannel SelectedDASChannel
|
||||
{
|
||||
get => _selectedDASChannel;
|
||||
set
|
||||
{
|
||||
_selectedDASChannel = value;
|
||||
DetermineRemoveEnableStatus();
|
||||
}
|
||||
}
|
||||
public void DetermineRemoveEnableStatus()
|
||||
{
|
||||
if (_selectedDASChannel?.Channel != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_selectedDASChannel.EID) &&
|
||||
_selectedDASChannel.EID == _selectedDASChannel.Channel.SensorEID)
|
||||
{
|
||||
//can only be replaced, can't be removed
|
||||
RemoveEnabled = false;
|
||||
}
|
||||
RemoveEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveEnabled = false;
|
||||
}
|
||||
OnPropertyChanged("RemoveEnabled");
|
||||
AssignEnabled = null != _selectedDASChannel && null != _selectedRemainingChannel;
|
||||
OnPropertyChanged("AssignEnabled");
|
||||
EnableOrDisableEnabled = null != _selectedDASChannel && null != _selectedDASChannel.Channel;
|
||||
OnPropertyChanged("EnableOrDisableEnabled");
|
||||
OnPropertyChanged("EnableOrDisableText");
|
||||
}
|
||||
|
||||
public string EnableOrDisableText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SelectedDASChannel?.Channel == null)
|
||||
{
|
||||
return StringResources.Analog_Enable;
|
||||
}
|
||||
return SelectedDASChannel.Channel.Disabled
|
||||
? StringResources.Analog_Enable
|
||||
: StringResources.Analog_Disable;
|
||||
}
|
||||
}
|
||||
#endregion Properties
|
||||
|
||||
#region Commands
|
||||
#region assign
|
||||
/// <summary>
|
||||
/// Assign a channel code to a channel
|
||||
/// </summary>
|
||||
private DelegateCommand _assignCommand;
|
||||
public DelegateCommand AssignCommand => _assignCommand ?? (_assignCommand = new DelegateCommand(Assign));
|
||||
private void Assign()
|
||||
{
|
||||
if (SelectedRemainingChannel == null) { return; }
|
||||
|
||||
var error = string.Empty;
|
||||
if (!VoltageIsValid(SelectedRemainingChannel, SelectedDASChannel, out error))
|
||||
{
|
||||
var window = Application.Current.MainWindow;
|
||||
if (null == window) { return; }
|
||||
MessageBox.Show(window, error, StringResources.AssignSensorExcitationError, MessageBoxButton.OK);
|
||||
return;
|
||||
}
|
||||
_eventAggregator.GetEvent<TTSImportTestSetupChangedEvent>().Publish(_setup);
|
||||
//BEFORE we go any further, check the state of sensor ids
|
||||
//if the channel has a sensor id AND there's a sensor on the channel with the same id
|
||||
//then prompt on replacing the id
|
||||
//IF the channel has a sensor id and there's no sensor on the channel BUT the new sensor has a different id
|
||||
//then prompt on replacing the id
|
||||
var bReplacingID = false;
|
||||
if (!string.IsNullOrWhiteSpace(SelectedDASChannel.EID))
|
||||
{
|
||||
if (null != SelectedRemainingChannel && SelectedRemainingChannel.SensorEID != SelectedDASChannel.EID)
|
||||
{
|
||||
bReplacingID = true;
|
||||
}
|
||||
//if existing channel has this ID, we will need to clear out the old id and assign a new one...
|
||||
if (null != SelectedDASChannel.Channel &&
|
||||
SelectedDASChannel.Channel.SensorEID == SelectedDASChannel.EID)
|
||||
{
|
||||
bReplacingID = true;
|
||||
}
|
||||
}
|
||||
else if (_setup.RequireEIDFound && !string.IsNullOrWhiteSpace(SelectedRemainingChannel.SensorEID))
|
||||
{
|
||||
var window = Application.Current.MainWindow;
|
||||
if (null == window) { return; }
|
||||
var msg = string.Format(StringResources.AllowEIDToBlankChannelChallenge, SelectedRemainingChannel.SensorSerialNumber);
|
||||
msg = msg.Replace("\\r\\n", "\r\n");
|
||||
|
||||
var dialogResult = MessageBox.Show(window, msg, StringResources.UserFeedbackRequired, MessageBoxButton.OKCancel);
|
||||
APILogger.Log(msg, $"User pressed {dialogResult.ToString()}");
|
||||
if (dialogResult == MessageBoxResult.OK)
|
||||
{
|
||||
SelectedRemainingChannel.SensorEID = string.Empty;
|
||||
AssignWork();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (bReplacingID)
|
||||
{
|
||||
var window = Application.Current.MainWindow;
|
||||
if (null == window) { return; }
|
||||
var dialogResult = MessageBox.Show(window, StringResources.AssignSensorPrompt, StringResources.UserFeedbackRequired, MessageBoxButton.YesNo);
|
||||
APILogger.Log(StringResources.AssignSensorPrompt, $"User pressed {dialogResult.ToString()}");
|
||||
if (dialogResult == MessageBoxResult.Yes)
|
||||
{
|
||||
AssignWork();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AssignWork();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns True if the sensor's voltage is supported by the hardware channel, or an error message and False if not
|
||||
/// </summary>
|
||||
/// <param name="selectedRemainingChannel"></param>
|
||||
/// <param name="selectedDASChannel"></param>
|
||||
/// <param name="error"></param>
|
||||
/// <returns></returns>
|
||||
private bool VoltageIsValid(ITTSChannelRecord selectedRemainingChannel, DASChannel selectedDASChannel, out string error)
|
||||
{
|
||||
error = string.Empty;
|
||||
var voltageEnum = ExcitationVoltageOptions.ExcitationVoltageOption.Undefined;
|
||||
try
|
||||
{
|
||||
voltageEnum = Test.Module.Channel.Sensor.GetExcitationVoltageEnumFromMagnitude(selectedRemainingChannel.SensorExcitationVolts);
|
||||
}
|
||||
catch { } //GetExcitationVoltageEnumFromMagnitude will throw an exception if an invalid voltage is passed to it
|
||||
|
||||
if (selectedDASChannel.HardwareChannel.IsSupportedExcitation(voltageEnum)) return true;
|
||||
error = string.Format(StringResources.InvalidExcitationAssignment, selectedRemainingChannel.SensorExcitationVolts);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// removes the channel as a selected channel for any level triggers
|
||||
/// removes the channel as a possible channel for any level triggers
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
private void RemoveFromLevelTriggers(ITTSChannelRecord channel)
|
||||
{
|
||||
if (null != _setup)
|
||||
{
|
||||
foreach (var lt in _setup.LevelTriggers)
|
||||
{
|
||||
lt.Remove(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// adds the channel as a possible channel to any level triggers
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
private void AddToLevelTriggers(ITTSChannelRecord channel)
|
||||
{
|
||||
if (null != _setup)
|
||||
{
|
||||
foreach (var lt in _setup.LevelTriggers)
|
||||
{
|
||||
lt.Add(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// assigns a channel record to a physical channel
|
||||
/// </summary>
|
||||
private void AssignWork()
|
||||
{
|
||||
if (null == SelectedDASChannel || null == SelectedRemainingChannel) { return; }
|
||||
var excitation =
|
||||
Test.Module.Channel.Sensor.GetExcitationVoltageEnumFromMagnitude(SelectedRemainingChannel
|
||||
.SensorExcitationVolts);
|
||||
if (!SelectedDASChannel.HardwareChannel.IsSupportedExcitation(excitation))
|
||||
{
|
||||
var window = Application.Current.MainWindow;
|
||||
if (null == window) { return; }
|
||||
var msg = string.Format(StringResources.ExcitationNotSupportedByChannel, excitation.ToString());
|
||||
var result = MessageBox.Show(window, msg, StringResources.Warning, MessageBoxButton.OK);
|
||||
APILogger.Log(msg, $"user pressed {result.ToString()}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (null != SelectedDASChannel.Channel)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SelectedDASChannel.EID))
|
||||
{
|
||||
SelectedDASChannel.Channel.SensorEID = "";
|
||||
}
|
||||
RemainingChannels.Add(SelectedDASChannel.Channel);
|
||||
RemoveFromLevelTriggers(SelectedDASChannel.Channel);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(SelectedDASChannel.EID))
|
||||
{
|
||||
SelectedRemainingChannel.SensorEID = SelectedDASChannel.EID;
|
||||
}
|
||||
|
||||
SelectedDASChannel.SetITTSChannelRecord(SelectedRemainingChannel);
|
||||
|
||||
AddToLevelTriggers(SelectedDASChannel.Channel);
|
||||
var channel = SelectedRemainingChannel;
|
||||
var index = RemainingChannels.IndexOf(channel);
|
||||
SelectedRemainingChannel = null;
|
||||
RemainingChannels.Remove(channel);
|
||||
CollectionViewSource.GetDefaultView(DASChannels)?.Refresh();
|
||||
if (index < RemainingChannels.Count)
|
||||
{
|
||||
SelectedRemainingChannel = RemainingChannels[index];
|
||||
OnPropertyChanged("SelectedRemainingChannel");
|
||||
}
|
||||
else if (RemainingChannels.Count > 0)
|
||||
{
|
||||
SelectedRemainingChannel = RemainingChannels[index - 1];
|
||||
OnPropertyChanged("SelectedRemainingChannel");
|
||||
}
|
||||
index = DASChannels.IndexOf(SelectedDASChannel);
|
||||
for (var i = index; i < DASChannels.Count; i++)
|
||||
{
|
||||
var dasChannel = DASChannels[i];
|
||||
if (null != dasChannel.Channel) { continue; }
|
||||
SelectedDASChannel = dasChannel;
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
//didn't find a match, start from the beginning?
|
||||
for (var i = 0; i < index; i++)
|
||||
{
|
||||
var dasChannel = DASChannels[i];
|
||||
if (null != dasChannel.Channel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SelectedDASChannel = dasChannel;
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
//if we get here there's no new channel to go to, change remove/enable/disable button status
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
DetermineRemoveEnableStatus();
|
||||
}
|
||||
#endregion
|
||||
#region remove
|
||||
private DelegateCommand _removeCommand;
|
||||
public DelegateCommand RemoveCommand => _removeCommand ?? (_removeCommand = new DelegateCommand(Remove));
|
||||
/// <summary>
|
||||
/// remove a hardware channel assignment (does not remove the channel from the test setup though?)
|
||||
/// </summary>
|
||||
private void Remove()
|
||||
{
|
||||
if (null == SelectedDASChannel || null == SelectedDASChannel.Channel) { return; }
|
||||
_eventAggregator.GetEvent<TTSImportTestSetupChangedEvent>().Publish(_setup);
|
||||
RemainingChannels.Add(SelectedDASChannel.Channel);
|
||||
SelectedDASChannel.SetITTSChannelRecord(null);
|
||||
CollectionViewSource.GetDefaultView(DASChannels)?.Refresh();
|
||||
var index = DASChannels.IndexOf(SelectedDASChannel);
|
||||
for (var i = index; i < DASChannels.Count; i++)
|
||||
{
|
||||
if (null == DASChannels[i].Channel) { continue; }
|
||||
SelectedDASChannel = DASChannels[i];
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < index; i++)
|
||||
{
|
||||
if (null == DASChannels[i].Channel) { continue; }
|
||||
SelectedDASChannel = DASChannels[i];
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
//if we get here there's no new channel to go to, but we need to set the remove/enable/disable button status
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
DetermineRemoveEnableStatus();
|
||||
}
|
||||
#endregion
|
||||
#region enableordisable
|
||||
private DelegateCommand _enableOrDisableCommand;
|
||||
public DelegateCommand EnableOrDisableCommand =>
|
||||
_enableOrDisableCommand ?? (_enableOrDisableCommand = new DelegateCommand(EnableOrDisable));
|
||||
/// <summary>
|
||||
/// enables or disables a channel in the test.
|
||||
/// </summary>
|
||||
private void EnableOrDisable()
|
||||
{
|
||||
if (SelectedDASChannel?.Channel == null) { return; }
|
||||
SelectedDASChannel.Channel.Disabled = !SelectedDASChannel.Channel.Disabled;
|
||||
SelectedDASChannel.Disabled = SelectedDASChannel.Channel.Disabled;
|
||||
if (SelectedDASChannel.Channel.Disabled)
|
||||
{
|
||||
RemoveFromLevelTriggers(SelectedDASChannel.Channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddToLevelTriggers(SelectedDASChannel.Channel);
|
||||
}
|
||||
OnPropertyChanged("EnableOrDisableText");
|
||||
_eventAggregator.GetEvent<TTSImportTestSetupChangedEvent>().Publish(_setup);
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using DTS.Common.Events;
|
||||
using DTS.Common.Events.TTSImport;
|
||||
using DTS.Common.Interface.DataRecorders;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS.DIChannels;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS.ReadFile;
|
||||
using Prism.Events;
|
||||
using Unity;
|
||||
using DTS.Common.Interactivity;
|
||||
using Prism.Regions;
|
||||
using Prism.Commands;
|
||||
using TTSImport.Model;
|
||||
using TTSImport.Resources;
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace TTSImport
|
||||
{
|
||||
/// <summary>
|
||||
/// this class handles Level Trigger edit/create functionality
|
||||
/// </summary>
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class DigitalInputChannelsViewModel : IDigitalInputChannelsViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The Hardware Scan view
|
||||
/// </summary>
|
||||
public IDigitalInputChannelsView View { get; set; }
|
||||
|
||||
private IEventAggregator _eventAggregator { get; }
|
||||
private IRegionManager _regionManager;
|
||||
private IUnityContainer UnityContainer { get; }
|
||||
|
||||
public InteractionRequest<Notification> NotificationRequest { get; }
|
||||
public InteractionRequest<Confirmation> ConfirmationRequest { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the TechnologyDomainEditViewModel.
|
||||
/// </summary>
|
||||
/// <param name="view">The IDigitalInputChannelsView.</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 DigitalInputChannelsViewModel(IDigitalInputChannelsView view, IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator, IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
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<AssignedChannelsChangedEvent>().Subscribe(OnAssignedChannelsChangedEvent,
|
||||
ThreadOption.PublisherThread, true);
|
||||
_eventAggregator.GetEvent<TTSImportHardwareScanFinishedEvent>()
|
||||
.Subscribe(OnHardwareScanComplete, ThreadOption.PublisherThread, true);
|
||||
}
|
||||
|
||||
#region Methods
|
||||
private void OnHardwareScanComplete(List<IDASHardware> hardware)
|
||||
{
|
||||
_hardware = hardware;
|
||||
}
|
||||
|
||||
private void OnAssignedChannelsChangedEvent(ITTSSetup setup)
|
||||
{
|
||||
if (!Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
OnAssignedChannelsChangedEvent(setup);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
_setup = setup;
|
||||
if (null == _hardware || null == _setup) { return; }
|
||||
var channels = new ObservableCollection<DASChannel>();
|
||||
var remainingChannels = new ObservableCollection<ITTSChannelRecord>();
|
||||
var channelIdToChannelRecord = new Dictionary<string, DASChannel>();
|
||||
foreach (var das in _hardware)
|
||||
{
|
||||
var ichannels = das.GetIHardwareChannels();
|
||||
foreach (var ch in ichannels)
|
||||
{
|
||||
if (!ch.IsDigitalIn) { continue; }
|
||||
var newChannel = new DASChannel(ch);
|
||||
channels.Add(newChannel);
|
||||
channelIdToChannelRecord[ch.GetId()] = newChannel;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var channelRecord in _setup.Channels)
|
||||
{
|
||||
if (channelRecord.IsEmptyRecord)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!channelRecord.IsDigitalInput)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!channelRecord.IsChannelCodeValid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (channelRecord.ChannelCode == TTSChannelRecord.NONE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (null != channelRecord.HardwareChannel &&
|
||||
channelIdToChannelRecord.ContainsKey(channelRecord.HardwareChannel.GetId()))
|
||||
{
|
||||
channelIdToChannelRecord[channelRecord.HardwareChannel.GetId()].SetITTSChannelRecord(channelRecord);
|
||||
}
|
||||
else
|
||||
{
|
||||
remainingChannels.Add(channelRecord);
|
||||
}
|
||||
}
|
||||
|
||||
DASChannels = channels;
|
||||
RemainingChannels = remainingChannels;
|
||||
OnPropertyChanged("DASChannels");
|
||||
OnPropertyChanged("RemainingChannels");
|
||||
}
|
||||
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()
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
private ITTSSetup _setup;
|
||||
private IList<IDASHardware> _hardware;
|
||||
public bool IsDirty { get; private set; }
|
||||
private bool _isBusy;
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set
|
||||
{
|
||||
_isBusy = value;
|
||||
OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isMenuIncluded;
|
||||
|
||||
public bool IsMenuIncluded
|
||||
{
|
||||
get => _isMenuIncluded;
|
||||
set
|
||||
{
|
||||
_isMenuIncluded = value;
|
||||
OnPropertyChanged("IsMenuIncluded");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isNavigationIncluded;
|
||||
|
||||
public bool IsNavigationIncluded
|
||||
{
|
||||
get => _isNavigationIncluded;
|
||||
set
|
||||
{
|
||||
_isNavigationIncluded = value;
|
||||
OnPropertyChanged("IsNavigationIncluded");
|
||||
}
|
||||
}
|
||||
public bool AssignEnabled { get; set; }
|
||||
public bool RemoveEnabled { get; set; }
|
||||
public bool EnableOrDisableEnabled { get; set; }
|
||||
public ObservableCollection<DASChannel> DASChannels { get; set; } = new ObservableCollection<DASChannel>();
|
||||
public ObservableCollection<ITTSChannelRecord> RemainingChannels { get; set; } =
|
||||
new ObservableCollection<ITTSChannelRecord>();
|
||||
|
||||
private ITTSChannelRecord _selectedRemainingChannel;
|
||||
public ITTSChannelRecord SelectedRemainingChannel
|
||||
{
|
||||
get => _selectedRemainingChannel;
|
||||
set
|
||||
{
|
||||
_selectedRemainingChannel = value;
|
||||
if (null == _selectedRemainingChannel || null == SelectedDASChannel) return;
|
||||
AssignEnabled = true;
|
||||
OnPropertyChanged("AssignEnabled");
|
||||
}
|
||||
}
|
||||
private DASChannel _selectedDASChannel;
|
||||
public DASChannel SelectedDASChannel
|
||||
{
|
||||
get => _selectedDASChannel;
|
||||
set
|
||||
{
|
||||
_selectedDASChannel = value;
|
||||
DetermineRemoveEnableStatus();
|
||||
}
|
||||
}
|
||||
private void DetermineRemoveEnableStatus()
|
||||
{
|
||||
if (_selectedDASChannel?.Channel != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_selectedDASChannel.EID) &&
|
||||
_selectedDASChannel.EID == _selectedDASChannel.Channel.SensorEID)
|
||||
{
|
||||
//can only be replaced, can't be removed
|
||||
RemoveEnabled = false;
|
||||
}
|
||||
RemoveEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveEnabled = false;
|
||||
}
|
||||
OnPropertyChanged("RemoveEnabled");
|
||||
AssignEnabled = null != _selectedDASChannel && null != _selectedRemainingChannel;
|
||||
OnPropertyChanged("AssignEnabled");
|
||||
EnableOrDisableEnabled = _selectedDASChannel?.Channel != null;
|
||||
OnPropertyChanged("EnableOrDisableEnabled");
|
||||
OnPropertyChanged("EnableOrDisableText");
|
||||
}
|
||||
public string EnableOrDisableText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SelectedDASChannel?.Channel == null)
|
||||
{
|
||||
return StringResources.Analog_Enable;
|
||||
}
|
||||
return SelectedDASChannel.Channel.Disabled
|
||||
? StringResources.Analog_Enable
|
||||
: StringResources.Analog_Disable;
|
||||
}
|
||||
}
|
||||
#endregion Properties
|
||||
|
||||
#region Commands
|
||||
#region assign
|
||||
/// <summary>
|
||||
/// Assign a channel code to a channel
|
||||
/// </summary>
|
||||
private DelegateCommand _assignCommand;
|
||||
public DelegateCommand AssignCommand => _assignCommand ?? (_assignCommand = new DelegateCommand(Assign));
|
||||
private void Assign()
|
||||
{
|
||||
if (SelectedRemainingChannel == null) { return; }
|
||||
|
||||
_eventAggregator.GetEvent<TTSImportTestSetupChangedEvent>().Publish(_setup);
|
||||
//BEFORE we go any further, check the state of sensor ids
|
||||
//if the channel has a sensor id AND there's a sensor on the channel with the same id
|
||||
//then prompt on replacing the id
|
||||
//IF the channel has a sensor id and there's no sensor on the channel BUT the new sensor has a different id
|
||||
//then prompt on replacing the id
|
||||
var bReplacingID = false;
|
||||
if (!string.IsNullOrWhiteSpace(SelectedDASChannel.EID))
|
||||
{
|
||||
if (null != SelectedRemainingChannel && SelectedRemainingChannel.SensorEID != SelectedDASChannel.EID)
|
||||
{
|
||||
bReplacingID = true;
|
||||
}
|
||||
//if existing channel has this ID, we will need to clear out the old id and assign a new one...
|
||||
if (null != SelectedDASChannel.Channel && SelectedDASChannel.Channel.SensorEID == SelectedDASChannel.EID)
|
||||
{
|
||||
bReplacingID = true;
|
||||
}
|
||||
}
|
||||
if (bReplacingID)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var dialogResult = MessageBox.Show(StringResources.AssignSensorPrompt, StringResources.UserFeedbackRequired, MessageBoxButton.YesNo);
|
||||
if (dialogResult == MessageBoxResult.Yes)
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(AssignWork));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
AssignWork();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// assigns a channel record to a physical channel
|
||||
/// </summary>
|
||||
private void AssignWork()
|
||||
{
|
||||
if (null != SelectedDASChannel.Channel)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SelectedDASChannel.EID))
|
||||
{
|
||||
SelectedDASChannel.Channel.SensorEID = "";
|
||||
}
|
||||
RemainingChannels.Add(SelectedDASChannel.Channel);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(SelectedDASChannel.EID))
|
||||
{
|
||||
SelectedRemainingChannel.SensorEID = SelectedDASChannel.EID;
|
||||
}
|
||||
|
||||
SelectedRemainingChannel.DigitalInputMode = DTS.Common.Enums.DigitalInputModes.CCNO;
|
||||
SelectedDASChannel.SetITTSChannelRecord(SelectedRemainingChannel);
|
||||
|
||||
var channel = SelectedRemainingChannel;
|
||||
var index = RemainingChannels.IndexOf(channel);
|
||||
SelectedRemainingChannel = null;
|
||||
RemainingChannels.Remove(channel);
|
||||
CollectionViewSource.GetDefaultView(DASChannels)?.Refresh();
|
||||
if (index < RemainingChannels.Count)
|
||||
{
|
||||
SelectedRemainingChannel = RemainingChannels[index];
|
||||
OnPropertyChanged("SelectedRemainingChannel");
|
||||
}
|
||||
else if (RemainingChannels.Count > 0)
|
||||
{
|
||||
SelectedRemainingChannel = RemainingChannels[index - 1];
|
||||
OnPropertyChanged("SelectedRemainingChannel");
|
||||
}
|
||||
index = DASChannels.IndexOf(SelectedDASChannel);
|
||||
for (var i = index; i < DASChannels.Count; i++)
|
||||
{
|
||||
var dasChannel = DASChannels[i];
|
||||
if (null != dasChannel.Channel) { continue; }
|
||||
SelectedDASChannel = dasChannel;
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
//didn't find a match, start from the beginning?
|
||||
for (var i = 0; i < index; i++)
|
||||
{
|
||||
var dasChannel = DASChannels[i];
|
||||
if (null != dasChannel.Channel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SelectedDASChannel = dasChannel;
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
//if we get here there's no new channel to go to, but we need to set the remove/enable/disable button status
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
DetermineRemoveEnableStatus();
|
||||
}
|
||||
#endregion
|
||||
#region remove
|
||||
private DelegateCommand _removeCommand;
|
||||
public DelegateCommand RemoveCommand => _removeCommand ?? (_removeCommand = new DelegateCommand(Remove));
|
||||
/// <summary>
|
||||
/// remove a hardware channel assignment (does not remove the channel from the test setup though?)
|
||||
/// </summary>
|
||||
private void Remove()
|
||||
{
|
||||
if (null == SelectedDASChannel || null == SelectedDASChannel.Channel) { return; }
|
||||
_eventAggregator.GetEvent<TTSImportTestSetupChangedEvent>().Publish(_setup);
|
||||
RemainingChannels.Add(SelectedDASChannel.Channel);
|
||||
SelectedDASChannel.SetITTSChannelRecord(null);
|
||||
CollectionViewSource.GetDefaultView(DASChannels)?.Refresh();
|
||||
var index = DASChannels.IndexOf(SelectedDASChannel);
|
||||
for (var i = index; i < DASChannels.Count; i++)
|
||||
{
|
||||
if (null == DASChannels[i].Channel) { continue; }
|
||||
SelectedDASChannel = DASChannels[i];
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < index; i++)
|
||||
{
|
||||
if (null == DASChannels[i].Channel) { continue; }
|
||||
SelectedDASChannel = DASChannels[i];
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
//if we get here there's no new channel to go to, but we need to set the remove/enable/disable button status
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
DetermineRemoveEnableStatus();
|
||||
}
|
||||
#endregion
|
||||
#region enableordisable
|
||||
private DelegateCommand _enableOrDisableCommand;
|
||||
public DelegateCommand EnableOrDisableCommand =>
|
||||
_enableOrDisableCommand ?? (_enableOrDisableCommand = new DelegateCommand(EnableOrDisable));
|
||||
/// <summary>
|
||||
/// enables or disables a channel in the test.
|
||||
/// </summary>
|
||||
private void EnableOrDisable()
|
||||
{
|
||||
if (SelectedDASChannel?.Channel == null) { return; }
|
||||
SelectedDASChannel.Channel.Disabled = !SelectedDASChannel.Channel.Disabled;
|
||||
SelectedDASChannel.Disabled = SelectedDASChannel.Channel.Disabled;
|
||||
|
||||
OnPropertyChanged("EnableOrDisableText");
|
||||
_eventAggregator.GetEvent<TTSImportTestSetupChangedEvent>().Publish(_setup);
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using DTS.Common.Events;
|
||||
using DTS.Common.Events.TTSImport;
|
||||
using DTS.Common.Interface.DataRecorders;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS.DOChannels;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS.ReadFile;
|
||||
using Prism.Events;
|
||||
using Unity;
|
||||
using DTS.Common.Interactivity;
|
||||
using Prism.Regions;
|
||||
using Prism.Commands;
|
||||
using TTSImport.Model;
|
||||
using TTSImport.Resources;
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace TTSImport
|
||||
{
|
||||
/// <summary>
|
||||
/// this class handles Level Trigger edit/create functionality
|
||||
/// </summary>
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class DigitalOutputChannelsViewModel : IDigitalOutputChannelsViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The Hardware Scan view
|
||||
/// </summary>
|
||||
public IDigitalOutputChannelsView View { get; set; }
|
||||
|
||||
private IEventAggregator _eventAggregator { get; }
|
||||
private IRegionManager _regionManager;
|
||||
private IUnityContainer UnityContainer { get; }
|
||||
|
||||
public InteractionRequest<Notification> NotificationRequest { get; }
|
||||
public InteractionRequest<Confirmation> ConfirmationRequest { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the TechnologyDomainEditViewModel.
|
||||
/// </summary>
|
||||
/// <param name="view">The IDigitalOutputChannelsView.</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 DigitalOutputChannelsViewModel(IDigitalOutputChannelsView view, IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator, IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
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<AssignedChannelsChangedEvent>().Subscribe(OnAssignedChannelsChangedEvent,
|
||||
ThreadOption.PublisherThread, true);
|
||||
_eventAggregator.GetEvent<TTSImportHardwareScanFinishedEvent>()
|
||||
.Subscribe(OnHardwareScanComplete, ThreadOption.PublisherThread, true);
|
||||
_eventAggregator.GetEvent<EIDMappingEvent>()
|
||||
.Subscribe(OnEIDComplete, ThreadOption.PublisherThread, true);
|
||||
}
|
||||
|
||||
#region Methods
|
||||
private void OnEIDComplete(IDictionary<string, string> sensorIdToChannelId)
|
||||
{
|
||||
var channelIdToSensorId = new Dictionary<string, string>();
|
||||
using (var e = sensorIdToChannelId.GetEnumerator())
|
||||
{
|
||||
while (e.MoveNext())
|
||||
{
|
||||
channelIdToSensorId[e.Current.Value] = e.Current.Key;
|
||||
}
|
||||
}
|
||||
_hardwareChannelIdToSensorId = channelIdToSensorId;
|
||||
}
|
||||
private void OnHardwareScanComplete(List<IDASHardware> hardware)
|
||||
{
|
||||
_hardware = hardware;
|
||||
}
|
||||
|
||||
private void OnAssignedChannelsChangedEvent(ITTSSetup setup)
|
||||
{
|
||||
if (!Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
OnAssignedChannelsChangedEvent(setup);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
_setup = setup;
|
||||
if (null == _hardware || null == _setup) { return; }
|
||||
var channels = new ObservableCollection<DASChannel>();
|
||||
var channelIdToChannelRecord = new Dictionary<string, ITTSChannelRecord>();
|
||||
foreach (var ch in _setup.Channels)
|
||||
{
|
||||
if (!ch.IsDigitalOutput) { continue; }
|
||||
if (null == ch.HardwareChannel) { continue; }
|
||||
if (ch.ChannelCode == TTSChannelRecord.NONE) { continue; }
|
||||
channelIdToChannelRecord[ch.HardwareChannel.GetId()] = ch;
|
||||
}
|
||||
var channelIdToDASChannel = new Dictionary<string, DASChannel>();
|
||||
foreach (var das in _hardware)
|
||||
{
|
||||
var ichannels = das.GetIHardwareChannels();
|
||||
foreach (var ch in ichannels)
|
||||
{
|
||||
if (!ch.IsDigitalOut) { continue; }
|
||||
var newChannel = new DASChannel(ch, _setup);
|
||||
if (_hardwareChannelIdToSensorId.ContainsKey(ch.GetId()))
|
||||
{
|
||||
newChannel.EID = _hardwareChannelIdToSensorId[ch.GetId()];
|
||||
}
|
||||
channels.Add(newChannel);
|
||||
channelIdToDASChannel[newChannel.HardwareChannel.GetId()] = newChannel;
|
||||
if (channelIdToChannelRecord.ContainsKey(ch.GetId()))
|
||||
{
|
||||
newChannel.SetITTSChannelRecord(channelIdToChannelRecord[ch.GetId()]);
|
||||
newChannel.Channel.ChannelCode = $"Digital Out {ch.ToString()}";
|
||||
newChannel.Channel.SensorSerialNumber = newChannel.Channel.ChannelCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (var enumChannels = channelIdToDASChannel.GetEnumerator())
|
||||
{
|
||||
while (enumChannels.MoveNext())
|
||||
{
|
||||
if (null == enumChannels.Current.Value.Channel)
|
||||
{
|
||||
//CREATE new channel
|
||||
var ch = new TTSChannelRecord();
|
||||
ch.ChannelCode = $"Digital Out {enumChannels.Current.Value.HardwareChannel?.ToString()}";
|
||||
ch.IsChannelCodeValid = true;
|
||||
ch.IsDigitalOutput = true;
|
||||
ch.SensorEU = "V";
|
||||
ch.HardwareChannel = enumChannels.Current.Value.HardwareChannel;
|
||||
//ch.SensorSerialNumber = ch.ChannelCode;
|
||||
enumChannels.Current.Value.SetITTSChannelRecord(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DASChannels = channels;
|
||||
OnPropertyChanged("DASChannels");
|
||||
CollectionViewSource.GetDefaultView(DASChannels)?.Refresh();
|
||||
}
|
||||
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()
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
private ITTSSetup _setup;
|
||||
private IList<IDASHardware> _hardware;
|
||||
private IDictionary<string, string> _hardwareChannelIdToSensorId = new Dictionary<string, string>();
|
||||
public bool IsDirty { get; private set; }
|
||||
private bool _isBusy;
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set
|
||||
{
|
||||
_isBusy = value;
|
||||
OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isMenuIncluded;
|
||||
|
||||
public bool IsMenuIncluded
|
||||
{
|
||||
get => _isMenuIncluded;
|
||||
set
|
||||
{
|
||||
_isMenuIncluded = value;
|
||||
OnPropertyChanged("IsMenuIncluded");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isNavigationIncluded;
|
||||
|
||||
public bool IsNavigationIncluded
|
||||
{
|
||||
get => _isNavigationIncluded;
|
||||
set
|
||||
{
|
||||
_isNavigationIncluded = value;
|
||||
OnPropertyChanged("IsNavigationIncluded");
|
||||
}
|
||||
}
|
||||
public ObservableCollection<DASChannel> DASChannels { get; set; } = new ObservableCollection<DASChannel>();
|
||||
|
||||
private DASChannel _selectedDASChannel;
|
||||
public DASChannel SelectedDASChannel
|
||||
{
|
||||
get => _selectedDASChannel;
|
||||
set => _selectedDASChannel = value;
|
||||
}
|
||||
|
||||
public string EnableOrDisableText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SelectedDASChannel?.Channel == null)
|
||||
{
|
||||
return StringResources.Analog_Enable;
|
||||
}
|
||||
return SelectedDASChannel.Channel.Disabled
|
||||
? StringResources.Analog_Enable
|
||||
: StringResources.Analog_Disable;
|
||||
}
|
||||
}
|
||||
#endregion Properties
|
||||
|
||||
#region Commands
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,861 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using DTS.Common.Events;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS.ReadFile;
|
||||
using Prism.Events;
|
||||
using Unity;
|
||||
using DTS.Common.Interactivity;
|
||||
using Prism.Regions;
|
||||
using Prism.Commands;
|
||||
using TTSImport.Model;
|
||||
using TTSImport.Resources;
|
||||
using Application = System.Windows.Application;
|
||||
|
||||
namespace TTSImport
|
||||
{
|
||||
/// <summary>
|
||||
/// this class handles Level Trigger edit/create functionality
|
||||
/// </summary>
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class EditFileViewModel : IEditFileViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The Hardware Scan view
|
||||
/// </summary>
|
||||
public IEditFileView View { get; set; }
|
||||
private const string DOUBLEUPTO15 = "0.###############"; //Write up to 15 decimal places to .csv
|
||||
private const string STRINGWRITEFORMAT_CHANNELRANGE = DOUBLEUPTO15;
|
||||
private const string STRINGWRITEFORMAT_SENSITIVITY = DOUBLEUPTO15;
|
||||
private const string STRINGWRITEFORMAT_CAPACITY = DOUBLEUPTO15;
|
||||
|
||||
private IEventAggregator _eventAggregator { get; }
|
||||
private IRegionManager _regionManager;
|
||||
private IUnityContainer UnityContainer { get; }
|
||||
|
||||
public InteractionRequest<Notification> NotificationRequest { get; }
|
||||
public InteractionRequest<Confirmation> ConfirmationRequest { get; }
|
||||
|
||||
///<summary>
|
||||
///Occurs when a property value changes.
|
||||
///</summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
public void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
switch (propertyName)
|
||||
{
|
||||
case "TestName":
|
||||
ChangeValidationIsNeeded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#region constructors and initializers
|
||||
/// <summary>
|
||||
/// Creates a new instance of the TechnologyDomainEditViewModel.
|
||||
/// </summary>
|
||||
/// <param name="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 EditFileViewModel(EditFileView view, IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator, IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
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);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// filters the available sensors from the db by the given text
|
||||
/// we keep two lists, allchannels and SystemSensors
|
||||
/// SystemSensors only holds those that aren't in use and are
|
||||
/// available
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public void Search(string text)
|
||||
{
|
||||
SystemSensors.Clear();
|
||||
//build a list of sensors already used to exclude those sensors
|
||||
var hash = new HashSet<string>();
|
||||
foreach (var ch in RequiredChannels)
|
||||
{
|
||||
hash.Add(ch.SensorSerialNumber);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
//nothing to sort
|
||||
foreach (var s in _allChannels)
|
||||
{
|
||||
if (!hash.Contains(s.SensorSerialNumber))
|
||||
{
|
||||
SystemSensors.Add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
text = text.ToLower();
|
||||
foreach (var s in _allChannels)
|
||||
{
|
||||
if (s.SensorSerialNumber.ToLower().Contains(text))
|
||||
{
|
||||
if (!hash.Contains(s.SensorSerialNumber))
|
||||
{
|
||||
SystemSensors.Add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void OnReadFileFinished(ReadFileStatusArg statusArg)
|
||||
{
|
||||
if (!Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
OnReadFileFinished(statusArg);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
if (!statusArg.Status) return;
|
||||
_setup = statusArg.TTSSetup;
|
||||
}
|
||||
|
||||
public void InitializeView()
|
||||
{
|
||||
if (_setup == null) return;
|
||||
|
||||
//turn off Change Validation and remove old channels/sensors, if any
|
||||
ChangeValidationIsNeeded = false;
|
||||
RequiredChannels.Clear();
|
||||
SystemSensors.Clear();
|
||||
//will turn Change Validation back on
|
||||
TestName = _setup.TestId;
|
||||
SaveFileEnabled = false;
|
||||
|
||||
var requiredChannels = new ObservableCollection<ITTSChannelRecord>();
|
||||
_allChannels.Clear();
|
||||
|
||||
foreach (var channelRecord in _setup.Channels)
|
||||
{
|
||||
if (channelRecord.IsDigitalOutput) { continue; }
|
||||
if (channelRecord.ChannelCode != TTSChannelRecord.NONE)
|
||||
{
|
||||
//isn't a "reserved sensor"
|
||||
var requiredChannel = channelRecord.Copy();
|
||||
requiredChannel.Parent = this;
|
||||
requiredChannels.Add(requiredChannel);
|
||||
}
|
||||
}
|
||||
foreach (var sensor in DTS.SensorDB.SensorsCollection.SensorsList.GetAllSensors(false))
|
||||
{
|
||||
if (sensor.IsDigitalOutput()) { continue; }
|
||||
if (sensor.IsTestSpecificSquib) { continue; }
|
||||
if (sensor.IsTestSpecificDigitalIn) { continue; }
|
||||
_allChannels.Add(new TTSChannelRecord(sensor) { Parent = this });
|
||||
}
|
||||
RequiredChannels = requiredChannels;
|
||||
Search(_searchText);
|
||||
ValidateChannelCodes();
|
||||
ValidateJCodes();
|
||||
_originalHash = GenerateHash();
|
||||
NumChannelsAndSensors = string.Format(StringResources.NumChannelsAndSensors, RequiredChannels.Count, SystemSensors.Count);
|
||||
SaveFileEnabled = false;
|
||||
}
|
||||
|
||||
private string GenerateHash()
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
foreach (var rc in RequiredChannels)
|
||||
{
|
||||
bytes.AddRange(rc.GetBytes());
|
||||
}
|
||||
foreach (var rs in SystemSensors)
|
||||
{
|
||||
bytes.AddRange(rs.GetBytes());
|
||||
}
|
||||
|
||||
var sha = new SHA256Managed();
|
||||
var hash = sha.ComputeHash(bytes.ToArray());
|
||||
return BitConverter.ToString(hash).Replace("-", string.Empty);
|
||||
}
|
||||
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()
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// this is the list of all channels, this is a superset while SystemSensors is the subset
|
||||
/// this contains some records we don't want
|
||||
/// </summary>
|
||||
private readonly List<ITTSChannelRecord> _allChannels = new List<ITTSChannelRecord>();
|
||||
/// <summary>
|
||||
/// this is the current text the user is search for in available channels/sensors
|
||||
/// </summary>
|
||||
private readonly string _searchText = "";
|
||||
private ITTSSetup _setup;
|
||||
public bool ChangeValidationIsNeeded { get; set; }
|
||||
public bool IsDirty { get; private set; }
|
||||
private bool _isBusy;
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set
|
||||
{ _isBusy = value; OnPropertyChanged("IsBusy"); }
|
||||
}
|
||||
|
||||
private bool _isMenuIncluded;
|
||||
|
||||
public bool IsMenuIncluded
|
||||
{
|
||||
get => _isMenuIncluded;
|
||||
set
|
||||
{ _isMenuIncluded = value; OnPropertyChanged("IsMenuIncluded"); }
|
||||
}
|
||||
|
||||
private bool _isNavigationIncluded;
|
||||
|
||||
public bool IsNavigationIncluded
|
||||
{
|
||||
get => _isNavigationIncluded;
|
||||
set
|
||||
{ _isNavigationIncluded = value; OnPropertyChanged("IsNavigationIncluded"); }
|
||||
}
|
||||
|
||||
private bool _isTestNameValid;
|
||||
public bool IsTestNameValid { get => _isTestNameValid; set { _isTestNameValid = value; OnPropertyChanged("IsTestNameValid"); } }
|
||||
|
||||
private string _testName = string.Empty;
|
||||
public string TestName
|
||||
{
|
||||
get => _testName;
|
||||
set
|
||||
{
|
||||
_testName = value;
|
||||
IsTestNameValid = !string.IsNullOrWhiteSpace(value);
|
||||
OnPropertyChanged("TestName");
|
||||
}
|
||||
}
|
||||
private DelegateCommand<string> _testNameLostFocus;
|
||||
public DelegateCommand<string> TestNameLostFocus => _testNameLostFocus ?? (_testNameLostFocus = new DelegateCommand<string>(TestNameLostFocusMethod));
|
||||
public void TestNameLostFocusMethod(string code)
|
||||
{
|
||||
if (!ChangeValidationIsNeeded) return;
|
||||
ValidateChange();
|
||||
}
|
||||
private bool _saveFileEnabled;
|
||||
public bool SaveFileEnabled
|
||||
{
|
||||
get => _saveFileEnabled;
|
||||
set
|
||||
{
|
||||
_saveFileEnabled = value;
|
||||
OnPropertyChanged("SaveFileEnabled");
|
||||
|
||||
EnableOrDisableButtons();
|
||||
}
|
||||
}
|
||||
|
||||
private string _numChannelsAndSensors = string.Empty;
|
||||
public string NumChannelsAndSensors
|
||||
{
|
||||
get => _numChannelsAndSensors;
|
||||
set { _numChannelsAndSensors = value; OnPropertyChanged("NumChannelsAndSensors"); }
|
||||
}
|
||||
|
||||
private bool _replaceEnabled;
|
||||
public bool ReplaceEnabled
|
||||
{
|
||||
get => _replaceEnabled;
|
||||
set { _replaceEnabled = value; OnPropertyChanged("ReplaceEnabled"); }
|
||||
}
|
||||
|
||||
private bool _addCodeEnabled;
|
||||
public bool AddCodeEnabled
|
||||
{
|
||||
get => _addCodeEnabled;
|
||||
set { _addCodeEnabled = value; OnPropertyChanged("AddCodeEnabled"); }
|
||||
}
|
||||
|
||||
private bool _deleteCodeEnabled;
|
||||
public bool DeleteCodeEnabled
|
||||
{
|
||||
get => _deleteCodeEnabled;
|
||||
set { _deleteCodeEnabled = value; OnPropertyChanged("DeleteCodeEnabled"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stores a hash representing the state of channels at the time they were read in
|
||||
/// this is used to determine if any changes of significance were made in edit file
|
||||
/// </summary>
|
||||
private string _originalHash;
|
||||
|
||||
private ObservableCollection<ITTSChannelRecord> _requiredChannels = new ObservableCollection<ITTSChannelRecord>();
|
||||
public ObservableCollection<ITTSChannelRecord> RequiredChannels
|
||||
{
|
||||
get => _requiredChannels;
|
||||
set
|
||||
{ _requiredChannels = value; OnPropertyChanged("RequiredChannels"); }
|
||||
}
|
||||
private ObservableCollection<ITTSChannelRecord> _systemSensors = new ObservableCollection<ITTSChannelRecord>();
|
||||
public ObservableCollection<ITTSChannelRecord> SystemSensors
|
||||
{
|
||||
get => _systemSensors;
|
||||
set
|
||||
{ _systemSensors = value; OnPropertyChanged("SystemSensors"); }
|
||||
}
|
||||
|
||||
private ITTSChannelRecord _selectedRequiredChannel;
|
||||
public ITTSChannelRecord SelectedRequiredChannel
|
||||
{
|
||||
get => _selectedRequiredChannel;
|
||||
set
|
||||
{
|
||||
_selectedRequiredChannel = value; OnPropertyChanged("SelectedRequiredChannel");
|
||||
|
||||
EnableOrDisableButtons();
|
||||
}
|
||||
}
|
||||
|
||||
private ITTSChannelRecord _selectedSystemSensor;
|
||||
public ITTSChannelRecord SelectedSystemSensor
|
||||
{
|
||||
get => _selectedSystemSensor;
|
||||
set
|
||||
{
|
||||
_selectedSystemSensor = value;
|
||||
OnPropertyChanged("SelectedSystemSensor");
|
||||
EnableOrDisableButtons();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnableOrDisableButtons()
|
||||
{
|
||||
if (null == _selectedRequiredChannel || null == _selectedSystemSensor) { ReplaceEnabled = false; }
|
||||
else if (_selectedRequiredChannel.IsDigitalInput)
|
||||
{
|
||||
ReplaceEnabled = _selectedSystemSensor.IsDigitalInput;
|
||||
}
|
||||
else if (_selectedRequiredChannel.IsDigitalOutput)
|
||||
{
|
||||
ReplaceEnabled = _selectedSystemSensor.IsDigitalOutput;
|
||||
}
|
||||
else if (_selectedRequiredChannel.IsSquib)
|
||||
{
|
||||
ReplaceEnabled = _selectedSystemSensor.IsSquib;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplaceEnabled = !(_selectedSystemSensor.IsSquib
|
||||
|| _selectedSystemSensor.IsDigitalInput ||
|
||||
_selectedSystemSensor.IsDigitalOutput);
|
||||
}
|
||||
|
||||
|
||||
AddCodeEnabled = _selectedSystemSensor != null;
|
||||
DeleteCodeEnabled = _selectedRequiredChannel != null;
|
||||
}
|
||||
#endregion Properties
|
||||
|
||||
#region Commands
|
||||
/// <summary>
|
||||
/// Browse to import file
|
||||
/// </summary>
|
||||
private DelegateCommand _saveFileClicked;
|
||||
public DelegateCommand SaveFileClicked => _saveFileClicked ?? (_saveFileClicked = new DelegateCommand(SaveFileMethod));
|
||||
public void SaveFileMethod()
|
||||
{
|
||||
using (var sfd = new System.Windows.Forms.SaveFileDialog())
|
||||
{
|
||||
var fullFilePath = Path.GetFullPath(_setup.OriginalImportFile);
|
||||
sfd.Filter = @"TTS import (*.csv)|*.csv";
|
||||
sfd.RestoreDirectory = true;
|
||||
sfd.FilterIndex = 0;
|
||||
var fi = new FileInfo(fullFilePath);
|
||||
|
||||
sfd.FileName = fi.Name;
|
||||
|
||||
if (fi.DirectoryName != null) sfd.InitialDirectory = Path.GetFullPath(fi.DirectoryName);
|
||||
var result = sfd.ShowDialog();
|
||||
if (result != System.Windows.Forms.DialogResult.OK) return;
|
||||
fullFilePath = sfd.FileName;
|
||||
|
||||
var csv = new StringBuilder();
|
||||
|
||||
var newRow = _setup.Line1;
|
||||
csv.Append(newRow + Environment.NewLine);
|
||||
|
||||
newRow = _setup.Line2;
|
||||
_setup.TestId = TestName;
|
||||
if (_setup.Line2 != null)
|
||||
{
|
||||
var fields = _setup.Line2.Split(',');
|
||||
var newLine2 = fields.Where((t, index) => index > 0).Aggregate(_setup.TestId, (current, t) => current + "," + t);
|
||||
newRow = newLine2;
|
||||
}
|
||||
csv.Append(newRow + Environment.NewLine);
|
||||
|
||||
newRow = _setup.Line3;
|
||||
csv.Append(newRow + Environment.NewLine);
|
||||
|
||||
newRow = _setup.Line4;
|
||||
csv.Append(newRow + Environment.NewLine);
|
||||
|
||||
//Order the required channels first
|
||||
var channelList = RequiredChannels.ToList();
|
||||
|
||||
_setup.Channels = channelList.ToArray();
|
||||
|
||||
foreach (var channel in _setup.Channels)
|
||||
{
|
||||
var channelNumber = channel.ChannelNumber.ToString();
|
||||
var channelCode = channel.ChannelCode;
|
||||
var channelRange = channel.IsSquib ? channel.SquibFireDelayMs.ToString(STRINGWRITEFORMAT_CHANNELRANGE) : channel.ChannelRange.ToString(STRINGWRITEFORMAT_CHANNELRANGE); //Write up to 15 decimal places to .csv
|
||||
var channelFilter = channel.IsSquib ? channel.SquibFireDurationMs.ToString(STRINGWRITEFORMAT_CHANNELRANGE) : channel.ChannelFilterHz.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
if (channel.ChannelCode == TTSChannelRecord.NONE)
|
||||
{
|
||||
//Blank the following fields from reserved sensors
|
||||
channelNumber = "";
|
||||
channelCode = "";
|
||||
channelRange = "";
|
||||
channelFilter = "";
|
||||
}
|
||||
|
||||
var sensorSensitivity = channel.SensorSensitivity.ToString(STRINGWRITEFORMAT_SENSITIVITY); //Write up to 15 decimal places to .csv
|
||||
var sensorExcitationVolts = channel.SensorExcitationVolts.ToString("0.#");
|
||||
var sensorCapacity = channel.SensorCapacity.ToString(STRINGWRITEFORMAT_CAPACITY); //Write up to 15 decimal places to .csv
|
||||
var sensorPolarity = channel.SensorPolarity ? "+" : "-";
|
||||
|
||||
//Only FullBridge and HalfBridge contain a space
|
||||
string channelType;
|
||||
switch (channel.ChannelType)
|
||||
{
|
||||
case DTS.Common.Enums.TTS.ToyotaBridgeType.FullBridge:
|
||||
channelType = "Full Bridge";
|
||||
break;
|
||||
case DTS.Common.Enums.TTS.ToyotaBridgeType.HalfBridge:
|
||||
channelType = "Half Bridge";
|
||||
break;
|
||||
default:
|
||||
channelType = channel.ChannelType.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
var proportionalToExcitation = channel.ProportionalToExcitation ? "mv/V/EU" : "";
|
||||
var bridgeResistance = channel.BridgeResistance.ToString("0.#");
|
||||
var initialOffsetVoltage = channel.InitialOffsetVoltage.ToString("0.#");
|
||||
var initialOffsetVoltageTolerance = channel.InitialOffsetVoltageTolerance.ToString("0.#");
|
||||
var removeOffset = channel.RemoveOffset ? "1" : "0";
|
||||
var zeroMethod = ((int)channel.ZeroMethod).ToString();
|
||||
var initialEUInMv = double.IsNaN(channel.InitialEUInMV) ? "" : channel.InitialEUInMV.ToString("0.##");
|
||||
var initialEUInEU = double.IsNaN(channel.InitialEUInEU) ? "" : channel.InitialEUInEU.ToString("0.##");
|
||||
var iRTraccExponent = double.IsNaN(channel.IRTraccExponent) ? "" : channel.IRTraccExponent.ToString("0.##");
|
||||
var polynomialConstant = double.IsNaN(channel.PolynomialConstant) ? "" : channel.PolynomialConstant.ToString("0.##");
|
||||
var polynomialCoefficientC = double.IsNaN(channel.PolynomialCoefficientC) ? "" : channel.PolynomialCoefficientC.ToString("0.##");
|
||||
var polynomialCoefficentB = double.IsNaN(channel.PolynomialCoefficentB) ? "" : channel.PolynomialCoefficentB.ToString("0.##");
|
||||
var polynomialCoefficientA = double.IsNaN(channel.PolynomialCoefficientA) ? "" : channel.PolynomialCoefficientA.ToString("0.##");
|
||||
var polynomialCoefficientAlpha = double.IsNaN(channel.PolynomialCoefficientAlpha) ? "" : channel.PolynomialCoefficientAlpha.ToString("0.##");
|
||||
var diagnosticsMode = channel.DiagnosticsMode ? TTSChannelRecord.DIAGNOSTICSMODE : "";
|
||||
|
||||
if (channel.IsDigitalInput)
|
||||
{
|
||||
channelRange = string.Empty;
|
||||
channelFilter = string.Empty;
|
||||
}
|
||||
|
||||
if (channel.IsDigitalOutput)
|
||||
{
|
||||
//14708 Squib appears to be unassigned when using edit file during TTS import
|
||||
//I changed this to be consistent with the reader, which only reads the first 3
|
||||
//fields for digital outputs
|
||||
newRow = $"{channelNumber},{channelCode},{channel.JCodeOrDescription}{Environment.NewLine}";
|
||||
}
|
||||
|
||||
if (channel.IsDigitalInput || channel.IsSquib)
|
||||
{
|
||||
//14708 Squib appears to be unassigned when using edit file during TTS import
|
||||
//don't wipe out EID or serial number, these are valid fields for these sensors
|
||||
//isocode and description should be valid too? as well as channel description?
|
||||
//note that the import reads up to the serial number, so no point it writing more
|
||||
newRow =
|
||||
$"{channelNumber},{channelCode},{channel.JCodeOrDescription},{channelRange},{channelFilter},{channel.SensorEID},{channel.SensorSerialNumber}{Environment.NewLine}";
|
||||
}
|
||||
else
|
||||
{
|
||||
newRow = $"{channelNumber},{channelCode},{channel.JCodeOrDescription},{channelRange},{channelFilter},{channel.SensorEID},{channel.SensorSerialNumber},{sensorSensitivity},{sensorExcitationVolts},{sensorCapacity},{channel.SensorEU}," +
|
||||
$"{sensorPolarity},{channelType},{channel.Description},{proportionalToExcitation},{bridgeResistance},{initialOffsetVoltage},{initialOffsetVoltageTolerance},{removeOffset},{zeroMethod},{channel.CableMultiplier.ToString(System.Globalization.CultureInfo.InvariantCulture)}," +
|
||||
$"{initialEUInMv},{initialEUInEU},{iRTraccExponent},{polynomialConstant},{polynomialCoefficientC},{polynomialCoefficentB},{polynomialCoefficientA},{polynomialCoefficientAlpha},{channel.ISOCode},{channel.ISODescription},{channel.ISOPolarity},{diagnosticsMode}{Environment.NewLine}";
|
||||
}
|
||||
|
||||
csv.Append(newRow);
|
||||
}
|
||||
|
||||
File.WriteAllText(fullFilePath, csv.ToString(), Encoding.GetEncoding("Shift-JIS"));
|
||||
_setup.OriginalImportFile = fullFilePath;
|
||||
}
|
||||
SaveFileEnabled = false;
|
||||
//Enable future nav steps since changes have been saved
|
||||
_eventAggregator.GetEvent<TTSImportSavedChangesStatusEvent>().Publish(true);
|
||||
_originalHash = GenerateHash();
|
||||
}
|
||||
|
||||
|
||||
#region Replace
|
||||
/// <summary>
|
||||
/// Swap the assignments of a Required Channel and a Reserved Sensor
|
||||
/// </summary>
|
||||
private DelegateCommand _replaceCommand;
|
||||
public DelegateCommand ReplaceCommand => _replaceCommand ?? (_replaceCommand = new DelegateCommand(Replace));
|
||||
private void Replace()
|
||||
{
|
||||
//11574 replace button removed JCode and range and filter from an "add code" channel in edit file
|
||||
//we preserve the original channel settings when doing replace
|
||||
//Initialize a Reserved Sensor that's about to become a Required Channel
|
||||
SelectedSystemSensor.ChannelNumber = SelectedRequiredChannel.ChannelNumber;
|
||||
SelectedSystemSensor.ChannelCode = SelectedRequiredChannel.ChannelCode;
|
||||
SelectedRequiredChannel.ChannelCode = TTSChannelRecord.NONE;
|
||||
SelectedSystemSensor.JCodeOrDescription = SelectedRequiredChannel.JCodeOrDescription;
|
||||
SelectedSystemSensor.ChannelRange = SelectedRequiredChannel.ChannelRange;
|
||||
SelectedSystemSensor.ChannelFilterHz = SelectedRequiredChannel.ChannelFilterHz;
|
||||
|
||||
// Save off the channels that are about to be removed from their old lists
|
||||
var savedSelectedSystemSensor = SelectedSystemSensor;
|
||||
var savedSelectedRequiredChannel = SelectedRequiredChannel;
|
||||
|
||||
//Save the indices of the channels that are about to be removed from their old lists
|
||||
var selectedRequiredChannelIndex = RequiredChannels.IndexOf(SelectedRequiredChannel);
|
||||
var selectedSystemSensorIndex = SystemSensors.IndexOf(SelectedSystemSensor);
|
||||
|
||||
//Remove the channels from their old lists
|
||||
RequiredChannels.Remove(SelectedRequiredChannel);
|
||||
SystemSensors.Remove(SelectedSystemSensor);
|
||||
|
||||
//Insert the saved channels into their new lists
|
||||
RequiredChannels.Insert(selectedRequiredChannelIndex, savedSelectedSystemSensor);
|
||||
SystemSensors.Insert(selectedSystemSensorIndex, savedSelectedRequiredChannel);
|
||||
|
||||
//Re-select the same rows in the tables
|
||||
SelectedRequiredChannel = RequiredChannels[selectedRequiredChannelIndex];
|
||||
SelectedSystemSensor = SystemSensors[selectedSystemSensorIndex];
|
||||
|
||||
UpdateAndValidate();
|
||||
}
|
||||
#endregion Replace
|
||||
|
||||
#region Add Code
|
||||
private DelegateCommand _addCodeCommand;
|
||||
public DelegateCommand AddCodeCommand => _addCodeCommand ?? (_addCodeCommand = new DelegateCommand(AddCode));
|
||||
/// <summary>
|
||||
/// remove a hardware channel assignment(does not remove the channel from the test setup though?)
|
||||
/// </summary>
|
||||
private void AddCode()
|
||||
{
|
||||
AddSystemSensor();
|
||||
UpdateAndValidate();
|
||||
}
|
||||
|
||||
private void AddSystemSensor()
|
||||
{
|
||||
var maxChannelNumber = RequiredChannels.Select(requiredChannel => requiredChannel.ChannelNumber).Concat(new[] { 0 }).Max();
|
||||
SelectedSystemSensor.ChannelNumber = maxChannelNumber + 1;
|
||||
SelectedSystemSensor.ChannelCode = string.Empty;
|
||||
SelectedSystemSensor.JCodeOrDescription = string.Empty;
|
||||
SelectedSystemSensor.ChannelRange = 0;
|
||||
SelectedSystemSensor.ChannelFilterHz = -1;
|
||||
RequiredChannels.Add(SelectedSystemSensor);
|
||||
|
||||
var selectedSystemSensorIndex = SystemSensors.IndexOf(SelectedSystemSensor);
|
||||
SystemSensors.Remove(SelectedSystemSensor);
|
||||
if (SystemSensors.Count > 0)
|
||||
{
|
||||
//If removing from the end of the list, set the previous record to Selected, otherwise set next record to Selected
|
||||
SelectedSystemSensor = selectedSystemSensorIndex == SystemSensors.Count ? SystemSensors[selectedSystemSensorIndex - 1] : SystemSensors[selectedSystemSensorIndex];
|
||||
}
|
||||
}
|
||||
#endregion Add Code
|
||||
|
||||
private DelegateCommand _addSquibCommand;
|
||||
|
||||
public DelegateCommand AddSquibCommand =>
|
||||
_addSquibCommand ?? (_addSquibCommand = new DelegateCommand(AddSquib));
|
||||
private void AddSquib()
|
||||
{
|
||||
var maxChannelNumber = RequiredChannels.Select(requiredChannel => requiredChannel.ChannelNumber).Concat(new[] { 0 }).Max();
|
||||
var channelRecord = new TTSChannelRecord();
|
||||
channelRecord.ChannelNumber = ++maxChannelNumber;
|
||||
channelRecord.IsSquib = true;
|
||||
int maxSquibNumber = 0;
|
||||
foreach (var rc in RequiredChannels)
|
||||
{
|
||||
if (!rc.IsSquib) continue;
|
||||
var channelCode = rc.ChannelCode.Replace("TF", "").Replace("SQ", "");
|
||||
if (int.TryParse(channelCode, out var temp))
|
||||
{
|
||||
maxSquibNumber = Math.Max(temp, maxSquibNumber);
|
||||
}
|
||||
}
|
||||
maxSquibNumber++;
|
||||
channelRecord.ChannelCode = $"SQ{maxSquibNumber}";
|
||||
channelRecord.JCodeOrDescription = string.Empty;
|
||||
channelRecord.LimitDuration = true;
|
||||
channelRecord.ChannelRange = (int)channelRecord.SquibFireDelayMs;
|
||||
channelRecord.SquibFireDurationMs = _setup.DefaultSquibFireDurationMs;
|
||||
channelRecord.ChannelFilterHz = (int)channelRecord.SquibFireDurationMs; //-1;
|
||||
channelRecord.Parent = this;
|
||||
RequiredChannels.Add(channelRecord);
|
||||
Validate();
|
||||
}
|
||||
|
||||
private DelegateCommand _addDICommand;
|
||||
|
||||
public DelegateCommand ADDDICommand =>
|
||||
_addDICommand ?? (_addDICommand = new DelegateCommand(AddDI));
|
||||
|
||||
private void AddDI()
|
||||
{
|
||||
var maxChannelNumber = RequiredChannels.Select(requiredChannel => requiredChannel.ChannelNumber).Concat(new[] { 0 }).Max();
|
||||
var channelRecord = new TTSChannelRecord();
|
||||
channelRecord.ChannelNumber = ++maxChannelNumber;
|
||||
channelRecord.IsDigitalInput = true;
|
||||
int maxDINumber = 0;
|
||||
foreach (var rc in RequiredChannels)
|
||||
{
|
||||
if (!rc.IsDigitalInput) continue;
|
||||
var channelCode = rc.ChannelCode.Replace("DI", "");
|
||||
if (int.TryParse(channelCode, out var temp))
|
||||
{
|
||||
maxDINumber = Math.Max(temp, maxDINumber);
|
||||
}
|
||||
}
|
||||
maxDINumber++;
|
||||
channelRecord.ChannelCode = $"DI{maxDINumber}";
|
||||
channelRecord.JCodeOrDescription = string.Empty;
|
||||
channelRecord.ChannelFilterHz = -1;
|
||||
channelRecord.Parent = this;
|
||||
channelRecord.IsChannelCodeValid = true;
|
||||
RequiredChannels.Add(channelRecord);
|
||||
}
|
||||
|
||||
#region Delete Code
|
||||
private DelegateCommand _deleteCodeCommand;
|
||||
public DelegateCommand DeleteCodeCommand =>
|
||||
_deleteCodeCommand ?? (_deleteCodeCommand = new DelegateCommand(DeleteCode));
|
||||
/// <summary>
|
||||
/// enables or disables a channel in the test.
|
||||
/// </summary>
|
||||
private void DeleteCode()
|
||||
{
|
||||
DeleteRequiredChannel();
|
||||
UpdateAndValidate();
|
||||
}
|
||||
|
||||
private void DeleteRequiredChannel()
|
||||
{
|
||||
if (null == SelectedRequiredChannel) { return; }
|
||||
SelectedRequiredChannel.ChannelCode = TTSChannelRecord.NONE; //So that when it's saved, these fields will be set to empty strings, but if it's re-Added, force user to enter a value.
|
||||
SelectedRequiredChannel.JCodeOrDescription = string.Empty; //In case it's re-Added, force user to enter a value.
|
||||
SelectedRequiredChannel.ChannelRange = 0; //In case it's re-Added, force user to enter a value.
|
||||
SelectedRequiredChannel.ChannelFilterHz = -1; //In case it's re-Added, force user to choose a filter or "None".
|
||||
SelectedRequiredChannel.HardwareChannel = null;
|
||||
SystemSensors.Add(SelectedRequiredChannel);
|
||||
|
||||
var selectedRequiredChannelIndex = RequiredChannels.IndexOf(SelectedRequiredChannel);
|
||||
RequiredChannels.Remove(SelectedRequiredChannel);
|
||||
if (RequiredChannels.Count > 0)
|
||||
{
|
||||
//Ensure the channels don't have any gaps
|
||||
foreach (var channel in RequiredChannels)
|
||||
{
|
||||
channel.ChannelNumber = RequiredChannels.IndexOf(channel) + 1;
|
||||
}
|
||||
|
||||
//If removing from the end of the list, set the previous record to Selected, otherwise set next record to Selected
|
||||
SelectedRequiredChannel = selectedRequiredChannelIndex == RequiredChannels.Count ? RequiredChannels[selectedRequiredChannelIndex - 1] : RequiredChannels[selectedRequiredChannelIndex];
|
||||
}
|
||||
}
|
||||
#endregion Delete Code
|
||||
|
||||
private void UpdateAndValidate()
|
||||
{
|
||||
NumChannelsAndSensors = string.Format(StringResources.NumChannelsAndSensors, RequiredChannels.Count, SystemSensors.Count);
|
||||
EnableOrDisableButtons();
|
||||
ValidateChange();
|
||||
CollectionViewSource.GetDefaultView(RequiredChannels)?.Refresh();
|
||||
}
|
||||
#endregion Commands
|
||||
|
||||
/// <summary>
|
||||
/// Returns True if all fields are valid, False if not
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool Validate()
|
||||
{
|
||||
var duplicates = RequiredChannels.GroupBy(x => x.ChannelCode).Any(g => g.Count() > 1);
|
||||
return !duplicates && IsTestNameValid && RequiredChannels.All(x => x.IsChannelCodeValid /*&& x.IsJCodeValid*/ &&
|
||||
(x.IsRangeValid || x.RangeVisible != System.Windows.Visibility.Visible) &&
|
||||
(x.IsFilterValid || x.FilterVisible != System.Windows.Visibility.Visible));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns true if there are any changes that have not been saved yet
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool HasUnsavedChanges()
|
||||
{
|
||||
var hash = GenerateHash();
|
||||
return hash != _originalHash;
|
||||
}
|
||||
/// <summary>
|
||||
/// If all fields are valid after a change, enables the Save File button and returns True.
|
||||
/// If not all fields are valid after a change, disables the Save File button and returns False.
|
||||
/// Disables all future nav steps (they will be enabled when the Save File button is clicked).
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool ValidateChange(ITTSChannelRecord record = null)
|
||||
{
|
||||
//Enable the Save File button if everything is valid
|
||||
ValidateChannelCodes();
|
||||
ValidateJCodes();
|
||||
bool bValid = Validate();
|
||||
SaveFileEnabled = bValid && HasUnsavedChanges();
|
||||
|
||||
//Prevent calls to Validate if losing focus on an unchanged control, for example
|
||||
ChangeValidationIsNeeded = false;
|
||||
|
||||
//Disable future nav steps since changes have been made but not saved, or are just or are just invalid
|
||||
_eventAggregator.GetEvent<TTSImportSavedChangesStatusEvent>().Publish(!SaveFileEnabled);
|
||||
return SaveFileEnabled;
|
||||
}
|
||||
|
||||
private void ValidateChannelCodes()
|
||||
{
|
||||
var channelCodeToChannel = new Dictionary<string, ITTSChannelRecord>();
|
||||
foreach (var channel in RequiredChannels)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(channel.ChannelCode))
|
||||
{
|
||||
channel.IsChannelCodeValid = false;
|
||||
}
|
||||
else if (channel.ChannelCode == TTSChannelRecord.NONE)
|
||||
{
|
||||
channel.IsChannelCodeValid = false;
|
||||
}
|
||||
else if (channelCodeToChannel.ContainsKey(channel.ChannelCode))
|
||||
{
|
||||
channelCodeToChannel[channel.ChannelCode].IsChannelCodeValid = false;
|
||||
channel.IsChannelCodeValid = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
channelCodeToChannel[channel.ChannelCode] = channel;
|
||||
channel.IsChannelCodeValid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateJCodes()
|
||||
{
|
||||
var channelJCodeToChannel = new Dictionary<string, ITTSChannelRecord>();
|
||||
foreach (var channel in RequiredChannels)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(channel.JCodeOrDescription))
|
||||
{
|
||||
channel.IsJCodeValid = false;
|
||||
}
|
||||
//else if (channelJCodeToChannel.ContainsKey(channel.JCodeOrDescription))
|
||||
//{
|
||||
// channelJCodeToChannel[channel.JCodeOrDescription].IsJCodeValid = false;
|
||||
// channel.IsJCodeValid = false;
|
||||
//}
|
||||
else
|
||||
{
|
||||
channelJCodeToChannel[channel.JCodeOrDescription] = channel;
|
||||
channel.IsJCodeValid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using DTS.Common.DAS.Concepts;
|
||||
using DTS.Common.Enums;
|
||||
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.LevelTrigger;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS.ReadFile;
|
||||
using Prism.Events;
|
||||
using Unity;
|
||||
using DTS.Common.Interactivity;
|
||||
using Prism.Regions;
|
||||
using Prism.Commands;
|
||||
using TTSImport.Model;
|
||||
using TTSImport.Resources;
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace TTSImport
|
||||
{
|
||||
/// <summary>
|
||||
/// this class handles Level Trigger edit/create functionality
|
||||
/// </summary>
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class LevelTriggerViewModel : ILevelTriggerViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The Hardware Scan view
|
||||
/// </summary>
|
||||
public ILevelTriggerView View { get; set; }
|
||||
|
||||
private IEventAggregator _eventAggregator { get; }
|
||||
private IRegionManager _regionManager;
|
||||
private IUnityContainer UnityContainer { get; }
|
||||
|
||||
public InteractionRequest<Notification> NotificationRequest { get; }
|
||||
public InteractionRequest<Confirmation> ConfirmationRequest { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the TechnologyDomainEditViewModel.
|
||||
/// </summary>
|
||||
/// <param name="levelTriggerView">The Level Trigger 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 LevelTriggerViewModel(ILevelTriggerView levelTriggerView, IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator, IUnityContainer unityContainer)
|
||||
{
|
||||
View = levelTriggerView;
|
||||
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<EIDMappingEvent>().Subscribe(OnEIDMapping, ThreadOption.PublisherThread, true);
|
||||
_eventAggregator.GetEvent<TTSImportHardwareScanFinishedEvent>().Subscribe(OnHardwareScanFinished, ThreadOption.PublisherThread, true);
|
||||
_eventAggregator.GetEvent<TTSImportSavedChangesStatusEvent>().Subscribe(OnImportSavedChanges, ThreadOption.PublisherThread, true);
|
||||
}
|
||||
|
||||
#region Methods
|
||||
|
||||
private void OnImportSavedChanges(bool bSaved)
|
||||
{
|
||||
if (bSaved)
|
||||
{
|
||||
UpdateLevelTriggers();
|
||||
}
|
||||
}
|
||||
private void OnHardwareScanFinished(List<IDASHardware> hardware)
|
||||
{
|
||||
_hardware = hardware;
|
||||
}
|
||||
|
||||
private void OnReadFileFinished(ReadFileStatusArg statusArg)
|
||||
{
|
||||
if (statusArg.Status)
|
||||
{
|
||||
_setup = statusArg.TTSSetup;
|
||||
}
|
||||
}
|
||||
|
||||
private IDictionary<string, string> _sensorIdToChannelId = new Dictionary<string, string>();
|
||||
private void OnEIDMapping(IDictionary<string, string> sensorIdToChannelId)
|
||||
{
|
||||
_sensorIdToChannelId = sensorIdToChannelId;
|
||||
UpdateLevelTriggers();
|
||||
}
|
||||
|
||||
private void UpdateLevelTriggers()
|
||||
{
|
||||
if (null == _setup)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (null == _hardware || !_hardware.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var channelIdToDASChannel = new Dictionary<string, DASChannel>();
|
||||
var channelIdToIChannel = new Dictionary<string, IHardwareChannel>();
|
||||
var availableSquibChannels = new Queue<IHardwareChannel>();
|
||||
var availableDigitalInputChannels = new Queue<IHardwareChannel>();
|
||||
foreach (var h in _hardware)
|
||||
{
|
||||
var channels = h.GetIHardwareChannels();
|
||||
foreach (var ch in channels)
|
||||
{
|
||||
//Create a DASChannel so that voltage can be validated below
|
||||
channelIdToDASChannel[ch.GetId()] = new DASChannel(ch);
|
||||
channelIdToIChannel[ch.GetId()] = ch;
|
||||
if (ch.IsDigitalIn)
|
||||
{
|
||||
availableDigitalInputChannels.Enqueue(ch);
|
||||
}
|
||||
else if (ch.IsSquib)
|
||||
{
|
||||
availableSquibChannels.Enqueue(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
//handle pre-assigned channels if any
|
||||
//these are channels which we know the sensor should be assigned to a given hardware channel ahead of time
|
||||
//we do this before the EID check so that EID will override it
|
||||
if (_setup.PreAssignedSensorIdAndHwId.Any())
|
||||
{
|
||||
var remainingPreassigned = new List<Tuple<string, string>>();
|
||||
foreach (var tuple in _setup.PreAssignedSensorIdAndHwId)
|
||||
{
|
||||
var sensorId = tuple.Item1;
|
||||
var hwId = tuple.Item2;
|
||||
if (!channelIdToIChannel.ContainsKey(hwId))
|
||||
{
|
||||
remainingPreassigned.Add(tuple);
|
||||
continue;
|
||||
}
|
||||
var matches = from ch in _setup.Channels where ch.SensorSerialNumber == sensorId select ch;
|
||||
if (matches.Any())
|
||||
{
|
||||
var ittsChannel = matches.First();
|
||||
//do not make the assignment if the user has already assigned the channel to a different hardware channel
|
||||
//this would happen if the user scanned, didn't find the hardware, went to analog channels, assigned the sensor
|
||||
//then finally went to hardware scan and then DID find the hardware. in this case just ignore the preassignment
|
||||
if (null == ittsChannel.HardwareChannel)
|
||||
{
|
||||
var hardwareChannel = channelIdToIChannel[hwId];
|
||||
var excitation = Test.Module.Channel.Sensor.GetExcitationVoltageEnumFromMagnitude(ittsChannel.SensorExcitationVolts);
|
||||
//if we have a hardware assignment, but the excitation is no longer valid, then don't make the assignment
|
||||
if (hardwareChannel.IsAnalog && !hardwareChannel.IsSupportedExcitation(excitation))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ittsChannel.HardwareChannel = hardwareChannel;
|
||||
}
|
||||
}
|
||||
}
|
||||
//if there are any remaining channels, these are hardware assignments that we didn't find hardware for, and we still need
|
||||
//to look for assignment
|
||||
_setup.PreAssignedSensorIdAndHwId = remainingPreassigned.ToArray();
|
||||
}
|
||||
|
||||
foreach (var channel in _setup.Channels)
|
||||
{
|
||||
if (null != channel.HardwareChannel &&
|
||||
!channelIdToIChannel.ContainsKey(channel.HardwareChannel.GetId()))
|
||||
{
|
||||
//hardware no longer present, unassign
|
||||
channel.HardwareChannel = null;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(channel.SensorEID))
|
||||
{
|
||||
//11245 TOM and DI channels in TTS imports without IDs should have sensors assigned automatically
|
||||
if (channel.IsSquib)
|
||||
{
|
||||
if (availableSquibChannels.Any())
|
||||
{
|
||||
var first = availableSquibChannels.Dequeue();
|
||||
channel.HardwareChannel = first;
|
||||
//first channel voltage/initiation, second current, etc
|
||||
var second = availableSquibChannels.Dequeue();
|
||||
}
|
||||
}
|
||||
else if (channel.IsDigitalInput)
|
||||
{
|
||||
if (availableDigitalInputChannels.Any())
|
||||
{
|
||||
var first = availableDigitalInputChannels.Dequeue();
|
||||
channel.HardwareChannel = first;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_sensorIdToChannelId.ContainsKey(channel.SensorEID) ||
|
||||
!channelIdToDASChannel.ContainsKey(_sensorIdToChannelId[channel.SensorEID]) ||
|
||||
!VoltageIsValid(channel, channelIdToDASChannel[_sensorIdToChannelId[channel.SensorEID]])
|
||||
//the below condition I think was missing a not, it appears to be looking for channels that don't have a valid channel code
|
||||
//but DO have a valid match for sensor id ... this is probably a reserve channel where the sensor was still found that it's looking for
|
||||
//15643 Sensors with EID do not remain assigned after import of XML produced from TTS import
|
||||
//changed it again ... this is inhibiting sensors from being assigned, additionally tested it with a sensor without a valid channel code but
|
||||
//a valid eid, and it worked fine
|
||||
|| (_sensorIdToChannelId.ContainsKey(channel.SensorEID) && !channel.IsChannelCodeValid)
|
||||
)
|
||||
{
|
||||
//unassign hardware if one is assigned, sensor id wasn't found or the sensor has an invalid excitation voltage for this hardware
|
||||
//or the channel does have a jcode
|
||||
channel.HardwareChannel = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
var channelId = _sensorIdToChannelId[channel.SensorEID];
|
||||
if (!channelIdToIChannel.ContainsKey(channelId)) continue;
|
||||
channel.HardwareChannel = channelIdToIChannel[channelId];
|
||||
}
|
||||
|
||||
|
||||
foreach (var lt in _setup.LevelTriggers)
|
||||
{
|
||||
lt.Refresh();
|
||||
}
|
||||
OnPropertyChanged("LevelTriggers");
|
||||
_eventAggregator.GetEvent<AssignedChannelsChangedEvent>().Publish(_setup);
|
||||
CollectionViewSource.GetDefaultView(LevelTriggers)?.Refresh();
|
||||
}
|
||||
/// <summary>
|
||||
/// returns True if the sensor's voltage is supported by the hardware channel, False if not
|
||||
/// </summary>
|
||||
/// <param name="selectedRemainingChannel"></param>
|
||||
/// <param name="selectedDASChannel"></param>
|
||||
/// <returns></returns>
|
||||
private bool VoltageIsValid(ITTSChannelRecord selectedRemainingChannel, DASChannel selectedDASChannel)
|
||||
{
|
||||
var voltageEnum = ExcitationVoltageOptions.ExcitationVoltageOption.Undefined;
|
||||
try
|
||||
{
|
||||
voltageEnum = Test.Module.Channel.Sensor.GetExcitationVoltageEnumFromMagnitude(selectedRemainingChannel.SensorExcitationVolts);
|
||||
}
|
||||
catch { } //GetExcitationVoltageEnumFromMagnitude will throw an exception if an invalid voltage is passed to it
|
||||
|
||||
return selectedDASChannel.HardwareChannel.IsSupportedExcitation(voltageEnum);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
private IList<IDASHardware> _hardware;
|
||||
private ITTSSetup _setup;
|
||||
public ILevelTrigger[] LevelTriggers => _setup?.LevelTriggers;
|
||||
public bool IsDirty { get; private set; }
|
||||
private bool _isBusy;
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set
|
||||
{
|
||||
_isBusy = value;
|
||||
OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isMenuIncluded;
|
||||
|
||||
public bool IsMenuIncluded
|
||||
{
|
||||
get => _isMenuIncluded;
|
||||
set
|
||||
{
|
||||
_isMenuIncluded = value;
|
||||
OnPropertyChanged("IsMenuIncluded");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isNavigationIncluded;
|
||||
|
||||
public bool IsNavigationIncluded
|
||||
{
|
||||
get => _isNavigationIncluded;
|
||||
set
|
||||
{
|
||||
_isNavigationIncluded = value;
|
||||
OnPropertyChanged("IsNavigationIncluded");
|
||||
}
|
||||
}
|
||||
|
||||
#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));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,751 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using DTS.Common.Events;
|
||||
using DTS.Common.Interface;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS.ReadFile;
|
||||
using Prism.Events;
|
||||
using Unity;
|
||||
using DTS.Common.Interactivity;
|
||||
using Prism.Regions;
|
||||
using Prism.Commands;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using DTS.Common;
|
||||
using DTS.Common.Interface.DataRecorders;
|
||||
using DTS.Common.Base;
|
||||
using DTS.Common.Classes;
|
||||
using DTS.Common.Enums;
|
||||
using DTS.Common.Events.TTSImport;
|
||||
using TTSImport.Model;
|
||||
using TTSImport.Resources;
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace TTSImport
|
||||
{
|
||||
/// <summary>
|
||||
/// this class handles Summary functionality
|
||||
/// </summary>
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class SummaryViewModel : ISummaryViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The Status and Progress bars
|
||||
/// </summary>
|
||||
public IStatusAndProgressBarView StatusAndProgressBarView { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Summary view
|
||||
/// </summary>
|
||||
public ISummaryView 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="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 SummaryViewModel(ISummaryView view, IRegionManager regionManager, IEventAggregator eventAggregator,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
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<TTSImportTestSetupChangedEvent>().Subscribe(OnTestSetupChanged, 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
|
||||
});
|
||||
}
|
||||
|
||||
public string GetTestId()
|
||||
{
|
||||
return null == View ? "" : View.GetTestId();
|
||||
}
|
||||
private void OnReadFileFinished(ReadFileStatusArg statusArg)
|
||||
{
|
||||
TestSetupImported = false;
|
||||
if (!statusArg.Status) return;
|
||||
_setup = statusArg.TTSSetup;
|
||||
View?.SetTestName(_setup.TestId);
|
||||
OnPropertyChanged(PropertyNames.ImportFileName.ToString());
|
||||
OnPropertyChanged(PropertyNames.TestSetupName.ToString());
|
||||
OnPropertyChanged(PropertyNames.SampleRate.ToString());
|
||||
OnPropertyChanged(PropertyNames.RecordingMode.ToString());
|
||||
OnPropertyChanged(PropertyNames.TestLength.ToString());
|
||||
OnPropertyChanged(PropertyNames.PreTrigger.ToString());
|
||||
OnPropertyChanged(PropertyNames.PostTrigger.ToString());
|
||||
OnPropertyChanged(PropertyNames.ROIStart.ToString());
|
||||
OnPropertyChanged(PropertyNames.ROIEnd.ToString());
|
||||
OnPropertyChanged(PropertyNames.AAF_TDAS.ToString());
|
||||
OnPropertyChanged(PropertyNames.AAF_SLICE.ToString());
|
||||
}
|
||||
private void OnTestSetupChanged(ITTSSetup setup)
|
||||
{
|
||||
TestSetupImported = false;
|
||||
}
|
||||
public void UpdateUI()
|
||||
{
|
||||
SampleRate = _setup.SampleRate;
|
||||
SetAvailableRecordingModes();
|
||||
RecordingMode = _setup.Mode;
|
||||
SetChannelList();
|
||||
TestSetupComplete = !SummaryChannelList.Exists(channelType => (channelType.Unassigned != "0") && (channelType.Unassigned != Resources.StringResources.Table_NA));
|
||||
if (!TestSetupComplete || Array.Exists(_setup.Channels, channel => channel.IsModified) || Array.Exists(_setup.LevelTriggers, levelTrigger => levelTrigger.IsModified))
|
||||
{
|
||||
TestSetupImported = false;
|
||||
}
|
||||
}
|
||||
|
||||
private List<double> _availableSampleRates = new List<double>();
|
||||
public List<double> AvailableSampleRates
|
||||
{
|
||||
get => _availableSampleRates;
|
||||
set
|
||||
{
|
||||
_availableSampleRates = value;
|
||||
OnPropertyChanged("AvailableSampleRates");
|
||||
}
|
||||
}
|
||||
public void SetAvailableSampleRates(int[] values)
|
||||
{
|
||||
AvailableSampleRates.Clear();
|
||||
|
||||
var minSampleRate = (double)values.Min();
|
||||
var maxSampleRate = (double)values.Max();
|
||||
foreach (var channel in _setup.Channels)
|
||||
{
|
||||
if (channel.HardwareChannel == null) continue;
|
||||
minSampleRate = Math.Max(channel.HardwareChannel.GetParentDAS().GetMinSampleRateDouble(), minSampleRate);
|
||||
maxSampleRate = Math.Min(channel.HardwareChannel.GetParentDAS().GetMaxSampleRateDouble(), maxSampleRate);
|
||||
}
|
||||
foreach (var sampleRate in values)
|
||||
{
|
||||
if (sampleRate >= minSampleRate && sampleRate <= maxSampleRate)
|
||||
{
|
||||
AvailableSampleRates.Add(sampleRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string AAF_TDAS => string.Format(StringResources.AAF_TDAS, !_aafExceptions.ContainsKey(SampleRate) ? (SampleRate / 5) : _aafExceptions[SampleRate][0]);
|
||||
public string AAF_SLICE => string.Format(StringResources.AAF_SLICE, !_aafExceptions.ContainsKey(SampleRate) ? (SampleRate / 5) : _aafExceptions[SampleRate][1]);
|
||||
|
||||
private Dictionary<double, List<double>> _aafExceptions = new Dictionary<double, List<double>>();
|
||||
public void SetAAFExceptions(Dictionary<double, List<double>> values)
|
||||
{
|
||||
_aafExceptions.Clear();
|
||||
_aafExceptions = values;
|
||||
}
|
||||
|
||||
public List<RecordingModes> AvailableRecordingModes { get; } = new List<RecordingModes>();
|
||||
|
||||
private void SetAvailableRecordingModes()
|
||||
{
|
||||
AvailableRecordingModes.Clear();
|
||||
|
||||
AvailableRecordingModes.Add(RecordingModes.CircularBuffer);
|
||||
AvailableRecordingModes.Add(RecordingModes.Recorder);
|
||||
if (_setup.AllowAdvancedRecordingModes)
|
||||
{
|
||||
AvailableRecordingModes.Add(RecordingModes.HybridRecorder);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSerializedTestIdValues(string[] values)
|
||||
{
|
||||
View?.UpdateTestIds(values);
|
||||
}
|
||||
/// <summary>
|
||||
/// Fill in the table of channels, including those added after the file was read
|
||||
/// </summary>
|
||||
public void SetChannelList()
|
||||
{
|
||||
const int Assigned = 0;
|
||||
const int Unassigned = 1;
|
||||
|
||||
var analogChannels = new[] { 0, 0 };
|
||||
var tomChannels = new[] { 0, 0 };
|
||||
var digitalInChannels = new[] { 0, 0 };
|
||||
var levelTriggers = new[] { 0, 0 };
|
||||
|
||||
foreach (var channelRecord in _setup.Channels)
|
||||
{
|
||||
if (string.Equals(channelRecord.ChannelCode, TTSChannelRecord.NONE, StringComparison.CurrentCultureIgnoreCase) || channelRecord.Disabled) continue;
|
||||
if (channelRecord.IsSquib)
|
||||
{
|
||||
if (channelRecord.HardwareChannel != null && channelRecord.HardwareChannel.IsSquib)
|
||||
{
|
||||
tomChannels[Assigned] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
tomChannels[Unassigned] += 1;
|
||||
}
|
||||
}
|
||||
else if (channelRecord.IsDigitalInput)
|
||||
{
|
||||
if (channelRecord.HardwareChannel != null && channelRecord.HardwareChannel.IsDigitalIn)
|
||||
{
|
||||
digitalInChannels[Assigned] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
digitalInChannels[Unassigned] += 1;
|
||||
}
|
||||
}
|
||||
else if (!channelRecord.IsDigitalOutput)
|
||||
{
|
||||
if (channelRecord.HardwareChannel != null && channelRecord.HardwareChannel.IsAnalog)
|
||||
{
|
||||
analogChannels[Assigned] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
analogChannels[Unassigned] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var levelTrigger in _setup.LevelTriggers)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(levelTrigger.Code))
|
||||
{
|
||||
levelTriggers[Assigned] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
var temp = new List<SummaryChannel>();
|
||||
var channel = new SummaryChannel
|
||||
{
|
||||
ChannelType = "Analog", //internationalize these
|
||||
Assigned = analogChannels[Assigned],
|
||||
Unassigned = analogChannels[Unassigned].ToString()
|
||||
};
|
||||
temp.Add(channel);
|
||||
|
||||
channel = new SummaryChannel
|
||||
{
|
||||
ChannelType = "TOM",
|
||||
Assigned = tomChannels[Assigned],
|
||||
Unassigned = tomChannels[Unassigned].ToString()
|
||||
};
|
||||
temp.Add(channel);
|
||||
|
||||
channel = new SummaryChannel
|
||||
{
|
||||
ChannelType = "Digital In",
|
||||
Assigned = digitalInChannels[Assigned],
|
||||
Unassigned = digitalInChannels[Unassigned].ToString()
|
||||
};
|
||||
temp.Add(channel);
|
||||
|
||||
channel = new SummaryChannel
|
||||
{
|
||||
ChannelType = "Level Trigger",
|
||||
Assigned = levelTriggers[Assigned],
|
||||
Unassigned = Resources.StringResources.Table_NA
|
||||
};
|
||||
temp.Add(channel);
|
||||
|
||||
SummaryChannelList = temp;
|
||||
}
|
||||
|
||||
private readonly StatusAndProgressBarEventArgs statusAndProgressBarEventArgs = new StatusAndProgressBarEventArgs();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the text, background color, progress value, and/or progress visibility
|
||||
/// </summary>
|
||||
/// <param name="status"></param>
|
||||
/// <param name="error"></param>
|
||||
public void SetStatus(string status, string error = default(string)) //default(string) is null
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case "Waiting": //change this to string resources but use the same one in DataPRO and here???
|
||||
statusAndProgressBarEventArgs.StatusColor = BrushesAndColors.Brush_ApplicationStatus_Waiting.Color;
|
||||
statusAndProgressBarEventArgs.ProgressBarVisibility = Visibility.Collapsed;
|
||||
break;
|
||||
case "Working":
|
||||
statusAndProgressBarEventArgs.StatusColor = BrushesAndColors.Brush_ApplicationStatus_Busy.Color;
|
||||
statusAndProgressBarEventArgs.ProgressValue = 0;
|
||||
statusAndProgressBarEventArgs.ProgressBarVisibility = Visibility.Visible;
|
||||
break;
|
||||
case "Failed":
|
||||
TestSetupImported = false;
|
||||
statusAndProgressBarEventArgs.StatusColor = BrushesAndColors.Brush_ApplicationStatus_Failed.Color;
|
||||
statusAndProgressBarEventArgs.ProgressBarVisibility = Visibility.Collapsed;
|
||||
break;
|
||||
case "Done":
|
||||
TestSetupImported = true;
|
||||
foreach (var ch in _setup.Channels)
|
||||
{
|
||||
ch.IsModified = false;
|
||||
}
|
||||
foreach (var lt in _setup.LevelTriggers)
|
||||
{
|
||||
lt.IsModified = false;
|
||||
}
|
||||
statusAndProgressBarEventArgs.StatusColor = BrushesAndColors.Brush_ApplicationStatus_Complete.Color;
|
||||
statusAndProgressBarEventArgs.ProgressBarVisibility = Visibility.Collapsed;
|
||||
break;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
statusAndProgressBarEventArgs.StatusText = status + " - " + error;
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
#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<SummaryChannel> _summaryChannelList = new List<SummaryChannel>();
|
||||
|
||||
public List<SummaryChannel> SummaryChannelList
|
||||
{
|
||||
get => _summaryChannelList;
|
||||
set
|
||||
{
|
||||
_summaryChannelList = value;
|
||||
OnPropertyChanged("SummaryChannelList");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _testSetupComplete;
|
||||
public bool TestSetupComplete
|
||||
{
|
||||
get => _testSetupComplete;
|
||||
set
|
||||
{
|
||||
_testSetupComplete = value;
|
||||
OnPropertyChanged("ImportEnabled");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _testSetupImported;
|
||||
public bool TestSetupImported
|
||||
{
|
||||
get => _testSetupImported;
|
||||
set
|
||||
{
|
||||
_testSetupImported = value;
|
||||
OnPropertyChanged("RunTestVisible");
|
||||
}
|
||||
}
|
||||
|
||||
public bool ImportEnabled => TestSetupComplete & IsROIValid;
|
||||
|
||||
public Visibility RunTestVisible => TestSetupImported ? Visibility.Visible : Visibility.Hidden;
|
||||
public Visibility SummaryPreTriggerVisibility => RecordingMode == RecordingModes.CircularBuffer ? Visibility.Visible : Visibility.Collapsed;
|
||||
public string PostTriggerOrTestLength => RecordingMode == RecordingModes.CircularBuffer ? StringResources.PostTrigger : StringResources.TestLength;
|
||||
|
||||
private bool _isROIStartValid = true;
|
||||
|
||||
public bool IsROIStartValid
|
||||
{
|
||||
get => _isROIStartValid;
|
||||
set
|
||||
{
|
||||
_isROIStartValid = value;
|
||||
OnPropertyChanged("IsROIStartValid");
|
||||
}
|
||||
}
|
||||
private bool _isROIEndValid = true;
|
||||
|
||||
public bool IsROIEndValid
|
||||
{
|
||||
get => _isROIEndValid;
|
||||
set
|
||||
{
|
||||
_isROIEndValid = value;
|
||||
OnPropertyChanged("IsROIEndValid");
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateROIValid()
|
||||
{
|
||||
if (null == _setup)
|
||||
{
|
||||
IsROIEndValid = true;
|
||||
IsROIStartValid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_setup.ROIEnd < _setup.ROIStart)
|
||||
{
|
||||
IsROIStartValid = false;
|
||||
IsROIEndValid = false;
|
||||
return;
|
||||
}
|
||||
if (RecordingMode == RecordingModes.Recorder)
|
||||
{
|
||||
IsROIStartValid = true;
|
||||
IsROIEndValid = !(_setup.ROIEnd - _setup.ROIStart > _setup.PostTrigger);
|
||||
}
|
||||
else
|
||||
{
|
||||
IsROIEndValid = !(_setup.ROIEnd > _setup.PostTrigger);
|
||||
IsROIStartValid = !(Math.Abs(_setup.ROIStart) > _setup.PreTrigger);
|
||||
}
|
||||
}
|
||||
public bool IsROIValid
|
||||
{
|
||||
get
|
||||
{
|
||||
CalculateROIValid();
|
||||
return IsROIStartValid && IsROIEndValid;
|
||||
}
|
||||
}
|
||||
//=> RecordingMode != RecordingModes.Recorder || !(_setup.ROIEnd - _setup.ROIStart > _setup.PostTrigger);
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// browse to a file to import, should be xml, maybe needs a few other criteria
|
||||
/// </summary>
|
||||
private DelegateCommand _importCommand;
|
||||
|
||||
public DelegateCommand ImportClicked => _importCommand ?? (_importCommand = new DelegateCommand(ImportMethod));
|
||||
|
||||
private void ImportMethod()
|
||||
{
|
||||
_eventAggregator.GetEvent<TTSImportSummaryImportEvent>().Publish(_setup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the test since the Run Test button was clicked
|
||||
/// </summary>
|
||||
private DelegateCommand _runTestCommand;
|
||||
|
||||
public DelegateCommand RunTestClicked =>
|
||||
_runTestCommand ?? (_runTestCommand = new DelegateCommand(RunTestMethod));
|
||||
|
||||
private void RunTestMethod()
|
||||
{
|
||||
SetStatus(Resources.StringResources.ImportTestSetup_PossibleStatus_Working); //use string resource (same both here and where passed)
|
||||
ThreadPool.QueueUserWorkItem(RunTestWorkThread, null);
|
||||
}
|
||||
void RunTestWorkThread(object obj)
|
||||
{
|
||||
_eventAggregator.GetEvent<TTSImportSummaryRunTestEvent>().Publish(_setup);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region properties
|
||||
|
||||
/// <summary>
|
||||
/// all properties that are exposed
|
||||
/// </summary>
|
||||
public enum PropertyNames
|
||||
{
|
||||
ImportFileName,
|
||||
TestSetupName,
|
||||
SampleRate,
|
||||
RecordingMode,
|
||||
TestLength,
|
||||
PreTrigger,
|
||||
PostTrigger,
|
||||
ROIStart,
|
||||
ROIEnd,
|
||||
AAF_TDAS,
|
||||
AAF_SLICE,
|
||||
AllowAdvancedRecordingModes,
|
||||
AllowTSRAIRRecordingModes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// full path to import file
|
||||
/// </summary>
|
||||
public string ImportFileName => _setup?.Filename ?? "";
|
||||
|
||||
public string TestSetupName
|
||||
{
|
||||
get => _setup?.TestId ?? "";
|
||||
set
|
||||
{
|
||||
TestSetupImported = false;
|
||||
_setup.TestId = value;
|
||||
View?.SetTestName(_setup.TestId);
|
||||
}
|
||||
}
|
||||
|
||||
public double SampleRate
|
||||
{
|
||||
get => _setup?.SampleRate ?? 10000;
|
||||
set
|
||||
{
|
||||
if (_setup.SampleRate != value)
|
||||
{
|
||||
TestSetupImported = false;
|
||||
_setup.SampleRate = value;
|
||||
}
|
||||
OnPropertyChanged(PropertyNames.SampleRate.ToString());
|
||||
OnPropertyChanged(PropertyNames.AAF_TDAS.ToString());
|
||||
OnPropertyChanged(PropertyNames.AAF_SLICE.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(PropertyNames sampleRate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public RecordingModes RecordingMode
|
||||
{
|
||||
get => _setup?.Mode ?? RecordingModes.CircularBuffer;
|
||||
set
|
||||
{
|
||||
if (_setup.Mode != value)
|
||||
{
|
||||
TestSetupImported = false;
|
||||
switch (value)
|
||||
{
|
||||
case RecordingModes.CircularBuffer:
|
||||
case RecordingModes.Recorder:
|
||||
case RecordingModes.HybridRecorder:
|
||||
_setup.Mode = value;
|
||||
break;
|
||||
default:
|
||||
_setup.Mode = RecordingModes.CircularBuffer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
OnPropertyChanged("RecordingMode");
|
||||
OnPropertyChanged("SummaryPreTriggerVisibility");
|
||||
OnPropertyChanged("PostTriggerOrTestLength");
|
||||
OnPropertyChanged("IsROIValid");
|
||||
OnPropertyChanged("ImportEnabled");
|
||||
}
|
||||
}
|
||||
|
||||
public string PreTrigger
|
||||
{
|
||||
get => _setup?.PreTrigger.ToString("0.00") ?? "";
|
||||
set
|
||||
{
|
||||
if (!double.TryParse(value, out double preTrigger)) return;
|
||||
//if (preTrigger * -1 > _setup.ROIStart) ROIStart = (preTrigger * -1).ToString("0.00");
|
||||
//if (preTrigger * -1 > _setup.ROIEnd) ROIEnd = (preTrigger * -1).ToString("0.00");
|
||||
TestSetupImported = false;
|
||||
_setup.PreTrigger = preTrigger;
|
||||
OnPropertyChanged("PreTrigger");
|
||||
|
||||
OnPropertyChanged("IsROIValid");
|
||||
OnPropertyChanged("ImportEnabled");
|
||||
}
|
||||
}
|
||||
|
||||
public string PostTrigger
|
||||
{
|
||||
get => _setup?.PostTrigger.ToString("0.00") ?? "";
|
||||
set
|
||||
{
|
||||
if (!double.TryParse(value, out double postTrigger)) return;
|
||||
//if (postTrigger < _setup.ROIStart) ROIStart = postTrigger.ToString("0.00");
|
||||
//if (postTrigger < _setup.ROIEnd) ROIEnd = postTrigger.ToString("0.00");
|
||||
TestSetupImported = false;
|
||||
_setup.PostTrigger = postTrigger;
|
||||
OnPropertyChanged("PostTrigger");
|
||||
|
||||
OnPropertyChanged("IsROIValid");
|
||||
OnPropertyChanged("ImportEnabled");
|
||||
}
|
||||
}
|
||||
|
||||
public string ROIStart
|
||||
{
|
||||
get => _setup?.ROIStart.ToString("0.00") ?? "";
|
||||
set
|
||||
{
|
||||
if (!double.TryParse(value, out double roiStart)) return;
|
||||
if (roiStart < _setup.PreTrigger * -1 || roiStart > _setup.PostTrigger || roiStart > _setup.ROIEnd) return;
|
||||
TestSetupImported = false;
|
||||
_setup.ROIStart = roiStart;
|
||||
OnPropertyChanged("ROIStart");
|
||||
|
||||
OnPropertyChanged("IsROIValid");
|
||||
OnPropertyChanged("ImportEnabled");
|
||||
}
|
||||
}
|
||||
|
||||
public string ROIEnd
|
||||
{
|
||||
get => _setup?.ROIEnd.ToString("0.00") ?? "";
|
||||
set
|
||||
{
|
||||
if (!double.TryParse(value, out double roiEnd)) return;
|
||||
if (roiEnd < _setup.PreTrigger * -1 || roiEnd > _setup.PostTrigger || roiEnd < _setup.ROIStart) return;
|
||||
TestSetupImported = false;
|
||||
_setup.ROIEnd = roiEnd;
|
||||
OnPropertyChanged("ROIEnd");
|
||||
|
||||
OnPropertyChanged("IsROIValid");
|
||||
OnPropertyChanged("ImportEnabled");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
///<summary>
|
||||
///Occurs when a property value changes.
|
||||
///</summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using DTS.Common.Events;
|
||||
using DTS.Common.Events.TTSImport;
|
||||
using DTS.Common.Interface.DataRecorders;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS;
|
||||
using DTS.Common.Interface.TestSetups.Imports.TTS.ReadFile;
|
||||
using Prism.Events;
|
||||
using Unity;
|
||||
using DTS.Common.Interactivity;
|
||||
using Prism.Regions;
|
||||
using Prism.Commands;
|
||||
using TTSImport.Model;
|
||||
using TTSImport.Resources;
|
||||
using DTS.DASLib.Service;
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace TTSImport
|
||||
{
|
||||
/// <summary>
|
||||
/// this class handles Level Trigger edit/create functionality
|
||||
/// </summary>
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class TOMChannelsViewModel : ITOMChannelsViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The Hardware Scan view
|
||||
/// </summary>
|
||||
public ITOMChannelsView View { get; set; }
|
||||
|
||||
private IEventAggregator _eventAggregator { get; }
|
||||
private IRegionManager _regionManager;
|
||||
private IUnityContainer UnityContainer { get; }
|
||||
|
||||
public InteractionRequest<Notification> NotificationRequest { get; }
|
||||
public InteractionRequest<Confirmation> ConfirmationRequest { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the TechnologyDomainEditViewModel.
|
||||
/// </summary>
|
||||
/// <param name="view">The ITOMChannelsView.</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 TOMChannelsViewModel(ITOMChannelsView view, IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator, IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
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<AssignedChannelsChangedEvent>().Subscribe(OnAssignedChannelsChangedEvent,
|
||||
ThreadOption.PublisherThread, true);
|
||||
_eventAggregator.GetEvent<TTSImportHardwareScanFinishedEvent>()
|
||||
.Subscribe(OnHardwareScanComplete, ThreadOption.PublisherThread, true);
|
||||
_eventAggregator.GetEvent<TTSImportReadFileStatusEvent>().Subscribe(OnReadFileFinished, ThreadOption.PublisherThread, true);
|
||||
}
|
||||
|
||||
#region Methods
|
||||
private void OnReadFileFinished(ReadFileStatusArg statusArg)
|
||||
{
|
||||
_setup = statusArg.TTSSetup;
|
||||
_hardware = null;
|
||||
}
|
||||
private void OnHardwareScanComplete(List<IDASHardware> hardware)
|
||||
{
|
||||
_hardware = hardware;
|
||||
}
|
||||
|
||||
private void OnAssignedChannelsChangedEvent(ITTSSetup setup)
|
||||
{
|
||||
if (!Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
OnAssignedChannelsChangedEvent(setup);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
_setup = setup;
|
||||
if (null == _hardware || null == _setup) { return; }
|
||||
var channels = new ObservableCollection<Model.DASChannel>();
|
||||
var remainingChannels = new ObservableCollection<ITTSChannelRecord>();
|
||||
var channelIdToChannel = new Dictionary<string, Model.DASChannel>();
|
||||
foreach (var das in _hardware)
|
||||
{
|
||||
var ichannels = das.GetIHardwareChannels();
|
||||
for (var i = 0; i < ichannels.Length; i += 2)
|
||||
{
|
||||
var ch = ichannels[i];
|
||||
if (!ch.IsSquib) { continue; }
|
||||
var newChannel = new Model.DASChannel(ch);
|
||||
channels.Add(newChannel);
|
||||
channelIdToChannel[ch.GetId()] = newChannel;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var channelRecord in _setup.Channels)
|
||||
{
|
||||
if (channelRecord.IsEmptyRecord)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!channelRecord.IsSquib)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!channelRecord.IsChannelCodeValid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (channelRecord.ChannelCode == TTSChannelRecord.NONE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (null != channelRecord.HardwareChannel)
|
||||
{
|
||||
if (channelIdToChannel.ContainsKey(channelRecord.HardwareChannel.GetId()))
|
||||
{
|
||||
channelIdToChannel[channelRecord.HardwareChannel.GetId()].SetITTSChannelRecord(channelRecord);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
remainingChannels.Add(channelRecord);
|
||||
}
|
||||
}
|
||||
|
||||
DASChannels = channels;
|
||||
RemainingChannels = remainingChannels;
|
||||
OnPropertyChanged("DASChannels");
|
||||
OnPropertyChanged("RemainingChannels");
|
||||
}
|
||||
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()
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
private ITTSSetup _setup;
|
||||
private IList<IDASHardware> _hardware;
|
||||
public bool IsDirty { get; private set; }
|
||||
private bool _isBusy;
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set
|
||||
{
|
||||
_isBusy = value;
|
||||
OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isMenuIncluded;
|
||||
|
||||
public bool IsMenuIncluded
|
||||
{
|
||||
get => _isMenuIncluded;
|
||||
set
|
||||
{
|
||||
_isMenuIncluded = value;
|
||||
OnPropertyChanged("IsMenuIncluded");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isNavigationIncluded;
|
||||
|
||||
public bool IsNavigationIncluded
|
||||
{
|
||||
get => _isNavigationIncluded;
|
||||
set
|
||||
{
|
||||
_isNavigationIncluded = value;
|
||||
OnPropertyChanged("IsNavigationIncluded");
|
||||
}
|
||||
}
|
||||
public bool AssignEnabled { get; set; }
|
||||
public bool RemoveEnabled { get; set; }
|
||||
public bool EnableOrDisableEnabled { get; set; }
|
||||
public ObservableCollection<Model.DASChannel> DASChannels { get; set; } = new ObservableCollection<Model.DASChannel>();
|
||||
public ObservableCollection<ITTSChannelRecord> RemainingChannels { get; set; } =
|
||||
new ObservableCollection<ITTSChannelRecord>();
|
||||
|
||||
private ITTSChannelRecord _selectedRemainingChannel;
|
||||
public ITTSChannelRecord SelectedRemainingChannel
|
||||
{
|
||||
get => _selectedRemainingChannel;
|
||||
set
|
||||
{
|
||||
_selectedRemainingChannel = value;
|
||||
if (null == _selectedRemainingChannel || null == SelectedDASChannel) return;
|
||||
AssignEnabled = true;
|
||||
OnPropertyChanged("AssignEnabled");
|
||||
}
|
||||
}
|
||||
private void DetermineRemoveEnableStatus()
|
||||
{
|
||||
if (_selectedDASChannel?.Channel != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_selectedDASChannel.EID) &&
|
||||
_selectedDASChannel.EID == _selectedDASChannel.Channel.SensorEID)
|
||||
{
|
||||
//can only be replaced, can't be removed
|
||||
RemoveEnabled = false;
|
||||
}
|
||||
RemoveEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveEnabled = false;
|
||||
}
|
||||
OnPropertyChanged("RemoveEnabled");
|
||||
AssignEnabled = null != _selectedDASChannel && null != _selectedRemainingChannel;
|
||||
OnPropertyChanged("AssignEnabled");
|
||||
EnableOrDisableEnabled = _selectedDASChannel?.Channel != null;
|
||||
OnPropertyChanged("EnableOrDisableEnabled");
|
||||
OnPropertyChanged("EnableOrDisableText");
|
||||
}
|
||||
private Model.DASChannel _selectedDASChannel;
|
||||
public Model.DASChannel SelectedDASChannel
|
||||
{
|
||||
get => _selectedDASChannel;
|
||||
set
|
||||
{
|
||||
_selectedDASChannel = value;
|
||||
DetermineRemoveEnableStatus();
|
||||
}
|
||||
}
|
||||
|
||||
public string EnableOrDisableText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SelectedDASChannel?.Channel == null)
|
||||
{
|
||||
return StringResources.Analog_Enable;
|
||||
}
|
||||
return SelectedDASChannel.Channel.Disabled
|
||||
? StringResources.Analog_Enable
|
||||
: StringResources.Analog_Disable;
|
||||
}
|
||||
}
|
||||
#endregion Properties
|
||||
|
||||
#region Commands
|
||||
#region assign
|
||||
/// <summary>
|
||||
/// Assign a channel code to a channel
|
||||
/// </summary>
|
||||
private DelegateCommand _assignCommand;
|
||||
public DelegateCommand AssignCommand => _assignCommand ?? (_assignCommand = new DelegateCommand(Assign));
|
||||
private void Assign()
|
||||
{
|
||||
if (SelectedRemainingChannel == null) { return; }
|
||||
|
||||
_eventAggregator.GetEvent<TTSImportTestSetupChangedEvent>().Publish(_setup);
|
||||
//BEFORE we go any further, check the state of sensor ids
|
||||
//if the channel has a sensor id AND there's a sensor on the channel with the same id
|
||||
//then prompt on replacing the id
|
||||
//IF the channel has a sensor id and there's no sensor on the channel BUT the new sensor has a different id
|
||||
//then prompt on replacing the id
|
||||
var bReplacingID = false;
|
||||
if (!string.IsNullOrWhiteSpace(SelectedDASChannel.EID))
|
||||
{
|
||||
if (null != SelectedRemainingChannel && SelectedRemainingChannel.SensorEID != SelectedDASChannel.EID)
|
||||
{
|
||||
bReplacingID = true;
|
||||
}
|
||||
//if existing channel has this ID, we will need to clear out the old id and assign a new one...
|
||||
if (null != SelectedDASChannel.Channel && SelectedDASChannel.Channel.SensorEID == SelectedDASChannel.EID)
|
||||
{
|
||||
bReplacingID = true;
|
||||
}
|
||||
}
|
||||
if (bReplacingID)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var dialogResult = MessageBox.Show(StringResources.AssignSensorPrompt, StringResources.UserFeedbackRequired, MessageBoxButton.YesNo);
|
||||
if (dialogResult == MessageBoxResult.Yes)
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(AssignWork));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
AssignWork();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// assigns a channel record to a physical channel
|
||||
/// </summary>
|
||||
private void AssignWork()
|
||||
{
|
||||
if (null != SelectedDASChannel.Channel)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SelectedDASChannel.EID))
|
||||
{
|
||||
SelectedDASChannel.Channel.SensorEID = "";
|
||||
}
|
||||
RemainingChannels.Add(SelectedDASChannel.Channel);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(SelectedDASChannel.EID))
|
||||
{
|
||||
SelectedRemainingChannel.SensorEID = SelectedDASChannel.EID;
|
||||
}
|
||||
|
||||
SelectedDASChannel.SetITTSChannelRecord(SelectedRemainingChannel);
|
||||
|
||||
var channel = SelectedRemainingChannel;
|
||||
var index = RemainingChannels.IndexOf(channel);
|
||||
SelectedRemainingChannel = null;
|
||||
RemainingChannels.Remove(channel);
|
||||
if (index < RemainingChannels.Count)
|
||||
{
|
||||
SelectedRemainingChannel = RemainingChannels[index];
|
||||
OnPropertyChanged("SelectedRemainingChannel");
|
||||
}
|
||||
else if (RemainingChannels.Count > 0)
|
||||
{
|
||||
SelectedRemainingChannel = RemainingChannels[index - 1];
|
||||
OnPropertyChanged("SelectedRemainingChannel");
|
||||
}
|
||||
CollectionViewSource.GetDefaultView(DASChannels)?.Refresh();
|
||||
index = DASChannels.IndexOf(SelectedDASChannel);
|
||||
for (var i = index; i < DASChannels.Count; i++)
|
||||
{
|
||||
var dasChannel = DASChannels[i];
|
||||
if (null != dasChannel.Channel) { continue; }
|
||||
SelectedDASChannel = dasChannel;
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
//didn't find a match, start from the beginning?
|
||||
for (var i = 0; i < index; i++)
|
||||
{
|
||||
var dasChannel = DASChannels[i];
|
||||
if (null != dasChannel.Channel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SelectedDASChannel = dasChannel;
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
//if we get here there's no new channel to go to, but we need to set the remove/enable/disable button status
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
DetermineRemoveEnableStatus();
|
||||
}
|
||||
#endregion
|
||||
#region remove
|
||||
private DelegateCommand _removeCommand;
|
||||
public DelegateCommand RemoveCommand => _removeCommand ?? (_removeCommand = new DelegateCommand(Remove));
|
||||
/// <summary>
|
||||
/// remove a hardware channel assignment (does not remove the channel from the test setup though?)
|
||||
/// </summary>
|
||||
private void Remove()
|
||||
{
|
||||
if (null == SelectedDASChannel || null == SelectedDASChannel.Channel) { return; }
|
||||
_eventAggregator.GetEvent<TTSImportTestSetupChangedEvent>().Publish(_setup);
|
||||
RemainingChannels.Add(SelectedDASChannel.Channel);
|
||||
SelectedDASChannel.SetITTSChannelRecord(null);
|
||||
CollectionViewSource.GetDefaultView(DASChannels)?.Refresh();
|
||||
var index = DASChannels.IndexOf(SelectedDASChannel);
|
||||
for (var i = index; i < DASChannels.Count; i++)
|
||||
{
|
||||
if (null == DASChannels[i].Channel) { continue; }
|
||||
SelectedDASChannel = DASChannels[i];
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < index; i++)
|
||||
{
|
||||
if (null == DASChannels[i].Channel) { continue; }
|
||||
SelectedDASChannel = DASChannels[i];
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
return;
|
||||
}
|
||||
//if we get here there's no new channel to go to, but we need to set the remove/enable/disable button status
|
||||
OnPropertyChanged("SelectedDASChannel");
|
||||
DetermineRemoveEnableStatus();
|
||||
}
|
||||
#endregion
|
||||
#region enableordisable
|
||||
private DelegateCommand _enableOrDisableCommand;
|
||||
public DelegateCommand EnableOrDisableCommand =>
|
||||
_enableOrDisableCommand ?? (_enableOrDisableCommand = new DelegateCommand(EnableOrDisable));
|
||||
/// <summary>
|
||||
/// enables or disables a channel in the test.
|
||||
/// </summary>
|
||||
private void EnableOrDisable()
|
||||
{
|
||||
if (SelectedDASChannel?.Channel == null) { return; }
|
||||
SelectedDASChannel.Channel.Disabled = !SelectedDASChannel.Channel.Disabled;
|
||||
SelectedDASChannel.Disabled = SelectedDASChannel.Channel.Disabled;
|
||||
|
||||
OnPropertyChanged("EnableOrDisableText");
|
||||
_eventAggregator.GetEvent<TTSImportTestSetupChangedEvent>().Publish(_setup);
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user