/// /// 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 Config.Entities; using SchematicDrawing; 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, IDrawingItCmpntWithMeasuredVal { private static readonly ILog log = LogManager.GetLogger(typeof(BalanceDev)); public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } /// Configuration and wrappers protected readonly BalanceCfg balanceCfg; /// public double Capacity { get { return balanceCfg.Capacity; } } public int EmptyTimeSec { get { return balanceCfg.DrainTimeSec; } } public IValve DrainValve2 { get { return null; } } 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; } } /// public IDrawingItem DrawingItem { get { return balanceCfg as IDrawingItem; } } protected SerialPort serialPort; protected StringBuilder stringBuilder; /// 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; public bool MsrmntAvailable { get { return true; } } public double MeasuredVal { get { return mass; } } public string AltString { get { return string.Empty; } } /// The time stamp after StartMassMeasurement() when MsrmntState == MsrmntState.Valid public int MsrmntTimeStamp { get { return msrmntTimeStamp; } } protected int msrmntTimeStamp; public string Format { get { return balanceCfg.MsrdFormat; } } 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(); /// Initializes DrainValve, etc., should be called in DebugMode.Simulate Activity = Activity.Idle; msrmntState = MsrmntState.Failed; /// Data not valid yet MsrmntTime = 0; stringBuilder = new StringBuilder(40); if (balanceCfg.CalibValidDate != DateTime.MinValue && balanceCfg.CalibValidDate.Date < DateTime.Now.Date) { throw new Exception(string.Format("{0}: {1}", Name, Strings.Calibration_certificate_validity_expired)); } if (balanceCfg.DebugLevel == DebugMode.Normal) { string comPortName = "COM" + balanceCfg.ComPortNr.ToString(); serialPort = new SerialPort(comPortName, balanceCfg.BaudRate, balanceCfg.Parity, balanceCfg.DataBits, balanceCfg.StopBits); serialPort.Handshake = balanceCfg.Handshake; serialPort.Open(); log.FatalFormat("{0} - Device successfully initialized", Name); } else { serialPort = null; log.FatalFormat("{0} - Device simulated", Name); } } 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 (msrmntState == MsrmntState.Busy) { log.WarnFormat("{0} - ZeroWhenStable() 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 virtual void SendTaraCmd() { if (balanceCfg.DebugLevel == DebugMode.Simulate) return; if (msrmntState == MsrmntState.Busy) { log.WarnFormat("{0} - Taring() 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 (msrmntState == MsrmntState.Busy) { log.WarnFormat("{0} - GetStableMassMeasurement() 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 || serialPort == null) return; if (msrmntState == MsrmntState.Busy) { log.WarnFormat("{0} - GetImmediateMassMeasurement() 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 (msrmntState == MsrmntState.Busy) { log.WarnFormat("{0} - SetUnits() 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) { mass = 123.456; msrmntState = MsrmntState.Valid; msrmntTimeStamp = StateMachine.Time; return; } if (serialPort == null) { log.ErrorFormat("{0} - RunDevice() - 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); if (stringBuilder.ToString().EndsWith("\r\n")) { msrmntTimeStamp = StateMachine.Time; 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[] field = received.Substring(0, received.Length - 2).Split(new char[] { ' ' }); ///-------- /// Zero ///-------- if (field.Length == 1 && field[0] == "ZB") { /// Zero command successful, reading is 0 now mass = 0; msrmntState = MsrmntState.Valid; } else if (field.Length == 1 && field[0] == "Z-") { /// Zero command cannot be executed : Below lower limit of zero set range mass = 0; msrmntState = MsrmntState.Failed; } else if (field.Length == 1 && field[0] == "Z+") { /// Zero command cannot be executed : Above upper limit of zero set range mass = 0; msrmntState = MsrmntState.Overload; } ///-------- /// Tare ///-------- else if (field.Length >= 3 && field[0] == "TB") { /// Tare command successful : response is "TB value unit\r\n" double m; if (double.TryParse(field[1], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out m)) { mass = Common.Units.ConvertFrom(ParseUnit(field[2]), m); msrmntState = MsrmntState.Valid; } else { msrmntState = MsrmntState.Failed; } } else if (field.Length == 1 && field[0] == "T-") { /// Tare command cannot be executed : Below lower limit of zero set range mass = 0; msrmntState = MsrmntState.Failed; } else if (field.Length == 1 && field[0] == "T+") { /// Tare command cannot be executed : Above upper limit of zero set range mass = 0; msrmntState = MsrmntState.Overload; } ///-------------------- /// Mass measurement ///-------------------- else if (field.Length >= 3 && (field[0] == "S" || field[0] == "SD")) /// Stable mass: field[1] = field[2] = "kg" { double m; if (double.TryParse(field[1], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out m)) { mass = Common.Units.ConvertFrom(ParseUnit(field[2]), m); msrmntState = MsrmntState.Valid; } else { msrmntState = MsrmntState.Failed; } } else if (field.Length == 1 && field[0] == "SI") { /// Invalid value mass = 0; msrmntState = MsrmntState.Valid; } else if (field.Length == 1 && field[0] == "SI-") { /// Invalid value : Scale is in underload range mass = 0; msrmntState = MsrmntState.Failed; } else if (field.Length == 1 && field[0] == "SI+") { /// Invalid value : Scale is in overload range mass = 0; msrmntState = MsrmntState.Overload; } /// /// Other responses are ignored /// /// Logging if (field.Length > 0) { if (msrmntState == MsrmntState.Valid || msrmntState == MsrmntState.Busy) { log.InfoFormat("{0} - Received \"{1}\", Mass = {2}, MsrmntState = {3}", Name, received, mass, msrmntState); } else { log.ErrorFormat("{0} - Received \"{1}\", Mass = {2}, MsrmntState = {3}", Name, received, mass, msrmntState); } } } } } /// 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 /// 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 delayBefore, MassMethod method, int readingsCount, double maxSpread) { return new ReadStableMassOp(this, ref mass, delayBefore, method, readingsCount, maxSpread); } /// /// 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; } } }