Ally iPerl initial - reader,test method, unit test
Add AllyReader and AllyCalibration support with tests: Introduce new register readers, calibration methods, and comprehensive unit tests for integration and functionality validation. Update project files accordingly.
This commit is contained in:
parent
c619c7d3b8
commit
c71fe75446
618
TBF/Rig/RegisterReaders/AllyReader/AllyMeterReader.cs
Normal file
618
TBF/Rig/RegisterReaders/AllyReader/AllyMeterReader.cs
Normal file
@ -0,0 +1,618 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using Common;
|
||||
using Config.Entities;
|
||||
using log4net;
|
||||
using TBF.Rig.Generic;
|
||||
using TBF.Rig.GenericDevices;
|
||||
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
{
|
||||
public class AllyMeterReader : ComponentBase,
|
||||
IDevice,
|
||||
IRegReaderDatastream,
|
||||
ISessionDataMngmnt,
|
||||
IOperation
|
||||
{
|
||||
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; private set; }
|
||||
|
||||
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()
|
||||
{
|
||||
if (!streamEnabled || opticalPort == null || !opticalPort.IsOpen)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
string 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 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; } }
|
||||
|
||||
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; } }
|
||||
public double EndWMState { get { return endWMState; } }
|
||||
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; } }
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
TBF/Rig/RegisterReaders/AllyReader/AllyMeterSize.cs
Normal file
11
TBF/Rig/RegisterReaders/AllyReader/AllyMeterSize.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
{
|
||||
public enum AllyMeterSize
|
||||
{
|
||||
AutoDetect,
|
||||
FiveEighths,
|
||||
ThreeQuarterShort,
|
||||
ThreeQuarterLong,
|
||||
OneInch
|
||||
}
|
||||
}
|
||||
81
TBF/Rig/RegisterReaders/AllyReader/AllyOpticalSample.cs
Normal file
81
TBF/Rig/RegisterReaders/AllyReader/AllyOpticalSample.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Validated common portion of the 42-byte optical telegram used by the
|
||||
/// register-reader pattern referenced by UI-2093. ALLY calibration-only
|
||||
/// fields are intentionally not inferred without UI-1204/UI-1236.
|
||||
/// </summary>
|
||||
public sealed class AllyOpticalSample
|
||||
{
|
||||
private const int TelegramLength = 42;
|
||||
|
||||
public string RawLine { get; private set; }
|
||||
public DateTime ReceivedAtUtc { get; private set; }
|
||||
public short RawFlow { get; private set; }
|
||||
public uint RawVolume { get; private set; }
|
||||
public uint RawTimestamp { get; private set; }
|
||||
public double ExtendedVolumeLiters { get; internal set; }
|
||||
public double ElapsedSeconds { get; internal set; }
|
||||
|
||||
private AllyOpticalSample()
|
||||
{
|
||||
}
|
||||
|
||||
public static bool TryParse(
|
||||
string line,
|
||||
DateTime receivedAtUtc,
|
||||
out AllyOpticalSample sample)
|
||||
{
|
||||
sample = null;
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
return false;
|
||||
|
||||
if (line.Length < TelegramLength)
|
||||
return false;
|
||||
|
||||
string telegram = line.Substring(line.Length - TelegramLength, TelegramLength);
|
||||
if (telegram[6] != '\t' || telegram[11] != '\t' || telegram[16] != '\t' ||
|
||||
telegram[23] != '\t' || telegram[28] != '\t' || telegram[37] != '\t' ||
|
||||
telegram[40] != '\r' || telegram[41] != '\n')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ushort rawFlowUnsigned;
|
||||
uint rawVolume;
|
||||
uint rawTimestamp;
|
||||
byte checksum;
|
||||
if (!ushort.TryParse(telegram.Substring(12, 4), NumberStyles.HexNumber,
|
||||
CultureInfo.InvariantCulture, out rawFlowUnsigned) ||
|
||||
!uint.TryParse(telegram.Substring(17, 6), NumberStyles.HexNumber,
|
||||
CultureInfo.InvariantCulture, out rawVolume) ||
|
||||
!uint.TryParse(telegram.Substring(29, 8), NumberStyles.HexNumber,
|
||||
CultureInfo.InvariantCulture, out rawTimestamp) ||
|
||||
!byte.TryParse(telegram.Substring(38, 2), NumberStyles.HexNumber,
|
||||
CultureInfo.InvariantCulture, out checksum))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
byte calculatedChecksum = 0;
|
||||
for (int i = 0; i < TelegramLength - 4; i++)
|
||||
calculatedChecksum += (byte)telegram[i];
|
||||
|
||||
if (calculatedChecksum != checksum || rawVolume > 0xFFFFFF)
|
||||
return false;
|
||||
|
||||
sample = new AllyOpticalSample
|
||||
{
|
||||
RawLine = telegram,
|
||||
ReceivedAtUtc = receivedAtUtc,
|
||||
RawFlow = unchecked((short)rawFlowUnsigned),
|
||||
RawVolume = rawVolume,
|
||||
RawTimestamp = rawTimestamp
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
191
TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfg.cs
Normal file
191
TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfg.cs
Normal file
@ -0,0 +1,191 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Ports;
|
||||
using System.Xml.Serialization;
|
||||
using Common;
|
||||
using TBF.Rig.Generic;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
{
|
||||
public class AllyReaderCfg : ComponentCfgBase, IComponentCfg, IParamsProvider
|
||||
{
|
||||
public static readonly XmlSerializer Serializer =
|
||||
XmlSerializer.FromTypes(new[] { typeof(AllyReaderCfg) })[0];
|
||||
|
||||
public int CommandComPortNr;
|
||||
public int CommandBaudRate;
|
||||
public int OptoComPortNr;
|
||||
public int OptoBaudRate;
|
||||
public AllyMeterSize ConfiguredMeterSize;
|
||||
public double MeterPulsesPerLiter;
|
||||
|
||||
private AllyReaderCfg()
|
||||
{
|
||||
}
|
||||
|
||||
public AllyReaderCfg(IComponentFactory factory)
|
||||
{
|
||||
Factory = factory;
|
||||
Name = "Ally";
|
||||
ParentName = string.Empty;
|
||||
InitializeAll();
|
||||
}
|
||||
|
||||
public override XmlSerializer GetSerializer()
|
||||
{
|
||||
return Serializer;
|
||||
}
|
||||
|
||||
public IComponentCfgCtrl GetControl(IList<Config.Entities.Component> cmpntEntities)
|
||||
{
|
||||
return new Configs.ParamsProvider.ComponentCfgCtrl(this, null);
|
||||
}
|
||||
|
||||
public string ComponentName { get { return Name; } }
|
||||
|
||||
public void InitializeAll()
|
||||
{
|
||||
CommandComPortNr = 1;
|
||||
CommandBaudRate = 2400;
|
||||
OptoComPortNr = 2;
|
||||
OptoBaudRate = 9600;
|
||||
ConfiguredMeterSize = AllyMeterSize.AutoDetect;
|
||||
MeterPulsesPerLiter = 1000D;
|
||||
}
|
||||
|
||||
public int ParamsCount()
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
|
||||
public string ParamName(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return "Touch-Read COM port";
|
||||
case 1: return "Touch-Read baud rate";
|
||||
case 2: return "Optical COM port";
|
||||
case 3: return "Optical baud rate";
|
||||
case 4: return "Configured ALLY meter size";
|
||||
case 5: return "Meter pulses per liter";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<string> ParamValues(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 1:
|
||||
return new[] { "1200", "2400", "4800", "9600" };
|
||||
case 3:
|
||||
return new[] { "9600", "19200", "38400", "57600" };
|
||||
case 4:
|
||||
return new[]
|
||||
{
|
||||
AllyMeterSize.AutoDetect.ToString(),
|
||||
AllyMeterSize.FiveEighths.ToString(),
|
||||
AllyMeterSize.ThreeQuarterShort.ToString(),
|
||||
AllyMeterSize.ThreeQuarterLong.ToString(),
|
||||
AllyMeterSize.OneInch.ToString()
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string ToString(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return CommandComPortNr.ToString();
|
||||
case 1: return CommandBaudRate.ToString();
|
||||
case 2: return OptoComPortNr.ToString();
|
||||
case 3: return OptoBaudRate.ToString();
|
||||
case 4: return ConfiguredMeterSize.ToString();
|
||||
case 5: return MeterPulsesPerLiter.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
default:
|
||||
return string.Format(
|
||||
"{0}: Touch-Read=COM{1}/{2}, Optical=COM{3}/{4}, Size={5}",
|
||||
Name,
|
||||
CommandComPortNr,
|
||||
CommandBaudRate,
|
||||
OptoComPortNr,
|
||||
OptoBaudRate,
|
||||
ConfiguredMeterSize);
|
||||
}
|
||||
}
|
||||
|
||||
public CfgUpdateFlags UpdateParam(int i, string strValue)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: CommandComPortNr = int.Parse(strValue); break;
|
||||
case 1: CommandBaudRate = int.Parse(strValue); break;
|
||||
case 2: OptoComPortNr = int.Parse(strValue); break;
|
||||
case 3: OptoBaudRate = int.Parse(strValue); break;
|
||||
case 4: ConfiguredMeterSize = (AllyMeterSize)System.Enum.Parse(typeof(AllyMeterSize), strValue); break;
|
||||
case 5: MeterPulsesPerLiter = Utils.ParseUDouble(strValue); break;
|
||||
default: return CfgUpdateFlags.None;
|
||||
}
|
||||
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
}
|
||||
|
||||
public bool ValidateParam(int i, string strValue, out string message)
|
||||
{
|
||||
message = string.Empty;
|
||||
int integerValue;
|
||||
double doubleValue;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
case 2:
|
||||
if (int.TryParse(strValue, out integerValue) && integerValue > 0)
|
||||
return true;
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
if (int.TryParse(strValue, out integerValue) && integerValue > 0)
|
||||
return true;
|
||||
break;
|
||||
case 4:
|
||||
AllyMeterSize meterSize;
|
||||
if (System.Enum.TryParse(strValue, out meterSize))
|
||||
return true;
|
||||
break;
|
||||
case 5:
|
||||
if (double.TryParse(strValue, out doubleValue) && doubleValue > 0)
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
message = "Invalid parameter index.";
|
||||
return false;
|
||||
}
|
||||
|
||||
message = ParamName(i) + " is invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool UpdateEmbeddedDbEntity()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public IParamsProvider Clone()
|
||||
{
|
||||
return new AllyReaderCfg
|
||||
{
|
||||
Name = Name,
|
||||
ParentName = ParentName,
|
||||
Factory = Factory,
|
||||
CommandComPortNr = CommandComPortNr,
|
||||
CommandBaudRate = CommandBaudRate,
|
||||
OptoComPortNr = OptoComPortNr,
|
||||
OptoBaudRate = OptoBaudRate,
|
||||
ConfiguredMeterSize = ConfiguredMeterSize,
|
||||
MeterPulsesPerLiter = MeterPulsesPerLiter
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
{
|
||||
/// <summary>
|
||||
/// ALLY commands required by UI-2093 R9.0. The service deliberately omits
|
||||
/// iPerl-only Q2, 2 Hz, flip-mode and RFID structure operations.
|
||||
/// </summary>
|
||||
public sealed class AllyCommandService
|
||||
{
|
||||
private static readonly DateTime SystemTimeEpochUtc =
|
||||
new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
private readonly IAllyTransport transport;
|
||||
private readonly AllyFrameParser parser = new AllyFrameParser();
|
||||
|
||||
public AllyCommandService(IAllyTransport transport)
|
||||
{
|
||||
this.transport = transport ?? throw new ArgumentNullException(nameof(transport));
|
||||
}
|
||||
|
||||
public string ReadSerialNumber(int timeoutMs)
|
||||
{
|
||||
AllyResponse response = Send(
|
||||
new AllyFrameBuilder().WithCommand(AllyCommand.ViewFactoryId).BuildBytes(),
|
||||
timeoutMs);
|
||||
return response.GetNullTerminatedAscii();
|
||||
}
|
||||
|
||||
public AllyVersionInfo ReadVersionAndType(int timeoutMs)
|
||||
{
|
||||
AllyResponse response = Send(
|
||||
new AllyFrameBuilder().WithCommand(AllyCommand.ViewVersionAndType).BuildBytes(),
|
||||
timeoutMs);
|
||||
return AllyVersionInfo.Parse(response.GetNullTerminatedAscii());
|
||||
}
|
||||
|
||||
public DateTime ReadSystemTimeUtc(int timeoutMs)
|
||||
{
|
||||
AllyResponse response = Send(
|
||||
new AllyFrameBuilder().WithDeviceCommand(AllyDeviceCommand.ViewSystemTime).BuildBytes(),
|
||||
timeoutMs);
|
||||
return SystemTimeEpochUtc.AddSeconds(response.GetUInt32LittleEndian());
|
||||
}
|
||||
|
||||
public byte ReadRebootCount(int timeoutMs)
|
||||
{
|
||||
AllyResponse response = Send(
|
||||
new AllyFrameBuilder().WithDeviceCommand(AllyDeviceCommand.ViewRebootCount).BuildBytes(),
|
||||
timeoutMs);
|
||||
return response.GetByte();
|
||||
}
|
||||
|
||||
public void OpenValve(int timeoutMs)
|
||||
{
|
||||
Send(
|
||||
new AllyFrameBuilder()
|
||||
.WithCommand(AllyCommand.SetValvePosition)
|
||||
.WithBytes(0x00, 0x02)
|
||||
.BuildBytes(),
|
||||
timeoutMs);
|
||||
}
|
||||
|
||||
public void SetSpreadSpectrum(bool enabled, int timeoutMs)
|
||||
{
|
||||
Send(
|
||||
new AllyFrameBuilder()
|
||||
.WithDeviceCommand(AllyDeviceCommand.Configuration)
|
||||
.WithBytes(0x06, enabled ? (byte)0x00 : (byte)0x01)
|
||||
.BuildBytes(),
|
||||
timeoutMs);
|
||||
}
|
||||
|
||||
public void SetMeterMode(byte mode, int timeoutMs)
|
||||
{
|
||||
Send(
|
||||
new AllyFrameBuilder()
|
||||
.WithCommand(AllyCommand.SetMeterMode)
|
||||
.WithByte(mode)
|
||||
.BuildBytes(),
|
||||
timeoutMs);
|
||||
}
|
||||
|
||||
public void SetDiagnosticLed(byte mode, int timeoutMs)
|
||||
{
|
||||
Send(
|
||||
new AllyFrameBuilder()
|
||||
.WithDeviceCommand(AllyDeviceCommand.SetDiagnosticLed)
|
||||
.WithByte(mode)
|
||||
.BuildBytes(),
|
||||
timeoutMs);
|
||||
}
|
||||
|
||||
public double ReadCalibrationFactorPercent(int timeoutMs)
|
||||
{
|
||||
AllyResponse response = Send(
|
||||
new AllyFrameBuilder().WithDeviceCommand(AllyDeviceCommand.ViewCalibration).BuildBytes(),
|
||||
timeoutMs);
|
||||
return response.GetUInt16LittleEndian() / 40.96D;
|
||||
}
|
||||
|
||||
public void SetCalibrationFactorPercent(double factorPercent, int timeoutMs)
|
||||
{
|
||||
if (factorPercent <= 0 || factorPercent > ushort.MaxValue / 40.96D)
|
||||
throw new ArgumentOutOfRangeException(nameof(factorPercent));
|
||||
|
||||
ushort rawValue = checked((ushort)Math.Round(
|
||||
factorPercent * 40.96D,
|
||||
MidpointRounding.AwayFromZero));
|
||||
|
||||
Send(
|
||||
new AllyFrameBuilder()
|
||||
.WithDeviceCommand(AllyDeviceCommand.SetCalibration)
|
||||
.WithUInt16LittleEndian(rawValue)
|
||||
.BuildBytes(),
|
||||
timeoutMs);
|
||||
}
|
||||
|
||||
public void SetMagneticTamperProfile(AllyMagneticTamperProfile profile, int timeoutMs)
|
||||
{
|
||||
AllyFrameBuilder builder = new AllyFrameBuilder()
|
||||
.WithDeviceCommand(AllyDeviceCommand.Configuration);
|
||||
|
||||
// Exact UI-2093 frames. The leading byte selects the RCS profile
|
||||
// (0x02) or the firmware-default profile (0x05).
|
||||
if (profile == AllyMagneticTamperProfile.Calibration)
|
||||
builder.WithBytes(0x02, 0x5A, 0x0C, 0x05, 0x3C, 0x50);
|
||||
else
|
||||
builder.WithBytes(0x05, 0x1E, 0x04, 0x05, 0x3C, 0x50);
|
||||
|
||||
Send(builder.BuildBytes(), timeoutMs);
|
||||
}
|
||||
|
||||
public void SetDisplayVolume(string eightDigitVolume, int timeoutMs)
|
||||
{
|
||||
if (eightDigitVolume == null ||
|
||||
eightDigitVolume.Length != 8 ||
|
||||
!eightDigitVolume.All(char.IsDigit))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"ALLY display volume must contain exactly eight digits.",
|
||||
nameof(eightDigitVolume));
|
||||
}
|
||||
|
||||
Send(
|
||||
new AllyFrameBuilder()
|
||||
.WithCommand(AllyCommand.SetPresetTotal)
|
||||
.WithNullTerminatedAscii(eightDigitVolume)
|
||||
.BuildBytes(),
|
||||
timeoutMs);
|
||||
}
|
||||
|
||||
public void SetLcdTimeout(byte seconds, int timeoutMs)
|
||||
{
|
||||
Send(
|
||||
new AllyFrameBuilder()
|
||||
.WithDeviceCommand(AllyDeviceCommand.SetLcdTimeout)
|
||||
.WithByte(seconds)
|
||||
.BuildBytes(),
|
||||
timeoutMs);
|
||||
}
|
||||
|
||||
public void StartOffsetLearning(ushort samples, ushort startDelaySeconds, int timeoutMs)
|
||||
{
|
||||
if (samples == 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(samples));
|
||||
|
||||
Send(
|
||||
new AllyFrameBuilder()
|
||||
.WithDeviceCommand(AllyDeviceCommand.StartOffsetLearning)
|
||||
.WithUInt16LittleEndian(samples)
|
||||
.WithUInt16LittleEndian(startDelaySeconds)
|
||||
.BuildBytes(),
|
||||
timeoutMs);
|
||||
}
|
||||
|
||||
private AllyResponse Send(byte[] request, int timeoutMs)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!transport.IsOpen)
|
||||
transport.Open();
|
||||
|
||||
byte[] rawResponse = transport.SendAndWait(request, timeoutMs);
|
||||
AllyResponse response = parser.ParseResponse(rawResponse);
|
||||
if (!response.IsSuccess)
|
||||
{
|
||||
throw new AllyCommunicationException(
|
||||
"ALLY command failed with status 0x" +
|
||||
response.Status.ToString("X2", CultureInfo.InvariantCulture) + ".",
|
||||
response.Status);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (AllyCommunicationException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new AllyCommunicationException("ALLY communication failed.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
{
|
||||
public sealed class AllyFrame
|
||||
{
|
||||
public byte Direction { get; private set; }
|
||||
public byte[] Information { get; private set; }
|
||||
|
||||
public AllyFrame(byte direction, byte[] information)
|
||||
{
|
||||
Direction = direction;
|
||||
Information = information ?? Array.Empty<byte>();
|
||||
}
|
||||
|
||||
public byte[] ToArray()
|
||||
{
|
||||
int length = 4 + Information.Length;
|
||||
if (length > byte.MaxValue)
|
||||
throw new InvalidOperationException("ALLY frame is too long.");
|
||||
|
||||
byte[] bytes = new byte[length];
|
||||
bytes[0] = AllyProtocol.Start;
|
||||
bytes[1] = Direction;
|
||||
bytes[2] = (byte)length;
|
||||
Buffer.BlockCopy(Information, 0, bytes, 3, Information.Length);
|
||||
bytes[bytes.Length - 1] = AllyProtocol.End;
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds the 0x53 0x57 <length> ... 0x0D request frames used by
|
||||
/// the ALLY Touch-Read commands in UI-2093.
|
||||
/// </summary>
|
||||
public sealed class AllyFrameBuilder
|
||||
{
|
||||
private readonly List<byte> information = new List<byte>();
|
||||
|
||||
internal AllyFrameBuilder WithCommand(AllyCommand command)
|
||||
{
|
||||
EnsureCommandNotSet();
|
||||
information.Add((byte)command);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal AllyFrameBuilder WithDeviceCommand(AllyDeviceCommand command)
|
||||
{
|
||||
EnsureCommandNotSet();
|
||||
information.Add(AllyProtocol.DeviceSpecific);
|
||||
information.Add((byte)command);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AllyFrameBuilder WithByte(byte value)
|
||||
{
|
||||
EnsureCommandSet();
|
||||
information.Add(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AllyFrameBuilder WithBytes(params byte[] values)
|
||||
{
|
||||
EnsureCommandSet();
|
||||
if (values != null)
|
||||
information.AddRange(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AllyFrameBuilder WithUInt16LittleEndian(ushort value)
|
||||
{
|
||||
EnsureCommandSet();
|
||||
information.Add((byte)(value & 0xFF));
|
||||
information.Add((byte)(value >> 8));
|
||||
return this;
|
||||
}
|
||||
|
||||
public AllyFrameBuilder WithNullTerminatedAscii(string value)
|
||||
{
|
||||
EnsureCommandSet();
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
information.AddRange(Encoding.ASCII.GetBytes(value));
|
||||
information.Add(0x00);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AllyFrame Build()
|
||||
{
|
||||
EnsureCommandSet();
|
||||
return new AllyFrame(AllyProtocol.Write, information.ToArray());
|
||||
}
|
||||
|
||||
public byte[] BuildBytes()
|
||||
{
|
||||
return Build().ToArray();
|
||||
}
|
||||
|
||||
private void EnsureCommandSet()
|
||||
{
|
||||
if (information.Count == 0)
|
||||
throw new InvalidOperationException("ALLY command must be set before its payload.");
|
||||
}
|
||||
|
||||
private void EnsureCommandNotSet()
|
||||
{
|
||||
if (information.Count != 0)
|
||||
throw new InvalidOperationException("ALLY frame already contains a command.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
{
|
||||
public sealed class AllyFrameParser
|
||||
{
|
||||
public AllyResponse ParseResponse(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
if (data.Length < 5)
|
||||
throw new FormatException("ALLY response is too short.");
|
||||
if (data[0] != AllyProtocol.Start)
|
||||
throw new FormatException("ALLY response has an invalid start byte.");
|
||||
if (data[1] != AllyProtocol.Read)
|
||||
throw new FormatException("ALLY frame is not a response.");
|
||||
if (data[2] != data.Length)
|
||||
throw new FormatException("ALLY response length does not match its length field.");
|
||||
if (data[data.Length - 1] != AllyProtocol.End)
|
||||
throw new FormatException("ALLY response has an invalid end byte.");
|
||||
|
||||
byte[] payload = new byte[data.Length - 5];
|
||||
if (payload.Length > 0)
|
||||
Buffer.BlockCopy(data, 4, payload, 0, payload.Length);
|
||||
|
||||
return new AllyResponse(data[3], payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
{
|
||||
internal static class AllyProtocol
|
||||
{
|
||||
public const byte Start = 0x53;
|
||||
public const byte Write = 0x57;
|
||||
public const byte Read = 0x52;
|
||||
public const byte End = 0x0D;
|
||||
public const byte CommandCompleted = 0x01;
|
||||
public const byte DeviceSpecific = 0xFD;
|
||||
}
|
||||
|
||||
internal enum AllyCommand : byte
|
||||
{
|
||||
ViewFactoryId = 0x01,
|
||||
ViewVersionAndType = 0x05,
|
||||
SetPresetTotal = 0x14,
|
||||
SetMeterMode = 0x1A,
|
||||
SetValvePosition = 0x1E
|
||||
}
|
||||
|
||||
internal enum AllyDeviceCommand : byte
|
||||
{
|
||||
ViewSystemTime = 0x10,
|
||||
Configuration = 0x15,
|
||||
ViewCalibration = 0x53,
|
||||
SetCalibration = 0x54,
|
||||
ViewRebootCount = 0x55,
|
||||
SetDiagnosticLed = 0x60,
|
||||
SetLcdTimeout = 0x8C,
|
||||
StartOffsetLearning = 0xD1
|
||||
}
|
||||
|
||||
public enum AllyMagneticTamperProfile
|
||||
{
|
||||
Calibration,
|
||||
FirmwareDefaults
|
||||
}
|
||||
|
||||
public sealed class AllyCommunicationException : Exception
|
||||
{
|
||||
public byte? Status { get; private set; }
|
||||
|
||||
public AllyCommunicationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AllyCommunicationException(string message, byte status)
|
||||
: base(message)
|
||||
{
|
||||
Status = status;
|
||||
}
|
||||
|
||||
public AllyCommunicationException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
{
|
||||
public sealed class AllyResponse
|
||||
{
|
||||
public byte Status { get; private set; }
|
||||
public byte[] Payload { get; private set; }
|
||||
public bool IsSuccess { get { return Status == AllyProtocol.CommandCompleted; } }
|
||||
|
||||
internal AllyResponse(byte status, byte[] payload)
|
||||
{
|
||||
Status = status;
|
||||
Payload = payload ?? Array.Empty<byte>();
|
||||
}
|
||||
|
||||
public string GetNullTerminatedAscii()
|
||||
{
|
||||
int length = Array.IndexOf(Payload, (byte)0x00);
|
||||
if (length < 0)
|
||||
length = Payload.Length;
|
||||
return Encoding.ASCII.GetString(Payload, 0, length).Trim();
|
||||
}
|
||||
|
||||
public byte GetByte()
|
||||
{
|
||||
if (Payload.Length != 1)
|
||||
throw new FormatException("ALLY response does not contain one byte.");
|
||||
return Payload[0];
|
||||
}
|
||||
|
||||
public ushort GetUInt16LittleEndian()
|
||||
{
|
||||
if (Payload.Length != 2)
|
||||
throw new FormatException("ALLY response does not contain a UInt16 value.");
|
||||
return (ushort)(Payload[0] | (Payload[1] << 8));
|
||||
}
|
||||
|
||||
public uint GetUInt32LittleEndian()
|
||||
{
|
||||
if (Payload.Length != 4)
|
||||
throw new FormatException("ALLY response does not contain a UInt32 value.");
|
||||
|
||||
return (uint)(Payload[0]
|
||||
| (Payload[1] << 8)
|
||||
| (Payload[2] << 16)
|
||||
| (Payload[3] << 24));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Ports;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
{
|
||||
public sealed class AllySerialTransportBuilder
|
||||
{
|
||||
private string portName;
|
||||
private int baudRate = 2400;
|
||||
private int dataBits = 8;
|
||||
private Parity parity = Parity.None;
|
||||
private StopBits stopBits = StopBits.One;
|
||||
|
||||
public AllySerialTransportBuilder WithPort(string value)
|
||||
{
|
||||
portName = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AllySerialTransportBuilder WithBaudRate(int value)
|
||||
{
|
||||
baudRate = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AllySerialTransportBuilder WithDataBits(int value)
|
||||
{
|
||||
dataBits = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AllySerialTransportBuilder WithParity(Parity value)
|
||||
{
|
||||
parity = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AllySerialTransportBuilder WithStopBits(StopBits value)
|
||||
{
|
||||
stopBits = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AllySerialTransport Build()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(portName))
|
||||
throw new InvalidOperationException("ALLY command port is not configured.");
|
||||
if (baudRate <= 0 || dataBits <= 0)
|
||||
throw new InvalidOperationException("ALLY serial settings are invalid.");
|
||||
|
||||
return new AllySerialTransport(portName, baudRate, dataBits, parity, stopBits);
|
||||
}
|
||||
|
||||
public AllySerialTransport BuildAndConnect()
|
||||
{
|
||||
AllySerialTransport transport = Build();
|
||||
transport.Open();
|
||||
return transport;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AllySerialTransport : IAllyTransport
|
||||
{
|
||||
private readonly object sync = new object();
|
||||
private readonly string portName;
|
||||
private readonly int baudRate;
|
||||
private readonly int dataBits;
|
||||
private readonly Parity parity;
|
||||
private readonly StopBits stopBits;
|
||||
private SerialPort serialPort;
|
||||
|
||||
internal AllySerialTransport(
|
||||
string portName,
|
||||
int baudRate,
|
||||
int dataBits,
|
||||
Parity parity,
|
||||
StopBits stopBits)
|
||||
{
|
||||
this.portName = portName;
|
||||
this.baudRate = baudRate;
|
||||
this.dataBits = dataBits;
|
||||
this.parity = parity;
|
||||
this.stopBits = stopBits;
|
||||
}
|
||||
|
||||
public bool IsOpen { get { return serialPort != null && serialPort.IsOpen; } }
|
||||
|
||||
public void Open()
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
if (IsOpen)
|
||||
return;
|
||||
|
||||
DisposePort();
|
||||
serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
|
||||
serialPort.Open();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] SendAndWait(byte[] request, int timeoutMs)
|
||||
{
|
||||
if (request == null || request.Length == 0)
|
||||
throw new ArgumentException("ALLY request is empty.", nameof(request));
|
||||
if (timeoutMs <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(timeoutMs));
|
||||
|
||||
lock (sync)
|
||||
{
|
||||
if (!IsOpen)
|
||||
Open();
|
||||
|
||||
serialPort.DiscardInBuffer();
|
||||
serialPort.WriteTimeout = timeoutMs;
|
||||
serialPort.Write(request, 0, request.Length);
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
int first;
|
||||
do
|
||||
{
|
||||
first = ReadByte(stopwatch, timeoutMs);
|
||||
}
|
||||
while (first != AllyProtocol.Start);
|
||||
|
||||
int direction = ReadByte(stopwatch, timeoutMs);
|
||||
int length = ReadByte(stopwatch, timeoutMs);
|
||||
if (length < 5)
|
||||
throw new FormatException("ALLY response length is invalid.");
|
||||
|
||||
byte[] response = new byte[length];
|
||||
response[0] = (byte)first;
|
||||
response[1] = (byte)direction;
|
||||
response[2] = (byte)length;
|
||||
|
||||
for (int i = 3; i < response.Length; i++)
|
||||
response[i] = (byte)ReadByte(stopwatch, timeoutMs);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
private int ReadByte(Stopwatch stopwatch, int timeoutMs)
|
||||
{
|
||||
int remaining = timeoutMs - (int)stopwatch.ElapsedMilliseconds;
|
||||
if (remaining <= 0)
|
||||
throw new TimeoutException("ALLY response timeout.");
|
||||
|
||||
serialPort.ReadTimeout = remaining;
|
||||
return serialPort.ReadByte();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
DisposePort();
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposePort()
|
||||
{
|
||||
if (serialPort == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (serialPort.IsOpen)
|
||||
serialPort.Close();
|
||||
}
|
||||
finally
|
||||
{
|
||||
serialPort.Dispose();
|
||||
serialPort = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
{
|
||||
public sealed class AllyVersionInfo
|
||||
{
|
||||
public string TouchReadVersion { get; private set; }
|
||||
public string DeviceType { get; private set; }
|
||||
public string FirmwareVersion { get; private set; }
|
||||
public string RawValue { get; private set; }
|
||||
|
||||
private AllyVersionInfo()
|
||||
{
|
||||
}
|
||||
|
||||
public static AllyVersionInfo Parse(string rawValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawValue))
|
||||
throw new FormatException("ALLY version/type response is empty.");
|
||||
|
||||
string[] fields = rawValue.Split(',');
|
||||
if (fields.Length != 3)
|
||||
throw new FormatException("ALLY version/type response must contain three comma-separated fields.");
|
||||
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
fields[i] = fields[i].Trim();
|
||||
|
||||
if (Array.Exists(fields, string.IsNullOrWhiteSpace))
|
||||
throw new FormatException("ALLY version/type response contains an empty field.");
|
||||
|
||||
return new AllyVersionInfo
|
||||
{
|
||||
TouchReadVersion = fields[0],
|
||||
DeviceType = fields[1],
|
||||
FirmwareVersion = fields[2],
|
||||
RawValue = rawValue.Trim()
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return RawValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader.Communication
|
||||
{
|
||||
public interface IAllyTransport : IDisposable
|
||||
{
|
||||
bool IsOpen { get; }
|
||||
void Open();
|
||||
byte[] SendAndWait(byte[] request, int timeoutMs);
|
||||
}
|
||||
}
|
||||
35
TBF/Rig/RegisterReaders/AllyReader/Factory.cs
Normal file
35
TBF/Rig/RegisterReaders/AllyReader/Factory.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using TBF.Rig.Generic;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.AllyReader
|
||||
{
|
||||
public class Factory : IComponentFactory
|
||||
{
|
||||
public string ClassName { get { return GetType().Namespace.Substring(8); } }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ClassName;
|
||||
}
|
||||
|
||||
public IComponent DummyComponent()
|
||||
{
|
||||
return new AllyMeterReader();
|
||||
}
|
||||
|
||||
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components)
|
||||
{
|
||||
return new AllyMeterReader(cfg);
|
||||
}
|
||||
|
||||
public IComponentCfg DefaultConfig()
|
||||
{
|
||||
return new AllyReaderCfg(this);
|
||||
}
|
||||
|
||||
public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component)
|
||||
{
|
||||
return ComponentCfgBase.CreateFromDbEntity(AllyReaderCfg.Serializer, component, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
namespace TBF.Rig.TestMethods.AllyCalibration
|
||||
{
|
||||
public enum AllyCalibrationActivity
|
||||
{
|
||||
ReadSerialNumber,
|
||||
ReadVersionAndType,
|
||||
ReadSystemTime,
|
||||
ReadRebootCount,
|
||||
OpenValve,
|
||||
DisableSpreadSpectrum,
|
||||
SetInitialMeterMode,
|
||||
TurnDiagnosticLedOn,
|
||||
ResetCalibrationFactor,
|
||||
ReadCalibrationFactor,
|
||||
SetCalibrationMagneticTamperProfile,
|
||||
TurnDiagnosticLedOff,
|
||||
SetActiveMeterMode,
|
||||
EnableSpreadSpectrum,
|
||||
SetDefaultMagneticTamperProfile,
|
||||
WriteCalibrationFactor,
|
||||
WriteDisplayVolume,
|
||||
SetLcdTimeout,
|
||||
StartOffsetLearning
|
||||
}
|
||||
|
||||
internal static class AllyCalibrationActivityNames
|
||||
{
|
||||
public const string ReadSerialNumber = "Read serial number";
|
||||
public const string ReadVersionAndType = "Read version and type";
|
||||
public const string ReadSystemTime = "Read system time";
|
||||
public const string ReadRebootCount = "Read reboot count";
|
||||
public const string OpenValve = "Open ALLY valve";
|
||||
public const string DisableSpreadSpectrum = "Disable spread spectrum";
|
||||
public const string SetInitialMeterMode = "Set initial meter mode";
|
||||
public const string TurnDiagnosticLedOn = "Turn diagnostic LED on";
|
||||
public const string ResetCalibrationFactor = "Reset calibration factor";
|
||||
public const string ReadCalibrationFactor = "Read calibration factor";
|
||||
public const string SetCalibrationMagneticTamperProfile = "Set calibration magnetic-tamper profile";
|
||||
public const string TurnDiagnosticLedOff = "Turn diagnostic LED off";
|
||||
public const string SetActiveMeterMode = "Set active meter mode";
|
||||
public const string EnableSpreadSpectrum = "Enable spread spectrum";
|
||||
public const string SetDefaultMagneticTamperProfile = "Set default magnetic-tamper profile";
|
||||
public const string WriteCalibrationFactor = "Write calibration factor";
|
||||
public const string WriteDisplayVolume = "Write display volume";
|
||||
public const string SetLcdTimeout = "Set LCD timeout";
|
||||
public const string StartOffsetLearning = "Start offset learning";
|
||||
|
||||
public static readonly string[] All =
|
||||
{
|
||||
ReadSerialNumber,
|
||||
ReadVersionAndType,
|
||||
ReadSystemTime,
|
||||
ReadRebootCount,
|
||||
OpenValve,
|
||||
DisableSpreadSpectrum,
|
||||
SetInitialMeterMode,
|
||||
TurnDiagnosticLedOn,
|
||||
ResetCalibrationFactor,
|
||||
ReadCalibrationFactor,
|
||||
SetCalibrationMagneticTamperProfile,
|
||||
TurnDiagnosticLedOff,
|
||||
SetActiveMeterMode,
|
||||
EnableSpreadSpectrum,
|
||||
SetDefaultMagneticTamperProfile,
|
||||
WriteCalibrationFactor,
|
||||
WriteDisplayVolume,
|
||||
SetLcdTimeout,
|
||||
StartOffsetLearning
|
||||
};
|
||||
|
||||
public static bool TryParse(string value, out AllyCalibrationActivity activity)
|
||||
{
|
||||
for (int i = 0; i < All.Length; i++)
|
||||
{
|
||||
if (string.Equals(All[i], value, System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
activity = (AllyCalibrationActivity)i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
activity = default(AllyCalibrationActivity);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
259
TBF/Rig/TestMethods/AllyCalibration/AllyCalibrationSeq.cs
Normal file
259
TBF/Rig/TestMethods/AllyCalibration/AllyCalibrationSeq.cs
Normal file
@ -0,0 +1,259 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Common;
|
||||
using Config.Entities;
|
||||
using Results.Entities;
|
||||
using TBF.Rig.RegisterReaders.AllyReader;
|
||||
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||
using TBF.Rig.Sequences;
|
||||
using TBF.UiBridge;
|
||||
|
||||
namespace TBF.Rig.TestMethods.AllyCalibration
|
||||
{
|
||||
public class AllyCalibrationSeq : SequenceBase
|
||||
{
|
||||
private const byte InitialMeterMode = 0x09;
|
||||
private const byte ActiveMeterMode = 0x02;
|
||||
private const byte DiagnosticLedCalibrationMode = 0xC2;
|
||||
private const string DisplayVolume = "01134010";
|
||||
private const byte LcdTimeoutSeconds = 30;
|
||||
private const ushort OffsetLearningSamples = 1000;
|
||||
private const ushort OffsetLearningDelaySeconds = 1;
|
||||
|
||||
public IList<Event> Execute(
|
||||
Test test,
|
||||
int repetitionNr,
|
||||
TestMethod method,
|
||||
TestMethodCfg cfg,
|
||||
TestMethodParams parameters)
|
||||
{
|
||||
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, Progress.JustStarted));
|
||||
|
||||
TestRslt testResult = BatchRslts.GetTestRslt(test.Name, 0);
|
||||
if (testResult != null)
|
||||
testResult.StartTime = DateTime.Now;
|
||||
|
||||
List<string> messages = new List<string>();
|
||||
|
||||
for (int position = 0; position < BatchRslts.WMPositionsCount; position++)
|
||||
{
|
||||
Results.Entities.WaterMeter waterMeter = BatchRslts.Batch.WaterMeters[position];
|
||||
if (waterMeter == null || waterMeter.Disabled)
|
||||
continue;
|
||||
|
||||
MeterTestRslt meterResult = BatchRslts.GetMeterTestRslt(
|
||||
test.Name,
|
||||
position,
|
||||
CompoundMeterId.Single);
|
||||
|
||||
AllyMeterReader reader = sensPath != null &&
|
||||
sensPath.RegisterReaders != null &&
|
||||
position < sensPath.RegisterReaders.Length
|
||||
? sensPath.RegisterReaders[position] as AllyMeterReader
|
||||
: null;
|
||||
|
||||
bool passed;
|
||||
string resultMessage;
|
||||
if (reader == null)
|
||||
{
|
||||
passed = false;
|
||||
resultMessage = "ALLY" + (position + 1) + ": ALLY reader is not configured.";
|
||||
}
|
||||
else
|
||||
{
|
||||
passed = ExecuteWithRetries(reader, cfg, parameters, out resultMessage);
|
||||
resultMessage = reader.Name + ": " + resultMessage;
|
||||
}
|
||||
|
||||
messages.Add(resultMessage);
|
||||
|
||||
if (meterResult != null)
|
||||
{
|
||||
meterResult.RegReaderType = (int)RegisterReaderType.DataStream;
|
||||
meterResult.TestDone = true;
|
||||
meterResult.Passed = passed;
|
||||
}
|
||||
}
|
||||
|
||||
if (testResult != null)
|
||||
{
|
||||
testResult.EndTime = DateTime.Now;
|
||||
testResult.TestDone = true;
|
||||
testResult.Remark = string.Join("; ", messages);
|
||||
}
|
||||
|
||||
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, Progress.Completed));
|
||||
Bridge.OnTestCompleted(this, new TestCompletedEventArgs(test.Name, testResult));
|
||||
allResults.Info(TestResult2CsvLine(test.Name, 0));
|
||||
|
||||
return new List<Event> { Event.Done };
|
||||
}
|
||||
|
||||
private static bool ExecuteWithRetries(
|
||||
AllyMeterReader reader,
|
||||
TestMethodCfg cfg,
|
||||
TestMethodParams parameters,
|
||||
out string resultMessage)
|
||||
{
|
||||
Exception lastException = null;
|
||||
int attempts = Math.Max(1, cfg.MaxAttempts);
|
||||
|
||||
for (int attempt = 1; attempt <= attempts; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool passed = ExecuteOnce(reader, cfg, parameters, out resultMessage);
|
||||
if (!passed)
|
||||
return false;
|
||||
if (attempt > 1)
|
||||
resultMessage += " (attempt " + attempt + ")";
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastException = ex;
|
||||
if (attempt < attempts && cfg.DelayBetweenAttemptsMs > 0)
|
||||
Thread.Sleep(cfg.DelayBetweenAttemptsMs);
|
||||
}
|
||||
}
|
||||
|
||||
resultMessage = lastException == null
|
||||
? "ALLY command failed."
|
||||
: "ALLY command failed after " + attempts + " attempts: " + lastException.Message;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ExecuteOnce(
|
||||
AllyMeterReader reader,
|
||||
TestMethodCfg cfg,
|
||||
TestMethodParams parameters,
|
||||
out string resultMessage)
|
||||
{
|
||||
AllyCalibrationActivity activity;
|
||||
if (!AllyCalibrationActivityNames.TryParse(parameters.Activity, out activity))
|
||||
throw new InvalidOperationException("Unknown ALLY calibration activity: " + parameters.Activity);
|
||||
|
||||
int timeout = cfg.CommandTimeoutMs;
|
||||
switch (activity)
|
||||
{
|
||||
case AllyCalibrationActivity.ReadSerialNumber:
|
||||
string serialNumber = reader.ReadSerialNumber(timeout);
|
||||
if (string.IsNullOrWhiteSpace(serialNumber))
|
||||
throw new FormatException("ALLY serial number response is empty.");
|
||||
resultMessage = "SerialNumber=" + serialNumber;
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.ReadVersionAndType:
|
||||
AllyVersionInfo version = reader.ReadVersionAndType(timeout);
|
||||
bool deviceTypeOk = string.Equals(
|
||||
version.DeviceType,
|
||||
cfg.ExpectedDeviceType,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
bool firmwareOk = string.IsNullOrWhiteSpace(cfg.ExpectedFirmwareVersion) ||
|
||||
string.Equals(
|
||||
version.FirmwareVersion,
|
||||
cfg.ExpectedFirmwareVersion,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
resultMessage = "Version=" + version +
|
||||
", DeviceType=" + (deviceTypeOk ? "OK" : "NOK") +
|
||||
", Firmware=" + (firmwareOk ? "OK" : "NOK");
|
||||
return deviceTypeOk && firmwareOk;
|
||||
|
||||
case AllyCalibrationActivity.ReadSystemTime:
|
||||
DateTime systemTime = reader.ReadSystemTimeUtc(timeout);
|
||||
resultMessage = "SystemTimeUtc=" + systemTime.ToString("O");
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.ReadRebootCount:
|
||||
byte rebootCount = reader.ReadRebootCount(timeout);
|
||||
resultMessage = "RebootCount=" + rebootCount;
|
||||
return rebootCount == 0;
|
||||
|
||||
case AllyCalibrationActivity.OpenValve:
|
||||
reader.OpenValve(timeout);
|
||||
resultMessage = "Valve=open";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.DisableSpreadSpectrum:
|
||||
reader.SetSpreadSpectrum(false, timeout);
|
||||
resultMessage = "SpreadSpectrum=disabled";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.SetInitialMeterMode:
|
||||
reader.SetMeterMode(InitialMeterMode, timeout);
|
||||
resultMessage = "MeterMode=0x09";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.TurnDiagnosticLedOn:
|
||||
reader.SetDiagnosticLed(DiagnosticLedCalibrationMode, timeout);
|
||||
resultMessage = "DiagnosticLED=0xC2";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.ResetCalibrationFactor:
|
||||
double resetFactor = reader.ResetCalibrationFactor(timeout);
|
||||
resultMessage = "ResetCalibrationFactor=" + resetFactor.ToString("F2") + "%";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.ReadCalibrationFactor:
|
||||
double currentFactor = reader.ReadCalibrationFactor(timeout);
|
||||
bool factorMatches = !reader.ExpectedCalibrationFactorPercent.HasValue ||
|
||||
Math.Abs(currentFactor - reader.ExpectedCalibrationFactorPercent.Value) <= 0.05D;
|
||||
resultMessage = "CalibrationFactor=" + currentFactor.ToString("F3") + "%";
|
||||
return factorMatches;
|
||||
|
||||
case AllyCalibrationActivity.SetCalibrationMagneticTamperProfile:
|
||||
reader.SetMagneticTamperProfile(AllyMagneticTamperProfile.Calibration, timeout);
|
||||
resultMessage = "MagneticTamperProfile=calibration";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.TurnDiagnosticLedOff:
|
||||
reader.SetDiagnosticLed(0x00, timeout);
|
||||
resultMessage = "DiagnosticLED=off";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.SetActiveMeterMode:
|
||||
reader.SetMeterMode(ActiveMeterMode, timeout);
|
||||
resultMessage = "MeterMode=active";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.EnableSpreadSpectrum:
|
||||
reader.SetSpreadSpectrum(true, timeout);
|
||||
resultMessage = "SpreadSpectrum=enabled";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.SetDefaultMagneticTamperProfile:
|
||||
reader.SetMagneticTamperProfile(AllyMagneticTamperProfile.FirmwareDefaults, timeout);
|
||||
resultMessage = "MagneticTamperProfile=firmware defaults";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.WriteCalibrationFactor:
|
||||
reader.SetCalibrationFactor(parameters.CalibrationFactorPercent, timeout);
|
||||
resultMessage = "CalibrationFactorWritten=" +
|
||||
parameters.CalibrationFactorPercent.ToString("F3") + "%";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.WriteDisplayVolume:
|
||||
reader.SetDisplayVolume(DisplayVolume, timeout);
|
||||
resultMessage = "DisplayVolume=" + DisplayVolume;
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.SetLcdTimeout:
|
||||
reader.SetLcdTimeout(LcdTimeoutSeconds, timeout);
|
||||
resultMessage = "LcdTimeout=30s";
|
||||
return true;
|
||||
|
||||
case AllyCalibrationActivity.StartOffsetLearning:
|
||||
reader.StartOffsetLearning(
|
||||
OffsetLearningSamples,
|
||||
OffsetLearningDelaySeconds,
|
||||
timeout);
|
||||
resultMessage = "OffsetLearning=1000 samples, 1s delay";
|
||||
return true;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported ALLY calibration activity.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
TBF/Rig/TestMethods/AllyCalibration/Factory.cs
Normal file
30
TBF/Rig/TestMethods/AllyCalibration/Factory.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using TBF.Rig.Generic;
|
||||
|
||||
namespace TBF.Rig.TestMethods.AllyCalibration
|
||||
{
|
||||
public class Factory : IComponentFactory
|
||||
{
|
||||
public string ClassName { get { return GetType().Namespace.Substring(8); } }
|
||||
|
||||
public IComponent DummyComponent()
|
||||
{
|
||||
return new TestMethod();
|
||||
}
|
||||
|
||||
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components)
|
||||
{
|
||||
return new TestMethod(cfg);
|
||||
}
|
||||
|
||||
public IComponentCfg DefaultConfig()
|
||||
{
|
||||
return new TestMethodCfg(this);
|
||||
}
|
||||
|
||||
public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component)
|
||||
{
|
||||
return ComponentCfgBase.CreateFromDbEntity(TestMethodCfg.Serializer, component, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
TBF/Rig/TestMethods/AllyCalibration/TestMethod.cs
Normal file
94
TBF/Rig/TestMethods/AllyCalibration/TestMethod.cs
Normal file
@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Common;
|
||||
using Config.Entities;
|
||||
using log4net;
|
||||
using TBF.Rig.Generic;
|
||||
using TBF.Rig.GenericDevices;
|
||||
using TBF.Rig.Sequences;
|
||||
using TBF.UiBridge;
|
||||
|
||||
namespace TBF.Rig.TestMethods.AllyCalibration
|
||||
{
|
||||
public class TestMethod : ComponentBase, ISimultTestMethod, ITestMethodSmart
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(TestMethod));
|
||||
private readonly TestMethodCfg allyCfg;
|
||||
|
||||
public TestMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public TestMethod(IComponentCfg cfg)
|
||||
: base(cfg)
|
||||
{
|
||||
allyCfg = cfg as TestMethodCfg;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Cfg == null ? ClassName : string.Format("{0}({1})", ClassName, Cfg.ToString(-1));
|
||||
}
|
||||
|
||||
public FlowType MethodFlowType { get { return FlowType.volume; } }
|
||||
public bool SimultWithPrevious
|
||||
{
|
||||
get
|
||||
{
|
||||
TestMethodParams parameters = allyCfg == null ? null : allyCfg.TestParams as TestMethodParams;
|
||||
return parameters != null && parameters.SimultWithPrevious;
|
||||
}
|
||||
}
|
||||
public bool SimultWithNext
|
||||
{
|
||||
get
|
||||
{
|
||||
TestMethodParams parameters = allyCfg == null ? null : allyCfg.TestParams as TestMethodParams;
|
||||
return parameters != null && parameters.SimultWithNext;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanTest(MetersKind meters)
|
||||
{
|
||||
return meters == MetersKind.Single;
|
||||
}
|
||||
|
||||
public bool DoTransitions()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CheckDeviceCaps(Test test, OutputPath devices, out string message)
|
||||
{
|
||||
message = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
if (allyCfg == null)
|
||||
throw new InvalidOperationException("ALLY calibration test method configuration is missing.");
|
||||
|
||||
log.FatalFormat("{0} initialized: {1}", Name, this);
|
||||
}
|
||||
|
||||
public IList<Event> Execute(Test test, int repetitionNr, bool isLastRepetition)
|
||||
{
|
||||
TestMethodParams parameters = allyCfg.TestParams as TestMethodParams;
|
||||
if (parameters == null)
|
||||
throw new InvalidOperationException("ALLY calibration test parameters are missing.");
|
||||
|
||||
if (DebugLevel == DebugMode.Normal)
|
||||
return new AllyCalibrationSeq().Execute(test, repetitionNr, this, allyCfg, parameters);
|
||||
|
||||
new AllyCalibrationSeq().MakeSimulatedTrivial(test, repetitionNr, test.Part);
|
||||
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, 1, Progress.Completed));
|
||||
Bridge.OnTestCompleted(
|
||||
this,
|
||||
new TestCompletedEventArgs(
|
||||
test.Name,
|
||||
ProcessData.BatchRslts.GetTestRslt(Common.Utils.GetTestName(test.Name, 1, 1), 0)));
|
||||
return new List<Event> { Event.Done };
|
||||
}
|
||||
}
|
||||
}
|
||||
186
TBF/Rig/TestMethods/AllyCalibration/TestMethodCfg.cs
Normal file
186
TBF/Rig/TestMethods/AllyCalibration/TestMethodCfg.cs
Normal file
@ -0,0 +1,186 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
using Common;
|
||||
using Config.Entities;
|
||||
using TBF.Rig.Generic;
|
||||
|
||||
namespace TBF.Rig.TestMethods.AllyCalibration
|
||||
{
|
||||
public class TestMethodCfg : ComponentCfgBase, IComponentCfg, IParamsProvider
|
||||
{
|
||||
public static readonly XmlSerializer Serializer =
|
||||
XmlSerializer.FromTypes(new[] { typeof(TestMethodCfg) })[0];
|
||||
|
||||
public int CommandTimeoutMs;
|
||||
public int MaxAttempts;
|
||||
public int DelayBetweenAttemptsMs;
|
||||
public string ExpectedDeviceType;
|
||||
public string ExpectedFirmwareVersion;
|
||||
|
||||
[XmlIgnore]
|
||||
public ITestParams TestParams { get; set; }
|
||||
|
||||
private TestMethodCfg()
|
||||
{
|
||||
}
|
||||
|
||||
public TestMethodCfg(IComponentFactory factory)
|
||||
{
|
||||
Factory = factory;
|
||||
Name = "AllyCalibrationCommunication";
|
||||
ParentName = string.Empty;
|
||||
InitializeAll();
|
||||
TestParams = CreateTestParamsProvider() as TestMethodParams;
|
||||
}
|
||||
|
||||
public override XmlSerializer GetSerializer()
|
||||
{
|
||||
return Serializer;
|
||||
}
|
||||
|
||||
public IComponentCfgCtrl GetControl(IList<Component> cmpntEntities)
|
||||
{
|
||||
return new Configs.ParamsProvider.ComponentCfgCtrl(this, null);
|
||||
}
|
||||
|
||||
public override IParamsProvider GetRuntimeTestParamsProvider()
|
||||
{
|
||||
return TestParams;
|
||||
}
|
||||
|
||||
public override IParamsProvider CreateTestParamsProvider()
|
||||
{
|
||||
return new TestMethodParams(true);
|
||||
}
|
||||
|
||||
public override IParamsProvider GetUITestParamsProvider(Test test)
|
||||
{
|
||||
return test.Method == Name ? base.GetUITestParamsProvider(test) : null;
|
||||
}
|
||||
|
||||
public string ComponentName { get { return Name; } }
|
||||
|
||||
public void InitializeAll()
|
||||
{
|
||||
CommandTimeoutMs = 5000;
|
||||
MaxAttempts = 4;
|
||||
DelayBetweenAttemptsMs = 250;
|
||||
ExpectedDeviceType = "SWM003";
|
||||
ExpectedFirmwareVersion = string.Empty;
|
||||
}
|
||||
|
||||
public int ParamsCount()
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
public string ParamName(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return "Command timeout [ms]";
|
||||
case 1: return "Maximum command attempts";
|
||||
case 2: return "Delay between attempts [ms]";
|
||||
case 3: return "Expected ALLY device type";
|
||||
case 4: return "Expected firmware version";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<string> ParamValues(int i)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public string ToString(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return CommandTimeoutMs.ToString();
|
||||
case 1: return MaxAttempts.ToString();
|
||||
case 2: return DelayBetweenAttemptsMs.ToString();
|
||||
case 3: return ExpectedDeviceType;
|
||||
case 4: return ExpectedFirmwareVersion;
|
||||
default:
|
||||
return string.Format(
|
||||
"{0}: timeout={1}ms, attempts={2}, device={3}, firmware={4}",
|
||||
Name,
|
||||
CommandTimeoutMs,
|
||||
MaxAttempts,
|
||||
ExpectedDeviceType,
|
||||
string.IsNullOrEmpty(ExpectedFirmwareVersion) ? "not checked" : ExpectedFirmwareVersion);
|
||||
}
|
||||
}
|
||||
|
||||
public CfgUpdateFlags UpdateParam(int i, string strValue)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: CommandTimeoutMs = int.Parse(strValue); break;
|
||||
case 1: MaxAttempts = int.Parse(strValue); break;
|
||||
case 2: DelayBetweenAttemptsMs = int.Parse(strValue); break;
|
||||
case 3: ExpectedDeviceType = strValue; break;
|
||||
case 4: ExpectedFirmwareVersion = strValue; break;
|
||||
default: return CfgUpdateFlags.None;
|
||||
}
|
||||
|
||||
return CfgUpdateFlags.RestartRqrd;
|
||||
}
|
||||
|
||||
public bool ValidateParam(int i, string strValue, out string message)
|
||||
{
|
||||
message = string.Empty;
|
||||
int value;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
if (int.TryParse(strValue, out value) && value >= 100 && value <= 60000)
|
||||
return true;
|
||||
break;
|
||||
case 1:
|
||||
if (int.TryParse(strValue, out value) && value >= 1 && value <= 4)
|
||||
return true;
|
||||
break;
|
||||
case 2:
|
||||
if (int.TryParse(strValue, out value) && value >= 0 && value <= 10000)
|
||||
return true;
|
||||
break;
|
||||
case 3:
|
||||
if (!string.IsNullOrWhiteSpace(strValue))
|
||||
return true;
|
||||
break;
|
||||
case 4:
|
||||
return true;
|
||||
default:
|
||||
message = "Invalid parameter index.";
|
||||
return false;
|
||||
}
|
||||
|
||||
message = ParamName(i) + " is invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool UpdateEmbeddedDbEntity()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public IParamsProvider Clone()
|
||||
{
|
||||
return new TestMethodCfg
|
||||
{
|
||||
Name = Name,
|
||||
ParentName = ParentName,
|
||||
Factory = Factory,
|
||||
CommandTimeoutMs = CommandTimeoutMs,
|
||||
MaxAttempts = MaxAttempts,
|
||||
DelayBetweenAttemptsMs = DelayBetweenAttemptsMs,
|
||||
ExpectedDeviceType = ExpectedDeviceType,
|
||||
ExpectedFirmwareVersion = ExpectedFirmwareVersion,
|
||||
TestParams = TestParams == null ? null : TestParams.Clone() as ITestParams
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
163
TBF/Rig/TestMethods/AllyCalibration/TestMethodParams.cs
Normal file
163
TBF/Rig/TestMethods/AllyCalibration/TestMethodParams.cs
Normal file
@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
using Common;
|
||||
using Config.Entities;
|
||||
using TBF.Rig.Generic;
|
||||
using TBF.Resources;
|
||||
|
||||
namespace TBF.Rig.TestMethods.AllyCalibration
|
||||
{
|
||||
public class TestMethodParams : TestParamsBase, IParamsProvider, ITestParams
|
||||
{
|
||||
public static readonly XmlSerializer Serializer =
|
||||
XmlSerializer.FromTypes(new[] { typeof(TestMethodParams) })[0];
|
||||
|
||||
public double CalibrationFactorPercent;
|
||||
|
||||
public override XmlSerializer GetSerializer()
|
||||
{
|
||||
return Serializer;
|
||||
}
|
||||
|
||||
public override void InitializeAll()
|
||||
{
|
||||
Activity = AllyCalibrationActivityNames.ReadSerialNumber;
|
||||
CalibrationFactorPercent = 100D;
|
||||
SimultWithPrevious = false;
|
||||
SimultWithNext = false;
|
||||
}
|
||||
|
||||
public override int ParamsCount()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
public override string ParamName(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return Strings.Activity;
|
||||
case 1: return "Calibration factor [%]";
|
||||
case 2: return Strings.Simultaneous_with_previous_step;
|
||||
case 3: return Strings.Simultaneous_with_next_step;
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public override ICollection<string> ParamValues(int i)
|
||||
{
|
||||
if (i == 0)
|
||||
return AllyCalibrationActivityNames.All;
|
||||
if (i == 2 || i == 3)
|
||||
return new[] { Strings.yes, Strings.no };
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string ToString(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return Activity;
|
||||
case 1: return CalibrationFactorPercent.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
case 2: return SimultWithPrevious ? Strings.yes : Strings.no;
|
||||
case 3: return SimultWithNext ? Strings.yes : Strings.no;
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public new CfgUpdateFlags UpdateParam(int i, string strValue)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: Activity = strValue; break;
|
||||
case 1: CalibrationFactorPercent = Utils.ParseUDouble(strValue); break;
|
||||
case 2: SimultWithPrevious = string.Equals(strValue, Strings.yes, StringComparison.OrdinalIgnoreCase); break;
|
||||
case 3: SimultWithNext = string.Equals(strValue, Strings.yes, StringComparison.OrdinalIgnoreCase); break;
|
||||
default: return CfgUpdateFlags.None;
|
||||
}
|
||||
|
||||
return CfgUpdateFlags.None;
|
||||
}
|
||||
|
||||
public new bool ValidateParam(int i, string strValue, out string message)
|
||||
{
|
||||
message = string.Empty;
|
||||
AllyCalibrationActivity activity;
|
||||
double factor;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
if (AllyCalibrationActivityNames.TryParse(strValue, out activity))
|
||||
return true;
|
||||
break;
|
||||
case 1:
|
||||
if (double.TryParse(strValue, out factor) && factor > 0D && factor < 1600D)
|
||||
return true;
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
if (string.Equals(strValue, Strings.yes, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(strValue, Strings.no, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
message = "Invalid parameter index.";
|
||||
return false;
|
||||
}
|
||||
|
||||
message = ParamName(i) + " is invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
public new IParamsProvider Clone()
|
||||
{
|
||||
return new TestMethodParams
|
||||
{
|
||||
Activity = Activity,
|
||||
CalibrationFactorPercent = CalibrationFactorPercent,
|
||||
SimultWithPrevious = SimultWithPrevious,
|
||||
SimultWithNext = SimultWithNext
|
||||
};
|
||||
}
|
||||
|
||||
public override void UpdateFromDbEntity(ComponentTest dbEntity)
|
||||
{
|
||||
if (dbEntity == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
TestMethodParams source = Serializer.Deserialize(new StringReader(dbEntity.Parameters)) as TestMethodParams;
|
||||
testParamsEntity = dbEntity;
|
||||
componentName = dbEntity.CmpntName;
|
||||
test = dbEntity.Test;
|
||||
|
||||
if (source != null)
|
||||
{
|
||||
Activity = source.Activity;
|
||||
CalibrationFactorPercent = source.CalibrationFactorPercent;
|
||||
SimultWithPrevious = source.SimultWithPrevious;
|
||||
SimultWithNext = source.SimultWithNext;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public TestMethodParams()
|
||||
{
|
||||
}
|
||||
|
||||
public TestMethodParams(bool initialize)
|
||||
{
|
||||
if (initialize)
|
||||
InitializeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1626,6 +1626,20 @@
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\GenesisCfgCtrl.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyMeterReader.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyMeterSize.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyOpticalSample.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyReaderCfg.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyCommandService.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyFrame.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyFrameBuilder.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyFrameParser.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyProtocol.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyResponse.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllySerialTransport.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\AllyVersionInfo.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\Communication\IAllyTransport.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\Factory.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\GenesisCfgCtrl.designer.cs">
|
||||
<DependentUpon>GenesisCfgCtrl.cs</DependentUpon>
|
||||
</Compile>
|
||||
@ -1950,6 +1964,12 @@
|
||||
<DependentUpon>TestMethodCfgCtrl.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Rig\TestMethods\FlyingStartMassCollection\Single\TestParams.cs" />
|
||||
<Compile Include="Rig\TestMethods\AllyCalibration\AllyCalibrationActivity.cs" />
|
||||
<Compile Include="Rig\TestMethods\AllyCalibration\AllyCalibrationSeq.cs" />
|
||||
<Compile Include="Rig\TestMethods\AllyCalibration\Factory.cs" />
|
||||
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethod.cs" />
|
||||
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethodCfg.cs" />
|
||||
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethodParams.cs" />
|
||||
<Compile Include="Rig\TestMethods\GenesisCommunication\Factory.cs" />
|
||||
<Compile Include="Rig\TestMethods\GenesisCommunication\GenesisCommunicationSeq.cs" />
|
||||
<Compile Include="Rig\TestMethods\GenesisCommunication\iPerlCommunicationParams.cs" />
|
||||
|
||||
125
TBFTests/Rig/RegisterReaders/AllyReader/AllyMeterReaderTest.cs
Normal file
125
TBFTests/Rig/RegisterReaders/AllyReader/AllyMeterReaderTest.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Common;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.AllyReader;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(AllyMeterReader))]
|
||||
public class AllyMeterReaderTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void GetResetCalibrationFactorPercent_AllSupportedSizes_ReturnsUi2093Value()
|
||||
{
|
||||
Assert.AreEqual(100D, CreateReader(AllyMeterSize.FiveEighths).GetResetCalibrationFactorPercent());
|
||||
Assert.AreEqual(100D, CreateReader(AllyMeterSize.ThreeQuarterShort).GetResetCalibrationFactorPercent());
|
||||
Assert.AreEqual(100D, CreateReader(AllyMeterSize.ThreeQuarterLong).GetResetCalibrationFactorPercent());
|
||||
Assert.AreEqual(260D, CreateReader(AllyMeterSize.OneInch).GetResetCalibrationFactorPercent());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsCalibrationFactorWithinSpecification_BoundariesMatchUi2093()
|
||||
{
|
||||
AssertLimits(AllyMeterSize.FiveEighths, 76D, 96D);
|
||||
AssertLimits(AllyMeterSize.ThreeQuarterShort, 88D, 108D);
|
||||
AssertLimits(AllyMeterSize.ThreeQuarterLong, 89D, 120D);
|
||||
AssertLimits(AllyMeterSize.OneInch, 236D, 281D);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void GetResetCalibrationFactorPercent_AutoDetect_ThrowsInvalidOperationException()
|
||||
{
|
||||
CreateReader(AllyMeterSize.AutoDetect).GetResetCalibrationFactorPercent();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessOpticalText_VolumeAndTimestampRollover_ProducesContinuousMeasurement()
|
||||
{
|
||||
AllyMeterReader reader = CreateReader(AllyMeterSize.FiveEighths);
|
||||
reader.Initialize();
|
||||
reader.Start();
|
||||
|
||||
string input =
|
||||
AllyOpticalTelegramFactory.Create(10, 0xFFFFF0, 0xFFFFFF00) +
|
||||
AllyOpticalTelegramFactory.Create(11, 0x000010, 0x00000100);
|
||||
InvokeProcessOpticalText(reader, input);
|
||||
reader.Stop();
|
||||
|
||||
Assert.AreEqual(2, reader.OpticalSamples.Count);
|
||||
Assert.IsFalse(reader.NoSamples);
|
||||
Assert.AreEqual(32D / 16000D, reader.WMVolume, 1E-9);
|
||||
Assert.AreEqual(512D / 8192D,
|
||||
reader.TimestampSecEnd - reader.TimestampSecStart, 1E-9);
|
||||
Assert.AreEqual(2, reader.WMPulses);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Position_NameWithNumericSuffix_ReturnsSuffix()
|
||||
{
|
||||
AllyReaderCfg config = CreateConfig(AllyMeterSize.FiveEighths);
|
||||
config.Name = "Ally12";
|
||||
|
||||
AllyMeterReader reader = new AllyMeterReader(config);
|
||||
|
||||
Assert.AreEqual(12, reader.Position);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SetAndReadCalibrationFactor_SimulatedReader_PreservesExpectedValue()
|
||||
{
|
||||
AllyMeterReader reader = CreateReader(AllyMeterSize.FiveEighths);
|
||||
|
||||
reader.SetCalibrationFactor(90D, 5000);
|
||||
double readback = reader.ReadCalibrationFactor(5000);
|
||||
|
||||
Assert.AreEqual(90D, reader.ExpectedCalibrationFactorPercent);
|
||||
Assert.AreEqual(90D, readback);
|
||||
Assert.AreEqual(90D, reader.CalibrationFactorPercent);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SetCalibrationFactor_OutsideConfiguredSizeLimit_ThrowsArgumentOutOfRangeException()
|
||||
{
|
||||
CreateReader(AllyMeterSize.FiveEighths).SetCalibrationFactor(100D, 5000);
|
||||
}
|
||||
|
||||
private static void AssertLimits(AllyMeterSize meterSize, double lower, double upper)
|
||||
{
|
||||
AllyMeterReader reader = CreateReader(meterSize);
|
||||
Assert.IsTrue(reader.IsCalibrationFactorWithinSpecification(lower));
|
||||
Assert.IsTrue(reader.IsCalibrationFactorWithinSpecification(upper));
|
||||
Assert.IsFalse(reader.IsCalibrationFactorWithinSpecification(lower - 0.01D));
|
||||
Assert.IsFalse(reader.IsCalibrationFactorWithinSpecification(upper + 0.01D));
|
||||
}
|
||||
|
||||
private static AllyMeterReader CreateReader(AllyMeterSize meterSize)
|
||||
{
|
||||
return new AllyMeterReader(CreateConfig(meterSize));
|
||||
}
|
||||
|
||||
private static AllyReaderCfg CreateConfig(AllyMeterSize meterSize)
|
||||
{
|
||||
return new AllyReaderCfg(
|
||||
new TBF.Rig.RegisterReaders.AllyReader.Factory())
|
||||
{
|
||||
ConfiguredMeterSize = meterSize,
|
||||
DebugLevel = DebugMode.Simulate
|
||||
};
|
||||
}
|
||||
|
||||
private static void InvokeProcessOpticalText(AllyMeterReader reader, string text)
|
||||
{
|
||||
MethodInfo method = typeof(AllyMeterReader).GetMethod(
|
||||
"ProcessOpticalText",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
Assert.IsNotNull(method, "ProcessOpticalText method was not found.");
|
||||
method.Invoke(reader, new object[] { text });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.AllyReader;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(AllyOpticalSample))]
|
||||
public class AllyOpticalSampleTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void TryParse_ValidTelegram_ParsesFlowVolumeAndTimestamp()
|
||||
{
|
||||
string telegram = AllyOpticalTelegramFactory.Create(-2, 0x123456, 0x89ABCDEF);
|
||||
DateTime receivedAt = new DateTime(2026, 8, 14, 10, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
AllyOpticalSample sample;
|
||||
bool result = AllyOpticalSample.TryParse(telegram, receivedAt, out sample);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.IsNotNull(sample);
|
||||
Assert.AreEqual((short)-2, sample.RawFlow);
|
||||
Assert.AreEqual(0x123456U, sample.RawVolume);
|
||||
Assert.AreEqual(0x89ABCDEFU, sample.RawTimestamp);
|
||||
Assert.AreEqual(receivedAt, sample.ReceivedAtUtc);
|
||||
Assert.AreEqual(telegram, sample.RawLine);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryParse_PrefixedValidTelegram_UsesLastCompleteTelegram()
|
||||
{
|
||||
string telegram = AllyOpticalTelegramFactory.Create(1, 2, 3);
|
||||
|
||||
AllyOpticalSample sample;
|
||||
bool result = AllyOpticalSample.TryParse("noise" + telegram, DateTime.UtcNow, out sample);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(2U, sample.RawVolume);
|
||||
Assert.AreEqual(3U, sample.RawTimestamp);
|
||||
Assert.AreEqual(telegram, sample.RawLine);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryParse_CorruptChecksum_ReturnsFalse()
|
||||
{
|
||||
string telegram = AllyOpticalTelegramFactory.Create(1, 2, 3);
|
||||
string corrupt = (telegram[0] == '0' ? "1" : "0") + telegram.Substring(1);
|
||||
|
||||
AllyOpticalSample sample;
|
||||
bool result = AllyOpticalSample.TryParse(corrupt, DateTime.UtcNow, out sample);
|
||||
|
||||
Assert.IsFalse(result);
|
||||
Assert.IsNull(sample);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryParse_InvalidStructure_ReturnsFalse()
|
||||
{
|
||||
AllyOpticalSample sample;
|
||||
bool result = AllyOpticalSample.TryParse(
|
||||
"000000 0000 0000 000001 0000 00000001 00\r\n",
|
||||
DateTime.UtcNow,
|
||||
out sample);
|
||||
|
||||
Assert.IsFalse(result);
|
||||
Assert.IsNull(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader
|
||||
{
|
||||
internal static class AllyOpticalTelegramFactory
|
||||
{
|
||||
public static string Create(short rawFlow, uint rawVolume, uint rawTimestamp)
|
||||
{
|
||||
string prefix = string.Join("\t", new[]
|
||||
{
|
||||
"000000",
|
||||
"0000",
|
||||
unchecked((ushort)rawFlow).ToString("X4", CultureInfo.InvariantCulture),
|
||||
rawVolume.ToString("X6", CultureInfo.InvariantCulture),
|
||||
"0000",
|
||||
rawTimestamp.ToString("X8", CultureInfo.InvariantCulture),
|
||||
string.Empty
|
||||
});
|
||||
|
||||
byte checksum = 0;
|
||||
for (int i = 0; i < prefix.Length; i++)
|
||||
checksum += (byte)prefix[i];
|
||||
|
||||
return prefix + checksum.ToString("X2", CultureInfo.InvariantCulture) + "\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
87
TBFTests/Rig/RegisterReaders/AllyReader/AllyReaderCfgTest.cs
Normal file
87
TBFTests/Rig/RegisterReaders/AllyReader/AllyReaderCfgTest.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Common;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.AllyReader;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(AllyReaderCfg))]
|
||||
public class AllyReaderCfgTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void DefaultConfig_ContainsExpectedCommunicationSettings()
|
||||
{
|
||||
AllyReaderCfg config = CreateConfig();
|
||||
|
||||
Assert.AreEqual(1, config.CommandComPortNr);
|
||||
Assert.AreEqual(2400, config.CommandBaudRate);
|
||||
Assert.AreEqual(2, config.OptoComPortNr);
|
||||
Assert.AreEqual(9600, config.OptoBaudRate);
|
||||
Assert.AreEqual(AllyMeterSize.AutoDetect, config.ConfiguredMeterSize);
|
||||
Assert.AreEqual(1000D, config.MeterPulsesPerLiter);
|
||||
Assert.AreEqual(6, config.ParamsCount());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MeterSizeParameter_OffersOnlySupportedAllySizes()
|
||||
{
|
||||
ICollection<string> values = CreateConfig().ParamValues(4);
|
||||
|
||||
Assert.AreEqual(5, values.Count);
|
||||
CollectionAssert.Contains((System.Collections.ICollection)values, AllyMeterSize.FiveEighths.ToString());
|
||||
CollectionAssert.Contains((System.Collections.ICollection)values, AllyMeterSize.OneInch.ToString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateAndUpdateParameters_ValidValues_UpdateConfiguration()
|
||||
{
|
||||
AllyReaderCfg config = CreateConfig();
|
||||
string message;
|
||||
|
||||
Assert.IsTrue(config.ValidateParam(0, "7", out message));
|
||||
Assert.IsTrue(config.ValidateParam(4, AllyMeterSize.OneInch.ToString(), out message));
|
||||
Assert.IsTrue(config.ValidateParam(5, "2000", out message));
|
||||
Assert.IsFalse(config.ValidateParam(4, "Unsupported", out message));
|
||||
|
||||
Assert.AreEqual(CfgUpdateFlags.RestartRqrd, config.UpdateParam(0, "7"));
|
||||
config.UpdateParam(4, AllyMeterSize.OneInch.ToString());
|
||||
config.UpdateParam(5, "2000");
|
||||
|
||||
Assert.AreEqual(7, config.CommandComPortNr);
|
||||
Assert.AreEqual(AllyMeterSize.OneInch, config.ConfiguredMeterSize);
|
||||
Assert.AreEqual(2000D, config.MeterPulsesPerLiter);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serializer_RoundTrip_PreservesReaderSettings()
|
||||
{
|
||||
AllyReaderCfg source = CreateConfig();
|
||||
source.CommandComPortNr = 8;
|
||||
source.ConfiguredMeterSize = AllyMeterSize.ThreeQuarterLong;
|
||||
source.MeterPulsesPerLiter = 1234D;
|
||||
|
||||
string xml;
|
||||
using (StringWriter writer = new StringWriter())
|
||||
{
|
||||
source.GetSerializer().Serialize(writer, source);
|
||||
xml = writer.ToString();
|
||||
}
|
||||
|
||||
AllyReaderCfg restored;
|
||||
using (StringReader reader = new StringReader(xml))
|
||||
restored = (AllyReaderCfg)AllyReaderCfg.Serializer.Deserialize(reader);
|
||||
|
||||
Assert.AreEqual(8, restored.CommandComPortNr);
|
||||
Assert.AreEqual(AllyMeterSize.ThreeQuarterLong, restored.ConfiguredMeterSize);
|
||||
Assert.AreEqual(1234D, restored.MeterPulsesPerLiter);
|
||||
}
|
||||
|
||||
private static AllyReaderCfg CreateConfig()
|
||||
{
|
||||
return new AllyReaderCfg(new TBF.Rig.RegisterReaders.AllyReader.Factory());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(AllyCommandService))]
|
||||
public class AllyCommandServiceTest
|
||||
{
|
||||
private const int TimeoutMs = 1234;
|
||||
private FakeAllyTransport transport;
|
||||
private AllyCommandService service;
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
transport = new FakeAllyTransport
|
||||
{
|
||||
IsOpen = true,
|
||||
Response = AllyResponseFactory.CreateSuccess()
|
||||
};
|
||||
service = new AllyCommandService(transport);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadSerialNumber_ValidResponse_ReturnsValueAndBuildsSpecifiedFrame()
|
||||
{
|
||||
transport.Response = AllyResponseFactory.CreateAsciiSuccess("100104051");
|
||||
|
||||
string result = service.ReadSerialNumber(TimeoutMs);
|
||||
|
||||
Assert.AreEqual("100104051", result);
|
||||
AssertRequest(0x53, 0x57, 0x05, 0x01, 0x0D);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadVersionAndType_ValidResponse_ParsesAllFields()
|
||||
{
|
||||
transport.Response = AllyResponseFactory.CreateAsciiSuccess("TR9,SWM003,FW9");
|
||||
|
||||
AllyVersionInfo result = service.ReadVersionAndType(TimeoutMs);
|
||||
|
||||
Assert.AreEqual("TR9", result.TouchReadVersion);
|
||||
Assert.AreEqual("SWM003", result.DeviceType);
|
||||
Assert.AreEqual("FW9", result.FirmwareVersion);
|
||||
AssertRequest(0x53, 0x57, 0x05, 0x05, 0x0D);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReadSystemTimeAndRebootCount_ValidResponses_ParseLittleEndianPayloads()
|
||||
{
|
||||
transport.Response = AllyResponseFactory.CreateSuccess(0x80, 0x51, 0x01, 0x00);
|
||||
DateTime systemTime = service.ReadSystemTimeUtc(TimeoutMs);
|
||||
Assert.AreEqual(new DateTime(2000, 1, 2, 0, 0, 0, DateTimeKind.Utc), systemTime);
|
||||
AssertRequest(0x53, 0x57, 0x06, 0xFD, 0x10, 0x0D);
|
||||
|
||||
transport.Response = AllyResponseFactory.CreateSuccess(0x00);
|
||||
byte rebootCount = service.ReadRebootCount(TimeoutMs);
|
||||
Assert.AreEqual((byte)0, rebootCount);
|
||||
AssertRequest(0x53, 0x57, 0x06, 0xFD, 0x55, 0x0D);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SetupCommands_BuildExactUi2093Frames()
|
||||
{
|
||||
service.OpenValve(TimeoutMs);
|
||||
AssertRequest(0x53, 0x57, 0x07, 0x1E, 0x00, 0x02, 0x0D);
|
||||
|
||||
service.SetSpreadSpectrum(false, TimeoutMs);
|
||||
AssertRequest(0x53, 0x57, 0x08, 0xFD, 0x15, 0x06, 0x01, 0x0D);
|
||||
|
||||
service.SetSpreadSpectrum(true, TimeoutMs);
|
||||
AssertRequest(0x53, 0x57, 0x08, 0xFD, 0x15, 0x06, 0x00, 0x0D);
|
||||
|
||||
service.SetMeterMode(0x09, TimeoutMs);
|
||||
AssertRequest(0x53, 0x57, 0x06, 0x1A, 0x09, 0x0D);
|
||||
|
||||
service.SetDiagnosticLed(0xC2, TimeoutMs);
|
||||
AssertRequest(0x53, 0x57, 0x07, 0xFD, 0x60, 0xC2, 0x0D);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CalibrationFactor_WriteAndRead_UsesFactorTimes40Point96Encoding()
|
||||
{
|
||||
service.SetCalibrationFactorPercent(100D, TimeoutMs);
|
||||
AssertRequest(0x53, 0x57, 0x08, 0xFD, 0x54, 0x00, 0x10, 0x0D);
|
||||
|
||||
transport.Response = AllyResponseFactory.CreateSuccess(0x00, 0x10);
|
||||
double result = service.ReadCalibrationFactorPercent(TimeoutMs);
|
||||
|
||||
Assert.AreEqual(100D, result, 1E-9);
|
||||
AssertRequest(0x53, 0x57, 0x06, 0xFD, 0x53, 0x0D);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void SetCalibrationFactorPercent_Zero_ThrowsArgumentOutOfRangeException()
|
||||
{
|
||||
service.SetCalibrationFactorPercent(0D, TimeoutMs);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void SetDisplayVolume_InvalidDigitCount_ThrowsArgumentException()
|
||||
{
|
||||
service.SetDisplayVolume("123", TimeoutMs);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void StartOffsetLearning_ZeroSamples_ThrowsArgumentOutOfRangeException()
|
||||
{
|
||||
service.StartOffsetLearning(0, 1, TimeoutMs);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MagneticTamperProfiles_BuildExactUi2093Frames()
|
||||
{
|
||||
service.SetMagneticTamperProfile(AllyMagneticTamperProfile.Calibration, TimeoutMs);
|
||||
AssertRequest(0x53, 0x57, 0x0C, 0xFD, 0x15, 0x02, 0x5A, 0x0C, 0x05, 0x3C, 0x50, 0x0D);
|
||||
|
||||
service.SetMagneticTamperProfile(AllyMagneticTamperProfile.FirmwareDefaults, TimeoutMs);
|
||||
AssertRequest(0x53, 0x57, 0x0C, 0xFD, 0x15, 0x05, 0x1E, 0x04, 0x05, 0x3C, 0x50, 0x0D);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FinalizationCommands_BuildExactUi2093Frames()
|
||||
{
|
||||
service.SetDisplayVolume("01134010", TimeoutMs);
|
||||
AssertRequest(
|
||||
0x53, 0x57, 0x0E, 0x14,
|
||||
0x30, 0x31, 0x31, 0x33, 0x34, 0x30, 0x31, 0x30,
|
||||
0x00, 0x0D);
|
||||
|
||||
service.SetLcdTimeout(30, TimeoutMs);
|
||||
AssertRequest(0x53, 0x57, 0x07, 0xFD, 0x8C, 0x1E, 0x0D);
|
||||
|
||||
service.StartOffsetLearning(1000, 1, TimeoutMs);
|
||||
AssertRequest(0x53, 0x57, 0x0A, 0xFD, 0xD1, 0xE8, 0x03, 0x01, 0x00, 0x0D);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Send_ClosedTransport_OpensTransportAndPassesTimeout()
|
||||
{
|
||||
transport.IsOpen = false;
|
||||
|
||||
service.OpenValve(TimeoutMs);
|
||||
|
||||
Assert.AreEqual(1, transport.OpenCallCount);
|
||||
Assert.AreEqual(1, transport.SendAndWaitCallCount);
|
||||
Assert.AreEqual(TimeoutMs, transport.LastTimeoutMs);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Send_FailureStatus_ThrowsCommunicationExceptionWithStatus()
|
||||
{
|
||||
transport.Response = AllyResponseFactory.CreateFailure(0xFD);
|
||||
|
||||
try
|
||||
{
|
||||
service.OpenValve(TimeoutMs);
|
||||
Assert.Fail("Expected AllyCommunicationException.");
|
||||
}
|
||||
catch (AllyCommunicationException exception)
|
||||
{
|
||||
Assert.AreEqual((byte?)0xFD, exception.Status);
|
||||
StringAssert.Contains(exception.Message, "0xFD");
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertRequest(params byte[] expected)
|
||||
{
|
||||
CollectionAssert.AreEqual(expected, transport.LastRequest);
|
||||
Assert.AreEqual(TimeoutMs, transport.LastTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(AllyFrameParser))]
|
||||
public class AllyFrameParserTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ParseResponse_ValidFrame_ReturnsStatusAndPayload()
|
||||
{
|
||||
AllyFrameParser parser = new AllyFrameParser();
|
||||
|
||||
AllyResponse response = parser.ParseResponse(
|
||||
new byte[] { 0x53, 0x52, 0x07, 0x01, 0x34, 0x12, 0x0D });
|
||||
|
||||
Assert.IsTrue(response.IsSuccess);
|
||||
Assert.AreEqual((byte)0x01, response.Status);
|
||||
Assert.AreEqual((ushort)0x1234, response.GetUInt16LittleEndian());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ParseResponse_Null_ThrowsArgumentNullException()
|
||||
{
|
||||
new AllyFrameParser().ParseResponse(null);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void ParseResponse_InvalidLength_ThrowsFormatException()
|
||||
{
|
||||
new AllyFrameParser().ParseResponse(
|
||||
new byte[] { 0x53, 0x52, 0x06, 0x01, 0x0D });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void ParseResponse_InvalidDirection_ThrowsFormatException()
|
||||
{
|
||||
new AllyFrameParser().ParseResponse(
|
||||
new byte[] { 0x53, 0x57, 0x05, 0x01, 0x0D });
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void ParseResponse_InvalidEndByte_ThrowsFormatException()
|
||||
{
|
||||
new AllyFrameParser().ParseResponse(
|
||||
new byte[] { 0x53, 0x52, 0x05, 0x01, 0x00 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(AllyFrame))]
|
||||
public class AllyFrameTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void ToArray_InformationBytes_CalculatesLengthAndAddsEnvelope()
|
||||
{
|
||||
AllyFrame frame = new AllyFrame(0x57, new byte[] { 0xFD, 0x54, 0x00, 0x10 });
|
||||
|
||||
byte[] result = frame.ToArray();
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
new byte[] { 0x53, 0x57, 0x08, 0xFD, 0x54, 0x00, 0x10, 0x0D },
|
||||
result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void ToArray_InformationExceedsProtocolLength_ThrowsInvalidOperationException()
|
||||
{
|
||||
new AllyFrame(0x57, new byte[252]).ToArray();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void FrameBuilder_PayloadBeforeCommand_ThrowsInvalidOperationException()
|
||||
{
|
||||
new AllyFrameBuilder().WithByte(0x01);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
||||
{
|
||||
internal static class AllyResponseFactory
|
||||
{
|
||||
public static byte[] CreateSuccess(params byte[] payload)
|
||||
{
|
||||
return Create(0x01, payload);
|
||||
}
|
||||
|
||||
public static byte[] CreateFailure(byte status, params byte[] payload)
|
||||
{
|
||||
return Create(status, payload);
|
||||
}
|
||||
|
||||
public static byte[] CreateAsciiSuccess(string value)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
List<byte> payload = new List<byte>(System.Text.Encoding.ASCII.GetBytes(value));
|
||||
payload.Add(0x00);
|
||||
return CreateSuccess(payload.ToArray());
|
||||
}
|
||||
|
||||
private static byte[] Create(byte status, params byte[] payload)
|
||||
{
|
||||
payload = payload ?? Array.Empty<byte>();
|
||||
List<byte> response = new List<byte>
|
||||
{
|
||||
0x53,
|
||||
0x52,
|
||||
0x00,
|
||||
status
|
||||
};
|
||||
|
||||
response.AddRange(payload);
|
||||
response.Add(0x0D);
|
||||
response[2] = checked((byte)response.Count);
|
||||
return response.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(AllySerialTransportBuilder))]
|
||||
public class AllySerialTransportBuilderTest
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void Build_MissingPort_ThrowsInvalidOperationException()
|
||||
{
|
||||
new AllySerialTransportBuilder().Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void Build_InvalidBaudRate_ThrowsInvalidOperationException()
|
||||
{
|
||||
new AllySerialTransportBuilder()
|
||||
.WithPort("COM1")
|
||||
.WithBaudRate(0)
|
||||
.Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Build_ValidSettings_CreatesClosedTransportWithoutOpeningHardware()
|
||||
{
|
||||
AllySerialTransport transport = new AllySerialTransportBuilder()
|
||||
.WithPort("COM1")
|
||||
.WithBaudRate(2400)
|
||||
.Build();
|
||||
|
||||
Assert.IsFalse(transport.IsOpen);
|
||||
transport.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(AllyVersionInfo))]
|
||||
public class AllyVersionInfoTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void Parse_ThreeFields_TrimsAndMapsValues()
|
||||
{
|
||||
AllyVersionInfo result = AllyVersionInfo.Parse(" TR9 , SWM003 , FW9 ");
|
||||
|
||||
Assert.AreEqual("TR9", result.TouchReadVersion);
|
||||
Assert.AreEqual("SWM003", result.DeviceType);
|
||||
Assert.AreEqual("FW9", result.FirmwareVersion);
|
||||
Assert.AreEqual("TR9 , SWM003 , FW9", result.RawValue);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void Parse_MissingField_ThrowsFormatException()
|
||||
{
|
||||
AllyVersionInfo.Parse("TR9,SWM003");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void Parse_EmptyField_ThrowsFormatException()
|
||||
{
|
||||
AllyVersionInfo.Parse("TR9,,FW9");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader.communication
|
||||
{
|
||||
internal sealed class FakeAllyTransport : IAllyTransport
|
||||
{
|
||||
public bool IsOpen { get; set; }
|
||||
public int OpenCallCount { get; private set; }
|
||||
public int SendAndWaitCallCount { get; private set; }
|
||||
public int DisposeCallCount { get; private set; }
|
||||
public byte[] Response { get; set; }
|
||||
public byte[] LastRequest { get; private set; }
|
||||
public int LastTimeoutMs { get; private set; }
|
||||
|
||||
public void Open()
|
||||
{
|
||||
OpenCallCount++;
|
||||
IsOpen = true;
|
||||
}
|
||||
|
||||
public byte[] SendAndWait(byte[] request, int timeoutMs)
|
||||
{
|
||||
SendAndWaitCallCount++;
|
||||
LastRequest = request;
|
||||
LastTimeoutMs = timeoutMs;
|
||||
return Response;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeCallCount++;
|
||||
IsOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using Common;
|
||||
using TBF.Rig.RegisterReaders.AllyReader;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
||||
{
|
||||
internal sealed class AllyHardwareIntegrationSettings
|
||||
{
|
||||
public bool Enabled { get; private set; }
|
||||
public int CommandPortNumber { get; private set; }
|
||||
public int OpticalPortNumber { get; private set; }
|
||||
public int CommandBaudRate { get; private set; }
|
||||
public int OpticalBaudRate { get; private set; }
|
||||
public int CommandTimeoutMs { get; private set; }
|
||||
public int OpticalCaptureSeconds { get; private set; }
|
||||
public int ActiveModeSettleMs { get; private set; }
|
||||
public AllyMeterSize MeterSize { get; private set; }
|
||||
|
||||
public string CommandPortName { get { return "COM" + CommandPortNumber; } }
|
||||
public string OpticalPortName { get { return "COM" + OpticalPortNumber; } }
|
||||
|
||||
public static AllyHardwareIntegrationSettings FromEnvironment()
|
||||
{
|
||||
return new AllyHardwareIntegrationSettings
|
||||
{
|
||||
Enabled = ParseEnabled(Environment.GetEnvironmentVariable("ALLY_HW_TESTS")),
|
||||
CommandPortNumber = ParsePort("ALLY_COMMAND_PORT", "COM3"),
|
||||
OpticalPortNumber = ParsePort("ALLY_OPTICAL_PORT", "COM4"),
|
||||
CommandBaudRate = ParsePositiveInt("ALLY_COMMAND_BAUD", 2400),
|
||||
OpticalBaudRate = ParsePositiveInt("ALLY_OPTICAL_BAUD", 9600),
|
||||
CommandTimeoutMs = ParsePositiveInt("ALLY_COMMAND_TIMEOUT_MS", 5000),
|
||||
OpticalCaptureSeconds = ParsePositiveInt("ALLY_OPTICAL_CAPTURE_SECONDS", 10),
|
||||
ActiveModeSettleMs = ParsePositiveInt("ALLY_ACTIVE_SETTLE_MS", 1000),
|
||||
MeterSize = ParseMeterSize(Environment.GetEnvironmentVariable("ALLY_METER_SIZE"))
|
||||
};
|
||||
}
|
||||
|
||||
public AllyReaderCfg CreateReaderConfig()
|
||||
{
|
||||
return new AllyReaderCfg(new TBF.Rig.RegisterReaders.AllyReader.Factory())
|
||||
{
|
||||
CommandComPortNr = CommandPortNumber,
|
||||
CommandBaudRate = CommandBaudRate,
|
||||
OptoComPortNr = OpticalPortNumber,
|
||||
OptoBaudRate = OpticalBaudRate,
|
||||
ConfiguredMeterSize = MeterSize,
|
||||
MeterPulsesPerLiter = 1000D,
|
||||
DebugLevel = DebugMode.Normal
|
||||
};
|
||||
}
|
||||
|
||||
private static bool ParseEnabled(string value)
|
||||
{
|
||||
return string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(value, "yes", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static int ParsePort(string variableName, string defaultValue)
|
||||
{
|
||||
string value = Environment.GetEnvironmentVariable(variableName) ?? defaultValue;
|
||||
if (value.StartsWith("COM", StringComparison.OrdinalIgnoreCase))
|
||||
value = value.Substring(3);
|
||||
|
||||
int portNumber;
|
||||
if (!int.TryParse(value, out portNumber) || portNumber <= 0)
|
||||
throw new InvalidOperationException(variableName + " must contain COM<n> or a positive port number.");
|
||||
return portNumber;
|
||||
}
|
||||
|
||||
private static int ParsePositiveInt(string variableName, int defaultValue)
|
||||
{
|
||||
string value = Environment.GetEnvironmentVariable(variableName);
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return defaultValue;
|
||||
|
||||
int parsed;
|
||||
if (!int.TryParse(value, out parsed) || parsed <= 0)
|
||||
throw new InvalidOperationException(variableName + " must be a positive integer.");
|
||||
return parsed;
|
||||
}
|
||||
|
||||
private static AllyMeterSize ParseMeterSize(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return AllyMeterSize.FiveEighths;
|
||||
|
||||
AllyMeterSize result;
|
||||
if (!Enum.TryParse(value, true, out result) || result == AllyMeterSize.AutoDetect)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"ALLY_METER_SIZE must be a supported explicit size; AutoDetect requires UI-2031 parsing.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,331 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.AllyReader;
|
||||
using TBF.Rig.RegisterReaders.AllyReader.Communication;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("HardwareIntegration")]
|
||||
[DoNotParallelize]
|
||||
public class AllyHardwareIntegrationTest
|
||||
{
|
||||
private const byte ActiveMeterMode = 0x02;
|
||||
private const byte InitialMeterMode = 0x09;
|
||||
|
||||
private AllyHardwareIntegrationSettings settings;
|
||||
private AllyIntegrationTestLogger logger;
|
||||
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
[TestInitialize]
|
||||
public void InitializeTest()
|
||||
{
|
||||
settings = AllyHardwareIntegrationSettings.FromEnvironment();
|
||||
logger = new AllyIntegrationTestLogger(TestContext);
|
||||
logger.Log(string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Configuration | command={0}/{1}, optical={2}/{3}, size={4}, timeout={5} ms, capture={6} s",
|
||||
settings.CommandPortName,
|
||||
settings.CommandBaudRate,
|
||||
settings.OpticalPortName,
|
||||
settings.OpticalBaudRate,
|
||||
settings.MeterSize,
|
||||
settings.CommandTimeoutMs,
|
||||
settings.OpticalCaptureSeconds));
|
||||
|
||||
if (!settings.Enabled)
|
||||
{
|
||||
logger.Log("SKIP | Hardware tests are disabled. Set ALLY_HW_TESTS=1 to enable them.");
|
||||
Assert.Inconclusive("Set ALLY_HW_TESTS=1 to run ALLY hardware integration tests.");
|
||||
}
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void CleanupTest()
|
||||
{
|
||||
if (logger != null)
|
||||
logger.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CommandPort_ReadIdentityAndHealth_LogsParsedResponses()
|
||||
{
|
||||
RequirePorts(settings.CommandPortName);
|
||||
AllyMeterReader reader = CreateReader();
|
||||
reader.StartSession();
|
||||
try
|
||||
{
|
||||
string serialNumber = logger.Step(
|
||||
"Read manufacturing serial number",
|
||||
() => reader.ReadSerialNumber(settings.CommandTimeoutMs));
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(serialNumber), "The manufacturing serial number is empty.");
|
||||
|
||||
AllyVersionInfo version = logger.Step(
|
||||
"Read and parse version/type",
|
||||
() => reader.ReadVersionAndType(settings.CommandTimeoutMs),
|
||||
value => string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"raw={0}; TouchRead={1}; type={2}; firmware={3}; parse=PASS",
|
||||
value.RawValue,
|
||||
value.TouchReadVersion,
|
||||
value.DeviceType,
|
||||
value.FirmwareVersion));
|
||||
Assert.AreEqual("SWM003", version.DeviceType, "The connected device is not an ALLY meter.");
|
||||
|
||||
logger.Step("Read meter system time (UTC)", () => reader.ReadSystemTimeUtc(settings.CommandTimeoutMs),
|
||||
value => value.ToString("O", CultureInfo.InvariantCulture) + "; parse=PASS");
|
||||
logger.Step("Read reboot count", () => reader.ReadRebootCount(settings.CommandTimeoutMs),
|
||||
value => value.ToString(CultureInfo.InvariantCulture) + "; parse=PASS");
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.EndSession();
|
||||
logger.Log("SESSION | command port closed");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CommandPort_ActiveModeRoundTrip_AlwaysRestoresInitialMode()
|
||||
{
|
||||
RequirePorts(settings.CommandPortName);
|
||||
AllyMeterReader reader = CreateReader();
|
||||
reader.StartSession();
|
||||
try
|
||||
{
|
||||
logger.Step(
|
||||
"Set active meter mode 0x02",
|
||||
() => reader.SetMeterMode(ActiveMeterMode, settings.CommandTimeoutMs));
|
||||
logger.Log("WAIT | active-mode settling for " + settings.ActiveModeSettleMs + " ms");
|
||||
Thread.Sleep(settings.ActiveModeSettleMs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.Step(
|
||||
"Restore initial meter mode 0x09",
|
||||
() => reader.SetMeterMode(InitialMeterMode, settings.CommandTimeoutMs));
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.EndSession();
|
||||
logger.Log("SESSION | command port closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OpticalPort_CaptureAndParseTelegrams_LogsRawAndParseResult()
|
||||
{
|
||||
RequirePorts(settings.OpticalPortName);
|
||||
IList<AllyOpticalSample> samples = CaptureOpticalTelegramsDirectly();
|
||||
Assert.IsTrue(samples.Count > 0, "No valid ALLY optical telegram was parsed.");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Complex_ReadSerialActivateCaptureParseAndDeactivate_LogsWholeWorkflow()
|
||||
{
|
||||
RequirePorts(settings.CommandPortName, settings.OpticalPortName);
|
||||
AllyMeterReader reader = CreateReader();
|
||||
bool streamStarted = false;
|
||||
reader.StartSession();
|
||||
try
|
||||
{
|
||||
string serialNumber = logger.Step(
|
||||
"Read manufacturing serial number",
|
||||
() => reader.ReadSerialNumber(settings.CommandTimeoutMs));
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(serialNumber), "The manufacturing serial number is empty.");
|
||||
|
||||
logger.Step(
|
||||
"Set active meter mode 0x02",
|
||||
() => reader.SetMeterMode(ActiveMeterMode, settings.CommandTimeoutMs));
|
||||
logger.Log("WAIT | active-mode settling for " + settings.ActiveModeSettleMs + " ms");
|
||||
Thread.Sleep(settings.ActiveModeSettleMs);
|
||||
|
||||
logger.Step("Open optical stream and start measurement", reader.Start);
|
||||
streamStarted = true;
|
||||
CaptureThroughReader(reader);
|
||||
|
||||
IReadOnlyList<AllyOpticalSample> samples = reader.OpticalSamples;
|
||||
Assert.IsTrue(samples.Count >= 2, "At least two valid optical samples are required for a measurement.");
|
||||
Assert.IsFalse(reader.NoSamples, "The reader did not establish a valid optical measurement interval.");
|
||||
logger.Log(string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"MEASUREMENT | PASS | serial={0}, samples={1}, volume={2:R} l, elapsed={3:R} s",
|
||||
serialNumber,
|
||||
samples.Count,
|
||||
reader.WMVolume,
|
||||
reader.TimestampSecEnd - reader.TimestampSecStart));
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (streamStarted)
|
||||
logger.Step("Stop optical measurement and close COM4", reader.Stop);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.Step(
|
||||
"Restore initial meter mode 0x09",
|
||||
() => reader.SetMeterMode(InitialMeterMode, settings.CommandTimeoutMs));
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.EndSession();
|
||||
logger.Log("SESSION | all ports closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AllyMeterReader CreateReader()
|
||||
{
|
||||
AllyMeterReader reader = new AllyMeterReader(settings.CreateReaderConfig());
|
||||
logger.Step("Initialize ALLY reader", reader.Initialize);
|
||||
return reader;
|
||||
}
|
||||
|
||||
private IList<AllyOpticalSample> CaptureOpticalTelegramsDirectly()
|
||||
{
|
||||
List<AllyOpticalSample> samples = new List<AllyOpticalSample>();
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
int parsedLines = 0;
|
||||
int rejectedLines = 0;
|
||||
|
||||
using (SerialPort port = new SerialPort(
|
||||
settings.OpticalPortName,
|
||||
settings.OpticalBaudRate,
|
||||
Parity.None,
|
||||
8,
|
||||
StopBits.One))
|
||||
{
|
||||
logger.Step("Open optical source " + settings.OpticalPortName, port.Open);
|
||||
try
|
||||
{
|
||||
port.DiscardInBuffer();
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
while (stopwatch.Elapsed < TimeSpan.FromSeconds(settings.OpticalCaptureSeconds))
|
||||
{
|
||||
string text = port.ReadExisting();
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
buffer.Append(text);
|
||||
|
||||
string line;
|
||||
while (TryTakeLine(buffer, out line))
|
||||
{
|
||||
AllyOpticalSample sample;
|
||||
bool parsed = AllyOpticalSample.TryParse(line, DateTime.UtcNow, out sample);
|
||||
logger.OpticalParse(line, parsed, sample);
|
||||
if (parsed)
|
||||
{
|
||||
parsedLines++;
|
||||
samples.Add(sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
rejectedLines++;
|
||||
}
|
||||
}
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
port.Close();
|
||||
logger.Log("PORT | optical source closed");
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
logger.Log("OPTO PARTIAL | trailing incomplete data: " + buffer.ToString()
|
||||
.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t"));
|
||||
}
|
||||
logger.Log(string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"OPTO SUMMARY | valid={0}, rejected={1}, partialChars={2}",
|
||||
parsedLines,
|
||||
rejectedLines,
|
||||
buffer.Length));
|
||||
return samples;
|
||||
}
|
||||
|
||||
private void CaptureThroughReader(AllyMeterReader reader)
|
||||
{
|
||||
int loggedSampleCount = 0;
|
||||
string lastRawLine = null;
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
while (stopwatch.Elapsed < TimeSpan.FromSeconds(settings.OpticalCaptureSeconds))
|
||||
{
|
||||
reader.RunDeviceBefore();
|
||||
|
||||
string rawLine = reader.ReadOptoData();
|
||||
if (!string.IsNullOrEmpty(rawLine) && rawLine != lastRawLine)
|
||||
{
|
||||
AllyOpticalSample parsedSample;
|
||||
bool parsed = AllyOpticalSample.TryParse(rawLine, DateTime.UtcNow, out parsedSample);
|
||||
logger.OpticalParse(rawLine, parsed, parsedSample);
|
||||
lastRawLine = rawLine;
|
||||
}
|
||||
|
||||
IReadOnlyList<AllyOpticalSample> currentSamples = reader.OpticalSamples;
|
||||
while (loggedSampleCount < currentSamples.Count)
|
||||
{
|
||||
AllyOpticalSample sample = currentSamples[loggedSampleCount++];
|
||||
logger.Log(string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"OPTO SAMPLE | index={0}, flow={1}, rawVolume={2}, rawTimestamp={3}, liters={4:R}, seconds={5:R}",
|
||||
loggedSampleCount,
|
||||
sample.RawFlow,
|
||||
sample.RawVolume,
|
||||
sample.RawTimestamp,
|
||||
sample.ExtendedVolumeLiters,
|
||||
sample.ElapsedSeconds));
|
||||
}
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
logger.Log("OPTO CAPTURE | completed; parsed samples=" + loggedSampleCount);
|
||||
}
|
||||
|
||||
private void RequirePorts(params string[] requiredPorts)
|
||||
{
|
||||
string[] available = SerialPort.GetPortNames();
|
||||
logger.Log("PORTS | available=" + (available.Length == 0 ? "<none>" : string.Join(", ", available)));
|
||||
string[] missing = requiredPorts
|
||||
.Where(required => !available.Contains(required, StringComparer.OrdinalIgnoreCase))
|
||||
.ToArray();
|
||||
if (missing.Length == 0)
|
||||
return;
|
||||
|
||||
string message = "Required ALLY port(s) are not connected: " + string.Join(", ", missing) + ".";
|
||||
logger.Log("SKIP | " + message);
|
||||
Assert.Inconclusive(message);
|
||||
}
|
||||
|
||||
private static bool TryTakeLine(StringBuilder buffer, out string line)
|
||||
{
|
||||
string text = buffer.ToString();
|
||||
int lineEnd = text.IndexOf('\n');
|
||||
if (lineEnd < 0)
|
||||
{
|
||||
line = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
line = text.Substring(0, lineEnd + 1);
|
||||
buffer.Remove(0, lineEnd + 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.AllyReader;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.AllyReader.Integration
|
||||
{
|
||||
internal sealed class AllyIntegrationTestLogger : IDisposable
|
||||
{
|
||||
private readonly TestContext context;
|
||||
private readonly StreamWriter writer;
|
||||
|
||||
public AllyIntegrationTestLogger(TestContext context)
|
||||
{
|
||||
this.context = context;
|
||||
string directory = string.IsNullOrWhiteSpace(context.ResultsDirectory) ? Path.Combine(Path.GetTempPath(), "TBFTests", "AllyIntegration") : context.ResultsDirectory;
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
string testName = SanitizeFileName(context.TestName ?? "AllyIntegration");
|
||||
string path = Path.Combine( directory, testName + "_" + DateTime.Now.ToString("yyyyMMdd_HHmmss_fff", CultureInfo.InvariantCulture) + ".log");
|
||||
writer = new StreamWriter(path, false) { AutoFlush = true };
|
||||
Log("Log file: " + path);
|
||||
}
|
||||
|
||||
public void Log(string message)
|
||||
{
|
||||
string line = string.Format( CultureInfo.InvariantCulture, "{0:O} | {1}", DateTime.Now, message);
|
||||
context.WriteLine(line);
|
||||
Console.WriteLine(line);
|
||||
writer.WriteLine(line);
|
||||
}
|
||||
|
||||
public T Step<T>(string name, Func<T> action, Func<T, string> formatResult = null)
|
||||
{
|
||||
Log("STEP START | " + name);
|
||||
try
|
||||
{
|
||||
T result = action();
|
||||
string response = formatResult == null
|
||||
? Convert.ToString(result, CultureInfo.InvariantCulture)
|
||||
: formatResult(result);
|
||||
Log("STEP PASS | " + name + " | meter response: " + Escape(response));
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log("STEP FAIL | " + name + " | " + ex.GetType().Name + ": " + ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Step(string name, Action action)
|
||||
{
|
||||
Step( name, () => { action(); return "ACK"; });
|
||||
}
|
||||
|
||||
public void OpticalParse(string rawLine, bool parsed, AllyOpticalSample sample)
|
||||
{
|
||||
Log("OPTO RAW | " + Escape(rawLine));
|
||||
if (!parsed || sample == null)
|
||||
{
|
||||
Log("OPTO PARSE | FAIL");
|
||||
return;
|
||||
}
|
||||
|
||||
Log(string.Format( CultureInfo.InvariantCulture, "OPTO PARSE | PASS | flow={0}, volume=0x{1:X6} ({1}), timestamp=0x{2:X8} ({2})",
|
||||
sample.RawFlow,
|
||||
sample.RawVolume,
|
||||
sample.RawTimestamp));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
writer.Dispose();
|
||||
}
|
||||
|
||||
private static string Escape(string value)
|
||||
{
|
||||
if (value == null)
|
||||
return "<null>";
|
||||
return value.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t");
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string value)
|
||||
{
|
||||
foreach (char invalid in Path.GetInvalidFileNameChars())
|
||||
value = value.Replace(invalid, '_');
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.TestMethods.AllyCalibration;
|
||||
|
||||
namespace TBFTests.Rig.TestMethods.AllyCalibration
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(TestMethodCfg))]
|
||||
public class TestMethodConfigTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void DefaultConfig_UsesUi2093CommunicationDefaults()
|
||||
{
|
||||
TestMethodCfg config = new TestMethodCfg(
|
||||
new TBF.Rig.TestMethods.AllyCalibration.Factory());
|
||||
|
||||
Assert.AreEqual(5000, config.CommandTimeoutMs);
|
||||
Assert.AreEqual(4, config.MaxAttempts);
|
||||
Assert.AreEqual(250, config.DelayBetweenAttemptsMs);
|
||||
Assert.AreEqual("SWM003", config.ExpectedDeviceType);
|
||||
Assert.AreEqual(string.Empty, config.ExpectedFirmwareVersion);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestMethodParams_ExposeOnlyAllyActivitiesAndValidateFactor()
|
||||
{
|
||||
TestMethodParams parameters = new TestMethodParams(true);
|
||||
ICollection<string> activities = parameters.ParamValues(0);
|
||||
|
||||
Assert.AreEqual(19, activities.Count);
|
||||
CollectionAssert.Contains((System.Collections.ICollection)activities, "Read serial number");
|
||||
CollectionAssert.Contains((System.Collections.ICollection)activities, "Start offset learning");
|
||||
CollectionAssert.DoesNotContain((System.Collections.ICollection)activities, "Q2 correction");
|
||||
|
||||
string message;
|
||||
Assert.IsTrue(parameters.ValidateParam(1, "100", out message));
|
||||
Assert.IsFalse(parameters.ValidateParam(1, "0", out message));
|
||||
Assert.IsFalse(parameters.ValidateParam(1, "1600", out message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,6 +136,22 @@
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonReader\UniHeadTestCtrlTest.cs" />
|
||||
<Compile Include="Rig\Scales\MettlerToledo\ReadStableMassOpTest.cs" />
|
||||
<Compile Include="Rig\TestMethods\GenesisCommunication\GenesisHead\GenesisHeadBatchIntegrationTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyMeterReaderTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyOpticalSampleTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyOpticalTelegramFactory.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\AllyReaderCfgTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\communication\AllyCommandServiceTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\communication\AllyFrameTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\communication\AllyFrameParserTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\communication\AllyResponseFactory.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\communication\AllySerialTransportBuilderTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\communication\AllyVersionInfoTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\communication\FakeAllyTransport.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\communication\AllyIntegrationTests.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\integration\AllyHardwareIntegrationSettings.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\integration\AllyIntegrationTestLogger.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\integration\AllyHardwareIntegrationTest.cs" />
|
||||
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethodConfigTest.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\common\OptoTelegramRawTest.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\FakeSerialDriver.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\IperlResponseFactory.cs" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user