/// /// 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; } } }