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

345 lines
12 KiB
C#
Raw Normal View History

///
2023-04-18 12:01:23 +00:00
/// Copyright (c) 2018-2023 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using Common;
using log4net;
2021-10-26 09:23:57 +00:00
using TBF.Rig.Generic;
using TBF.Rig.GenericDevices;
using TBF.Boxes;
2021-10-26 09:23:57 +00:00
using TBF.Rig.Hart.Common;
using TBF.Resources;
2023-04-18 12:01:23 +00:00
using TBF.UI.Calendar;
2021-10-26 09:23:57 +00:00
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
///
2023-04-18 12:01:23 +00:00
public List<Config.CalendarEvent.ICalendarEvent> GetCalendarEvents()
{
2023-04-18 12:01:23 +00:00
var calEvents = new List<Config.CalendarEvent.ICalendarEvent>();
2023-04-18 12:01:23 +00:00
DateTime calibDue = nivotrackCfg.CalibValidDate;
if (calibDue > TBF.UI.Constants.MinDate)
{
2023-04-18 12:01:23 +00:00
/// Five weekly notifications
foreach (var days in new int[] { -35, -28, -21, -14 })
{
2023-04-18 12:01:23 +00:00
if (calibDue.Date.AddDays(days + 6) >= DateTime.Now.Date)
{
calEvents.Add(new CalibrationReminderEvent(Config.CalendarEvent.AutoAction.Notification,
calibDue.Date.AddDays(days),
Name,
string.Format(Strings.Calibration_due_date_is_0,
calibDue.Date.ToString(TBF.UI.Constants.DateFormat))));
}
}
2023-04-18 12:01:23 +00:00
/// Five daily warnings
foreach (var days in new int[] { -7, -6, -5, -4, -3, -2, -1 })
{
2023-04-18 12:01:23 +00:00
if (calibDue.Date.AddDays(days) >= DateTime.Now.Date)
{
/// Weekly reminders (last 5 weeks)
calEvents.Add(new CalibrationReminderEvent(Config.CalendarEvent.AutoAction.Warning,
calibDue.Date.AddDays(days),
Name,
string.Format(Strings.Calibration_due_date_is_0,
calibDue.Date.ToString(TBF.UI.Constants.DateFormat))));
}
}
/// Calibration due date
if (calibDue.Date >= DateTime.Now.Date)
{
calEvents.Add(new CalibrationDueDateEvent(calibDue.Date,
Name,
string.Format(Strings.Calibration_due_date_is_0,
calibDue.Date.ToString(TBF.UI.Constants.DateFormat))));
}
}
2023-04-18 12:01:23 +00:00
return calEvents;
}
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;
}
}
}