tbf/TBF/Rig/Hart/Nivotrack/Nivotrack.cs

328 lines
12 KiB
C#

///
/// Copyright (c) 2018 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using Common;
using log4net;
using TBF.Rig.Generic;
using TBF.Rig.GenericDevices;
using TBF.Boxes;
using TBF.Rig.Hart.Common;
using TBF.Resources;
namespace TBF.Rig.Hart.Nivotrack
{
/// <summary>
/// Root component for Modbus communication via serial port (RS485)
/// </summary>
public class Nivotrack : ComponentBase, IDevice, ILevelMeter, IOperation, GenericDevices.IHasCalendarEvents
{
const int StableReadingsCount = 9;
private static readonly ILog log = LogManager.GetLogger(typeof(Nivotrack));
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
readonly NivotrackCfg nivotrackCfg;
readonly IHart hart;
public double Level { get { return ReadLevel(); } }
double rawLevel_mm;
bool isRawLevelValid;
/// For stable measurement calculation
double maxSDev;
double[] levelReadings;
double[] sortedLevelReadings;
int currentReadingsCount;
bool measurementCompleted; /// stableMeasurementCompleted
DoubleBox levelBox;
///
enum CurrentOp
{
None,
ReadLevel,
ReadStableLevel,
}
CurrentOp currentOp;
public Nivotrack() { }
/// <summary>
/// Ambient temperature / humidity / pressure meter 'Greco' connected via serial interface (RS232)
/// Connection settings: 19200 Bd 8-bits No-parity 1-stop-bit Flow control: none or hardware.
/// </summary>
public Nivotrack(Generic.IComponentCfg cfg, IList<Generic.IComponent> components)
: base(cfg)
{
nivotrackCfg = cfg as NivotrackCfg;
hart = (IHart)TbfComponents.FindComponent(cfg.ParentName, components);
if (hart == null) throw new Exception("Cannot find " + Name + " parent");
}
public override void Initialize()
{
levelReadings = new double[StableReadingsCount];
sortedLevelReadings = new double[StableReadingsCount];
rawLevel_mm = 0;
isRawLevelValid = false;
log.FatalFormat("{0} initialized: {1}", Name, this);
}
///
/// Calendar support
///
public IList<Config.CalendarEvent.ICalendarEvent> GetCalendarEvents()
{
DateTime calibrationDue = nivotrackCfg.CalibValidDate;
IList<Config.CalendarEvent.ICalendarEvent> calendarEvents = new List<Config.CalendarEvent.ICalendarEvent>();
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;
}
void Detect()
{
byte bcnt = (byte)0; /// block count
byte[] data = new byte[5];
data[0] = (byte)Mark.ShortFrameMaster2Slave;
data[1] = (byte)((int)Master.Primary + (byte)nivotrackCfg.Address);
data[2] = (byte)Cmd.ReadUniqueID;
data[3] = bcnt;
data[4] = 0; /// reserved for chehcksum
hart.SendMessage(5, data);
}
void RequestProcessValue()
{
byte bcnt = (byte)0; /// block count
byte[] data = new byte[5];
data[0] = (byte)Mark.ShortFrameMaster2Slave;
data[1] = (byte)((int)Master.Primary + (byte)nivotrackCfg.Address);
data[2] = (byte)Cmd.ReadPrimaryVar;
data[3] = bcnt;
data[4] = 0; /// reserved for chehcksum
hart.SendMessage(5, data);
}
/// <summary>Run this device</summary>
public void RunDeviceBefore()
{
if (nivotrackCfg.DebugLevel == DebugMode.Simulate) return;
if (hart.ReceivedTelegrams[nivotrackCfg.Address].Count > 0)
{
byte[] data = hart.ReceivedTelegrams[nivotrackCfg.Address].Dequeue();
if ((data.Length == 12) && (data[0] == (byte)Mark.ShortFrameSlave2Master)
&& (data[2] == (byte)Cmd.ReadPrimaryVar)
&& (data[3] == 7))
{
///
/// A frame with a status and a primary variable
///
ushort state = (ushort)(256 * data[4] + data[5]);
byte unitsID = data[6];
byte[] floatData = new byte[] { data[10], data[9], data[8], data[7] };
rawLevel_mm = System.BitConverter.ToSingle(floatData, 0);
isRawLevelValid = true;
log.InfoFormat("{0} : Received water level = {1} mm, State = {2}", Name, rawLevel_mm, state.ToString("X4"));
}
else if ((data.Length == 7) && (data[0] == (byte)Mark.ShortFrameSlave2Master)
&& (data[2] == (byte)Cmd.ReadPrimaryVar)
&& (data[3] == 2))
{
///
/// A frame with a status only
///
ushort state = (ushort)(256 * data[4] + data[5]);
isRawLevelValid = false;
log.InfoFormat("{0} : State = {1}", Name, state.ToString("X4"));
}
}
}
public void RunDeviceAfter()
{
if (nivotrackCfg.DebugLevel == DebugMode.Simulate) return;
if ((StateMachine.Time % 2) == (nivotrackCfg.Address % 2))
{
RequestProcessValue();
}
}
public void StopDevice() { }
public void StopDevice2() { }
/// <summary>
/// Gets the most recent raw level measurement and applies correction table
/// </summary>
/// <returns></returns>
public double ReadLevel()
{
return Config.Entities.MeasurementCorrection.CorrectedValue(rawLevel_mm, Corrections);
}
/// <returns>Reference to the operation</returns>
public IOperation ReadLevelOp(ref DoubleBox levelBox)
{
if (currentOp != CurrentOp.None) throw new Exception("Sequence error");
this.levelBox = levelBox;
currentOp = CurrentOp.ReadLevel;
return this;
}
/// <returns>Reference to the operation</returns>
public IOperation ReadStableLevelOp(ref DoubleBox levelBox, double maxSDev)
{
if (currentOp != CurrentOp.None) throw new Exception("Sequence error");
this.levelBox = levelBox;
this.maxSDev = maxSDev;
currentOp = CurrentOp.ReadStableLevel;
return this;
}
/// <summary>Start this operation</summary>
public void Start()
{
if (currentOp == CurrentOp.ReadLevel)
{
if (isRawLevelValid && (levelBox != null)) levelBox.Val = ReadLevel();
}
else if (currentOp == CurrentOp.ReadStableLevel)
{
currentReadingsCount = 0;
measurementCompleted = false;
if (isRawLevelValid)
{
levelReadings[currentReadingsCount++] = ReadLevel();
}
}
}
/// <summary>Run this operation</summary>
/// <returns>Event.ResultsPrinted</returns>
public Event Run()
{
if (measurementCompleted)
{
if ((currentOp == CurrentOp.ReadLevel) && (levelBox != null)) levelBox.Val = ReadLevel();
return Event.LevelDone;
}
if ((currentOp == CurrentOp.ReadLevel) && isRawLevelValid)
{
if (levelBox != null) levelBox.Val = ReadLevel();
measurementCompleted = true;
return Event.LevelDone;
}
else if ((currentOp == CurrentOp.ReadStableLevel) && isRawLevelValid)
{
///
/// Read new level and put it into a FIFO buffer
///
if (currentReadingsCount < StableReadingsCount)
{
levelReadings[currentReadingsCount++] = ReadLevel();
}
else
{
/// Shift data in the (already full) buffer, save the mass into the last buffer item
for (int i = 1; i < StableReadingsCount; i++)
{
levelReadings[i - 1] = levelReadings[i];
}
levelReadings[StableReadingsCount - 1] = ReadLevel();
}
///
/// Evaluate reading stability
///
if (currentReadingsCount >= StableReadingsCount)
{
Array.Copy(levelReadings, sortedLevelReadings, StableReadingsCount);
Array.Sort(sortedLevelReadings);
if (true)
{
/// Spread of values (except of the largest and the smalles value) must be <= maxSDev
if ((sortedLevelReadings[StableReadingsCount - 2] - sortedLevelReadings[1]) <= maxSDev)
{
double massSum = 0;
for (int i = 1; i < StableReadingsCount - 1; i++) massSum += sortedLevelReadings[i];
double ave = massSum / (StableReadingsCount - 2);
if (levelBox != null) levelBox.Val = ave;
measurementCompleted = true;
log.InfoFormat("ReadStableLevelOp.Run() ... valid level={0} ... returning LevelDone", ave);
return Event.LevelDone;
}
}
else
{
/// Standard deviation (except of the largest and the smalles value) must be <= maxSDev
double massSum = 0;
for (int i = 1; i < StableReadingsCount - 1; i++) massSum += sortedLevelReadings[i];
double ave = massSum / (StableReadingsCount - 2);
double sdev = 0;
for (int i = 1; i < StableReadingsCount - 1; i++) sdev += ((sortedLevelReadings[i] - ave) * (sortedLevelReadings[i] - ave));
if (Math.Sqrt(sdev / (StableReadingsCount - 2)) <= maxSDev)
{
if (levelBox != null) levelBox.Val = ave;
measurementCompleted = true;
log.InfoFormat("ReadStableMassOp.Run() ... valid mass={0} ... returning Event.BalanceDone", ave);
return Event.LevelDone;
}
}
}
}
return Event.Busy;
}
/// <summary>Stop this operation</summary>
public void Stop()
{
currentOp = CurrentOp.None;
}
}
}