///
/// Copyright (c) 2018 Sensus Slovensko a.s.
///
using System;
using System.IO;
using System.IO.Ports;
using System.Windows.Forms;
using log4net;
using Config.Entities;
using TBF.BenchControl.Generic;
namespace TBF.BenchControl.RegisterReaders.SerialStream
{
///
/// This component = instance of this class is a placeholder for a combined main watermeter
///
public class SerialStream : ComponentBase, IDevice, GenericDevices.IRegReaderDatastream, GenericDevices.IHasTestName, IOperation
{
private static readonly ILog log = LogManager.GetLogger(typeof(SerialStream));
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
readonly SerialStreamCfg myCfg;
public int Position
{
get
{
int firstDigitPos = Name.IndexOfAny(new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' });
int position;
return (firstDigitPos < 0) ? 0 : (int.TryParse(Name.Substring(firstDigitPos), out position) ? position : 0);
}
}
public Config.Entities.RegisterReaderType RegisterReaderType { get { return Config.Entities.RegisterReaderType.DataStream; } }
public double PulsesPerLtr { get { return 1000.0; } }
public double LtrsPerPulse { get { return 1 / PulsesPerLtr; } }
///
/// Wrappers for procedure parameters
///
public StreamType StreamType { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.StreamType : StreamType.None; } }
public int FrameLength { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.FrameLength : 0; } }
public double FrameFrequency { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.FrameFrequency : 0; } }
public Config.Unit VolumeUnits { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.VolumeUnits : Config.Unit.l; } }
public double VolumeScaleFactor { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.VolumeScaleFactor : 1; } }
public int VolumeFieldStart { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.VolumeFieldStart : 0; } }
public int VolumeFieldEnd { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.VolumeFieldEnd : 0; } }
public FieldFormat VolumeFieldFormat { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.VolumeFieldFormat : FieldFormat.None; } }
public Config.Unit TimeUnits { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.TimeUnits : Config.Unit.s; } }
public double TimeScaleFactor { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.TimeScaleFactor : 1; } }
public int TimeFieldStart { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.TimeFieldStart : 0; } }
public int TimeFieldEnd { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.TimeFieldEnd : 0; } }
public FieldFormat TimeFieldFormat { get { return (myCfg.ProcParams != null) ? myCfg.ProcParams.TimeFieldFormat : FieldFormat.None; } }
public bool CommFailed
{
get { return commFailed; }
set { commFailed = value; }
}
bool commFailed;
///
/// Passed to OptoTelegramRaw.UpdateFromString(...)
///
Int64 volumeRawExtLast;
Int64 timestampExtLast;
///
/// Required for IRegisterReader interface
///
public int WMPulses { get { return wmPulses; } }
public int WMRefPulses { get { return wmRefPulses; } }
public double WMVolume { get { return wmVolume; } }
public double BeginWMState { get { return beginWMState; } }
public double EndWMState { get { return endWMState; } }
public double WMTestTime { get { return wmTestTime; } }
double beginWMState;
double endWMState;
double wmVolume;
int wmPulses;
int wmRefPulses;
double wmTestTime;
/// Name set by the test, to be used as a part of the opto-data log file name
public string TestName { set { testName = value; } }
public int TestRepeats { set { testRepeats = value; } }
public int RepetitionNr { set { repetitionNr = value; } }
///
string testName;
int testRepeats;
int repetitionNr;
/// Name set by the test, to be used as a part of the opto-data log file name
public string BenchName;
///
/// Volume of water from the opto telegram
///
private Int64 lastVolumeRaw; /// Last read raw volume
private double volumeLtr; ///
private double volumeLtr0;
public double VolumeLtrStart { get { return volumeLtrStart; } } /// Test start volume for metrology
public double VolumeLtrEnd { get { return volumeLtrEnd; } } /// Test end volume for metrology
double volumeLtrStart; /// Test start volume for metrology
double volumeLtrEnd; /// Test end volume for metrology
double volumeLtrEnd1; /// auxiliary buffer1 to keep the end volume before test stops
double volumeLtrEnd2; /// auxiliary buffer2 to keep the end volume before test stops
double volumeLtrEnd3; /// auxiliary buffer3 to keep the end volume before test stops
///
/// Timestamp from the opto telegram
///
bool lastTimestampValid;
private Int64 lastTimestamp;
private double timestampSec;
private double timestampSec0;
public bool NoSamples { get { return (timestampSecEnd - timestampSecStart) < float.Epsilon; } }
public double TimestampSecStart { get { return timestampSecStart; } }
public double TimestampSecEnd { get { return timestampSecEnd; } }
double timestampSecStart;
double timestampSecEnd;
double timestampSecEnd1;
double timestampSecEnd2;
double timestampSecEnd3;
private int frameIx;
public int TestStartFrameIx;
public int TestEndFrameIx;
DatastreamFrame[] datastreamFrames;
const int MaxDatastreamFramesCount = 80000; /// almost 3h at 8 Hz
int datastreamFramesCount;
string datastreamLogFileName;
DatastreamFrame toBeFlushed;
int flushedFramesCount;
public int FlushedFramesCount
{
get { return flushedFramesCount; }
set { flushedFramesCount = lastFlushedFramesCount = lastFlushedFramesCount_1 = value; }
}
int lastFlushedFramesCount_1;
int lastFlushedFramesCount;
public int FlushedFramesDelta
{
get
{
int retval = Math.Max(flushedFramesCount - lastFlushedFramesCount, lastFlushedFramesCount - lastFlushedFramesCount_1);
lastFlushedFramesCount_1 = lastFlushedFramesCount;
lastFlushedFramesCount = flushedFramesCount;
return retval;
}
}
///
/// Opto serial port and worker thread related private variables
///
private SerialPort serialPort; /// Used in DebugMode.Normal
private TextReader textReader; /// Used instead of serialPort in DebugMode.Simulate
public SerialStream() { }
public SerialStream(Generic.IComponentCfg cfg)
: base(cfg)
{
myCfg = cfg as SerialStreamCfg;
}
public override void Initialize()
{
ClearData();
datastreamParsingEnabled = false;
currentFrameFormat = new FrameFormat(FrameLength, FrameFrequency,
VolumeUnits, VolumeScaleFactor,
VolumeFieldStart, VolumeFieldEnd, VolumeFieldFormat,
TimeUnits, TimeScaleFactor,
TimeFieldStart, TimeFieldEnd, TimeFieldFormat);
log.WarnFormat("Initialize() ... FrameFormat set to {0}", currentFrameFormat);
/// Allocate memory for opto-data from iPerl
datastreamFrames = new DatastreamFrame[MaxDatastreamFramesCount];
for (int i = 0; i < MaxDatastreamFramesCount; i++) datastreamFrames[i] = new DatastreamFrame();
toBeFlushed = new DatastreamFrame();
flushedFramesCount = 0;
synchronized = false;
synchronized2 = false;
partOfTelegram = string.Empty;
if (DebugLevel == DebugMode.Normal)
{
/// Prepare serial port
serialPort = new SerialPort(string.Format("COM{0}", myCfg.ComPortNr),
myCfg.BaudRate,
myCfg.Parity,
myCfg.DataBits,
myCfg.StopBits);
serialPort.Handshake = myCfg.Handshake;
serialPort.Open();
log.FatalFormat("{0} initialized: {1}", Name, this);
}
else if (DebugLevel == DebugMode.Simulate)
{
try
{
textReader = new StreamReader("C:\\TBF\\Simulate\\serialstream.txt");
}
catch (Exception)
{
MessageBox.Show("Missing file C:\\TBF\\Simulate\\seriastream.txt");
}
log.FatalFormat("{0} simulated: {1}", Name, this);
}
else
{
serialPort = null;
log.FatalFormat("{0} in other mode: {1}", Name, this);
}
}
///
/// Clear data related to a specific water meter
///
public void ClearData()
{
commFailed = false;
datastreamFramesCount = 0;
}
public void RunDeviceBefore()
{
if (DebugLevel == DebugMode.Normal || DebugLevel == DebugMode.Simulate)
{
try
{
if (datastreamParsingEnabled)
ReadDatastream(DatastreamState.Read, currentFrameFormat);
else
ReadDatastream(DatastreamState.Flush, currentFrameFormat);
}
catch (Exception e)
{
DebugLevel = DebugMode.FailureDuringOperation;
log.FatalFormat("Opto-data serial port failure : {0}", e.Message);
if (e.InnerException != null)
{
log.FatalFormat("InnerMessage : {0}", e.InnerException.Message);
}
}
}
else if (DebugLevel == DebugMode.FailureDuringOperation)
{
}
}
public void RunDeviceAfter() { }
public void StopDevice()
{
try
{
if (DebugLevel == DebugMode.Normal && serialPort != null)
{
serialPort.Close();
serialPort = null;
}
}
catch
{
}
}
public void StopDevice2() { }
///
/// Events: Event.ReadRegisterDone, Event.Error
///
/// ReadWaterMeter instance reference casted to IOperaton
public IOperation ReadRegisterOp()
{
return this;
}
///
/// Clear data/counters related to a specific tests
///
public void Clear()
{
sampleNr = 0;
volumeLtr = 0;
volumeLtr0 = 0;
timestampSec = 0;
timestampSec0 = 0;
ReadPulses();
}
public void TestCompleted()
{
/// TODO: Implement
}
int sampleNr; /// This is to determine when the test start sample should be taken
/// Start this operation
public void Start()
{
Clear();
/// Reset opto data
datastreamFramesCount = 0;
TestStartFrameIx = 0;
TestEndFrameIx = 0;
/// File name
int digitIx = Name.IndexOfAny(new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
string position = (digitIx >= 0) ? Name.Substring(digitIx) : string.Empty;
datastreamLogFileName = string.Format("{0}_{1}_{2}_{3}.txt",
StateMachine.CycleStartTimeStamp.ToString("HHmmss"),
position,
testName,
repetitionNr);
StartParsingDatastream(new FrameFormat(FrameLength, FrameFrequency,
VolumeUnits, VolumeScaleFactor,
VolumeFieldStart, VolumeFieldEnd, VolumeFieldFormat,
TimeUnits, TimeScaleFactor,
TimeFieldStart, TimeFieldEnd, TimeFieldFormat));
}
/// Run this operation
/// eventDone
public Event Run()
{
sampleNr++;
ReadPulses();
if (sampleNr == 4)
{
/// Take the test start sample
volumeLtrStart = volumeLtr;
timestampSecStart = timestampSec;
TestStartFrameIx = frameIx;
}
/// Shift data in pipelines
volumeLtrEnd = volumeLtrEnd3;
volumeLtrEnd3 = volumeLtrEnd2;
volumeLtrEnd2 = volumeLtrEnd1;
volumeLtrEnd1 = volumeLtr;
timestampSecEnd = timestampSecEnd3;
timestampSecEnd3 = timestampSecEnd2;
timestampSecEnd2 = timestampSecEnd1;
timestampSecEnd1 = timestampSec;
TestEndFrameIx = frameIx - 3;
return Event.ReadRegisterDone;
}
/// Stop this operation
public void Stop()
{
if (TestStartFrameIx > 0 && TestStartFrameIx < datastreamFrames.Length && datastreamFrames[TestStartFrameIx].Flags == FrameFlags.OK)
{
datastreamFrames[TestStartFrameIx].Flags = FrameFlags.OK_TestStart;
DatastreamFrame.TestStartTimestampDec = datastreamFrames[TestStartFrameIx].TimestampDec(currentFrameFormat);
}
if (TestEndFrameIx > 0 && TestEndFrameIx < datastreamFrames.Length && datastreamFrames[TestEndFrameIx].Flags == FrameFlags.OK)
{
datastreamFrames[TestEndFrameIx].Flags = FrameFlags.OK_TestEnd;
}
StopParsingDatastream();
DatastreamFrame.FIRFilterFlow(datastreamFrames, datastreamFramesCount);
SaveDatastreamLog(currentFrameFormat);
}
void SaveDatastreamLog(FrameFormat ff)
{
string directory = string.Format("C:\\TBF\\ProcessData\\{0}\\{1}\\{2}\\",
StateMachine.CycleStartTimeStamp.ToString("yy"),
StateMachine.CycleStartTimeStamp.ToString("MM"),
StateMachine.CycleStartTimeStamp.ToString("dd"));
string datastreamLogPathName = directory + datastreamLogFileName;
try
{
Directory.CreateDirectory(directory);
double scalFact = ScalingFactor();
using (TextWriter datastreamLogFile = new StreamWriter(datastreamLogPathName))
{
datastreamLogFile.WriteLine(datastreamFrames[0].ToString(ff, null));
for (int i = 1; i < datastreamFramesCount; i++) datastreamLogFile.WriteLine(datastreamFrames[i].ToString(ff, datastreamFrames[i - 1]));
datastreamLogFile.Close();
}
}
catch (Exception exc)
{
File.Delete(datastreamLogPathName);
log.ErrorFormat(string.Format("Error writing into file {0}", datastreamLogPathName));
log.ErrorFormat(string.Format("Exception message: {0}", exc.Message));
}
}
void ReadPulses()
{
beginWMState = volumeLtr0;
endWMState = volumeLtr;
wmVolume = Math.Abs(endWMState - beginWMState);
wmPulses = (int)(wmVolume * (double)PulsesPerLtr + 0.5);
wmRefPulses = StateMachine.ControlBoard.EtPulses(0);
wmTestTime = timestampSec - timestampSec0;
}
bool datastreamParsingEnabled;
FrameFormat currentFrameFormat; /// Set by StartParsingDatastream()
/// Flush internal buffers and start parsing the opto serial port data
void StartParsingDatastream(FrameFormat ff)
{
currentFrameFormat = ff;
datastreamParsingEnabled = true;
log.WarnFormat("StartParsingDatastream() ... FrameFormat set to {0}", currentFrameFormat);
}
/// Stop parsig the opto serial port data
void StopParsingDatastream()
{
datastreamParsingEnabled = false;
}
///
/// Variables storing the context of serial port data parsing (ReadOptoSerialPort(...))
///
bool synchronized;
bool synchronized2;
string partOfTelegram;
///
/// 9600 Bd, 8 data bits, 1 stop bit, no parity
///
/// Telegram description:
///
/// AAAAAA[tab]BBBB[tab]CCCC[tab]DDDDDD[tab]EEEE[tab]FFFFFFFF[tab]GG[cr][lf] (42 bytes)
///
/// Example:
/// FFFFFE 51EA 0000 65324E 0087 F6319DFF 86
/// FFDD3A 51F9 0000 65324E 0088 F631A60B 45
/// ...
///
/// OptoState.Read or OptoState.Flush
void ReadDatastream(DatastreamState streamState, FrameFormat ff)
{
string received = null;
if (DebugLevel == DebugMode.Normal)
{
int nrBytes = serialPort.BytesToRead;
char[] buffer = new char[nrBytes];
serialPort.Read(buffer, 0, nrBytes);
received = new string(buffer);
}
else if (DebugLevel == DebugMode.Simulate)
{
string line = textReader.ReadLine();
received = line + '\r' + '\n';
}
if (received != null)
{
string allRcvd = partOfTelegram + received;
while (true)
{
int pos = allRcvd.IndexOf("\r\n");
if (pos < 0)
{
/// No CR+LF found, wait for more characters in the next invocation
partOfTelegram = allRcvd;
return;
}
else
{
// CR+LF found
if (streamState == DatastreamState.Read)
{
if (pos < ff.FrameLength - 2)
{
/// CR+LF found too early, truncate the beginning incl CR+LF and keep scanning in this loop
allRcvd = allRcvd.Substring(pos + 2);
if (synchronized)
{
datastreamFrames[datastreamFramesCount].Counter = datastreamFramesCount;
datastreamFrames[datastreamFramesCount++].SetFlags(FrameFlags.SyncError);
}
synchronized = true;
}
// CR+LF found and (pos >= OptoTelegramRaw.Length - 2)
else if (datastreamFrames[datastreamFramesCount].UpdateFromString(allRcvd.Substring(pos - ff.FrameLength + 2, ff.FrameLength),
ff, datastreamFramesCount, ref volumeRawExtLast, ref timestampExtLast))
{
OptoTelegramRreceived(datastreamFramesCount++, synchronized2);
synchronized2 = synchronized;
allRcvd = allRcvd.Substring(pos + 2);
}
else
{
datastreamFrames[datastreamFramesCount].Counter = datastreamFramesCount;
datastreamFramesCount++;
allRcvd = allRcvd.Substring(pos + 2);
}
}
else /// optoState == SerialStreamState.Flush
{
if (pos < FrameLength - 2)
{
/// CR+LF found too early, truncate the beginning incl CR+LF and keep scanning in this loop
allRcvd = allRcvd.Substring(pos + 2);
synchronized = true;
}
// CR+LF found and (pos >= OptoTelegram.Length - 2)
else if (toBeFlushed.UpdateFromStringDummy(allRcvd.Substring(pos - ff.FrameLength + 2), ff))
{
flushedFramesCount++;
synchronized2 = synchronized;
allRcvd = allRcvd.Substring(pos + 2);
}
else
{
allRcvd = allRcvd.Substring(pos + 2);
}
}
}
}
//OnFrameReceived(this, new FrameReceivedEventArgs(s));
}
else
{
//OnFrameReceived(this, new FrameReceivedEventArgs("."));
}
}
void OptoTelegramRreceived(int currentIx, bool async)
{
DatastreamFrame optoTelegram = datastreamFrames[currentIx];
frameIx = currentIx;
lastVolumeRaw = volumeRawExtLast;
lastTimestamp = timestampExtLast;
if (volumeLtr == 0 && volumeLtr0 == 0)
{
volumeLtr = (double)lastVolumeRaw / currentFrameFormat.VolumeScaleFactor;
volumeLtr0 = volumeLtr;
}
else
{
volumeLtr = (double)lastVolumeRaw / currentFrameFormat.VolumeScaleFactor;
}
if (timestampSec == 0 && timestampSec0 == 0)
{
timestampSec = (double)lastTimestamp / currentFrameFormat.TimeScaleFactor;
timestampSec0 = timestampSec;
}
else
{
timestampSec = (double)lastTimestamp / currentFrameFormat.TimeScaleFactor;
}
//optoDataLogger.InfoFormat("{0} {1} {2} ltr {3} {4}", optoTelegram, lastTimestamp, lastVolumeRaw.ToString("X8"), timestampSec.ToString("F1"), volumeLtr.ToString("F3"));
}
///
/// Called from the state machine when a test is selected and UI needs to be updated.
///
public void OnFrameReceived(object sender, FrameReceivedEventArgs args)
{
if (FrameReceivedHandler == null) return;
try { FrameReceivedHandler(sender, args); }
catch (Exception) { }
}
public event EventHandler FrameReceivedHandler;
public static double UnitVolume(Config.Unit units, double scaleFactor)
{
if (Config.Units.IsQuantity(units, Config.Quantity.Volume))
{
return Config.Units.ConvertFrom(units, 1.0) * scaleFactor;
}
else
{
return 1.0;
}
}
public double ScalingFactor()
{
return 1.0;
}
}
}