Files
laatzen/Common/Ui/GenesisToolBox/FrmTempMeter.cs
T
2023-08-02 16:45:17 +02:00

908 lines
31 KiB
C#

using CordonelPreadjustmentUi;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages;
using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
using Xylem.Common.Logic.ServiceCore;
using Xylem.Common.Logic.SoftwareAccessHelper;
using Xylem.Common.Ui.GenesisToolBox.Infrastructure;
using Watcher = System.Timers.Timer;
namespace Xylem.Common.Ui.GenesisToolBox
{
public partial class FrmTempMeter : Form
{
public class CordonelTempLut
{
public Int32 Id { get; set; }
public Int32 PcbId { get; set; }
public Double RefTempC { get; set; }
public Double AvgTof { get; set; }
public Boolean Active { get; set; }
}
public class LutTemp
{
public Int32 PcbId { get; set; }
public Int32 FromTempC { get; set; }
public Int32 ToTempC { get; set; }
public Double? AvgRefTempC { get; set; }
public List<Double> ListOfRecordedTofs { get; set; }
public Double? AvgTofs { get; set; }
}
public FrmTempMeter()
{
InitializeComponent();
SetEnabled(ProgrammState.Start);
}
public ZeroFlowGenesisMeter _currentGenesis { get; private set; }
public MeterBatch _meterBatch { get; private set; }
private void btnConnect_Click(Object sender, EventArgs e)
{
Connect();
}
private void Connect()
{
try
{
Int32 slotNr;
if (string.IsNullOrEmpty(cbComSlot.SelectedItem.ToString()) ||
!int.TryParse(cbComSlot.SelectedItem.ToString(), out slotNr))
{
return;
}
//var drive = 1;
_currentGenesis?.DisposeMeter();
//dispose old meter
_meterBatch = new MeterBatch();
_currentGenesis = null;
Thread.Sleep(200);
//assign new meter and assign meter to FW update file if this exists
_currentGenesis = new ZeroFlowGenesisMeter(slotNr, 3, false);
//_currentGenesis.SetupFromConfigFile(slotNr);
_meterBatch.AddMeter(_currentGenesis);
Task.Factory.StartNew(() =>
{
_meterBatch.MetersLogin();
if (!_currentGenesis.IsLoggedOn)
{
Thread.Sleep(2000);
_meterBatch.MetersLogin();
}
if (!_currentGenesis.IsLoggedOn)
{
SetEnabled(ProgrammState.Start);
return;
}
var a = GetLutsText(_currentGenesis.PcbId);
//
_currentGenesis.InitMeasurement();
Invoke(new Action(() =>
{
txtLuts.Text = a;
if (LutsMissing)
{
txtLuts.BackColor = Color.IndianRed;
}
else
{
txtLuts.BackColor = Color.LightGreen;
}
lblPcbId.Text = _currentGenesis.PcbId;
SetEnabled(ProgrammState.Connected);
}));
});//.ContinueWith(delegate { ViewProgressPcb(false); });
}
catch (Exception ex)
{
txtLuts.Text = "-";
MessageBox.Show(ex.Message);
SetEnabled(ProgrammState.Start);
}
}
private void btnRefreshLut_Click(Object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(_currentGenesis?.PcbId))
{
Task.Factory.StartNew(() =>
{
var a = GetLutsText(_currentGenesis.PcbId);
Invoke(new Action(() =>
{
txtLuts.Text = a;
if (LutsMissing)
{
txtLuts.BackColor = Color.IndianRed;
}
else
{
txtLuts.BackColor = Color.LightGreen;
}
}));
});
}
else
{
txtLuts.Text = "-";
}
}
public class RefTempRange
{
public Double Min { get; set; }
public Double Max { get; set; }
}
private List<RefTempRange> ReftempRangeReq = new List<RefTempRange>()
{
new RefTempRange() { Min = 7, Max= 9 },
new RefTempRange() { Min = 13,Max = 15 },
new RefTempRange() { Min = 16,Max = 18 },
new RefTempRange() { Min = 19,Max = 20 },
new RefTempRange() { Min = 20,Max = 22 },
new RefTempRange() { Min = 23,Max = 25 },
new RefTempRange() { Min = 26,Max = 28 },
new RefTempRange() { Min = 28,Max = 30 },
new RefTempRange() { Min = 33,Max = 36 },
};
private List<CordonelTempLut> cordonelTempLut = null;
private Boolean LutsMissing = false;
private String GetLutsText(String PcbId)
{
var ret = new StringBuilder();
var err = 0;
var resp = LocalWebRequest.GetRequest($"http://10.49.40.25/MeterProcessState/api/TempFlange/GetLut?PcbId={PcbId}");
ret.AppendLine($"Ref Temp C°\t|\t AvgTof");
var CurrentReqRequierd = new List<RefTempRange>();
CurrentReqRequierd.AddRange(ReftempRangeReq);
try
{
cordonelTempLut = JsonConvert.DeserializeObject<List<CordonelTempLut>>(resp);
foreach (var item in cordonelTempLut.OrderBy(a => a.RefTempC))
{
CurrentReqRequierd.RemoveAll(rem => item.RefTempC >= rem.Min && item.RefTempC <= rem.Max);
ret.AppendLine($"{item.RefTempC.ToString("n8")}\t|\t{item.AvgTof}");
}
}
catch (Exception)
{
ret.AppendLine($"No entrys");
}
LutsMissing = false;
foreach (var item in CurrentReqRequierd)
{
LutsMissing = true;
ret.AppendLine($"{item.Min}-{item.Max}\t|\t Missing");
}
return ret.ToString();
}
private ProgrammState State = ProgrammState.Start;
private enum ProgrammState
{
Start,
Connected,
RecordRuning,
RecordStoped,
RecodCalculated,
}
private void SetEnabled(ProgrammState state)
{
this.State = state;
switch (State)
{
case ProgrammState.Start:
this.btnStartRecord.Enabled = false;
this.btnStopRecord.Enabled = false;
this.btnAddLut.Enabled = false;
this.nudRef.Visible = false;
this.btnRefreshLut.Enabled = false;
this.StartAutomaticalyCalibrationBtn.Enabled = false;
this.StopAutomaticalCalibrationBtn.Enabled = false;
this.UpdateGridBtn.Enabled = false;
break;
case ProgrammState.Connected:
this.btnStartRecord.Enabled = true;
this.btnStopRecord.Enabled = false;
this.btnAddLut.Enabled = false;
this.nudRef.Visible = false;
this.btnRefreshLut.Enabled = true;
this.StartAutomaticalyCalibrationBtn.Enabled = true;
this.StopAutomaticalCalibrationBtn.Enabled = false;
this.UpdateGridBtn.Enabled = true;
break;
case ProgrammState.RecordRuning:
this.btnStartRecord.Enabled = false;
this.btnStopRecord.Enabled = true;
this.btnAddLut.Enabled = false;
this.nudRef.Visible = false;
this.btnRefreshLut.Enabled = true;
this.StartAutomaticalyCalibrationBtn.Enabled = false;
this.StopAutomaticalCalibrationBtn.Enabled = true;
this.UpdateGridBtn.Enabled = true;
break;
case ProgrammState.RecordStoped:
this.btnStartRecord.Enabled = true;
this.btnStopRecord.Enabled = false;
this.btnAddLut.Enabled = true;
this.nudRef.Visible = true;
this.btnRefreshLut.Enabled = true;
this.StartAutomaticalyCalibrationBtn.Enabled = true;
this.StopAutomaticalCalibrationBtn.Enabled = false;
this.UpdateGridBtn.Enabled = true;
break;
default:
break;
}
}
public void tmp()
{
//var cRecords = _currentGenesis.CurrentRecords.ToList();
//Log($"cRecords.Count={cRecords.Count}");
//if (cRecords != null && cRecords.Any())
//{
// decimal checkTempMeter = 0;
// if (MeterTempMode == TempMode.CalcTof)
// {
// //
// var sumTof = 0.0;
// var countTof = 0;
// foreach (var tofItem in cRecords.Where(c => c.Channel == 2))
// {
// if (tofItem.Channel == 2)
// {
// Log($"TotalTimeOfFlightS;{(tofItem.TotalTimeOfFlightS * 1000).ToString()}");
// Log($"RAWTEMP;{interPol(TempFromTofLUT, (tofItem.TotalTimeOfFlightS * 1000))}");
// sumTof = (tofItem.TotalTimeOfFlightS * 1000) + sumTof;
// countTof = countTof + 1;
// }
// }
// var x = (sumTof / countTof) / 1000;
// Log($"avgTof;{x}({sumTof}/{countTof})");
// checkTempMeter = interPol(TempFromTofLUT, x);
// Meter.CurrentRecords.Clear();
// }
// else
// {
// checkTempMeter = (decimal)cRecords.Last().TemperatureDegC;
// }
//}
}
private DateTimeOffset start;
private Task tempTask;
private void BtnStartRecord_Click(Object sender, EventArgs e)
{
automatic = false;
start = DateTimeOffset.Now;
_currentGenesis.StartRecordData();
_currentGenesis.WriteRegister<Byte>(Register.Genesisflow.SampleRate, 16, checkRegister: true);
SetEnabled(ProgrammState.RecordRuning);
timer2.Enabled = true;
}
private void BtnStopRecord_Click(Object sender, EventArgs e)
{
timer2.Enabled = false;
OutputAvgTof(true);
_currentGenesis.WriteRegister<Byte>(Register.Genesisflow.SampleRate, 0, checkRegister: true);
SetEnabled(ProgrammState.RecordStoped);
}
private void btnAddLut_Click(Object sender, EventArgs e)
{
var req = LocalWebRequest.PostRequestAsync($"http://10.49.40.25/MeterProcessState/api/TempFlange/AddLut?PcbId={_currentGenesis.PcbId}&RefTempC={nudRef.Value.ToString().Replace(",", ".")}&AvgTof={lblAvgDutTof.Text.Replace(",", ".")}");
SetEnabled(ProgrammState.Connected);
}
private void btnCheck_Click(Object sender, EventArgs e)
{
if (btnCheck.Text == "Start Check")
{
_currentGenesis.StartRecordData();
timer1.Enabled = true;
btnCheck.Text = "Stop Check";
}
else
{
_currentGenesis.StopRecordData();
timer1.Enabled = false;
btnCheck.Text = "Start Check";
}
}
private static Decimal interPol(List<CordonelTempLut> points, Double x)
{
var x1 = 0.000;
var y1 = 0.000;
var x2 = 0.000;
var y2 = 0.000;
Decimal checkTempMeter;
if ((x <= points[0].AvgTof) && (x >= points.Last().AvgTof))
{
var i = 0;
foreach (var val in points)
{
if (points[i].AvgTof <= x)
{
x1 = points[i - 1].AvgTof;
y1 = points[i - 1].RefTempC;
x2 = points[i].AvgTof;
y2 = points[i].RefTempC;
break;
}
i = i + 1;
}
checkTempMeter = (Decimal)((y2 - y1) / (x2 - x1) * (x - x1) + y1);
}
else
{
checkTempMeter = 1;
}
return checkTempMeter;
}
private void timer1_Tick(Object sender, EventArgs e)
{
try
{
var cRecords = _currentGenesis.CurrentRecords.ToList();
if (cRecords != null && cRecords.Any())
{
Decimal checkTempMeter = 0;
//
var sumTof = 0.0;
var countTof = 0;
foreach (var tofItem in cRecords.Where(c => c.Channel == 2))
{
if (tofItem.Channel == 2)
{
sumTof = (tofItem.TotalTimeOfFlightS * 1000) + sumTof;
countTof = countTof + 1;
}
}
var x = (sumTof / countTof) / 1000;
checkTempMeter = interPol(cordonelTempLut, x);
var a = new Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords.CalibrationRecord();
while (_currentGenesis.CurrentRecords.TryDequeue(out a))
{
// do nothing
}
lblCheck.Text = checkTempMeter.ToString();
}
else
{
lblCheck.Text = $"temp NA";
}
}
catch (Exception ex)
{
lblCheck.Text = $"temp NA - {ex.Message}";
}
}
private void timer2_Tick(Object sender, EventArgs e)
{
if (State == ProgrammState.RecordRuning)
{
if (automatic)
{
}
else
{
OutputAvgTof();
}
}
}
private double OutputAvgTof(Boolean stop = false)
{
var avgToF = default(double);
try
{
StringBuilder cTofs = new StringBuilder();
var listOfRec = _currentGenesis.CurrentRecords.Where(s => s.Channel == 2).ToList();
if (stop)
{
_currentGenesis.StopRecordData();
}
var list = listOfRec.Select(s => new { ToF = s.TotalTimeOfFlightS * 1000, Channel = s.Channel });
foreach (var item in list.OrderBy(o => o.Channel))
{
cTofs.Append(item.Channel);
cTofs.Append(":");
cTofs.AppendLine((item.ToF / 1000).ToString());
}
avgToF = (list.Average(s => s.ToF) / 1000);
lblAvgDutTof.Text = avgToF.ToString();
txtCurrentTofs.Text = cTofs.ToString();
lblLines.Text = list.Count().ToString(); ;
lblDuration.Text = $"{(DateTimeOffset.Now - start).TotalSeconds}S";
}
catch (Exception ex)
{
txtCurrentTofs.Text = ex.Message;
}
return avgToF;
}
private class TempTime
{
public TempTime(Double temp)
{
date = DateTime.UtcNow;
Temp = temp;
}
public TempTime(String temp)
{
Double tmp = double.MinValue;
if (double.TryParse(temp, out tmp))
{
date = DateTime.UtcNow;
Temp = tmp;
}
}
public DateTime date;
public Double Temp;
}
private class ToFTime
{
public ToFTime(Double temp, DateTimeOffset date)
{
Date = date.ToUniversalTime().DateTime;
Tof = temp;
}
public DateTime Date;
public Double Tof;
}
private ConcurrentQueue<TempTofCalc> listTempTime = new ConcurrentQueue<TempTofCalc>();
private void RunTemConnection()
{
using (var pipeClient = new NamedPipeClientStream(".", "TempPipe", PipeDirection.In))
{
// Connect to the pipe or wait until the pipe is available.
//Console.Write("Attempting to connect to pipe...");
pipeClient.Connect();
//Console.WriteLine("Connected to pipe.");
//Console.WriteLine("There are currently {0} pipe server instances open.",
// pipeClient.NumberOfServerInstances);
using (var sr = new StreamReader(pipeClient))
{
string temp;
while ((temp = sr.ReadLine()) != null)
{
CalibrationRecord record;
Double avgTof = 0;
Double reftemp = 0;
if (double.TryParse(temp, out reftemp))
{
avgTof = 0;
var i = 0;
while (_currentGenesis.CurrentRecords.TryDequeue(out record))
{
if (record.Channel == 2)
{
avgTof = avgTof + record.TotalTimeOfFlightS;
i = 1 + i;
}
}
if (i > 0)
{
avgTof = avgTof / i;
if (!double.IsNaN(avgTof))
{
listTempTime.Enqueue(new TempTofCalc() { RefDate = DateTime.Now, RefTemp = reftemp, Tof = avgTof });
if (avgTof.ToString().Contains("a"))
{
}
}
}
}
}
}
}
}
private List<ToFTime> toFTimes = new List<ToFTime>();
private void MergeToAutoFile()
{
List<TempTofCalc> itimestowri = new List<TempTofCalc>();
TempTofCalc lItem;
while (listTempTime.TryDequeue(out lItem))
{
itimestowri.Add(lItem);
}
var fiName = $"{_currentGenesis.PcbId}_{start.ToString("yyyy_MM_dd_HH-mm-ss")}.TempTof";
String jsonData = JsonConvert.SerializeObject(itimestowri.ToList());
jsonData = jsonData.TrimStart('[').TrimEnd(']');
if (File.Exists(fiName))
{
jsonData = $",{jsonData}";
}
File.AppendAllText(fiName, jsonData);
}
private Int32 PlateauMinLengt = 15;
private Double TempConstMarker = 0.01;
private List<TempTofCalc> toGrid = new List<TempTofCalc>();
private void UpdateGridBtn_Clicked(object sender, EventArgs e)
{
}
private void timer3_Tick(Object sender, EventArgs e)
{
MergeToAutoFile();
}
private Double generateTof(Random r , Double temp)
{
var tmp = r.Next(1, 5);
var retrn = (temp / 9000) - Math.Pow(temp, 1.6) / 100_000;
return retrn - (retrn * tmp / 100 + Math.Pow(tmp, 1.6) / 100_000);
}
private void button3_Click(Object sender, EventArgs e)
{
List<TempTofCalc> refTempss = new List<TempTofCalc>();
var sDate = DateTime.UtcNow;
Double refTemp = 5.0;
for (Int32 i = 0; i <= 5; i++)
{
var needUp = 2.5;
var rramp = new Random();
var rtof = new Random();
//plateau
while (needUp >= 0)
{
var temp = rramp.NextDouble() / (needUp * 100);//+0
needUp = needUp - temp;
refTemp = refTemp + temp;
refTempss.Add(new TempTofCalc() { RefDate = sDate.AddSeconds(refTempss.Count), RefTemp = refTemp, Tof = generateTof(rtof, refTemp) });
}
var ra = new Random();
var tillI = ra.Next(150, 4000);//+0
var r = new Random(tillI);
for (Int32 platI = 0; platI < tillI; platI++)
{
var temp = r.NextDouble() / 5;
refTempss.Add(new TempTofCalc() { RefDate = sDate.AddSeconds(refTempss.Count), RefTemp = refTemp + temp, Tof = generateTof(rtof, refTemp) });
}
}
var jsonData = JsonConvert.SerializeObject(refTempss);
File.AppendAllText($"124563_{start.ToString("yyyy_MM_dd_HH-mm-ss")}.TempTof", jsonData);
}
private Boolean automatic = false;
private AutomaticTemperaturCalibration automaticCalibration;
private void StartAutomaticalyBtn_Clicked(object sender, EventArgs _)
{
this.automatic = true;
this.start = DateTimeOffset.Now;
this._currentGenesis?.StartRecordData();
this._currentGenesis?.WriteRegister<Byte>(Register.Genesisflow.SampleRate, 16, checkRegister: true);
this.automaticCalibration = new AutomaticTemperaturCalibration(() => this.OutputAvgTof());
this.automaticCalibration.TemperatureMesured += this.SaveMessurement;
this.automaticCalibration.StartMessuring();
this.SetEnabled(ProgrammState.RecordRuning);
}
private void StopAutomaticalyBtn_Clicked(Object sender, EventArgs e)
{
this.automatic = false;
this.automaticCalibration.TemperatureMesured -= this.SaveMessurement;
this.MergeToAutoFile();
this.automaticCalibration.StopMessuring();
this._currentGenesis?.WriteRegister<Byte>(Register.Genesisflow.SampleRate, 0, checkRegister: true);
this.SetEnabled(ProgrammState.RecordStoped);
}
private void SaveMessurement(double meterTOF, double referenceT, DateTime timestamp)
{
var pcbid = this._currentGenesis.PcbId;
File.AppendAllLines($"{timestamp:yyyy_MM_dd}.messurements", new[] { $"{pcbid};{referenceT};{meterTOF};{timestamp.ToLongTimeString()}" });
var tmp = referenceT.ToString("us-US");
var tof = meterTOF.ToString("us-US");
LocalWebRequest.PostRequestAsync($"http://10.49.40.25/MeterProcessState/api/TempFlange/AddLut?PcbId={pcbid}&RefTempC={tmp}&AvgTof={tof}");
}
}
internal class AutomaticTemperaturCalibration
{
private readonly int messurementsCount;
private readonly Watcher temperatureWatcher;
private readonly Watcher messurementWatcher;
private readonly Func<double> meterTCallback;
private readonly ICollection<KeyValuePair<double, double>> messurements;
private double referenceT;
private int messurmentN;
private int checksCount;
private StreamReader streamReader;
private NamedPipeClientStream pipeStream;
public AutomaticTemperaturCalibration(Func<double> meterTCallback)
{
if (meterTCallback != null)
{
this.meterTCallback = meterTCallback;
this.messurementsCount = Appsettings.MessurementDauer / Appsettings.MessurementStep;
this.temperatureWatcher = new Watcher(Appsettings.TemperatureCheckInMs);
this.temperatureWatcher.Elapsed += this.CheckTemperature;
this.messurementWatcher = new Watcher(Appsettings.MessurementStep);
this.messurementWatcher.Elapsed += this.Messure;
this.messurements = new List<KeyValuePair<double, double>>();
}
}
public event Action<double, double, DateTime> TemperatureMesured;
public void StartMessuring()
{
this.pipeStream = new NamedPipeClientStream(".", "TempPipe", PipeDirection.In);
this.pipeStream.Connect(3000);
if (this.pipeStream.IsConnected)
{
this.streamReader = new StreamReader(this.pipeStream);
this.StopMessurementWatcher();
this.StartTemperatureWatcher();
}
}
public void StopMessuring()
{
this.StopMessurementWatcher();
this.StopTemperatureWatcher();
this.streamReader.Dispose();
this.pipeStream.Close();
}
private void CheckTemperature(object sender, ElapsedEventArgs e)
{
// If reading reference temperature succeeds.
if (this.TryGetReferenceT(out var referenceT))
{
// calculating the difference to previouse point.
var differenceT = Math.Abs(this.referenceT - referenceT);
// If difference is greater as configured then reset counter and start counting.
if (differenceT >= Appsettings.TemperatureStepInC)
{
this.checksCount = 1;
}
// If smaller as 0.05, the temperature is same also continue counting.
else if (differenceT <= Appsettings.TemperaturToleranceInC)
{
this.checksCount++;
}
this.referenceT = referenceT;
// if count of same temperature events reaches 10 then start messurement for configured time.
if (this.checksCount == 10)
{
this.checksCount = 1;
// next call breaks current method execution.
this.StopTemperatureWatcher();
// next call starts messurements again.
this.StartMessurementWatcher();
}
}
}
private void Messure(object sender, ElapsedEventArgs e)
{
// If reading reference temperature succeeds and is in the expected range.
if (this.TryGetReferenceT(out var referenceT))
{
if (Math.Abs(this.referenceT - referenceT) <= Appsettings.TemperaturToleranceInC)
{
this.referenceT = referenceT;
var meterTOF = this.meterTCallback();
this.messurements.Add(new KeyValuePair<double, double>(this.referenceT, meterTOF));
}
else
{
this.messurmentN = this.messurementsCount;
}
this.messurmentN++;
if (this.messurmentN >= this.messurementsCount)
{
var avgT = this.messurements.Average(x => x.Key);
var avgTOF = this.messurements.Average(x => x.Value);
this.messurements.Clear();
this.TemperatureMesured?.Invoke(avgTOF, avgTOF, e.SignalTime);
// next call breaks current method execution.
this.StopMessurementWatcher();
// next call starts seeking for new temperature change.
this.StartTemperatureWatcher();
}
}
}
private void StartTemperatureWatcher()
{
this.temperatureWatcher.AutoReset = true;
this.temperatureWatcher.Enabled = true;
this.temperatureWatcher.Start();
}
private void StopTemperatureWatcher()
{
this.temperatureWatcher.Stop();
this.temperatureWatcher.Enabled = false;
this.temperatureWatcher.AutoReset = false;
}
private void StartMessurementWatcher()
{
this.messurmentN = 0;
this.messurementWatcher.AutoReset = true;
this.messurementWatcher.Enabled = true;
this.messurementWatcher.Start();
}
private void StopMessurementWatcher()
{
this.messurmentN = 0;
this.messurementWatcher.Stop();
this.messurementWatcher.Enabled = false;
this.messurementWatcher.AutoReset = false;
}
private bool TryGetReferenceT(out double referenceT)
{
referenceT = 0;
var succeeded = false;
var readAttempts = 1000;
do
{
var referenceTemperatureLine = this.streamReader.ReadLine();
if (double.TryParse(referenceTemperatureLine, NumberStyles.Any, new CultureInfo("de"), out referenceT)
|| double.TryParse(referenceTemperatureLine, NumberStyles.Any, new CultureInfo("us"), out referenceT))
{
succeeded = true;
break;
}
Thread.Sleep(10);
} while (readAttempts-- > 0);
return succeeded;
}
}
}