diff --git a/TBF/Rig/BridgeComponents/GciBridge/GciBridge.Simulation.cs b/TBF/Rig/BridgeComponents/GciBridge/GciBridge.Simulation.cs new file mode 100644 index 000000000..78edc2432 --- /dev/null +++ b/TBF/Rig/BridgeComponents/GciBridge/GciBridge.Simulation.cs @@ -0,0 +1,355 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Common; +using Config.Entities; +using TBF.Rig.Generic; +using GenesisCordonelInterface.Core.Threading; +using GciPublicModels = GenesisCordonelInterface.API.PublicModels; + +namespace TBF.Rig.BridgeComponents.GciBridge +{ + public partial class GciBridge + { + public bool IsSimulation { get { return DebugLevel == DebugMode.Simulate; } } + public GciSimulationSettings Simulation { get; } = new GciSimulationSettings(); + private readonly AsyncLocal simulationOverride = new AsyncLocal(); + + // Scoped to this execution context, including awaited calls; does not change defaults. + public IDisposable BeginSimulation(GciSimulationProfile profile) + { + if (!IsSimulation) throw new InvalidOperationException("GCI Bridge must be in Simulate mode."); + if (profile == null) throw new ArgumentNullException("profile"); + var previous = simulationOverride.Value; + simulationOverride.Value = profile.Snapshot(); + return new SimulationScope(() => simulationOverride.Value = previous); + } + + private sealed class SimulationScope : IDisposable + { + private Action restore; + public SimulationScope(Action restore) { this.restore = restore; } + public void Dispose() { var action = restore; restore = null; if (action != null) action(); } + } + private readonly object simulationLock = new object(); + private readonly Dictionary simulationSlots = new Dictionary(); + private readonly Dictionary simulationWorkers = new Dictionary(); + private readonly ConcurrentDictionary simulationGates = new ConcurrentDictionary(); + private readonly HashSet simulationSelection = new HashSet(); + private static readonly NLog.Logger simulationLogger = NLog.LogManager.GetLogger("GenesisCordonelInterface.GciBridge.Simulation"); + public event Action SimulationLog; + + private void LogSimulation(int slot, string operation, string message) + { + var entry = new NLog.LogEventInfo(NLog.LogLevel.Info, simulationLogger.Name, string.Format("[SIMULATION] Bridge={0}, Slot={1}, Operation={2}, {3}", Name, slot, operation, message)); + entry.Properties["Slot"] = slot; + entry.Properties["Operation"] = operation; + entry.Properties["Bridge"] = Name; + simulationLogger.Log(entry); + var handlers = SimulationLog; + if (handlers == null) return; + int[] targets; + lock (simulationLock) targets = slot > 0 ? new[] { slot } : simulationSelection.ToArray(); + foreach (int target in targets) + { + foreach (Action handler in handlers.GetInvocationList()) + { + try { handler(target, entry.Message); } + catch (Exception ex) { log.Warn("Simulation diagnostic subscriber failed.", ex); } + } + } + } + + private async Task SimulateAsync(string operation, int slot, CancellationToken token, Func create, string register = null) + { + token.ThrowIfCancellationRequested(); + var profile = Simulation.Get(operation, register); + var dynamicProfile = simulationOverride.Value; + if (dynamicProfile != null) + { + profile.DurationMs = dynamicProfile.DurationMs; + profile.Success = dynamicProfile.Success; + profile.ErrorMessage = dynamicProfile.ErrorMessage; + } + var gate = simulationGates.GetOrAdd(slot, _ => new SemaphoreSlim(1, 1)); + lock (simulationLock) + { + if (!simulationWorkers.ContainsKey(slot)) simulationWorkers[slot] = new GciPublicModels.WorkerDebugStatus { Slot = slot, Name = slot == 0 ? "Simulated batch" : "Simulated slot " + slot }; + simulationWorkers[slot].QueueLength++; + } + bool acquired = false; + try + { + await gate.WaitAsync(token).ConfigureAwait(false); + acquired = true; + lock (simulationLock) + { + var worker = simulationWorkers[slot]; + worker.QueueLength--; + worker.IsBusy = true; + worker.CurrentOperation = operation; + worker.LastError = null; + worker.LastActivity = DateTime.Now; + } + LogSimulation(slot, operation, "START, DurationMs=" + profile.DurationMs); + await Task.Delay(profile.DurationMs, token).ConfigureAwait(false); + token.ThrowIfCancellationRequested(); + T result; + lock (simulationLock) + { + result = create(profile); + simulationWorkers[slot].LastError = profile.Success ? null : profile.ErrorMessage; + } + LogSimulation(slot, operation, "FINISH, Success=" + profile.Success + ", ErrorMessage=" + profile.ErrorMessage); + return result; + } + catch (OperationCanceledException) + { + LogSimulation(slot, operation, "CANCELLED"); + throw; + } + finally + { + lock (simulationLock) + { + var worker = simulationWorkers[slot]; + if (!acquired) worker.QueueLength--; + else + { + worker.IsBusy = false; + worker.CurrentOperation = null; + worker.LastActivity = DateTime.Now; + } + } + if (acquired) gate.Release(); + } + } + + private static void ValidateSimulationSlot(int slot) + { + if (slot <= 0) throw new ArgumentOutOfRangeException("slot"); + } + + private string SimulationPcb(int slot, GciSimulationProfile profile) + { + return profile.PcbId ?? (900000000L + slot).ToString(System.Globalization.CultureInfo.InvariantCulture); + } + + // Successful canned operations may create a logical slot; no COM device is created. + private GciPublicModels.GciSlotInfo SimSlot(int slot, GciSimulationProfile profile) + { + GciPublicModels.GciSlotInfo state; + if (!simulationSlots.TryGetValue(slot, out state)) + { + state = new GciPublicModels.GciSlotInfo { SlotId = slot, Exists = true, Success = true, PcbId = SimulationPcb(slot, profile) }; + simulationSlots.Add(slot, state); + simulationSelection.Add(slot); + } + return state; + } + + private static GciPublicModels.GciPortConfig CopyPort(GciPublicModels.GciPortConfig port) + { + return port == null ? null : new GciPublicModels.GciPortConfig { PortName = port.PortName, Type = port.Type }; + } + + private static GciPublicModels.GciSlotInfo CopySlot(GciPublicModels.GciSlotInfo s) + { + return new GciPublicModels.GciSlotInfo { SlotId = s.SlotId, Exists = s.Exists, Success = s.Success, IsConnected = s.IsConnected, IsLoggedOn = s.IsLoggedOn, Message = s.Message, PcbId = s.PcbId, Password = s.Password, ConfigSource = s.ConfigSource, PasswordSource = s.PasswordSource, RequestPort = CopyPort(s.RequestPort), StreamingPort = CopyPort(s.StreamingPort) }; + } + + private Task SimInit(string operation, GciPublicModels.GciInitSlotRequest request, CancellationToken token) + { + if (request == null) throw new ArgumentNullException("request"); + ValidateSimulationSlot(request.SlotId); + return SimulateAsync(operation, request.SlotId, token, p => + { + bool existed = simulationSlots.ContainsKey(request.SlotId); + if (p.Success) + { + var s = SimSlot(request.SlotId, p); + s.RequestPort = CopyPort(request.RequestPort); + s.StreamingPort = CopyPort(request.StreamingPort); + s.Password = request.Password; + s.ConfigSource = request.ConfigSource; + s.PasswordSource = request.PasswordSource; + } + return new GciPublicModels.GciInitSlotResult { SlotId = request.SlotId, Success = p.Success, Message = p.ErrorMessage, PcbId = p.Success ? SimulationPcb(request.SlotId, p) : null, Created = p.Success && !existed, Updated = p.Success && existed, AlreadyExists = existed }; + }); + } + + private Task SimPhase(string operation, int slot, CancellationToken token) + { + return SimulateAsync(operation, slot, token, p => new GciPublicModels.PreAdjustmentProcessResult { Success = p.Success, Slot = slot, ProcessName = operation, ErrorMessage = p.ErrorMessage }); + } + + private int[] GetSimulatedPreadjustmentSlots() + { + lock (simulationLock) + { + // Slot 0 represented the old aggregate simulation. Preadjustment now reports + // only real logical slot workers. + GciPublicModels.WorkerDebugStatus batchWorker; + if (simulationWorkers.TryGetValue(0, out batchWorker) && !batchWorker.IsBusy) simulationWorkers.Remove(0); + return simulationSelection.OrderBy(slot => slot).ToArray(); + } + } + + private async Task SimBatchPhase(string operation, CancellationToken token) + { + int[] slots = GetSimulatedPreadjustmentSlots(); + if (slots.Length == 0) + return new GciPublicModels.PreAdjustmentProcessResult { Success = false, Slot = -1, ProcessName = operation, ErrorMessage = "No simulated preadjustment slots are selected." }; + + // Start every slot operation before awaiting the aggregate. SimulateAsync supplies + // an independent worker and serialization gate for each slot. + Task[] tasks = slots.Select(slot => SimPhase(operation, slot, token)).ToArray(); + GciPublicModels.PreAdjustmentProcessResult[] results = await Task.WhenAll(tasks).ConfigureAwait(false); + GciPublicModels.PreAdjustmentProcessResult[] failed = results.Where(result => !result.Success).ToArray(); + return new GciPublicModels.PreAdjustmentProcessResult + { + Success = failed.Length == 0, + Slot = -1, + ProcessName = operation, + ErrorMessage = failed.Length == 0 ? null : string.Join("; ", failed.Select(result => "Slot " + result.Slot + ": " + result.ErrorMessage)) + }; + } + + private async Task SimInitializeBatch(string operation, IEnumerable selectedSlots, CancellationToken token) + { + int[] slots = selectedSlots.Distinct().OrderBy(slot => slot).ToArray(); + if (slots.Length == 0) + return new GciPublicModels.PreAdjustmentProcessResult { Success = false, Slot = -1, ProcessName = operation, ErrorMessage = "No simulated preadjustment slots were supplied." }; + + lock (simulationLock) + { + GciPublicModels.WorkerDebugStatus batchWorker; + if (simulationWorkers.TryGetValue(0, out batchWorker) && !batchWorker.IsBusy) simulationWorkers.Remove(0); + simulationSelection.Clear(); + foreach (int slot in slots) + { + // Keep failed simulated slots visible in diagnostics until GenesisCommunication + // records and excludes them. + SimSlot(slot, Simulation.Get(operation, null)); + simulationSelection.Add(slot); + } + } + + Task[] tasks = slots.Select(slot => SimPhase(operation, slot, token)).ToArray(); + GciPublicModels.PreAdjustmentProcessResult[] results = await Task.WhenAll(tasks).ConfigureAwait(false); + GciPublicModels.PreAdjustmentProcessResult[] failed = results.Where(result => !result.Success).ToArray(); + return new GciPublicModels.PreAdjustmentProcessResult + { + Success = failed.Length == 0, + Slot = -1, + ProcessName = operation, + ErrorMessage = failed.Length == 0 ? null : string.Join("; ", failed.Select(result => "Slot " + result.Slot + ": " + result.ErrorMessage)) + }; + } + + // Aggregate simulation for UI-only workflows (Q3, preparation check, hold). + // This deliberately does not claim to execute their individual register steps. + public Task SimulateMeterActivityAsync(string activity, int slot, CancellationToken token = default(CancellationToken)) + { + if (!IsSimulation) throw new InvalidOperationException("GCI Bridge must be in Simulate mode."); + ValidateSimulationSlot(slot); + return SimulateAsync("MeterActivity", slot, token, p => + { + if (p.Success) SimSlot(slot, p); + return new GciPublicModels.PreAdjustmentProcessResult { Success = p.Success, Slot = slot, ProcessName = activity, ErrorMessage = p.ErrorMessage }; + }); + } + + private Task SimFullLogin(int slot, CancellationToken token) + { + // One independently configurable canned workflow; does not also delay every child. + return SimulateAsync("ConnectFullPassLoginWithRetryAsync", slot, token, p => + { + string pcb = p.Success ? SimulationPcb(slot, p) : null; + if (p.Success) + { + var s = SimSlot(slot, p); + s.IsConnected = true; + s.IsLoggedOn = true; + s.Password = p.Password; + } + return new Interfaces.PublicModels.GciFullLoginResult + { + SlotId = slot, Success = p.Success, Message = p.ErrorMessage, + ConnectResult = SimRetry(new GciPublicModels.GciConnectResult { SlotId = slot, Success = p.Success, IsConnected = p.Success, PcbId = pcb, Message = p.ErrorMessage }, p), + PcbResult = p.Success ? SimRetry(new GciPublicModels.GciGetPcbIdResult { SlotId = slot, Success = true, PcbId = pcb }, p) : null, + PasswordResult = p.Success ? SimRetry(new Interfaces.PublicModels.UdsPasswordResult { Success = true, PcbId = pcb, Password = p.Password }, p) : null, + SetPasswordResult = p.Success ? SimRetry(new GciPublicModels.GciSetPasswordResult { SlotId = slot, Success = true, Password = p.Password }, p) : null, + LoginResult = p.Success ? SimRetry(new GciPublicModels.GciLoginResult { SlotId = slot, Success = true, IsLoggedOn = true, PcbId = pcb }, p) : null + }; + }); + } + + private static RetryResult SimRetry(T result, GciSimulationProfile p) + { + return new RetryResult { Result = result, Success = p.Success, Attempts = 1, Duration = TimeSpan.Zero, TimedOut = false }; + } + + private async Task SimDetect(IEnumerable slots, CancellationToken token) + { + if (slots == null) throw new ArgumentNullException("slots"); + int[] selected = slots.Where(status => status.Selected).Select(status => status.Slot).Distinct().OrderBy(slot => slot).ToArray(); + foreach (int slot in selected) ValidateSimulationSlot(slot); + if (selected.Length == 0) return new GciPublicModels.PreadjustmentDetectResult { Success = false, ErrorMessage = "No simulated slots are selected.", DetectedMeterCount = 0 }; + + lock (simulationLock) + { + GciPublicModels.WorkerDebugStatus batchWorker; + if (simulationWorkers.TryGetValue(0, out batchWorker) && !batchWorker.IsBusy) simulationWorkers.Remove(0); + simulationSelection.Clear(); + foreach (int slot in selected) + { + SimSlot(slot, Simulation.Get("PreAdjustment_Detect", null)); + simulationSelection.Add(slot); + } + } + + Task[] tasks = selected.Select(slot => SimPhase("PreAdjustment_Detect", slot, token)).ToArray(); + GciPublicModels.PreAdjustmentProcessResult[] results = await Task.WhenAll(tasks).ConfigureAwait(false); + GciPublicModels.PreAdjustmentProcessResult[] failed = results.Where(result => !result.Success).ToArray(); + return new GciPublicModels.PreadjustmentDetectResult + { + Success = failed.Length == 0, + ErrorMessage = failed.Length == 0 ? null : string.Join("; ", failed.Select(result => "Slot " + result.Slot + ": " + result.ErrorMessage)), + DetectedMeterCount = results.Count(result => result.Success) + }; + } + + // Exclusion changes both the overview selection and the process control. + public void ExcludePreadjustmentSlot(int slot) + { + if (IsSimulation) { lock (simulationLock) simulationSelection.Remove(slot); return; } + EnsureExternalInterface(); + gciExternalInterface.SetSlotSelected(slot, false); + var controls = gciExternalInterface._innerMeterAPI._meterControls; + if (controls == null) return; + foreach (var control in controls.Where(c => c.Slot == slot)) + { + control.SetChecked(false); + control.IsEnabled = false; + } + } + + public List GetMeterBatchDebugStatuses() + { + if (!IsSimulation) { EnsureExternalInterface(); return gciExternalInterface.GetMeterBatchDebugStatuses(); } + lock (simulationLock) + return simulationSlots.Values.OrderBy(s => s.SlotId).Select(s => new GciPublicModels.MeterBatchDebugStatus { Slot = s.SlotId, Selected = simulationSelection.Contains(s.SlotId), PcbId = s.PcbId, IsConnected = s.IsConnected, IsLoggedOn = s.IsLoggedOn, FwVersion = "SIM", RequestPort = s.RequestPort == null ? "" : s.RequestPort.PortName, RequestPortType = s.RequestPort == null ? "" : s.RequestPort.Type, StreamingPort = s.StreamingPort == null ? "" : s.StreamingPort.PortName }).ToList(); + } + + public List GetWorkerDebugStatuses() + { + if (!IsSimulation) { EnsureExternalInterface(); return gciExternalInterface.GetWorkerDebugStatuses(); } + lock (simulationLock) + return simulationWorkers.Values.OrderBy(s => s.Slot).Select(s => new GciPublicModels.WorkerDebugStatus { Slot = s.Slot, Name = s.Name, QueueLength = s.QueueLength, IsBusy = s.IsBusy, CurrentOperation = s.CurrentOperation, LastActivity = s.LastActivity, LastError = s.LastError }).ToList(); + } + } +} diff --git a/TBF/Rig/BridgeComponents/GciBridge/GciBridge.cs b/TBF/Rig/BridgeComponents/GciBridge/GciBridge.cs index 086531f03..9f913db3d 100644 --- a/TBF/Rig/BridgeComponents/GciBridge/GciBridge.cs +++ b/TBF/Rig/BridgeComponents/GciBridge/GciBridge.cs @@ -11,6 +11,7 @@ using NHibernate.Mapping; /// Copyright (c) 2015-2021 Sensus Slovensko a.s. /// using System; +using System.Linq; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -41,7 +42,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge /// TBF bridge component for integration with the sibling GCI project. /// The component can be linked to UniDataStorage reader and writer components. /// - public class GciBridge : ComponentBase + public partial class GciBridge : ComponentBase { private static readonly ILog log = LogManager.GetLogger(typeof(GciBridge)); @@ -134,6 +135,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge public override void Initialize() { + if (IsSimulation) { log.InfoFormat("{0}: Simulation mode initialized; hardware initialization skipped.", Name); return; } if (gciBridgeCfg.EnableExternalAccess) { TryInitializeExternalInterface(); @@ -218,6 +220,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge public void ShowGciGui() { + if (IsSimulation) { MessageBox.Show("Use GenesisCommunication diagnostics for simulated slots. The hardware GCI GUI is unavailable in Simulate mode.", "GCI simulation"); return; } if (!gciBridgeCfg.EnableGuiAccess) return; if (gciGuiHostForm == null || gciGuiHostForm.IsDisposed) @@ -245,6 +248,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge public void ShowGui() { + if (IsSimulation) { MessageBox.Show("Use GenesisCommunication diagnostics for simulated slots. The hardware GCI GUI is unavailable in Simulate mode.", "GCI simulation"); return; } if (!gciBridgeCfg.EnableGuiAccess) return; if (gciBridgeGuiForm == null || gciBridgeGuiForm.IsDisposed) @@ -310,6 +314,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge GciPublicModels.GciInitSlotRequest request, CancellationToken token = default) { + if (IsSimulation) + { + return await SimInit(nameof(InitSlotAsync), request, token).ConfigureAwait(false); + } EnsureExternalInterface(); if (request == null) @@ -391,6 +399,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge GciPublicModels.GciInitSlotRequest request, CancellationToken token = default) { + if (IsSimulation) + { + return await SimInit(nameof(UpdateSlotAsync), request, token).ConfigureAwait(false); + } EnsureExternalInterface(); if (request == null) @@ -469,6 +481,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge int slotId, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slotId); + + return await SimulateAsync(nameof(GetSlotAsync), slotId, token, p => { if (!p.Success) return new GciPublicModels.GciSlotInfo { SlotId = slotId, Success = false, Message = p.ErrorMessage }; var copy = CopySlot(SimSlot(slotId, p)); copy.Message = p.ErrorMessage; return copy; }).ConfigureAwait(false); + } EnsureExternalInterface(); if (slotId <= 0) @@ -545,6 +563,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge int slot, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slot); + + return await SimulateAsync(nameof(CleanSlotAsync), slot, token, p => { if (p.Success) { simulationSlots.Remove(slot); simulationSelection.Remove(slot); } return new GciPublicModels.GciCleanSlotResult { SlotId = slot, Success = p.Success, Message = p.ErrorMessage }; }).ConfigureAwait(false); + } EnsureExternalInterface(); if (slot <= 0) @@ -621,6 +645,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge public async Task CleanAllSlotsAsync( CancellationToken token = default) { + if (IsSimulation) + { + + + return await SimulateAsync(nameof(CleanAllSlotsAsync), 0, token, p => { if (p.Success) { simulationSlots.Clear(); simulationSelection.Clear(); } return new GciPublicModels.GciCleanAllSlotsResult { Success = p.Success, Message = p.ErrorMessage }; }).ConfigureAwait(false); + } EnsureExternalInterface(); GciPublicModels.GciCleanAllSlotsResult result = @@ -693,6 +723,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge int slotId, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slotId); + + return await SimulateAsync(nameof(GetPcbIdAsync), slotId, token, p => { if (p.Success) SimSlot(slotId, p).PcbId = SimulationPcb(slotId, p); return new GciPublicModels.GciGetPcbIdResult { SlotId = slotId, Success = p.Success, PcbId = p.Success ? SimulationPcb(slotId, p) : null, Message = p.ErrorMessage }; }).ConfigureAwait(false); + } EnsureExternalInterface(); if (slotId <= 0) @@ -810,6 +846,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge int slot, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slot); + + return await SimulateAsync(nameof(ConnectAsync), slot, token, p => { if (p.Success) SimSlot(slot, p).IsConnected = true; return new GciPublicModels.GciConnectResult { SlotId = slot, Success = p.Success, IsConnected = p.Success, PcbId = p.Success ? SimulationPcb(slot, p) : null, Message = p.ErrorMessage }; }).ConfigureAwait(false); + } EnsureExternalInterface(); if (slot <= 0) @@ -842,6 +884,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge int slotId, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slotId); + + return await SimulateAsync(nameof(LoginAsync), slotId, token, p => { if (p.Success) { SimSlot(slotId, p).IsConnected = true; SimSlot(slotId, p).IsLoggedOn = true; } return new GciPublicModels.GciLoginResult { SlotId = slotId, Success = p.Success, IsLoggedOn = p.Success, PcbId = p.Success ? SimulationPcb(slotId, p) : null, Message = p.ErrorMessage }; }).ConfigureAwait(false); + } EnsureExternalInterface(); if (slotId <= 0) @@ -913,6 +961,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge int slotId, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slotId); + + return await SimulateAsync(nameof(DisconnectAsync), slotId, token, p => { if (p.Success && simulationSlots.ContainsKey(slotId)) { simulationSlots[slotId].IsConnected = false; simulationSlots[slotId].IsLoggedOn = false; } return new GciPublicModels.GciDisconnectResult { SlotId = slotId, Success = p.Success, Message = p.ErrorMessage }; }).ConfigureAwait(false); + } EnsureExternalInterface(); if (slotId <= 0) @@ -992,6 +1046,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge string password, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slotId); + if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Password is empty."); + return await SimulateAsync(nameof(SetPasswordAsync), slotId, token, p => { if (p.Success) SimSlot(slotId, p).Password = password; return new GciPublicModels.GciSetPasswordResult { SlotId = slotId, Success = p.Success, Password = p.Success ? password : null, Message = p.ErrorMessage }; }).ConfigureAwait(false); + } EnsureExternalInterface(); if (slotId <= 0) @@ -1068,6 +1128,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge string registerName, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slotId); + if (string.IsNullOrWhiteSpace(registerName)) throw new ArgumentException("Register name is empty."); + return await SimulateAsync(nameof(ReadRegisterAsync), slotId, token, p => new GciPublicModels.RegisterReadResult { Success = p.Success, RegisterName = registerName, RawHex = p.Success ? p.RawHex : null, ErrorMessage = p.ErrorMessage }, registerName).ConfigureAwait(false); + } EnsureExternalInterface(); if (slotId <= 0) @@ -1158,6 +1224,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge bool refreshSystemState = false, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slotId); + if (string.IsNullOrWhiteSpace(registerName)) throw new ArgumentException("Register name is empty."); + return await SimulateAsync(nameof(WriteRegisterAsync), slotId, token, p => new GciPublicModels.RegisterWriteResult { Success = p.Success, RegisterName = registerName, WrittenValue = value, StoreToDevice = storeToDevice, RefreshSystemState = refreshSystemState, ErrorMessage = p.ErrorMessage }, registerName).ConfigureAwait(false); + } EnsureExternalInterface(); if (slotId <= 0) @@ -1261,6 +1333,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge string pcbId, CancellationToken token = default) { + if (IsSimulation) + { + + if (string.IsNullOrWhiteSpace(pcbId)) throw new ArgumentException("PCB ID is empty."); + return await SimulateAsync(nameof(GetPasswordAsync), 0, token, p => { return new UdsPasswordResult { Success = p.Success, PcbId = pcbId, Password = p.Success ? p.Password : null, Message = p.ErrorMessage }; }).ConfigureAwait(false); + } EnsureReader(); if (string.IsNullOrWhiteSpace(pcbId)) @@ -1339,6 +1417,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge int meterSize, CancellationToken token = default) { + if (IsSimulation) + { + + if (meterSize < 0) throw new ArgumentOutOfRangeException("meterSize"); + return await SimulateAsync(nameof(GetPreAdjustmentCalibrationParamsAsync), 0, token, p => { return new UdsPreAdjustmentCalibrationParamsResult { Success = p.Success, MeterSize = meterSize, CalibrationParams = p.Success ? new Dictionary(p.CalibrationParams) : null, Message = p.ErrorMessage }; }).ConfigureAwait(false); + } EnsureReader(); if (meterSize < 0) @@ -1526,6 +1610,11 @@ namespace TBF.Rig.BridgeComponents.GciBridge /// Complete workflow result including all retry statistics and operation results. public async Task ConnectFullPassLoginWithRetryAsync(int slotId, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slotId); + return await SimFullLogin(slotId, token).ConfigureAwait(false); + } EnsureExternalInterface(); EnsureReader(); @@ -1581,6 +1670,14 @@ namespace TBF.Rig.BridgeComponents.GciBridge public PreAdjustmentInitializationResult Preadjustment_Initialization( ProcessProgress pp, List mc) { + if (IsSimulation) + { + if (pp == null || mc == null) throw new ArgumentNullException(pp == null ? "pp" : "mc"); + var slots = mc.Where(m => m.IsEnabled).Select(m => m.Slot).Distinct().ToArray(); + foreach (int slot in slots) ValidateSimulationSlot(slot); + GciPublicModels.PreAdjustmentProcessResult simulated = SimInitializeBatch(nameof(Preadjustment_Initialization), slots, CancellationToken.None).GetAwaiter().GetResult(); + return new PreAdjustmentInitializationResult { Success = simulated.Success, ErrorMessage = simulated.ErrorMessage, Slots = simulated.Success ? string.Join(",", slots) : "" }; + } const string operation = nameof(Preadjustment_Initialization); try @@ -1627,6 +1724,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge IEnumerable selectedSlots, CancellationToken token = default) { + if (IsSimulation) + { + return await SimDetect(selectedSlots, token).ConfigureAwait(false); + } const string operation = nameof(PreAdjustment_DetectAsync); try @@ -1666,6 +1767,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge IEnumerable selectedSlots, CancellationToken token = default) { + if (IsSimulation) + { + return SimDetect(selectedSlots, token).GetAwaiter().GetResult(); + } const string operation = nameof(PreAdjustment_DetectDirect); try @@ -1700,6 +1805,11 @@ namespace TBF.Rig.BridgeComponents.GciBridge int slot, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slot); + return await SimPhase("PreAdjustment_Preparation", slot, token).ConfigureAwait(false); + } const string operation = nameof(PreAdjustment_PreparationAsync); try @@ -1745,6 +1855,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge public PreAdjustmentProcessResult PreAdjustment_PreparationDirect() { + if (IsSimulation) + { + return SimBatchPhase("PreAdjustment_Preparation", CancellationToken.None).GetAwaiter().GetResult(); + } const string operation = nameof(PreAdjustment_PreparationDirect); try @@ -1780,6 +1894,11 @@ namespace TBF.Rig.BridgeComponents.GciBridge int slot, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slot); + return await SimPhase("PreAdjustment_AmplitudeTest", slot, token).ConfigureAwait(false); + } const string operation = nameof(PreAdjustment_AmplitudeTestAsync); try @@ -1825,6 +1944,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge public PreAdjustmentProcessResult PreAdjustment_AmplitudeTestDirect() { + if (IsSimulation) + { + return SimBatchPhase("PreAdjustment_AmplitudeTest", CancellationToken.None).GetAwaiter().GetResult(); + } const string operation = nameof(PreAdjustment_AmplitudeTestDirect); try @@ -1860,6 +1983,11 @@ namespace TBF.Rig.BridgeComponents.GciBridge int slot, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slot); + return await SimPhase("PreAdjustment_TemperatureCalibration", slot, token).ConfigureAwait(false); + } const string operation = nameof(PreAdjustment_TemperatureCalibrationAsync); try @@ -1906,6 +2034,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge public bool PreAdjustment_PushTemperature( double temperature) { + if (IsSimulation) + { + return SimBatchPhase(nameof(PreAdjustment_PushTemperature), CancellationToken.None).GetAwaiter().GetResult().Success; + } const string operation = nameof(PreAdjustment_PushTemperature); try @@ -1932,6 +2064,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge public PreAdjustmentProcessResult PreAdjustment_TemperatureCalibrationDirect() { + if (IsSimulation) + { + return SimBatchPhase("PreAdjustment_TemperatureCalibration", CancellationToken.None).GetAwaiter().GetResult(); + } const string operation = nameof(PreAdjustment_TemperatureCalibrationDirect); try @@ -1967,6 +2103,11 @@ namespace TBF.Rig.BridgeComponents.GciBridge int slot, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slot); + return await SimPhase("PreAdjustment_OffsetTest", slot, token).ConfigureAwait(false); + } const string operation = nameof(PreAdjustment_OffsetTestAsync); try @@ -2012,6 +2153,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge public PreAdjustmentProcessResult PreAdjustment_OffsetTestDirect() { + if (IsSimulation) + { + return SimBatchPhase("PreAdjustment_OffsetTest", CancellationToken.None).GetAwaiter().GetResult(); + } const string operation = nameof(PreAdjustment_OffsetTestDirect); try @@ -2047,6 +2192,11 @@ namespace TBF.Rig.BridgeComponents.GciBridge int slot, CancellationToken token = default) { + if (IsSimulation) + { + ValidateSimulationSlot(slot); + return await SimPhase("PreAdjustment_Completion", slot, token).ConfigureAwait(false); + } const string operation = nameof(PreAdjustment_CompletionAsync); try @@ -2092,6 +2242,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge public PreAdjustmentProcessResult PreAdjustment_CompletionDirect() { + if (IsSimulation) + { + return SimBatchPhase("PreAdjustment_Completion", CancellationToken.None).GetAwaiter().GetResult(); + } const string operation = nameof(PreAdjustment_CompletionDirect); try @@ -2158,8 +2312,12 @@ namespace TBF.Rig.BridgeComponents.GciBridge public List GetAllRegisterNames() { + if (IsSimulation) + { + return Simulation.Registers.Keys.Select(k => k.Substring(k.IndexOf(':') + 1)).Distinct().ToList(); + } EnsureExternalInterface(); return _gciEngine.gciExternalInterface.GetAllRegisterNames(); } } -} \ No newline at end of file +} diff --git a/TBF/Rig/BridgeComponents/GciBridge/GciSimulationSettings.cs b/TBF/Rig/BridgeComponents/GciBridge/GciSimulationSettings.cs new file mode 100644 index 000000000..6cc5bb833 --- /dev/null +++ b/TBF/Rig/BridgeComponents/GciBridge/GciSimulationSettings.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; + +namespace TBF.Rig.BridgeComponents.GciBridge +{ + // Source-editable simulation profiles. Configure before starting a test. + public sealed class GciSimulationProfile + { + public int DurationMs { get; set; } = 5000; + public bool Success { get; set; } = true; + public string ErrorMessage { get; set; } = ""; + public string PcbId { get; set; } // Null generates a deterministic nine-digit ID per slot. + public string Password { get; set; } = "000000000000"; + public string RawHex { get; set; } = "00000000"; + public Dictionary CalibrationParams { get; set; } = new Dictionary(); + + internal GciSimulationProfile Snapshot() + { + if (DurationMs < 0) throw new ArgumentOutOfRangeException("DurationMs"); + return new GciSimulationProfile + { + DurationMs = DurationMs, Success = Success, ErrorMessage = ErrorMessage, + PcbId = PcbId, Password = Password, RawHex = RawHex, + CalibrationParams = new Dictionary(CalibrationParams) + }; + } + } + + public sealed class GciSimulationSettings + { + public Dictionary Operations { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "MeterActivity", new GciSimulationProfile() }, + { "InitSlotAsync", new GciSimulationProfile() }, + { "UpdateSlotAsync", new GciSimulationProfile() }, + { "GetSlotAsync", new GciSimulationProfile() }, + { "CleanSlotAsync", new GciSimulationProfile() }, + { "CleanAllSlotsAsync", new GciSimulationProfile() }, + { "GetPcbIdAsync", new GciSimulationProfile() }, + { "ConnectAsync", new GciSimulationProfile() }, + { "LoginAsync", new GciSimulationProfile() }, + { "DisconnectAsync", new GciSimulationProfile() }, + { "SetPasswordAsync", new GciSimulationProfile() }, + { "ReadRegisterAsync", new GciSimulationProfile() }, + { "WriteRegisterAsync", new GciSimulationProfile() }, + { "GetPasswordAsync", new GciSimulationProfile() }, + { "GetPreAdjustmentCalibrationParamsAsync", new GciSimulationProfile() }, + { "ConnectFullPassLoginWithRetryAsync", new GciSimulationProfile() }, + { "Preadjustment_Initialization", new GciSimulationProfile() }, + { "PreAdjustment_Detect", new GciSimulationProfile() }, + { "PreAdjustment_Preparation", new GciSimulationProfile() }, + { "PreAdjustment_AmplitudeTest", new GciSimulationProfile() }, + { "PreAdjustment_TemperatureCalibration", new GciSimulationProfile() }, + { "PreAdjustment_PushTemperature", new GciSimulationProfile() }, + { "PreAdjustment_OffsetTest", new GciSimulationProfile() }, + { "PreAdjustment_Completion", new GciSimulationProfile() }, + }; + + // Optional exact register profiles, e.g. WriteRegisterAsync:GENESISFLOW_TriggerActive. + public Dictionary Registers { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + + internal GciSimulationProfile Get(string operation, string register) + { + GciSimulationProfile profile; + if (register != null && Registers.TryGetValue(operation + ":" + register, out profile)) return profile.Snapshot(); + if (!Operations.TryGetValue(operation, out profile)) throw new InvalidOperationException("Missing simulation profile: " + operation); + return profile.Snapshot(); + } + } +} diff --git a/TBF/Rig/TestMethods/GenesisCommunication/GenesisCommunicationSeq.cs b/TBF/Rig/TestMethods/GenesisCommunication/GenesisCommunicationSeq.cs index a4c12f250..f62d22ffc 100644 --- a/TBF/Rig/TestMethods/GenesisCommunication/GenesisCommunicationSeq.cs +++ b/TBF/Rig/TestMethods/GenesisCommunication/GenesisCommunicationSeq.cs @@ -51,7 +51,18 @@ namespace TBF.Rig.TestMethods.GenesisCommunication System.Windows.Forms.Form modelessDlg; - private GenesisCommunicationDiagnosticsForm preadjustmentDiagnosticsForm; + private GenesisCommunicationDiagnosticsForm diagnosticsForm; + + private void DiagnosticUi(Action action) + { + try + { + if (Program.MainWnd == null || Program.MainWnd.IsDisposed) return; + if (Program.MainWnd.InvokeRequired) Program.MainWnd.Invoke(action); + else action(); + } + catch (Exception ex) { log.Warn("GCI diagnostic UI failed; execution continues.", ex); } + } /// delegate void iPerlCommFormDlgt(GenesisCommunicationSeq myRef, TestMethod method, Test test, iPerlCommunicationParams testParams); /// @@ -71,40 +82,6 @@ namespace TBF.Rig.TestMethods.GenesisCommunication modelessDlg = null; } - private delegate void PreadjustmentDiagnosticsFormDelegate(GenesisCommunicationSeq sequence, GciBridgeType bridge, string activity); - - private void OpenPreadjustmentDiagnosticsForm(GenesisCommunicationSeq sequence, GciBridgeType bridge, string activity) - { - sequence.ClosePreadjustmentDiagnosticsForm(); - sequence.preadjustmentDiagnosticsForm = new GenesisCommunicationDiagnosticsForm(bridge, activity); - sequence.preadjustmentDiagnosticsForm.Show(); - } - - private void ClosePreadjustmentDiagnosticsForm() - { - if (preadjustmentDiagnosticsForm == null) - return; - - if (!preadjustmentDiagnosticsForm.IsDisposed) - preadjustmentDiagnosticsForm.Close(); - - preadjustmentDiagnosticsForm.Dispose(); - preadjustmentDiagnosticsForm = null; - } - - private void ShowPreadjustmentDiagnostics(GciBridgeType bridge, string activity) - { - Program.MainWnd.Invoke(new PreadjustmentDiagnosticsFormDelegate(OpenPreadjustmentDiagnosticsForm), new object[] { this, bridge, activity }); - } - - private void HidePreadjustmentDiagnostics() - { - if (Program.MainWnd == null || Program.MainWnd.IsDisposed) - return; - - Program.MainWnd.Invoke(new Action(ClosePreadjustmentDiagnosticsForm)); - } - /// /// Flying start mass collection method sequence @@ -127,7 +104,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication processDataLoggingOp = new TBF.Rig.Operations.ProcessDataLoggingOp(processDataLogger, this, false); iPerlCommunicationParams genesisTestParams = testParams as iPerlCommunicationParams; - if (genesisTestParams != null && IsPreadjustmentActivity(genesisTestParams.Activity)) + if (genesisTestParams != null && (IsPreadjustmentActivity(genesisTestParams.Activity) || method.DebugLevel == DebugMode.Simulate)) { return ExecutePreadjustment(test, method, genesisTestParams); } @@ -471,6 +448,52 @@ namespace TBF.Rig.TestMethods.GenesisCommunication return new List { Event.Done }; } + private static bool ExecuteSimulatedMeterActivity(GciBridgeType bridge, IList> readers, iPerlCommunicationParams parameters, out string response) + { + var tasks = readers.Select(item => System.Threading.Tasks.Task.Run(async () => + { + int slot = item.Value.GetSlotNr; + string activity = parameters.Activity; + bool ok; + string message; + switch (activity) + { + case GenesisCommunicationForm.SlotInitializeStr: + case GenesisCommunicationForm.SlotUpdateStr: + var request = new GciPublicModels.GciInitSlotRequest { SlotId = slot }; + var init = activity == GenesisCommunicationForm.SlotInitializeStr ? await bridge.InitSlotAsync(request) : await bridge.UpdateSlotAsync(request); + ok = init.Success; message = init.Message; break; + case GenesisCommunicationForm.SlotConnectStr: + var connected = await bridge.ConnectAsync(slot); ok = connected.Success; message = connected.Message; break; + case GenesisCommunicationForm.SlotPCBSlotStr: + var pcb = await bridge.GetPcbIdAsync(slot); ok = pcb.Success; message = pcb.ToString(); break; + case GenesisCommunicationForm.SlotSetPasswordStr: + var password = await bridge.SetPasswordAsync(slot, bridge.Simulation.Operations["SetPasswordAsync"].Password); ok = password.Success; message = password.Message; break; + case GenesisCommunicationForm.SlotLoginStr: + var login = await bridge.LoginAsync(slot); ok = login.Success; message = login.Message; break; + case GenesisCommunicationForm.SlotGroupedLoginStr: + var full = await bridge.ConnectFullPassLoginWithRetryAsync(slot); ok = full.Success; message = full.Message; break; + case GenesisCommunicationForm.SlotDisconnectStr: + var disconnected = await bridge.DisconnectAsync(slot); ok = disconnected.Success; message = disconnected.Message; break; + case GenesisCommunicationForm.SlotSetTestModeStr: + case GenesisCommunicationForm.SlotSetActiveModeStr: + var mode = await bridge.WriteRegisterAsync(slot, TBF.Rig.RegisterReaders.GenesisRegReader.communication.RadioService.LedMode, activity == GenesisCommunicationForm.SlotSetTestModeStr ? (byte)6 : (byte)0); + ok = mode.Success; message = mode.ErrorMessage; break; + case GenesisCommunicationForm.PrepareSlotQ3CalibrationStr: + case GenesisCommunicationForm.WriteSlotQ3CalibrationStr: + case GenesisCommunicationForm.CheckMeterPrepareStr: + case GenesisCommunicationForm.HoldSlotStr: + var aggregate = await bridge.SimulateMeterActivityAsync(activity, slot); ok = aggregate.Success; message = aggregate.ErrorMessage; break; + default: + throw new InvalidOperationException("No simulated Genesis activity mapping: " + activity); + } + return new KeyValuePair(ok, string.Format("Slot={0}, Success={1}, Message={2}", slot, ok, message)); + })).ToArray(); + var results = System.Threading.Tasks.Task.WhenAll(tasks).GetAwaiter().GetResult(); + response = string.Join(Environment.NewLine, results.Select(r => r.Value)); + return results.All(r => r.Key); + } + private static bool IsPreadjustmentActivity(string activity) { return string.Equals(activity, PreadjustmentInitializationStr, StringComparison.OrdinalIgnoreCase) || @@ -486,9 +509,12 @@ namespace TBF.Rig.TestMethods.GenesisCommunication private IList ExecutePreadjustment(Test test, TestMethod method, iPerlCommunicationParams testParams) { DateTime startTime = DateTime.Now; + GenesisPreadjustmentBatchState batchState = null; + bool preadjustment = IsPreadjustmentActivity(testParams.Activity); bool success = false; string response = string.Empty; List> readers = new List>(); + List> resultReaders = readers; TestProgressEventArgs.SetEstimatedTimes(new int[] { 0, 0, 0, 30, 0, 30, 0, 0 }); Bridge.OnTestProgress(this, new TestProgressEventArgs(test, Progress.JustStarted)); @@ -506,7 +532,16 @@ namespace TBF.Rig.TestMethods.GenesisCommunication try { readers = GetPreadjustmentReaders(test); + resultReaders = readers; GciBridgeType bridge = GetCommonGciBridge(readers); + if (preadjustment) batchState = GenesisPreadjustmentBatchState.For(ProcessData.BatchRslts, bridge); + if (preadjustment) + { + foreach (int slot in batchState.Slots.Where(batchState.IsExcluded)) bridge.ExcludePreadjustmentSlot(slot); + readers = readers.Where(item => !batchState.IsExcluded(item.Value.GetSlotNr)).ToList(); + } + if (method.DebugLevel == DebugMode.Simulate && !bridge.IsSimulation) + throw new InvalidOperationException("GenesisCommunication is simulated, but GCI Bridge is not in Simulate mode."); preadjustmentLogger.Info( "[PREADJUSTMENT] CONTEXT Test={0}, Activity={1}, Bridge={2}, Slots={3}, ReaderCount={4}", @@ -516,11 +551,66 @@ namespace TBF.Rig.TestMethods.GenesisCommunication string.Join(",", readers.Select(item => item.Value.GetSlotNr)), readers.Count); - ShowPreadjustmentDiagnostics(bridge, testParams.Activity); - success = ExecutePreadjustmentActivity(bridge, readers, testParams, out response); + if (preadjustment) DiagnosticUi(() => + { + diagnosticsForm = new GenesisCommunicationDiagnosticsForm(bridge, testParams.Activity, batchState); + if (!bridge.IsSimulation) diagnosticsForm.AttachProgress(bridge.gciExternalInterface._innerMeterAPI._progressProcess); + diagnosticsForm.Show(Program.MainWnd); + }); + try + { + if (preadjustment) batchState.Start(readers.Select(item => item.Value.GetSlotNr), testParams.Activity); + if (readers.Count == 0) throw new InvalidOperationException("No eligible slots remain: failed slots are excluded for this batch."); + using (method.DebugLevel == DebugMode.Simulate ? bridge.BeginSimulation(new TBF.Rig.BridgeComponents.GciBridge.GciSimulationProfile { DurationMs = testParams.SimulationDurationMs, Success = testParams.SimulationSuccess, ErrorMessage = testParams.SimulationErrorMessage }) : null) + { + success = IsPreadjustmentActivity(testParams.Activity) + ? ExecutePreadjustmentActivity(bridge, readers, testParams, out response) + : ExecuteSimulatedMeterActivity(bridge, readers, testParams, out response); + } + } + catch (Exception ex) + { + response = ex.Message; + throw; + } + finally + { + try + { + if (preadjustment && batchState != null) + { + foreach (var item in readers) + { + int slot = item.Value.GetSlotNr; + bool passed = success; + if (passed && !bridge.IsSimulation && !string.Equals(testParams.Activity, PreadjustmentInitializationStr, StringComparison.OrdinalIgnoreCase)) + { + var controls = bridge.gciExternalInterface._innerMeterAPI._meterControls; + var control = controls == null ? null : controls.FirstOrDefault(c => c.Slot == slot); + passed = control != null && control.IsEnabled && !control.Failed; + } + batchState.Finish(slot, testParams.Activity, passed, response); + if (!passed) bridge.ExcludePreadjustmentSlot(slot); + } + } + } + finally + { + DiagnosticUi(() => + { + if (diagnosticsForm != null) + { + diagnosticsForm.RefreshProgress(); + diagnosticsForm.Dispose(); + } + diagnosticsForm = null; + }); + } + } } catch (Exception ex) { + success = false; response = ex.Message; log.Error(string.Format("{0} failed.", testParams.Activity), ex); @@ -533,12 +623,8 @@ namespace TBF.Rig.TestMethods.GenesisCommunication (long)(DateTime.Now - startTime).TotalMilliseconds, ex.Message); } - finally - { - HidePreadjustmentDiagnostics(); - } - UpdatePreadjustmentResult(test, method, readers, success, startTime); + UpdatePreadjustmentResult(test, method, resultReaders, success, startTime, preadjustment ? batchState : null); if (success) { @@ -621,7 +707,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication return commonBridge; } - private static bool ExecutePreadjustmentActivity(GciBridgeType bridge, IList> readers, iPerlCommunicationParams testParams, out string response) + private bool ExecutePreadjustmentActivity(GciBridgeType bridge, IList> readers, iPerlCommunicationParams testParams, out string response) { preadjustmentLogger.Debug( "[PREADJUSTMENT] DISPATCH Activity={0}, Bridge={1}, Slots={2}, Thread={3}", @@ -658,6 +744,10 @@ namespace TBF.Rig.TestMethods.GenesisCommunication meterControls.Add(meterControl); } + DiagnosticUi(() => + { + if (diagnosticsForm != null && !diagnosticsForm.IsDisposed) diagnosticsForm.AttachProgress(progress); + }); PublicModels.PreAdjustmentInitializationResult result = bridge.Preadjustment_Initialization(progress, meterControls); response = result.ToString(); @@ -728,7 +818,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication return processResult.Success; } - private static void UpdatePreadjustmentResult(Test test, TestMethod method, IEnumerable> readers, bool success, DateTime startTime) + private static void UpdatePreadjustmentResult(Test test, TestMethod method, IEnumerable> readers, bool success, DateTime startTime, GenesisPreadjustmentBatchState batchState) { TestRslt testResult = ProcessData.BatchRslts.GetTestRslt(test.Name, test.Part); if (testResult == null) @@ -747,7 +837,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication continue; meterResult.WaterMeter.SerialNr = item.Value.SerialNr; - meterResult.Passed = success; + meterResult.Passed = success && (batchState == null || !batchState.IsExcluded(item.Value.GetSlotNr)); meterResult.TestDone = true; preadjustmentLogger.Debug( @@ -756,7 +846,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication test.Part, item.Key, item.Value.GetSlotNr, - success); + meterResult.Passed); } } diff --git a/TBF/Rig/TestMethods/GenesisCommunication/GenesisPreadjustmentBatchState.cs b/TBF/Rig/TestMethods/GenesisCommunication/GenesisPreadjustmentBatchState.cs new file mode 100644 index 000000000..c20af78ca --- /dev/null +++ b/TBF/Rig/TestMethods/GenesisCommunication/GenesisPreadjustmentBatchState.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using GciBridgeType = TBF.Rig.BridgeComponents.GciBridge.GciBridge; + +namespace TBF.Rig.TestMethods.GenesisCommunication +{ + // Owned by the batch result object, never by the diagnostic window. + internal sealed class GenesisPreadjustmentBatchState + { + private static readonly ConditionalWeakTable> batches = new ConditionalWeakTable>(); + private readonly object sync = new object(); + private readonly Dictionary> phases = new Dictionary>(); + private readonly Dictionary> logs = new Dictionary>(); + private readonly HashSet excluded = new HashSet(); + public static GenesisPreadjustmentBatchState For(object batch, GciBridgeType bridge) + { + lock (batches) + { + var states = batches.GetOrCreateValue(batch); + GenesisPreadjustmentBatchState state; + if (!states.TryGetValue(bridge, out state)) states.Add(bridge, state = new GenesisPreadjustmentBatchState()); + return state; + } + } + public int[] Slots { get { lock (sync) return phases.Keys.ToArray(); } } + public bool IsExcluded(int slot) { lock (sync) return excluded.Contains(slot); } + public string Phase(int slot, string phase) + { + lock (sync) + { + Dictionary values; + string value; + return phases.TryGetValue(slot, out values) && values.TryGetValue(phase, out value) ? value : "Pending"; + } + } + public void Start(IEnumerable slots, string phase) + { + lock (sync) + { + foreach (int slot in slots) + { + if (!phases.ContainsKey(slot)) phases.Add(slot, new Dictionary(StringComparer.OrdinalIgnoreCase)); + if (excluded.Contains(slot)) continue; + phases[slot][phase] = "Running"; + AddLog(slot, phase + " — START"); + } + } + } + public void Finish(int slot, string phase, bool success, string message) + { + lock (sync) + { + if (!phases.ContainsKey(slot)) phases.Add(slot, new Dictionary(StringComparer.OrdinalIgnoreCase)); + phases[slot][phase] = success ? "Passed" : "Failed"; + if (!success) excluded.Add(slot); + AddLog(slot, phase + " — Success=" + success + ". " + message); + } + } + public void AddLog(int slot, string message) + { + if (slot <= 0) return; + lock (sync) + { + Queue queue; + if (!logs.TryGetValue(slot, out queue)) logs.Add(slot, queue = new Queue()); + queue.Enqueue(DateTime.Now.ToString("HH:mm:ss.fff") + " " + message); + while (queue.Count > 1000) queue.Dequeue(); + } + } + public string LogText(int slot) { lock (sync) return logs.ContainsKey(slot) ? string.Join(Environment.NewLine, logs[slot]) : ""; } + } +} diff --git a/TBF/Rig/TestMethods/GenesisCommunication/TestMethod.cs b/TBF/Rig/TestMethods/GenesisCommunication/TestMethod.cs index aad87d9c1..e510bc157 100644 --- a/TBF/Rig/TestMethods/GenesisCommunication/TestMethod.cs +++ b/TBF/Rig/TestMethods/GenesisCommunication/TestMethod.cs @@ -112,7 +112,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication public IList Execute(Test test, int repetNr, bool isLastRepetition) { - if (DebugLevel == DebugMode.Normal) + if (DebugLevel == DebugMode.Normal || DebugLevel == DebugMode.Simulate) { return (new GenesisCommunicationSeq()).Execute(test, repetNr, this, testMethodCfg.TestParams); } diff --git a/TBF/Rig/TestMethods/GenesisCommunication/TestMethodCfg.cs b/TBF/Rig/TestMethods/GenesisCommunication/TestMethodCfg.cs index e35cd3b8e..9ff37e6f1 100644 --- a/TBF/Rig/TestMethods/GenesisCommunication/TestMethodCfg.cs +++ b/TBF/Rig/TestMethods/GenesisCommunication/TestMethodCfg.cs @@ -23,11 +23,25 @@ namespace TBF.Rig.TestMethods.GenesisCommunication public IComponentCfgCtrl GetControl(IList cmpntEntities) { return new TestMethodCfgCtrl(); } // - public override IParamsProvider GetRuntimeTestParamsProvider() { return TestParams; } + public override IParamsProvider GetRuntimeTestParamsProvider() + { + var parameters = TestParams as iPerlCommunicationParams; + if (parameters != null) parameters.ShowSimulationParameters = SimulationColumnsEnabled(); + return TestParams; + } public override IParamsProvider CreateTestParamsProvider() { return new iPerlCommunicationParams(true); } public override IParamsProvider GetUITestParamsProvider(Test test) { - return (test.Method == Name) ? base.GetUITestParamsProvider(test) : null; + var provider = (test.Method == Name) ? base.GetUITestParamsProvider(test) : null; + var genesis = provider as iPerlCommunicationParams; + if (genesis != null) genesis.ShowSimulationParameters = SimulationColumnsEnabled(); + return provider; + } + + private bool SimulationColumnsEnabled() + { + var component = TbfComponents.FindComponent(Name) as TestMethod; + return component != null && component.DebugLevel == DebugMode.Simulate; } /// Private parameterless constructor invoked by all other (public) constructors diff --git a/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.Designer.cs b/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.Designer.cs index f942bf50e..f272ec68a 100644 --- a/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.Designer.cs +++ b/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.Designer.cs @@ -1,346 +1,103 @@ -namespace TBF.Rig.TestMethods.GenesisCommunication +namespace TBF.Rig.TestMethods.GenesisCommunication { partial class GenesisCommunicationDiagnosticsForm { - private System.ComponentModel.IContainer components = null; + private System.ComponentModel.IContainer components; + private System.Windows.Forms.SplitContainer verticalSplit; + private System.Windows.Forms.SplitContainer overviewSplit; + private System.Windows.Forms.DataGridView slotsGrid; + private System.Windows.Forms.DataGridView workersGrid; + private System.Windows.Forms.TabControl slotTabs; + private System.Windows.Forms.Timer refreshTimer; + private System.Windows.Forms.StatusStrip statusStrip; + private System.Windows.Forms.ToolStripStatusLabel statusLabel; + private System.Windows.Forms.ToolStrip toolbar; + private System.Windows.Forms.ToolStripButton refreshButton; + private System.Windows.Forms.ToolStripButton bridgeButton; protected override void Dispose(bool disposing) { - if (disposing && (components != null)) - components.Dispose(); - + if (disposing) + { + ReleaseDiagnostics(); + if (components != null) components.Dispose(); + } base.Dispose(disposing); } - #region Windows Form Designer generated code - private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - System.Windows.Forms.DataGridViewCellStyle lastActivityCellStyle = new System.Windows.Forms.DataGridViewCellStyle(); - this.diagnosticsToolStrip = new System.Windows.Forms.ToolStrip(); - this.activityToolStripLabel = new System.Windows.Forms.ToolStripLabel(); - this.refreshToolStripButton = new System.Windows.Forms.ToolStripButton(); - this.showBridgeToolStripButton = new System.Windows.Forms.ToolStripButton(); - this.mainSplitContainer = new System.Windows.Forms.SplitContainer(); - this.slotsGroupBox = new System.Windows.Forms.GroupBox(); - this.slotsDataGridView = new System.Windows.Forms.DataGridView(); - this.slotColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.selectedColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn(); - this.pcbIdColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.connectedColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn(); - this.loggedOnColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn(); - this.fwVersionColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.requestPortColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.requestPortTypeColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.streamingPortColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.interfaceVersionColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.workersGroupBox = new System.Windows.Forms.GroupBox(); - this.workersDataGridView = new System.Windows.Forms.DataGridView(); - this.workerSlotColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.workerNameColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.queueLengthColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.busyColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn(); - this.currentOperationColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.lastErrorColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.lastActivityColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.diagnosticsStatusStrip = new System.Windows.Forms.StatusStrip(); - this.statusToolStripStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.verticalSplit = new System.Windows.Forms.SplitContainer(); + this.overviewSplit = new System.Windows.Forms.SplitContainer(); + this.slotsGrid = new System.Windows.Forms.DataGridView(); + this.workersGrid = new System.Windows.Forms.DataGridView(); + this.slotTabs = new System.Windows.Forms.TabControl(); this.refreshTimer = new System.Windows.Forms.Timer(this.components); - this.diagnosticsToolStrip.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.mainSplitContainer)).BeginInit(); - this.mainSplitContainer.Panel1.SuspendLayout(); - this.mainSplitContainer.Panel2.SuspendLayout(); - this.mainSplitContainer.SuspendLayout(); - this.slotsGroupBox.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.slotsDataGridView)).BeginInit(); - this.workersGroupBox.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.workersDataGridView)).BeginInit(); - this.diagnosticsStatusStrip.SuspendLayout(); + this.statusStrip = new System.Windows.Forms.StatusStrip(); + this.statusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.toolbar = new System.Windows.Forms.ToolStrip(); + this.refreshButton = new System.Windows.Forms.ToolStripButton(); + this.bridgeButton = new System.Windows.Forms.ToolStripButton(); + ((System.ComponentModel.ISupportInitialize)(this.verticalSplit)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.overviewSplit)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.slotsGrid)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.workersGrid)).BeginInit(); this.SuspendLayout(); - // - // diagnosticsToolStrip - // - this.diagnosticsToolStrip.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; - this.diagnosticsToolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.activityToolStripLabel, - this.refreshToolStripButton, - this.showBridgeToolStripButton}); - this.diagnosticsToolStrip.Location = new System.Drawing.Point(0, 0); - this.diagnosticsToolStrip.Name = "diagnosticsToolStrip"; - this.diagnosticsToolStrip.Size = new System.Drawing.Size(1234, 25); - this.diagnosticsToolStrip.TabIndex = 0; - // - // activityToolStripLabel - // - this.activityToolStripLabel.Name = "activityToolStripLabel"; - this.activityToolStripLabel.Size = new System.Drawing.Size(99, 22); - this.activityToolStripLabel.Text = "Running activity:"; - // - // refreshToolStripButton - // - this.refreshToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; - this.refreshToolStripButton.Name = "refreshToolStripButton"; - this.refreshToolStripButton.Size = new System.Drawing.Size(50, 22); - this.refreshToolStripButton.Text = "Refresh"; - this.refreshToolStripButton.Click += new System.EventHandler(this.refreshToolStripButton_Click); - // - // showBridgeToolStripButton - // - this.showBridgeToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; - this.showBridgeToolStripButton.Name = "showBridgeToolStripButton"; - this.showBridgeToolStripButton.Size = new System.Drawing.Size(124, 22); - this.showBridgeToolStripButton.Text = "Open GCI Bridge GUI"; - this.showBridgeToolStripButton.Click += new System.EventHandler(this.showBridgeToolStripButton_Click); - // - // mainSplitContainer - // - this.mainSplitContainer.Dock = System.Windows.Forms.DockStyle.Fill; - this.mainSplitContainer.Location = new System.Drawing.Point(0, 25); - this.mainSplitContainer.Name = "mainSplitContainer"; - this.mainSplitContainer.Panel1.Controls.Add(this.slotsGroupBox); - this.mainSplitContainer.Panel2.Controls.Add(this.workersGroupBox); - this.mainSplitContainer.Size = new System.Drawing.Size(1234, 534); - this.mainSplitContainer.SplitterDistance = 710; - this.mainSplitContainer.TabIndex = 1; - // - // slotsGroupBox - // - this.slotsGroupBox.Controls.Add(this.slotsDataGridView); - this.slotsGroupBox.Dock = System.Windows.Forms.DockStyle.Fill; - this.slotsGroupBox.Location = new System.Drawing.Point(0, 0); - this.slotsGroupBox.Name = "slotsGroupBox"; - this.slotsGroupBox.Padding = new System.Windows.Forms.Padding(6); - this.slotsGroupBox.Size = new System.Drawing.Size(710, 534); - this.slotsGroupBox.TabIndex = 0; - this.slotsGroupBox.TabStop = false; - this.slotsGroupBox.Text = "GCI slots"; - // - // slotsDataGridView - // - this.slotsDataGridView.AllowUserToAddRows = false; - this.slotsDataGridView.AllowUserToDeleteRows = false; - this.slotsDataGridView.AllowUserToResizeRows = false; - this.slotsDataGridView.AutoGenerateColumns = false; - this.slotsDataGridView.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.DisplayedCells; - this.slotsDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.slotsDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { - this.slotColumn, - this.selectedColumn, - this.pcbIdColumn, - this.connectedColumn, - this.loggedOnColumn, - this.fwVersionColumn, - this.requestPortColumn, - this.requestPortTypeColumn, - this.streamingPortColumn, - this.interfaceVersionColumn}); - this.slotsDataGridView.Dock = System.Windows.Forms.DockStyle.Fill; - this.slotsDataGridView.Location = new System.Drawing.Point(6, 19); - this.slotsDataGridView.MultiSelect = false; - this.slotsDataGridView.Name = "slotsDataGridView"; - this.slotsDataGridView.ReadOnly = true; - this.slotsDataGridView.RowHeadersWidth = 35; - this.slotsDataGridView.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; - this.slotsDataGridView.Size = new System.Drawing.Size(698, 509); - this.slotsDataGridView.TabIndex = 0; - // - // slot columns - // - this.slotColumn.DataPropertyName = "Slot"; - this.slotColumn.HeaderText = "Slot"; - this.slotColumn.Name = "slotColumn"; - this.slotColumn.ReadOnly = true; - this.selectedColumn.DataPropertyName = "Selected"; - this.selectedColumn.HeaderText = "Selected"; - this.selectedColumn.Name = "selectedColumn"; - this.selectedColumn.ReadOnly = true; - this.pcbIdColumn.DataPropertyName = "PcbId"; - this.pcbIdColumn.HeaderText = "PCB ID"; - this.pcbIdColumn.Name = "pcbIdColumn"; - this.pcbIdColumn.ReadOnly = true; - this.connectedColumn.DataPropertyName = "IsConnected"; - this.connectedColumn.HeaderText = "Connected"; - this.connectedColumn.Name = "connectedColumn"; - this.connectedColumn.ReadOnly = true; - this.loggedOnColumn.DataPropertyName = "IsLoggedOn"; - this.loggedOnColumn.HeaderText = "Logged on"; - this.loggedOnColumn.Name = "loggedOnColumn"; - this.loggedOnColumn.ReadOnly = true; - this.fwVersionColumn.DataPropertyName = "FwVersion"; - this.fwVersionColumn.HeaderText = "FW version"; - this.fwVersionColumn.Name = "fwVersionColumn"; - this.fwVersionColumn.ReadOnly = true; - this.requestPortColumn.DataPropertyName = "RequestPort"; - this.requestPortColumn.HeaderText = "Request port"; - this.requestPortColumn.Name = "requestPortColumn"; - this.requestPortColumn.ReadOnly = true; - this.requestPortTypeColumn.DataPropertyName = "RequestPortType"; - this.requestPortTypeColumn.HeaderText = "Request type"; - this.requestPortTypeColumn.Name = "requestPortTypeColumn"; - this.requestPortTypeColumn.ReadOnly = true; - this.streamingPortColumn.DataPropertyName = "StreamingPort"; - this.streamingPortColumn.HeaderText = "Streaming port"; - this.streamingPortColumn.Name = "streamingPortColumn"; - this.streamingPortColumn.ReadOnly = true; - this.interfaceVersionColumn.DataPropertyName = "InterfaceVersion"; - this.interfaceVersionColumn.HeaderText = "Interface version"; - this.interfaceVersionColumn.Name = "interfaceVersionColumn"; - this.interfaceVersionColumn.ReadOnly = true; - // - // workersGroupBox - // - this.workersGroupBox.Controls.Add(this.workersDataGridView); - this.workersGroupBox.Dock = System.Windows.Forms.DockStyle.Fill; - this.workersGroupBox.Location = new System.Drawing.Point(0, 0); - this.workersGroupBox.Name = "workersGroupBox"; - this.workersGroupBox.Padding = new System.Windows.Forms.Padding(6); - this.workersGroupBox.Size = new System.Drawing.Size(520, 534); - this.workersGroupBox.TabIndex = 0; - this.workersGroupBox.TabStop = false; - this.workersGroupBox.Text = "GCI workers"; - // - // workersDataGridView - // - this.workersDataGridView.AllowUserToAddRows = false; - this.workersDataGridView.AllowUserToDeleteRows = false; - this.workersDataGridView.AllowUserToResizeRows = false; - this.workersDataGridView.AutoGenerateColumns = false; - this.workersDataGridView.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.DisplayedCells; - this.workersDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.workersDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { - this.workerSlotColumn, - this.workerNameColumn, - this.queueLengthColumn, - this.busyColumn, - this.currentOperationColumn, - this.lastErrorColumn, - this.lastActivityColumn}); - this.workersDataGridView.Dock = System.Windows.Forms.DockStyle.Fill; - this.workersDataGridView.Location = new System.Drawing.Point(6, 19); - this.workersDataGridView.MultiSelect = false; - this.workersDataGridView.Name = "workersDataGridView"; - this.workersDataGridView.ReadOnly = true; - this.workersDataGridView.RowHeadersWidth = 35; - this.workersDataGridView.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; - this.workersDataGridView.Size = new System.Drawing.Size(508, 509); - this.workersDataGridView.TabIndex = 0; - // - // worker columns - // - this.workerSlotColumn.DataPropertyName = "Slot"; - this.workerSlotColumn.HeaderText = "Slot"; - this.workerSlotColumn.Name = "workerSlotColumn"; - this.workerSlotColumn.ReadOnly = true; - this.workerNameColumn.DataPropertyName = "Name"; - this.workerNameColumn.HeaderText = "Name"; - this.workerNameColumn.Name = "workerNameColumn"; - this.workerNameColumn.ReadOnly = true; - this.queueLengthColumn.DataPropertyName = "QueueLength"; - this.queueLengthColumn.HeaderText = "Queue length"; - this.queueLengthColumn.Name = "queueLengthColumn"; - this.queueLengthColumn.ReadOnly = true; - this.busyColumn.DataPropertyName = "IsBusy"; - this.busyColumn.HeaderText = "Busy"; - this.busyColumn.Name = "busyColumn"; - this.busyColumn.ReadOnly = true; - this.currentOperationColumn.DataPropertyName = "CurrentOperation"; - this.currentOperationColumn.HeaderText = "Current operation"; - this.currentOperationColumn.Name = "currentOperationColumn"; - this.currentOperationColumn.ReadOnly = true; - this.lastErrorColumn.DataPropertyName = "LastError"; - this.lastErrorColumn.HeaderText = "Last error"; - this.lastErrorColumn.Name = "lastErrorColumn"; - this.lastErrorColumn.ReadOnly = true; - this.lastActivityColumn.DataPropertyName = "LastActivity"; - lastActivityCellStyle.Format = "G"; - this.lastActivityColumn.DefaultCellStyle = lastActivityCellStyle; - this.lastActivityColumn.HeaderText = "Last activity"; - this.lastActivityColumn.Name = "lastActivityColumn"; - this.lastActivityColumn.ReadOnly = true; - // - // diagnosticsStatusStrip - // - this.diagnosticsStatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.statusToolStripStatusLabel}); - this.diagnosticsStatusStrip.Location = new System.Drawing.Point(0, 559); - this.diagnosticsStatusStrip.Name = "diagnosticsStatusStrip"; - this.diagnosticsStatusStrip.Size = new System.Drawing.Size(1234, 22); - this.diagnosticsStatusStrip.TabIndex = 2; - // - // statusToolStripStatusLabel - // - this.statusToolStripStatusLabel.Name = "statusToolStripStatusLabel"; - this.statusToolStripStatusLabel.Size = new System.Drawing.Size(122, 17); - this.statusToolStripStatusLabel.Text = "Waiting for GCI data..."; - // - // refreshTimer - // + this.verticalSplit.Dock = System.Windows.Forms.DockStyle.Fill; + this.verticalSplit.Orientation = System.Windows.Forms.Orientation.Horizontal; + this.verticalSplit.Size = new System.Drawing.Size(1280, 720); + this.verticalSplit.SplitterDistance = 220; + this.verticalSplit.Name = "verticalSplit"; + this.verticalSplit.Panel1.Controls.Add(this.overviewSplit); + this.verticalSplit.Panel2.Controls.Add(this.slotTabs); + this.overviewSplit.Dock = System.Windows.Forms.DockStyle.Fill; + this.overviewSplit.Size = new System.Drawing.Size(1280, 220); + this.overviewSplit.SplitterDistance = 640; + this.overviewSplit.Name = "overviewSplit"; + this.overviewSplit.Panel1.Controls.Add(this.slotsGrid); + this.overviewSplit.Panel2.Controls.Add(this.workersGrid); + this.slotsGrid.Dock = System.Windows.Forms.DockStyle.Fill; + this.slotsGrid.Name = "slotsGrid"; + this.slotsGrid.ReadOnly = true; + this.slotsGrid.AllowUserToAddRows = false; + this.slotsGrid.AllowUserToDeleteRows = false; + this.workersGrid.Dock = System.Windows.Forms.DockStyle.Fill; + this.workersGrid.Name = "workersGrid"; + this.workersGrid.ReadOnly = true; + this.workersGrid.AllowUserToAddRows = false; + this.workersGrid.AllowUserToDeleteRows = false; + this.slotTabs.Dock = System.Windows.Forms.DockStyle.Fill; + this.slotTabs.Name = "slotTabs"; this.refreshTimer.Interval = 250; - this.refreshTimer.Tick += new System.EventHandler(this.refreshTimer_Tick); - // - // GenesisCommunicationDiagnosticsForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.refreshTimer.Tick += new System.EventHandler(this.RefreshTick); + this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.statusLabel }); + this.statusStrip.Dock = System.Windows.Forms.DockStyle.Bottom; + this.statusStrip.Name = "statusStrip"; + this.statusLabel.Text = "Waiting for slot data"; + this.Controls.Add(this.verticalSplit); + this.Controls.Add(this.statusStrip); + this.refreshButton.Text = "Refresh"; + this.refreshButton.Click += new System.EventHandler(this.RefreshTick); + this.bridgeButton.Text = "Open GCI Bridge GUI"; + this.bridgeButton.Click += new System.EventHandler(this.OpenBridge); + this.toolbar.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.refreshButton, this.bridgeButton }); + this.toolbar.Dock = System.Windows.Forms.DockStyle.Top; + this.Controls.Add(this.toolbar); + this.ClientSize = new System.Drawing.Size(1280, 742); + this.MinimumSize = new System.Drawing.Size(900, 550); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1234, 581); - this.Controls.Add(this.mainSplitContainer); - this.Controls.Add(this.diagnosticsStatusStrip); - this.Controls.Add(this.diagnosticsToolStrip); - this.MinimizeBox = false; - this.MinimumSize = new System.Drawing.Size(900, 400); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; this.Name = "GenesisCommunicationDiagnosticsForm"; - this.ShowInTaskbar = false; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "Genesis Communication - GCI diagnostics"; - this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.GenesisCommunicationDiagnosticsForm_FormClosed); - this.Load += new System.EventHandler(this.GenesisCommunicationDiagnosticsForm_Load); - this.diagnosticsToolStrip.ResumeLayout(false); - this.diagnosticsToolStrip.PerformLayout(); - this.mainSplitContainer.Panel1.ResumeLayout(false); - this.mainSplitContainer.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.mainSplitContainer)).EndInit(); - this.mainSplitContainer.ResumeLayout(false); - this.slotsGroupBox.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.slotsDataGridView)).EndInit(); - this.workersGroupBox.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.workersDataGridView)).EndInit(); - this.diagnosticsStatusStrip.ResumeLayout(false); - this.diagnosticsStatusStrip.PerformLayout(); + this.Text = "Genesis diagnostics"; + this.Load += new System.EventHandler(this.FormLoaded); + ((System.ComponentModel.ISupportInitialize)(this.slotsGrid)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.workersGrid)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.overviewSplit)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.verticalSplit)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); } - - #endregion - - private System.Windows.Forms.ToolStrip diagnosticsToolStrip; - private System.Windows.Forms.ToolStripLabel activityToolStripLabel; - private System.Windows.Forms.ToolStripButton refreshToolStripButton; - private System.Windows.Forms.ToolStripButton showBridgeToolStripButton; - private System.Windows.Forms.SplitContainer mainSplitContainer; - private System.Windows.Forms.GroupBox slotsGroupBox; - private System.Windows.Forms.DataGridView slotsDataGridView; - private System.Windows.Forms.GroupBox workersGroupBox; - private System.Windows.Forms.DataGridView workersDataGridView; - private System.Windows.Forms.StatusStrip diagnosticsStatusStrip; - private System.Windows.Forms.ToolStripStatusLabel statusToolStripStatusLabel; - private System.Windows.Forms.Timer refreshTimer; - private System.Windows.Forms.DataGridViewTextBoxColumn slotColumn; - private System.Windows.Forms.DataGridViewCheckBoxColumn selectedColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn pcbIdColumn; - private System.Windows.Forms.DataGridViewCheckBoxColumn connectedColumn; - private System.Windows.Forms.DataGridViewCheckBoxColumn loggedOnColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn fwVersionColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn requestPortColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn requestPortTypeColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn streamingPortColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn interfaceVersionColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn workerSlotColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn workerNameColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn queueLengthColumn; - private System.Windows.Forms.DataGridViewCheckBoxColumn busyColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn currentOperationColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn lastErrorColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn lastActivityColumn; } } diff --git a/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.cs b/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.cs index f19f7471c..698839f32 100644 --- a/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.cs +++ b/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.cs @@ -1,78 +1,218 @@ -using System; +using System; using System.Collections.Generic; +using System.Drawing; +using System.Linq; using System.Windows.Forms; +using CordonelPreadjustmentUi; +using CordonelPreadjustmentUi.Processes.Itinerary; +using Xylem.Common.Ui.CordonelPreadjustmentUi; using GciBridgeType = TBF.Rig.BridgeComponents.GciBridge.GciBridge; -using GciPublicModels = GenesisCordonelInterface.API.PublicModels; namespace TBF.Rig.TestMethods.GenesisCommunication { - /// - /// Displays live GCI slot and worker information while a GenesisCommunication - /// preadjustment activity is running. - /// public partial class GenesisCommunicationDiagnosticsForm : Form { - private readonly GciBridgeType bridge; - private readonly string activity; + private GciBridgeType bridge; + private string activity; + private ProcessProgress progress; + private GenesisPreadjustmentBatchState batchState; + private readonly Dictionary> phaseLabels = new Dictionary>(); + private readonly Dictionary pages = new Dictionary(); + private readonly Dictionary states = new Dictionary(); + private readonly Dictionary activePhases = new Dictionary(); + private readonly Dictionary memos = new Dictionary(); + private readonly object logLock = new object(); + private readonly Queue> pending = new Queue>(); + private bool acceptingLogs; + private int dropped; + private static readonly string[] Phases = { "Initialization", "Detect", "Preparation", "Amplitude Test", "Temperature Calibration", "Push Temperature", "Offset Test", "Completion" }; - public GenesisCommunicationDiagnosticsForm(GciBridgeType bridge, string activity) + public GenesisCommunicationDiagnosticsForm() { - if (bridge == null) - throw new ArgumentNullException("bridge"); - - this.bridge = bridge; - this.activity = activity ?? string.Empty; - InitializeComponent(); - activityToolStripLabel.Text = "Running activity: " + this.activity; + slotTabs.DrawMode = TabDrawMode.OwnerDrawFixed; + slotTabs.DrawItem += DrawSlotTab; + slotTabs.ItemSize = new Size(100, 28); + slotTabs.SizeMode = TabSizeMode.Fixed; } - private void GenesisCommunicationDiagnosticsForm_Load(object sender, EventArgs e) + public GenesisCommunicationDiagnosticsForm(GciBridgeType bridge, string activity) : this(bridge, activity, new GenesisPreadjustmentBatchState()) { } + + internal GenesisCommunicationDiagnosticsForm(GciBridgeType bridge, string activity, GenesisPreadjustmentBatchState batchState) : this() { - RefreshDiagnostics(); + this.batchState = batchState; + this.bridge = bridge; + this.activity = activity; + Text = "Genesis diagnostics — " + activity; + acceptingLogs = true; + if (bridge.IsSimulation) bridge.SimulationLog += OnSimulationLog; + } + + public void AttachProgress(ProcessProgress value) + { + if (ReferenceEquals(progress, value)) return; + if (progress != null) progress.OnDebugMessageChanged -= OnDebugMessage; + progress = value; + if (progress != null) progress.OnDebugMessageChanged += OnDebugMessage; + } + + private void OnDebugMessage(object sender, EventArgsDebug e) + { + if (!e.Slot.HasValue || e.Slot.Value <= 0) return; + lock (logLock) + { + if (!acceptingLogs) return; + if (pending.Count >= 5000) { pending.Dequeue(); dropped++; } + pending.Enqueue(new KeyValuePair(e.Slot.Value, string.Format("{0:HH:mm:ss.fff} [{1}] {2}", DateTime.Now, e.Source, e.Value))); + } + } + + private void OnSimulationLog(int slot, string message) + { + lock (logLock) + { + if (!acceptingLogs) return; + if (pending.Count >= 5000) { pending.Dequeue(); dropped++; } + pending.Enqueue(new KeyValuePair(slot, DateTime.Now.ToString("HH:mm:ss.fff") + " " + message)); + } + } + + private void RefreshTick(object sender, EventArgs e) + { + if (bridge == null) return; + try + { + var api = bridge.IsSimulation ? null : bridge.gciExternalInterface; + var slots = bridge.GetMeterBatchDebugStatuses(); + slotsGrid.DataSource = slots; + workersGrid.DataSource = bridge.GetWorkerDebugStatuses(); + var selected = new HashSet((slots ?? new List()).Where(s => s.Selected).Select(s => s.Slot)); + foreach (int slot in batchState.Slots.Where(batchState.IsExcluded)) selected.Add(slot); + foreach (int slot in batchState.Slots.Where(slot => batchState.Phase(slot, activity) == "Running")) selected.Add(slot); + foreach (int slot in selected.OrderBy(s => s)) + { + if (!pages.ContainsKey(slot)) CreateSlot(slot); + if (!slotTabs.TabPages.Contains(pages[slot])) slotTabs.TabPages.Add(pages[slot]); + bool excluded = batchState.IsExcluded(slot); + states[slot].Text = excluded ? "Excluded from further preadjustment phases in this batch" : activity; + states[slot].BackColor = excluded ? Color.FromArgb(253, 226, 226) : Color.FromArgb(255, 237, 213); + states[slot].ForeColor = excluded ? Color.DarkRed : Color.FromArgb(124, 65, 12); + foreach (var phase in phaseLabels[slot]) + { + string state = batchState.Phase(slot, "Preadjustment " + phase.Key); + phase.Value.Text = phase.Key + Environment.NewLine + (excluded && state == "Pending" ? "Excluded" : state); + phase.Value.BackColor = state == "Passed" ? Color.FromArgb(209, 250, 229) : state == "Failed" ? Color.FromArgb(254, 202, 202) : state == "Running" ? Color.FromArgb(255, 214, 153) : Color.FromArgb(235, 239, 244); + phase.Value.ForeColor = state == "Passed" ? Color.FromArgb(6, 95, 70) : state == "Failed" ? Color.FromArgb(153, 27, 27) : state == "Running" ? Color.FromArgb(146, 64, 14) : Color.FromArgb(80, 95, 115); + } + foreach (DataGridViewRow row in slotsGrid.Rows) + { + if (row.Cells["Slot"].Value != null && Convert.ToInt32(row.Cells["Slot"].Value) == slot && excluded) + { + row.Cells["Selected"].Value = false; + row.DefaultCellStyle.BackColor = Color.FromArgb(253, 226, 226); + } + } + } + foreach (int slot in pages.Keys.Where(s => !selected.Contains(s)).ToArray()) slotTabs.TabPages.Remove(pages[slot]); + slotTabs.Invalidate(); + statusLabel.Text = string.Format("Selected slots: {0} | Refresh: {1:HH:mm:ss} | Dropped log messages: {2}", selected.Count, DateTime.Now, dropped); + } + catch (Exception ex) { statusLabel.Text = "Diagnostics: " + ex.Message; } + FlushLogs(); + } + + private void CreateSlot(int slot) + { + var page = new TabPage("Slot " + slot) { Tag = slot, BackColor = Color.White }; + phaseLabels[slot] = new Dictionary(); + var phasePanel = new FlowLayoutPanel { Dock = DockStyle.Top, Height = 100, AutoScroll = true }; + foreach (string phase in Phases) + { + bool current = string.Equals(activity, "Preadjustment " + phase, StringComparison.OrdinalIgnoreCase); + var label = new Label { Text = phase + (current ? "\nRunning" : "\nPending"), AutoSize = false, Size = new Size(145, 60), TextAlign = ContentAlignment.MiddleCenter, BackColor = Color.FromArgb(235, 239, 244), Margin = new Padding(5), Font = new Font("Segoe UI", 9F, FontStyle.Bold) }; + phasePanel.Controls.Add(label); + phaseLabels[slot].Add(phase, label); + if (current) activePhases[slot] = label; + } + var state = new Label { Dock = DockStyle.Top, Height = 30, TextAlign = ContentAlignment.MiddleLeft }; + var memo = new RichTextBox { Dock = DockStyle.Fill, ReadOnly = true, WordWrap = false, DetectUrls = false, BackColor = Color.White, Font = new Font("Consolas", 9), HideSelection = false }; + page.Controls.Add(memo); + page.Controls.Add(state); + page.Controls.Add(phasePanel); + pages.Add(slot, page); + states.Add(slot, state); + memos.Add(slot, memo); + } + + private void FlushLogs() + { + var batch = new List>(); + lock (logLock) { while (pending.Count > 0 && batch.Count < 500) batch.Add(pending.Dequeue()); } + foreach (var entry in batch) batchState.AddLog(entry.Key, entry.Value); + foreach (var pair in memos) + { + string text = batchState.LogText(pair.Key); + if (pair.Value.Text == text) continue; + bool atEnd = pair.Value.SelectionStart + pair.Value.SelectionLength >= pair.Value.TextLength; + int selection = pair.Value.SelectionStart; + pair.Value.Text = text; + pair.Value.Select(atEnd ? pair.Value.TextLength : Math.Min(selection, pair.Value.TextLength), 0); + if (atEnd) pair.Value.ScrollToCaret(); + } + } + + private void DrawSlotTab(object sender, DrawItemEventArgs e) + { + var page = slotTabs.TabPages[e.Index]; + int slot = (int)page.Tag; + bool failed = batchState != null && batchState.IsExcluded(slot); + Color color = failed ? Color.FromArgb(185, 28, 28) : e.Index == slotTabs.SelectedIndex ? Color.FromArgb(30, 64, 105) : Color.FromArgb(226, 232, 240); + using (var brush = new SolidBrush(color)) e.Graphics.FillRectangle(brush, e.Bounds); + TextRenderer.DrawText(e.Graphics, page.Text + (failed ? " !" : ""), Font, e.Bounds, failed || e.Index == slotTabs.SelectedIndex ? Color.White : Color.FromArgb(30, 41, 59), TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter); + } + + public void RefreshProgress() + { + if (IsDisposed) return; + RefreshTick(this, EventArgs.Empty); + Update(); + } + + private void FormLoaded(object sender, EventArgs e) + { + if (bridge == null) return; + // Fit the results region, keeping the top toolbar and right TBF controls visible. + var ownerArea = Owner == null ? Screen.FromControl(this).WorkingArea : Owner.RectangleToScreen(Owner.ClientRectangle); + var workArea = Screen.FromControl(Owner ?? this).WorkingArea; + int topInset = (int)Math.Round(ownerArea.Height * 0.09); + int rightInset = (int)Math.Round(ownerArea.Width * 0.125); + StartPosition = FormStartPosition.Manual; + WindowState = FormWindowState.Normal; + MinimumSize = new Size(640, 360); + Bounds = Rectangle.Intersect(workArea, new Rectangle(ownerArea.Left, ownerArea.Top + topInset, ownerArea.Width - rightInset, ownerArea.Height - topInset - 24)); + RefreshTick(sender, e); refreshTimer.Start(); } - private void GenesisCommunicationDiagnosticsForm_FormClosed(object sender, FormClosedEventArgs e) + private void OpenBridge(object sender, EventArgs e) + { + try { if (bridge != null) bridge.ShowGui(); } + catch (Exception ex) { statusLabel.Text = "GCI GUI: " + ex.Message; } + } + + private void ReleaseDiagnostics() { refreshTimer.Stop(); - } - - private void refreshTimer_Tick(object sender, EventArgs e) - { - RefreshDiagnostics(); - } - - private void refreshToolStripButton_Click(object sender, EventArgs e) - { - RefreshDiagnostics(); - } - - private void showBridgeToolStripButton_Click(object sender, EventArgs e) - { - bridge.ShowGui(); - } - - private void RefreshDiagnostics() - { - try + lock (logLock) { acceptingLogs = false; } + if (batchState != null) { - var api = bridge.gciExternalInterface; - List slotStatuses = api == null - ? new List() - : api.GetMeterBatchDebugStatuses() ?? new List(); - List workerStatuses = api == null - ? new List() - : api.GetWorkerDebugStatuses() ?? new List(); - - slotsDataGridView.DataSource = slotStatuses; - workersDataGridView.DataSource = workerStatuses; - statusToolStripStatusLabel.Text = string.Format("Updated {0:HH:mm:ss.fff} | Slots: {1} | Workers: {2}", DateTime.Now, slotStatuses.Count, workerStatuses.Count); - } - catch (Exception ex) - { - statusToolStripStatusLabel.Text = "Diagnostic refresh failed: " + ex.Message; + // No producer can append after acceptingLogs is cleared. + do { FlushLogs(); } while (pending.Count > 0); } + AttachProgress(null); + if (bridge != null) bridge.SimulationLog -= OnSimulationLog; + foreach (var page in pages.Values) page.Dispose(); + pages.Clear(); } } } diff --git a/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.resx b/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.resx index 3ac5e42c8..13913bdda 100644 --- a/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.resx +++ b/TBF/Rig/TestMethods/GenesisCommunication/UI/GenesisCommunicationDiagnosticsForm.resx @@ -1,61 +1,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/TBF/Rig/TestMethods/GenesisCommunication/iPerlCommunicationParams.cs b/TBF/Rig/TestMethods/GenesisCommunication/iPerlCommunicationParams.cs index cc2dace83..ea608128d 100644 --- a/TBF/Rig/TestMethods/GenesisCommunication/iPerlCommunicationParams.cs +++ b/TBF/Rig/TestMethods/GenesisCommunication/iPerlCommunicationParams.cs @@ -10,6 +10,7 @@ using Config.Entities; using TBF.Resources; using TBF.Rig.Generic; using TBF.Rig.TestMethods.iPerlCommunication; +using Xylem.Common.Logic.ProductionOrderCore.OrderData; namespace TBF.Rig.TestMethods.GenesisCommunication { @@ -29,6 +30,11 @@ namespace TBF.Rig.TestMethods.GenesisCommunication public bool SimultWithNext; public string MeterSize = "DN50"; public float Temperature = 20.0f; + public int SimulationDurationMs = 5000; + public bool SimulationSuccess = true; + public string SimulationErrorMessage = ""; + [XmlIgnore] + public bool ShowSimulationParameters; public override void InitializeAll() { @@ -37,6 +43,9 @@ namespace TBF.Rig.TestMethods.GenesisCommunication SimultWithNext = false; MeterSize = "DN50"; Temperature = 20.0f; + SimulationDurationMs = 5000; + SimulationSuccess = true; + SimulationErrorMessage = ""; } @@ -47,9 +56,12 @@ namespace TBF.Rig.TestMethods.GenesisCommunication Strings.Simultaneous_with_next_step, "Meter size", "Temperature [°C]", + "Simulation duration [ms]", + "Simulation Success", + "Simulation ErrorMessage", }; public override string ParamName(int i) { return paramNames[i]; } - public override int ParamsCount() { return paramNames.Length; } + public override int ParamsCount() { return ShowSimulationParameters ? paramNames.Length : 5; } public override ICollection ParamValues(int i) { @@ -140,14 +152,14 @@ namespace TBF.Rig.TestMethods.GenesisCommunication return retVal; } - else if (i == 1 || i == 2) + else if (i == 1 || i == 2 || i == 6) { return new string[] { Strings.yes, Strings.no }; } else if (i == 3) { - var meterSizes = new List(System.Enum.GetNames(typeof(Xylem.Common.Logic.ProductionOrderCore.OrderData.MeterSize))); - meterSizes.Remove(Xylem.Common.Logic.ProductionOrderCore.OrderData.MeterSize.NA.ToString()); + var meterSizes = new List(System.Enum.GetNames(typeof(MeterSize))); + meterSizes.RemoveAll(value => value.EndsWith("MAX", System.StringComparison.OrdinalIgnoreCase)); return meterSizes; } @@ -163,6 +175,9 @@ namespace TBF.Rig.TestMethods.GenesisCommunication case 2: return (SimultWithNext ? Strings.yes : Strings.no); case 3: return MeterSize; case 4: return Temperature.ToString(); + case 5: return SimulationDurationMs.ToString(); + case 6: return SimulationSuccess ? Strings.yes : Strings.no; + case 7: return SimulationErrorMessage; default: return string.Empty; } } @@ -176,6 +191,9 @@ namespace TBF.Rig.TestMethods.GenesisCommunication case 2: SimultWithNext = strValue.ToLower().Equals(Strings.yes.ToLower()); return CfgUpdateFlags.None; case 3: MeterSize = strValue; return CfgUpdateFlags.None; case 4: Temperature = Utils.ParseSFloat(strValue); return CfgUpdateFlags.None; + case 5: SimulationDurationMs = int.Parse(strValue); return CfgUpdateFlags.None; + case 6: SimulationSuccess = strValue.Equals(Strings.yes, System.StringComparison.OrdinalIgnoreCase); return CfgUpdateFlags.None; + case 7: SimulationErrorMessage = strValue; return CfgUpdateFlags.None; default: return CfgUpdateFlags.None; } } @@ -191,13 +209,19 @@ namespace TBF.Rig.TestMethods.GenesisCommunication return true; case 1: case 2: + case 6: if (strValue.Equals(Strings.yes) || strValue.Equals(Strings.no)) return true; break; case 3: - return System.Enum.TryParse(strValue, true, out Xylem.Common.Logic.ProductionOrderCore.OrderData.MeterSize meterSize) && meterSize != Xylem.Common.Logic.ProductionOrderCore.OrderData.MeterSize.NA; + MeterSize meterSize; + return System.Enum.TryParse(strValue, true, out meterSize) && !meterSize.ToString().EndsWith("MAX", System.StringComparison.OrdinalIgnoreCase); case 4: if (Utils.TryParseSFloat(strValue, out temperature) && temperature >= -20.0f && temperature <= 100.0f) return true; break; + case 5: + int duration; + return int.TryParse(strValue, out duration) && duration >= 0; + case 7: return true; default: message = "Invalid index"; return false; @@ -214,12 +238,16 @@ namespace TBF.Rig.TestMethods.GenesisCommunication prms.SimultWithNext = this.SimultWithNext; prms.MeterSize = this.MeterSize; prms.Temperature = this.Temperature; + prms.SimulationDurationMs = SimulationDurationMs; + prms.SimulationSuccess = SimulationSuccess; + prms.SimulationErrorMessage = SimulationErrorMessage; } public IParamsProvider Clone() { iPerlCommunicationParams pars = new iPerlCommunicationParams(); CopyContentTo(pars); + pars.ShowSimulationParameters = ShowSimulationParameters; return pars; } diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj index 8d6909bba..630eb3dfe 100644 --- a/TBF/TBF.csproj +++ b/TBF/TBF.csproj @@ -249,6 +249,7 @@ + UserControl @@ -257,6 +258,7 @@ GciBridgeCfgCtrl.cs + @@ -2001,6 +2003,7 @@ +