///
/// 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;
}
}
}