352 lines
12 KiB
C#
352 lines
12 KiB
C#
using System;
|
|
using System.ComponentModel.Composition;
|
|
using DataStreamInterface;
|
|
|
|
namespace DataStreamMeter
|
|
{
|
|
[Export(typeof(IDataStreamMeter))]
|
|
public class MeterSimulation : IDataStreamMeter
|
|
{
|
|
MeterSimulationDlg modelessDlg;
|
|
|
|
/// Water meter specification
|
|
public readonly string MeterID = "3141592653";
|
|
public const Unit TimeUnits = Unit.s; /// Unit.s, Unit.ms, ...
|
|
public const Unit VolumeUnits = Unit.l; /// Unit.l, Unit.USgal, ...
|
|
public const Unit FlowUnits = Unit.m3ph; /// Unit.m3ph, Unit.USgalps, Unit.cfs, ...
|
|
public const double SamplingPeriodSec = 0.125; /// Sampling period in seconds (here 125 ms, 8 Hz)
|
|
public readonly double SamplingPeriod;
|
|
|
|
|
|
public const Int64 MaxSamplesCount = 40000; /// Maximal test time is SamplingPeriod * MaxSamplesCount
|
|
Sample[] samples = new Sample[MaxSamplesCount];
|
|
Int64 storedSamplesCount;
|
|
|
|
|
|
readonly object stateChangeAndTimerTickLock = new object();
|
|
public State State;
|
|
string connectionParameters;
|
|
|
|
/// initialTime is time when simulation started
|
|
/// (lastSampleTime - initialTime).TotalSeconds is multiple of Sampling Period
|
|
DateTime initialTime;
|
|
|
|
/// Last user interface tick info
|
|
bool lastTickValid;
|
|
State lastTickState;
|
|
|
|
/// Values incrementally updated on each timer tick
|
|
double currentTime;
|
|
double currentVolume;
|
|
double currentFlow;
|
|
double currentFlow_m3ph;
|
|
|
|
DateTime startTimeStamp; /// Measurement start DateTime
|
|
double startTime; /// Measurement start time in seconds
|
|
DateTime stopTimeStamp; /// Measurement end DateTime
|
|
double stopTime; /// Measurement end time in seconds
|
|
|
|
|
|
public MeterSimulation()
|
|
{
|
|
modelessDlg = null;
|
|
State = State.Disconnected;
|
|
SamplingPeriod = DataStreamInterface.Units.ConvertTo(TimeUnits, SamplingPeriodSec);
|
|
lastTickValid = false;
|
|
initialTime = DateTime.Now.Date; /// An arbitrary initial time (in this case the last midnight)
|
|
}
|
|
|
|
public Unit GetTimeUnits()
|
|
{
|
|
return TimeUnits;
|
|
}
|
|
|
|
public Unit GetVolumeUnits()
|
|
{
|
|
return VolumeUnits;
|
|
}
|
|
|
|
public int GetQuantitiesCount()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
public string GetQuantityCaption(int quantityNr)
|
|
{
|
|
if (quantityNr == 0) return "Flow";
|
|
return string.Empty;
|
|
}
|
|
|
|
public Unit GetQuantityUnits(int quantityNr)
|
|
{
|
|
if (quantityNr == 0) return FlowUnits;
|
|
return Unit.None;
|
|
}
|
|
|
|
|
|
public int GetMetersCount()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
public bool OpenConnection(int meterIx, string connectionParameters, out string meterID)
|
|
{
|
|
lock (stateChangeAndTimerTickLock)
|
|
{
|
|
if (State != State.Disconnected)
|
|
{
|
|
/// Meter is already connected
|
|
meterID = MeterID;
|
|
return true;
|
|
}
|
|
|
|
/// Connect the meter
|
|
meterID = MeterID;
|
|
this.connectionParameters = connectionParameters;
|
|
lastTickValid = false;
|
|
State = State.Connected;
|
|
}
|
|
|
|
/// Open modeless form
|
|
modelessDlg = new MeterSimulationDlg(this, MeterID);
|
|
modelessDlg.Show();
|
|
return true;
|
|
}
|
|
|
|
public bool CloseConnection(int meterIx)
|
|
{
|
|
bool closeModelessDlg = false;
|
|
|
|
lock (stateChangeAndTimerTickLock)
|
|
{
|
|
if (State != State.Disconnected)
|
|
{
|
|
State = State.Disconnected;
|
|
storedSamplesCount = 0;
|
|
closeModelessDlg = true;
|
|
}
|
|
}
|
|
|
|
if (closeModelessDlg)
|
|
{
|
|
if (modelessDlg != null) modelessDlg.Close();
|
|
modelessDlg = null;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool CloseConnectionAll()
|
|
{
|
|
return CloseConnection(0);
|
|
}
|
|
|
|
public bool Shutdown()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public bool GetState(int meterIx, out int state, out string parameter)
|
|
{
|
|
state = (int)this.State;
|
|
parameter = this.connectionParameters;
|
|
return true;
|
|
}
|
|
|
|
public bool SetState(int meterIx, int state, string parameter)
|
|
{
|
|
/// It's not allowed to change the satate in this demo
|
|
return false;
|
|
}
|
|
|
|
public bool SetStateAll(int state, string parameter)
|
|
{
|
|
return SetState(0, state, parameter);
|
|
}
|
|
|
|
public bool StartMeasurement(int meterIx = 0)
|
|
{
|
|
lock (stateChangeAndTimerTickLock)
|
|
{
|
|
if (State == State.Connected)
|
|
{
|
|
startTimeStamp = DateTime.Now;
|
|
startTime = TimeInSecondsFromDateTime(startTimeStamp, initialTime, SamplingPeriodSec);
|
|
storedSamplesCount = 0;
|
|
State = State.MeasurementInProgress;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool StartMeasurementAll()
|
|
{
|
|
return StartMeasurement(0);
|
|
}
|
|
|
|
public bool StopMeasurement(int meterIx, out Int64 storedFramesCount)
|
|
{
|
|
lock (stateChangeAndTimerTickLock)
|
|
{
|
|
if (State == State.MeasurementInProgress)
|
|
{
|
|
stopTimeStamp = DateTime.Now;
|
|
stopTime = TimeInSecondsFromDateTime(stopTimeStamp, initialTime, SamplingPeriodSec);
|
|
|
|
int newSamplesCount = Convert.ToInt32(Math.Round((stopTime - currentTime) / SamplingPeriodSec));
|
|
double time = Units.ConvertTo(TimeUnits, currentTime);
|
|
double volume = currentVolume;
|
|
double volumeIncrement = Units.ConvertTo(VolumeUnits, currentFlow_m3ph * (SamplingPeriodSec / 3.6));
|
|
for (int i = 0; i < newSamplesCount; i++)
|
|
{
|
|
time += SamplingPeriod;
|
|
volume += volumeIncrement;
|
|
if (storedSamplesCount < MaxSamplesCount)
|
|
{
|
|
samples[storedSamplesCount++] = new Sample(time, volume, currentFlow);
|
|
}
|
|
}
|
|
|
|
State = State.Connected;
|
|
storedFramesCount = storedSamplesCount;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
storedFramesCount = 0;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool StopMeasurementAll(out Int64[] storedFramesCount)
|
|
{
|
|
storedFramesCount = new Int64[1];
|
|
return StopMeasurement(0, out storedFramesCount[0]);
|
|
}
|
|
|
|
public void TimerTick(double flow_m3ph)
|
|
{
|
|
lock (stateChangeAndTimerTickLock)
|
|
{
|
|
double lastSampleTime = TimeInSecondsFromDateTime(DateTime.Now, initialTime, SamplingPeriodSec);
|
|
currentFlow_m3ph = flow_m3ph;
|
|
currentFlow = Units.ConvertTo(FlowUnits, flow_m3ph);
|
|
|
|
if (!lastTickValid)
|
|
{
|
|
currentTime = lastSampleTime;
|
|
currentVolume = 0;
|
|
lastTickValid = true;
|
|
lastTickState = State;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
int newSamplesCount = Convert.ToInt32(Math.Round((lastSampleTime - currentTime) / SamplingPeriodSec));
|
|
double volumeIncrement = Units.ConvertTo(VolumeUnits, currentFlow_m3ph * (SamplingPeriodSec / 3.6));
|
|
for (int i = 0; i < newSamplesCount; i++)
|
|
{
|
|
currentTime += SamplingPeriodSec;
|
|
currentVolume += volumeIncrement;
|
|
|
|
if (State == State.MeasurementInProgress && currentTime > startTime && storedSamplesCount < MaxSamplesCount)
|
|
{
|
|
samples[storedSamplesCount++] = new Sample(Units.ConvertTo(TimeUnits, currentTime), currentVolume, currentFlow);
|
|
}
|
|
}
|
|
currentTime = lastSampleTime; /// Rectify, prevent error propagation
|
|
lastTickState = State;
|
|
}
|
|
}
|
|
|
|
modelessDlg.OnMeterdata(new MeterDataEventArgs(State, currentTime, currentVolume, currentFlow));
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Obtain the last time instance before 'DateTime time' which is multiple of samplingPeriod-s after 'DateTime initialTime'.
|
|
/// </summary>
|
|
/// <param name="time">Time to be converted to seconds and rounded to samplingPeriod-s</param>
|
|
/// <param name="startTime">Initial time</param>
|
|
/// <param name="samplePeriod">Sampling period in seconds</param>
|
|
/// <returns></returns>
|
|
double TimeInSecondsFromDateTime(DateTime time, DateTime initialTime, double samplingPeriod)
|
|
{
|
|
TimeSpan span = time - initialTime;
|
|
return samplingPeriod * Math.Floor(span.TotalSeconds / samplingPeriod);
|
|
}
|
|
|
|
|
|
///---------------------------
|
|
/// Datastream data exchange
|
|
///---------------------------
|
|
|
|
/// <summary>
|
|
/// Retuns 'count' data frames starting with data frame with ID = 'id'
|
|
/// </summary>
|
|
/// <param name="id">First frame ID</param>
|
|
/// <param name="count">Frames count</param>
|
|
/// <returns>Selected data frames</returns>
|
|
public DataFrame[] GetFrames(int meterIx, Int64 startID, int count)
|
|
{
|
|
DataFrame[] frames = new DataFrame[count];
|
|
|
|
if (State != State.MeasurementInProgress)
|
|
{
|
|
for (int j = 0; j < count; j++)
|
|
{
|
|
Int64 id = startID + j;
|
|
if (id < storedSamplesCount)
|
|
{
|
|
frames[j] = new DataFrame(id, samples[id].Time, samples[id].Volume, new double[1] { samples[id].Flow });
|
|
}
|
|
}
|
|
}
|
|
|
|
return frames;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns ID of the data frame where time equals or exceeds the specified time.
|
|
/// When time of the first frame (ID=0) is larger then specified time, function returns 0.
|
|
/// </summary>
|
|
/// <param name="time">Time</param>
|
|
/// <returns>ID of the data frame at or after the pecified time</returns>
|
|
public Int64 GetID(int meterIx, double time)
|
|
{
|
|
if (storedSamplesCount == 0) return -1;
|
|
|
|
Int64 lo = 0;
|
|
Int64 hi = storedSamplesCount - 1;
|
|
|
|
if (samples[hi].Time < time) return -1;
|
|
|
|
while (lo < hi)
|
|
{
|
|
Int64 mid = (lo + hi) / 2;
|
|
if (samples[mid].Time < time)
|
|
{
|
|
lo = mid + 1;
|
|
}
|
|
else
|
|
{
|
|
hi = mid;
|
|
}
|
|
}
|
|
|
|
return lo;
|
|
}
|
|
}
|
|
|
|
public enum State
|
|
{
|
|
Disconnected = 0,
|
|
Connected = 1,
|
|
MeasurementInProgress = 2,
|
|
}
|
|
}
|