/// /// Copyright (c) 2020 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.IO.Ports; using System.Text; using System.Windows.Forms; using log4net; using Common; using SharedDatabase.Entities; using System.Globalization; using Workplace.Resources; namespace Workplace.CheckItems { public partial class Scale : UserControl, IDevice, ICheckItem { private static readonly ILog log = LogManager.GetLogger(typeof(Scale)); const string MassFormat = "{0:F1} g"; public override string ToString() { return string.Format("Scale: {0} {1}", IxPolozky, Part.Name); } public string ClassName { get { return "Scale"; } } public string ParentName { get { return string.Empty; } } public DebugMode DebugLevel { get { return DebugMode.Normal; } } public LogLevel LogLevel { get { return LogLevel.Warn; } } WorkplaceDlg parent; public Part Part { get { return part; } } Part part; double minWeight = 0; public double MinWeight { get { return minWeight; } set { minWeight = value; rangeLoLabel.Text = string.Format("{0}: {1}", Strings.Min_weight, string.Format(MassFormat, minWeight * 1000)); } } double maxWeight = 10.0; public double MaxWeight { get { return maxWeight; } set { maxWeight = value; rangeHiLabel.Text = string.Format("{0}: {1}", Strings.Max_weight, string.Format(MassFormat, maxWeight * 1000)); } } bool initialized; public int IxPolozky { get { return ixPolozky; } set { ixPolozky = value; } } int ixPolozky; public IList BlackListedCodes { get { return blackList; } set { blackList = value; } } IList blackList; public string ScannedCode { get { return string.Format(MassFormat, preciseMass * 1000); ; } set { } } Scale() { InitializeComponent(); blackList = new List(); } /// Empty implementation, might be overriden in derived classes public virtual void StartChangeHandler() { } /// Empty implementation, might be overriden in derived classes public virtual void StopChangeHandler() { } public Scale(WorkplaceDlg parent, SharedDatabase.Entities.Part part) : this() { this.parent = parent; this.part = part; kodMaterialuLabel.Text = part.Name; descriptionMaterialuLabel.Text = part.Description; RunWizard(); } void Localize() { rangeLoLabel.Text = Strings.Min_weight; rangeHiLabel.Text = Strings.Max_weight; massLabel.Text = Strings.Weight; scaleSerialNrLabel.Text = Strings.Scale_sn; } void RunWizard() { rangeLoLabel.Text = string.Format("{0}: {1}", Strings.Min_weight, string.Format(MassFormat, minWeight * 1000)); rangeHiLabel.Text = string.Format("{0}: {1}", Strings.Max_weight, string.Format(MassFormat, maxWeight * 1000)); } public void ClearCode() { } public void SubmitCode(string code) { //CommandQueue.Enqueue(Activity.PreciseMeasurement); //CommandQueue.Enqueue(Activity.Evaluate); } public void ResetFocus() { setFocusButton.Text = ""; } public void SetFocus() { setFocusButton.Text = ">"; CommandQueue.Enqueue(Activity.PreciseMeasurement); CommandQueue.Enqueue(Activity.Evaluate); } private void setFocusButton_Click(object sender, EventArgs e) { setFocusButton.Text = ">"; CommandQueue.Enqueue(Activity.PreciseMeasurement); CommandQueue.Enqueue(Activity.Evaluate); parent.OnFocusPressed(this, new FocusPressedEventArgs(IxPolozky, Part.CodeLocation)); } void DisplayMass(double m) { massTextBox.Text = string.Format(MassFormat, m * 1000); } void DisplaySerialNr(string sn) { scaleSerialNrLabel.Text = string.Format("{0}: {1}", Strings.Scale_sn, sn); } void DisplayError() { massTextBox.Text = "Error"; } #region Scale control public enum Activity { Idle, ImmediateMeasurement, Zero, Tara, SerialNrRead, PreciseMeasurement, Evaluate, } public Queue CommandQueue; enum MsrmntState { Valid, Overload, Failed, Busy, Busy1, /// Busy2, /// Used by Mettler-Toledo Multi to distinguish which scale was queried Busy3, /// } SerialPort serialPort; /// serial port where the scale is connected to StringBuilder stringBuilder; /// string builder for received characters string scaleSerialNumber; Activity activity; /// scale activity bool activityStarted; /// command for selected activity was sent MsrmntState msrmntState; /// state/result of the mass measurement int msrmntTime; /// time expired since the measurement start DateTime msrmntTimeStamp; /// time stamp of the mass measurement (of the last received \r\n characters) double mass; /// measured mass in kg const double Capacity = 10.0; /// max. measured mass in kg /// Precise mass measurement specific double[] massReadings; double[] sortedMassReadings; MassMethod method; int totalReadingsCount; double massSpread; int currentReadingsCount; int timeout; double preciseMass; public void Initialize() { activity = Activity.Idle; activityStarted = false; msrmntState = MsrmntState.Failed; /// Data not valid yet msrmntTime = 0; /// Precise mass measurement specific method = Program.LocalSettings.MassMethod; totalReadingsCount = Math.Max(Program.LocalSettings.MassRepeats, 5); massSpread = Program.LocalSettings.MassSpread; massReadings = new double[totalReadingsCount]; sortedMassReadings = new double[totalReadingsCount]; currentReadingsCount = 0; timeout = (method == MassMethod.Scale) ? 60 : 2; if (Program.LocalSettings.ScaleSerialPort != 0) { stringBuilder = new StringBuilder(40); string comPortName = string.Format("COM{0}", Program.LocalSettings.ScaleSerialPort); serialPort = new SerialPort(comPortName, 2400, Parity.Even, 7, StopBits.One); serialPort.Handshake = Handshake.None; serialPort.Open(); log.FatalFormat("Scale on COM{0} successfully initialized", Program.LocalSettings.ScaleSerialPort); } else { serialPort = null; log.FatalFormat("Scale is disabledd"); } CommandQueue = new Queue(); CommandQueue.Enqueue(Activity.SerialNrRead); CommandQueue.Enqueue(Activity.Zero); } public void RunDeviceBefore() { if (!initialized || serialPort == null) return; if (msrmntState != MsrmntState.Busy) { log.Debug("Scale.RunDeviceBefore() ... msrmntState != MsrmntState.Busy"); return; /// No measurement in progress } log.Debug("Scale.RunDeviceBefore()"); if (activity == Activity.ImmediateMeasurement) { activity = Activity.Idle; } string justReceived = serialPort.ReadExisting(); if (justReceived != null && justReceived.Length > 0) { stringBuilder.Append(justReceived); if (stringBuilder.ToString().EndsWith("\r\n")) { msrmntTimeStamp = DateTime.Now; string received = stringBuilder.ToString(); /// Eliminate double spaces in the received string while (true) { string tmp = received.Replace(" ", " "); if (tmp.Length == received.Length) break; received = tmp; } string[] fields = received.Substring(0, received.Length - 2).Split(new char[] { ' ' }); if (fields.Length < 2) { msrmntState = MsrmntState.Failed; DisplayError(); log.WarnFormat("{0} - Balance response = {1}, Unexpected error !!!", Name, received.Replace("\r", "").Replace("\n", "")); return; } if (fields[0].Equals("I4")) /// Serial number { if (fields.Length == 3 && fields[1].Equals("A") && fields[2].Length >= 3) { scaleSerialNumber = fields[2].Substring(1, fields[2].Length - 2); DisplaySerialNr(scaleSerialNumber); msrmntState = MsrmntState.Valid; log.WarnFormat("{0} - Balance response = {1}, s/n = {2}", Name, received.Replace("\r", "").Replace("\n", ""), scaleSerialNumber); } else { msrmntState = MsrmntState.Failed; DisplayError(); log.WarnFormat("{0} - Balance response = {1}, error !!!", Name, received.Replace("\r", "").Replace("\n", "")); } } else if (fields[0].Equals("Z")) /// Zero when stable { if (fields.Length == 2 && fields[1].Equals("A")) { mass = 0; msrmntState = MsrmntState.Valid; DisplayMass(mass); log.WarnFormat("{0} - Balance response = {1}", Name, received.Replace("\r", "").Replace("\n", "")); } else { msrmntState = MsrmntState.Failed; DisplayError(); log.WarnFormat("{0} - Balance response = \"{1}\", error !!!", Name, received.Replace("\r", "").Replace("\n", "")); } } else if (fields[0].Equals("S")) /// Stable (field[1] == "S") or dynamic (field[1] == "D") mass measurement { if (fields.Length == 4 && (fields[1].Equals("S") || fields[1].Equals("D")) && double.TryParse(fields[2], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowLeadingWhite, CultureInfo.InvariantCulture, out mass)) { if (fields[3].Equals("g")) mass /= 1000.0f; if (fields[3].Equals("t")) mass *= 1000.0f; msrmntState = MsrmntState.Valid; DisplayMass(mass); log.InfoFormat("{0} - Balance response = {1}, Mass = {2}", Name, received.Replace("\r", "").Replace("\n", ""), mass); } else if (fields.Length == 2 && fields[1].Equals("-")) { mass = 0; msrmntState = MsrmntState.Failed; DisplayMass(mass); log.InfoFormat("{0} - Balance response = {1}, Mass = {2}", Name, received.Replace("\r", "").Replace("\n", ""), mass); } else if (fields.Length == 2 && fields[1].Equals("+")) { mass = Capacity; msrmntState = MsrmntState.Overload; DisplayMass(mass); log.InfoFormat("{0} - Balance response = {1}, Overload", Name, received.Replace("\r", "").Replace("\n", "")); } else if (fields.Length >= 2 && fields[1].Equals("I")) { mass = Capacity; msrmntState = MsrmntState.Overload; DisplayMass(mass); log.InfoFormat("{0} - Balance response = {1}, Overload", Name, received.Replace("\r", "").Replace("\n", ""), mass); } else { msrmntState = MsrmntState.Failed; DisplayError(); log.WarnFormat("{0} - Balance response = \"{1}\", Overload or and unexpected error !!!", Name, received.Replace("\r", "").Replace("\n", "")); } } else if (fields[0].Equals("T")) /// Taring { if (fields.Length == 4 && fields[1].Equals("S") && double.TryParse(fields[2], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowLeadingWhite, CultureInfo.InvariantCulture, out mass)) { if (fields[3].Equals("g")) mass /= 1000.0f; if (fields[3].Equals("t")) mass *= 1000.0f; msrmntState = MsrmntState.Valid; DisplayMass(mass); log.WarnFormat("{0} - Balance response = {1}, Mass = {2}", Name, received.Replace("\r", "").Replace("\n", ""), mass); } else if (fields.Length >= 2 && fields[1].Equals("I")) { mass = Capacity; msrmntState = MsrmntState.Overload; DisplayMass(mass); log.WarnFormat("{0} - Balance response = {1}, Overload", Name, received.Replace("\r", "").Replace("\n", ""), mass); } else { msrmntState = MsrmntState.Failed; DisplayError(); log.WarnFormat("{0} - Balance response = \"{1}\", Overload or and unexpected error !!!", Name, received.Replace("\r", "").Replace("\n", "")); } } else if (fields[0].Equals("U")) { if (fields.Length == 2 && fields[1].Equals("A")) { msrmntState = MsrmntState.Valid; log.InfoFormat("{0} - Balance response = \"{1}\"", Name, received.Replace("\r", "").Replace("\n", "")); } else { msrmntState = MsrmntState.Failed; DisplayError(); log.WarnFormat("{0} - Balance response = \"{1}\", Unexpected error !!!", Name, received.Replace("\r", "").Replace("\n", "")); } } else if (fields[0].Equals("SI")) /// "SI I" received in case of overload -> empty the tank { if (fields.Length >= 2 && fields[1].Equals("I")) { mass = Capacity; msrmntState = MsrmntState.Valid; DisplayMass(mass); log.WarnFormat("{0} - Balance response = \"{1}\", Overload.", Name, received.Replace("\r", "").Replace("\n", "")); } } } } } public void Run() { if (!initialized || serialPort == null) return; log.Debug("Scale.Run()"); if (activity == Activity.Idle && CommandQueue != null && CommandQueue.Count > 0) { activity = CommandQueue.Dequeue(); activityStarted = false; } switch (activity) { case Activity.Zero: case Activity.Tara: case Activity.SerialNrRead: if (RunZeroTaraSNRead()) { activity = Activity.Idle; } break; case Activity.PreciseMeasurement: if (RunPreciseMeasurement()) { activity = Activity.Idle; } break; case Activity.Evaluate: activity = Activity.Idle; if (minWeight <= preciseMass && preciseMass <= maxWeight) { /// Mass is OK parent.OnBarcodeReceived(this, new BarcodeReceivedEventArgs(string.Format(MassFormat, preciseMass * 1000), IxPolozky, Part.CodeLocation)); } else { /// Mass is out of range parent.OnBarcodeReceived(this, new BarcodeReceivedEventArgs(string.Format(MassFormat, preciseMass * 1000), IxPolozky, Part.CodeLocation, PartError.ValueOutOfRange)); } break; default: activity = Activity.Idle; break; } } public void RunDeviceAfter() { if (!initialized || serialPort == null) return; if (activity == Activity.Idle) { log.Debug("Scale.RunDeviceAfter() ... SendImmediateMeasurementCmd()"); SendImmediateMeasurementCmd(); activity = Activity.ImmediateMeasurement; } else { log.Debug("Scale.RunDeviceAfter()"); } } /// Stop this device public void StopDevice() { if (initialized && serialPort != null) { serialPort.Close(); serialPort = null; } initialized = false; } /// /// Zero the scale. /// /// true = operation completed public bool RunZeroTaraSNRead() { if (!activityStarted) { switch (activity) { case Activity.Zero: SendZeroWhenStableCmd(); break; case Activity.Tara: SendTaraCmd(); break; case Activity.SerialNrRead: SendGetSerialNumberCmd(); break; } activityStarted = true; return false; } if (msrmntState == MsrmntState.Busy) return false; return true; } /// /// Perform precise measurement. /// /// true = operation completed public bool RunPreciseMeasurement() { if (!activityStarted) { msrmntTime = 0; currentReadingsCount = 0; activityStarted = true; StartOneMeasurement(); return false; } msrmntTime++; if (msrmntState == MsrmntState.Busy) { /// Measurement is in progress - resend command if timeout occurs if (msrmntTime >= timeout) StartOneMeasurement(); return false; /// Wait for a mass measurement } if (msrmntState == MsrmntState.Valid) { /// Save the measurement if (currentReadingsCount < totalReadingsCount) { massReadings[currentReadingsCount++] = mass; } else { /// Shift data in the (already full) buffer, save the mass into the last buffer item for (int i = 1; i < totalReadingsCount; i++) { massReadings[i - 1] = massReadings[i]; } massReadings[totalReadingsCount - 1] = mass; } } if (currentReadingsCount >= totalReadingsCount) { Array.Copy(massReadings, sortedMassReadings, totalReadingsCount); Array.Sort(sortedMassReadings); if (method == MassMethod.Spread) { /// Immediate mass measurement, end condition calculated below if ((sortedMassReadings[totalReadingsCount - 2] - sortedMassReadings[1]) <= massSpread) { double massSum = 0; for (int i = 1; i < totalReadingsCount - 1; i++) massSum += sortedMassReadings[i]; preciseMass = (massSum / (totalReadingsCount - 2)); DisplayMass(preciseMass); //log.InfoFormat("ReadStableMassOp.Run() ... valid mass={0} ... completed", preciseMass); return true; /// Mass has just been calculated } } else if (method == MassMethod.StdDev) { /// Immediate mass measurement, end condition calculated below double massSum = 0; for (int i = 1; i < totalReadingsCount - 1; i++) massSum += sortedMassReadings[i]; double ave = (massSum / (totalReadingsCount - 2)); double sdev = 0; for (int i = 1; i < totalReadingsCount - 1; i++) sdev += ((sortedMassReadings[i] - ave) * (sortedMassReadings[i] - ave)); if (Math.Sqrt(sdev / (totalReadingsCount - 2)) <= massSpread) { preciseMass = ave; DisplayMass(preciseMass); //log.InfoFormat("ReadStableMassOp.Run() ... valid mass={0} ... completed", preciseMass); return true; /// Mass has just been calculated } } else { /// Stable mass measurement /// Sort, exclude min. and max. values, calculate the average double massSum = 0; for (int i = 1; i < totalReadingsCount - 1; i++) massSum += massReadings[i]; preciseMass = (massSum / (totalReadingsCount - 2)); DisplayMass(preciseMass); //log.InfoFormat("ReadStableMassOp.Run() ... valid mass={0} ... completed", preciseMass); return true; /// Mass has just been calculated } } StartOneMeasurement(); /// Start a new measurement return false; } void StartOneMeasurement() { if (method == MassMethod.Scale) SendStableMeasurementCmd(); else SendImmediateMeasurementCmd(); } public void SendZeroWhenStableCmd() { if (serialPort == null || msrmntState == MsrmntState.Busy) return; msrmntState = MsrmntState.Busy; msrmntTime = 0; stringBuilder.Clear(); serialPort.Write("Z\r\n"); } public virtual void SendTaraCmd() { if (serialPort == null || msrmntState == MsrmntState.Busy) return; msrmntState = MsrmntState.Busy; msrmntTime = 0; stringBuilder.Clear(); serialPort.Write("T\r\n"); } /// /// Get the serial number /// public void SendGetSerialNumberCmd() { if (serialPort == null || msrmntState == MsrmntState.Busy) return; msrmntState = MsrmntState.Busy; stringBuilder.Clear(); serialPort.Write(new byte[] { (byte)'I', (byte)'4', (byte)0x0D, (byte)0x0A }, 0, 4); } /// /// Get a stable mass measurement /// public void SendStableMeasurementCmd() { if (serialPort == null || msrmntState == MsrmntState.Busy) return; msrmntState = MsrmntState.Busy; msrmntTime = 0; stringBuilder.Clear(); serialPort.Write("S\r\n"); } /// /// Get an immediate mass measurement /// public void SendImmediateMeasurementCmd() { if (serialPort == null || msrmntState == MsrmntState.Busy) return; msrmntState = MsrmntState.Busy; msrmntTime = 0; stringBuilder.Clear(); serialPort.Write("SI\r\n"); } #endregion Scale control } }