diff --git a/TBF/Rig/TestMethods/GenesisFullCommunication/TestMethodCfg.cs b/TBF/Rig/TestMethods/GenesisFullCommunication/TestMethodCfg.cs index 45539ac63..ade94d6dd 100644 --- a/TBF/Rig/TestMethods/GenesisFullCommunication/TestMethodCfg.cs +++ b/TBF/Rig/TestMethods/GenesisFullCommunication/TestMethodCfg.cs @@ -8,13 +8,13 @@ using System.Xml.Serialization; using Common; using Config.Entities; using TBF.Rig.Generic; - +using TBF.Rig.RegisterReaders.CommonRR.IPerl; namespace TBF.Rig.TestMethods.GenesisFullCommunication { [XmlRoot("TestMethodCfg")] // Add this attribute to match the XML root - public class TestMethodCfg : ComponentCfgBase, IComponentCfg + public class TestMethodCfg : ComponentCfgBase, ITestMethodCfg/*IComponentCfg*/ { public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(TestMethodCfg) })[0]; public override XmlSerializer GetSerializer() { return Serializer; } diff --git a/TBF/Rig/Uni/SharedDialogs/SmartMetersCommunication/SmartCommunicationForm.cs b/TBF/Rig/Uni/SharedDialogs/SmartMetersCommunication/SmartCommunicationForm.cs index 7bf8e3a72..385cdc387 100644 --- a/TBF/Rig/Uni/SharedDialogs/SmartMetersCommunication/SmartCommunicationForm.cs +++ b/TBF/Rig/Uni/SharedDialogs/SmartMetersCommunication/SmartCommunicationForm.cs @@ -14,7 +14,9 @@ using log4net; using NHibernate.Util; using TBF.Resources; using TBF.Rig.Generic; +using TBF.Rig.GenericDevices; using TBF.Rig.RegisterReaders.CommonRR.IPerl; +using TBF.Rig.RegisterReaders.GenesisRegReader.implementations; using TBF.Rig.RegisterReaders.iPerlReaderUNI; using TBF.Rig.RegisterReaders.PoseidonReader; using TBF.Rig.Sequences; @@ -157,6 +159,14 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication correctionsList.Add(new PoseidonCorrections(this,log, rfidDataLogger, componentBase, cfg, tests, multiTestParams)); continue; } + + if (smartHead is GenesisSmartReader genSmartReader) + { + if (correctionsList.Any(x => x is GenesisSmartReader)) + continue; + correctionsList.Add(new GenesisCorrections( this,componentBase, cfg, tests, multiTestParams)); + continue; + } throw new Exception("Unknown smart head type"); } @@ -189,6 +199,14 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication correctionsList.Add(new PoseidonCorrections(form)); continue; } + + if (smartHead is GenesisSmartReader genSmartReader) + { + if (correctionsList.Any(x => x is GenesisSmartReader)) + continue; + correctionsList.Add(new GenesisCorrections(form)); + continue; + } throw new Exception("Unknown smart head type"); } @@ -223,6 +241,12 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication typeReaders.Add(smartReader.ClassName); continue; } + + if (smartHead is GenesisSmartReader genSmartReader) + { + typeReaders.Add(genSmartReader.ClassName); + continue; + } throw new Exception("Unknown smart head type"); } @@ -327,7 +351,13 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication { checkBoxesEditMode = false; - ITestMethodCfg cfg = (componentBase as ITestMethodCfg); + ITestMethodSmart smart = (componentBase as ITestMethodSmart); + ITestMethodCfg cfg = (componentBase as ITestMethodCfg); + if (smart != null && cfg == null) + { + cfg = (componentBase.Cfg as ITestMethodCfg); + } + ISmartTestMethod smartTestMethod = componentBase as ISmartTestMethod; //TODO get corrections based on defined meter @@ -410,7 +440,8 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication if (iperlHeads == null) iperlHeads = new List(); waterMeterPositions0?.Clear(); if (waterMeterPositions0 == null) waterMeterPositions0 = new List(); - //iperlHeads = ProcessData.SmartHeadsUni; + //TODO BUMI check + iperlHeads = ProcessData.SmartHeadsUni; string comparedTypeReader = SelectedTypeReader; if (string.IsNullOrEmpty(SelectedTypeReader)) { @@ -513,7 +544,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication WaterMetersCount = iperlHeads.Count; ShuffleTextBoxes(WaterMetersCount, ProcessData.LineSize); - Correction.PrepareForTestsActivities(WaterMetersCount); + Correction?.PrepareForTestsActivities(WaterMetersCount); } /// @@ -552,7 +583,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication private void DoOnCommCompleted(object sender, CommCompletedEventArgs data) { - Correction.DoOnCommCompleted(sender, data, waterMeterPositions0); + Correction?.DoOnCommCompleted(sender, data, waterMeterPositions0); } @@ -594,7 +625,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication textBoxesCount = wmsCount; } - int count = checkBoxesEditMode ? textBoxesCount : Math.Min(textBoxesCount, Correction.GetHeadsCount()); + int count = checkBoxesEditMode ? textBoxesCount : Math.Min(textBoxesCount, Correction?.GetHeadsCount() ?? 0); for (int j = 0; j < count; j++) { labels[j].Text = (j + 1).ToString(); @@ -704,7 +735,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication { meterTypeComboBox.Visible = false; } - Correction.Load(labels,counters,messages,checkBoxes,ckbIndex,ckbState, iperlHeads,textBoxesCount,checkBoxesEditMode ); + Correction?.Load(labels,counters,messages,checkBoxes,ckbIndex,ckbState, iperlHeads,textBoxesCount,checkBoxesEditMode ); /// /// Set location and checkbox states to values stored in local settings @@ -719,7 +750,7 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication /// Regular activity (not a checkbox edit mode invoked from TBF menu) /// Reset opto-data indication - for (int i = 0; i < Correction.GetHeadsCount(); i++) + for (int i = 0; i < (Correction?.GetHeadsCount() ?? 0); i++) { counters[i].BackColor = iPerlCommunicationConstants.OptoNokColor; } @@ -728,11 +759,14 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication //currentGroup++; int wtId = 0; - foreach (var wt in Correction.GetAllThreads()) - { - wt.Start(new Boxes.IntBox(wtId++)); /// Start worker threads !!! - } - } + if (Correction?.GetAllThreads() != null) + { + foreach (var wt in Correction?.GetAllThreads()) + { + wt.Start(new Boxes.IntBox(wtId++)); /// Start worker threads !!! + } + } + } } diff --git a/TBF/Rig/Uni/SharedDialogs/SmartMetersCommunication/implementations/GenesisCorrections.cs b/TBF/Rig/Uni/SharedDialogs/SmartMetersCommunication/implementations/GenesisCorrections.cs new file mode 100644 index 000000000..c724c649a --- /dev/null +++ b/TBF/Rig/Uni/SharedDialogs/SmartMetersCommunication/implementations/GenesisCorrections.cs @@ -0,0 +1,2878 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Common; +using Config.Entities; +using log4net; +using Results.Entities; +using Sensus.iPerl.NfcHandler; +using TBF.Resources; +using TBF.Rig.Generic; +using TBF.Rig.RegisterReaders.CommonRR.IPerl; +using TBF.Rig.RegisterReaders.IPerlReader.implementations; +using TBF.Rig.Sequences; +using TBF.Rig.TestMethods.iPerlCommunication; +using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead; +using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common; +using CalibrationStruct = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.CalibrationStruct; +using CalibrationStructV4 = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.CalibrationStructV4; +using CheckBoxImage = TBF.Boxes.CheckBoxImage; +using Command = TBF.Rig.RegisterReaders.CommonRR.Command; +using CommunicationInterface = TBF.Rig.RegisterReaders.CommonRR.CommunicationInterface; +using ConfigStruct = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.ConfigStruct; +using Factory = TBF.Rig.RegisterReaders.iPerlReaderUNI.Factory; +using MessageID = TBF.Rig.RegisterReaders.CommonRR.MessageID; +using MeterState = TBF.Rig.RegisterReaders.CommonRR.MeterState; +using MeterType = TBF.Rig.RegisterReaders.CommonRR.MeterType; +using NfcServices = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.NfcServices; +using RfidServices = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.RfidServices; +using SimulationServices = TBF.Rig.RegisterReaders.CommonRR.IPerl.communication.SimulationServices; +using TestMethod = TBF.Rig.TestMethods.SmartTest.TestMethod; + +namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.implementations +{ + public class GenesisCorrections : ICorrections + { + ILog log {get => ParentFrom?.Log;} + ILog rfidDataLogger{get => ParentFrom?.RfidDataLogger;} + + static int currentActivityStep; + static int currentGroup; + + /// form -> worker thread (0 = none) + static int lastGroup; + + static int completedCommCount; + + /// Number of completed communication steps + + + static IList workerThreads; + + static IList muxBrdOrGroup14Nrs; + static bool stopWorkerThreads; + + public string TypeIdentificatorName() + { + return "Genesis Cordonel"; + } + + /// form -> worker thread + + + + public DateTime StartTime { get; set; } + + public void StopWorkerThreads(bool bStopAllThreads) + { + stopWorkerThreads = bStopAllThreads; + } + + public bool GetStopWorkerThreads() + { + return stopWorkerThreads; + } + + public IList GetAllThreads() + { + return workerThreads; + } + + public ICorrections GetNewCorrection() + { + throw new NotImplementedException(); + } + + public void GetGroup() + { + throw new NotImplementedException(); + } + + public int GetHeadsCount() + { + return iperlHeads != null ? iperlHeads.Count : 0; + } + + public int StartTimeSec { get; set; } + public SmartCommunicationForm ParentFrom { get; set; } + + + private ISmartTestMethod testMethod; + private ITestMethodCfg cfg; + private IList tests; + private IList multiTestParams; + + public ITestMethodCfg Cfg + { + get => cfg; + set => cfg = value; + } + + public IList MultiTestParams + { + get => multiTestParams; + } + + public IList Tests + { + get => tests; set => tests = value; + } + + public IList iperlHeads { get => ParentFrom.Heads; } + + + private Label activityLabel { get => ParentFrom?.ActivityLabel;} + private Label[] labels { get => ParentFrom?.Labels; } + private PictureBox[] counters{get => ParentFrom?.Counters;} + private TextBox[] messages{get => ParentFrom?.Messages;} + private CheckBoxImage[] checkBoxes{get => ParentFrom?.CheckBoxes;} + private int[] ckbIndex{get => ParentFrom?.CkbIndex;} + private bool[] ckbState{get => ParentFrom?.CkbState;} + + + + + public GenesisCorrections(SmartCommunicationForm parent) + { + this.ParentFrom = parent; + TBF.Rig.TestMethods.GenesisFullCommunication.Factory factory = new TBF.Rig.TestMethods.GenesisFullCommunication.Factory(); + TBF.Rig.TestMethods.GenesisFullCommunication.TestMethod method = new TBF.Rig.TestMethods.GenesisFullCommunication.TestMethod(factory.DefaultConfig()); + this.testMethod = method; + this.Cfg = method.TestMethodCfg; + } + + public GenesisCorrections(SmartCommunicationForm parent, ISmartTestMethod testMethod, + ITestMethodCfg cfg, IList tests, IList multiTestParams) + { + this.ParentFrom = parent; + this.testMethod = testMethod; + this.tests = tests; + this.cfg = cfg; + this.multiTestParams = multiTestParams; + } + + + public ContextMenu GetContextMenu() + { + ContextMenu cm = new ContextMenu(); + cm.MenuItems.Add(NewMenuItem("Read PCB Number", "ReadPCB")); + cm.MenuItems.Add(NewMenuItem("Enter Test Mode", "StartTestMode")); + cm.MenuItems.Add(NewMenuItem("Turn Off Test Mode (Enter Active Mode)", "TurnOffTestMode")); + cm.MenuItems.Add(NewMenuItem("Set Production Mode (Radio not start with flow)", "SetProductionMode")); + cm.MenuItems.Add(NewMenuItem("Turn Off Radio", "TurnOffRadio")); + cm.MenuItems.Add(NewMenuItem( + $"Set RFID mode ({Strings.Program_restart_is_required_to_apply_some_settings})", "SetRFID")); + cm.MenuItems.Add(NewMenuItem($"Set NFC mode ({Strings.Program_restart_is_required_to_apply_some_settings})", + "SetNFC")); + /*if (Users.CurrentUser.AuthorizedAs == AuthorizedAs.PowerUser) + { + //cm.MenuItems.Add(NewMenuItem("Test WriteRequestPort (u8_Customer_Text)", "WriteRequestPort_u8_Customer_Text")); + cm.MenuItems.Add(NewMenuItem("Open sealing", "OpenSealing")); + }*/ + return cm; + } + + /// + /// Worker thread + /// + /// Thread ID (integer) wrapped into IntBox + public void Worker(object threadData) + { + int threadID = (threadData as Boxes.IntBox).Val; + + int activityStep = 0; /// activity step > 0 in case multiTestParams are used + + for (int iMultiTestParamsItem = 0; iMultiTestParamsItem < MultiTestParams.Count; iMultiTestParamsItem++) + { + Test currentTest = Tests[iMultiTestParamsItem]; + ITestParams currentTestParams = MultiTestParams[iMultiTestParamsItem]; + string currentActivity = currentTestParams.Activity; /// Current activity + + TBF.UiBridge.TestProgressEventArgs.SetEstimatedTimes(new int[] { 0, 0, 10, 0, 140, 0, 0, 0 }); + TBF.UiBridge.Bridge.OnTestProgress(null, + new TBF.UiBridge.TestProgressEventArgs(currentTest, Progress.JustStarted)); + + + if (threadID == 0) + { + StartDataStreamProcessingForActiveMeters(iMultiTestParamsItem); + + /// A new activity starts - information into RFID data log + rfidDataLogger.InfoFormat(""); + rfidDataLogger.WarnFormat("Activity = {0}", currentActivity); + rfidDataLogger.InfoFormat(""); + } + + for (int group = 1; group <= lastGroup; group++) + { + /// Synchronize with QuidoRS and other threads + while (((group != currentGroup) || (activityStep != currentActivityStep)) && + !GetStopWorkerThreads()) + { + Thread.Sleep(50); + } + + if (GetStopWorkerThreads()) break; + +#if TURA_SPECIAL + int threadIx = threadID; /// Just one thread for TURA_SPECIAL +#else + for (int threadIx = threadID; threadIx < threadID + 4; threadIx += Cfg.NrThreads) +#endif + { + bool wmFound = false; + + for (int wmNr0 = 0; wmNr0 < iperlHeads.Count; wmNr0++) + { + IperlHead ihead = iperlHeads[wmNr0] as IperlHead; + if ((ihead.Group == group) && (threadIx < muxBrdOrGroup14Nrs.Count) && + (ihead.MuxBoardNrOrGroup14 == muxBrdOrGroup14Nrs[threadIx])) + { + wmFound = true; + + WaterMeter wm = null; + if (ProcessData.BatchRslts.Batch.WaterMeters != null) + { + foreach (var w in ProcessData.BatchRslts.Batch.WaterMeters) + { + if (w.WMPosition == wmNr0 + 1) + { + wm = w; + break; + } + } + } + + //Do worker activity + CommErr error = CommErr.None; + string resultStr = string.Empty; + + + // WorkerActivity(currentActivity, ihead, wm, currentTest, + // wmNr0, ref error, ref resultStr, ckbState, threadID, currentActivityStep); + // ProcessResultOfWorkerActivity(iMultiTestParamsItem, currentActivity, + // currentGroup, ihead, wm, wmNr0, error, resultStr, ckbState, threadID); + + break; + } + + TBF.UiBridge.Bridge.OnTestProgress(null, + new TBF.UiBridge.TestProgressEventArgs(Tests[iMultiTestParamsItem], + Progress.FlowSetting)); + } + + if (!wmFound) + { + SmartCommunicationForm.OnCommCompleted(null, + new CommCompletedEventArgs(threadID, -1, null, null, string.Empty, + CommErr.None)); /// Send negative wmNr + } + + if (GetStopWorkerThreads()) break; + } + + if (GetStopWorkerThreads()) break; + } /// for (int group + + TBF.UiBridge.Bridge.OnTestProgress(null, + new TBF.UiBridge.TestProgressEventArgs(Tests[iMultiTestParamsItem], Progress.Completed)); + activityStep++; + + if (GetStopWorkerThreads()) break; + } + } + + private MenuItem NewMenuItem(string text, string tag) + { + MenuItem menuItem = new MenuItem { Text = text, Tag = tag }; + menuItem.Click += OnClick_Optical_Heads_Settings_Menu; + return menuItem; + } + + private async void OnClick_Optical_Heads_Settings_Menu(object sender, EventArgs e) + { + // Validate sender + if (!(sender is MenuItem menuItem)) + { + log?.Error("OnClick_Optical_Heads_Settings_Menu: sender is not a MenuItem"); + return; + } + + if (activityLabel != null) + activityLabel.Text = menuItem.Text; + + List tasks = new List(); + foreach (var iSmartReader in ProcessData.SmartHeadsUni) + { + if (!(iSmartReader is IperlHead iHead)) + { + continue;//ignore different types of heads + } + + int position = iHead.Position - 1; + if (position < 0 || position >= checkBoxes.Length || position >= messages.Length) + { + continue; // Skip this head if position is out of range + } + if (!checkBoxes[position].Checked) + { + if (position < messages.Length) messages[position].Text = ""; + continue; + } + + messages[position].Text = $"COM{iHead.RfidComPortNr}"; + Application.DoEvents(); // Refresh UI + tasks.Add(Task.Run(async () => + { + string result = await ProcessTask(iHead, menuItem.Tag); + ParentFrom?.Invoke((Action)(() => + { + messages[position].Text = result; + Application.DoEvents(); // Refresh UI + })); + + })); + + } + + await Task.WhenAll(tasks); + } + + private async Task ProcessTask(IperlHead head, object tag) + { + string txt = ""; + switch (tag) + { + case "ReadPCB": + txt = OpticalHeadTest.ReadRequest_PCB(head); + break; + case "WriteRequestPort_u8_Customer_Text": + txt = OpticalHeadTest.WriteRequestPort_u8_Customer_Text(head); + break; + case "OpenSealing": + txt = OpticalHeadTest.OpenSealing(head); + break; + case "StartTestMode": + txt = OpticalHeadTest.SetTestMode(head); + break; + case "TurnOffTestMode": + txt = OpticalHeadTest.SetActiveMode(head); + break; + case "TurnOffRadio": + txt = OpticalHeadTest.TurnOffRadio(head); + break; + case "SetProductionMode": + txt = OpticalHeadTest.SetProductionMode(head); + break; + case "SetRFID": + txt = OpticalHeadTest.SetRfidMode(head); + break; + case "SetNFC": + txt = OpticalHeadTest.SetNfcMode(head); + break; + } + + return txt; + } + + + public void Load(Label[] labels, PictureBox[] counters, TextBox[] messages, CheckBoxImage[] checkBoxes, + int[] ckbIndex, + bool[] ckbState, IList iperlHeads, int textBoxesCount, bool checkBoxesEditMode) + { + if (iperlHeads == null) + { + for (int i = 0; i < textBoxesCount; i++) + { + checkBoxes[i].Enabled = checkBoxes[i].Checked = ckbState[i] = true; + messages[i].Text = "---"; + } + + return; + } + /// + /// Set checkbox states accroding to iPerlHeads[i].Disabled states + /// + for (int i = 0; i < textBoxesCount; i++) + { + labels[i].Visible = counters[i].Visible = messages[i].Visible = checkBoxes[i].Visible = true; + + + IperlHead iperlHead = iperlHeads[i] as IperlHead; + if (!checkBoxesEditMode && (iperlHead == null || iperlHead.Disabled)) + { + /// iPerl position i+1 is disabled + checkBoxes[i].Enabled = checkBoxes[i].Checked = ckbState[i] = false; + counters[i].BackColor = iPerlCommunicationConstants.DisabledColor; + messages[i].Text = Strings.Head_was_disabled_by_the_user; + } + else + { + /// iPerl position i+1 is enabled + checkBoxes[i].Enabled = checkBoxes[i].Checked = ckbState[i] = true; + messages[i].Text = "---"; + } + } + + } + + public void PrepareForTestsActivities(int waterMeterPositions0) + { + StartTime = DateTime.Now; + StartTimeSec = StateMachine.Time; + + /// + /// Prepare worker threads, 'rfidPortNrs', 'lastGroup', etc.. + /// + currentActivityStep = 0; + currentGroup = 0; + completedCommCount = 0; + stopWorkerThreads = false; + + /// group numbers are >=1, lastGroup == 0 means there is no group + lastGroup = 0; + if (iperlHeads != null) + { + foreach (var iSmartReader in iperlHeads) + { + try + { + var iPerl = iSmartReader as IperlHead; + if (iPerl != null && iPerl.Group > lastGroup) lastGroup = iPerl.Group; + } + catch (Exception E) + { + log.ErrorFormat("PrepareForTestsActivities: {0}", E.Message); + } + } + } + + workerThreads = new List(); + if (Cfg != null) + { + for (int i = 0; i < Cfg.NrThreads; i++) + { + Thread thread = new Thread(Worker); + thread.CurrentCulture = CultureInfo.CurrentCulture; + thread.CurrentUICulture = CultureInfo.CurrentUICulture; + workerThreads.Add(thread); + } + } + + muxBrdOrGroup14Nrs = new List(); + if (iperlHeads != null) + { + foreach (ISmartReader iPerl in iperlHeads) + { + if (iPerl is IperlHead ihead) + { + if (!muxBrdOrGroup14Nrs.Contains(ihead.MuxBoardNrOrGroup14)) + muxBrdOrGroup14Nrs.Add(ihead.MuxBoardNrOrGroup14); + } + } + } + + log.WarnFormat("nrThreads = {0}", workerThreads.Count); + } + + public void StartDataStreamProcessingForActiveMeters(int iMultiTestParamsItem) + { + /// Check whether previous activity was 'Set test mode A0' or 'A4' + if (iMultiTestParamsItem > 0 && + MultiTestParams[iMultiTestParamsItem - 1].Activity.ToLower() + .Contains(iPerlCommunicationConstants.SetTestModeStr.ToLower()) && + !MultiTestParams[iMultiTestParamsItem - 1].Activity.Contains("80")) + { + /// Start processing of opto-datastreams from all iPERL-s + int count = 0; + for (int wmNr0 = 0; wmNr0 < iperlHeads.Count; wmNr0++) + { + WaterMeter wm = (ProcessData.BatchRslts.Batch.WaterMeters != null && + ProcessData.BatchRslts.Batch.WaterMeters.Count > wmNr0) + ? ProcessData.BatchRslts.Batch.WaterMeters[wmNr0] + : null; + + ISmartReader ihead = iperlHeads[wmNr0]; + + if (ihead != null && wm != null && !wm.Disabled) + { + lock (ihead) + { + ihead.StartDataStreamProcessing(); + count++; + } + } + } + + log.WarnFormat("End of activity '{0}', StartDataStreamProcessing() of {1} heads was called.", + MultiTestParams[iMultiTestParamsItem - 1].Activity, count); + } + } + + + + // #region Simulation / RFID / NFC Interface: ReadRequestPort, WriteRequestPort + /// + /// Wrapper function with safe interface and unsafe body + /// + /// Water meter (iPerlHead) object + /// Value returned by readRequestPort(...) + public static int ReadRequestPort(ITestMethodCfg cfgMethod, ISmartReader smartHead, MessageID messageID, + MCI_Protocol.StructName structName, int offset, int length, out byte[] buffer) + { + TestMethodCfg cfg = cfgMethod as TestMethodCfg; + if (cfg == null) + { + if (smartHead.Cfg is TestMethodCfg cfg2) cfg = cfg2; + } + + Thread.Sleep(cfg == null ? 250 : Math.Max(250, cfg.DelayBetweenRetries)); + if (smartHead.DebugLevel == DebugMode.FailureDuringOperation) smartHead.DebugLevel = DebugMode.Normal; + + if (smartHead is SmartReader iperlHead) + //if (smartHead is IperlHead iperlHead) + { + + if (iperlHead.DebugLevel != DebugMode.Normal) // Simulation + { + return SimulationServices.ReadRequest(iperlHead, messageID, offset, length, out buffer); + } + + if (iperlHead.CommInterface == CommunicationInterface.NFC) // NFC Interface + { + return NfcServices.ReadRequest(cfg, iperlHead, structName, offset, length, out buffer); + } + else // RFID Interface + { + return RfidServices.ReadRequest(cfg, iperlHead, messageID, offset, length, out buffer); + } + } + else + { + throw new Exception("iPerlHead is not a IPerl object"); + } + } + + /// + /// Wrapper function with safe interface and unsafe body + /// + /// Water meter (iPerlHead) object + /// Value returned by writeRequestPort(...) + public static int WriteRequestPort(ITestMethodCfg cfg, ISmartReader iperlHead, MessageID messageID, + MCI_Protocol.StructName structName, int offset, int length, byte[] buffer) + { + Thread.Sleep(Math.Max(250, cfg.DelayBetweenRetries)); + if (iperlHead.DebugLevel == DebugMode.FailureDuringOperation) iperlHead.DebugLevel = DebugMode.Normal; + + if (iperlHead.DebugLevel != DebugMode.Normal) // Simulation + { + return SimulationServices.WriteRequest(cfg, iperlHead, messageID, offset, length, buffer); + } + + if (iperlHead.CommInterface == CommunicationInterface.NFC.ToDescription()) // NFC Interface + { + return NfcServices.WriteRequest(cfg, iperlHead, structName, offset, length, buffer); + } + else // RFID Interface + { + return RfidServices.WriteRequest(cfg, iperlHead, messageID, offset, length, buffer); + } + } + //#endregion iPerl_Head_RFID_Interface: ReadRequestPort, WriteRequestPort + + /// + /// Wrapper function with safe interface and unsafe body + /// + /// Water meter (iPerlHead) object + /// Value returned by readRequestPort(...) + int ReadRequestPort(ISmartReader iperlHead, MessageID messageID, MCI_Protocol.StructName structName, int offset, + int length, out byte[] buffer) + { + Thread.Sleep(Math.Max(250, cfg.DelayBetweenRetries)); + if (iperlHead.DebugLevel == DebugMode.FailureDuringOperation) iperlHead.DebugLevel = DebugMode.Normal; + + if (iperlHead.DebugLevel != DebugMode.Normal) // Simulation + { + return SimulationServices.ReadRequest(iperlHead, messageID, offset, length, out buffer); + } + + if (iperlHead.CommInterface == CommunicationInterface.NFC.ToDescription()) // NFC Interface + { + return NfcServices.ReadRequest(cfg, iperlHead, structName, offset, length, out buffer); + } + else // RFID Interface + { + return RfidServices.ReadRequest(cfg, iperlHead, messageID, offset, length, out buffer); + } + } + + /// + /// Wrapper function with safe interface and unsafe body + /// + /// Water meter (iPerlHead) object + /// Value returned by writeRequestPort(...) + int WriteRequestPort(ISmartReader iperlHead, MessageID messageID, MCI_Protocol.StructName structName, + int offset, int length, byte[] buffer) + { + Thread.Sleep(Math.Max(250, cfg.DelayBetweenRetries)); + if (iperlHead.DebugLevel == DebugMode.FailureDuringOperation) iperlHead.DebugLevel = DebugMode.Normal; + + if (iperlHead.DebugLevel != DebugMode.Normal) // Simulation + { + return SimulationServices.WriteRequest(cfg, iperlHead, messageID, offset, length, buffer); + } + + if (iperlHead.CommInterface == CommunicationInterface.NFC.ToDescription()) // NFC Interface + { + return NfcServices.WriteRequest(cfg, iperlHead, structName, offset, length, buffer); + } + else // RFID Interface + { + return RfidServices.WriteRequest(cfg, iperlHead, messageID, offset, length, buffer); + } + } + + public bool WorkerActivity(string currentActivity, common.ISmartReader iHead, WaterMeter wm, Test currentTest, + int wmNr0, + ref CommErr error, ref string resultStr, bool[] ckbState, int threadID, int currentActivityStep) + { + + if (iHead is SmartReader ihead) + { + /// + /// RFID communication activity call + /// + if ((ihead == null) || ihead.Disabled || !ckbState[wmNr0]) error = CommErr.HeadDisabledByUser; +#if IPERL + else if (currentActivity.ToLower().Contains(iPerlCommunicationConstants.SetTestModeStr.ToLower())) + error = SetTestMode(currentActivityStep, ihead, ref resultStr); + else if (currentActivity.ToLower().Equals(iPerlCommunicationConstants.SetActiveModeStr.ToLower())) + error = SetActiveMode(threadID, ihead, ref resultStr); + /// + /// RFID communication functions below require a reference to water meter entity (wm != null) + /// + else if (wm == null) error = CommErr.CommFailed; + else if (currentActivity.ToLower().Contains(iPerlCommunicationConstants.ReadConfigurationStr.ToLower())) + error = ReadConfiguration(currentActivityStep, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.ReadCalibrationStr.ToLower()) + error = ReadCalibration(ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.ReadCalibrationV4Str.ToLower()) + error = ReadCalibrationV4(ihead, wm, ref resultStr); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.WriteCalibrationFactorStr.ToLower())) + error = WriteCalibrationFactor(currentActivityStep, threadID, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.WriteCalibrationV4FactorsStr.ToLower())) + error = WriteCalibrationV4Factors(currentActivityStep, threadID, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == + iPerlCommunicationConstants.NormalizeCalibrationFactorStr.ToLower()) + error = NormalizeCalibrationFactor(threadID, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == + iPerlCommunicationConstants.NormalizeCalibrationV4FactorsStr.ToLower()) + error = NormalizeCalibrationV4Factors(threadID, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.ReadQ2CorrectionStr.ToLower()) + error = ReadQ2Correction(threadID, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.ResetQ2CorrectionStr.ToLower()) + error = ResetQ2Correction(threadID, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == + iPerlCommunicationConstants.WriteDefaultQ2CorrectionsStr.ToLower()) + error = WriteDefaultQ2Corrections(threadID, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.InitOrReadQ2CorrectionsStr.ToLower()) + error = InitOrReadQ2Corrections(threadID, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.WriteQ2CorrectionStr.ToLower()) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.Standard, null); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.WriteQ2CorrectionAltStr.ToLower()) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.Dewa, null); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.WriteQ2CorrectionGreeceStr.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.Greece, + currentActivity.Substring(iPerlCommunicationConstants.WriteQ2CorrectionGreeceStr.Length) + .Trim()); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.WriteQ2CorrectionRLStr.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.RL, + currentActivity.Substring(iPerlCommunicationConstants.WriteQ2CorrectionRLStr.Length).Trim()); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.WriteQ2CorrectionLRStr.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.LR, + currentActivity.Substring(iPerlCommunicationConstants.WriteQ2CorrectionLRStr.Length).Trim()); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.WriteQ2CorrectionIncl05Str.ToLower()) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.Standard_incl_05, null); + else if (currentActivity.ToLower() == + iPerlCommunicationConstants.WriteQ2CorrectionPlusIncl05Str.ToLower()) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.Standard_plus_incl_05, null); + else if (currentActivity.ToLower() == + iPerlCommunicationConstants.WriteQ2CorrectionAltIncl05Str.ToLower()) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.Dewa_incl_05, null); + else if (currentActivity.ToLower() == + iPerlCommunicationConstants.WriteQ2CorrectionPlusAltIncl05Str.ToLower()) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.Dewa_plus_incl_05, null); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.WriteQ2CorrectionGreeceIncl05Str.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.Greece_incl_05, + currentActivity.Substring(iPerlCommunicationConstants.WriteQ2CorrectionGreeceIncl05Str.Length) + .Trim()); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.WriteQ2CorrectionRLIncl05Str.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.RL_incl_05, + currentActivity.Substring(iPerlCommunicationConstants.WriteQ2CorrectionRLIncl05Str.Length) + .Trim()); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.WriteQ2CorrectionLRIncl05Str.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.LR_incl_05, + currentActivity.Substring(iPerlCommunicationConstants.WriteQ2CorrectionLRIncl05Str.Length) + .Trim()); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.UpdateBothQ2FactorsTestRLOnlyStr.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.UpdateBothQ2FactorsTestRLDir, null); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.UpdateBothQ2FactorsTestLROnlyStr.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.UpdateBothQ2FactorsTestLRDir, null); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.UpdateQ2CorrectionsStr.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.Update, + currentActivity.Substring(iPerlCommunicationConstants.UpdateQ2CorrectionsStr.Length).Trim()); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.ConditnlUpdateQ2CorrectionsStr.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.ConditionalUpdate, + currentActivity.Substring(iPerlCommunicationConstants.ConditnlUpdateQ2CorrectionsStr.Length) + .Trim()); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.ConditnlUpdateQ2CorrRLStr.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.ConditionalUpdateRL, + currentActivity.Substring(iPerlCommunicationConstants.ConditnlUpdateQ2CorrRLStr.Length).Trim()); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.ConditnlUpdateQ2CorrLRStr.ToLower())) + error = CalculateAndWriteQ2Corrections(threadID, ihead, wm, currentTest, ref resultStr, + Q2CorrType.ConditionalUpdateLR, + currentActivity.Substring(iPerlCommunicationConstants.ConditnlUpdateQ2CorrLRStr.Length).Trim()); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.Reset2HzCorrectionStr.ToLower()) + error = Reset2HzCorrection(threadID, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.Write2HzCorrectionStr.ToLower()) + error = Write2HzCorrection(threadID, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.GetDefaultQ2CorrectionsStr.ToLower()) + error = GetQ2PreCorrectionsFormRest(threadID, ihead, wm, ref resultStr); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.DewaReworkRLStr.ToLower()) + error = DewaRework(ihead, wm, FlowDir.R_L, ref resultStr); + else if (currentActivity.ToLower() == iPerlCommunicationConstants.DewaReworkLRStr.ToLower()) + error = DewaRework(ihead, wm, FlowDir.L_R, ref resultStr); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.StartTestingSealedMetersStr.ToLower())) + error = StartTestingSealedMeter(ihead, currentActivityStep, ref resultStr); + else if (currentActivity.ToLower() + .Contains(iPerlCommunicationConstants.EndTestingSealedMetersStr.ToLower())) + error = EndTestingSealedMeter(ihead, wm, ref resultStr); + else if (currentActivity.ToLower().Contains(iPerlCommunicationConstants.SimulateCmd.ToLower())) + error = Simulate(currentActivityStep, ihead, wm, ref resultStr); +#endif + else + { + error = CommErr.None; + + ConditionId id = ConditionId.Count; +#if IPERL + for (id = ConditionId.A; id < ConditionId.Count; id++) + { + if (currentActivity.ToLower() == ISequenceConditionOp.GetConditionNameFmt(id).ToLower()) + { + error = SetIperlCommMilestoneReached(testMethod, id, ref resultStr); + break; + } + } +#endif + if (id == ConditionId.Count) + { + resultStr = "Invalid activity"; /// No activity or milestone + } + } + } + + return true; + } + + + + public void ProcessResultOfWorkerActivity(int iMultiTestParamsItem, string currentActivity, int currentGroup, + common.ISmartReader iHead, WaterMeter wm, int wmNr0, CommErr error, string resultStr, bool[] ckbState, + int threadID) + { + if (iHead is SmartReader ihead) + { + /// + /// Process the result of RFID communication activity + /// + if (error == CommErr.HeadDisabledByUser || !ckbState[wmNr0]) + { + SmartCommunicationForm.OnCommCompleted(null, + new CommCompletedEventArgs(threadID, wmNr0, ihead, wm, Strings.Head_was_disabled_by_the_user, + CommErr.HeadDisabledByUser)); + } + else if (error == CommErr.None) + { + SmartCommunicationForm.OnCommCompleted(null, + new CommCompletedEventArgs(threadID, wmNr0, ihead, wm, resultStr, CommErr.None)); + } + else if ((error == CommErr.OutOfRange) || (error == CommErr.Q2OutOfRange)) + { + SmartCommunicationForm.OnCommCompleted(null, + new CommCompletedEventArgs(threadID, wmNr0, ihead, wm, resultStr, CommErr.CommFailed)); + } + else if (ihead.CommFailed || (error == CommErr.CommFailed)) + { + ihead.CommFailed = true; + + if (wm != null && (wm.ResultCode & (int)Results.Entities.ResultCode.RfidErrorCodeMask) == 0) + { + wm.ResultCode |= (((int)error << 16) | ((int)tests[iMultiTestParamsItem].ItemNr << 20)); + } + + SmartCommunicationForm.OnCommCompleted(null, + new CommCompletedEventArgs(threadID, wmNr0, ihead, wm, Strings.RFID_communication_failed, + error)); + } + else + { + ihead.CommFailed = true; + + if (wm != null && (wm.ResultCode & (int)Results.Entities.ResultCode.RfidErrorCodeMask) == 0) + { + wm.ResultCode |= (((int)error << 16) | ((int)tests[iMultiTestParamsItem].ItemNr << 20)); + } + + string failureCauseLocal = string.Format(Strings.failed_0_1_exclamation, currentActivity, error); + rfidDataLogger.ErrorFormat("Group={0}, Board={1}, {2}", currentGroup, ihead.MuxBoardNrOrGroup14, + failureCauseLocal); + + SmartCommunicationForm.OnCommCompleted(null, + new CommCompletedEventArgs(threadID, wmNr0, ihead, wm, failureCauseLocal, error)); + } + + } + } + + + + + +// #region Communication functions + +//#if IPERL + + /// + /// Read a complete configuration structure of the watermeter + /// + /// iPERL head object + /// Water meter object + /// String passed to caller + /// true on success + private CommErr ReadConfiguration(int currentActivityStep, SmartReader ihead, WaterMeter wm, + ref string resultStr) + { + /// + /// The activity is "Read configuration" (this enables the watermeter, resets error flag) + /// or "Read configuration if enabled" (this keeps the error flag). + /// + if (!multiTestParams[currentActivityStep].Activity.ToLower().Contains(" if enabled")) + { + ihead.CommFailed = false; + } + + if (ihead.CommFailed) return CommErr.CommFailed; + + ihead.ConfigStruct = null; /// Clear previous ConfigStruct, avoid reuse of (not anymore valid) PCB Number + + CommErr error = CommErr.Read; + int readRetVal = 0; + + /// Read configuration + byte[] config = null; + readRetVal = ReadRequestPort(ihead, MessageID.Configuration, MCI_Protocol.StructName.Configuration, 0, + ConfigStruct.Length, out config); + if (readRetVal == 0) + { + error = CommErr.None; + ihead.ConfigStruct = ConfigStruct.FromByteArray(config); + resultStr = ihead.ConfigStruct.ToString(1); + } + + return error + Math.Max(0, Math.Min(readRetVal, 4)); + } + + + /// + /// Set the watermeter to the test mode. + /// If testModeConfig is specified as a hexadeximal number appended to "set test mode ", it is verified + /// whether the testModeConfig is correctly set, if necessary it is changed to the specified value. + /// Read a part of configuration afterwards to verify the mode was set correctly. + /// + /// + /// iPERL head object + /// String passed to caller + /// true on success + private CommErr SetTestMode(int currentActivityStep, SmartReader ihead, ref string resultStr) + { + if (ihead.CommFailed || ihead.ConfigStruct == null) return CommErr.CommFailed; + + Byte testModeConfig = 0xA0; /// Default value + /// + if (multiTestParams[currentActivityStep].Activity.Length > + iPerlCommunicationConstants.SetTestModeStr.Length) + { + string testModeConfigStr = multiTestParams[currentActivityStep].Activity + .Substring(iPerlCommunicationConstants.SetTestModeStr.Length + 1); + UInt16 byteVal; + if (UInt16.TryParse(testModeConfigStr, NumberStyles.HexNumber, CultureInfo.CurrentCulture, + out byteVal) && byteVal <= 255) + { + testModeConfig = (Byte)byteVal; /// Update with specified value + } + } + + if (ihead.ConfigStruct.MeterState == MeterState.Test && ihead.ConfigStruct.TestModeConfig == testModeConfig) + { + /// Already in the correct test mode + resultStr = "Already " + ihead.ConfigStruct.ToString(1); + return CommErr.None; + } + + /// Communication necessary + CommErr error = CommErr.None; + + if (error == CommErr.None && (ihead.ConfigStruct.TestModeConfig != testModeConfig) && + (ihead.ConfigStruct.MeterState != MeterState.Active)) + { + /// Switch to Active mode in order to change TestModeConfig + error = CommErr.CmdActive; + byte[] cmd = new byte[1] { (byte)Command.SetActiveMode }; + if (0 == WriteRequestPort(ihead, MessageID.Command, MCI_Protocol.StructName.Command, 0, 1, cmd)) + { + error = CommErr.None; + } + + /// Delay min. 250 ms + Thread.Sleep(Math.Max(250, cfg.DelayBetweenRetries)); + } + + if (error == CommErr.None && (ihead.ConfigStruct.TestModeConfig != testModeConfig)) + { + /// Change the TestModeConfig if necessary + error = CommErr.Write; + byte[] tstMdCfg = new byte[1] { testModeConfig }; + if (0 == WriteRequestPort(ihead, MessageID.Configuration, MCI_Protocol.StructName.Configuration, 21, 1, + tstMdCfg)) + { + ihead.ConfigStruct.Update(21, tstMdCfg); + error = CommErr.None; + } + + /// Delay min. 250 ms + Thread.Sleep(Math.Max(250, cfg.DelayBetweenRetries)); + } + + if (error == CommErr.None) + { + /// Switch to test mode + error = CommErr.CmdTest; + byte[] cmd = new byte[1] { (byte)Command.SetTestMode }; + if (0 == WriteRequestPort(ihead, MessageID.Command, MCI_Protocol.StructName.Command, 0, 1, cmd)) + { + error = CommErr.None; + } + + /// Delay min. 250 ms + Thread.Sleep(Math.Max(250, cfg.DelayBetweenRetries)); + } + + /// Now the meter should be in the Test mode ... verify + if (error == CommErr.None) + { + /// Verify the configuration + Thread.Sleep(2000); // RFID: 1000 ms is enough, NFC needs min 2000 ms + error = CommErr.Verify; + byte[] config = null; + int retVal = ReadRequestPort(ihead, MessageID.Configuration, MCI_Protocol.StructName.Configuration, 0, + ConfigStruct.Length, out config); + if (0 == retVal) + { + ihead.ConfigStruct = ConfigStruct.FromByteArray(config); + if ((ihead.ConfigStruct.MeterState == MeterState.Test) && + (ihead.ConfigStruct.TestModeConfig == testModeConfig)) + { + error = CommErr.None; + resultStr = ihead.ConfigStruct.ToString(1); + } + } + } + + return error; + } + + + /// + /// Set the watermeter to the active mode. + /// Read a part of configuration afterwards to verify the mode was set correctly. + /// + /// iPERL head object + /// String passed to caller + /// true on success + private CommErr SetActiveMode(int threadId, SmartReader ihead, ref string resultStr) + { + CommErr error = CommErr.CmdActive; + + /// Switch to active mode + byte[] cmd = new byte[1] { (byte)Command.SetActiveMode }; + if (0 == WriteRequestPort(ihead, MessageID.Command, MCI_Protocol.StructName.Command, 0, 1, cmd)) + { + error = CommErr.None; + } + +#if VERIFY_ACTIVE_MODE + if (error == CommErr.None) + { + error = CommErr.Verify; + + /// Read configuration + byte[] cfg_0_3 = null; + if (ihead.ConfigStruct == null) + { + if (0 == ReadRequestPort(threadId, ihead, MessageID.Configuration, 0, 4, out cfg_0_3)) + { + ihead.ConfigStruct.Update(0, cfg_0_3); + if (ihead.ConfigStruct.MeterState == MeterState.Active) + { + error = CommErr.None; + resultStr = ihead.ConfigStruct.ToString(1); + } + } + } + } +#else + if (error == CommErr.None) + { + if (ihead.ConfigStruct == null) + resultStr = "OK (Config not available)"; + else + resultStr = ihead.ConfigStruct.ToString(1); + } +#endif + + return error; + } + + + /// + /// Read a complete calibration structure from the watermeter + /// + /// Water meter object + /// String passed to caller + /// true on success + private CommErr ReadCalibration(SmartReader ihead, WaterMeter wm, ref string resultStr) + { + if (ihead.CommFailed) return CommErr.CommFailed; + + CommErr error = CommErr.Read; + int readRetVal = 0; + + /// Read calibration + byte[] calib = null; + readRetVal = ReadRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 0, + CalibrationStruct.Length, out calib); + if (readRetVal == 0) + { + ihead.CalibrationStruct = CalibrationStruct.FromByteArray(calib); + + if (wm.OrigCalibFactor == 0) + { + wm.OrigCalibFactor = ihead.CalibrationStruct.Calibration; + wm.FWVersion = ihead.CalibrationStruct.FWVersionStr(); + } + + resultStr = ihead.CalibrationStruct.ToString(); + error = ihead.VerifyIPerlType() ? CommErr.None : CommErr.WrongIPerlType; + } + + return error + Math.Max(0, Math.Min(readRetVal, 4)); + } + + + /// + /// Read a complete calibration structure from the watermeter + /// + /// Water meter object + /// String passed to caller + /// true on success + private CommErr ReadCalibrationV4(SmartReader ihead, WaterMeter wm, ref string resultStr) + { + if (ihead.CommFailed) return CommErr.CommFailed; + + CommErr error = CommErr.Read; + int readRetVal = 0; + + /// Read calibration + byte[] calib = null; + readRetVal = ReadRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 0, + CalibrationStructV4.Length, out calib); + if (readRetVal == 0) + { + ihead.CalibrationStructV4 = CalibrationStructV4.FromByteArray(calib); + + if ((wm.OrigCalibFactor == 0) && (wm.OrigCalibFactorLNA == 0)) + { + wm.OrigCalibFactor = ihead.CalibrationStructV4.Calibration; + wm.OrigCalibFactorLNA = ihead.CalibrationStructV4.CalibrationLNA; + wm.FWVersion = ihead.CalibrationStructV4.FWVersionStr(); + } + + resultStr = ihead.CalibrationStructV4.ToString(); + error = ihead.VerifyIPerlType() ? CommErr.None : CommErr.WrongIPerlType; + } + + return error + Math.Max(0, Math.Min(readRetVal, 4)); + } + + + /// + /// Write the calculated calibration factor to the water meter. + /// Read a part of CalibrationStruct afterwards to verify factor was written correctly. + /// + /// + /// Thread ID + /// IPerlReader object + /// Water meter object + /// String passed to caller + /// true on success + private CommErr WriteCalibrationFactor(int currentActivityStep, int threadId, SmartReader ihead, WaterMeter wm, + ref string resultStr) + { + if (ihead.CommFailed || (ihead.CalibrationStruct == null)) return CommErr.CommFailed; + + /// + /// Parse calibration factor (1 argument) or calibration factor limits (2 arguments) + /// + UInt16 newCalibFactor = 0; + if (multiTestParams[currentActivityStep].Activity.Length > + iPerlCommunicationConstants.WriteCalibrationFactorStr.Length) + { + UInt16 factorLimitLo; + UInt16 factorLimitHi; + UInt16 val; + + string calibFactrorStr = multiTestParams[currentActivityStep].Activity + .Substring(iPerlCommunicationConstants.WriteCalibrationFactorStr.Length + 1); + string[] arguments = calibFactrorStr.Split(new char[] { ' ' }); + + if (arguments.Length >= 2 && + UInt16.TryParse(arguments[0], out factorLimitLo) && factorLimitLo > 0 && + UInt16.TryParse(arguments[1], out factorLimitHi) && factorLimitHi > 0) + { + /// + /// Lower and upper limits for the calibration factor are specified as iPerlCommunication activity arguments + /// + if (ihead.LastTestResult == null || !ihead.LastTestResult.TestDone) return CommErr.MissingTest; + + newCalibFactor = ihead.CalculateNewCalibFactor(ihead.LastTestResult, ihead.CalibFactor, + factorLimitLo, factorLimitHi); + } + else if (arguments.Length == 1 && UInt16.TryParse(arguments[0], out val) && val > 0) + { + /// + /// Calibration factor value is specified as an iPerlCommunication activity argument + /// + newCalibFactor = val; /// Update with specified value + } + else if (arguments.Length == 1 && wm.GetTestData(arguments[0]) != null) + { + /// + /// Adjustment test name is specified as an iPerlCommunication activity argument + /// + Results.Entities.TestData adjustTestData = wm.GetTestData(arguments[0]); + Results.Entities.MeterTestRslt adjustTestRslt; + if (adjustTestData == null) + { + return CommErr.MissingTest; + } + else if (adjustTestData.Repeats == 1) + { + /// Find a test result if Repeats == 1 + adjustTestRslt = wm.GetMeterTestRslt(arguments[0]); + + if (adjustTestRslt == null || !adjustTestRslt.TestDone) return CommErr.MissingTest; + } + else + { + /// Calculate a summarized test result if Repeats > 1 + adjustTestRslt = new Results.Entities.MeterTestRslt(); + for (int i = 1; i <= adjustTestData.Repeats; i++) + { + Results.Entities.MeterTestRslt oneMTR = + wm.GetMeterTestRslt(Utils.TestTitle(adjustTestData, i)); + if (oneMTR == null || !oneMTR.TestDone) return CommErr.MissingTest; + + adjustTestRslt.VolumeMeter += oneMTR.VolumeMeter; + adjustTestRslt.VolumeRef += oneMTR.VolumeRef; + } + } + + newCalibFactor = ihead.CalculateNewCalibFactor(adjustTestRslt, ihead.CalibFactor, + ihead.FactorLimitLo, ihead.FactorLimitHi); + } + else + { + /// + /// Otherwise the last test is supposed to be an adjustment test + /// + if (ihead.LastTestResult == null || !ihead.LastTestResult.TestDone) return CommErr.MissingTest; + + newCalibFactor = ihead.CalculateNewCalibFactor(ihead.LastTestResult, ihead.CalibFactor, + ihead.FactorLimitLo, ihead.FactorLimitHi); + } + } + else + { + /// + /// No iPerlCommunication activity arguments --> The last test is supposed to be an adjustment test + /// + if (ihead.LastTestResult == null || !ihead.LastTestResult.TestDone) return CommErr.MissingTest; + + newCalibFactor = ihead.CalculateNewCalibFactor(ihead.LastTestResult, ihead.CalibFactor, + ihead.FactorLimitLo, ihead.FactorLimitHi); + } + + if (newCalibFactor == 0) return CommErr.OutOfRange; + + /// + /// Start communication with iPerl + /// + CommErr error; + byte[] data = new byte[2] { (byte)(newCalibFactor & 0x00FF), (byte)((newCalibFactor >> 8) & 0x00FF) }; + /// + /// Write the new calibration factor (up to cfg.MaxCommRetries tims) + /// + error = CommErr.Write; + if (0 == WriteRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 2, 2, data)) + { + /// + /// Read and verify the calibration factor + /// + error = CommErr.ReadAfterWrite; + byte[] calib_2_3 = null; + if (0 == ReadRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 2, 2, + out calib_2_3)) + { + error = CommErr.Verify; + if (calib_2_3 != null && calib_2_3.Length == 2 && data[0] == calib_2_3[0] && + data[1] == calib_2_3[1]) + { + error = CommErr.None; + ihead.CalibrationStruct.Update(data, 2); + wm.CalibFactor = newCalibFactor; + resultStr = ihead.CalibrationStruct.ToString(); + } + } + } + + ihead.CalibrationStruct.Update(data, 2); + + return error; + } + + + /// + /// Write the calculated calibration factor to the water meter. + /// Read a part of CalibrationStruct afterwards to verify factor was written correctly. + /// + /// Thread ID + /// IPerlReader object + /// Water meter object + /// String passed to caller + /// true on success + private CommErr WriteCalibrationV4Factors(int currentActivityStep, int threadId, SmartReader ihead, + WaterMeter wm, ref string resultStr) + { + if (ihead.CommFailed || (ihead.CalibrationStructV4 == null)) return CommErr.CommFailed; + + /// + /// Parse calibration factor (1 argument) or calibration factor limits (2 arguments) + /// + UInt16 newCalibFactor = 0; + UInt16 newCalibFactorLNA = 0; + if (multiTestParams[currentActivityStep].Activity.Length > + iPerlCommunicationConstants.WriteCalibrationV4FactorsStr.Length) + { + UInt16 factorLimitLo = 0; + UInt16 factorLimitHi = 0; + UInt16 lnaFactorLimitLo = 0; + UInt16 lnaFactorLimitHi = 0; + Results.Entities.TestData adjustTestData = null; + Results.Entities.TestData lnaAdjustTestData = null; + Results.Entities.MeterTestRslt adjustTestRslt = null; + Results.Entities.MeterTestRslt lnaAdjustTestRslt = null; + + string calibFactrorStr = multiTestParams[currentActivityStep].Activity + .Substring(iPerlCommunicationConstants.WriteCalibrationV4FactorsStr.Length + 1); + string[] arguments = calibFactrorStr.Split(new char[] { ' ' }); + + if (arguments.Length == 6) + { + if (!wm.TryGetTestData(arguments[0], out adjustTestData) || + !UInt16.TryParse(arguments[1], out factorLimitLo) || factorLimitLo <= 0 && + !UInt16.TryParse(arguments[2], out factorLimitHi) || factorLimitHi <= 0 && + !wm.TryGetTestData(arguments[3], out lnaAdjustTestData) || + !UInt16.TryParse(arguments[4], out lnaFactorLimitLo) || lnaFactorLimitLo <= 0 && + !UInt16.TryParse(arguments[5], out lnaFactorLimitHi) || lnaFactorLimitHi <= 0) + { + return CommErr.WrongArguments; + } + else + { + adjustTestRslt = GetAverageTestRslt(wm, adjustTestData); + lnaAdjustTestRslt = GetAverageTestRslt(wm, lnaAdjustTestData); + + if ((adjustTestRslt == null) || (lnaAdjustTestRslt == null)) + { + return CommErr.MissingTest; + } + + newCalibFactor = ihead.CalculateNewCalibFactor(adjustTestRslt, ihead.CalibFactor, factorLimitLo, + factorLimitHi); + newCalibFactorLNA = ihead.CalculateNewCalibFactor(lnaAdjustTestRslt, ihead.CalibFactorLNA, + lnaFactorLimitLo, lnaFactorLimitHi); + } + } + else if (arguments.Length == 4) + { + if (!wm.TryGetTestData(arguments[0], out adjustTestData) || + !wm.TryGetTestData(arguments[1], out lnaAdjustTestData) || + !UInt16.TryParse(arguments[2], out lnaFactorLimitLo) || lnaFactorLimitLo <= 0 && + !UInt16.TryParse(arguments[3], out lnaFactorLimitHi) || lnaFactorLimitHi <= 0) + { + return CommErr.WrongArguments; + } + else + { + factorLimitLo = ihead.FactorLimitLo; + factorLimitHi = ihead.FactorLimitHi; + + adjustTestRslt = GetAverageTestRslt(wm, adjustTestData); + lnaAdjustTestRslt = GetAverageTestRslt(wm, lnaAdjustTestData); + + if ((adjustTestRslt == null) || (lnaAdjustTestRslt == null)) + { + return CommErr.MissingTest; + } + + newCalibFactor = ihead.CalculateNewCalibFactor(adjustTestRslt, ihead.CalibFactor, factorLimitLo, + factorLimitHi); + newCalibFactorLNA = ihead.CalculateNewCalibFactor(lnaAdjustTestRslt, ihead.CalibFactorLNA, + lnaFactorLimitLo, lnaFactorLimitHi); + } + } + else if (arguments.Length == 2) + { + if (!wm.TryGetTestData(arguments[0], out adjustTestData) || + !wm.TryGetTestData(arguments[1], out lnaAdjustTestData)) + { + return CommErr.WrongArguments; + } + else + { + factorLimitLo = ihead.FactorLimitLo; + factorLimitHi = ihead.FactorLimitHi; + lnaFactorLimitLo = ihead.FactorLimitLo; + lnaFactorLimitHi = ihead.FactorLimitHi; + + adjustTestRslt = GetAverageTestRslt(wm, adjustTestData); + lnaAdjustTestRslt = GetAverageTestRslt(wm, lnaAdjustTestData); + + if ((adjustTestRslt == null) || (lnaAdjustTestRslt == null)) + { + return CommErr.MissingTest; + } + + newCalibFactor = ihead.CalculateNewCalibFactor(adjustTestRslt, ihead.CalibFactor, factorLimitLo, + factorLimitHi); + newCalibFactorLNA = ihead.CalculateNewCalibFactor(lnaAdjustTestRslt, ihead.CalibFactorLNA, + lnaFactorLimitLo, lnaFactorLimitHi); + } + } + else + { + return CommErr.WrongArguments; + } + } + else + { + return CommErr.WrongArguments; + } + + if (newCalibFactor == 0 || newCalibFactorLNA == 0) return CommErr.OutOfRange; + + /// + /// Start communication with iPerl + /// + CommErr error; + byte[] data = new byte[2] { (byte)(newCalibFactor & 0x00FF), (byte)((newCalibFactor >> 8) & 0x00FF) }; + byte[] dataLNA = new byte[2] + { (byte)(newCalibFactorLNA & 0x00FF), (byte)((newCalibFactorLNA >> 8) & 0x00FF) }; + /// + /// Write the new calibration factor (up to cfg.MaxCommRetries tims) + /// + error = CommErr.Write; + if (0 == WriteRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 2, 2, data) && + 0 == WriteRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 34, 2, + dataLNA)) + { + /// + /// Read and verify the calibration factor + /// + error = CommErr.ReadAfterWrite; + byte[] calib_2_3 = null; + byte[] calib_34_35 = null; + if (0 == ReadRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 2, 2, + out calib_2_3) && + 0 == ReadRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 34, 2, + out calib_34_35)) + { + error = CommErr.Verify; + if (calib_2_3 != null && calib_2_3.Length == 2 && data[0] == calib_2_3[0] && + data[1] == calib_2_3[1] && + calib_34_35 != null && calib_34_35.Length == 2 && dataLNA[0] == calib_34_35[0] && + dataLNA[1] == calib_34_35[1]) + { + error = CommErr.None; + ihead.CalibrationStructV4.Update(data, 2); + ihead.CalibrationStructV4.Update(dataLNA, 34); + wm.CalibFactor = newCalibFactor; + wm.CalibFactorLNA = newCalibFactorLNA; + resultStr = ihead.CalibrationStructV4.ToString(); + } + } + } + + ihead.CalibrationStructV4.Update(data, 2); + ihead.CalibrationStructV4.Update(dataLNA, 34); + + return error; + } + + + /// + /// Get averaged meter test result using test name and repetitions from a given test data. + /// + /// Water meter + /// TestData (name and repetitions) + /// (1) selected MeterTestRslt or (2) average of repeated MTR-s or (3) null when at least one MTR is missing + private Results.Entities.MeterTestRslt GetAverageTestRslt(WaterMeter wm, Results.Entities.TestData testData) + { + Results.Entities.MeterTestRslt avgTestRslt; + + if (testData.Repeats > 1) + { + /// Calculate a summarized test result if Repeats > 1 + avgTestRslt = new Results.Entities.MeterTestRslt(); + for (int i = 1; i <= testData.Repeats; i++) + { + Results.Entities.MeterTestRslt oneMTR = wm.GetMeterTestRslt(Utils.TestTitle(testData, i)); + if (oneMTR == null || !oneMTR.TestDone) return null; + + avgTestRslt.VolumeMeter += oneMTR.VolumeMeter; + avgTestRslt.VolumeRef += oneMTR.VolumeRef; + } + } + else + { + avgTestRslt = wm.GetMeterTestRslt(Utils.TestTitle(testData, 1)); + if (avgTestRslt == null || !avgTestRslt.TestDone) return null; + } + + return avgTestRslt; + } + + /// + /// Normalize calibration factor in case ti is close to value 8000. + /// + /// Water meter object + /// String passed to caller + /// true on success + private CommErr NormalizeCalibrationFactor(int threadId, SmartReader ihead, WaterMeter wm, ref string resultStr) + { + if (ihead.CommFailed || (ihead.CalibrationStruct == null)) return CommErr.CommFailed; + + if (ihead.FactorLimitLo <= ihead.CalibFactor && ihead.CalibFactor <= ihead.FactorLimitHi) + { + resultStr = "Calibration factor is OK"; + return CommErr.None; /// No need to update the calibration factor + } + + CommErr error = CommErr.Write; + + /// Determine the new calibration factor + UInt16 defaultCalibFactor; + switch (ihead.CalibrationStruct.MeterType) + { + case MeterType.DN15: + defaultCalibFactor = 2710; + break; + case MeterType.DN20: + defaultCalibFactor = 3746; + break; + case MeterType.DN25: + defaultCalibFactor = 3300; + break; + case MeterType.DN32: + defaultCalibFactor = 2500; + break; + case MeterType.DN40: + defaultCalibFactor = 3080; + break; + case MeterType.DN25_Q3_10: + case MeterType.CoaxManifold: + default: + defaultCalibFactor = 3040; + break; + } + + byte[] data = new byte[2] + { (byte)(defaultCalibFactor & 0x00FF), (byte)((defaultCalibFactor >> 8) & 0x00FF) }; + if (0 == WriteRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 2, 2, data)) + { + error = CommErr.None; + ihead.CalibrationStruct.Update(data, 2); + } + + if (error == CommErr.None) + { + error = CommErr.Verify; + + /// Read calibration + byte[] calib_2_3 = null; + if (0 == ReadRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 2, 2, + out calib_2_3)) + { + error = CommErr.None; + ihead.CalibrationStruct.Update(calib_2_3, 2); + resultStr = ihead.CalibrationStruct.ToString(); + } + } + + return error; + } + + + /// + /// Normalize calibration factor in case ti is close to value 8000. + /// + /// Water meter object + /// String passed to caller + /// true on success + private CommErr NormalizeCalibrationV4Factors(int threadId, SmartReader ihead, WaterMeter wm, + ref string resultStr) + { + if (ihead.CommFailed || (ihead.CalibrationStructV4 == null)) return CommErr.CommFailed; + + if (ihead.FactorLimitLo <= ihead.CalibFactor && ihead.CalibFactor <= ihead.FactorLimitHi) + { + resultStr = "Calibratin factor is OK"; + return CommErr.None; /// No need to update the calibration factor + } + + CommErr error = CommErr.Write; + + /// Determine the new calibration factor + UInt16 defaultCalibFactor; + switch (ihead.CalibrationStructV4.MeterType) + { + case MeterType.DN15: + defaultCalibFactor = 2710; + break; + case MeterType.DN20: + defaultCalibFactor = 3746; + break; + case MeterType.DN25: + defaultCalibFactor = 3300; + break; + case MeterType.DN32: + defaultCalibFactor = 2500; + break; + case MeterType.DN40: + defaultCalibFactor = 3080; + break; + case MeterType.DN25_Q3_10: + case MeterType.CoaxManifold: + default: + defaultCalibFactor = 3040; + break; + } + + byte[] data = new byte[2] + { (byte)(defaultCalibFactor & 0x00FF), (byte)((defaultCalibFactor >> 8) & 0x00FF) }; + if (0 == WriteRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 2, 2, data)) + { + error = CommErr.None; + ihead.CalibrationStructV4.Update(data, 2); + } + + if (error == CommErr.None) + { + error = CommErr.Verify; + + /// Read calibration + byte[] calib_2_3 = null; + if (0 == ReadRequestPort(ihead, MessageID.Calibration, MCI_Protocol.StructName.Calibration, 2, 2, + out calib_2_3)) + { + error = CommErr.None; + ihead.CalibrationStructV4.Update(calib_2_3, 2); + resultStr = ihead.CalibrationStructV4.ToString(); + } + } + + return error; + } + + + /// + /// Reset both Q2 correction factors in the memory to 0. + /// Read them back to verify factors were written correctly. + /// + /// Water meter object + /// String passed to caller + /// true on success + private CommErr ReadQ2Correction(int threadId, SmartReader ihead, WaterMeter wm, ref string resultStr) + { + if (ihead.CommFailed) return CommErr.CommFailed; + + CommErr error = CommErr.Read; + + /// Write zero Q2 correction + byte[] rdData = null; + /// + if (0 == ReadRequestPort(ihead, MessageID.MetrologyMemory, MCI_Protocol.StructName.ProductionInfo, + iPerlCommunicationConstants.Q2CorrFactorsAddr, 2, out rdData)) + { + error = CommErr.None; + } + + if ((error == CommErr.None) && (rdData != null) && (rdData.Length == 2)) + { + resultStr = "Q2 corrections successfully read"; + wm.Q2CorrLR = ihead.Q2CorrLR = (int)((SByte)rdData[0]); + wm.Q2CorrRL = ihead.Q2CorrRL = (int)((SByte)rdData[1]); + } + + return error; + } + + /// + /// Reset both Q2 correction factors to 0. + /// Read them back to verify factors were written correctly. + /// + /// Water meter object + /// String passed to caller + /// true on success + private CommErr ResetQ2Correction(int threadId, SmartReader ihead, WaterMeter wm, ref string resultStr) + { + return WriteQ2Corrections(threadId, ihead, wm, ref resultStr, 0, 0); + } + + /// + /// Set both Q2 correction factors to default values obtained by a REST service + /// Read them back to verify factors were written correctly. + /// + /// Water meter object + /// String passed to caller + /// true on success + private CommErr WriteDefaultQ2Corrections(int threadId, SmartReader ihead, WaterMeter wm, ref string resultStr) + { + if (ihead.CommFailed) return CommErr.CommFailed; + + if (ProcessData.IsQ2PreCorrectionCalculated) + { + return WriteQ2Corrections(threadId, ihead, wm, ref resultStr, ProcessData.CalculatedQ2PreCorrectionLR, + ProcessData.CalculatedQ2PreCorrectionRL); + } + else + { + int q2corrRL = 0; + int q2corrLR = 0; + + if (cfg is IiPerlTestMethodCfg cfgIPerl) + { + + switch (ihead.MeterType) + { + case MeterType.DN15: + q2corrRL = cfgIPerl.DfltQ2c_15_rl; + q2corrLR = cfgIPerl.DfltQ2c_15_lr; + break; + case MeterType.DN20: + q2corrRL = cfgIPerl.DfltQ2c_20_rl; + q2corrLR = cfgIPerl.DfltQ2c_20_lr; + break; + case MeterType.DN25: + q2corrRL = cfgIPerl.DfltQ2c_25_63_rl; + q2corrLR = cfgIPerl.DfltQ2c_25_63_lr; + break; + case MeterType.DN25_Q3_10: + q2corrRL = cfgIPerl.DfltQ2c_25_10_rl; + q2corrLR = cfgIPerl.DfltQ2c_25_10_lr; + break; + case MeterType.DN32: + q2corrRL = cfgIPerl.DfltQ2c_32_rl; + q2corrLR = cfgIPerl.DfltQ2c_32_lr; + break; + case MeterType.DN40: + q2corrRL = cfgIPerl.DfltQ2c_40_rl; + q2corrLR = cfgIPerl.DfltQ2c_40_lr; + break; + default: + q2corrRL = 0; + q2corrLR = 0; + break; + } + } + + return WriteQ2Corrections(threadId, ihead, wm, ref resultStr, q2corrLR, q2corrRL); + } + } + + /// + /// Write both Q2 correction factors to given values. + /// Read them back to verify factors were written correctly. + /// + /// Water meter object + /// String passed to caller + /// true on success + private CommErr WriteQ2Corrections(int threadId, SmartReader ihead, WaterMeter wm, ref string resultStr, + int factorLR, int factorRL) + { + if (ihead.CommFailed) return CommErr.CommFailed; + + CommErr error = CommErr.Write; + + /// Write zero Q2 correction + byte[] wrData = new byte[2] { (byte)factorLR, (byte)factorRL }; + /// + if (0 == WriteRequestPort(ihead, MessageID.MetrologyMemory, MCI_Protocol.StructName.ProductionInfo, + iPerlCommunicationConstants.Q2CorrFactorsAddr, wrData.Length, wrData)) + { + wm.Q2CorrRL = ihead.Q2CorrRL = factorRL; + wm.Q2CorrLR = ihead.Q2CorrLR = factorLR; + error = CommErr.None; + } + + /// + /// Verification disabled on 16.02.2016 + /// +#if VERIFY_Q2_CORR_RESET + /// Verify the correction factors + if (error == CommErr.None) + { + error = CommErr.Verify; + + /// Read calibration + byte[] rdData = null; + if ((0 == ReadRequestPort(threadId, ihead, MessageID.MetrologyMemory, Q2CorrFactorsAddr, 2, out rdData)) && + (rdData != null) && (rdData.Length == 2) && (rdData[0] == 0) && (rdData[1] == 0)) + { + error = CommErr.None; + resultStr = string.Format("Q2 correction set to LR={0}, RL={1}", factorLR, factorRL); + } + } +#else + if (error == CommErr.None) + { + resultStr = string.Format("Q2 correction set to LR={0}, RL={1}", factorLR, factorRL); + } +#endif + + return error; + } + + + private CommErr InitOrReadQ2Corrections(int threadId, SmartReader ihead, WaterMeter wm, ref string resultStr) + { + if (ihead.CommFailed) return CommErr.CommFailed; + if (wm.Pruefindex == 0) return CommErr.CommFailed; /// Unknown pruefindex + + if (wm.Pruefindex == 1) + { + int q2corrRL = 0; + int q2corrLR = 0; + if (ProcessData.IsQ2PreCorrectionCalculated) + { + q2corrRL = ProcessData.CalculatedQ2PreCorrectionRL; + q2corrLR = ProcessData.CalculatedQ2PreCorrectionLR; + } + else + { + if (cfg is IiPerlTestMethodCfg cfgIPerl) + switch (ihead.MeterType) + { + case MeterType.DN15: + q2corrRL = cfgIPerl.DfltQ2c_15_rl; + q2corrLR = cfgIPerl.DfltQ2c_15_lr; + break; + case MeterType.DN20: + q2corrRL = cfgIPerl.DfltQ2c_20_rl; + q2corrLR = cfgIPerl.DfltQ2c_20_lr; + break; + case MeterType.DN25: + q2corrRL = cfgIPerl.DfltQ2c_25_63_rl; + q2corrLR = cfgIPerl.DfltQ2c_25_63_lr; + break; + case MeterType.DN25_Q3_10: + q2corrRL = cfgIPerl.DfltQ2c_25_10_rl; + q2corrLR = cfgIPerl.DfltQ2c_25_10_lr; + break; + case MeterType.DN32: + q2corrRL = cfgIPerl.DfltQ2c_32_rl; + q2corrLR = cfgIPerl.DfltQ2c_32_lr; + break; + case MeterType.DN40: + q2corrRL = cfgIPerl.DfltQ2c_40_rl; + q2corrLR = cfgIPerl.DfltQ2c_40_lr; + break; + default: + q2corrRL = 0; + q2corrLR = 0; + break; + } + } + + return WriteQ2Corrections(threadId, ihead, wm, ref resultStr, q2corrLR, q2corrRL); + } + else + { + return ReadQ2Correction(threadId, ihead, wm, ref resultStr); + } + } + + + /// + /// Reset 2Hz correction factor in the memory to 0. + /// + /// Water meter object + /// String passed to caller + /// true on success + private CommErr Reset2HzCorrection(int threadId, SmartReader ihead, WaterMeter wm, ref string resultStr) + { + if (ihead.CommFailed) return CommErr.CommFailed; + + CommErr error = CommErr.Write; + + /// Write zero Q2 correction + byte[] wrData = new byte[1] { 0 }; + /// + if (0 == WriteRequestPort(ihead, MessageID.MetrologyMemory, MCI_Protocol.StructName.ProductionInfo, + iPerlCommunicationConstants.Hz2CorrFactorsAddr, wrData.Length, wrData)) + { + error = CommErr.None; + } + + /// + /// Verification disabled on 16.02.2016 + /// +#if false + /// Verify the correction factors + if (error == CommErr.None) + { + error = CommErr.Verify; + + /// Read calibration + byte[] rdData = null; + if ((0 == ReadRequestPort(threadId, ihead, MessageID.MetrologyMemory, Hz2CorrFactorsAddr, 1, out rdData)) && + (rdData != null) && (rdData.Length == 2) && (rdData[0] == 0) && (rdData[1] == 0)) + { + error = CommErr.None; + resultStr = "2Hz correction reset to 0"; + ihead.Q2CorrectionFactor = 0; + } + } +#else + if (error == CommErr.None) + { + resultStr = "2Hz correction reset to 0"; + ihead.Hz2Correction = 0; + } +#endif + + return error; + } + + + /// + /// Write the calculated Q2 correction factors to the memory. + /// Read them back to verify factors were written correctly. + /// + /// Write Q2 correction + /// Write Q2 correction Alt + /// Write Q2 correction [Greece | R-L | L-R] testname + /// Update Q2 corrections + /// Conditional update of Q2 corrections + /// + /// Water meter object + /// String passed to caller + /// true on success + private CommErr CalculateAndWriteQ2Corrections(int threadId, SmartReader ihead, WaterMeter wm, + Test test, ref string resultStr, Q2CorrType q2CorrType, string restOfLine) + { + var args = string.IsNullOrEmpty(restOfLine) ? new string[0] : restOfLine.Split(new char[] { ' ' }); + + Results.Entities.MeterTestRslt q2adjResult; /// The only test or the 1st test (the test in R-L direction) + Results.Entities.TestData + q2adjTestData; /// The 2nd test in L-R direction in case tests in both directions are done + /// + if (args.Length == 0) + { + /// In case of no test name argument, ihead.LastTestResult is used (if it exists) + if (ihead.LastTestResult == null || !ihead.LastTestResult.TestDone) return CommErr.MissingTest; + q2adjResult = ihead.LastTestResult; + q2adjTestData = q2adjResult.TestRslt.TestData; + } + else // if (args.Length >= 1) + { + /// Get test data from (the 1st) test name + q2adjTestData = wm.GetTestData(args[0]); + if (q2adjTestData == null) return CommErr.MissingTest; + + if (q2adjTestData.Repeats == 1) + { + /// Find a test result if Repets == 1 + q2adjResult = wm.GetMeterTestRslt(args[0]); + if (q2adjResult == null || !q2adjResult.TestDone) return CommErr.MissingTest; + } + else + { + /// Calculate a summarized test result if Repeats > 1 + q2adjResult = new Results.Entities.MeterTestRslt(); + for (int i = 1; i <= q2adjTestData.Repeats; i++) + { + Results.Entities.MeterTestRslt oneMTR = + wm.GetMeterTestRslt(string.Format("{0} ({1}/{2})", args[0], i, q2adjTestData.Repeats)); + if (oneMTR == null || !oneMTR.TestDone) return CommErr.MissingTest; + + q2adjResult.VolumeMeter += oneMTR.VolumeMeter; + q2adjResult.VolumeRef += oneMTR.VolumeRef; + + if (i == 1) + { + q2adjResult.CalibFactor = oneMTR.CalibFactor; + q2adjResult.CalibFactorLNA = oneMTR.CalibFactorLNA; + q2adjResult.Q2CorrRL = oneMTR.Q2CorrRL; + q2adjResult.Q2CorrLR = oneMTR.Q2CorrLR; + } + } + + q2adjResult.Error = + Config.Formulas.ErrorFromVolumes(q2adjResult.VolumeMeter, q2adjResult.VolumeRef); + } + } + + var q3mtr = wm.MeterTestRslts.FirstOrDefault(x => x.Name() == "Q3"); + double q3error = (q3mtr != null) ? q3mtr.Error : 0; + + Results.Entities.MeterTestRslt q2adjResult2 = null; /// 2nd test name for a test in L-R direction + Results.Entities.TestData q2adjTestData2 = null; + /// + if (args.Length >= 2) + { + /// Get the 2nd test data from the 2nd test name + q2adjTestData2 = wm.GetTestData(args[1]); + if (q2adjTestData2 == null) return CommErr.MissingTest; + + if (q2adjTestData2.Repeats == 1) + { + /// Find a test result if Repets == 1 + q2adjResult2 = wm.GetMeterTestRslt(args[1]); + if (q2adjResult2 == null || !q2adjResult2.TestDone) return CommErr.MissingTest; + } + else + { + /// Calculate a summarized test result if Repeats > 1 + q2adjResult2 = new Results.Entities.MeterTestRslt(); + for (int i = 1; i <= q2adjTestData2.Repeats; i++) + { + Results.Entities.MeterTestRslt oneMTR = + wm.GetMeterTestRslt(string.Format("{0} ({1}/{2})", args[1], i, q2adjTestData2.Repeats)); + if (oneMTR == null || !oneMTR.TestDone) return CommErr.MissingTest; + + q2adjResult2.VolumeMeter += oneMTR.VolumeMeter; + q2adjResult2.VolumeRef += oneMTR.VolumeRef; + + if (i == 1) + { + q2adjResult2.CalibFactor = oneMTR.CalibFactor; + q2adjResult2.CalibFactorLNA = oneMTR.CalibFactorLNA; + q2adjResult2.Q2CorrRL = oneMTR.Q2CorrRL; + q2adjResult2.Q2CorrLR = oneMTR.Q2CorrLR; + } + } + + q2adjResult2.Error = + Config.Formulas.ErrorFromVolumes(q2adjResult2.VolumeMeter, q2adjResult2.VolumeRef); + } + } + + + if (q2adjResult == null || + ((q2CorrType == Q2CorrType.Update || q2CorrType == Q2CorrType.ConditionalUpdate) && + q2adjResult2 == null)) + { + return CommErr.MissingTest; /// Procedure configuration error + } + + + if ((q2CorrType == Q2CorrType.ConditionalUpdate || + q2CorrType == Q2CorrType.ConditionalUpdateRL || + q2CorrType == Q2CorrType.ConditionalUpdateLR) && wm.PassedFromTests()) + { + resultStr = string.Format("No Q2 correction update"); + rfidDataLogger.InfoFormat("{0}({1}): No Q2 correction update", ihead.Name, ihead.SerialNr); + return CommErr.None; /// No Q2 correction when doing conditional update and WM passed + } + + double errLimitLo = test.ErrLimLo + test.Uncertainty; + double errLimitHi = test.ErrLimHi - test.Uncertainty; + + /// + /// Calculate Q2 correction factor(s) + /// + int q2CorrRL = 0; + int q2CorrLR = 0; + /// + if (q2CorrType == Q2CorrType.Standard || + q2CorrType == Q2CorrType.Standard_incl_05 || + q2CorrType == Q2CorrType.Standard_plus_incl_05) + { + /// + /// Standard process (RL) + /// No 2nd Q2 test, MakeQ2CorrectedFrom(...) is used to calculate Q2 error + /// Do not forget to modify iPerlCommunicationSeq.MakeQ2CorrectedFrom(...) + /// + double errorTarget = (q3error * q2adjResult.Error < 0) + ? (0.1 * q2adjResult.Error) + : (-0.1 * q2adjResult.Error); + q2CorrRL = Math.Max(-128, + Math.Min(127, + Convert.ToInt32(ihead.CalculateQ2CorrectionFactor(q2adjResult, q2adjResult.Q2CorrRL, + q2adjTestData.Qfrom, errorTarget)))); + } + else if (q2CorrType == Q2CorrType.Dewa || + q2CorrType == Q2CorrType.Dewa_incl_05 || + q2CorrType == Q2CorrType.Dewa_plus_incl_05) + { + /// + /// Standard process for the oposite flow direction (LR) + /// No 2nd Q2 test, MakeQ2CorrectedFrom(...) is used to calculate Q2 error + /// Do not forget to modify iPerlCommunicationSeq.MakeQ2CorrectedFrom(...) + /// + double errorTarget = (q3error * q2adjResult.Error < 0) + ? (0.1 * q2adjResult.Error) + : (-0.1 * q2adjResult.Error); + q2CorrLR = Math.Max(-128, + Math.Min(127, + Convert.ToInt32(ihead.CalculateQ2CorrectionFactor(q2adjResult, q2adjResult.Q2CorrLR, + q2adjTestData.Qfrom, errorTarget)))); + } + else if (q2CorrType == Q2CorrType.RL || + q2CorrType == Q2CorrType.RL_incl_05 || + q2CorrType == Q2CorrType.ConditionalUpdateRL) + { + /// + /// RL only + /// Assuming the 2nd Q2 test is done afterwards + /// MakeQ2CorrectedFrom(...) is not used + /// + if (q2CorrType == Q2CorrType.RL && Math.Abs(q2adjResult.Error) <= 0.5) + { + resultStr = string.Format("Q2 correction = 0 (writing bypassed)"); + return CommErr.None; + } + + q2CorrRL = Math.Max(-128, + Math.Min(127, + Convert.ToInt32(ihead.CalculateQ2CorrectionFactor(q2adjResult, q2adjResult.Q2CorrRL, + q2adjTestData.Qfrom)))); + } + else if (q2CorrType == Q2CorrType.LR || + q2CorrType == Q2CorrType.LR_incl_05 || + q2CorrType == Q2CorrType.ConditionalUpdateLR) + { + /// + /// LR only + /// Assuming the 2nd Q2 test is done afterwards + /// MakeQ2CorrectedFrom(...) is not used + /// + if (q2CorrType == Q2CorrType.LR && Math.Abs(q2adjResult.Error) <= 0.5) + { + resultStr = string.Format("Q2 correction = 0 (writing bypassed)"); + return CommErr.None; + } + + q2CorrLR = Math.Max(-128, + Math.Min(127, + Convert.ToInt32(ihead.CalculateQ2CorrectionFactor(q2adjResult, q2adjResult.Q2CorrLR, + q2adjTestData.Qfrom)))); + } + else if (q2CorrType == Q2CorrType.Update || q2CorrType == Q2CorrType.ConditionalUpdate) + { + /// + /// After tests in both directions + /// Assuming the 2nd Q2 tests are done afterwards + /// MakeQ2CorrectedFrom(...) is not used + /// + q2CorrRL = Math.Max(-128, + Math.Min(127, + Convert.ToInt32(ihead.CalculateQ2CorrectionFactor(q2adjResult, q2adjResult.Q2CorrRL, + q2adjTestData.Qfrom)))); + q2CorrLR = Math.Max(-128, + Math.Min(127, + Convert.ToInt32(ihead.CalculateQ2CorrectionFactor(q2adjResult2, q2adjResult2.Q2CorrLR, + q2adjTestData2.Qfrom)))); + } + else if (q2CorrType == Q2CorrType.Greece || q2CorrType == Q2CorrType.Greece_incl_05 || + q2CorrType == Q2CorrType.UpdateBothQ2FactorsTestLRDir) + { + /// + /// Process for Greece + /// + if (q2CorrType == Q2CorrType.Greece && Math.Abs(q2adjResult.Error) <= 0.5) + { + resultStr = string.Format("Q2 correction = 0 (writing bypassed)"); + return CommErr.None; + } + + q2CorrLR = Math.Max(-128, + Math.Min(127, + Convert.ToInt32(ihead.CalculateQ2CorrectionFactor(q2adjResult, q2adjResult.Q2CorrLR, + q2adjTestData.Qfrom)))); + q2CorrRL = q2CorrLR; + } + else if (q2CorrType == Q2CorrType.UpdateBothQ2FactorsTestRLDir) + { + /// + /// Another strange process + /// + double errorTarget = (q3error * q2adjResult.Error < 0) + ? (0.1 * q2adjResult.Error) + : (-0.1 * q2adjResult.Error); + q2CorrRL = Math.Max(-128, + Math.Min(127, + Convert.ToInt32(ihead.CalculateQ2CorrectionFactor(q2adjResult, q2adjResult.Q2CorrRL, + q2adjTestData.Qfrom, errorTarget)))); + q2CorrLR = q2CorrRL; + } + + + /// + /// Write calculated Q2 correction factors into the water meter + /// + if (ihead.CommFailed) return CommErr.CommFailed; + + CommErr error = CommErr.Write; + + if (q2CorrType == Q2CorrType.Standard || + q2CorrType == Q2CorrType.Standard_incl_05 || + q2CorrType == Q2CorrType.Standard_plus_incl_05 || + q2CorrType == Q2CorrType.RL || + q2CorrType == Q2CorrType.RL_incl_05 || + q2CorrType == Q2CorrType.ConditionalUpdateRL) + { + /// + /// Write R-L Q2 correction factor only + /// + byte[] wrData = new byte[1] { (byte)q2CorrRL }; + if (0 == WriteRequestPort(ihead, MessageID.MetrologyMemory, MCI_Protocol.StructName.ProductionInfo, + iPerlCommunicationConstants.Q2CorrFactorsAddrRL, wrData.Length, wrData)) + { + wm.Q2CorrRL = ihead.Q2CorrRL = q2CorrRL; + resultStr = string.Format("Q2 correction: RL={0}", q2CorrRL); + error = CommErr.None; + } + } + else if (q2CorrType == Q2CorrType.Dewa || + q2CorrType == Q2CorrType.Dewa_incl_05 || + q2CorrType == Q2CorrType.Dewa_plus_incl_05 || + q2CorrType == Q2CorrType.LR || + q2CorrType == Q2CorrType.LR_incl_05 || + q2CorrType == Q2CorrType.ConditionalUpdateLR) + { + /// + /// Write L-R Q2 correction factor only + /// + byte[] wrData = new byte[1] { (byte)q2CorrLR }; + if (0 == WriteRequestPort(ihead, MessageID.MetrologyMemory, MCI_Protocol.StructName.ProductionInfo, + iPerlCommunicationConstants.Q2CorrFactorsAddrLR, wrData.Length, wrData)) + { + wm.Q2CorrLR = ihead.Q2CorrLR = q2CorrLR; + resultStr = string.Format("Q2 correction: LR={0}", q2CorrLR); + error = CommErr.None; + } + } + else + { + /// + /// Write both Q2 correction factors + /// + byte[] wrData = new byte[2] { (byte)q2CorrLR, (byte)q2CorrRL }; + if (0 == WriteRequestPort(ihead, MessageID.MetrologyMemory, MCI_Protocol.StructName.ProductionInfo, + iPerlCommunicationConstants.Q2CorrFactorsAddr, wrData.Length, wrData)) + { + wm.Q2CorrRL = ihead.Q2CorrRL = q2CorrRL; + wm.Q2CorrLR = ihead.Q2CorrLR = q2CorrLR; + resultStr = string.Format("Q2 correction: RL={0}, LR={1}", q2CorrRL, q2CorrLR); + error = CommErr.None; + } + } + + + /// + /// Q2 correction factor was successfully written to the water meter. + /// Check whether the original error at Q2 was within limits. + /// + if (error == CommErr.None && + (q2adjResult.Error < errLimitLo || errLimitHi < q2adjResult.Error || + (q2adjResult2 != null && (q2adjResult2.Error < errLimitLo || errLimitHi < q2adjResult2.Error)))) + { + resultStr += " (OoR)"; + + /// At least one of Q2 errors is out of range for Q2 correction + log.WarnFormat( + "OoR => anyhow Q2 correction was done: Pos={0}, PCB#={1}, ***Q2_RL err={2}%***, ***Q2_LR err={3}%***, [Lo={4}%, Hi={5}%]", + ihead.Name, + ihead.SerialNr, + q2adjResult.Error.ToString("F2"), + q2adjResult2 != null ? q2adjResult2.Error.ToString("F2") : string.Empty, + errLimitLo.ToString("F2"), + errLimitHi.ToString("F2")); + + rfidDataLogger.InfoFormat("{0}({1}): Q2 error ({2},{3}) is out of range ({4}..{5}) => Failed", + ihead.Name, + ihead.SerialNr, + q2adjResult.Error.ToString("F2"), + q2adjResult2 != null ? q2adjResult2.Error.ToString("F2") : string.Empty, + errLimitLo.ToString("F2"), + errLimitHi.ToString("F2")); + + error = CommErr.Q2OutOfRange; + } + + /// + /// Verification disabled on 16.02.2016 + /// +#if false + if (error == CommErr.None) + { + error = CommErr.Verify; + + /// Read calibration + byte[] rdData = null; + if ((0 == ReadRequestPort(threadId, ihead, MessageID.MetrologyMemory, Q2CorrFactorsAddr, 2, out rdData)) && + (rdData != null) && (rdData.Length == 2) && (rdData[0] == q2CorrRFlow) && (rdData[1] == q2CorrLFlow)) + { + error = CommErr.None; + resultStr = + string.Format("Q2 factors: R-flow={0}, L-flow={1}", (SByte)q2CorrRFlow, (SByte)q2CorrLFlow); + rfidDataLogger.Warn(ihead.Name + ": " + resultStr); + + wm.Q2ErrWOCorrection = ihead.Q2ErrorWOCorrection; + if (q2CorrType != Q2CorrType.LR) wm.Q2CorrRFlow = ihead.Q2CorrRFlow = (int)(sbyte)q2CorrRFlow; + if (q2CorrType != Q2CorrType.RL) wm.Q2CorrLFlow = ihead.Q2CorrLFlow = (int)(sbyte)q2CorrLFlow; + } + } +#else + + if ((q2CorrType != Q2CorrType.Update) && (q2CorrType != Q2CorrType.ConditionalUpdate)) + { + wm.Q2ErrWOCorrection = ihead.Q2ErrWOCorrection = q2adjResult.Error; + } + + /// + /// Update WaterMeter entity and IPerlReader + /// + if (error == CommErr.None) + { + rfidDataLogger.WarnFormat("{0}({1}): {2}", ihead.Name, ihead.SerialNr, resultStr); + } +#endif + + return error; + } + + + /// + /// Write the calculated 2Hz correction factor to the memory. + /// + /// Water meter object + /// String passed to caller + /// true on success + private CommErr Write2HzCorrection(int threadId, SmartReader ihead, WaterMeter wm, ref string resultStr) + { + if (ihead.CommFailed) return CommErr.CommFailed; + + /// Calculate the correction + ihead.Hz2CorrectionDone = false; + int hz2CorrectionFactor; + bool wmOK = ihead.Calculate2HzCorrectionFactor(ihead.LastTestResult2, ihead.LastTestResult, + out ihead.Diff2Hz8Hz, out hz2CorrectionFactor); + + if (hz2CorrectionFactor == 0) + { + resultStr = string.Format("2Hz correction = 0 (writing bypassed)"); + return CommErr.None; + } + + CommErr error = CommErr.Write; + + Byte hz2CorrectionByte = (byte)(hz2CorrectionFactor & 0x000000FF); + byte[] wrData = new byte[1] { hz2CorrectionByte }; + /// + if (0 == WriteRequestPort(ihead, MessageID.MetrologyMemory, MCI_Protocol.StructName.ProductionInfo, + iPerlCommunicationConstants.Hz2CorrFactorsAddr, wrData.Length, wrData)) + { + error = CommErr.None; + wm.Hz2Correction = ihead.Hz2Correction = hz2CorrectionFactor; + wm.Hz2CorrectionDone = ihead.Hz2CorrectionDone = true; + } + + /// + /// Verification disabled on 16.02.2016 + /// +#if false + if (error == CommErr.None) + { + error = CommErr.Verify; + + /// Read calibration + byte[] rdData = null; + if ((0 == ReadRequestPort(wm, MessageID.MetrologyMemory, Hz2CorrFactorsAddr, 1, out rdData)) && + (rdData != null) && (rdData.Length == 2) && (rdData[0] == hz2CorrectionByte)) + { + error = CommErr.None; + resultStr = string.Format("2Hz factor = {0}", (SByte)hz2CorrectionByte); + rfidDataLogger.Warn(ihead.Name + ": " + resultStr); + wm.Hz2Correction = ihead.Hz2CorrectionFactor = hz2CorrectionFactor; + wm.Diff2Hz8Hz = ihead.Diff2Hz8Hz; + wm.Hz2CorrectionDone = ihead.Hz2CorrectionDone; + } + } +#else + if (error == CommErr.None) + { + resultStr = string.Format("2Hz correction = {0}", (SByte)hz2CorrectionByte); + rfidDataLogger.Warn(ihead.Name + ": " + resultStr); + wm.Diff2Hz8Hz = ihead.Diff2Hz8Hz; + wm.Hz2CorrectionDone = ihead.Hz2CorrectionDone = true; + wm.Hz2Correction = ihead.Hz2Correction = hz2CorrectionFactor; + } +#endif + + return error; + } + + /// + /// DEWA rework. + /// + /// iPERL head object + /// Water meter object + /// Arrow direction + /// String passed to caller + /// true on success + private CommErr DewaRework(SmartReader ihead, WaterMeter wm, FlowDir flowDir, ref string resultStr) + { + if (ihead.CommFailed) return CommErr.CommFailed; + + byte arrow = (flowDir == FlowDir.L_R) + ? (byte)1 + : (byte)2; /// L-R is reverse flow (=1), R-L is forward flow (=2)(default) + + List dataToBeWritten = new List + { + new iPerlDataWrite(MessageID.MetrologyMemory, MCI_Protocol.StructName.Calibration, + ihead.CommInterface == CommunicationInterface.NFC ? 33 : 0x19A1, new byte[] { (byte)0xA5 }, + "Open Sealing"), // ????? + new iPerlDataWrite(MessageID.MetrologyMemory, MCI_Protocol.StructName.Calibration, + ihead.CommInterface == CommunicationInterface.NFC ? 5 : 0x1985, new byte[] { arrow }, "Arrow"), + new iPerlDataWrite(MessageID.MetrologyMemory, MCI_Protocol.StructName.Calibration, + ihead.CommInterface == CommunicationInterface.NFC ? 28 : 0x199C, new byte[5], "Clear S/N"), + new iPerlDataWrite(MessageID.Configuration, MCI_Protocol.StructName.Configuration, 0x0015, + new byte[] { (byte)0xA0 }, "Test mode config = A0"), + new iPerlDataWrite(MessageID.MetrologyMemory, MCI_Protocol.StructName.Calibration, + ihead.CommInterface == CommunicationInterface.NFC ? 14 : 0x198E, + new byte[] { (byte)(2510 & 0xFF), (byte)((2510 >> 8) & 0xFF) }, "Receipt mean current") + }; + + // check meter status: 1-Idle, 2-Active, 3-Test, 4-End Of Life + if (0 == ReadRequestPort(ihead, MessageID.Configuration, MCI_Protocol.StructName.Configuration, 1, 1, + out byte[] rdData)) + { + int eMeterState = (int)((SByte)rdData[0]); + switch (eMeterState) + { + case 1: // Idle -> Test -> Active + dataToBeWritten.Add(new iPerlDataWrite(MessageID.Command, MCI_Protocol.StructName.Command, + 0x0000, new byte[] { (byte)Command.SetTestMode }, "Set test mode")); + dataToBeWritten.Add(new iPerlDataWrite(MessageID.Command, MCI_Protocol.StructName.Command, + 0x0000, new byte[] { (byte)Command.SetActiveMode }, "Set active mode")); + break; + case 3: // Test Mode -> Active + dataToBeWritten.Add(new iPerlDataWrite(MessageID.Command, MCI_Protocol.StructName.Command, + 0x0000, new byte[] { (byte)Command.SetActiveMode }, "Set active mode")); + break; + } + } + + dataToBeWritten.Add(new iPerlDataWrite(MessageID.RadioPassthrough, MCI_Protocol.StructName.RadioInfo, + 0x1804, new byte[] { (byte)1 }, "System Status")); + dataToBeWritten.Add(new iPerlDataWrite(MessageID.RadioPassthrough, MCI_Protocol.StructName.RadioParams, + 0x1898, new byte[] { (byte)3 }, "WakeUpInterval")); + dataToBeWritten.Add(new iPerlDataWrite(MessageID.RadioPassthrough, MCI_Protocol.StructName.RadioParams, + 0x18A2, new byte[] + { + (byte)0x4A, (byte)0x53, (byte)0x3B, (byte)0x8F, + (byte)0x70, (byte)0x31, (byte)0xC2, (byte)0x5D, + (byte)0x6F, (byte)0x2D, (byte)0xE8, (byte)0x07, + (byte)0x6E, (byte)0x0F, (byte)0x97, (byte)0xC3, + }, "AES Key Crypted")); + + int successfyllyWrittenFlags = + 0; /// Ones in this word represent communication failures, LSB represents the first step + int mask = 1; + foreach (var wData in dataToBeWritten) + { + bool isSuccessfullyWritten = false; + if (0 == WriteRequestPort(ihead, wData.MessageID, wData.StructName, wData.Offset, wData.Data.Length, + wData.Data)) + { + isSuccessfullyWritten = true; + } + + if (!isSuccessfullyWritten) + { + /// Communication failed in this step + successfyllyWrittenFlags = (successfyllyWrittenFlags | mask); + } + + mask = (mask << 1); /// Adjust the mask for the next step + } + + if (successfyllyWrittenFlags == 0) + { + /// Success + if (ihead.ConfigStruct != null) + { + ihead.ConfigStruct.MeterState = MeterState.Active; + ihead.ConfigStruct.TestModeConfig = (byte)0xA0; + } + + resultStr = "OK"; + return CommErr.None; + } + else + { + return CommErr.Write; + } + } + + /// + /// Start testing a sealed meter + /// + /// iPERL head object + /// + /// String passed to caller + /// true on success + private CommErr StartTestingSealedMeter(SmartReader ihead, int currentActivityStep, ref string resultStr) + { + if (ihead.CommFailed) return CommErr.CommFailed; + + Byte testModeConfig = 0xA0; /// Default value + /// + if (multiTestParams[currentActivityStep].Activity.Length > + iPerlCommunicationConstants.StartTestingSealedMetersStr.Length) + { + string testModeConfigStr = multiTestParams[currentActivityStep].Activity + .Substring(iPerlCommunicationConstants.StartTestingSealedMetersStr.Length + 1); + UInt16 byteVal; + if (UInt16.TryParse(testModeConfigStr, NumberStyles.HexNumber, CultureInfo.CurrentCulture, + out byteVal) && byteVal <= 255) + { + testModeConfig = (Byte)byteVal; /// Update with specified value + } + } + + iPerlDataWrite[] dataToBeWritten = new iPerlDataWrite[] + { + new iPerlDataWrite(MessageID.MetrologyMemory, MCI_Protocol.StructName.Calibration, + ihead.CommInterface == CommunicationInterface.NFC ? 33 : 0x19A1, new byte[] { (byte)0xA5 }, + "Open Sealing"), /// 0x5A=sealed, 0xA5=unsealed ?????? + new iPerlDataWrite(MessageID.RadioPassthrough, MCI_Protocol.StructName.RadioInfo, 0x1804, + new byte[] { (byte)1 }, "System Status"), + new iPerlDataWrite(MessageID.Command, MCI_Protocol.StructName.Command, 0x0000, + new byte[] { (byte)Command.SetActiveMode }, "Set active mode"), + new iPerlDataWrite(MessageID.Configuration, MCI_Protocol.StructName.Configuration, 0x0015, + new byte[] { testModeConfig }, string.Format("Test mode config = {0:X2}", testModeConfig)), + }; + + if (ihead.ConfigStruct != null) ihead.OrigTestModeConfig = ihead.ConfigStruct.TestModeConfig; + + int successfyllyWrittenFlags = + 0; /// Ones in this word represent communication failures, LSB represents the first step + int mask = 1; + foreach (var wData in dataToBeWritten) + { + bool isSuccessfullyWritten = false; + if (0 == WriteRequestPort(ihead, wData.MessageID, wData.StructName, wData.Offset, wData.Data.Length, + wData.Data)) + { + isSuccessfullyWritten = true; + } + + if (!isSuccessfullyWritten) + { + /// Communication failed in this step + successfyllyWrittenFlags = (successfyllyWrittenFlags | mask); + } + + mask = (mask << 1); /// Adjust the mask for the next step + } + + if (successfyllyWrittenFlags == 0) + { + /// Success + if (ihead.ConfigStruct != null) + { + ihead.ConfigStruct.MeterState = MeterState.Active; + ihead.ConfigStruct.TestModeConfig = (byte)0xA0; + } + + resultStr = "OK"; + return CommErr.None; + } + else + { + return CommErr.Write; + } + } + + public void NormalClose(IList waterMeterPositions0) + { + + if (MultiTestParams != null && + MultiTestParams.Count > 0 && + !MultiTestParams[0] + .SimultWithNext) /// RFID Communication at the end of the cycle does not influence the result + { + UpdateRfidCommResult(tests, waterMeterPositions0); + } + + } + + private static readonly List SupportedReaders = new List() + { + typeof(IperlHead), + typeof(TestMethods.iPerlCommunication.iPerlHead.Factory) + }; + public bool IsFamilyOfSmartReader(ISmartReader smartHead) + { + return SupportedReaders.Any(type => + type.FullName == smartHead?.GetType().FullName); + } + + /// + /// Update test result representing RFID communication success/failure + /// + /// + void UpdateRfidCommResult(IList tests, IList waterMeterPositions0) + { + DateTime endTime = DateTime.Now; + int testTime = StateMachine.Time - StartTimeSec; + + if (!tests.Contains(StateMachine.TestInstances[0].Test)) + { + tests.Insert(0, StateMachine.TestInstances[0].Test); /// Add RFID test as the 1st item + } + + foreach (var test in tests) + { + Results.Entities.TestRslt tstRslt = ProcessData.BatchRslts.GetTestRslt(test.Name, test.Part); + + if (tstRslt != null) + { + Results.Utils.GetCounterStates(tstRslt, Program.LocalSettings.Counters); + + /// Auxiliary results ... not required + + /// Main results + tstRslt.MethodClass = TbfComponents.FindComponent(test.Method).ClassName; + tstRslt.TestDone = true; + tstRslt.StartTime = tstRslt.Batch.StartTime; + tstRslt.EndTime = endTime; + tstRslt.FlowSetTime = 0; + tstRslt.MassOfEvapWater = 0; + tstRslt.TestTime += testTime; /// [s] total communication time of all tests + + for (int i = 0; i < iperlHeads.Count; i++) + { + Results.Entities.MeterTestRslt meterRslt = + ProcessData.BatchRslts.GetMeterTestRslt(test.Name, waterMeterPositions0[i], + CompoundMeterId.Single); + + if (meterRslt != null && iperlHeads[i] != null) + { + meterRslt.WaterMeter.SerialNr = iperlHeads[i].SerialNr; + meterRslt.Passed = (!iperlHeads[i].CommFailed && !iperlHeads[i].Disabled); + meterRslt.TestDone = true; + } + } + } + } + + UiBridge.Bridge.OnTestCompleted(this, new UiBridge.TestCompletedEventArgs(string.Empty, null)); + } + + /// + /// End testing a sealed meter + /// + /// iPERL head object + /// Water meter object + /// String passed to caller + /// true on success + private CommErr EndTestingSealedMeter(SmartReader ihead, WaterMeter wm, ref string resultStr) + { + if (ihead.CommFailed) return CommErr.CommFailed; + + Byte testModeConfig = + (ihead.OrigTestModeConfig != 0) + ? ihead.OrigTestModeConfig + : (byte)0x80; /// Restore original value (default is 0x80) + + iPerlDataWrite[] dataToBeWritten = new iPerlDataWrite[] + { + new iPerlDataWrite(MessageID.Command, MCI_Protocol.StructName.Command, 0x0000, + new byte[] { (byte)Command.SetActiveMode }, "Set active mode"), + new iPerlDataWrite(MessageID.Configuration, MCI_Protocol.StructName.Configuration, 0x0015, + new byte[] { testModeConfig }, string.Format("Test mode config = {0:X2}", testModeConfig)), + new iPerlDataWrite(MessageID.RadioPassthrough, MCI_Protocol.StructName.RadioInfo, 0x1804, + new byte[] { (byte)0 }, "System Status"), + new iPerlDataWrite(MessageID.MetrologyMemory, MCI_Protocol.StructName.Calibration, + ihead.CommInterface == CommunicationInterface.NFC ? 33 : 0x19A1, new byte[] { (byte)0x5A }, + "Close Sealing") /// 0x5A=sealed, 0xA5=unsealed ?????? + }; + + int successfyllyWrittenFlags = + 0; /// Ones in this word represent communication failures, LSB represents the first step + int mask = 1; + foreach (var wData in dataToBeWritten) + { + bool isSuccessfullyWritten = false; + if (0 == WriteRequestPort(ihead, wData.MessageID, wData.StructName, wData.Offset, wData.Data.Length, + wData.Data)) + { + isSuccessfullyWritten = true; + } + + if (!isSuccessfullyWritten) + { + /// Communication failed in this step + successfyllyWrittenFlags = (successfyllyWrittenFlags | mask); + } + + mask = (mask << 1); /// Adjust the mask for the next step + } + + if (successfyllyWrittenFlags == 0) + { + /// Success + if (ihead.ConfigStruct != null) + { + ihead.ConfigStruct.MeterState = MeterState.Active; + ihead.ConfigStruct.TestModeConfig = (byte)0xA0; + } + + resultStr = "OK"; + return CommErr.None; + } + else + { + return CommErr.Write; + } + } + + private CommErr GetQ2PreCorrectionsFormRest(int threadId, SmartReader ihead, WaterMeter wm, + ref string resultStr) + { + if (!ProcessData.IsQ2PreCorrectionCalculated) + { + ProcessData.IsQ2PreCorrectionCalculated = true; + /*bool success = iPerlCommunicationSeq.GetQ2PreCorrectionsOrBackups(cfg, wm.WMTypeId(), + out ProcessData.CalculatedQ2PreCorrectionLR, + out ProcessData.CalculatedQ2PreCorrectionRL);*/ + } + + resultStr = string.Format("Q2 pre-corrections: LR={0} RL={1}", ProcessData.CalculatedQ2PreCorrectionLR, + ProcessData.CalculatedQ2PreCorrectionRL); + return CommErr.None; + } + + private CommErr SetIperlCommMilestoneReached(ISmartTestMethod testMethod, ConditionId id, ref string resultStr) + { + if (id < ConditionId.A || id >= ConditionId.Count) + { + resultStr = Strings.Error; + return CommErr.WrongArguments; + } + + testMethod.MeterCommMilestone((int)id, true); + resultStr = ISequenceConditionOp.GetConditionNameFmt(id); + return CommErr.None; + } + + private CommErr Simulate(int currentActivityStep, SmartReader ihead, WaterMeter wm, ref string resultStr) + { + string[] arguments = multiTestParams[currentActivityStep].Activity.Split(new char[] { ' ' }); + + if (arguments.Length < 2 || arguments[1].ToLower() != "iperls") return CommErr.None; + + if (ihead.CommFailed) return CommErr.CommFailed; + + string[] pcbNrs = new string[] + { + "831232435539", "831232435562", + "831232435587", "831232432141", + "831232432497", "831232763641" + }; + + if (wm.WMPosition > 0 && wm.WMPosition <= pcbNrs.Length) + { + ihead.SerialNr = wm.SerialNr = pcbNrs[wm.WMPosition - 1]; + } + + resultStr = string.Format("PCB Nr. = {0}", wm.SerialNr ?? ""); + return CommErr.None; + } + + + public void DoOnCommCompleted(object sender, CommCompletedEventArgs data, IList waterMeterPositions0) + { + try + { + /// + /// Update the text message + /// + if (data.WMNr0 >= 0) messages[data.WMNr0].Text = data.CommMessage; + + /// + /// Update head active/inactive switch + /// + if (data.WMNr0 >= 0 && data.CommErr == CommErr.HeadDisabledByUser) + { + /// iPerl head was disabled by the user + ckbState[data.WMNr0] = false; + checkBoxes[data.WMNr0].Checked = false; + checkBoxes[data.WMNr0].Enabled = false; + if (data.Ihead != null) data.Ihead.Disabled = true; + if (data.Wm != null) data.Wm.Disabled = true; + } + else if (data.WMNr0 >= 0 && data.CommErr == CommErr.None) + { + /// One RFID communication successful => iPerl cannot be disabled by the user anymore + ckbState[data.WMNr0] = true; + checkBoxes[data.WMNr0].Checked = true; + checkBoxes[data.WMNr0].Enabled = false; + } + + /// + /// Update opto-communication indication + /// + for (int i = 0; i < iperlHeads.Count; i++) + { + if (iperlHeads[i] == null || iperlHeads[i].Disabled) + { + counters[i].BackColor = iPerlCommunicationConstants.DisabledColor; + } + else + { + IperlHead iperlHead = (iperlHeads[i] as IperlHead); + + OptoHeadState checkFlowDirection = + ((iperlHead == null) ? OptoHeadState.Disabled : iperlHead.CheckFlowDirection()); + switch (checkFlowDirection) + { + case OptoHeadState.OptoAndDirOK: + counters[i].BackColor = iPerlCommunicationConstants.OptoAndDirOKColor; + break; + + case OptoHeadState.DirNok: + counters[i].BackColor = iPerlCommunicationConstants.DirNokColor; + break; + + default: + case OptoHeadState.OptoNok: + counters[i].BackColor = iPerlCommunicationConstants.OptoNokColor; + break; + } + } + } + +#if !TURA_SPECIAL + /// + /// Branch + /// + lock (this) + { + if (++completedCommCount < 4) return; + completedCommCount = 0; + } +#endif + + if (currentGroup < lastGroup) + { + /// Go to the next step / next group + currentGroup++; + } + else if (currentActivityStep + 1 < MultiTestParams.Count) + { + currentGroup = 0; + currentActivityStep++; + activityLabel.Text = MultiTestParams[currentActivityStep].Activity; + currentGroup++; + } + else + { + /// Wait until all threads are finished + workerThreads[data.ThreadId].Join(2000); + ParentFrom.NormalClose(); + } + } + catch (Exception e) + { + log.ErrorFormat("DoOnCommCompleted({0}) failed: {1}", data, e.Message); + log.FatalFormat("StackTrace : {0}{1}", Environment.NewLine, e.StackTrace); + } + } + + + } +} \ No newline at end of file diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj index 4eb0ad26c..ca5441a18 100644 --- a/TBF/TBF.csproj +++ b/TBF/TBF.csproj @@ -2316,6 +2316,7 @@ + diff --git a/TBF/UI/MainWnd.cs b/TBF/UI/MainWnd.cs index 8bb041a6f..bd027aaaf 100644 --- a/TBF/UI/MainWnd.cs +++ b/TBF/UI/MainWnd.cs @@ -1199,7 +1199,8 @@ namespace TBF.UI private void optoHeadsToolStripMenuItem_Click(object sender, EventArgs e) { - new iPerlCommunicationForm(true).ShowDialog(); + //new iPerlCommunicationForm(true).ShowDialog(); + new SmartCommunicationForm(true).ShowDialog(); } private void statusStrip1_DoubleClick(object sender, EventArgs e) diff --git a/TBF/UI/Process/ProcessTabPageCtrl.cs b/TBF/UI/Process/ProcessTabPageCtrl.cs index a7e609ea3..749d2124d 100644 --- a/TBF/UI/Process/ProcessTabPageCtrl.cs +++ b/TBF/UI/Process/ProcessTabPageCtrl.cs @@ -86,8 +86,38 @@ namespace TBF.UI.Process Bridge.StateMachineTickHandler += delegate(object sndr, UiBridge.StateMachineTickEventArgs args) { - if (InvokeRequired) { Invoke(new EventHandler(OnStateMachineTick), sndr, args); } - else OnStateMachineTick(sndr, args); + try + { + if (IsDisposed || Disposing || !IsHandleCreated) + { + return; + } + + if (InvokeRequired) + { + BeginInvoke(new EventHandler(OnStateMachineTick), sndr, args); + } + else + { + OnStateMachineTick(sndr, args); + } + } + catch (ObjectDisposedException e) + { + log.DebugFormat("ProcessTabPageCtrl.OnStateMachineTick() skipped because control is disposed: {0}", e.Message); + } + catch (InvalidOperationException e) + { + log.DebugFormat("ProcessTabPageCtrl.OnStateMachineTick() skipped because UI is not available: {0}", e.Message); + } + catch (System.ComponentModel.InvalidAsynchronousStateException e) + { + log.DebugFormat("ProcessTabPageCtrl.OnStateMachineTick() skipped because UI thread is no longer available: {0}", e.Message); + } + catch (Exception e) + { + log.ErrorFormat("ProcessTabPageCtrl.OnStateMachineTick() failed: {0}", e.Message); + } }; Bridge.TestCompletedHandler += delegate(object sender, TestCompletedEventArgs args)