/// /// Copyright (c) 2013-2021 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.Globalization; using System.Text; using System.IO.Ports; using log4net; using Common; using TBF.Rig.Generic; using TBF.Rig.GenericDevices; using TBF.Boxes; using TBF.Resources; namespace TBF.Rig.MettlerToledo.Standard { public enum Activity { Idle, ImmediateMeasurement, RunningOperation, } /// /// Mettler Toledo ICS4_5 Weighting terminal connected via serial interface (RS232) /// /// /// Implemented and tested on 15.10.2013 by Milan Hanajik /// public class BalanceDev : TankDraining, IDevice, GenericDevices.IScale, GenericDevices.IHasCalendarEvents { /// /// Info: StartMassMeasurement() and "Balnce response = .., Mass = ..." /// Warn: Start measurement when busy is true /// private static readonly ILog log = LogManager.GetLogger(typeof(BalanceDev)); public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } protected readonly BalanceCfg balanceCfg; /// /// Enumeration of balances via static fields and methods /// static int nextBalanceIdx = 0; public static int BalancesCount { get { return nextBalanceIdx; } } public static BalanceDev[] Balances; public static double[] Masses; /// protected int balanceNr; /// 0-based balance number protected SerialPort serialPort; protected StringBuilder stringBuilder; public int ScaleNr { get { return balanceNr; } } public double Capacity { get { return balanceCfg.Capacity; } } public int EmptyTimeSec { get { return balanceCfg.DrainTimeSec; } } public IValve DrainValve2 { get { return null; } } /// Buoyancy public float BuoyancyTemp { get { return balanceCfg.BuoyancyTemp; } } public float BuoyancyPress { get { return balanceCfg.BuoyancyPress; } } public float BuoyancyHumi { get { return balanceCfg.BuoyancyHumi; } } public double WeightStandardDensity { get { return balanceCfg.WeightStandardDensity; } } /// The state of the mass measurement public Activity Activity; public MsrmntState MsrmntState { get { return msrmntState; } set { msrmntState = value; } } protected MsrmntState msrmntState; /// Time since the last serial command in seconds public int MsrmntTime; /// The mass after StartMassMeasurement() when MsrmntState == MsrmntState.Valid public double Mass { get { return mass; } } protected double mass; /// The time stamp after StartMassMeasurement() when MsrmntState == MsrmntState.Valid public int MsrmntTimeStamp { get { return msrmntTimeStamp; } } protected int msrmntTimeStamp; public string Format { get { return balanceCfg.Format; } } public BalanceDev() { } /// /// Constructor /// /// Balance properties /// A list of components loaded so far public BalanceDev(Generic.IComponentCfg cfg) : base(cfg) { balanceCfg = cfg as BalanceCfg; } public override void Initialize() { base.Initialize(); balanceNr = nextBalanceIdx++; Masses = new double[nextBalanceIdx]; /// (re)initialize the static array of masses of all balances /// if (Balances == null || Balances.Length < nextBalanceIdx) { BalanceDev[] balancesSoFar = Balances; Balances = new BalanceDev[nextBalanceIdx]; if (balancesSoFar != null) for (int i = 0; i < balancesSoFar.Length; i++) Balances[i] = balancesSoFar[i]; Balances[nextBalanceIdx - 1] = this; } if (balanceCfg.CalibValidDate != DateTime.MinValue && balanceCfg.CalibValidDate.Date < DateTime.Now.Date) { throw new Exception(string.Format("{0}: {1}", Name, Strings.Calibration_certificate_validity_expired)); } Activity = Activity.Idle; msrmntState = MsrmntState.Failed; /// Data not valid yet MsrmntTime = 0; stringBuilder = new StringBuilder(40); if (balanceCfg.DebugLevel == DebugMode.Normal) { string portName = "COM" + balanceCfg.ComPortNr.ToString(); serialPort = new SerialPort(portName, balanceCfg.BaudRate, balanceCfg.Parity, balanceCfg.DataBits, balanceCfg.StopBits); serialPort.Handshake = balanceCfg.Handshake; serialPort.Open(); log.FatalFormat("{0} initialized: {1}", Name, this); } else { serialPort = null; log.FatalFormat("{0} simulated: {1}", Name, this); } } public IList GetCalendarEvents() { DateTime calibrationDue = balanceCfg.CalibValidDate; IList calendarEvents = new List(); if (calibrationDue > TBF.UI.Constants.MinDate) { /// Calibration due date calendar event calendarEvents.Add(new TBF.UI.Calendar.CalibrationDueDateEvent(calibrationDue.Date, Name, string.Format(Strings.Calibration_due_date_is_0, calibrationDue.Date.ToString(TBF.UI.Constants.DateFormat)))); if (DateTime.Now.Date <= calibrationDue.AddDays(-7)) { /// Weekly reminders (last 5 weeks) calendarEvents.Add(new TBF.UI.Calendar.CalibrationReminderEvent(calibrationDue.Date.AddDays(-35), Name, string.Format(Strings.Calibration_due_date_is_0, calibrationDue.Date.ToString(TBF.UI.Constants.DateFormat)), false)); } if (DateTime.Now.Date <= calibrationDue.Date) { /// Daily reminders (last 5 days) calendarEvents.Add(new TBF.UI.Calendar.CalibrationReminderEvent(calibrationDue.AddDays(-5), Name, string.Format(Strings.Calibration_due_date_is_0, calibrationDue.Date.ToString(TBF.UI.Constants.DateFormat)), true)); } } return calendarEvents; } public override bool IsEmpty() { return DrainValve.State ? (mass <= balanceCfg.Empty) : (mass <= balanceCfg.EmptyUp); } public override bool ContainsMoreThen(float thld) { return (mass >= thld); } public virtual void SendZeroWhenStableCmd() { if (balanceCfg.DebugLevel == DebugMode.Simulate) return; if (serialPort == null || !serialPort.IsOpen) { log.ErrorFormat("{0} - SendZeroWhenStableCmd() - serial port closed", Name); return; } if (msrmntState == MsrmntState.Busy) { log.WarnFormat("{0} - SendZeroWhenStableCmd() returns without starting zeroing the scale because measurementState == MeasurementState.Busy", Name); return; } log.InfoFormat("{0} - SendZeroWhenStableCmd() - \"Z\\r\\n\"", Name); msrmntState = MsrmntState.Busy; MsrmntTime = 0; stringBuilder.Clear(); serialPort.Write("Z\r\n"); } public void SendTaraCmd() { if (balanceCfg.DebugLevel == DebugMode.Simulate) return; if (serialPort == null || !serialPort.IsOpen) { log.ErrorFormat("{0} - SendTaraCmd() - serial port closed", Name); return; } if (msrmntState == MsrmntState.Busy) { log.WarnFormat("{0} - SendTaraCmd() returns without starting taring the scale because measurementState == MeasurementState.Busy", Name); return; } log.InfoFormat("{0} - SendTaraCmd() - - \"T\\r\\n\"", Name); msrmntState = MsrmntState.Busy; MsrmntTime = 0; stringBuilder.Clear(); serialPort.Write("T\r\n"); } /// /// Get a stable mass measurement /// public void SendStableMeasurementCmd() { if (balanceCfg.DebugLevel == DebugMode.Simulate) return; if (serialPort == null || !serialPort.IsOpen) { log.ErrorFormat("{0} - SendStableMeasurementCmd() - serial port closed", Name); return; } if (msrmntState == MsrmntState.Busy) { log.WarnFormat("{0} - SendStableMeasurementCmd() returns without starting a measurement because measurementState == MeasurementState.Busy", Name); return; } log.InfoFormat("{0} - SendStableMeasurementCmd() - \"S\\r\\n\"", Name); msrmntState = MsrmntState.Busy; MsrmntTime = 0; stringBuilder.Clear(); serialPort.Write("S\r\n"); } /// /// Get an immediate mass measurement /// public void SendImmediateMeasurementCmd() { if (balanceCfg.DebugLevel == DebugMode.Simulate) return; if (serialPort == null || !serialPort.IsOpen) { log.ErrorFormat("{0} - SendImmediateMeasurementCmd() - serial port closed", Name); return; } if (msrmntState == MsrmntState.Busy) { log.WarnFormat("{0} - SendImmediateMeasurementCmd() returns without starting a measurement because measurementState == MeasurementState.Busy", Name); return; } log.InfoFormat("{0} - SendImmediateMeasurementCmd() - \"SI\\r\\n\"", Name); msrmntState = MsrmntState.Busy; MsrmntTime = 0; stringBuilder.Clear(); serialPort.Write("SI\r\n"); } /// /// Get the serial number /// /// After a successful SetUnits(kg) mass is still sent in g. MH 15.10.2013 public void SendSetUnitsCmd(Common.Unit unit) { if (balanceCfg.DebugLevel == DebugMode.Simulate) return; if (serialPort == null || !serialPort.IsOpen) { log.ErrorFormat("{0} - SendSetUnitsCmd() - serial port closed", Name); return; } if (msrmntState == MsrmntState.Busy) { log.WarnFormat("{0} - SendSetUnitsCmd() returns without setting the units because measurementState == MeasurementState.Busy", Name); return; } msrmntState = MsrmntState.Busy; stringBuilder.Clear(); switch (unit) { case Common.Unit.g: log.InfoFormat("{0} - SetUnits({1}) - \"U g\\r\\n\"", Name, unit); serialPort.Write("U g\r\n"); break; case Common.Unit.t: log.InfoFormat("{0} - SetUnits({1}) - \"U t\\r\\n\"", Name, unit); serialPort.Write("U t\r\n"); break; case Common.Unit.lb: log.InfoFormat("{0} - SetUnits({1}) - \"U lb\\r\\n\"", Name, unit); serialPort.Write("U lb\r\n"); break; case Common.Unit.oz: log.InfoFormat("{0} - SetUnits({1}) - \"U oz\\r\\n\"", Name, unit); serialPort.Write("U oz\r\n"); break; case Common.Unit.kg: default: log.InfoFormat("{0} - SetUnits({1}) - \"U kg\\r\\n\"", Name, unit); serialPort.Write("U kg\r\n"); break; } } /// /// Parse characters received from the serial port /// public virtual void RunDeviceBefore() { base.RunBefore(); if (balanceCfg.DebugLevel == DebugMode.Simulate) { Masses[balanceNr] = 123.456; msrmntState = MsrmntState.Valid; msrmntTimeStamp = StateMachine.Time; return; } if (serialPort == null || !serialPort.IsOpen) { log.ErrorFormat("{0} - RunDeviceBefore() - serial port closed", Name); return; } log.DebugFormat("{0} - RunDeviceBefore()", Name); if (msrmntState != MsrmntState.Busy) return; /// No measurement in progress /// if (Activity == Activity.ImmediateMeasurement) { Activity = Activity.Idle; } string justReceived = serialPort.ReadExisting(); if (justReceived != null && justReceived.Length > 0) { log.DebugFormat("{0} - RunDeviceBefore() received {1}", Name, justReceived); stringBuilder.Append(justReceived); } string currentBuffer = stringBuilder.ToString(); int eolPos = currentBuffer.IndexOf("\r\n"); if (eolPos >= 0) { msrmntTimeStamp = StateMachine.Time; /// Get part of the string before EOL string received = currentBuffer.Substring(0, eolPos); /// Put the rest back to the string builder stringBuilder.Clear(); if (currentBuffer.Length > eolPos + 2) { stringBuilder.Append(currentBuffer.Substring(eolPos + 2)); } while (true) { string tmp = received.Replace(" ", " "); if (tmp.Length == received.Length) break; received = tmp; } string[] field = received.Split(new char[] { ' ' }); if (field.Length == 1 && field[0] == "ZA") /// Zero when stable { mass = 0; Masses[balanceNr] = mass; //msrmntState = MsrmntState.Valid; log.InfoFormat("{0} - Balance response = \"{1}\"", Name, received); return; } else if (field.Length < 2) { msrmntState = MsrmntState.Failed; log.WarnFormat("{0} - Balance response = \"{1}\", Unexpected error !!!", Name, received); return; } if (field[0] == "Z") /// Zero when stable { if (field.Length == 2 && field[1] == "A") { mass = 0; Masses[balanceNr] = mass; //msrmntState = MsrmntState.Valid; log.InfoFormat("{0} - Balance response = \"{1}\"", Name, received); return; } else { msrmntState = MsrmntState.Failed; log.WarnFormat("{0} - Balance response = \"{1}\", error !!!", Name, received); } } else if (field[0] == "S") /// Stable mass: field[1] = field[2] = "kg" { if (field.Length == 3 && double.TryParse(field[1], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out mass)) { mass = Common.Units.ConvertFrom(ParseUnit(field[2]), mass); Masses[balanceNr] = mass; msrmntState = MsrmntState.Valid; log.InfoFormat("{0} - Balance response = \"{1}\", Mass = {2}", Name, received, mass); } else if (field.Length == 4 && (field[1] == "S" || field[1] == "D") && double.TryParse(field[2], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out mass)) { mass = Common.Units.ConvertFrom(ParseUnit(field[3]), mass); Masses[balanceNr] = mass; msrmntState = MsrmntState.Valid; log.InfoFormat("{0} - Balance response = \"{1}\", Mass = {2}", Name, received, mass); } else if (field.Length == 2 && field[1] == "-") { mass = 0; Masses[balanceNr] = mass; msrmntState = MsrmntState.Valid; log.InfoFormat("{0} - Balance response = \"{1}\", Mass = {2}", Name, received, mass); } else { msrmntState = MsrmntState.Failed; log.WarnFormat("{0} Balance response = \"{1}\", Overload or and unexpected error !!!", Name, received); bool b = double.TryParse(field[1], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out mass); } } else if (field[0] == "SD") /// Dynamic mass: field[1] = field[2] = "kg" { if (field.Length >= 3 && double.TryParse(field[1], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out mass)) { mass = Common.Units.ConvertFrom(ParseUnit(field[2]), mass); Masses[balanceNr] = mass; msrmntState = MsrmntState.Valid; log.InfoFormat("{0} - Balance response = \"{1}\", Mass = {2}", Name, received, mass); } else if (field.Length == 2 && field[1] == "-") { mass = 0; Masses[balanceNr] = mass; msrmntState = MsrmntState.Valid; log.InfoFormat("{0} - Balance response = \"{1}\", Mass = {2}", Name, received, mass); } else { msrmntState = MsrmntState.Failed; log.WarnFormat("{0} - Balance response = \"{1}\", Overload or and unexpected error !!!", Name, received); bool b = double.TryParse(field[1], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out mass); } } else if (field[0] == "TB") /// Taring { if (field.Length >= 3 && double.TryParse(field[1], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out mass)) { mass = Common.Units.ConvertFrom(ParseUnit(field[2]), mass); msrmntState = MsrmntState.Valid; log.InfoFormat("{0} - Balance response = \"{1}\", Tara = {2}", Name, received, mass); } else { msrmntState = MsrmntState.Failed; log.WarnFormat("{0} - Balance response = \"{1}\", Overload or and unexpected error !!!", Name, received); } } else if (field[0] == "T") /// Taring { if (field.Length >= 4 && field[1] == "S" && double.TryParse(field[2], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out mass)) { mass = Common.Units.ConvertFrom(ParseUnit(field[3]), mass); msrmntState = MsrmntState.Valid; log.InfoFormat("{0} - Balance response = \"{1}\", Tara = {2}", Name, received, mass); } else { msrmntState = MsrmntState.Failed; log.WarnFormat("{0} - Balance response = \"{1}\", Overload or and unexpected error !!!", Name, received); } } else { msrmntState = MsrmntState.Failed; log.WarnFormat("{0} - Balance response = \"{1}\", Unexpected error !!!", Name, received); } } } /// Run this device public void RunDeviceAfter() { if (Activity == Activity.Idle) { SendImmediateMeasurementCmd(); Activity = Activity.ImmediateMeasurement; } base.RunAfter(); } /// Stop this device public void StopDevice() { if ((balanceCfg.DebugLevel != DebugMode.Simulate) && (serialPort != null)) { serialPort.Close(); serialPort = null; } } public void StopDevice2() { } /// /// Events: BalanceDone, Error /// /// ZeroOp reference public IOperation ZeroOp() { switch (balanceCfg.ActionAfter1stDraining) { case ActionA1D.Zero: return new ZeroOp(this); case ActionA1D.Tara: DoubleBox dummy = new DoubleBox(); return new TaringOp(this, ref dummy); default: return null; } } /// /// Events: BalanceDone, Error /// /// Reference to a variable for 'tara' in kg /// TaringOp reference public IOperation TaringOp(ref DoubleBox tara) { return new TaringOp(this, ref tara); } /// /// Events: BalanceDone, Error /// /// Reference to a variable for the measured mass in kg /// ReadMassOp instance reference casted to IOperaton public IOperation ReadMassOp(ref DoubleBox mass) { return new ReadMassOp(this, ref mass); } /// /// Events: BalanceDone, Error /// /// Reference to a variable for the measured mass in kg /// Required mass readings count (>= 3) /// Maximum spread of measurements in kg (otherwise the measurement continues) /// Method: false = slow (precise), true = fast (immediate) /// ReadMassAverageOp instance reference casted to IOperaton public IOperation ReadStableMassOp(ref DoubleBox mass, int readingsCount, double maxSpread, MassMethod method) { return new ReadStableMassOp(this, ref mass, readingsCount, maxSpread, method); } /// /// Events: BalanceDone, Error /// /// Reference to the serialNumber /// GetBalanceSNOp instance reference casted to IOperaton public virtual IOperation GetSerNumOp(ref string serialNumber) { return null; } /// /// Parse a string representing mass units. /// Defaults to 'kg'in case unitStr not recognized. /// /// String: "kg", "g", "t", "lb" or "oz" expected /// protected Common.Unit ParseUnit(string unitStr) { if (unitStr == "g") return Common.Unit.g; if (unitStr == "t") return Common.Unit.t; if (unitStr == "lb") return Common.Unit.lb; if (unitStr == "oz") return Common.Unit.oz; return Common.Unit.kg; } } }