From a04de8ab29aa8464d9d05ce1c5fceec4fcd97289 Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Fri, 27 Feb 2026 11:44:59 -0500 Subject: [PATCH 1/6] GSF.PhasorProtocols.UI.WPF: In input wizard, fix CanBeOutrankedBy() and add additional low-confidence checks --- .../UI/WPF/ViewModels/InputWizardDevices.cs | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs index 13c9849717..266b8eded0 100755 --- a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs +++ b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs @@ -82,6 +82,11 @@ public DeviceMapping(Device device, IConfigurationCell cell, double ordinalDista StationAcronym = cell.StationName?.Replace(" ", "_").Replace("'", "").ToUpper(); LabelMatchValue = device.Acronym.Equals(StationLabel, StringComparison.Ordinal) ? 1.0D : 0.0D; MatchValue = ComputeMatchValue(); + + // The minimum Levenshtein distance has to be at least one + // because we already checked if the strings are equal + MinLabelDistance = new(() => Math.Max(1.0D, Device.Acronym.LevenshteinDistanceLowerBounds(StationLabel))); + MaxLabelDistance = new(() => Math.Max(1.0D, Device.Acronym.LevenshteinDistanceUpperBounds(StationLabel))); } #endregion @@ -94,9 +99,8 @@ public DeviceMapping(Device device, IConfigurationCell cell, double ordinalDista // A match value below 1 indicates the // mapping should probably be disregarded - public bool HasLowConfidence => - (CanBeUpdated && MatchValue < 1.0D - InverseSquared(MinLabelDistance)) || - (!CanBeUpdated && MatchValue < 1.0D); + public bool HasLowConfidence => MaxMatchValue < 1.0D; + public bool MaybeLowConfidence => MinMatchValue < 1.0D; private string LabelPrefix { get; } private string StationAcronym { get; } @@ -104,15 +108,21 @@ public DeviceMapping(Device device, IConfigurationCell cell, double ordinalDista private double LabelMatchValue { get; set; } private double MatchValue { get; set; } + private double MinMatchValue => CanBeUpdated + ? MatchValue + InverseSquared(MaxLabelDistance.Value) + : MatchValue; + + private double MaxMatchValue => CanBeUpdated + ? MatchValue + InverseSquared(MinLabelDistance.Value) + : MatchValue; + private string StationLabel => Device.Acronym.StartsWith(LabelPrefix, StringComparison.Ordinal) ? $"{LabelPrefix}{StationAcronym}" : StationAcronym; - // The minimum Levenshtein distance has to be at least one - // because we already checked if the strings are equal - private double MinLabelDistance => - Math.Max(1.0D, Device.Acronym.LevenshteinDistanceLowerBounds(StationLabel)); + private Lazy MinLabelDistance { get; } + private Lazy MaxLabelDistance { get; } // Indicates whether computing Levenshtein distance could change the ranking private bool CanBeUpdated => @@ -130,8 +140,7 @@ public bool CanBeOutrankedBy(DeviceMapping mapping) { return MatchValue < 3.0D && - mapping.CanBeUpdated && - MatchValue - mapping.MatchValue < InverseSquared(1.0D); + MinMatchValue < mapping.MaxMatchValue; } // Levenshtein distance can be expensive to compute @@ -246,6 +255,16 @@ public DeviceMapping Dequeue() } } + // Edge case for mappings that + // straddle the low-confidence threshold + if (mapping.MaybeLowConfidence) + { + mapping.UpdateRank(); + + if (mapping.HasLowConfidence) + return null; + } + MappedDevices.Add(mapping.Device); MappedCells.Add(mapping.Cell); return mapping; From 97ebac814666c5b6d1542ad329a17d10590f278e Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Tue, 3 Mar 2026 13:30:45 -0500 Subject: [PATCH 2/6] GSF.TimeSeries.UI: Add graphic for unlink --- .../GSF.TimeSeries/UI/GSF.TimeSeries.UI.csproj | 3 +++ .../GSF.TimeSeries/UI/Images/Unlink.png | Bin 0 -> 1790 bytes 2 files changed, 3 insertions(+) create mode 100644 Source/Libraries/GSF.TimeSeries/UI/Images/Unlink.png diff --git a/Source/Libraries/GSF.TimeSeries/UI/GSF.TimeSeries.UI.csproj b/Source/Libraries/GSF.TimeSeries/UI/GSF.TimeSeries.UI.csproj index ad3d4248ea..b74922a77a 100755 --- a/Source/Libraries/GSF.TimeSeries/UI/GSF.TimeSeries.UI.csproj +++ b/Source/Libraries/GSF.TimeSeries/UI/GSF.TimeSeries.UI.csproj @@ -168,6 +168,9 @@ + + + diff --git a/Source/Libraries/GSF.TimeSeries/UI/Images/Unlink.png b/Source/Libraries/GSF.TimeSeries/UI/Images/Unlink.png new file mode 100644 index 0000000000000000000000000000000000000000..8013fd4bb8c35b35626f768fdd13739abe80caba GIT binary patch literal 1790 zcmZ8hc{JPU8vZ2_wbVA2Q9@E$syZZ$eJPrVa4n6Xwl)Y&7@@?{)^1SCRk}oVxMSC$ z_SkBxL90~Mu83Hwot9Fi*3$danRD(r_dVzNp6A`)zrJKU8@!N!i~s-tLIeve5oGBj z@bQB0T$s@gNRVJ6-VCT@Bi@4!&s9@vQvj&BE%?;~4*L9o7S6!{Al!KbNK>ZYM=*GT zfxE=85Ab1xc?5X_b{;f3L(K+9LTRaC)G(M7J}EX>)Z~4M;6oss1^7S*4#+^@00dN) zAY}j1N9UY@z`nhqQ_qymZ#1&Sg&(?F}wYtHLHg43K8UV!o zXtL6?Bu-u5t2if;Iv#*CG@h@hYZXyiYz?Q?u&!t5P8TSA-Tej)#Z7kDO^vFug5!N5 zXr5tanD*7vBZo5ConpF_>ZC-tVDHOGR7 zX;b_5Nmcsh+=pb?jjdQMxObgr=`%X=;?`k!Gxe%+e}H;f#J9261RVs+6)&53M^Hzt z5qdMuQuZeFj6xBw*MnogQ^<_@E2gCDtYuI&Z*C!r+HIC#Zp*J#nj+To`CKTXMOw@- z#9SSx2|@JbFd~*yAY^W=P4Sy%D*jAYQ^N5@r5u(bws2)029UyplC6H@O`UqPM^_}M zFmBW>u=DDh@&~1bfs89b;t&99{X1raQrx3xUY;=O_xKmIOEV2wrm`e^4Tocd_2j*~dv9akG(3M;i-(nv3KXU}G@S9v3@#mxDuE(zEC`=X;E-A`0T5-#G~?vO+KAWm$Z^X2U_-1JP>=bLH` za&`Qn$IIGh;1<7#h?M2b@e9U|)YZDLm6{!e;;yqqmQ$w-&&J{i*HwB6vwU3R{Zdpz za4wN8m~JKJrDppp5Ob}?OsG#@ekT+iJrMO`i*w!f#GLt^qL3l>{ovJ_lUXifL++pR zXY$BHJ#jhr-krOmGbQrm+E2@%gmG%rEOOuXYF$l3Ze-Z%5(Yu_Wiw`LA zcSDU*`A%yWm!4*X7$Yp`_i1dyk&Hh^%=8i_grfsRfw>l5y?N0TKT$w=(%Zd;Mkv^> zTM%56f&f2{k0En%n*(FT#Rx{eCC#`JSuK}GDrdWNMbj>L*S=SEt{8Zq8OR?FgP;Tl zut3cCL~75kdKs7Xiz4iT58OFVO%jNOjlFw`iX{Db99ieG_y+@~&LL}!zx-eW8H+)s z)jXf@fhy%vfSB&h6`uKfG%;$#B+9S-tz4wk?^%w#gda2?0zkH;f9h?2XTOb!YLmn6 zjvoAOQsMZjzQoet{$e!z9H+W}f2Ow8Nd3zWti7=#cZ9k2{fj5yY_OGP Date: Tue, 3 Mar 2026 13:31:23 -0500 Subject: [PATCH 3/6] GSF.PhasorProtocols.UI.WPF: On input wizard, expand status colors and enable device unlink --- .../UI/DataModels/InputWizardDevice.cs | 164 +++++++++++++++++- .../UserControls/InputWizardUserControl.xaml | 23 ++- .../UI/WPF/ViewModels/InputWizardDevices.cs | 58 +++++-- 3 files changed, 215 insertions(+), 30 deletions(-) diff --git a/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/InputWizardDevice.cs b/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/InputWizardDevice.cs index b317208d61..2e6447a25c 100755 --- a/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/InputWizardDevice.cs +++ b/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/InputWizardDevice.cs @@ -42,10 +42,12 @@ public class InputWizardDevice : DataModelBase #region [ Members ] // Fields + private int m_id; private string m_acronym; private string m_name; private string m_configAcronym; private string m_configName; + private string m_linkAcronym; private decimal m_longitude; private decimal m_latitude; private int? m_vendorDeviceId; @@ -56,9 +58,10 @@ public class InputWizardDevice : DataModelBase private int m_analogCount; private bool m_addDigitals; private bool m_addAnalogs; - private bool m_existing; + private bool m_hasConflict; private string m_statusColor; private ObservableCollection m_phasorList; + private bool m_useConfigLabels; #endregion @@ -67,13 +70,31 @@ public class InputWizardDevice : DataModelBase /// /// Gets or sets existing device ID, if any. /// - public int ID { get; set; } + public int ID + { + get => m_id; + set + { + m_id = value; + UpdateStatusColor(); + } + } /// /// Gets or sets and provided unique ID for the device. /// public Guid? UniqueID { get; set; } + /// + /// Gets or sets and provided global ID for the device's configuration cell. + /// + public Guid? GlobalID3 { get; set; } + + /// + /// Gets or sets the acronym of the existing device record. + /// + public string OldAcronym { get; set; } + /// /// Gets or sets acronym of the . /// @@ -91,7 +112,15 @@ public string Acronym m_acronym = m_acronym.Substring(0, 200); OnPropertyChanged(nameof(Acronym)); - Existing = Device.GetDevice(null, $"WHERE Acronym = '{m_acronym.ToUpper()}'") is not null; + + HasConflict = + m_acronym != OldAcronym && + Device.GetDevice(null, $"WHERE Acronym = '{m_acronym.ToUpper()}'") is not null; + + if (UseConfigLabels) + ConfigFrameAcronym = value; + else + DatabaseAcronym = value; } } @@ -106,6 +135,11 @@ public string Name { m_name = value is null || value.Length <= 200 ? value : value.Substring(0, 200); OnPropertyChanged(nameof(Name)); + + if (UseConfigLabels) + ConfigFrameName = value; + else + DatabaseName = value; } } @@ -135,6 +169,19 @@ public string ConfigName } } + /// + /// Gets or sets acronym from database. + /// + public string LinkAcronym + { + get => m_linkAcronym; + set + { + m_linkAcronym = value; + OnPropertyChanged(nameof(LinkAcronym)); + } + } + /// /// Gets or sets Longitude. /// @@ -268,14 +315,14 @@ public bool AddAnalogs /// /// Gets or sets existing flag. /// - public bool Existing + public bool HasConflict { - get => m_existing; + get => m_hasConflict; set { - m_existing = value; - OnPropertyChanged(nameof(Existing)); - StatusColor = m_existing ? "green" : "transparent"; + m_hasConflict = value; + OnPropertyChanged(nameof(HasConflict)); + UpdateStatusColor(); } } @@ -289,9 +336,16 @@ public string StatusColor { m_statusColor = value; OnPropertyChanged(nameof(StatusColor)); + OnPropertyChanged(nameof(IsHighlighted)); } } + /// + /// Gets flag to indicate whether the device is highlighted via . + /// + public bool IsHighlighted => + StatusColor != "transparent"; + /// /// Gets or sets phasor list. /// @@ -380,10 +434,63 @@ public string DigitalLabelsPreview /// public string AnalogLabelsPreview => AnalogLabels is not null ? string.Join(Environment.NewLine, AnalogLabels.Select((label, index) => $"Analog {index}: {label}")) : string.Empty; + internal string DatabaseAcronym { get; set; } + internal string ConfigFrameAcronym { get; set; } + internal string DatabaseName { get; set; } + internal string ConfigFrameName { get; set; } + + internal bool UseConfigLabels + { + get => m_useConfigLabels; + set + { + if (m_useConfigLabels == value) return; + m_useConfigLabels = value; + Acronym = m_useConfigLabels ? ConfigFrameAcronym : DatabaseAcronym; + Name = m_useConfigLabels ? ConfigFrameName : DatabaseName; + + foreach (InputWizardDevicePhasor phasor in PhasorList) + phasor.UseConfigLabels = value; + } + } + #endregion #region [ Methods ] + /// + /// Detaches the input wizard device from the database record it was mapped to. + /// + public void Unlink() + { + ID = 0; + UniqueID = GlobalID3; + OldAcronym = null; + VendorDeviceID = null; + DatabaseAcronym = ConfigFrameAcronym; + DatabaseName = ConfigFrameName; + + if (!UseConfigLabels) + { + Acronym = ConfigFrameAcronym; + Name = ConfigFrameName; + } + + foreach (InputWizardDevicePhasor phasor in m_phasorList) + { + phasor.DatabaseLabel = phasor.ConfigFrameLabel; + phasor.DatabaseType = phasor.ConfigFrameType; + phasor.DatabasePhase = phasor.ConfigFramePhase; + + if (!phasor.UseConfigLabels) + { + phasor.Label = phasor.ConfigFrameLabel; + phasor.Type = phasor.ConfigFrameType; + phasor.Phase = phasor.ConfigFramePhase; + } + } + } + /// /// Retrieves type collection of . /// @@ -419,6 +526,22 @@ public static Dictionary GetLookupList(AdoDataConnection database, public static string Delete(AdoDataConnection database, int deviceID) => string.Empty; + private void UpdateStatusColor() + { + string statusColor; + if (m_acronym == OldAcronym) + statusColor = "green"; + else if (HasConflict) + statusColor = "red"; + else if (m_id != 0) + statusColor = "yellow"; + else + statusColor = "transparent"; + + if (statusColor != StatusColor) + StatusColor = statusColor; + } + #endregion } @@ -438,11 +561,14 @@ public class InputWizardDevicePhasor : DataModelBase private string m_baseKVInput; //private string m_destinationLabel; private bool m_include; + private bool m_useConfigLabels; internal string DatabaseLabel; internal string ConfigFrameLabel; internal string DatabaseType; internal string ConfigFrameType; + internal string DatabasePhase; + internal string ConfigFramePhase; #endregion @@ -460,6 +586,11 @@ public string Label { m_label = value is null || value.Length <= 200 ? value : value.Substring(0, 200); OnPropertyChanged("Label"); + + if (UseConfigLabels) + ConfigFrameLabel = value; + else + DatabaseLabel = value; } } @@ -475,6 +606,11 @@ public string Type { m_type = value; OnPropertyChanged("Type"); + + if (UseConfigLabels) + ConfigFrameType = value; + else + DatabaseType = value; } } @@ -579,6 +715,18 @@ public bool Include /// public float AngleAdder { get; set; } + internal bool UseConfigLabels + { + get => m_useConfigLabels; + set + { + m_useConfigLabels = value; + Label = m_useConfigLabels ? ConfigFrameLabel : DatabaseLabel; + Type = m_useConfigLabels ? ConfigFrameType : DatabaseType; + Phase = m_useConfigLabels ? ConfigFramePhase : DatabasePhase; + } + } + #endregion } } diff --git a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml index 1d2c148617..4f938ea11f 100755 --- a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml +++ b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml @@ -251,8 +251,10 @@ - - + + + + @@ -276,13 +278,28 @@ + + + + + + + + + Visibility="{Binding Path=IsHighlighted, Converter={StaticResource ObjectToVisibilityConverter}}"/> diff --git a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs index 266b8eded0..c14fe678be 100755 --- a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs +++ b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs @@ -337,6 +337,7 @@ private static IEnumerable BuildInitialMappings(IEnumerable public ICommand ManualConfigurationCommand => m_manualConfigurationCommand ??= new RelayCommand(ManualConfiguration, () => CanSave); + /// + /// Gets to unlink from database record. + /// + public ICommand UnlinkCommand => m_unlinkCommand ??= new RelayCommand(UnlinkDevice); + /// /// Gets or sets summary message to be displayed on UI after parsing configuration file or frame. /// @@ -1254,11 +1251,13 @@ private void ParseConfiguration(bool displayPopup = true) string stationName = CultureInfo.CurrentUICulture.TextInfo.ToTitleCase(cell.StationName?.ToLower() ?? stationAcronym); string deviceAcronym = i < DeviceAcronyms.Length ? DeviceAcronyms[i] : (existingDevice?.Acronym ?? stationAcronym); int deviceID = existingDevice?.ID ?? 0; + Guid? globalID3 = null; Guid? uniqueID = null; decimal? longitude = null, latitude = null; if (cell is ConfigurationCell3 configCell3) { + globalID3 = configCell3.GlobalID; uniqueID = configCell3.GlobalID; longitude = configCell3.LongitudeM; latitude = configCell3.LatitudeM; @@ -1279,6 +1278,7 @@ private void ParseConfiguration(bool displayPopup = true) } finally { + globalID3 = null; uniqueID = null; } } @@ -1465,11 +1465,9 @@ string guessBaseKV(string baseKV, string phasorLabel, string deviceLabel) string getConfigFrameType(IPhasorDefinition phasor) => phasor.PhasorType == PhasorType.Current ? "I" : "V"; - string getPhasorLabel(IPhasorDefinition phasor) => UseConfigLabels ? getConfigFrameLabel(phasor) : getDatabaseLabel(phasor); + string getDatabasePhase(IPhasorDefinition phasor) => phasorExists(phasor) ? existingPhasors?[phasor.Index].Phase : getConfigFramePhase(phasor); - string getPhasorType(IPhasorDefinition phasor) => UseConfigLabels ? getConfigFrameType(phasor) : getDatabaseType(phasor); - - string getPhasorPhase(IPhasorDefinition phasor) + string getConfigFramePhase(IPhasorDefinition phasor) { string configPhase = string.Empty; @@ -1504,9 +1502,15 @@ string getPhasorPhase(IPhasorDefinition phasor) } } - return guessPhase(phasorExists(phasor) ? existingPhasors?[phasor.Index].Phase : configPhase, phasor.Label); + return guessPhase(configPhase, phasor.Label); } + string getPhasorLabel(IPhasorDefinition phasor) => UseConfigLabels ? getConfigFrameLabel(phasor) : getDatabaseLabel(phasor); + + string getPhasorType(IPhasorDefinition phasor) => UseConfigLabels ? getConfigFrameType(phasor) : getDatabaseType(phasor); + + string getPhasorPhase(IPhasorDefinition phasor) => UseConfigLabels ? getConfigFramePhase(phasor) : getDatabasePhase(phasor); + string getPhasorBaseKV(IPhasorDefinition phasor) { string configBaseKV = "0"; @@ -1530,15 +1534,24 @@ Tuple[] getAnalogScalars(AnalogDefinitionCollection analogs) => analogs.Select(getAnalogScalarSet).ToArray(); string deviceIndex = m_configurationFrame.Cells.Count > 1 ? $" {i + 1:N0}" : ""; + string databaseAcronym = string.IsNullOrWhiteSpace(existingDevice?.Acronym) ? deviceAcronym : existingDevice.Acronym; + string databaseName = string.IsNullOrWhiteSpace(existingDevice?.Name) ? stationName : existingDevice.Name; wizardDeviceList.Add(new InputWizardDevice { ID = deviceID, UniqueID = existingDevice?.UniqueID ?? uniqueID, - Acronym = string.IsNullOrWhiteSpace(existingDevice?.Acronym) ? deviceAcronym : existingDevice.Acronym, - Name = string.IsNullOrWhiteSpace(existingDevice?.Name) ? stationName : existingDevice.Name, - ConfigAcronym = $"Device{deviceIndex} label from config: {deviceAcronym}{(string.IsNullOrWhiteSpace(cell.IDLabel) ? "" : $" ({cell.IDLabel})")}", + GlobalID3 = globalID3, + OldAcronym = existingDevice?.Acronym, + Acronym = UseConfigLabels ? stationAcronym : databaseAcronym, + DatabaseAcronym = databaseAcronym, + ConfigFrameAcronym = stationAcronym, + Name = UseConfigLabels ? stationName : databaseName, + DatabaseName = databaseName, + ConfigFrameName = stationName, + ConfigAcronym = $"Device{deviceIndex} label from config: {stationAcronym}{(string.IsNullOrWhiteSpace(cell.IDLabel) ? "" : $" ({cell.IDLabel})")}", ConfigName = $"Device{deviceIndex} name derived from config: {stationName}", + LinkAcronym = $"Unlink from {deviceAcronym} in database", Longitude = existingDevice?.Longitude ?? longitude ?? -98.6m, Latitude = existingDevice?.Latitude ?? latitude ?? 37.5m, VendorDeviceID = existingDevice?.VendorDeviceID, @@ -1549,7 +1562,6 @@ Tuple[] getAnalogScalars(AnalogDefinitionCollection analogs) => AnalogCount = cell.AnalogDefinitions.Count, AddDigitals = cell.DigitalDefinitions.Count > 0, AddAnalogs = cell.AnalogDefinitions.Count > 0, - Existing = existingDevice is not null, DigitalLabels = GetAnalogOrDigitalLabels(cell.DigitalDefinitions), AnalogLabels = GetAnalogOrDigitalLabels(cell.AnalogDefinitions), AnalogScalars = getAnalogScalars(cell.AnalogDefinitions), @@ -1567,6 +1579,8 @@ from phasor in cell.PhasorDefinitions ConfigLabel = $"Phasor {phasor.Index + 1:N0} label from config: {phasor.Label}", ConfigType = $"Phasor {phasor.Index + 1:N0} type from config: {phasor.PhasorType}", Phase = getPhasorPhase(phasor), + DatabasePhase = getDatabasePhase(phasor), + ConfigFramePhase = getConfigFramePhase(phasor), BaseKVInput = getPhasorBaseKV(phasor), Include = true, MagnitudeMultiplier = getMagnitudeMultiplier(phasor), @@ -1911,6 +1925,12 @@ private void ManualConfiguration() cc.ShowDialog(); } + private void UnlinkDevice(object context) + { + if (context is InputWizardDevice device) + device.Unlink(); + } + /// /// Handles ReceivedServiceUpdate event. /// From a578df88cc43d94211a943aafc0bedd38f7e8d6a Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Tue, 3 Mar 2026 16:12:07 -0500 Subject: [PATCH 4/6] GSF.PhasorProtocols.UI.WPF: Fix errors with phasor reordering --- .../GSF.PhasorProtocols/UI/DataModels/Phasor.cs | 2 ++ .../UI/WPF/UserControls/InputWizardUserControl.xaml | 6 +++--- .../UI/WPF/ViewModels/InputWizardDevices.cs | 10 +++++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/Phasor.cs b/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/Phasor.cs index 96855564e7..8494276e11 100755 --- a/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/Phasor.cs +++ b/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/Phasor.cs @@ -432,6 +432,8 @@ public static string SaveAndReorder(AdoDataConnection database, Phasor phasor, i if (phasor.SourceIndex == 0) phasor.SourceIndex = database.ExecuteScalar("SELECT MAX(SourceIndex) FROM Phasor WHERE DeviceID = {0}", phasor.DeviceID) + 1; + else if (phasor.SourceIndex != oldSourceIndex) + database.ExecuteNonQuery("UPDATE Phasor SET SourceIndex = -SourceIndex WHERE DeviceID = {0} AND SourceIndex = {1}", phasor.DeviceID, phasor.SourceIndex); // Since phasors could be reordered in the source device, this test could inadvertently throw an exception when it should not - so the validation has been removed //if (database.ExecuteScalar("SELECT COUNT(*) FROM Phasor WHERE ID <> {0} AND DeviceID = {1} AND SourceIndex = {2}", phasor.ID, phasor.DeviceID, phasor.SourceIndex) > 0) diff --git a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml index 4f938ea11f..e3a9170e41 100755 --- a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml +++ b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml @@ -252,9 +252,9 @@ - - - + + + diff --git a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs index c14fe678be..30deea3af2 100755 --- a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs +++ b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/ViewModels/InputWizardDevices.cs @@ -1584,9 +1584,11 @@ from phasor in cell.PhasorDefinitions BaseKVInput = getPhasorBaseKV(phasor), Include = true, MagnitudeMultiplier = getMagnitudeMultiplier(phasor), - AngleAdder = getAngleAdder(phasor) + AngleAdder = getAngleAdder(phasor), + UseConfigLabels = UseConfigLabels } - ).ToList()) + ).ToList()), + UseConfigLabels = UseConfigLabels }); } @@ -2037,9 +2039,7 @@ public void SaveConfiguration() if (inputWizardDevice.ID > 0) device = Device.GetDevice(database, $"WHERE ID = {inputWizardDevice.ID}"); - if (device is null) - device = Device.GetDevice(database, $"WHERE Acronym = '{inputWizardDevice.Acronym.ToUpper()}' AND NodeID = '{database.CurrentNodeID()}'"); - else + if (device is not null) device.Acronym = inputWizardDevice.Acronym.ToUpper(); bool newDevice = false; From 2809c9b7f60bba73c59b781651e6c8cb7608a1ed Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Tue, 3 Mar 2026 16:15:45 -0500 Subject: [PATCH 5/6] GSF.PhasorProtocols.UI.WPF: Adjust label for UseConfigLabels checkbox --- .../UI/WPF/UserControls/InputWizardUserControl.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml index e3a9170e41..f630c923d3 100755 --- a/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml +++ b/Source/Libraries/GSF.PhasorProtocols/UI/WPF/UserControls/InputWizardUserControl.xaml @@ -249,7 +249,7 @@ - + From 0a3fa4168b8c2b10e928f570c876d4f4358be03f Mon Sep 17 00:00:00 2001 From: StephenCWills Date: Wed, 4 Mar 2026 13:20:56 -0500 Subject: [PATCH 6/6] GSF.PhasorProtocols.UI: Add clarity to comments in InputWizardDevice --- .../GSF.PhasorProtocols/UI/DataModels/InputWizardDevice.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/InputWizardDevice.cs b/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/InputWizardDevice.cs index 2e6447a25c..4e2b2bbd92 100755 --- a/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/InputWizardDevice.cs +++ b/Source/Libraries/GSF.PhasorProtocols/UI/DataModels/InputWizardDevice.cs @@ -68,7 +68,7 @@ public class InputWizardDevice : DataModelBase #region [ Properties ] /// - /// Gets or sets existing device ID, if any. + /// Gets or sets ID of the existing database record, if any. /// public int ID { @@ -170,7 +170,7 @@ public string ConfigName } /// - /// Gets or sets acronym from database. + /// Gets or sets tooltip info describing the device acronym from the database. /// public string LinkAcronym {