DataEntry.UNI - timeout, autoclose improve

Introduce configurable timeouts for data entry volume reads in `IRegReaderSmart` and its implementations: added optional timeout parameters and updated default values across associated forms and methods. Updated `EntryFormCfg` to support customizable timeout settings.
This commit is contained in:
Michal Buzik 2026-08-19 23:47:50 +02:00
parent 955b59a195
commit b0083afa10
7 changed files with 115 additions and 59 deletions

View File

@ -43,6 +43,7 @@ namespace TBF.Rig.DataEntry.Uni
public string BgFormCloseKeys; /// 7
public bool BgIsAutoReadingSerialNo;/// 8 - new
public int BgAutoCloseGap; /// 9 - new
public int DataEntryReadTimeoutSec;
public bool EnShowForm; /// 8 + 2
public string EnTitle; /// 9
@ -347,6 +348,7 @@ namespace TBF.Rig.DataEntry.Uni
BgFormCloseKeys = string.Empty;
BgIsAutoReadingSerialNo = false;
BgAutoCloseGap = 5;
DataEntryReadTimeoutSec = 5;
EnShowForm = false;
EnTitle = "Enter water meter data";
@ -403,6 +405,7 @@ namespace TBF.Rig.DataEntry.Uni
"Beginning: Keys to close the form",
"Beginning: Read Automatic Serial No from watermeter", //8
"All: Auto-continue delay after automatic reading [s; <=0=off]", //9
"All: Timeout for reading parameters [s; <=0=5]", //10 (stored internally as parameter 28)
"End: Show form",//8+2
"End: Form title",
@ -424,6 +427,16 @@ namespace TBF.Rig.DataEntry.Uni
"Test start/end: End picture name",
"Test start/end: Keys to close the form",
};
// Keep the original internal parameter indexes for backward compatibility,
// but display the timeout directly after the auto-continue parameter.
int MapDisplayedParameterIndex(int i)
{
if (i == 10) return 28;
if (i > 10 && i <= 28) return i - 1;
return i;
}
public string ParamName(int i)
{
if (i < paramNames.Length) return paramNames[i];
@ -505,6 +518,8 @@ namespace TBF.Rig.DataEntry.Uni
public ICollection<string> ParamValues(int i)
{
i = MapDisplayedParameterIndex(i);
var list = new List<string>();
if (i < paramNames.Length)
@ -590,6 +605,7 @@ namespace TBF.Rig.DataEntry.Uni
return string.Format("Name={0}, Show cycle beginning form={1}, Show cycle end form={2}",
Name, BgShowForm, EnShowForm);
}
i = MapDisplayedParameterIndex(i);
if (i < paramNames.Length)
{
@ -625,6 +641,7 @@ namespace TBF.Rig.DataEntry.Uni
case 25: return TestStartPicName;
case 26: return TestEndPicName;
case 27: return TestFormCloseKeys;
case 28: return DataEntryReadTimeoutSec.ToString();
default: return string.Empty;
}
@ -702,6 +719,8 @@ namespace TBF.Rig.DataEntry.Uni
public CfgUpdateFlags UpdateParam(int i, string str)
{
i = MapDisplayedParameterIndex(i);
if (i < paramNames.Length)
{
switch (i)
@ -763,6 +782,14 @@ namespace TBF.Rig.DataEntry.Uni
case 25: TestStartPicName = str; return CfgUpdateFlags.RestartRqrd;
case 26: TestEndPicName = str; return CfgUpdateFlags.RestartRqrd;
case 27: TestFormCloseKeys = str; return CfgUpdateFlags.RestartRqrd;
case 28:
int timeoutSec;
if (!Int32.TryParse(str, out timeoutSec))
{
return CfgUpdateFlags.None;
}
DataEntryReadTimeoutSec = timeoutSec;
return CfgUpdateFlags.RestartRqrd;
default:
return CfgUpdateFlags.None;
@ -884,35 +911,39 @@ namespace TBF.Rig.DataEntry.Uni
case 5:
case 6:
case 8://Auto close - yes/No
case 10://8+2
case 12:
case 15:
case 11://8+2
case 13:
case 16:
case 18:
case 20:
case 22:
case 17:
case 19:
case 21:
case 23:
case 24:
case 25:
message = string.Empty;
if (ParamValues(i).Contains(str)) return true;
break;
case 3:
case 4:
case 9: //Gap AutoClose
case 13:
case 14:
case 21:
case 15:
case 22:
message = string.Empty;
if (int.TryParse(str, out idummy)) return true;
break;
case 10: // Timeout; <= 0 uses the default timeout.
message = string.Empty;
if (int.TryParse(str, out idummy)) return true;
break;
case 1:
case 7:
case 11:
case 17:
case 19:
case 25:
case 12:
case 18:
case 20:
case 26:
case 27:
case 28:
message = string.Empty;
return true;
default:
@ -957,6 +988,7 @@ namespace TBF.Rig.DataEntry.Uni
prms.BgFormCloseKeys = BgFormCloseKeys;
prms.BgIsAutoReadingSerialNo = BgIsAutoReadingSerialNo;
prms.BgAutoCloseGap = BgAutoCloseGap;
prms.DataEntryReadTimeoutSec = DataEntryReadTimeoutSec;
prms.EnShowForm = EnShowForm;
prms.EnTitle = EnTitle;

View File

@ -311,7 +311,7 @@ namespace TBF.Rig.DataEntry.Uni
myCfg.TestStartBoxAlwaysEn, myCfg.TestIsLrOrder, myCfg.TestFormCloseKeys,
DEItem.GetColumns(), isCompound, volumeUnit, myCfg.TestIsCameraPicture,
startImages,endImages, ocr, ocrMessage, myRef.OcrStream,
myCfg.BgIsAutoReadingSerialNo, myCfg.BgAutoCloseGap);
myCfg.BgIsAutoReadingSerialNo, myCfg.BgAutoCloseGap, myCfg.DataEntryReadTimeoutSec);
modelessDlg.Show();
}
///
@ -333,7 +333,8 @@ namespace TBF.Rig.DataEntry.Uni
modelessDlg = new TestStartEndForm(waterMeters, myRef.regReaders, TBF.Data.LineSize, myCfg.TestTitle, myCfg.TestSize,
myCfg.TestStartBoxAlwaysEn, myCfg.TestIsLrOrder, myCfg.TestFormCloseKeys, DEItem.GetColumns(),
isCompound, volumeUnit, myCfg.TestIsCameraPicture, startImages, endImages,
ocr, ocrMessage, myRef.OcrStream, myCfg.BgIsAutoReadingSerialNo, myCfg.BgAutoCloseGap, wmStartStateStr, refVolume, errLimLo, errLimHi);
ocr, ocrMessage, myRef.OcrStream, myCfg.BgIsAutoReadingSerialNo, myCfg.BgAutoCloseGap,
myCfg.DataEntryReadTimeoutSec, wmStartStateStr, refVolume, errLimLo, errLimHi);
modelessDlg.Show();
}
///
@ -355,7 +356,8 @@ namespace TBF.Rig.DataEntry.Uni
modelessDlg = new TestStartEndForm(waterMeters, myRef.regReaders, TBF.Data.LineSize, myCfg.TestTitle, myCfg.TestSize,
myCfg.TestStartBoxAlwaysEn, myCfg.TestIsLrOrder, myCfg.TestFormCloseKeys, DEItem.GetColumns(),
isCompound, volumeUnit, myCfg.TestIsCameraPicture, startImages, endImages,
ocr, ocrMessage, myRef.OcrStream, myCfg.BgIsAutoReadingSerialNo,myCfg.BgAutoCloseGap, wmStartStateStr, refVolume, errLimLo, errLimHi);
ocr, ocrMessage, myRef.OcrStream, myCfg.BgIsAutoReadingSerialNo, myCfg.BgAutoCloseGap,
myCfg.DataEntryReadTimeoutSec, wmStartStateStr, refVolume, errLimLo, errLimHi);
modelessDlg.Show();
}
///

View File

@ -73,6 +73,8 @@ namespace TBF.Rig.DataEntry.Uni
/// - in seconds
/// </summary>
int iAutocloseGap;
const int DefaultDataEntryReadTimeoutSec = 5;
readonly int dataEntryReadTimeoutMs;
/// Derived from arguments in the constructor
readonly bool isEnd;
@ -173,7 +175,8 @@ namespace TBF.Rig.DataEntry.Uni
public TestStartEndForm(IList<WaterMeter> waterMeters, IRegReader[] regReaders, int _lineSize, string title, FontSz sz,
bool isStartBoxAlwaysEn, bool isLrOrder, string formCloseKeys, IList<DEItem> colItems, bool isCompound,
Unit initialVolumeUnit, bool isCameraPicture, string[] startImages, string[] endImages,
OcrVidi ocrVidi, string ocrMessage, string streamName, bool bAutoRead, int iAutocloseGap, string[] wmStartStateStr = null,
OcrVidi ocrVidi, string ocrMessage, string streamName, bool bAutoRead, int iAutocloseGap,
int dataEntryReadTimeoutSec, string[] wmStartStateStr = null,
double refVolume = 0, double errLimLo = 0, double errLimHi = 0)
: this()
{
@ -200,6 +203,10 @@ namespace TBF.Rig.DataEntry.Uni
this.warningLimHi = 2 * errLimHi;
this.bAutoRead = bAutoRead;
this.iAutocloseGap = iAutocloseGap;
int effectiveTimeoutSec = dataEntryReadTimeoutSec > 0
? dataEntryReadTimeoutSec
: DefaultDataEntryReadTimeoutSec;
this.dataEntryReadTimeoutMs = Math.Min(effectiveTimeoutSec, Int32.MaxValue / 1000) * 1000;
if (waterMeters == null || regReaders == null ||
(wmStartStateStr != null && wmStartStateStr.Length != (isCompound ? 2 : 1) * waterMeters.Count))
@ -1726,11 +1733,11 @@ namespace TBF.Rig.DataEntry.Uni
double volume = Double.NaN;
if (isEnd)
{
volume = await r.DataEntry_ReadEndVolume().ConfigureAwait(false);
volume = await r.DataEntry_ReadEndVolume(dataEntryReadTimeoutMs).ConfigureAwait(false);
}
else
{
volume = await r.DataEntry_ReadBeginVolume().ConfigureAwait(false);
volume = await r.DataEntry_ReadBeginVolume(dataEntryReadTimeoutMs).ConfigureAwait(false);
}
log.DebugFormat(
"DATA_ENTRY_VOLUME_READ_RESULT Name={0}, Position={1}, DebugLevel={2}, " +

View File

@ -6,8 +6,8 @@ namespace TBF.Rig.GenericDevices
public interface IRegReaderSmart : ISmartMeterReader
{
Task<string> DataEntry_ReadSerialNumber();
Task<double> DataEntry_ReadBeginVolume();
Task<double> DataEntry_ReadEndVolume();
Task<double> DataEntry_ReadBeginVolume(int timeoutMs = 3000);
Task<double> DataEntry_ReadEndVolume(int timeoutMs = 3000);
int Group { get; }
int MuxBoardNrOrGroup14 { get; }

View File

@ -24,7 +24,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader
ISmartReader
{
private const int DataEntryCommandTimeoutMs = 5000;
private const int DataEntryOpticalTimeoutMs = 10000;
private const int DefaultDataEntryOpticalTimeoutMs = 3000;
private const int MaxStoredSamples = 40000;
private const long RawVolumeModulo = 0x1000000L;
private const long RawVolumeHalfRange = RawVolumeModulo / 2;
@ -309,14 +309,14 @@ namespace TBF.Rig.RegisterReaders.AllyReader
});
}
public Task<double> DataEntry_ReadBeginVolume()
public Task<double> DataEntry_ReadBeginVolume(int timeoutMs = DefaultDataEntryOpticalTimeoutMs)
{
return ReadDataEntryVolume(true);
return ReadDataEntryVolume(true, timeoutMs);
}
public Task<double> DataEntry_ReadEndVolume()
public Task<double> DataEntry_ReadEndVolume(int timeoutMs = DefaultDataEntryOpticalTimeoutMs)
{
return ReadDataEntryVolume(false);
return ReadDataEntryVolume(false, timeoutMs);
}
public AllyVersionInfo ReadVersionAndType(int timeoutMs)
@ -560,7 +560,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader
endWMState = reader.ReadDouble();
}
private Task<double> ReadDataEntryVolume(bool isBegin)
private Task<double> ReadDataEntryVolume(bool isBegin, int timeoutMs)
{
return Task.Run(() =>
{
@ -572,7 +572,8 @@ namespace TBF.Rig.RegisterReaders.AllyReader
try
{
Start();
DateTime deadline = DateTime.UtcNow.AddMilliseconds(DataEntryOpticalTimeoutMs);
int effectiveTimeoutMs = Math.Max(1, timeoutMs);
DateTime deadline = DateTime.UtcNow.AddMilliseconds(effectiveTimeoutMs);
while (DateTime.UtcNow < deadline)
{
RunDeviceBefore();
@ -594,7 +595,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader
log.WarnFormat(
"ALLY Data Entry {0} volume timeout after {1} ms on COM{2}",
isBegin ? "begin" : "end",
DataEntryOpticalTimeoutMs,
effectiveTimeoutMs,
allyCfg.OptoComPortNr);
return Double.NaN;
}

View File

@ -36,6 +36,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{
private static readonly ILog log = LogManager.GetLogger(typeof(GenesisSmartReader));
private static readonly ILog logStream = LogManager.GetLogger("StreamData");
private const int DefaultDataEntryVolumeTimeoutMs = 3000;
public override string ToString()
{
@ -2901,26 +2902,26 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return SerialNr;
}
public Task<double> DataEntry_ReadBeginVolume()
public Task<double> DataEntry_ReadBeginVolume(int timeoutMs = DefaultDataEntryVolumeTimeoutMs)
{
log.Debug("called DataEntry_ReadBeginVolumer()");
Task<double> readedVolume = DataEntry_BeginVolumeAsync();
Task<double> readedVolume = DataEntry_BeginVolumeAsync(timeoutMs);
return readedVolume;
}
public Task<double> DataEntry_ReadEndVolume()
public Task<double> DataEntry_ReadEndVolume(int timeoutMs = DefaultDataEntryVolumeTimeoutMs)
{
log.Debug("called DataEntry_ReadBeginVolumer()");
Task<double> readedVolume = DataEntry_EndVolumeAsync();
Task<double> readedVolume = DataEntry_EndVolumeAsync(timeoutMs);
return readedVolume;
}
public async Task<double> DataEntry_EndVolumeAsync()
public async Task<double> DataEntry_EndVolumeAsync(int timeoutMs = DefaultDataEntryVolumeTimeoutMs)
{
if (optoSerialPort == null || !optoSerialPort.IsOpen)
@ -2939,13 +2940,16 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
int ch = channel0 >= 0 ? channel0 : 0;
volumeLtr[ch] = Double.NaN;
int counter = 0;
while (Double.IsNaN(volumeLtr[ch]) && counter < 10)
DateTime readDeadline = DateTime.UtcNow.AddMilliseconds(Math.Max(1, timeoutMs));
while (Double.IsNaN(volumeLtr[ch]))
{
counter++;
int remainingTimeoutMs = (int)(readDeadline - DateTime.UtcNow).TotalMilliseconds;
if (remainingTimeoutMs <= 0)
break;
try
{
string readOptoDataWithTimeout = ReadOptoDataWithTimeout(3000);
string readOptoDataWithTimeout = ReadOptoDataWithTimeout(remainingTimeoutMs);
if (!string.IsNullOrEmpty(readOptoDataWithTimeout))
{
try
@ -2983,9 +2987,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
log.Debug($"Try get End Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr}");
if (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort();
if (!Double.IsNaN(volumeLtr[channel0]))
if (!Double.IsNaN(volumeLtr[ch]))
{
endWMState = volumeLtr[channel0];
endWMState = volumeLtr[ch];
if (!Double.IsNaN(beginWMState) && !Double.IsNaN(endWMState))
{
//Solve roll over
@ -2994,7 +2998,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
log.Debug($"Solve roll over! endWMState: {endWMState} < beginWMState: {beginWMState}");
const double VOL_RANGE_LITERS = 16777216.0 * 0.00025; // 4,194.304 l
endWMState += VOL_RANGE_LITERS;
volumeLtr[channel0] = endWMState;
volumeLtr[ch] = endWMState;
ReadPulses();
log.Debug(
$"Solve roll over! Upgraded endWMState: {endWMState}, beginWMState: {beginWMState}");
@ -3011,7 +3015,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
}
public async Task<double> DataEntry_BeginVolumeAsync()
public async Task<double> DataEntry_BeginVolumeAsync(int timeoutMs = DefaultDataEntryVolumeTimeoutMs)
{
if (ConfigStruct == null)
{
@ -3029,13 +3033,16 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
int ch = channel0 >= 0 ? channel0 : 0;
volumeLtr0[ch] = Double.NaN;
int counter = 0;
while (Double.IsNaN(volumeLtr0[ch]) && counter < 10)
DateTime readDeadline = DateTime.UtcNow.AddMilliseconds(Math.Max(1, timeoutMs));
while (Double.IsNaN(volumeLtr0[ch]))
{
counter++;
int remainingTimeoutMs = (int)(readDeadline - DateTime.UtcNow).TotalMilliseconds;
if (remainingTimeoutMs <= 0)
break;
try
{
string readOptoDataWithTimeout = ReadOptoDataWithTimeout(5000);
string readOptoDataWithTimeout = ReadOptoDataWithTimeout(remainingTimeoutMs);
if (!string.IsNullOrEmpty(readOptoDataWithTimeout))
{
try

View File

@ -51,6 +51,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
private const int StartSampleDelaySec = 5;
private const int EndSampleDelayCount = 2;
private const int DefaultDataEntryVolumeTimeoutMs = 3000;
private OptoHeadTest _optoHeadTest;
@ -1671,26 +1672,26 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
return SerialNr;
}
public Task<double> DataEntry_ReadBeginVolume()
public Task<double> DataEntry_ReadBeginVolume(int timeoutMs = DefaultDataEntryVolumeTimeoutMs)
{
log.Debug("called DataEntry_ReadBeginVolumer()");
Task<double> readedVolume = DataEntry_BeginVolumeAsync();
Task<double> readedVolume = DataEntry_BeginVolumeAsync(timeoutMs);
return readedVolume;
}
public Task<double> DataEntry_ReadEndVolume()
public Task<double> DataEntry_ReadEndVolume(int timeoutMs = DefaultDataEntryVolumeTimeoutMs)
{
log.Debug("called DataEntry_ReadBeginVolumer()");
Task<double> readedVolume = DataEntry_EndVolumeAsync();
Task<double> readedVolume = DataEntry_EndVolumeAsync(timeoutMs);
return readedVolume;
}
public async Task<double> DataEntry_EndVolumeAsync()
public async Task<double> DataEntry_EndVolumeAsync(int timeoutMs = DefaultDataEntryVolumeTimeoutMs)
{
if (optoSerialPort == null || !optoSerialPort.IsOpen)
@ -1708,13 +1709,16 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
log.Debug($"Try get End Volume! COM: {this.OptoComPortNr}");
volumeLtr = Double.NaN;
int counter = 0;
while (Double.IsNaN(volumeLtr) && counter < 2)
DateTime readDeadline = DateTime.UtcNow.AddMilliseconds(Math.Max(1, timeoutMs));
while (Double.IsNaN(volumeLtr))
{
counter++;
int remainingTimeoutMs = (int)(readDeadline - DateTime.UtcNow).TotalMilliseconds;
if (remainingTimeoutMs <= 0)
break;
try
{
string readOptoDataWithTimeout = ReadOptoDataWithTimeout(2000);
string readOptoDataWithTimeout = ReadOptoDataWithTimeout(remainingTimeoutMs);
if (!string.IsNullOrEmpty(readOptoDataWithTimeout))
{
try
@ -1766,7 +1770,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
}
public async Task<double> DataEntry_BeginVolumeAsync()
public async Task<double> DataEntry_BeginVolumeAsync(int timeoutMs = DefaultDataEntryVolumeTimeoutMs)
{
if (ConfigStruct == null)
{
@ -1782,13 +1786,16 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
Start();
volumeLtr0 = Double.NaN;
int counter = 0;
while (Double.IsNaN(volumeLtr0) && counter < 10)
DateTime readDeadline = DateTime.UtcNow.AddMilliseconds(Math.Max(1, timeoutMs));
while (Double.IsNaN(volumeLtr0))
{
counter++;
int remainingTimeoutMs = (int)(readDeadline - DateTime.UtcNow).TotalMilliseconds;
if (remainingTimeoutMs <= 0)
break;
try
{
string readOptoDataWithTimeout = ReadOptoDataWithTimeout(5000);
string readOptoDataWithTimeout = ReadOptoDataWithTimeout(remainingTimeoutMs);
if (!string.IsNullOrEmpty(readOptoDataWithTimeout))
{
try