--- source_files: - DataPRO/Modules/TestSetups/Imports/TTS/ViewModel/DigitalOutputChannelsViewModel.cs - DataPRO/Modules/TestSetups/Imports/TTS/ViewModel/LevelTriggerViewModel.cs - DataPRO/Modules/TestSetups/Imports/TTS/ViewModel/DigitalInputChannelsViewModel.cs - DataPRO/Modules/TestSetups/Imports/TTS/ViewModel/TOMChannelsViewModel.cs generated_at: "2026-04-17T15:49:35.967692+00:00" model: "zai-org/GLM-5-FP8" schema_version: 1 sha256: "038dac49bea7aac0" --- # TTS Import ViewModels Documentation ## 1. Purpose This module provides four ViewModel classes for managing channel configuration within a TTS (Test Setup) Import workflow. Each ViewModel handles a specific channel type—Digital Output, Digital Input, Level Triggers, and TOM (Squib) channels—facilitating the mapping between physical hardware channels (`IDASHardware`/`IHardwareChannel`) and logical channel records (`ITTSChannelRecord`) in test setups. The ViewModels coordinate via `IEventAggregator` to respond to hardware scan results, file imports, and channel assignment changes, providing UI binding properties and commands for user interaction. --- ## 2. Public Interface ### DigitalOutputChannelsViewModel **Implements:** `IDigitalOutputChannelsViewModel` | Member | Signature | Description | |--------|-----------|-------------| | `View` | `public IDigitalOutputChannelsView View { get; set; }` | The associated view instance; DataContext is set to `this` in constructor. | | `NotificationRequest` | `public InteractionRequest NotificationRequest { get; }` | Used to raise notification dialogs. | | `ConfirmationRequest` | `public InteractionRequest ConfirmationRequest { get; }` | Used to raise confirmation dialogs. | | `DASChannels` | `public ObservableCollection DASChannels { get; set; }` | Collection of digital output channels from hardware. | | `SelectedDASChannel` | `public DASChannel SelectedDASChannel { get; set; }` | Currently selected DAS channel in UI. | | `EnableOrDisableText` | `public string EnableOrDisableText { get; }` | Returns localized "Enable" or "Disable" text based on `SelectedDASChannel.Channel.Disabled`. | | `IsBusy` | `public bool IsBusy { get; set; }` | Bound to busy indicator state. | | `IsMenuIncluded` | `public bool IsMenuIncluded { get; set; }` | Unclear purpose from source alone. | | `IsNavigationIncluded` | `public bool IsNavigationIncluded { get; set; }` | Unclear purpose from source alone. | | `IsDirty` | `public bool IsDirty { get; private set; }` | Indicates unsaved changes (never set to `true` in visible source). | | `Cleanup()` | `public void Cleanup()` | Empty implementation. | | `CleanupAsync()` | `public Task CleanupAsync()` | Returns `Task.CompletedTask`. | | `Initialize()` | `public void Initialize()` | Empty implementation. | | `Initialize(object)` | `public void Initialize(object parameter)` | Empty implementation. | | `Initialize(object, object)` | `public void Initialize(object parameter, object model)` | Empty implementation. | | `InitializeAsync()` | `public Task InitializeAsync()` | Returns `Task.CompletedTask`. | | `InitializeAsync(object)` | `public Task InitializeAsync(object parameter)` | Returns `Task.CompletedTask`. | | `Activated()` | `public void Activated()` | Empty implementation. | | `OnPropertyChanged(string)` | `public void OnPropertyChanged(string propertyName)` | Raises `PropertyChanged` event. | | `PropertyChanged` | `public event PropertyChangedEventHandler PropertyChanged` | Standard INotifyPropertyChanged event. | --- ### LevelTriggerViewModel **Implements:** `ILevelTriggerViewModel` | Member | Signature | Description | |--------|-----------|-------------| | `View` | `public ILevelTriggerView View { get; set; }` | The associated view instance. | | `LevelTriggers` | `public ILevelTrigger[] LevelTriggers { get; }` | Returns `_setup?.LevelTriggers`; provides access to level trigger configurations. | | `IsBusy`, `IsMenuIncluded`, `IsNavigationIncluded`, `IsDirty` | Same as above | Standard state properties. | | `NotificationRequest`, `ConfirmationRequest` | Same as above | Interaction requests. | | Lifecycle methods (`Cleanup`, `Initialize`, etc.) | Same signatures as above | All return empty or `Task.CompletedTask`. | | `OnPropertyChanged(string)` | Same as above | Raises `PropertyChanged` event. | | `PropertyChanged` | Same as above | INotifyPropertyChanged event. | --- ### DigitalInputChannelsViewModel **Implements:** `IDigitalInputChannelsViewModel` | Member | Signature | Description | |--------|-----------|-------------| | `View` | `public IDigitalInputChannelsView View { get; set; }` | The associated view instance. | | `DASChannels` | `public ObservableCollection DASChannels { get; set; }` | Physical digital input channels from hardware. | | `RemainingChannels` | `public ObservableCollection RemainingChannels { get; set; }` | Channel records not yet assigned to hardware. | | `SelectedDASChannel` | `public DASChannel SelectedDASChannel { get; set; }` | Currently selected hardware channel. | | `SelectedRemainingChannel` | `public ITTSChannelRecord SelectedRemainingChannel { get; set; }` | Currently selected unassigned channel record. | | `AssignEnabled` | `public bool AssignEnabled { get; set; }` | Enables/disables Assign button in UI. | | `RemoveEnabled` | `public bool RemoveEnabled { get; set; }` | Enables/disables Remove button in UI. | | `EnableOrDisableEnabled` | `public bool EnableOrDisableEnabled { get; set; }` | Enables/disables Enable/Disable button. | | `EnableOrDisableText` | `public string EnableOrDisableText { get; }` | Localized "Enable" or "Disable" text. | | `AssignCommand` | `public DelegateCommand AssignCommand { get; }` | Assigns `SelectedRemainingChannel` to `SelectedDASChannel`. | | `RemoveCommand` | `public DelegateCommand RemoveCommand { get; }` | Removes channel assignment from `SelectedDASChannel`. | | `EnableOrDisableCommand` | `public DelegateCommand EnableOrDisableCommand { get; }` | Toggles `Disabled` state on selected channel. | | Other members | Same as above | `IsBusy`, `IsDirty`, lifecycle methods, `PropertyChanged`. | --- ### TOMChannelsViewModel **Implements:** `ITOMChannelsViewModel` | Member | Signature | Description | |--------|-----------|-------------| | `View` | `public ITOMChannelsView View { get; set; }` | The associated view instance. | | `DASChannels` | `public ObservableCollection DASChannels { get; set; }` | Physical squib channels from hardware (every other channel, `i += 2`). | | `RemainingChannels` | `public ObservableCollection RemainingChannels { get; set; }` | Unassigned squib channel records. | | `SelectedDASChannel` | `public Model.DASChannel SelectedDASChannel { get; set; }` | Currently selected squib hardware channel. | | `SelectedRemainingChannel` | `public ITTSChannelRecord SelectedRemainingChannel { get; set; }` | Currently selected unassigned squib record. | | `AssignCommand`, `RemoveCommand`, `EnableOrDisableCommand` | Same as `DigitalInputChannelsViewModel` | Same command implementations for squib channels. | | Other members | Same as above | Standard state properties and lifecycle methods. | --- ## 3. Invariants 1. **Singleton Lifetime**: All four ViewModels are decorated with `[PartCreationPolicy(CreationPolicy.Shared)]`, ensuring a single shared instance per container. 2. **Event Subscription Thread Affinity**: All `IEventAggregator` subscriptions use `ThreadOption.PublisherThread` with `keepSubscriberReferenceAlive = true`, meaning handlers execute on the publisher's thread and subscribers are held strongly. 3. **UI Thread Requirement**: `OnAssignedChannelsChangedEvent` in `DigitalOutputChannelsViewModel`, `DigitalInputChannelsViewModel`, and `TOMChannelsViewModel` explicitly check `Application.Current.Dispatcher.CheckAccess()` and marshal to the UI thread via `BeginInvoke` if necessary. 4. **Hardware Channel Filtering**: - `DigitalOutputChannelsViewModel`: Filters for `ch.IsDigitalOut == true` - `DigitalInputChannelsViewModel`: Filters for `ch.IsDigitalIn == true` - `TOMChannelsViewModel`: Filters for `ch.IsSquib == true` and iterates by `i += 2` (squib channels are paired) 5. **Channel Record Validation**: All ViewModels skip channel records where: - `channelRecord.IsEmptyRecord == true` - `channelRecord.IsChannelCodeValid == false` - `channelRecord.ChannelCode == TTSChannelRecord.NONE` 6. **EID Mapping**: `DigitalOutputChannelsViewModel` and `LevelTriggerViewModel` maintain a mapping (`_hardwareChannelIdToSensorId` / `_sensorIdToChannelId`) from `EIDMappingEvent` for sensor-to-channel resolution. --- ## 4. Dependencies ### External Dependencies (from imports) - **Prism Framework**: `Prism.Events`, `Prism.Regions`, `Prism.Commands` — Event aggregation, region management, delegate commands. - **Unity**: `Unity` — Dependency injection container (`IUnityContainer`). - **MEF**: `System.ComponentModel.Composition` — Part creation policy. - **WPF**: `System.Windows`, `System.Windows.Data`, `System.Collections.ObjectModel` — UI collections and dispatcher. ### Internal Dependencies (DTS.* namespaces) - `DTS.Common.Events` / `DTS.Common.Events.TTSImport` — Event types: `RaiseNotification`, `BusyIndicatorChangeNotification`, `AssignedChannelsChangedEvent`, `TTSImportHardwareScanFinishedEvent`, `EIDMappingEvent`, `TTSImportReadFileStatusEvent`, `TTSImportSavedChangesStatusEvent`, `TTSImportTestSetupChangedEvent`. - `DTS.Common.Interface.DataRecorders` — `IDASHardware`, `IHardwareChannel`. - `DTS.Common.Interface.TestSetups.Imports.TTS.*` — `ITTSSetup`, `ITTSChannelRecord`, `ILevelTrigger`, view interfaces. - `DTS.Common.Interactivity` — `InteractionRequest`, `Notification`, `Confirmation`, `NotificationContentEventArgs`. - `DTS.Common.Enums` — `DigitalInputModes`, `ExcitationVoltageOptions`. - `TTSImport.Model` — `DASChannel` model class. - `TTSImport.Resources` — `StringResources` for localized strings. ### Consumers - Unclear from source alone; these ViewModels are likely resolved by the DI container and bound to their respective Views (`IDigitalOutputChannelsView`, `ILevelTriggerView`, etc.). --- ## 5. Gotchas 1. **Logic Bug in `DetermineRemoveEnableStatus`** (present in `DigitalInputChannelsViewModel` and `TOMChannelsViewModel`): ```csharp if (!string.IsNullOrWhiteSpace(_selectedDASChannel.EID) && _selectedDASChannel.EID == _selectedDASChannel.Channel.SensorEID) { RemoveEnabled = false; } RemoveEnabled = true; // This always overwrites the above! ``` The `RemoveEnabled = true` assignment always executes, making the preceding conditional check ineffective. 2. **Empty Async Lifecycle Methods**: All `InitializeAsync` and `CleanupAsync` methods return `Task.CompletedTask` with no actual async work. This may indicate incomplete implementation or a placeholder pattern. 3. **`IsDirty` Never Set to `true`**: The `IsDirty` property has a private setter but is never assigned `true` anywhere in the visible source. Its purpose is unclear. 4. **Historical Bug References in Comments** (`LevelTriggerViewModel.cs`): - Comment references "11245" and "15643" (likely defect tracking IDs) describing edge cases around EID assignment and channel code validation. - A comment notes: *"the below condition I think was missing a not"* indicating past confusion about boolean logic. 5. **Squib Channel Pairing** (`TOMChannelsViewModel`): Hardware channels are iterated with `i += 2`, implying squib channels are paired (voltage/initiation vs. current). This is not documented elsewhere and could cause confusion if the hardware configuration changes. 6. **Task.Run for MessageBox** (`DigitalInputChannelsViewModel.Assign` and `TOMChannelsViewModel.Assign`): The code uses `Task.Run(() => MessageBox.Show(...))` followed by `Application.Current.Dispatcher.BeginInvoke` for the actual assignment work. This pattern is unusual—MessageBox is typically shown on the UI thread directly. 7. **Pre-Assigned Channels Logic** (`LevelTriggerViewModel.UpdateLevelTriggers`): The method modifies `_setup.PreAssignedSensorIdAndHwId` in-place, reassigning it to a filtered list. This mutates shared state that may be observed elsewhere. 8. **Exception Swallowing** (`LevelTriggerViewModel.VoltageIsValid`): The method contains an empty `catch { }` block that silently ignores exceptions from `GetExcitationVoltageEnumFromMagnitude`, returning `false` for invalid voltages.