/// /// Copyright (c) 2013-2017 Sensus Metering Systems /// using System; using log4net; using TBF.Boxes; namespace TBF.Rig.MettlerToledo.Standard { /// /// Operation to read more mass values and average them to obtain a more accurate result. /// public class ReadStableMassOp : IOperation { private static readonly ILog log = LogManager.GetLogger(typeof(ReadStableMassOp)); public override string ToString() { return string.Format("ReadStableMassOp(.,{0},.)", totalReadingsCount); } /// Set by the constructor BalanceDev scale; int totalReadingsCount; double maxSpread; /// Maximum spread of measurements in kg (otherwise the measurement continues) Config.Entities.MassMethod method; /// by the scale, mass spread threshold or mass standard deviation threshold DoubleBox result; /// Operation specific double[] massReadings; double[] sortedMassReadings; int currentReadingsCount; bool activityStarted; bool measurementCompleted; int timeout; /// /// Events: BalanceDone, Error /// /// Balance device instance /// Reference to the measured mass in kg /// Required mass readings count (>= 4) /// Maximum spread of measurements in kg (otherwise the measurement continues) /// Method: false = slow (precise), true = fast (immediate) public ReadStableMassOp(BalanceDev scale, ref DoubleBox result, int readingsCount, double maxSpread, Config.Entities.MassMethod method) { if (scale == null) throw new ArgumentNullException("balanceDev"); this.scale = scale; this.totalReadingsCount = (readingsCount < 4) ? 5 : readingsCount; /// readingsCount is at least 4 this.maxSpread = (maxSpread == 0) ? (scale.Capacity / 20000.0) : maxSpread; this.method = method; this.result = result; massReadings = new double[totalReadingsCount]; sortedMassReadings = new double[totalReadingsCount]; currentReadingsCount = 0; activityStarted = false; measurementCompleted = false; timeout = (method == Config.Entities.MassMethod.Scale) ? 60 : 2; log.Debug(this.ToString()); } /// /// Call scale.XYStartMeasurement() function and set measurementStarted flag. /// void StartMeasurement() { if (method == Config.Entities.MassMethod.Scale) scale.SendStableMeasurementCmd(); else scale.SendImmediateMeasurementCmd(); } /// Start this operation public void Start() { currentReadingsCount = 0; measurementCompleted = false; if (scale.Activity == Activity.Idle) { StartMeasurement(); activityStarted = true; scale.Activity = Activity.RunningOperation; } else { activityStarted = false; } } /// Run this operation /// /// Event.None /// Event.BalanceDone /// Event.Error . . . . . Current.Tick == null /// public Event Run() { scale.MsrmntTime++; if (!activityStarted && scale.Activity == Activity.Idle) { StartMeasurement(); activityStarted = true; scale.Activity = Activity.RunningOperation; return Event.None; } if (measurementCompleted) { return Event.BalanceDone; } if (scale.MsrmntState == MsrmntState.Busy) { /// Measurement is in progress - resend command if timeout occurs if (scale.MsrmntTime >= timeout) StartMeasurement(); return Event.None; /// Wait for a mass measurement } if (scale.MsrmntState == MsrmntState.Valid) { /// Save the measurement if (currentReadingsCount < totalReadingsCount) { massReadings[currentReadingsCount++] = scale.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] = scale.Mass; } } if (currentReadingsCount >= totalReadingsCount) { Array.Copy(massReadings, sortedMassReadings, totalReadingsCount); Array.Sort(sortedMassReadings); #if STABLE_MASS_EXTRAS if (method == Config.Entities.MassMethod.Spread) { /// Immediate mass measurement, end condition calculated below if ((sortedMassReadings[totalReadingsCount - 2] - sortedMassReadings[1]) <= maxSpread) { double massSum = 0; for (int i = 1; i < totalReadingsCount - 1; i++) massSum += sortedMassReadings[i]; result.Val = (massSum / (totalReadingsCount - 2)); log.InfoFormat("ReadStableMassOp.Run() ... valid mass={0} ... returning Event.BalanceDone", result.Val); measurementCompleted = true; return Event.BalanceDone; /// Mass has just been calculated } } else if (method == Config.Entities.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)) <= maxSpread) { result.Val = ave; log.InfoFormat("ReadStableMassOp.Run() ... valid mass={0} ... returning Event.BalanceDone", result.Val); measurementCompleted = true; return Event.BalanceDone; /// Mass has just been calculated } } else #endif { /// 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]; result.Val = (massSum / (totalReadingsCount - 2)); log.InfoFormat("ReadStableMassOp.Run() ... valid mass={0} ... returning Event.BalanceDone", result.Val); measurementCompleted = true; return Event.BalanceDone; /// Mass has just been calculated } } if (measurementCompleted) { return Event.BalanceDone; } else { StartMeasurement(); /// Start a new measurement return Event.None; } } /// Stop this operation public void Stop() { if (scale.MsrmntState == MsrmntState.Busy) scale.MsrmntState = MsrmntState.Failed; scale.Activity = Activity.Idle; } } }