- Expand Q3 calibration tests with diverse scenarios in `GenesisSmartReader_Q3Test`. - Introduce channel-specific validation for calibration tests. - Adjust methods for Q3 calibration in `GenesisSmartReader` to support per-channel factors. - Add utilities for preparing simulation data and refinements for test consistency. - Update `CliRunner` with improved task management, timeout handling, and logging enhancements.
625 lines
19 KiB
C#
625 lines
19 KiB
C#
///
|
|
/// Copyright (c) 2013-2020 Sensus Slovensko a.s.
|
|
///
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Ports;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using log4net;
|
|
using Common;
|
|
using NHibernate.Util;
|
|
using SharedDatabase.Entities;
|
|
using TBF.Boxes;
|
|
using TBF.Rig.Generic;
|
|
using TBF.Rig.GenericDevices;
|
|
using TBF.Rig.RegisterReaders.SerialStream;
|
|
using TBF.Rig.RegisterReaders.StandingStartStop;
|
|
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
|
|
|
namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop
|
|
{
|
|
public class PoseidonReader : ComponentBase, IDevice, IRegReaderDatastream, ISessionDataMngmnt, IOperation, ICommonRegReader
|
|
{
|
|
private static readonly ILog log = LogManager.GetLogger(typeof(PoseidonReader));
|
|
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
|
|
|
|
readonly PoseidonCfg registerReaderCfg;
|
|
readonly ControlBoard.IControlBoard controlBoard;
|
|
|
|
public int ComPortNr => registerReaderCfg?.ComPortNr ?? -1;
|
|
public PoseidonCfg RegPoseidonCfg => registerReaderCfg;
|
|
|
|
private bool activeHandlerSessioEnabled = false;
|
|
private CliRunner _cliRunner;
|
|
|
|
private CliRunner CliRunner
|
|
{
|
|
get
|
|
{
|
|
return (_cliRunner != null ?
|
|
_cliRunner :
|
|
(_cliRunner = new CliRunner(GetCliLogging())));
|
|
}
|
|
}
|
|
|
|
public enum CurrentPoseidonOp
|
|
{
|
|
None,
|
|
SendStartDataStream,
|
|
SendStartDataStream_Runing,
|
|
SendStartDataStream_Done,
|
|
ReadDataStream_Start,
|
|
ReadDataStream_End,
|
|
ReadDatastream_Running,
|
|
ReadDatastream_Done,
|
|
Done,
|
|
Error,
|
|
}
|
|
|
|
CurrentPoseidonOp _currentOp;
|
|
private bool _isReadingStart = true;
|
|
private bool? _isCliLogging;
|
|
|
|
public CurrentPoseidonOp CurrentOp
|
|
{
|
|
get { return _currentOp; }
|
|
}
|
|
|
|
public void SetCurrentOp(CurrentPoseidonOp operation = CurrentPoseidonOp.None)
|
|
{
|
|
_currentOp = operation ;
|
|
}
|
|
|
|
public bool GetCliLogging()
|
|
{
|
|
return _isCliLogging.HasValue ? _isCliLogging.Value : true;
|
|
}
|
|
|
|
public void SetCliLogging(bool isCliLogging)
|
|
{
|
|
_isCliLogging = isCliLogging;
|
|
}
|
|
|
|
public int Position
|
|
{
|
|
get
|
|
{
|
|
int firstDigitPos = Name.IndexOfAny(new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' });
|
|
int position;
|
|
return (firstDigitPos < 0) ? 0 : (int.TryParse(Name.Substring(firstDigitPos), out position) ? position : 0);
|
|
}
|
|
}
|
|
public RegisterReaderType RegisterReaderType { get { return RegisterReaderType.DataStream; } }
|
|
public double PulsesPerLtr {
|
|
get { return 1000.0;}
|
|
set { PulsesPerLtr = value; }
|
|
}
|
|
public double LtrsPerPulse { get { return 1 / PulsesPerLtr; } }
|
|
public string QuantityUnits { get; set; }
|
|
|
|
///
|
|
/// Wrappers for procedure parameters
|
|
///
|
|
public StreamType StreamType { get { return (registerReaderCfg.ProcParams != null) ?registerReaderCfg.ProcParams.StreamType : StreamType.None; } }
|
|
public int FrameLength { get { return (registerReaderCfg.ProcParams != null) ?registerReaderCfg.ProcParams.FrameLength : 0; } }
|
|
public double FrameFrequency { get { return (registerReaderCfg.ProcParams != null) ?registerReaderCfg.ProcParams.FrameFrequency : 0; } }
|
|
|
|
public Common.Unit VolumeUnits { get { return (registerReaderCfg.ProcParams != null) ? registerReaderCfg.ProcParams.VolumeUnits : Common.Unit.l; } }
|
|
public double VolumeScaleFactor { get { return (registerReaderCfg.ProcParams != null) ? registerReaderCfg.ProcParams.VolumeScaleFactor : 1; } }
|
|
public int VolumeFieldStart { get { return (registerReaderCfg.ProcParams != null) ? registerReaderCfg.ProcParams.VolumeFieldStart : 0; } }
|
|
public int VolumeFieldEnd { get { return (registerReaderCfg.ProcParams != null) ? registerReaderCfg.ProcParams.VolumeFieldEnd : 0; } }
|
|
public FieldFormat VolumeFieldFormat { get { return (registerReaderCfg.ProcParams != null) ? registerReaderCfg.ProcParams.VolumeFieldFormat : FieldFormat.None; } }
|
|
|
|
public Common.Unit TimeUnits { get { return (registerReaderCfg.ProcParams != null) ? registerReaderCfg.ProcParams.TimeUnits : Common.Unit.s; } }
|
|
public double TimeScaleFactor { get { return (registerReaderCfg.ProcParams != null) ? registerReaderCfg.ProcParams.TimeScaleFactor : 1; } }
|
|
public int TimeFieldStart { get { return (registerReaderCfg.ProcParams != null) ? registerReaderCfg.ProcParams.TimeFieldStart : 0; } }
|
|
public int TimeFieldEnd { get { return (registerReaderCfg.ProcParams != null) ? registerReaderCfg.ProcParams.TimeFieldEnd : 0; } }
|
|
public FieldFormat TimeFieldFormat { get { return (registerReaderCfg.ProcParams != null) ? registerReaderCfg.ProcParams.TimeFieldFormat : FieldFormat.None; } }
|
|
|
|
|
|
public bool CommFailed
|
|
{
|
|
get { return commFailed; }
|
|
set { commFailed = value; }
|
|
}
|
|
bool commFailed;
|
|
|
|
|
|
/// <summary>
|
|
/// Passed to OptoTelegramRaw.UpdateFromString(...)
|
|
/// </summary>
|
|
Int64 volumeRawExtLast;
|
|
Int64 timestampExtLast;
|
|
|
|
|
|
///
|
|
/// Required for IRegisterReader interface
|
|
///
|
|
public int WMPulses { get { return wmPulses; } }
|
|
public int WMRefPulses { get { return wmRefPulses; } }
|
|
public double WMVolume { get { return wmVolume; } }
|
|
public double BeginWMState { get { return beginWMState; } set { beginWMState = value;} }
|
|
public double EndWMState { get { return endWMState; } set { endWMState = value;} }
|
|
public double WMTestTime { get { return wmTestTime; } }
|
|
public string SerialNr { get => wmSerialNr; set => wmSerialNr = value; }
|
|
|
|
|
|
double beginWMState;
|
|
double endWMState;
|
|
double wmVolume;
|
|
int wmPulses;
|
|
int wmRefPulses;
|
|
double wmTestTime;
|
|
private string wmSerialNr;
|
|
|
|
int timeFromStart; /// [s] Time from test start to determine when the test start sample should be taken
|
|
|
|
|
|
public IOperation ReadDatastreamOp()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Store/update values to be used as a part of the opto-data log file name.
|
|
/// Stop data stream processing and saving, if it is enabled.
|
|
/// </summary>
|
|
/// <param name="test">Currently executed test</param>
|
|
/// <param name="repetitionNr">Currently executed repetition number</param>
|
|
public void TestIsGoingToStartSoon(Config.Entities.Test test, int repetitionNr)
|
|
{
|
|
/// Store/update values to be used as a part of the opto-data log file name
|
|
testName = test.Name;
|
|
testRepeats = test.Repeats;
|
|
this.repetitionNr = repetitionNr;
|
|
}
|
|
///
|
|
string testName;
|
|
int testRepeats;
|
|
int repetitionNr;
|
|
|
|
|
|
///
|
|
/// Volume of water from the opto telegram
|
|
///
|
|
private Int64 lastVolumeRaw; /// Last read raw volume
|
|
private double volumeLtr; ///
|
|
private double volumeLtr0;
|
|
|
|
public double VolumeLtrStart { get { return volumeLtrStart; } } /// Test start volume for metrology
|
|
public double VolumeLtrEnd { get { return volumeLtrEnd; } } /// Test end volume for metrology
|
|
double volumeLtrStart; /// Test start volume for metrology
|
|
double volumeLtrEnd; /// Test end volume for metrology
|
|
double volumeLtrEnd1; /// auxiliary buffer1 to keep the end volume before test stops
|
|
double volumeLtrEnd2; /// auxiliary buffer2 to keep the end volume before test stops
|
|
double volumeLtrEnd3; /// auxiliary buffer3 to keep the end volume before test stops
|
|
|
|
///
|
|
/// Timestamp from the opto telegram
|
|
///
|
|
bool lastTimestampValid;
|
|
private Int64 lastTimestamp;
|
|
private double timestampSec;
|
|
private double timestampSec0;
|
|
|
|
public bool NoSamples { get { return (timestampSecEnd - timestampSecStart) < float.Epsilon; } }
|
|
public double TimestampSecStart { get { return timestampSecStart; } }
|
|
public double TimestampSecEnd { get { return timestampSecEnd; } }
|
|
double timestampSecStart;
|
|
double timestampSecEnd;
|
|
double timestampSecEnd1;
|
|
double timestampSecEnd2;
|
|
double timestampSecEnd3;
|
|
|
|
private int frameIx;
|
|
public int TestStartFrameIx;
|
|
public int TestEndFrameIx;
|
|
|
|
|
|
DatastreamFrame[] datastreamFrames;
|
|
const int MaxDatastreamFramesCount = 80000; /// almost 3h at 8 Hz
|
|
int datastreamFramesCount;
|
|
string datastreamLogFileName;
|
|
|
|
|
|
DatastreamFrame toBeFlushed;
|
|
int flushedFramesCount;
|
|
public int FlushedFramesCount
|
|
{
|
|
get { return flushedFramesCount; }
|
|
set { flushedFramesCount = lastFlushedFramesCount = lastFlushedFramesCount_1 = value; }
|
|
}
|
|
|
|
int lastFlushedFramesCount_1;
|
|
int lastFlushedFramesCount;
|
|
public int FlushedFramesDelta
|
|
{
|
|
get
|
|
{
|
|
int retval = Math.Max(flushedFramesCount - lastFlushedFramesCount, lastFlushedFramesCount - lastFlushedFramesCount_1);
|
|
lastFlushedFramesCount_1 = lastFlushedFramesCount;
|
|
lastFlushedFramesCount = flushedFramesCount;
|
|
return retval;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///
|
|
/// Opto serial port and worker thread related private variables
|
|
///
|
|
private SerialPortData serialPort; /// Used in DebugMode.Normal
|
|
private TextReader textReader; /// Used instead of serialPort in DebugMode.Simulate
|
|
|
|
|
|
|
|
public PoseidonReader() { }
|
|
|
|
public PoseidonReader(Generic.IComponentCfg cfg, IList<Generic.IComponent> components)
|
|
: base(cfg)
|
|
{
|
|
registerReaderCfg = cfg as PoseidonCfg;
|
|
|
|
/// Control board is used to read reference flowmeter pulses
|
|
// controlBoard = TbfComponents.FindComponent(cfg.ParentName, components) as ControlBoard.IControlBoard;
|
|
// if (controlBoard == null) throw new Exception("Cannot find " + Name + " parent");
|
|
//
|
|
// log.Warn(this.ToString());
|
|
}
|
|
|
|
public override void Initialize()
|
|
{
|
|
Clear();
|
|
//log.WarnFormat("Initialize() ... FrameFormat set to {0}");
|
|
|
|
/// Allocate memory for opto-data from iPerl
|
|
datastreamFrames = new DatastreamFrame[MaxDatastreamFramesCount];
|
|
for (int i = 0; i < MaxDatastreamFramesCount; i++) datastreamFrames[i] = new DatastreamFrame();
|
|
|
|
toBeFlushed = new DatastreamFrame();
|
|
flushedFramesCount = 0;
|
|
activeHandlerSessioEnabled = false;
|
|
|
|
if ((DebugLevel == DebugMode.Normal)||(DebugLevel == DebugMode.Simulate))
|
|
{
|
|
/// Prepare serial port
|
|
serialPort = new SerialPortData(string.Format("COM{0}", registerReaderCfg.ComPortNr),
|
|
registerReaderCfg.CliFileName,
|
|
registerReaderCfg.MeterType);
|
|
|
|
|
|
//TODO BUMI prepare serial port - for us do nothing
|
|
//serialPort.Open();
|
|
//we can check file program if exists
|
|
if (!File.Exists(serialPort.SerialPortCmdClientPath))
|
|
{
|
|
log.FatalFormat("Serial port CLI {0} does not exist!", serialPort.SerialPortCmdClientPath);
|
|
return;
|
|
}
|
|
log.FatalFormat("{0} initialized: {1}", Name, this);
|
|
}
|
|
// else if (DebugLevel == DebugMode.Simulate)
|
|
// {
|
|
// }
|
|
else
|
|
{
|
|
serialPort = null;
|
|
log.FatalFormat("{0} in other mode: {1}", Name, this);
|
|
}
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
log.DebugFormat("{0}:Clear()", Name);
|
|
beginWMState = 0;
|
|
endWMState = 0;
|
|
wmRefPulses = 0;
|
|
}
|
|
|
|
|
|
|
|
private void ReadPulses()
|
|
{
|
|
wmRefPulses = controlBoard?.RefPulses ?? 0;
|
|
}
|
|
|
|
|
|
/// <summary>Start this operation</summary>
|
|
public void Start()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// for debug purposes what time will consume answer
|
|
/// </summary>
|
|
private long startTimeInMilis = -1, fullTimeInMilis = -1;
|
|
/// 30 seconds
|
|
private static long SafetyTimeOut = 30 * 1000;
|
|
private long incommingTime = -1;
|
|
private bool _lastOpTimedOut;
|
|
|
|
|
|
|
|
public long DeltaTime { get{return fullTimeInMilis;}}
|
|
public long IncommingTime { get{return incommingTime;}}
|
|
|
|
/// <summary>Run this operation</summary>
|
|
/// <returns>eventDone</returns>
|
|
public Event Run()
|
|
{
|
|
lock (this)
|
|
{
|
|
timeFromStart += StateMachine.Period;
|
|
ReadPulses();
|
|
}
|
|
|
|
if (_currentOp == CurrentPoseidonOp.SendStartDataStream)
|
|
{
|
|
CliRunner.Clear();
|
|
_lastOpTimedOut = false;
|
|
CliRunner.AddSendAsync(serialPort, SerialPortData.EMeterArg.AllParams);
|
|
_currentOp = CurrentPoseidonOp.SendStartDataStream_Runing;
|
|
return Event.Busy;
|
|
}
|
|
else if (_currentOp == CurrentPoseidonOp.SendStartDataStream_Runing)
|
|
{
|
|
if (CliRunner.AreTasksDone())
|
|
{
|
|
_currentOp = CurrentPoseidonOp.SendStartDataStream_Done;
|
|
}
|
|
else if (CliRunner.TimeOutReceived(SafetyTimeOut))
|
|
{
|
|
_lastOpTimedOut = true;
|
|
CliRunner.CancelUndoneTasksAsTimedOut();
|
|
_currentOp = CurrentPoseidonOp.SendStartDataStream_Done;
|
|
}
|
|
|
|
return Event.Busy;
|
|
}
|
|
else if (_currentOp == CurrentPoseidonOp.SendStartDataStream_Done)
|
|
{
|
|
var firstTaskInfo = CliRunner.TaskPool
|
|
.FindLast(t => t.Task is Task<string> && t.UseResult);
|
|
|
|
if (firstTaskInfo != null)
|
|
{
|
|
var task = (Task<string>)firstTaskInfo.Task;
|
|
string data = task.Result;
|
|
|
|
if (!string.IsNullOrEmpty(data))
|
|
{
|
|
TryGetDeviceId(data, out wmSerialNr);
|
|
}
|
|
}
|
|
else if (_lastOpTimedOut)
|
|
{
|
|
log.Warn(
|
|
$"PoseidonReader {Name}: SendStartDataStream timed out, no completed result will be used.");
|
|
}
|
|
|
|
CliRunner.Clear();
|
|
_currentOp = _lastOpTimedOut ? CurrentPoseidonOp.Error : CurrentPoseidonOp.Done;
|
|
return Event.Done;
|
|
}
|
|
else if (_currentOp == CurrentPoseidonOp.ReadDataStream_Start
|
|
|| _currentOp == CurrentPoseidonOp.ReadDataStream_End)
|
|
{
|
|
startTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
incommingTime = -1;
|
|
_isReadingStart = (_currentOp == CurrentPoseidonOp.ReadDataStream_Start);
|
|
|
|
CliRunner.Clear();
|
|
_lastOpTimedOut = false;
|
|
CliRunner.AddRunAndCaptureJsonAsync<JsonDataFromPoseidon>(serialPort,
|
|
SerialPortData.EMeterArg.AllParams);
|
|
_currentOp = CurrentPoseidonOp.ReadDatastream_Running;
|
|
return Event.Busy;
|
|
}
|
|
else if (_currentOp == CurrentPoseidonOp.ReadDatastream_Running)
|
|
{
|
|
if (CliRunner.AreTasksDone())
|
|
{
|
|
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
|
incommingTime = CliRunner.IncommingTime;
|
|
}
|
|
else if (CliRunner.TimeOutReceived(SafetyTimeOut))
|
|
{
|
|
_lastOpTimedOut = true;
|
|
CliRunner.CancelUndoneTasksAsTimedOut();
|
|
_currentOp = CurrentPoseidonOp.ReadDatastream_Done;
|
|
incommingTime = CliRunner.IncommingTime;
|
|
}
|
|
|
|
return Event.Busy;
|
|
}
|
|
else if (_currentOp == CurrentPoseidonOp.ReadDatastream_Done)
|
|
{
|
|
fullTimeInMilis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTimeInMilis;
|
|
log.Debug($"PoseidonReader.Run() Name= {Cfg.Name} fullTimeInMilis = {fullTimeInMilis}");
|
|
|
|
JsonDataFromPoseidon data = null;
|
|
|
|
var firstTaskInfo = CliRunner.TaskPool
|
|
.FindLast(t => t.Task is Task<JsonDataFromPoseidon> && t.UseResult);
|
|
|
|
if (firstTaskInfo != null)
|
|
{
|
|
var task = (Task<JsonDataFromPoseidon>)firstTaskInfo.Task;
|
|
data = task.Result;
|
|
}
|
|
else
|
|
{
|
|
if (_lastOpTimedOut)
|
|
{
|
|
log.Warn($"PoseidonReader {Name}: ReadDatastream timed out, no completed result available.");
|
|
}
|
|
else
|
|
{
|
|
log.Warn("No completed JsonDataFromPoseidon task available.");
|
|
}
|
|
}
|
|
|
|
if (data != null)
|
|
{
|
|
if (string.IsNullOrEmpty(wmSerialNr))
|
|
{
|
|
try
|
|
{
|
|
wmSerialNr = data.DeviceId ?? wmSerialNr;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.Error("Serial Nr - parse error!", e);
|
|
}
|
|
}
|
|
|
|
double volume;
|
|
if (Double.TryParse(data.Reading, out volume))
|
|
{
|
|
double volumeLi = Units.ConvertFrom(Unit.USgal, volume);
|
|
|
|
if (_isReadingStart)
|
|
beginWMState = volumeLi;
|
|
else
|
|
endWMState = volumeLi;
|
|
}
|
|
}
|
|
|
|
CliRunner.Clear();
|
|
_currentOp = _lastOpTimedOut ? CurrentPoseidonOp.Error : CurrentPoseidonOp.Done;
|
|
return Event.Done;
|
|
}
|
|
|
|
return Event.None;
|
|
|
|
}
|
|
|
|
|
|
/// <summary>Stop this operation</summary>
|
|
public void Stop()
|
|
{
|
|
//TODO BUMI stop CLI serial port
|
|
}
|
|
|
|
public void RunDeviceBefore()
|
|
{
|
|
//Start CLI communication and set CLI inputs and get CLI data
|
|
//ValidateCliFileExistence(true);
|
|
|
|
}
|
|
|
|
private void ValidateCliFileExistence(bool showErrorIfMissing)
|
|
{
|
|
if (DebugLevel == DebugMode.Normal && serialPort != null)
|
|
{
|
|
if (!serialPort.CliExists)
|
|
{
|
|
log.ErrorFormat("CLI file {0} does not exist!", serialPort.SerialPortCmdClientPath);
|
|
if (showErrorIfMissing)
|
|
MessageBox.Show($"CLI file {serialPort.SerialPortCmdClientPath} does not exist!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return;
|
|
}
|
|
log.DebugFormat("CLI file {0} found", serialPort.SerialPortCmdClientPath);
|
|
}
|
|
}
|
|
|
|
public void RunDeviceAfter()
|
|
{
|
|
|
|
}
|
|
|
|
public void StopDevice()
|
|
{
|
|
try
|
|
{
|
|
if (DebugLevel == DebugMode.Normal && serialPort != null)
|
|
{
|
|
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
public void StopDevice2()
|
|
{
|
|
}
|
|
|
|
public void StartSession()
|
|
{
|
|
ValidateCliFileExistence(false);
|
|
//this is no place to get device id
|
|
}
|
|
|
|
// private void RetrieveDeviceIdFromResponse()
|
|
// {
|
|
// if (DebugLevel == DebugMode.Simulate)
|
|
// {
|
|
// wmSerialNr = "777321";
|
|
// }
|
|
// else
|
|
// {
|
|
// //TODO BUMI start session - CMD send data to Poseidon
|
|
//
|
|
// CliRunner.AddSendAsync(serialPort, SerialPortData.EMeterArg.DeviceId);
|
|
// CliRunner.WaitAll();
|
|
// foreach (Task task in CliRunner.TaskPool)
|
|
// {
|
|
// if (task is Task<string>)
|
|
// {
|
|
// string result = (task as Task<string>).Result;
|
|
// if (TryGetDeviceId(result, out wmSerialNr))
|
|
// {
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// CliRunner.Clear();
|
|
// }
|
|
// }
|
|
|
|
private static readonly Regex DeviceIdRegex = new Regex(
|
|
@"(?im)(?:" +
|
|
@"^\s*Device\s*Id\s*:\s*([^\r\n]+)\s*$" + // old format
|
|
@"|" +
|
|
@"""DeviceId""\s*:\s*""?([0-9]+)""?" + // JSON format
|
|
@")",
|
|
RegexOptions.Compiled);
|
|
public bool TryGetDeviceId(string result, out string s)
|
|
{
|
|
s = null;
|
|
if (string.IsNullOrEmpty(result))
|
|
return false;
|
|
|
|
var m = DeviceIdRegex.Match(result);
|
|
if (!m.Success)
|
|
return false;
|
|
|
|
// one of the groups will be filled
|
|
s = !string.IsNullOrEmpty(m.Groups[1].Value)
|
|
? m.Groups[1].Value.Trim()
|
|
: m.Groups[2].Value.Trim();
|
|
return true;
|
|
}
|
|
|
|
public void SaveMark(object mark)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void EndSession()
|
|
{
|
|
if (_cliRunner != null)
|
|
{
|
|
_cliRunner.Clear();
|
|
}
|
|
activeHandlerSessioEnabled = false;
|
|
}
|
|
}
|
|
}
|