tbf/Workplace/CheckItems/Scale.cs

717 lines
26 KiB
C#

///
/// 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<string> BlackListedCodes
{
get { return blackList; }
set { blackList = value; }
}
IList<string> blackList;
public string ScannedCode
{
get { return string.Format(MassFormat, preciseMass * 1000); ; }
set { }
}
Scale()
{
InitializeComponent();
blackList = new List<string>();
}
/// <summary> Empty implementation, might be overriden in derived classes </summary>
public virtual void StartChangeHandler() { }
/// <summary> Empty implementation, might be overriden in derived classes </summary>
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<Activity> 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<Activity>();
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", "<cr>").Replace("\n", "<lf>"));
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", "<cr>").Replace("\n", "<lf>"), scaleSerialNumber);
}
else
{
msrmntState = MsrmntState.Failed;
DisplayError();
log.WarnFormat("{0} - Balance response = {1}, error !!!", Name,
received.Replace("\r", "<cr>").Replace("\n", "<lf>"));
}
}
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", "<cr>").Replace("\n", "<lf>"));
}
else
{
msrmntState = MsrmntState.Failed;
DisplayError();
log.WarnFormat("{0} - Balance response = \"{1}\", error !!!", Name,
received.Replace("\r", "<cr>").Replace("\n", "<lf>"));
}
}
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", "<cr>").Replace("\n", "<lf>"), 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", "<cr>").Replace("\n", "<lf>"), 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", "<cr>").Replace("\n", "<lf>"));
}
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", "<cr>").Replace("\n", "<lf>"), mass);
}
else
{
msrmntState = MsrmntState.Failed;
DisplayError();
log.WarnFormat("{0} - Balance response = \"{1}\", Overload or and unexpected error !!!", Name,
received.Replace("\r", "<cr>").Replace("\n", "<lf>"));
}
}
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", "<cr>").Replace("\n", "<lf>"), 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", "<cr>").Replace("\n", "<lf>"), mass);
}
else
{
msrmntState = MsrmntState.Failed;
DisplayError();
log.WarnFormat("{0} - Balance response = \"{1}\", Overload or and unexpected error !!!", Name,
received.Replace("\r", "<cr>").Replace("\n", "<lf>"));
}
}
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", "<cr>").Replace("\n", "<lf>"));
}
else
{
msrmntState = MsrmntState.Failed;
DisplayError();
log.WarnFormat("{0} - Balance response = \"{1}\", Unexpected error !!!", Name,
received.Replace("\r", "<cr>").Replace("\n", "<lf>"));
}
}
else if (fields[0].Equals("SI")) /// "SI I<cr><lf>" 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", "<cr>").Replace("\n", "<lf>"));
}
}
}
}
}
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()");
}
}
/// <summary>Stop this device</summary>
public void StopDevice()
{
if (initialized && serialPort != null)
{
serialPort.Close();
serialPort = null;
}
initialized = false;
}
/// <summary>
/// Zero the scale.
/// </summary>
/// <returns>true = operation completed</returns>
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;
}
/// <summary>
/// Perform precise measurement.
/// </summary>
/// <returns>true = operation completed</returns>
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");
}
/// <summary>
/// Get the serial number
/// </summary>
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);
}
/// <summary>
/// Get a stable mass measurement
/// </summary>
public void SendStableMeasurementCmd()
{
if (serialPort == null || msrmntState == MsrmntState.Busy) return;
msrmntState = MsrmntState.Busy;
msrmntTime = 0;
stringBuilder.Clear();
serialPort.Write("S\r\n");
}
/// <summary>
/// Get an immediate mass measurement
/// </summary>
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
}
}