Poseidon move from feature/morisville/fix_cli_timeout
This commit is contained in:
parent
e4d4d75642
commit
8d42232589
@ -444,15 +444,19 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
if(poseidonReader == null)
|
||||
continue;
|
||||
poseidonReader.SetCliLogging(CliLogging);
|
||||
if (!(poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.ReadDatastream_Done
|
||||
|| poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.ReadDatastream_Running))
|
||||
// A terminal reader must not be armed again. Re-starting Error/Done
|
||||
// launched the CLI on every polling iteration and kept the dialog loading.
|
||||
if (poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.None)
|
||||
{
|
||||
log.DebugFormat("Poseidon read: starting {0} for {1}", currentOp, poseidonReader.Name);
|
||||
poseidonReader.SetCurrentOp((currentOp == CurrentOp.ReadDatastream_StartStates)?
|
||||
PoseidonReader.CurrentPoseidonOp.ReadDataStream_Start :
|
||||
PoseidonReader.CurrentPoseidonOp.ReadDataStream_End);
|
||||
}
|
||||
/// Send start data stream
|
||||
poseidonReader.Run();
|
||||
if (poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Error)
|
||||
log.ErrorFormat("Poseidon read: {0} completed with Error during {1}", poseidonReader.Name, currentOp);
|
||||
if (!(poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Done
|
||||
|| poseidonReader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Error))
|
||||
{
|
||||
@ -462,6 +466,7 @@ namespace TBF.Rig.DataEntry.PoseidonCmd
|
||||
}
|
||||
if (bAllReadersFinished)
|
||||
{
|
||||
log.DebugFormat("Poseidon read: all readers finished for {0}", currentOp);
|
||||
finishedReading = true;
|
||||
}
|
||||
else
|
||||
|
||||
@ -222,6 +222,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
WorkingDirectory = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(fileName)),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
@ -258,9 +259,18 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
string allOutput = (stdOutTask.Result ?? "") + (stdErrTask.Result ?? "");
|
||||
info.ExitCode = process.ExitCode;
|
||||
info.StandardOutput = stdOutTask.Result ?? "";
|
||||
info.StandardError = stdErrTask.Result ?? "";
|
||||
string allOutput = info.StandardOutput + info.StandardError;
|
||||
log?.Debug(allOutput);
|
||||
|
||||
if (info.ExitCode != 0)
|
||||
{
|
||||
info.FailureReason = $"CLI exited with exit code {info.ExitCode}.";
|
||||
log?.Error($"{info.Name}: {info.FailureReason} stderr='{info.StandardError}'");
|
||||
}
|
||||
|
||||
info.State = CliTaskState.Completed;
|
||||
return allOutput;
|
||||
}
|
||||
@ -311,6 +321,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = args,
|
||||
WorkingDirectory = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(fileName)),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
@ -347,9 +358,20 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
incommingTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
string allOutput = (stdoutTask.Result ?? "") + (stderrTask.Result ?? "");
|
||||
info.ExitCode = process.ExitCode;
|
||||
info.StandardOutput = stdoutTask.Result ?? "";
|
||||
info.StandardError = stderrTask.Result ?? "";
|
||||
string allOutput = info.StandardOutput + info.StandardError;
|
||||
log?.Debug(allOutput);
|
||||
|
||||
if (info.ExitCode != 0)
|
||||
{
|
||||
info.FailureReason = $"CLI exited with exit code {info.ExitCode}.";
|
||||
log?.Error($"{info.Name}: {info.FailureReason} stderr='{info.StandardError}'");
|
||||
info.State = CliTaskState.Completed;
|
||||
return default(T);
|
||||
}
|
||||
|
||||
string json = ExtractJson(allOutput);
|
||||
T result;
|
||||
if (TryJsonStringDeserialize(json, out result))
|
||||
@ -358,6 +380,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
return result;
|
||||
}
|
||||
|
||||
info.FailureReason = "CLI completed without a valid JSON response.";
|
||||
log?.Error($"{info.Name}: {info.FailureReason} stdout='{info.StandardOutput}' stderr='{info.StandardError}'");
|
||||
info.State = CliTaskState.Completed;
|
||||
return default(T);
|
||||
}
|
||||
@ -380,18 +404,17 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
public bool TryJsonStringDeserialize<T>(string json, out T value) where T : new()
|
||||
{
|
||||
if (json != null)
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
try
|
||||
{
|
||||
value = JsonConvert.DeserializeObject<T>(json);
|
||||
return true;
|
||||
return value != null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.Debug(ex.Message);
|
||||
value = TryConvert<T>(json);
|
||||
return true;
|
||||
return TryConvert(json, out value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -399,7 +422,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
return false;
|
||||
}
|
||||
|
||||
private static T TryConvert<T>(string json) where T : new()
|
||||
private static bool TryConvert<T>(string json, out T value) where T : new()
|
||||
{
|
||||
T obj = new T();
|
||||
|
||||
@ -417,21 +440,24 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
try
|
||||
{
|
||||
object value = token.ToObject(prop.PropertyType);
|
||||
prop.SetValue(obj, value);
|
||||
object propertyValue = token.ToObject(prop.PropertyType);
|
||||
prop.SetValue(obj, propertyValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = obj;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"TryConvert failed: {ex.Message}");
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public string ExtractJson(string text)
|
||||
|
||||
@ -11,6 +11,10 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
public Process Process { get; set; }
|
||||
public CliTaskState State { get; set; } = CliTaskState.Running;
|
||||
public string Name { get; set; }
|
||||
public int? ExitCode { get; set; }
|
||||
public string StandardOutput { get; set; }
|
||||
public string StandardError { get; set; }
|
||||
public string FailureReason { get; set; }
|
||||
|
||||
public bool UseResult
|
||||
{
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TBF.Rig;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
public interface IPoseidonReadOperation
|
||||
{
|
||||
string Name { get; }
|
||||
bool IsNotStarted { get; }
|
||||
bool IsFinished { get; }
|
||||
bool HasError { get; }
|
||||
void Start(bool readStart);
|
||||
Event Run();
|
||||
}
|
||||
|
||||
public sealed class PoseidonReaderOperation : IPoseidonReadOperation
|
||||
{
|
||||
private readonly PoseidonReader reader;
|
||||
public PoseidonReaderOperation(PoseidonReader reader)
|
||||
{
|
||||
if (reader == null) throw new ArgumentNullException(nameof(reader));
|
||||
this.reader = reader;
|
||||
}
|
||||
public string Name { get { return reader.Name; } }
|
||||
public bool IsNotStarted { get { return reader.CurrentOp == PoseidonReader.CurrentPoseidonOp.None; } }
|
||||
public bool IsFinished { get { return reader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Done || reader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Error; } }
|
||||
public bool HasError { get { return reader.CurrentOp == PoseidonReader.CurrentPoseidonOp.Error; } }
|
||||
public void Start(bool readStart) { reader.SetCurrentOp(readStart ? PoseidonReader.CurrentPoseidonOp.ReadDataStream_Start : PoseidonReader.CurrentPoseidonOp.ReadDataStream_End); }
|
||||
public Event Run() { return reader.Run(); }
|
||||
}
|
||||
|
||||
public static class PoseidonReadCycle
|
||||
{
|
||||
public static bool RunIteration(IEnumerable<IPoseidonReadOperation> readers, bool readStart)
|
||||
{
|
||||
if (readers == null) return true;
|
||||
bool allReadersFinished = true;
|
||||
foreach (IPoseidonReadOperation reader in readers)
|
||||
{
|
||||
if (reader == null) continue;
|
||||
if (reader.IsNotStarted) reader.Start(readStart);
|
||||
reader.Run();
|
||||
if (!reader.IsFinished) allReadersFinished = false;
|
||||
}
|
||||
return allReadersFinished;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
///
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Text.RegularExpressions;
|
||||
@ -62,12 +63,19 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
CurrentPoseidonOp _currentOp;
|
||||
private bool _isReadingStart = true;
|
||||
private bool? _isCliLogging;
|
||||
private bool lastCliReadingParsed;
|
||||
private string lastCliReadFailureReason;
|
||||
private bool lastCliReadSucceeded;
|
||||
|
||||
public CurrentPoseidonOp CurrentOp
|
||||
{
|
||||
get { return _currentOp; }
|
||||
}
|
||||
|
||||
public bool LastCliReadingParsed { get { return lastCliReadingParsed; } }
|
||||
public string LastCliReadFailureReason { get { return lastCliReadFailureReason; } }
|
||||
public bool LastCliReadSucceeded { get { return lastCliReadSucceeded; } }
|
||||
|
||||
public void SetCurrentOp(CurrentPoseidonOp operation = CurrentPoseidonOp.None)
|
||||
{
|
||||
_currentOp = operation ;
|
||||
@ -407,12 +415,18 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
else if (_currentOp == CurrentPoseidonOp.ReadDataStream_Start
|
||||
|| _currentOp == CurrentPoseidonOp.ReadDataStream_End)
|
||||
{
|
||||
lastCliReadingParsed = false;
|
||||
lastCliReadFailureReason = null;
|
||||
lastCliReadSucceeded = false;
|
||||
startTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
incommingTime = -1;
|
||||
_isReadingStart = (_currentOp == CurrentPoseidonOp.ReadDataStream_Start);
|
||||
|
||||
CliRunner.Clear();
|
||||
_lastOpTimedOut = false;
|
||||
log.DebugFormat("{0}: starting CLI read, direction={1}, path='{2}', args='{3}'",
|
||||
Name, _isReadingStart ? "start" : "end", serialPort.SerialPortCmdClientPath,
|
||||
serialPort.DefaultArgSettings(SerialPortData.EMeterArg.AllParams));
|
||||
CliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
|
||||
SerialPortData.EMeterArg.AllParams);
|
||||
_currentOp = CurrentPoseidonOp.ReadDatastream_Running;
|
||||
@ -449,6 +463,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
var task = (Task<JsonDataFromPoseidon>)firstTaskInfo.Task;
|
||||
data = task.Result;
|
||||
if (data == null)
|
||||
lastCliReadFailureReason = firstTaskInfo.FailureReason ?? "CLI returned no Poseidon JSON data.";
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -464,6 +480,15 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
string validationError;
|
||||
if (!TryValidateCliReadResponse(data, out validationError))
|
||||
{
|
||||
lastCliReadFailureReason = validationError;
|
||||
log.ErrorFormat("{0}: Poseidon {1} read rejected. {2}", Name,
|
||||
_isReadingStart ? "Begin" : "End", validationError);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(wmSerialNr))
|
||||
{
|
||||
try
|
||||
@ -477,14 +502,21 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
}
|
||||
|
||||
double volume;
|
||||
if (Double.TryParse(data.Reading, out volume))
|
||||
if (TryParseCliReading(data.Reading, out volume))
|
||||
{
|
||||
lastCliReadingParsed = true;
|
||||
lastCliReadSucceeded = true;
|
||||
double volumeLi = Units.ConvertFrom(Unit.USgal, volume);
|
||||
|
||||
if (_isReadingStart)
|
||||
beginWMState = volumeLi;
|
||||
else
|
||||
endWMState = volumeLi;
|
||||
log.InfoFormat("{0}: Poseidon {1} value stored. deviceId={2}, rawReading='{3}', gallons={4}, litres={5}, Begin={6}, End={7}",
|
||||
Name, _isReadingStart ? "Begin" : "End", data.DeviceId, data.Reading, volume, volumeLi, beginWMState, endWMState);
|
||||
}
|
||||
else
|
||||
lastCliReadFailureReason = "Reading could not be parsed: '" + data.Reading + "'.";
|
||||
}
|
||||
}
|
||||
|
||||
@ -497,6 +529,24 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
|
||||
}
|
||||
|
||||
public static bool TryParseCliReading(string reading, out double value)
|
||||
{
|
||||
value = 0;
|
||||
if (String.IsNullOrWhiteSpace(reading)) return false;
|
||||
return Double.TryParse(reading.Trim().Replace(',', '.'), NumberStyles.Float,
|
||||
CultureInfo.InvariantCulture, out value);
|
||||
}
|
||||
|
||||
public static bool TryValidateCliReadResponse(JsonDataFromPoseidon data, out string failureReason)
|
||||
{
|
||||
if (data == null) { failureReason = "CLI returned no JSON data."; return false; }
|
||||
if (data.NfcTagDetected != true) { failureReason = "NfcTagDetected is false or missing."; return false; }
|
||||
if (data.ReadingComplete != true) { failureReason = "ReadingComplete is false or missing."; return false; }
|
||||
if (String.IsNullOrWhiteSpace(data.Reading)) { failureReason = "Reading is empty."; return false; }
|
||||
failureReason = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Stop this operation</summary>
|
||||
public void Stop()
|
||||
|
||||
@ -5,6 +5,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
public class SerialPortData
|
||||
{
|
||||
public const string CliDirectory = @"C:\TBF\Cli";
|
||||
|
||||
private Boolean? _cliExists;
|
||||
public bool CliExists { get {
|
||||
if (_cliExists == null || !_cliExists.HasValue)
|
||||
@ -13,12 +15,16 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
}
|
||||
return _cliExists.Value;
|
||||
} }
|
||||
public string SerialPortCmdClientPath {
|
||||
#if DEBUG
|
||||
get { return Path.Combine("C:\\","TBF","Cli", CmdClientName);}
|
||||
#else
|
||||
get { return Path.Combine("..","Cli", CmdClientName);}
|
||||
#endif
|
||||
public string SerialPortCmdClientPath
|
||||
{
|
||||
get
|
||||
{
|
||||
string cliFileName = Path.GetFileName(CmdClientName);
|
||||
if (String.IsNullOrWhiteSpace(cliFileName))
|
||||
cliFileName = "HalCli.exe";
|
||||
|
||||
return Path.Combine(CliDirectory, cliFileName);
|
||||
}
|
||||
}
|
||||
public string CmdClientName { get; set; } = "HalCli.exe";
|
||||
public string PortName { get; set; }
|
||||
|
||||
@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TBF.Rig.GenericDevices;
|
||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||
|
||||
namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves persisted smart-reader selections. The visual dialog may have a
|
||||
/// fixed number of controls, but the persisted model contains only configured
|
||||
/// readers in each family.
|
||||
/// </summary>
|
||||
public static class SmartReaderSelection
|
||||
{
|
||||
private const string LegacyIPerlFamilyKey = "iperl";
|
||||
|
||||
public static void EnsureConfiguredReaders(LocalSettings settings, IEnumerable<ISmartReader> readers)
|
||||
{
|
||||
if (settings == null || readers == null)
|
||||
return;
|
||||
|
||||
List<ISmartReader> configuredReaders = readers.Where(reader => reader != null).ToList();
|
||||
if (configuredReaders.Count == 0)
|
||||
return;
|
||||
|
||||
SmartReaderSelectionSettings selections = GetOrCreateSettings(settings);
|
||||
bool migrateLegacyMask = !selections.LegacyOptoHeadsEnabledMigrated;
|
||||
string legacyFamily = configuredReaders.Any(reader => GetFamilyKey(reader) == LegacyIPerlFamilyKey)
|
||||
? LegacyIPerlFamilyKey
|
||||
: GetFamilyKey(configuredReaders[0]);
|
||||
|
||||
foreach (IGrouping<string, ISmartReader> family in configuredReaders.GroupBy(GetFamilyKey))
|
||||
{
|
||||
SmartReaderFamilySelection storedFamily = GetOrCreateFamily(selections, family.Key);
|
||||
int ordinal = 0;
|
||||
foreach (ISmartReader reader in family)
|
||||
{
|
||||
SmartReaderSelectionItem item = storedFamily.Readers
|
||||
.FirstOrDefault(candidate => candidate.ReaderKey == GetReaderKey(reader));
|
||||
if (item == null)
|
||||
{
|
||||
item = new SmartReaderSelectionItem
|
||||
{
|
||||
ReaderKey = GetReaderKey(reader),
|
||||
Enabled = !migrateLegacyMask || family.Key != legacyFamily ||
|
||||
IsLegacyMaskEnabled(settings.OptoHeadsEnabled, reader, ordinal)
|
||||
};
|
||||
storedFamily.Readers.Add(item);
|
||||
}
|
||||
ordinal++;
|
||||
}
|
||||
}
|
||||
|
||||
if (migrateLegacyMask)
|
||||
selections.LegacyOptoHeadsEnabledMigrated = true;
|
||||
}
|
||||
|
||||
public static long GetMask(LocalSettings settings, IEnumerable<ISmartReader> readers)
|
||||
{
|
||||
List<ISmartReader> familyReaders = ToReaderList(readers);
|
||||
if (familyReaders.Count == 0)
|
||||
return 0L;
|
||||
|
||||
EnsureConfiguredReaders(settings, familyReaders);
|
||||
long result = 0L;
|
||||
for (int index = 0; index < familyReaders.Count && index < 63; index++)
|
||||
{
|
||||
if (IsEnabled(settings, familyReaders[index]))
|
||||
result |= 1L << index;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void SetMask(LocalSettings settings, IEnumerable<ISmartReader> readers, long state)
|
||||
{
|
||||
List<ISmartReader> familyReaders = ToReaderList(readers);
|
||||
if (settings == null || familyReaders.Count == 0)
|
||||
return;
|
||||
|
||||
EnsureConfiguredReaders(settings, familyReaders);
|
||||
for (int index = 0; index < familyReaders.Count; index++)
|
||||
SetEnabled(settings, familyReaders[index], index < 63 && (state & (1L << index)) != 0L);
|
||||
}
|
||||
|
||||
public static bool IsEnabled(LocalSettings settings, IRegReader reader)
|
||||
{
|
||||
if (settings == null || reader == null)
|
||||
return true;
|
||||
|
||||
SmartReaderSelectionSettings selections = settings.SmartReaderSelections;
|
||||
if (selections == null)
|
||||
return true;
|
||||
|
||||
SmartReaderFamilySelection family = selections.Families == null
|
||||
? null
|
||||
: selections.Families.FirstOrDefault(item => item.FamilyKey == GetFamilyKey(reader));
|
||||
SmartReaderSelectionItem item = family == null || family.Readers == null
|
||||
? null
|
||||
: family.Readers.FirstOrDefault(candidate => candidate.ReaderKey == GetReaderKey(reader));
|
||||
|
||||
// A newly configured reader is enabled until the user explicitly
|
||||
// changes it in SmartCommunicationForm.
|
||||
return item == null || item.Enabled;
|
||||
}
|
||||
|
||||
public static string GetFamilyKey(IRegReader reader)
|
||||
{
|
||||
string typeName = reader == null ? string.Empty : reader.GetType().FullName ?? string.Empty;
|
||||
if (typeName.IndexOf(".AllyReader.", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
return "ally";
|
||||
if (typeName.IndexOf(".GenesisRegReader.", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
return "genesis";
|
||||
if (typeName.IndexOf(".iPerlASICReader.", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
return "iperl-asic";
|
||||
if (typeName.IndexOf(".Poseidon", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
return "poseidon";
|
||||
return LegacyIPerlFamilyKey;
|
||||
}
|
||||
|
||||
public static string GetReaderKey(IRegReader reader)
|
||||
{
|
||||
if (reader == null)
|
||||
return string.Empty;
|
||||
if (!string.IsNullOrEmpty(reader.Name))
|
||||
return reader.Name;
|
||||
return string.Format("{0}#{1}", reader.GetType().FullName, reader.Position);
|
||||
}
|
||||
|
||||
private static void SetEnabled(LocalSettings settings, IRegReader reader, bool enabled)
|
||||
{
|
||||
SmartReaderFamilySelection family = GetOrCreateFamily(GetOrCreateSettings(settings), GetFamilyKey(reader));
|
||||
SmartReaderSelectionItem item = family.Readers.FirstOrDefault(candidate => candidate.ReaderKey == GetReaderKey(reader));
|
||||
if (item == null)
|
||||
{
|
||||
item = new SmartReaderSelectionItem { ReaderKey = GetReaderKey(reader) };
|
||||
family.Readers.Add(item);
|
||||
}
|
||||
item.Enabled = enabled;
|
||||
}
|
||||
|
||||
private static SmartReaderSelectionSettings GetOrCreateSettings(LocalSettings settings)
|
||||
{
|
||||
if (settings.SmartReaderSelections == null)
|
||||
settings.SmartReaderSelections = new SmartReaderSelectionSettings();
|
||||
if (settings.SmartReaderSelections.Families == null)
|
||||
settings.SmartReaderSelections.Families = new List<SmartReaderFamilySelection>();
|
||||
return settings.SmartReaderSelections;
|
||||
}
|
||||
|
||||
private static SmartReaderFamilySelection GetOrCreateFamily(SmartReaderSelectionSettings settings, string familyKey)
|
||||
{
|
||||
SmartReaderFamilySelection family = settings.Families.FirstOrDefault(item => item.FamilyKey == familyKey);
|
||||
if (family == null)
|
||||
{
|
||||
family = new SmartReaderFamilySelection { FamilyKey = familyKey };
|
||||
settings.Families.Add(family);
|
||||
}
|
||||
if (family.Readers == null)
|
||||
family.Readers = new List<SmartReaderSelectionItem>();
|
||||
return family;
|
||||
}
|
||||
|
||||
private static bool IsLegacyMaskEnabled(long legacyMask, IRegReader reader, int ordinal)
|
||||
{
|
||||
int index = reader.Position > 0 && reader.Position <= 63 ? reader.Position - 1 : ordinal;
|
||||
return index >= 0 && index < 63 && (legacyMask & (1L << index)) != 0L;
|
||||
}
|
||||
|
||||
private static List<ISmartReader> ToReaderList(IEnumerable<ISmartReader> readers)
|
||||
{
|
||||
return readers == null ? new List<ISmartReader>() : readers.Where(reader => reader != null).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
28
TBF/SmartReaderSelectionSettings.cs
Normal file
28
TBF/SmartReaderSelectionSettings.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TBF
|
||||
{
|
||||
/// <summary>
|
||||
/// User selection of smart register readers. The selection is deliberately
|
||||
/// kept per reader family because reader positions are only meaningful inside
|
||||
/// one configured family.
|
||||
/// </summary>
|
||||
public sealed class SmartReaderSelectionSettings
|
||||
{
|
||||
public bool LegacyOptoHeadsEnabledMigrated;
|
||||
public List<SmartReaderFamilySelection> Families = new List<SmartReaderFamilySelection>();
|
||||
}
|
||||
|
||||
public sealed class SmartReaderFamilySelection
|
||||
{
|
||||
public string FamilyKey;
|
||||
public List<SmartReaderSelectionItem> Readers = new List<SmartReaderSelectionItem>();
|
||||
}
|
||||
|
||||
public sealed class SmartReaderSelectionItem
|
||||
{
|
||||
/// <summary>Stable configured component name; not a visual row index.</summary>
|
||||
public string ReaderKey;
|
||||
public bool Enabled;
|
||||
}
|
||||
}
|
||||
@ -1758,6 +1758,7 @@
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\JsonDataFromPoseidon.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonCfg.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonProcParams.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReadCycle.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReader.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\ProcessExtensions.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\ScopedLoggerFactory.cs" />
|
||||
|
||||
@ -245,5 +245,23 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
"Invalid JSON should not produce a valid DeviceId.");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task CliProcess_ExitErrorWithoutJson_RecordsDiagnosticsAndReturnsNoReading()
|
||||
{
|
||||
string cmdExe = Environment.GetEnvironmentVariable("ComSpec") ?? @"C:\Windows\System32\cmd.exe";
|
||||
var cliRunner = new CliRunner(false);
|
||||
var info = new CliTaskInfo { Name = "missing-dependency-simulation" };
|
||||
|
||||
JsonDataFromPoseidon result = await cliRunner.RunAndCaptureJsonAsync<JsonDataFromPoseidon>(
|
||||
cmdExe,
|
||||
"/d /c \"echo Could not load file or assembly 'log4net' 1>&2 & exit /b 17\"",
|
||||
info);
|
||||
|
||||
Assert.IsNull(result);
|
||||
Assert.AreEqual(17, info.ExitCode);
|
||||
StringAssert.Contains(info.FailureReason, "exit code 17");
|
||||
StringAssert.Contains(info.StandardError, "log4net");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig;
|
||||
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
|
||||
using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
[TestClass]
|
||||
[TestSubject(typeof(PoseidonReadCycle))]
|
||||
public class PoseidonReadCycleTest
|
||||
{
|
||||
private sealed class FakeReader : IPoseidonReadOperation
|
||||
{
|
||||
private readonly int iterationsToComplete;
|
||||
private int iterations;
|
||||
public FakeReader(string name, int iterationsToComplete) { Name = name; this.iterationsToComplete = iterationsToComplete; }
|
||||
public string Name { get; private set; }
|
||||
public int StartCount { get; private set; }
|
||||
public CmdPoseidonReader.CurrentPoseidonOp CurrentOp { get; private set; }
|
||||
public bool IsNotStarted { get { return CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.None; } }
|
||||
public bool IsFinished { get { return CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.Done || CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.Error; } }
|
||||
public bool HasError { get { return CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.Error; } }
|
||||
public void Start(bool readStart) { StartCount++; CurrentOp = readStart ? CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_Start : CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_End; }
|
||||
public Event Run()
|
||||
{
|
||||
if (CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_Start || CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_End)
|
||||
{ CurrentOp = CmdPoseidonReader.CurrentPoseidonOp.ReadDatastream_Running; return Event.Busy; }
|
||||
if (CurrentOp == CmdPoseidonReader.CurrentPoseidonOp.ReadDatastream_Running && ++iterations >= iterationsToComplete)
|
||||
{ CurrentOp = CmdPoseidonReader.CurrentPoseidonOp.Done; return Event.Done; }
|
||||
return Event.Busy;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RunIteration_FullBench_ArmsEachReaderOnce()
|
||||
{
|
||||
var readers = new List<IPoseidonReadOperation>();
|
||||
for (int i = 1; i <= 48; i++) readers.Add(new FakeReader("PoseidonPos" + i, 1 + i % 3));
|
||||
for (int i = 0; i < 5; i++) PoseidonReadCycle.RunIteration(readers, true);
|
||||
foreach (FakeReader reader in readers) Assert.AreEqual(1, reader.StartCount, reader.Name);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RunIteration_ReducedBench_DoesNotRestartCompletedReader()
|
||||
{
|
||||
var readers = new List<IPoseidonReadOperation> { new FakeReader("Pos1", 1), new FakeReader("Pos2", 3) };
|
||||
for (int i = 0; i < 5; i++) PoseidonReadCycle.RunIteration(readers, false);
|
||||
foreach (FakeReader reader in readers) Assert.AreEqual(1, reader.StartCount, reader.Name);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InputJson_ZeroReading_WithDecimalCommaOrDot_IsValid()
|
||||
{
|
||||
foreach (string reading in new[] { "00000,0", "00000.0" })
|
||||
{
|
||||
double value;
|
||||
Assert.IsTrue(CmdPoseidonReader.TryParseCliReading(reading, out value));
|
||||
Assert.AreEqual(0d, value);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CliReadResponse_RequiresNfcAndCompletedReading()
|
||||
{
|
||||
string reason;
|
||||
var data = new JsonDataFromPoseidon { NfcTagDetected = true, ReadingComplete = true, Reading = "00000.0" };
|
||||
Assert.IsTrue(CmdPoseidonReader.TryValidateCliReadResponse(data, out reason));
|
||||
data.ReadingComplete = false;
|
||||
Assert.IsFalse(CmdPoseidonReader.TryValidateCliReadResponse(data, out reason));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,5 +37,20 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
Assert.IsTrue(tryGetDeviceId);
|
||||
Assert.AreEqual("1000000267", strOut);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TryParseCliReading_ShouldAcceptZeroWithDotAndComma()
|
||||
{
|
||||
double dotValue;
|
||||
double commaValue;
|
||||
|
||||
Assert.IsTrue(TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader
|
||||
.TryParseCliReading("00000.0", out dotValue));
|
||||
Assert.IsTrue(TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader
|
||||
.TryParseCliReading("00000,0", out commaValue));
|
||||
|
||||
Assert.AreEqual(0d, dotValue);
|
||||
Assert.AreEqual(0d, commaValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.RegisterReaders.PoseidonCmdStartStop;
|
||||
using CmdPoseidonReader = TBF.Rig.RegisterReaders.PoseidonCmdStartStop.PoseidonReader;
|
||||
|
||||
namespace TBFTests.Rig.RegisterReaders.PoseidonCmdStartStop
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("Integration")]
|
||||
public class PoseidonSingleMeterIntegrationTest
|
||||
{
|
||||
// Set this only when a real Poseidon meter is connected to the selected hat.
|
||||
private const int RealPoseidonComPort = 0;
|
||||
private const string RealPoseidonCliFile = "HatCliDemo.exe";
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("Manual")]
|
||||
public void RealMeter_ReadStartValueThroughCli()
|
||||
{
|
||||
if (RealPoseidonComPort <= 0)
|
||||
Assert.Inconclusive("Set RealPoseidonComPort before running this manual Poseidon integration test.");
|
||||
|
||||
string cliPath = Path.Combine(SerialPortData.CliDirectory, RealPoseidonCliFile);
|
||||
if (!File.Exists(cliPath))
|
||||
Assert.Inconclusive("Poseidon CLI was not found: " + cliPath);
|
||||
|
||||
var cfg = new PoseidonCfg("PoseidonIntegration", new Factory())
|
||||
{
|
||||
ComPortNr = RealPoseidonComPort,
|
||||
CliFileName = RealPoseidonCliFile
|
||||
};
|
||||
var reader = new CmdPoseidonReader(cfg, null);
|
||||
reader.DebugLevel = Common.DebugMode.Normal;
|
||||
reader.Initialize();
|
||||
reader.SetCurrentOp(CmdPoseidonReader.CurrentPoseidonOp.ReadDataStream_Start);
|
||||
|
||||
for (int iteration = 0; iteration < 3500 &&
|
||||
reader.CurrentOp != CmdPoseidonReader.CurrentPoseidonOp.Done &&
|
||||
reader.CurrentOp != CmdPoseidonReader.CurrentPoseidonOp.Error; iteration++)
|
||||
{
|
||||
reader.Run();
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
|
||||
Assert.AreEqual(CmdPoseidonReader.CurrentPoseidonOp.Done, reader.CurrentOp);
|
||||
Assert.IsTrue(reader.LastCliReadingParsed,
|
||||
"CLI returned no parseable reading. Inspect the Poseidon CLI and TBF logs.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,6 +119,7 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonReader
|
||||
Name = "PoseidonCfg",
|
||||
OptoComPortNr = 6,
|
||||
RfidComPortNr = 5,
|
||||
DebugLevel = Common.DebugMode.Simulate,
|
||||
};
|
||||
|
||||
config = poseidonCfg;
|
||||
@ -156,7 +157,8 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonReader
|
||||
string allText = string.Join(", ", listBox.Items.Cast<object>().Select(i => i.ToString()));
|
||||
|
||||
Console.WriteLine(allText);
|
||||
Assert.IsTrue(allText.Contains("ReadSerialNo: 1000000267"));
|
||||
Assert.IsTrue(allText.Contains("ReadSerialNo: 1111"),
|
||||
"The UI unit test must use the deterministic simulated serial number, not physical hardware.");
|
||||
}
|
||||
|
||||
|
||||
@ -215,10 +217,6 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonReader
|
||||
string allText = string.Join(", ", listBox.Items.Cast<object>().Select(i => i.ToString()));
|
||||
Console.WriteLine(allText);
|
||||
Assert.IsTrue(allText.Contains("SetTestMode: OK"));
|
||||
//some input from the meter
|
||||
|
||||
|
||||
Thread.Sleep(50000);
|
||||
//SET OPTOTEST MODE OFF
|
||||
comboBox.SelectedIndex = (int)PoseidonImplHeadTestCtrl.Operations.SetTestModeOff;
|
||||
uniHeadTestCtrl.CommandTestButtonClick(commandTestButton, eventArgs);
|
||||
@ -226,22 +224,6 @@ namespace TBFTests.Rig.RegisterReaders.PoseidonReader
|
||||
allText = string.Join(", ", listBox.Items.Cast<object>().Select(i => i.ToString()));
|
||||
Console.WriteLine(allText);
|
||||
Assert.IsTrue(allText.Contains("SetActiveMode: OK"));
|
||||
|
||||
|
||||
/////////////// results of OptoHead
|
||||
// Get the private ListBox field via reflection
|
||||
var fieldOpto = uniHeadTestCtrl.GetType()
|
||||
.GetField("optoListBox", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
// Read its value from the control instance (not Text!)
|
||||
var listBoxOpto = fieldOpto?.GetValue(uniHeadTestCtrl) as System.Windows.Forms.ListBox;
|
||||
// Verify we found it
|
||||
Assert.IsNotNull(listBoxOpto, "optoListBox not found in UniHeadTestCtrl");
|
||||
|
||||
//check if the test mode is set OFF
|
||||
allText = string.Join(", ", listBoxOpto.Items.Cast<object>().Select(i => i.ToString()));
|
||||
Console.WriteLine(allText);
|
||||
Assert.IsTrue(allText.Contains("Status: OptoHeadC7"));
|
||||
Assert.IsTrue(allText.Contains("C7Data: C7"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,6 +135,8 @@
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\CliRunnerTimeoutUnitTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReaderTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonReadCycleTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonCmdStartStop\PoseidonSingleMeterIntegrationTest.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\PoseidonReader\UniHeadTestCtrlTest.cs" />
|
||||
<Compile Include="Rig\Scales\MettlerToledo\ReadStableMassOpTest.cs" />
|
||||
<Compile Include="Rig\TestMethods\GenesisCommunication\GenesisHead\GenesisHeadBatchIntegrationTest.cs" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user