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)); } /// /// Obtain the last time instance before 'DateTime time' which is multiple of samplingPeriod-s after 'DateTime initialTime'. /// /// Time to be converted to seconds and rounded to samplingPeriod-s /// Initial time /// Sampling period in seconds /// double TimeInSecondsFromDateTime(DateTime time, DateTime initialTime, double samplingPeriod) { TimeSpan span = time - initialTime; return samplingPeriod * Math.Floor(span.TotalSeconds / samplingPeriod); } ///--------------------------- /// Datastream data exchange ///--------------------------- /// /// Retuns 'count' data frames starting with data frame with ID = 'id' /// /// First frame ID /// Frames count /// Selected data frames 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; } /// /// 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. /// /// Time /// ID of the data frame at or after the pecified time 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, } }