/// /// Copyright (c) 2015 Sensus Metering Systems /// Author: Milan Hanajík /// using System; using System.Collections.Generic; using System.Drawing; using System.Globalization; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; using log4net; using TBF.Resources; using TBF.BenchControl.WaterMeters.iPerl; namespace TBF.BenchControl.TestMethods.iPerlCommunication { public partial class iPerlCommunicationForm : Form, GenericDevices.IHasCompleted { private static readonly ILog log = LogManager.GetLogger(typeof(iPerlCommunicationForm)); #region DLL_Interface [DllImport("libRfid.dll", EntryPoint = "getDllVersion")] public static extern unsafe int getDllVersion(); [DllImport("libRfid.dll", EntryPoint = "openPort")] static extern unsafe int openPort(int comPort); [DllImport("libRfid.dll", EntryPoint = "closePort")] static extern unsafe int closePort(); [DllImport("libRfid.dll", EntryPoint = "readRequestPort")] static extern unsafe int readRequestPort(MessageID messageId, int offset, int lenght, byte* ptr, int timeout_ms); [DllImport("libRfid.dll", EntryPoint = "writeRequestPort")] static extern unsafe int writeRequestPort(MessageID messageId, int offset, int lenght, byte* ptr, int timeout_ms); /// /// Wrapper function with safe interface adn unsafe body /// /// Value returned by readRequestPort(...) public static unsafe int ReadRequestPort(MessageID messageID, int offset, int lenght, out byte[] buffer, int timeout) { byte[] buf = new byte[1000]; int retv; fixed (byte* pBuf = buf) { retv = readRequestPort(messageID, offset, lenght, pBuf, timeout); buffer = new byte[lenght]; for (int i = 0; i < lenght; i++) buffer[i] = buf[i]; } return retv; } /// /// Wrapper function with safe interface adn unsafe body /// /// Value returned by writeRequestPort(...) public static unsafe int WriteRequestPort(MessageID messageID, int offset, int lenght, byte[] buffer, int timeout) { int retv; fixed (byte* pBuf = buffer) { retv = writeRequestPort(messageID, offset, lenght, pBuf, timeout); } return retv; } #endregion DLL_Interface const int CommTimeout = 500; const int MaxCommRetries = 3; const string ReadConfigurationStr = "Read configuration"; /// Example: "Read configuration" or "Read configuration if enabled" const string SetTestModeStr = "Set Test mode"; /// Example: "Set Test mode" or "Set Test mode A0" (hexadecimal number is the required 'testModeConfig' const string SetActiveModeStr = "Set Active mode"; const string ReadCalibrationStr = "Read calibration"; const string WriteCalibrationFactorStr = "Write calibration factor"; const string ResetQ2CorrectionStr = "Reset Q2 correction"; const string WriteQ2CorrectionStr = "Write Q2 correction"; // Set to 'true' when the form closes public bool Completed { get { return formCompleted; } } bool formCompleted; /// Number of text boxes for serial numbers public readonly int WaterMetersCount; readonly bool[] disabled; Entities.TestResult tr; int textBoxesCount; Label[] labels; TextBox[] messages; static IList waterMeters; Modbus.QuidoRS.QuidoRS quido; /// /// RFID multiplexer PCB / RFID serial port and worker thread related variables /// static string activity; static int currentGroup; /// form -> worker thread (0 = none) static int lastGroup; static int completedCommCount; /// Number of completed communication steps static IList workerThreads; static IList rfidPortNrs; static IList stopWorkerThreads; /// form -> worker thread /// Parameterless constructor for 3 watermeters public iPerlCommunicationForm() { InitializeComponent(); textBoxesCount = 48; labels = new Label[48] { wmLabel1, wmLabel2, wmLabel3, wmLabel4, wmLabel5, wmLabel6, wmLabel7, wmLabel8, wmLabel9, wmLabel10, wmLabel11, wmLabel12, wmLabel13, wmLabel14, wmLabel15, wmLabel16, wmLabel17, wmLabel18, wmLabel19, wmLabel20, wmLabel21, wmLabel22, wmLabel23, wmLabel24, wmLabel25, wmLabel26, wmLabel27, wmLabel28, wmLabel29, wmLabel30, wmLabel31, wmLabel32, wmLabel33, wmLabel34, wmLabel35, wmLabel36, wmLabel37, wmLabel38, wmLabel39, wmLabel40, wmLabel41, wmLabel42, wmLabel43, wmLabel44, wmLabel45, wmLabel46, wmLabel47, wmLabel48, }; messages = new TextBox[48] { wmTextBox1, wmTextBox2, wmTextBox3, wmTextBox4, wmTextBox5, wmTextBox6, wmTextBox7, wmTextBox8, wmTextBox9, wmTextBox10, wmTextBox11, wmTextBox12, wmTextBox13, wmTextBox14, wmTextBox15, wmTextBox16, wmTextBox17, wmTextBox18, wmTextBox19, wmTextBox20, wmTextBox21, wmTextBox22, wmTextBox23, wmTextBox24, wmTextBox25, wmTextBox26, wmTextBox27, wmTextBox28, wmTextBox29, wmTextBox30, wmTextBox31, wmTextBox32, wmTextBox33, wmTextBox34, wmTextBox35, wmTextBox36, wmTextBox37, wmTextBox38, wmTextBox39, wmTextBox40, wmTextBox41, wmTextBox42, wmTextBox43, wmTextBox44, wmTextBox45, wmTextBox46, wmTextBox47, wmTextBox48, }; currentGroup = 0; lastGroup = 0; /// group numbers are >=1, lastGroup == 0 means no group completedCommCount = 0; workerThreads = new List(); rfidPortNrs = new List(); stopWorkerThreads = new List(); formCompleted = false; tr = null; /// Find QuidoRS foreach (var comp in StateMachine.Components) { if (comp is Modbus.QuidoRS.QuidoRS) { quido = comp as Modbus.QuidoRS.QuidoRS; break; } } /// Attach to 'CommCompleted' handler CommCompletedHandler += delegate(object sender, CommCompletedEventArgs args) { if (InvokeRequired) { Invoke(new EventHandler(DoOnCommCompleted), sender, args); } else DoOnCommCompleted(sender, args); }; /// Attach to 'AllCompleted' handler AllCompletedHandler += delegate(object sender, AllCompletedEventArgs args) { if (InvokeRequired) { Invoke(new EventHandler(DoOnAllCompleted), sender, args); } else DoOnAllCompleted(sender, args); }; } /// /// Constructor /// /// Number of text boxes for serial numbers public iPerlCommunicationForm(IList waterMeters, string activity) : this() { iPerlCommunicationForm.activity = activity; this.WaterMetersCount = waterMeters.Count; iPerlCommunicationForm.waterMeters = new List(); #if DEFINE foreach (var wm in waterMeters) { WaterMeters.iPerl.WaterMeter iPerlWM = wm as WaterMeters.iPerl.WaterMeter; iPerlCommunicationForm.waterMeters.Add(iPerlWM); if (iPerlWM.Group > lastGroup) lastGroup = iPerlWM.Group; if (!rfidPortNrs.Contains(iPerlWM.RfidComPortNr)) { int threadId = workerThreads.Count; Thread thread = new Thread(iPerlCommunicationForm.Worker); workerThreads.Add(thread); rfidPortNrs.Add(iPerlWM.RfidComPortNr); stopWorkerThreads.Add(false); thread.Start(new Boxes.IntBox(threadId)); } } #else foreach (var wm in waterMeters) { WaterMeters.iPerl.WaterMeter iPerlWM = wm as WaterMeters.iPerl.WaterMeter; iPerlCommunicationForm.waterMeters.Add(iPerlWM); if (!rfidPortNrs.Contains(iPerlWM.RfidComPortNr)) rfidPortNrs.Add(iPerlWM.RfidComPortNr); if (iPerlWM.Group > lastGroup) lastGroup = iPerlWM.Group; } /// Only one thread { int threadId = workerThreads.Count; Thread thread = new Thread(iPerlCommunicationForm.Worker); workerThreads.Add(thread); stopWorkerThreads.Add(false); thread.Start(new Boxes.IntBox(threadId)); } #endif activityLabel.Text = activity; ShuffleTextBoxes(this.WaterMetersCount, Program.LineSize); disabled = new bool[this.WaterMetersCount]; } /// /// Make sure the layout of labels/text boxes on the screen /// corresponds to the layout of watermeters of the test bench. /// /// Number of watermeters /// Number of watermeters in one line void ShuffleTextBoxes(int wmsCount, int lineSize) { if (wmsCount < textBoxesCount && lineSize > 0) { int nrLines = (wmsCount + lineSize - 1) / lineSize; int gap = (textBoxesCount - wmsCount) / nrLines; int dest = 0; for (int l = 0; l < nrLines; l++) { for (int i = 0; i < lineSize; i++) { labels[dest] = labels[l * lineSize + l * gap + i]; messages[dest] = messages[l * lineSize + l * gap + i]; dest++; } } textBoxesCount = wmsCount; } } void Localize() { Text = Strings.Water_Meter_States; for (int i = 0; i < textBoxesCount; i++) { labels[i].Text = string.Format("iPerl{0}", i + 1); } } private void iPerlCommunicationForm_Load(object sender, EventArgs e) { Localize(); //Height = 50 + 90 * WaterMetersCount; for (int i = 0; i < textBoxesCount; i++) { if (disabled != null && i < disabled.Length && disabled[i]) { labels[i].Visible = messages[i].Visible = false; } else { labels[i].Visible = messages[i].Visible = true; messages[i].Text = "---"; } } /// /// Set QuidoRS outputs and start the whole communication process /// by incrementing 'currentGroup'. /// if (quido != null) { quido.SetOutputs((ushort)(16 - currentGroup - 1)); Thread.Sleep(200); } currentGroup++; } private void NormalClose() { CommCompletedHandler = null; AllCompletedHandler = null; formCompleted = true; DialogResult = DialogResult.OK; Close(); } void OnAdjustmentInProgress(object sender, UiBridge.AdjustmentInProgressEventArgs args) { tr = args.TestResult; Redraw(); } void Redraw() { if (tr != null) { for (int i = 0; i < textBoxesCount; i++) { messages[i].Text = tr.Meters[i].VolumeErrorPct.ToString("F1"); } } } private void WMErrorsForm_Paint(object sender, PaintEventArgs e) { //if (tr != null) //{ // System.Drawing.Graphics graphics = this.CreateGraphics(); // for (int i = 0; i < WaterMetersCount; i++) // { // PaintOne(graphics, rects[i], tr.Meters[i].VolumeErrorPct, tr.ErrLimLo, tr.ErrLimHi); // } //} } #region Forced close handling public void StartForceCloseHandler() { UiBridge.Bridge.CloseModelessFormHandler += delegate(object sender, EventArgs args) { if (InvokeRequired) { Invoke(new EventHandler(OnForceClose), sender, args); } else OnForceClose(sender, args); }; } void OnForceClose(object sender, EventArgs args) { CommCompletedHandler = null; AllCompletedHandler = null; DialogResult = DialogResult.Cancel; Close(); } #endregion /// /// Worker thread /// /// Thread ID (integer) wrapped into IntBox static void Worker(object threadData) { int threadId = (threadData as Boxes.IntBox).Val; for (int group = 1; group <= lastGroup; group++) { while (currentGroup != group && !stopWorkerThreads[threadId]) Thread.Sleep(50); if (stopWorkerThreads[threadId]) break; for (int rid = 0; rid < rfidPortNrs.Count; rid++) { int rfidPortNr = rfidPortNrs[rid]; int wmNr = 0; bool wmFound = false; foreach (var wm in waterMeters) { if ((wm.RfidComPortNr == rfidPortNr) && (wm.Group == group)) { wmFound = true; if (wm.DebugLevel == Entities.DebugMode.Normal) { bool success; string resultStr = string.Empty; if (activity.ToLower().Contains(ReadConfigurationStr.ToLower())) success = ReadConfiguration(wm, ref resultStr); else if (activity.ToLower().Contains(SetTestModeStr.ToLower())) success = SetTestMode(wm, ref resultStr); else if (activity.ToLower().Equals(SetActiveModeStr.ToLower())) success = SetActiveMode(wm, ref resultStr); else if (activity.ToLower().Equals(ReadCalibrationStr.ToLower())) success = ReadCalibration(wm, ref resultStr); else if (activity.ToLower().Equals(WriteCalibrationFactorStr.ToLower())) success = WriteCalibrationFactor(wm, ref resultStr); else if (activity.ToLower().Equals(ResetQ2CorrectionStr.ToLower())) success = ResetQ2Correction(wm, ref resultStr); else if (activity.ToLower().Equals(WriteQ2CorrectionStr.ToLower())) success = WriteQ2Correction(wm, ref resultStr); else { success = true; resultStr = "Invalid activity"; } if (success) OnCommCompleted(null, new CommCompletedEventArgs(threadId, wmNr, resultStr)); else if (wm.Disabled) OnCommCompleted(null, new CommCompletedEventArgs(threadId, wmNr, "Watermeter is disabled")); else { OnCommCompleted(null, new CommCompletedEventArgs(threadId, wmNr, "Cannot " + activity)); wm.Disabled = true; } } else { OnCommCompleted(null, new CommCompletedEventArgs(threadId, wmNr, "Simulation")); } break; } wmNr++; } if (!wmFound) OnCommCompleted(null, new CommCompletedEventArgs(threadId, -1, string.Empty)); /// Send negative wmNr } } } /// /// Read a complete configuration structure of the watermeter /// /// Water meter object /// String passed to caller /// true on success static bool ReadConfiguration(WaterMeters.iPerl.WaterMeter wm, ref string resultStr) { /// /// The activity is "Read configuration" (this enables the watermeter, resets error flag) /// or "Read configuration if enabled" (this keeps th error flag). /// if (!activity.ToLower().Contains(" if enabled")) { wm.Disabled = false; } if (wm.Disabled) return false; if (openPort(wm.RfidComPortNr) != 0) return false; /// Open RFID port bool success = false; /// Read configuration byte[] config = null; for (int j = 0; j < MaxCommRetries; j++) { if (0 == ReadRequestPort(MessageID.Configuration, 0, ConfigStruct.Length, out config, CommTimeout)) { success = true; wm.ConfigStruct = ConfigStruct.FromByteArray(config); resultStr = wm.ConfigStruct.ToString(1); break; } } closePort(); /// Close RFID port return success; } /// /// 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. /// /// Water meter object /// String passed to caller /// true on success static bool SetTestMode(WaterMeters.iPerl.WaterMeter wm, ref string resultStr) { if (wm.Disabled) return false; Byte testModeConfig = 0xA0; /// Default value /// if (activity.Length > SetTestModeStr.Length) { string testModeConfigStr = activity.Substring(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 (wm.ConfigStruct.MeterState == MeterState.Test && wm.ConfigStruct.TestModeConfig == testModeConfig) { /// Already in the correct test mode return true; } /// Communication necessary if (openPort(wm.RfidComPortNr) != 0) return false; /// Open RFID port bool success = true; if (success && (wm.ConfigStruct.TestModeConfig != testModeConfig) && (wm.ConfigStruct.MeterState != MeterState.Active)) { /// Switch to Active mode in order to change TestModeConfig success = false; byte[] cmd = new byte[1] { (byte)6 }; for (int j = 0; j < MaxCommRetries; j++) { if (0 == WriteRequestPort(MessageID.Command, 0, 1, cmd, CommTimeout)) { success = true; break; } } } if (success && (wm.ConfigStruct.TestModeConfig != testModeConfig)) { /// Change the TestModeConfig if necessary success = false; byte[] tstMdCfg = new byte[1] { testModeConfig }; for (int j = 0; j < MaxCommRetries; j++) { if (0 == WriteRequestPort(MessageID.Configuration, 21, 1, tstMdCfg, CommTimeout)) { wm.ConfigStruct.Update(21, tstMdCfg); success = true; break; } } } if (success) { /// Switch to test mode success = false; byte[] cmd = new byte[1] { (byte)7 }; for (int j = 0; j < MaxCommRetries; j++) { if (0 == WriteRequestPort(MessageID.Command, 0, 1, cmd, CommTimeout)) { success = true; break; } } } /// Now the meter should be in the Test mode ... verify if (success) { /// Verify the configuration success = false; byte[] cfg_0_3 = null; for (int j = 0; j < MaxCommRetries; j++) { if (0 == ReadRequestPort(MessageID.Configuration, 0, 4, out cfg_0_3, CommTimeout)) { wm.ConfigStruct.Update(0, cfg_0_3); success = (wm.ConfigStruct.MeterState == MeterState.Test); if (success) resultStr = wm.ConfigStruct.ToString(1); break; } } } closePort(); /// Close RFID port return success; } /// /// Set the watermeter to the active mode. /// Read a part of configuration afterwards to verify the mode was set correctly. /// /// Water meter object /// String passed to caller /// true on success static bool SetActiveMode(WaterMeters.iPerl.WaterMeter wm, ref string resultStr) { if (wm.Disabled) return false; if (openPort(wm.RfidComPortNr) != 0) return false; /// Open RFID port bool success = false; /// Switch to active mode byte[] cmd = new byte[1] { (byte)6 }; for (int j = 0; j < MaxCommRetries; j++) { if (0 == WriteRequestPort(MessageID.Command, 0, 1, cmd, CommTimeout)) { success = true; break; } } if (success) { success = false; /// Read configuration byte[] cfg_0_3 = null; for (int j = 0; j < MaxCommRetries; j++) { if (0 == ReadRequestPort(MessageID.Configuration, 0, 4, out cfg_0_3, CommTimeout)) { wm.ConfigStruct.Update(0, cfg_0_3); success = (wm.ConfigStruct.MeterState == MeterState.Active); if (success) resultStr = wm.ConfigStruct.ToString(1); break; } } } closePort(); /// Close RFID port return success; } /// /// Read a complete calibration structure from the watermeter /// /// Water meter object /// String passed to caller /// true on success static bool ReadCalibration(WaterMeters.iPerl.WaterMeter wm, ref string resultStr) { if (wm.Disabled) return false; if (openPort(wm.RfidComPortNr) != 0) return false; /// Open RFID port bool success = false; /// Read calibration byte[] calib = null; for (int j = 0; j < MaxCommRetries; j++) { if (0 == ReadRequestPort(MessageID.Calibration, 0, CalibrationStruct.Length, out calib, CommTimeout)) { success = true; wm.CalibrationStruct = CalibrationStruct.FromByteArray(calib); resultStr = wm.CalibrationStruct.ToString(); break; } } closePort(); /// Close RFID port return success; } /// /// Write the calculated calibration factor to the water meter. /// Read a part of CalibrationStruct afterwards to verify factor was written correctly. /// /// Water meter object /// String passed to caller /// true on success static bool WriteCalibrationFactor(WaterMeters.iPerl.WaterMeter wm, ref string resultStr) { if (wm.Disabled) return false; if (openPort(wm.RfidComPortNr) != 0) return false; /// Open RFID port bool success = false; UInt16 factor = wm.CalibrationFactor; /// Original calibration factor /// Write the calculated calibration factor UInt16 newCalFactor = wm.CalculatedCalibrationFactor; byte[] data = new byte[2] { (byte)(newCalFactor & 0x00FF), (byte)((newCalFactor >> 8) & 0x00FF) }; for (int j = 0; j < MaxCommRetries; j++) { if (0 == WriteRequestPort(MessageID.Calibration, 2, 2, data, CommTimeout)) { success = true; break; } } if (success) { success = false; /// Read calibration byte[] calib_2_3 = null; for (int j = 0; j < MaxCommRetries; j++) { if (0 == ReadRequestPort(MessageID.Calibration, 2, 2, out calib_2_3, CommTimeout)) { success = true; wm.CalibrationStruct.Update(calib_2_3, 2); resultStr = wm.CalibrationStruct.ToString(); break; } } } closePort(); /// Close RFID port return success; } const int Q2CorrFactorsAddr = 0x1878; /// Used by ResetQ2Correction(...) and WriteQ2Correction(...) /// /// 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 static bool ResetQ2Correction(WaterMeters.iPerl.WaterMeter wm, ref string resultStr) { if (wm.Disabled) return false; if (openPort(wm.RfidComPortNr) != 0) return false; /// Open RFID port bool success = false; /// Write zero Q2 correction Byte q2CorrRFlow = 0; Byte q2CorrLFlow = 0; byte[] wrData = new byte[2] { q2CorrRFlow, q2CorrLFlow }; for (int j = 0; j < MaxCommRetries; j++) { if (0 == WriteRequestPort(MessageID.MetrologyMemory, Q2CorrFactorsAddr, 2, wrData, CommTimeout)) { success = true; break; } } if (success) { success = false; /// Read calibration byte[] rdData = null; for (int j = 0; j < MaxCommRetries; j++) { if ((0 == ReadRequestPort(MessageID.MetrologyMemory, Q2CorrFactorsAddr, 2, out rdData, CommTimeout)) && (rdData != null) && (rdData.Length == 2) && (rdData[0] == 0) && (rdData[1] == 0)) { success = true; resultStr = "Q2 correction factors reset to 0"; break; } } } closePort(); /// Close RFID port return success; } /// /// Write the calculated Q2 correction factors to the memory. /// Read them back to verify factors were written correctly. /// /// Water meter object /// String passed to caller /// true on success static bool WriteQ2Correction(WaterMeters.iPerl.WaterMeter wm, ref string resultStr) { if (wm.Disabled) return false; if (openPort(wm.RfidComPortNr) != 0) return false; /// Open RFID port bool success = false; /// Write Q2 corrections Byte q2CorrRFlow = 0; Byte q2CorrLFlow = 0; byte[] wrData = new byte[2] { q2CorrRFlow, q2CorrLFlow }; for (int j = 0; j < MaxCommRetries; j++) { if (0 == WriteRequestPort(MessageID.MetrologyMemory, Q2CorrFactorsAddr, 2, wrData, CommTimeout)) { success = true; break; } } if (success) { success = false; /// Read calibration byte[] rdData = null; for (int j = 0; j < MaxCommRetries; j++) { if ((0 == ReadRequestPort(MessageID.MetrologyMemory, Q2CorrFactorsAddr, 2, out rdData, CommTimeout)) && (rdData != null) && (rdData.Length == 2) && (rdData[0] == q2CorrRFlow) && (rdData[1] == q2CorrRFlow)) { success = true; resultStr = string.Format("Q2 correction factors set: RightFlow = {0}, LeftFlow = {1}", q2CorrRFlow, q2CorrRFlow); break; } } } return success; } /// /// Called when communication with one watermeter is completed /// public static void OnCommCompleted(object sender, CommCompletedEventArgs data) { if (CommCompletedHandler == null) return; try { CommCompletedHandler(sender, data); } catch (Exception e) { log.Error("CommCompletedHandler(...) failed", e); } } public static event EventHandler CommCompletedHandler; void DoOnCommCompleted(object sender, CommCompletedEventArgs data) { if (data.WMNr >= 0) messages[data.WMNr].Text = data.CommMessage; lock (this) { if (++completedCommCount < rfidPortNrs.Count) return; completedCommCount = 0; } if (currentGroup < lastGroup) { /// Go to the next step / next group if (quido != null) { quido.SetOutputs((ushort)(16 - currentGroup - 1)); Thread.Sleep(100); } currentGroup++; } else { /// Wait until all threads are finished workerThreads[data.ThreadId].Join(2000); NormalClose(); } } /// /// Called when communication with all watermeters is completed /// public static void OnAllCompleted(object sender, AllCompletedEventArgs data) { if (AllCompletedHandler == null) return; try { AllCompletedHandler(sender, data); } catch (Exception e) { log.Error("AllCompletedHandler(...) failed", e); } } public static event EventHandler AllCompletedHandler; void DoOnAllCompleted(object sender, AllCompletedEventArgs data) { Text = data.CommMessage; } } }