Introduce configurable timeouts for data entry volume reads in `IRegReaderSmart` and its implementations: added optional timeout parameters and updated default values across associated forms and methods. Updated `EntryFormCfg` to support customizable timeout settings.
798 lines
26 KiB
C#
798 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Ports;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Common;
|
|
using Config.Entities;
|
|
using log4net;
|
|
using TBF.Rig.Generic;
|
|
using TBF.Rig.GenericDevices;
|
|
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
|
|
|
namespace TBF.Rig.RegisterReaders.AllyReader
|
|
{
|
|
public class AllyMeterReader : ComponentBase,
|
|
IDevice,
|
|
IRegReaderDatastream,
|
|
ISessionDataMngmnt,
|
|
IOperation,
|
|
IRegReaderSmart,
|
|
ISmartReader
|
|
{
|
|
private const int DataEntryCommandTimeoutMs = 5000;
|
|
private const int DefaultDataEntryOpticalTimeoutMs = 3000;
|
|
private const int MaxStoredSamples = 40000;
|
|
private const long RawVolumeModulo = 0x1000000L;
|
|
private const long RawVolumeHalfRange = RawVolumeModulo / 2;
|
|
private const long RawTimestampModulo = 0x100000000L;
|
|
private const long RawTimestampHalfRange = RawTimestampModulo / 2;
|
|
private static readonly ILog log = LogManager.GetLogger(typeof(AllyMeterReader));
|
|
|
|
private readonly object commandSync = new object();
|
|
private readonly object opticalSync = new object();
|
|
private readonly StringBuilder opticalBuffer = new StringBuilder();
|
|
private readonly List<AllyOpticalSample> opticalSamples = new List<AllyOpticalSample>();
|
|
|
|
private readonly AllyReaderCfg allyCfg;
|
|
private IAllyTransport commandTransport;
|
|
private AllyCommandService commandService;
|
|
private SerialPort opticalPort;
|
|
private bool streamEnabled;
|
|
private bool operationActive;
|
|
private bool hasPreviousRawVolume;
|
|
private bool hasPreviousRawTimestamp;
|
|
private bool hasTestStartSample;
|
|
private uint previousRawVolume;
|
|
private uint previousRawTimestamp;
|
|
private long extendedRawVolume;
|
|
private long extendedRawTimestamp;
|
|
private double beginWMState;
|
|
private double endWMState;
|
|
private double timestampSecStart;
|
|
private double timestampSecEnd;
|
|
private string lastOpticalLine;
|
|
|
|
public AllyMeterReader()
|
|
{
|
|
}
|
|
|
|
public AllyMeterReader(IComponentCfg cfg)
|
|
: base(cfg)
|
|
{
|
|
allyCfg = cfg as AllyReaderCfg;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format("{0}({1})", ClassName, Cfg.ToString(-1));
|
|
}
|
|
|
|
public string SerialNumber { get; private set; }
|
|
public AllyVersionInfo VersionInfo { get; private set; }
|
|
public DateTime? SystemTimeUtc { get; private set; }
|
|
public byte? RebootCount { get; private set; }
|
|
public double? CalibrationFactorPercent { get; private set; }
|
|
public double? ExpectedCalibrationFactorPercent { get; private set; }
|
|
public bool CommFailed { get; set; }
|
|
public bool Disabled { get; set; }
|
|
|
|
public string SerialNr
|
|
{
|
|
get { return SerialNumber ?? string.Empty; }
|
|
set { SerialNumber = value ?? string.Empty; }
|
|
}
|
|
|
|
public string CommInterface { get { return "Touch-Read"; } }
|
|
public int RfidComPortNr { get { return allyCfg == null ? 0 : allyCfg.CommandComPortNr; } }
|
|
|
|
public AllyMeterSize ConfiguredMeterSize
|
|
{
|
|
get { return allyCfg == null ? AllyMeterSize.AutoDetect : allyCfg.ConfiguredMeterSize; }
|
|
}
|
|
|
|
public IReadOnlyList<AllyOpticalSample> OpticalSamples
|
|
{
|
|
get
|
|
{
|
|
lock (opticalSync)
|
|
{
|
|
return opticalSamples.ToArray();
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Initialize()
|
|
{
|
|
if (allyCfg == null)
|
|
throw new InvalidOperationException("ALLY reader configuration is missing.");
|
|
|
|
if (allyCfg.CommandComPortNr <= 0 || allyCfg.OptoComPortNr <= 0)
|
|
throw new InvalidOperationException("ALLY COM port configuration is invalid.");
|
|
|
|
PulsesPerLtr = allyCfg.MeterPulsesPerLiter;
|
|
QuantityUnits = "l";
|
|
log.FatalFormat("{0} initialized: {1}", Name, this);
|
|
}
|
|
|
|
public void RunDeviceBefore()
|
|
{
|
|
try
|
|
{
|
|
string text;
|
|
lock (opticalSync)
|
|
{
|
|
if (!streamEnabled || opticalPort == null || !opticalPort.IsOpen)
|
|
return;
|
|
|
|
text = opticalPort.ReadExisting();
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(text))
|
|
ProcessOpticalText(text);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CommFailed = true;
|
|
log.Error("ALLY optical stream read failed.", ex);
|
|
}
|
|
}
|
|
|
|
public void RunDeviceAfter()
|
|
{
|
|
}
|
|
|
|
public void StopDevice()
|
|
{
|
|
StopDataStreamProcessing();
|
|
CloseCommandTransport();
|
|
}
|
|
|
|
public void StopDevice2()
|
|
{
|
|
}
|
|
|
|
public void StartSession()
|
|
{
|
|
lock (opticalSync)
|
|
{
|
|
operationActive = false;
|
|
opticalSamples.Clear();
|
|
opticalBuffer.Clear();
|
|
lastOpticalLine = string.Empty;
|
|
ResetVolumeState();
|
|
}
|
|
|
|
SerialNumber = string.Empty;
|
|
VersionInfo = null;
|
|
SystemTimeUtc = null;
|
|
RebootCount = null;
|
|
CalibrationFactorPercent = null;
|
|
ExpectedCalibrationFactorPercent = null;
|
|
CommFailed = false;
|
|
}
|
|
|
|
public void SaveMark(object mark)
|
|
{
|
|
}
|
|
|
|
public void EndSession()
|
|
{
|
|
StopDataStreamProcessing();
|
|
CloseCommandTransport();
|
|
}
|
|
|
|
public void TestIsGoingToStartSoon(Test test, int repetitionNr)
|
|
{
|
|
}
|
|
|
|
public IOperation ReadDatastreamOp()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
lock (opticalSync)
|
|
{
|
|
operationActive = true;
|
|
hasTestStartSample = false;
|
|
beginWMState = 0;
|
|
endWMState = 0;
|
|
timestampSecStart = 0;
|
|
timestampSecEnd = 0;
|
|
}
|
|
|
|
StartDataStreamProcessing();
|
|
}
|
|
|
|
public Event Run()
|
|
{
|
|
return Event.ReadRegisterDone;
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
lock (opticalSync)
|
|
{
|
|
operationActive = false;
|
|
}
|
|
StopDataStreamProcessing();
|
|
}
|
|
|
|
public void StartDataStreamProcessing()
|
|
{
|
|
GetOpticalVolumeLitersPerRawUnit();
|
|
|
|
lock (opticalSync)
|
|
{
|
|
if (streamEnabled)
|
|
return;
|
|
|
|
opticalSamples.Clear();
|
|
opticalBuffer.Clear();
|
|
lastOpticalLine = string.Empty;
|
|
ResetVolumeState();
|
|
if (DebugLevel == DebugMode.Normal)
|
|
{
|
|
opticalPort = new SerialPort(
|
|
"COM" + allyCfg.OptoComPortNr,
|
|
allyCfg.OptoBaudRate,
|
|
Parity.None,
|
|
8,
|
|
StopBits.One);
|
|
opticalPort.Open();
|
|
opticalPort.DiscardInBuffer();
|
|
}
|
|
|
|
streamEnabled = true;
|
|
}
|
|
}
|
|
|
|
public void StopDataStreamProcessing()
|
|
{
|
|
lock (opticalSync)
|
|
{
|
|
streamEnabled = false;
|
|
if (opticalPort == null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
if (opticalPort.IsOpen)
|
|
opticalPort.Close();
|
|
}
|
|
finally
|
|
{
|
|
opticalPort.Dispose();
|
|
opticalPort = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public string ReadOptoData()
|
|
{
|
|
lock (opticalSync)
|
|
{
|
|
return lastOpticalLine ?? string.Empty;
|
|
}
|
|
}
|
|
|
|
public string ReadSerialNumber(int timeoutMs)
|
|
{
|
|
if (DebugLevel != DebugMode.Normal)
|
|
return SerialNumber = "ALLY-SIMULATED";
|
|
|
|
return ExecuteCommand(service => SerialNumber = service.ReadSerialNumber(timeoutMs));
|
|
}
|
|
|
|
public Task<string> DataEntry_ReadSerialNumber()
|
|
{
|
|
if (!string.IsNullOrEmpty(SerialNumber))
|
|
return Task.FromResult(SerialNumber);
|
|
|
|
return Task.Run(() =>
|
|
{
|
|
try
|
|
{
|
|
return ReadSerialNumber(DataEntryCommandTimeoutMs);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CommFailed = true;
|
|
log.ErrorFormat("ALLY Data Entry serial-number read failed: {0}", ex.Message);
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
public Task<double> DataEntry_ReadBeginVolume(int timeoutMs = DefaultDataEntryOpticalTimeoutMs)
|
|
{
|
|
return ReadDataEntryVolume(true, timeoutMs);
|
|
}
|
|
|
|
public Task<double> DataEntry_ReadEndVolume(int timeoutMs = DefaultDataEntryOpticalTimeoutMs)
|
|
{
|
|
return ReadDataEntryVolume(false, timeoutMs);
|
|
}
|
|
|
|
public AllyVersionInfo ReadVersionAndType(int timeoutMs)
|
|
{
|
|
if (DebugLevel != DebugMode.Normal)
|
|
return VersionInfo = AllyVersionInfo.Parse("SIMULATED,SWM003,SIMULATED");
|
|
|
|
return ExecuteCommand(service => VersionInfo = service.ReadVersionAndType(timeoutMs));
|
|
}
|
|
|
|
public DateTime ReadSystemTimeUtc(int timeoutMs)
|
|
{
|
|
if (DebugLevel != DebugMode.Normal)
|
|
return (SystemTimeUtc = DateTime.UtcNow).Value;
|
|
|
|
return ExecuteCommand(service => (SystemTimeUtc = service.ReadSystemTimeUtc(timeoutMs)).Value);
|
|
}
|
|
|
|
public byte ReadRebootCount(int timeoutMs)
|
|
{
|
|
if (DebugLevel != DebugMode.Normal)
|
|
return (RebootCount = 0).Value;
|
|
|
|
return ExecuteCommand(service => (RebootCount = service.ReadRebootCount(timeoutMs)).Value);
|
|
}
|
|
|
|
public void OpenValve(int timeoutMs)
|
|
{
|
|
ExecuteCommand(service => service.OpenValve(timeoutMs));
|
|
}
|
|
|
|
public void SetSpreadSpectrum(bool enabled, int timeoutMs)
|
|
{
|
|
ExecuteCommand(service => service.SetSpreadSpectrum(enabled, timeoutMs));
|
|
}
|
|
|
|
public void SetMeterMode(byte mode, int timeoutMs)
|
|
{
|
|
ExecuteCommand(service => service.SetMeterMode(mode, timeoutMs));
|
|
}
|
|
|
|
public void SetDiagnosticLed(byte mode, int timeoutMs)
|
|
{
|
|
ExecuteCommand(service => service.SetDiagnosticLed(mode, timeoutMs));
|
|
}
|
|
|
|
public double ResetCalibrationFactor(int timeoutMs)
|
|
{
|
|
double factor = GetResetCalibrationFactorPercent();
|
|
ExecuteCommand(service => service.SetCalibrationFactorPercent(factor, timeoutMs));
|
|
ExpectedCalibrationFactorPercent = factor;
|
|
return factor;
|
|
}
|
|
|
|
public double ReadCalibrationFactor(int timeoutMs)
|
|
{
|
|
if (DebugLevel != DebugMode.Normal)
|
|
{
|
|
double simulated = ExpectedCalibrationFactorPercent ?? GetResetCalibrationFactorPercent();
|
|
CalibrationFactorPercent = simulated;
|
|
return simulated;
|
|
}
|
|
|
|
return ExecuteCommand(
|
|
service => (CalibrationFactorPercent =
|
|
service.ReadCalibrationFactorPercent(timeoutMs)).Value);
|
|
}
|
|
|
|
public void SetCalibrationFactor(double factorPercent, int timeoutMs)
|
|
{
|
|
if (!IsCalibrationFactorWithinSpecification(factorPercent))
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(factorPercent),
|
|
"ALLY calibration factor is outside the UI-2093 limit for the configured meter size.");
|
|
}
|
|
|
|
ExecuteCommand(service => service.SetCalibrationFactorPercent(factorPercent, timeoutMs));
|
|
ExpectedCalibrationFactorPercent = factorPercent;
|
|
}
|
|
|
|
public void SetMagneticTamperProfile(AllyMagneticTamperProfile profile, int timeoutMs)
|
|
{
|
|
ExecuteCommand(service => service.SetMagneticTamperProfile(profile, timeoutMs));
|
|
}
|
|
|
|
public void SetDisplayVolume(string eightDigitVolume, int timeoutMs)
|
|
{
|
|
ExecuteCommand(service => service.SetDisplayVolume(eightDigitVolume, timeoutMs));
|
|
}
|
|
|
|
public void SetLcdTimeout(byte seconds, int timeoutMs)
|
|
{
|
|
ExecuteCommand(service => service.SetLcdTimeout(seconds, timeoutMs));
|
|
}
|
|
|
|
public void StartOffsetLearning(ushort samples, ushort delaySeconds, int timeoutMs)
|
|
{
|
|
ExecuteCommand(service => service.StartOffsetLearning(samples, delaySeconds, timeoutMs));
|
|
}
|
|
|
|
public double GetResetCalibrationFactorPercent()
|
|
{
|
|
switch (ConfiguredMeterSize)
|
|
{
|
|
case AllyMeterSize.FiveEighths:
|
|
case AllyMeterSize.ThreeQuarterShort:
|
|
case AllyMeterSize.ThreeQuarterLong:
|
|
return 100D;
|
|
case AllyMeterSize.OneInch:
|
|
return 260D;
|
|
default:
|
|
throw new InvalidOperationException(
|
|
"ALLY meter size is AutoDetect. UI-2031 serial-number parsing is required before selecting the reset calibration factor.");
|
|
}
|
|
}
|
|
|
|
public bool IsCalibrationFactorWithinSpecification(double value)
|
|
{
|
|
switch (ConfiguredMeterSize)
|
|
{
|
|
case AllyMeterSize.FiveEighths: return value >= 76D && value <= 96D;
|
|
case AllyMeterSize.ThreeQuarterShort: return value >= 88D && value <= 108D;
|
|
case AllyMeterSize.ThreeQuarterLong: return value >= 89D && value <= 120D;
|
|
case AllyMeterSize.OneInch: return value >= 236D && value <= 281D;
|
|
default:
|
|
throw new InvalidOperationException(
|
|
"ALLY meter size is AutoDetect. Calibration-factor limits cannot be selected safely.");
|
|
}
|
|
}
|
|
|
|
public RegisterReaderType RegisterReaderType { get { return RegisterReaderType.DataStream; } }
|
|
|
|
// ALLY heads have independent ports. Separate subgroups keep Data Entry reads
|
|
// deterministic while retaining the common smart-reader scheduling contract.
|
|
public int Group { get { return 1; } }
|
|
public int MuxBoardNrOrGroup14 { get { return Position; } }
|
|
|
|
public int Position
|
|
{
|
|
get
|
|
{
|
|
if (string.IsNullOrEmpty(Name))
|
|
return 0;
|
|
|
|
int start = Name.Length;
|
|
while (start > 0 && char.IsDigit(Name[start - 1]))
|
|
start--;
|
|
|
|
int value;
|
|
return start < Name.Length && int.TryParse(Name.Substring(start), out value) ? value : 0;
|
|
}
|
|
}
|
|
|
|
public double PulsesPerLtr { get; set; }
|
|
public double LtrsPerPulse { get { return PulsesPerLtr > 0 ? 1D / PulsesPerLtr : 0D; } }
|
|
public string QuantityUnits { get; set; }
|
|
public int WMPulses { get { return (int)Math.Round(WMVolume * PulsesPerLtr); } }
|
|
public int WMRefPulses
|
|
{
|
|
get
|
|
{
|
|
return StateMachine.ControlBoardMain == null
|
|
? 0
|
|
: StateMachine.ControlBoardMain.RefPulses;
|
|
}
|
|
}
|
|
public double WMVolume { get { return Math.Abs(EndWMState - BeginWMState); } }
|
|
public double BeginWMState
|
|
{
|
|
get { return beginWMState; }
|
|
set { beginWMState = value; }
|
|
}
|
|
public double EndWMState
|
|
{
|
|
get { return endWMState; }
|
|
set { endWMState = value; }
|
|
}
|
|
public bool NoSamples { get { return !hasTestStartSample || opticalSamples.Count < 2; } }
|
|
public double VolumeLtrStart { get { return beginWMState; } }
|
|
public double VolumeLtrEnd { get { return endWMState; } }
|
|
public double TimestampSecStart { get { return timestampSecStart; } }
|
|
public double TimestampSecEnd { get { return timestampSecEnd; } }
|
|
|
|
public void ResetNfcInterface(bool? nfc_on = null)
|
|
{
|
|
log.DebugFormat(
|
|
"ALLY {0}: ResetNfcInterface({1}) ignored; command interface is fixed to Touch-Read.",
|
|
Name,
|
|
nfc_on.HasValue ? nfc_on.Value.ToString() : "null");
|
|
}
|
|
|
|
public void SetNfcInterface()
|
|
{
|
|
log.DebugFormat(
|
|
"ALLY {0}: SetNfcInterface ignored; command interface is fixed to Touch-Read.",
|
|
Name);
|
|
}
|
|
|
|
public void SetRfidInterface()
|
|
{
|
|
log.DebugFormat(
|
|
"ALLY {0}: SetRfidInterface ignored; command interface is fixed to Touch-Read.",
|
|
Name);
|
|
}
|
|
|
|
public void SetCommunicationInterface(string commInterface)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(commInterface) &&
|
|
!string.Equals(commInterface, CommInterface, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
log.WarnFormat(
|
|
"ALLY {0}: communication interface '{1}' is not supported; using {2}.",
|
|
Name,
|
|
commInterface,
|
|
CommInterface);
|
|
}
|
|
}
|
|
|
|
public void WriteBinary(BinaryWriter writer)
|
|
{
|
|
if (writer == null)
|
|
throw new ArgumentNullException("writer");
|
|
|
|
writer.Write(Disabled);
|
|
writer.Write(CommFailed);
|
|
writer.Write(SerialNr);
|
|
writer.Write(beginWMState);
|
|
writer.Write(endWMState);
|
|
}
|
|
|
|
public void ReadBinary(BinaryReader reader)
|
|
{
|
|
if (reader == null)
|
|
throw new ArgumentNullException("reader");
|
|
|
|
Disabled = reader.ReadBoolean();
|
|
CommFailed = reader.ReadBoolean();
|
|
SerialNr = reader.ReadString();
|
|
beginWMState = reader.ReadDouble();
|
|
endWMState = reader.ReadDouble();
|
|
}
|
|
|
|
private Task<double> ReadDataEntryVolume(bool isBegin, int timeoutMs)
|
|
{
|
|
return Task.Run(() =>
|
|
{
|
|
if (DebugLevel != DebugMode.Normal)
|
|
{
|
|
return Double.NaN;
|
|
}
|
|
|
|
try
|
|
{
|
|
Start();
|
|
int effectiveTimeoutMs = Math.Max(1, timeoutMs);
|
|
DateTime deadline = DateTime.UtcNow.AddMilliseconds(effectiveTimeoutMs);
|
|
while (DateTime.UtcNow < deadline)
|
|
{
|
|
RunDeviceBefore();
|
|
lock (opticalSync)
|
|
{
|
|
if (hasTestStartSample)
|
|
{
|
|
double volume = isBegin ? beginWMState : endWMState;
|
|
log.DebugFormat(
|
|
"ALLY Data Entry {0} volume read: {1} l",
|
|
isBegin ? "begin" : "end", volume);
|
|
return volume;
|
|
}
|
|
}
|
|
|
|
Thread.Sleep(20);
|
|
}
|
|
|
|
log.WarnFormat(
|
|
"ALLY Data Entry {0} volume timeout after {1} ms on COM{2}",
|
|
isBegin ? "begin" : "end",
|
|
effectiveTimeoutMs,
|
|
allyCfg.OptoComPortNr);
|
|
return Double.NaN;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
CommFailed = true;
|
|
log.ErrorFormat(
|
|
"ALLY Data Entry {0} volume read failed: {1}",
|
|
isBegin ? "begin" : "end", ex.Message);
|
|
return Double.NaN;
|
|
}
|
|
finally
|
|
{
|
|
Stop();
|
|
}
|
|
});
|
|
}
|
|
|
|
private void ProcessOpticalText(string text)
|
|
{
|
|
lock (opticalSync)
|
|
{
|
|
opticalBuffer.Append(text);
|
|
|
|
while (true)
|
|
{
|
|
string buffered = opticalBuffer.ToString();
|
|
int lineEnd = buffered.IndexOf('\n');
|
|
if (lineEnd < 0)
|
|
return;
|
|
|
|
string line = buffered.Substring(0, lineEnd + 1);
|
|
opticalBuffer.Remove(0, lineEnd + 1);
|
|
lastOpticalLine = line;
|
|
|
|
AllyOpticalSample sample;
|
|
if (!AllyOpticalSample.TryParse(line, DateTime.UtcNow, out sample))
|
|
continue;
|
|
|
|
ExtendRawVolume(sample.RawVolume);
|
|
ExtendRawTimestamp(sample.RawTimestamp);
|
|
sample.ExtendedVolumeLiters =
|
|
extendedRawVolume * GetOpticalVolumeLitersPerRawUnit();
|
|
sample.ElapsedSeconds = extendedRawTimestamp / 8192D;
|
|
|
|
if (opticalSamples.Count == MaxStoredSamples)
|
|
opticalSamples.RemoveAt(0);
|
|
opticalSamples.Add(sample);
|
|
|
|
endWMState = sample.ExtendedVolumeLiters;
|
|
timestampSecEnd = sample.ElapsedSeconds;
|
|
|
|
if (operationActive && !hasTestStartSample)
|
|
{
|
|
hasTestStartSample = true;
|
|
beginWMState = sample.ExtendedVolumeLiters;
|
|
timestampSecStart = sample.ElapsedSeconds;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ExtendRawVolume(uint rawVolume)
|
|
{
|
|
if (!hasPreviousRawVolume)
|
|
{
|
|
hasPreviousRawVolume = true;
|
|
previousRawVolume = rawVolume;
|
|
extendedRawVolume = rawVolume;
|
|
return;
|
|
}
|
|
|
|
long delta = (long)rawVolume - previousRawVolume;
|
|
if (delta < -RawVolumeHalfRange)
|
|
delta += RawVolumeModulo;
|
|
else if (delta > RawVolumeHalfRange)
|
|
delta -= RawVolumeModulo;
|
|
|
|
extendedRawVolume += delta;
|
|
previousRawVolume = rawVolume;
|
|
}
|
|
|
|
private void ExtendRawTimestamp(uint rawTimestamp)
|
|
{
|
|
if (!hasPreviousRawTimestamp)
|
|
{
|
|
hasPreviousRawTimestamp = true;
|
|
previousRawTimestamp = rawTimestamp;
|
|
extendedRawTimestamp = rawTimestamp;
|
|
return;
|
|
}
|
|
|
|
long delta = (long)rawTimestamp - previousRawTimestamp;
|
|
if (delta < -RawTimestampHalfRange)
|
|
delta += RawTimestampModulo;
|
|
else if (delta > RawTimestampHalfRange)
|
|
delta -= RawTimestampModulo;
|
|
|
|
extendedRawTimestamp += delta;
|
|
previousRawTimestamp = rawTimestamp;
|
|
}
|
|
|
|
private double GetOpticalVolumeLitersPerRawUnit()
|
|
{
|
|
// The optical format scales volume by flow-tube size: raw / 16000
|
|
// for 5/8", 2 * raw / 16000 for 3/4", and 4 * raw / 16000 for 1".
|
|
switch (ConfiguredMeterSize)
|
|
{
|
|
case AllyMeterSize.FiveEighths: return 1D / 16000D;
|
|
case AllyMeterSize.ThreeQuarterShort:
|
|
case AllyMeterSize.ThreeQuarterLong: return 2D / 16000D;
|
|
case AllyMeterSize.OneInch: return 4D / 16000D;
|
|
default:
|
|
throw new InvalidOperationException(
|
|
"ALLY meter size is AutoDetect. UI-2031 serial-number parsing is required before decoding optical volume.");
|
|
}
|
|
}
|
|
|
|
private void ResetVolumeState()
|
|
{
|
|
hasPreviousRawVolume = false;
|
|
hasPreviousRawTimestamp = false;
|
|
hasTestStartSample = false;
|
|
previousRawVolume = 0;
|
|
previousRawTimestamp = 0;
|
|
extendedRawVolume = 0;
|
|
extendedRawTimestamp = 0;
|
|
beginWMState = 0;
|
|
endWMState = 0;
|
|
timestampSecStart = 0;
|
|
timestampSecEnd = 0;
|
|
}
|
|
|
|
private AllyCommandService GetCommandService()
|
|
{
|
|
lock (commandSync)
|
|
{
|
|
if (commandService != null)
|
|
return commandService;
|
|
|
|
commandTransport = new AllySerialTransportBuilder()
|
|
.WithPort("COM" + allyCfg.CommandComPortNr)
|
|
.WithBaudRate(allyCfg.CommandBaudRate)
|
|
.WithDataBits(8)
|
|
.WithParity(Parity.None)
|
|
.WithStopBits(StopBits.One)
|
|
.Build();
|
|
commandService = new AllyCommandService(commandTransport);
|
|
return commandService;
|
|
}
|
|
}
|
|
|
|
private T ExecuteCommand<T>(Func<AllyCommandService, T> command, T simulatedValue = default(T))
|
|
{
|
|
if (DebugLevel != DebugMode.Normal)
|
|
return simulatedValue;
|
|
|
|
lock (commandSync)
|
|
{
|
|
try
|
|
{
|
|
T value = command(GetCommandService());
|
|
CommFailed = false;
|
|
return value;
|
|
}
|
|
catch
|
|
{
|
|
CommFailed = true;
|
|
CloseCommandTransport();
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ExecuteCommand(Action<AllyCommandService> command)
|
|
{
|
|
if (DebugLevel != DebugMode.Normal)
|
|
return;
|
|
|
|
ExecuteCommand(
|
|
service =>
|
|
{
|
|
command(service);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
private void CloseCommandTransport()
|
|
{
|
|
lock (commandSync)
|
|
{
|
|
if (commandTransport != null)
|
|
commandTransport.Dispose();
|
|
commandTransport = null;
|
|
commandService = null;
|
|
}
|
|
}
|
|
}
|
|
}
|