/// /// Copyright (c) 2013-2017 Sensus Metering Systems /// using System; using log4net; using Config.Entities; 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 readonly BalanceDev scale; readonly DoubleBox result; readonly int delayBefore; readonly MassMethod method; /// by the scale, mass spread threshold or mass standard deviation threshold readonly int totalReadingsCount; readonly double maxSpread; /// Maximum spread of measurements in kg (otherwise the measurement continues) /// Operation specific double[] massReadings; double[] sortedMassReadings; int currentReadingsCount; int startTime; 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(GenericDevices.IScale scale, ref DoubleBox result, int delayBefore, MassMethod method, int readingsCount, double maxSpread) { this.scale = scale as BalanceDev; if (this.scale == null) throw new ArgumentNullException("balanceDev"); this.result = result; this.delayBefore = delayBefore; this.method = method; this.totalReadingsCount = (readingsCount < 4) ? 5 : readingsCount; /// readingsCount is at least 4 this.maxSpread = (maxSpread == 0) ? (scale.Capacity / 20000.0) : maxSpread; massReadings = new double[totalReadingsCount]; sortedMassReadings = new double[totalReadingsCount]; currentReadingsCount = 0; measurementCompleted = false; timeout = (method == Config.Entities.MassMethod.Scale) ? 60 : 2; log.Debug(this.ToString()); } /// Internal states of this operation enum OpState { WaitingBeforeMeasurment, MassMeasurement, } OpState opState; /// /// 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() { startTime = StateMachine.Time; currentReadingsCount = 0; measurementCompleted = false; if (delayBefore == 0 && scale.Activity == Activity.Idle) { StartMeasurement(); scale.Activity = Activity.RunningOperation; opState = OpState.MassMeasurement; } else { opState = OpState.WaitingBeforeMeasurment; } } /// Run this operation /// /// Event.None /// Event.BalanceDone /// Event.Error . . . . . Current.Tick == null /// public Event Run() { if (opState == OpState.WaitingBeforeMeasurment) { if (StateMachine.Time >= startTime + delayBefore && scale.Activity == Activity.Idle) { StartMeasurement(); scale.Activity = Activity.RunningOperation; opState = OpState.MassMeasurement; } return Event.None; } scale.MsrmntTime++; if (measurementCompleted) { return Event.ScaleDone; } 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 (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.ScaleDone; /// 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.ScaleDone; /// 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]; result.Val = (massSum / (totalReadingsCount - 2)); log.InfoFormat("ReadStableMassOp.Run() ... valid mass={0} ... returning Event.BalanceDone", result.Val); measurementCompleted = true; return Event.ScaleDone; /// Mass has just been calculated } } if (measurementCompleted) { return Event.ScaleDone; } 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; } } }