/// /// Copyright (c) 2015-2017 Sensus Metering Systems /// Author: Milan Hanajík /// using System; using System.Collections.Generic; using System.IO; using System.IO.Ports; using System.Threading; using System.Windows.Forms; using log4net; using Config.Entities; using TBF.BenchControl; using TBF.BenchControl.Generic; using TBF.Resources; namespace TBF.BenchControl.WaterMeters.iPerl { /// /// This component = instance of this class is a placeholder for a combined main watermeter /// public class WaterMeter : ComponentBase, IDevice, GenericDevices.IWaterMeter, GenericDevices.IRegisterReader, IOperation { private static readonly ILog log = LogManager.GetLogger(typeof(WaterMeter)); public override string ToString() { return string.Format("iPerl({0})", Cfg.ToString(1)); } readonly WaterMeterCfg iPerlCfg; public int RfidComPortNr { get { return iPerlCfg.RfidComPortNr; } } public int MuxBoardNr { get { return iPerlCfg.MuxBoardNr; } } public int Group { get { return iPerlCfg.Group; } } public double PulsesPerLtr { get { return (double)iPerlCfg.ProcParams.PulsesPerLtr; } } public double LtrsPerPulse { get { return ltrsPerPulse; } } readonly double ltrsPerPulse; /// WaterMeter type (product name) and producer public string ProductName { get { return iPerlCfg.ProcParams.ProductName; } } public string Producer { get { return iPerlCfg.ProcParams.Producer; } } /// Pipe diameter [mm] public float DN { get { return iPerlCfg.ProcParams.DN; } } /// Build lenght [mm] public float L { get { return iPerlCfg.ProcParams.L; } } public Mounting Mounting { get { return iPerlCfg.ProcParams.Mounting; } } /// Nominal water flow in [m3/h] public float Q4_Qmax { get { return iPerlCfg.ProcParams.Q4; } } public float Q3_Qn { get { return iPerlCfg.ProcParams.Qn; } } public float Q2_Qt { get { return iPerlCfg.ProcParams.Q2; } } public float Q1_Qmin { get { return iPerlCfg.ProcParams.Q1; } } /// Metrological class, etc public string MetrologicalClass { get { return iPerlCfg.ProcParams.MetrologicalClass; } } public TemperatureClass TemperatureClass { get { return iPerlCfg.ProcParams.TemperatureClass; } } public PressureLossClass PressureLossClass { get { return iPerlCfg.ProcParams.PressureLossClass; } } public MaxAdmissiblePressure MaxAdmissiblePressure { get { return iPerlCfg.ProcParams.MaxAdmissiblePressure; } } public FlowProfileSensitivityClass FlowProfileSensitivityClass { get { return iPerlCfg.ProcParams.FlowProfileSensitivityClass; } } /// WaterMeter approval information or signature public string ApprovalInfo { get { return iPerlCfg.ProcParams.ApprovalInfo; } } public string Certificate { get { return iPerlCfg.ProcParams.Certificate; } } public string Text1 { get { return iPerlCfg.ProcParams.Text1; } } public string Text2 { get { return iPerlCfg.ProcParams.Text2; } } public string Text3 { get { return iPerlCfg.ProcParams.Text3; } } public string Text4 { get { return iPerlCfg.ProcParams.Text4; } } public string Text5 { get { return iPerlCfg.ProcParams.Text5; } } #if ORACLE_DB /// WM Type ID for Oracle DB public int WMType_ID { get { return iPerlCfg.ProcParams.WMType_ID; } } /// WM Type Revision for Oracle DB public int WMType_Rev { get { return iPerlCfg.ProcParams.WMType_Rev; } } #endif /// Properties set by the Begin and the End form public string SerialNr { get { if (ConfigStruct != null) return configStruct.PCBNumber2String(); else return string.Empty; } set { } } public string EndState { get { return endState; } set { endState = value; } } string endState; public string BeginState { get { return beginState; } set { beginState = value; } } string beginState; public bool Disabled { get { return disabled; } set { disabled = value; } } bool disabled; public bool CommFailed { get { return commFailed; } set { commFailed = value; } } bool commFailed; public bool PositiveCounting; /// ConfigStruct of the water meter obtained or updated by iPerlCommunication public ConfigStruct ConfigStruct { get { return configStruct; } set { configStruct = value; } } ConfigStruct configStruct; /// CalibrationStruct of the water meter obtained or updated by iPerlCommunication public CalibrationStruct CalibrationStruct { get { return calibrationStruct; } set { calibrationStruct = value; } } CalibrationStruct calibrationStruct; public ushort OriginalCalibFactor; public ushort CalibrationFactor { get { return (CalibrationStruct != null) ? CalibrationStruct.Calibration : (ushort)0; } } public double Q2ErrorWOCorrection; public bool Q2CorrectionDone; public double Q2CorrectionFactor; public double Diff2Hz8Hz; public bool Hz2CorrectionDone; public int Hz2CorrectionFactor; /// Result of the last test used to calculate Q2 correction factors, etc public Results.Entities.MeterTestRslt LastTestResult2; public Results.Entities.MeterTestRslt LastTestResult; public double NominalTestFlow; /// liter per hour /// /// 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; /// /// New calibration factor calculated from the original factor (argument) /// and results of the last test. /// /// Original calibration factor /// New calibration factor public UInt16 CalculateNewCalibFactor(UInt16 originalCalibrationFactor, UInt16 FactorLimitLo, UInt16 FactorLimitHi) { OriginalCalibFactor = originalCalibrationFactor; double volumeMeter = Math.Abs(VolumeLtrEnd - VolumeLtrStart); if (volumeMeter > 1E-2) { PositiveCounting = VolumeLtrEnd > VolumeLtrStart; UInt16 newFactor = (UInt16)((double)originalCalibrationFactor * VolumeLtrRef / volumeMeter + 0.5); log.InfoFormat("Calibration factor: orig={0} new={1} V_1={2} V_2={3} Vdiff={4} Vref={5}", originalCalibrationFactor, newFactor, VolumeLtrStart.ToString("F3"), VolumeLtrEnd.ToString("F3"), volumeMeter.ToString("F3"), VolumeLtrRef.ToString("F3")); if (newFactor < FactorLimitLo) newFactor = FactorLimitLo; if (newFactor > FactorLimitHi) newFactor = FactorLimitHi; return newFactor; } else { log.ErrorFormat("Calibration factor: orig={0} new={0} (unchanged!) Vdiff={1}", originalCalibrationFactor, volumeMeter.ToString("F3")); return originalCalibrationFactor; /// Too small volume in the denominator -> no correction at all } } /// /// Q2 correction factor calculated from the last test (Q2). /// This factors should be used only for R800 meters. /// /// Test result from which to calculate the factor /// The calculated Q2 correction factor /// true = OK, false = failed public bool CalculateQ2CorrectionFactor(Results.Entities.MeterTestRslt q2TestResult, out double q2CorrectionFactor) { q2CorrectionFactor = 0; if (q2TestResult == null) { return false; /// Q2 test result is missing ==> water meter failed } if (Math.Abs(q2TestResult.Error) <= 0.5) { return true; /// error < +/-0.5 % ==> no Q2 correction } double A = 16.0 / ScalingFactor(); /// Raw units per ml: DN15=16, DN20=8, DN25=4, DN32=2, DN40=1 const double B = 8.0; /// Raw units per minute, 8 const double C = B * 60.0; /// Raw units per hour, 480 double D = C / A; /// ml correction per hour double F = D / (NominalTestFlow * 10.0); /// Error corrected with 8 Raw Units per minute [%] double G = F / B; /// Error corrected with 1 Raw Unit per minute [%] double errLimitLo = q2TestResult.ErrLimLo() + q2TestResult.Uncertainty(); double errLimitHi = q2TestResult.ErrLimHi() - q2TestResult.Uncertainty(); if ((q2TestResult.Error < errLimitLo) || (q2TestResult.Error > errLimitHi)) { return false; /// Q2 error too large ==> water meter failed } double volumeMeterErrLimLo = q2TestResult.VolumeRef * (100.0 + errLimitLo) / 100.0; double volumeMeterErrLimHi = q2TestResult.VolumeRef * (100.0 + errLimitHi) / 100.0; double corrFactorHi = (-1) * (errLimitLo / G) * (q2TestResult.VolumeRef / volumeMeterErrLimLo); /// > 0 double corrFactorLo = (-1) * (errLimitHi / G) * (q2TestResult.VolumeRef / volumeMeterErrLimHi); /// < 0 q2CorrectionFactor = (-1) * (q2TestResult.Error / G) * (q2TestResult.VolumeRef / q2TestResult.VolumeMeter); log.WarnFormat("Q2 correction: Pos={0}, PCB#={1}, corrFactor={2}, error={3}% [Lo={4}%, Hi={5}%]", Name, SerialNr, q2CorrectionFactor.ToString("F1"), q2TestResult.Error.ToString("F2"), errLimitLo.ToString("F1"), errLimitHi.ToString("F1")); return true; } /// /// 2 Hz correction factor calculated from two Q3 tests - done at 2Hz and at 8Hz. /// This factors should be used only for DN32 and DN40 meters. /// /// Test result @2Hz from which to calculate the factor /// Test result @8Hz from which to calculate the factor /// The calculated Q2 correction factor /// true = OK, false = failed public bool Calculate2HzCorrectionFactor(Results.Entities.MeterTestRslt resultAt2Hz, Results.Entities.MeterTestRslt resultAt8Hz, out double diff2Hz8Hz, out int hz2CorrectionFactor) { hz2CorrectionFactor = 0; diff2Hz8Hz = 0; if ((resultAt2Hz == null) || (resultAt8Hz == null)) { return false; /// Test result @2Hz and/or @8Hz is missing ==> water meter failed } diff2Hz8Hz = resultAt2Hz.Error - resultAt8Hz.Error; if (Math.Abs(diff2Hz8Hz) > 2.5) return false; /// Difference of errors > 2.5 % ==> water meter failed hz2CorrectionFactor = -1 * (int)Math.Round(10 * diff2Hz8Hz); log.WarnFormat("2Hz correction: Pos={0}, PCB#={1}, corrFactor={2}, erro@2Hz={3}%, erro@8Hz={4}%", Name, SerialNr, hz2CorrectionFactor, resultAt2Hz.Error.ToString("F2"), resultAt8Hz.Error.ToString("F2")); return true; } /// Name set by the test, to be used as a part of the opto-data log file name public string TestName; /// 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 /// bool lastVolumeRawValid; /// true = valid private Int32 lastVolumeRaw; /// Last read raw volume private double volumeLtr; /// private double volumeLtr0; public double VolumeLtrStart; /// Test start volume for metrology public double VolumeLtrEnd; /// Test end volume for metrology public double VolumeLtrRef; /// Reference volume or 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 telegramIx; public int TestStartTelegramIx; public int TestEndTelegramIx; OptoTelegramRaw[] optoData; const int MaxOptoDataCount = 40000; int optoDataCount; string optoDataLogFileName; OptoTelegramRaw toBeFlushed; int flushedDataCount; public int FlushedDataCount { get { return flushedDataCount; } set { flushedDataCount = lastFlushedDataCount = lastFlushedDataCount_1 = value; } } int lastFlushedDataCount_1; int lastFlushedDataCount; public int FlushedDataDelta { get { int retval = Math.Max(flushedDataCount - lastFlushedDataCount, lastFlushedDataCount - lastFlushedDataCount_1); lastFlushedDataCount_1 = lastFlushedDataCount; lastFlushedDataCount = flushedDataCount; return retval; } } /// /// Opto serial port and worker thread related private variables /// private SerialPort optoSerialPort; public WaterMeter() { ClearData(); } public WaterMeter(Generic.IComponentCfg cfg) : base(cfg) { ClearData(); iPerlCfg = cfg as WaterMeterCfg; ltrsPerPulse = (iPerlCfg.ProcParams.PulsesPerLtr <= float.Epsilon) ? 0 : (1 / iPerlCfg.ProcParams.PulsesPerLtr); log.Debug(this.ToString()); } /// /// Clear data related to a specific water meter /// public void ClearData() { disabled = false; commFailed = false; endState = string.Empty; beginState = string.Empty; configStruct = null; calibrationStruct = null; LastTestResult = null; NominalTestFlow = 0; OriginalCalibFactor = 0; Q2ErrorWOCorrection = 0; Q2CorrectionDone = false; Q2CorrectionFactor = 0; optoDataCount = 0; } public void Initialize() { ClearData(); if (DebugLevel == DebugMode.Normal) { optoSerialPortParsingEnabled = false; /// Allocate memory for opto-data from iPerl optoData = new OptoTelegramRaw[MaxOptoDataCount]; for (int i = 0; i < MaxOptoDataCount; i++) optoData[i] = new OptoTelegramRaw(); toBeFlushed = new OptoTelegramRaw(); flushedDataCount = 0; synchronized = false; synchronized2 = false; partOfTelegram = string.Empty; /// Prepare serial port optoSerialPort = new SerialPort(string.Format("COM{0}", iPerlCfg.OptoComPortNr), 9600, Parity.None, 8, StopBits.One); optoSerialPort.Handshake = Handshake.None; optoSerialPort.Open(); } } public void RunDeviceBefore() { if (DebugLevel == DebugMode.Normal) { try { if (optoSerialPortParsingEnabled) ReadOptoData(OptoState.Read); else ReadOptoData(OptoState.Flush); } 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 && optoSerialPort != null) { optoSerialPort.Close(); optoSerialPort = null; } } catch { } } /// /// 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 optoDataCount = 0; TestStartTelegramIx = 0; TestEndTelegramIx = 0; /// File name is: PCB_AA_BB_HH_MI_SS..txt string wmPosition = Name.Substring(5); if (wmPosition.Length == 1) wmPosition = "0" + wmPosition; string flowNr; if (TestName != null && TestName.ToLower().Contains("q1")) flowNr = "01"; else if (TestName != null && TestName.ToLower().Contains("q2")) flowNr = "02"; else if (TestName != null && TestName.ToLower().Contains("q3")) flowNr = "03"; else if (TestName != null && TestName.ToLower().Contains("adj")) flowNr = "0-1"; else flowNr = "99"; optoDataLogFileName = string.Format("{0}_{1}_{2}_{3}.txt", (ConfigStruct != null) ? ConfigStruct.PCBNumber2String() : "UnknownPcbNr", wmPosition, flowNr, StateMachine.CycleStartTimeStamp.ToString("HH_mm_ss")); StartParsingOptoSerialPort(); } /// Run this operation /// eventDone public Event Run() { sampleNr++; ReadPulses(); if (sampleNr == 4) { /// Take the test start sample VolumeLtrStart = volumeLtr; timestampSecStart = timestampSec; TestStartTelegramIx = telegramIx; } /// Shift data in pipelines VolumeLtrEnd = volumeLtrEnd3; volumeLtrEnd3 = volumeLtrEnd2; volumeLtrEnd2 = volumeLtrEnd1; volumeLtrEnd1 = volumeLtr; timestampSecEnd = timestampSecEnd3; timestampSecEnd3 = timestampSecEnd2; timestampSecEnd2 = timestampSecEnd1; timestampSecEnd1 = timestampSec; TestEndTelegramIx = telegramIx - 3; return Event.ReadRegisterDone; } /// Stop this operation public void Stop() { if (TestStartTelegramIx > 0 && TestStartTelegramIx < optoData.Length && optoData[TestStartTelegramIx].Flags == OptoTelegramFlags.OK) { optoData[TestStartTelegramIx].Flags = OptoTelegramFlags.OK_TestStart; OptoTelegramRaw.TestStartTimestampDec = optoData[TestStartTelegramIx].TimestampDec(); } if (TestEndTelegramIx > 0 && TestEndTelegramIx < optoData.Length && optoData[TestEndTelegramIx].Flags == OptoTelegramFlags.OK) { optoData[TestEndTelegramIx].Flags = OptoTelegramFlags.OK_TestEnd; } StopParsingOptoSerialPort(); OptoTelegramRaw.FIRFilterFlow(optoData, optoDataCount); SaveOptoData(); } void SaveOptoData() { string directory = string.Format("C:\\TBF\\ProcessData\\{0}\\{1}\\{2}\\", StateMachine.CycleStartTimeStamp.ToString("yy"), StateMachine.CycleStartTimeStamp.ToString("MM"), StateMachine.CycleStartTimeStamp.ToString("dd")); try { Directory.CreateDirectory(directory); double scalFact = ScalingFactor(); using (TextWriter optoLogFile = new StreamWriter(directory + optoDataLogFileName)) { optoLogFile.WriteLine(optoData[0].ToString(scalFact, null)); for (int i = 1; i < optoDataCount; i++) optoLogFile.WriteLine(optoData[i].ToString(scalFact, optoData[i - 1])); optoLogFile.Close(); } } catch (Exception exc) { MessageBox.Show(Strings.Error_writing_into_file_ + Environment.NewLine + string.Format("{0}\\{1}\r\n{2}", directory, optoDataLogFileName, exc.Message), Strings.Error, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } void ReadPulses() { beginWMState = volumeLtr0; endWMState = volumeLtr; wmVolume = endWMState - beginWMState; wmPulses = (int)(wmVolume * (double)PulsesPerLtr + 0.5); wmRefPulses = StateMachine.ControlBoard.EtPulses(0); wmTestTime = timestampSec - timestampSec0; } bool optoSerialPortParsingEnabled; /// Flush internal buffers and start parsing the opto serial port data void StartParsingOptoSerialPort() { optoSerialPortParsingEnabled = true; } /// Stop parsig the opto serial port data void StopParsingOptoSerialPort() { optoSerialPortParsingEnabled = 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 ReadOptoData(OptoState optoState) { int nrBytes = optoSerialPort.BytesToRead; if (nrBytes > 0) { char[] buffer = new char[nrBytes]; optoSerialPort.Read(buffer, 0, nrBytes); string received = new string(buffer); 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 (optoState == OptoState.Read) { if (pos < OptoTelegramRaw.Length - 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) { optoData[optoDataCount].Counter = optoDataCount; optoData[optoDataCount++].SetFlags(OptoTelegramFlags.SyncError); } synchronized = true; } // CR+LF found and (pos >= OptoTelegramRaw.Length - 2) else if (optoData[optoDataCount].UpdateFromString(allRcvd.Substring(pos - OptoTelegramRaw.Length + 2), optoDataCount)) { OptoTelegramRreceived(optoDataCount++, synchronized2); synchronized2 = synchronized; allRcvd = allRcvd.Substring(pos + 2); } else { optoData[optoDataCount].Counter = optoDataCount; optoDataCount++; allRcvd = allRcvd.Substring(pos + 2); } } else /// optoState == OptoState.Flush { if (pos < OptoTelegramRaw.Length - 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.UpdateFromString(allRcvd.Substring(pos - OptoTelegramRaw.Length + 2), 0)) { flushedDataCount++; synchronized2 = synchronized; allRcvd = allRcvd.Substring(pos + 2); } else { allRcvd = allRcvd.Substring(pos + 2); } } } } //OnOptoReceived(this, new OptoReceivedEventArgs(s)); } else { //OnOptoReceived(this, new OptoReceivedEventArgs(".")); } } void OptoTelegramRreceived(int currentIx, bool async) { OptoTelegramRaw optoTelegram = optoData[currentIx]; telegramIx = currentIx; Int32 uncorrectedRawVolume = 0; if (!lastVolumeRawValid) { lastVolumeRaw = optoTelegram.VolumeRaw; lastVolumeRawValid = true; } else { uncorrectedRawVolume = (Int32)((lastVolumeRaw & 0x7F000000) | (optoTelegram.VolumeRaw & 0x00FFFFFF)); if (Math.Abs(uncorrectedRawVolume - lastVolumeRaw) <= 0x007FFFFF) { lastVolumeRaw = uncorrectedRawVolume; } else if (Math.Abs(uncorrectedRawVolume + 0x01000000 - lastVolumeRaw) <= 0x007FFFFF) { lastVolumeRaw = uncorrectedRawVolume + 0x01000000; } else if (Math.Abs(uncorrectedRawVolume - 0x01000000 - lastVolumeRaw) <= 0x007FFFFF) { lastVolumeRaw = uncorrectedRawVolume - 0x01000000; } else { lastVolumeRaw = uncorrectedRawVolume; /// This should never happen } } Int64 uncorrectedTimestamp = 0; if (!lastTimestampValid) { lastTimestamp = optoTelegram.Timestamp; lastTimestampValid = true; } else { uncorrectedTimestamp = (Int64)((lastTimestamp & 0x7FFFFFFF00000000) | (optoTelegram.Timestamp & 0xFFFFFFFF)); if (Math.Abs(uncorrectedTimestamp - lastTimestamp) <= 0x7FFFFFFF) { lastTimestamp = uncorrectedTimestamp; } else if (Math.Abs(uncorrectedTimestamp + 0x100000000 - lastTimestamp) <= 0x7FFFFFFF) { lastTimestamp = uncorrectedTimestamp + 0x100000000; } else if (Math.Abs(uncorrectedTimestamp - 0x100000000 - lastTimestamp) <= 0x7FFFFFFF) { lastTimestamp = uncorrectedTimestamp - 0x100000000; } else { lastTimestamp = uncorrectedTimestamp; /// This should never happen } } if (volumeLtr == 0 && volumeLtr0 == 0) { volumeLtr = (double)lastVolumeRaw * ScalingFactor() / 16000.0; volumeLtr0 = volumeLtr; } else { volumeLtr = (double)lastVolumeRaw * ScalingFactor() / 16000.0; } if (timestampSec == 0 && timestampSec0 == 0) { timestampSec = (double)lastTimestamp / 8192.0; timestampSec0 = timestampSec; } else { timestampSec = (double)lastTimestamp / 8192.0; } //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 OnOptoReceived(object sender, OptoReceivedEventArgs args) { if (OptoReceivedHandler == null) return; try { OptoReceivedHandler(sender, args); } catch (Exception) { } } public event EventHandler OptoReceivedHandler; public static double UnitVolume(VolumeUnits units) { switch (units) { default: case VolumeUnits.m3: return 1000.0; /// liter case VolumeUnits.UK_gallon: return 4.546092; /// liter case VolumeUnits.US_gallon: return 3.785412; // liter } } public double ScalingFactor() { if ((iPerlCfg.MeterType == MeterType.AutoDetect) && (CalibrationStruct != null)) { return WaterMeter.ScalingFactor(CalibrationStruct.MeterType); } else if (iPerlCfg.MeterType != MeterType.AutoDetect) { return WaterMeter.ScalingFactor(iPerlCfg.MeterType); } else { return WaterMeter.ScalingFactor(MeterType.DN20); } } /// /// Scaling factor: /// 0, 1 (DN15, Coax) . . . . 1 /// 2 (DN20) . . . . . . . . 2 /// 3 (DN25) . . . . . . . . 4 /// 4, 5 (DN26, DN32) . . . . 8 /// 6 (DN40) . . . . . . . . 16 /// /// MeterType (0..6) /// Scaling factor public static double ScalingFactor(MeterType meterType) { switch (meterType) { default: case MeterType.DN15: case MeterType.CoaxManifold: return 1.0; case MeterType.DN20: return 2.0; case MeterType.DN25: return 4.0; case MeterType.DN26: case MeterType.DN32: return 8.0; case MeterType.DN40: return 16.0; } } int PositionNrFormWMName(string name) { int len = name.Length; if (len < 2) return 0; int loNr = (int)name[len - 1] - (int)'0'; int hiNr = (int)name[len - 2] - (int)'0'; if (hiNr < 1 || hiNr > 4) hiNr = 0; return 10 * hiNr + loNr; } } }