using Edge.Core.Database.Models; using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump; using Edge.Core.Parser.BinaryParser.Util; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using Wayne.FDCPOSLibrary; using Global_Pump_Fdc.MessageEntity; using Global_Pump_Fdc.MessageEntity.Outgoing; using System.Collections.Specialized; using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump; namespace Global_Pump_Fdc { public class PumpHandler : IFdcPumpController, IDeviceHandler { static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("PumpHandler"); private LogicalDeviceState lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE; private DateTime lastLogicalDeviceStateReceivedTime; // by minutes, change this value need change the correlated deviceOfflineCountdownTimer's interval as well private const int lastLogicalDeviceStateExpiredTime = 3; private List nozzles = new List(); private System.Timers.Timer deviceOfflineCountdownTimer; /// /// will set to true once nozzle change to OUT while internal fdc state is FDC_FUELING, indicates a fule is done. /// 'true' will block pump auth request from outer /// private bool lastFillRetrievedSinceNozzleDownAtFdcFueling = true; /// /// the pump calling, fuelling message does not contain nozzle info, need track the operating nozzle /// seperatly in other message which will be kept in this variable. /// default set to 0 which is an invalid nozzle id for wayne dart pump(valid should starts from 1). /// private byte operatingNozzlePhysicalId = 0; private IContext context; private bool isOnFdcServerInitCalled = false; private int pumpId; private PumpGroupHandler parent; /// /// address used in wayne dart protocol to comm with physical pump. /// 0x4F + physical pump mother board side config address(range from 1-32) /// private byte dartPumpCommAddress; /// /// when first time connected with physical pump , in some case, the pump will not report any status actively, /// so need send a status query from FC. /// From then on, pump will actively notify FC when state changes, no need to send query anymore from FC. /// private bool initialPumpStatueEverRetrieved = false; private int amountDecimalDigits; private int volumeDecimalDigits; private int priceDecimalDigits; private int volumeTotalizerDecimalDigits; /// /// for avoid a case that FC may miss a pump status change event(pump side issue? or wire issue?), /// we timely actively request the pump state. /// //private System.Timers.Timer pollingPumpStatus; //private int pollingPumpStatusInterval = 30000; #region MyRegion public string Name => "Global_Pump_Fdc"; public int PumpId => this.pumpId; /// /// Gets the pump physical id. /// address used in wayne dart protocol to comm with physical pump. /// 0x4F + physical pump mother board side config address(range from 1-32) /// public int PumpPhysicalId => this.dartPumpCommAddress; public IEnumerable Nozzles => this.nozzles; public int AmountDecimalDigits => this.amountDecimalDigits; public int VolumeDecimalDigits => this.volumeDecimalDigits; public int PriceDecimalDigits => this.priceDecimalDigits; public int VolumeTotalizerDecimalDigits => this.volumeDecimalDigits; public Guid Id => Guid.NewGuid(); public event EventHandler OnStateChange; public event EventHandler OnCurrentFuellingStatusChange; public async Task AuthorizeAsync(byte logicalNozzleId) { if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling) { logger.Info("Pump: " + this.pumpId + ", " + "Start Authorize pump with logicalNozzle: " + logicalNozzleId + " is denied by internal"); return false; } var authorizeSucceed = false; logger.Info("Pump: " + this.pumpId + ", " + "Start Authorize pump with logicalNozzle: " + logicalNozzleId + ", first send AllowedNozzleNumbersRequest(allow all)"); // now always allow all nozzles var response = await this.context.Outgoing.WriteAsync( new AuthorizeRequest(this.pumpId, this.nozzles.First().LogicalId), (request, testResponse) => { var parameters = testResponse.Parameters as StringDictionary; if (parameters == null || parameters["EventType"] != "AuthorizePump") return false; string pumpId = parameters["PumpID"]; string success = parameters["Success"]; authorizeSucceed = int.Parse(pumpId) == this.PumpId; logger.Info("Pump: " + pumpId + ", " + "AuthorizeWithAmountAsync for nozzle: " + logicalNozzleId + " , success: " + success); return authorizeSucceed; }, 3500); return authorizeSucceed; } public async Task AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId) { if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling) { logger.Info("Pump: " + this.pumpId + ", " + "Start AuthorizeWithAmount pump with logicalNozzle: " + logicalNozzleId + " is denied by internal"); return false; } moneyAmountWithoutDecimalPoint = (int)(moneyAmountWithoutDecimalPoint / Math.Pow(10, AmountDecimalDigits)); logger.Info("Pump: " + this.pumpId + ", " + "start AuthorizeWithAmount pump with logicalNozzle: " + logicalNozzleId + ", moneyAmount: " + moneyAmountWithoutDecimalPoint + ", first send AllowedNozzleNumbersRequest(allow all)"); var authorizeSucceed = false; // now always allow all nozzles var response = await this.context.Outgoing.WriteAsync( new PresetAmountRequest(this.pumpId, this.nozzles.First().LogicalId, moneyAmountWithoutDecimalPoint), (request, testResponse) => { var parameters = testResponse.Parameters as StringDictionary; if (parameters == null || parameters["EventType"] != "AuthorizePump") return false; string pumpId = parameters["PumpID"]; string success = parameters["Success"]; authorizeSucceed = int.Parse(pumpId) == this.PumpId; logger.Info("Pump: " + pumpId + ", " + "AuthorizeWithAmountAsync for nozzle: " + logicalNozzleId + " , success: " + success); return authorizeSucceed; }, 3500); return authorizeSucceed; } public async Task AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId) { if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling) { logger.Info("Pump: " + this.pumpId + ", " + "Start AuthorizeWithVolumn pump with logicalNozzle: " + logicalNozzleId + " is denied by internal"); return false; } var authorizeSucceed = false; logger.Info("Pump: " + this.pumpId + ", " + "start AuthorizeWithVolumn pump with logicalNozzle: " + logicalNozzleId + ", volumn: " + volumnWithoutDecimalPoint + ", first send AllowedNozzleNumbersRequest(allow all)"); // now always allow all nozzles var response = await this.context.Outgoing.WriteAsync( new PresetVolumeRequest(this.pumpId, this.nozzles.First().LogicalId, volumnWithoutDecimalPoint), (request, testResponse) => { var parameters = testResponse.Parameters as StringDictionary; if (parameters == null || parameters["EventType"] != "AuthorizePump") return false; string pumpId = parameters["PumpID"]; string success = parameters["Success"]; authorizeSucceed = int.Parse(pumpId) == this.PumpId; logger.Info("Pump: " + pumpId + ", " + "AuthorizeWithVolumn for nozzle: " + logicalNozzleId + " , success: " + success); return authorizeSucceed; }, 3500); return authorizeSucceed; } public Task SetFuelPrice(int fuelGrade, double newFuelPrice) { return Task.FromResult(true); } public Task ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId) { try { if (newPriceWithoutDecimalPoint > 10000) { int fuelGrade = logicalNozzleId; double newFuelPrice = (double)(newPriceWithoutDecimalPoint - 10000) / 1000; return this.InternalChangeFuelPriceAsync(fuelGrade, newFuelPrice); } } catch (Exception exxx) { logger.Error("Pump: " + this.pumpId + ", " + "Exceptioned in ChangeFuelPrice: " + exxx); return Task.FromResult(false); } return Task.FromResult(true); } private async Task InternalChangeFuelPriceAsync(int fuelGrade, double newFuelPrice) { if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED || this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE) { logger.Info("Pump: " + this.pumpId + ", " + " Pump is in state FDC_CLOSED or FDC_OFFLINE, InternalChangeFuelPrice will return false"); return false; } bool changePriceSucceed = false; logger.Info("Pump: " + this.pumpId + ", " + " InternalChangeFuelPrice starting with prices: " + newFuelPrice.ToString()); var priceChangedResponse = await this.context.Outgoing.WriteAsync( new PriceUpdateRequest(fuelGrade, newFuelPrice), null, 2000); // this FC handle WayneDart pump price change one nozzle by nozzle, so change prices on single FuelPoint with multiple // nozzle will interpreted as multiple price change request, by testing, too fast send multiple price change request // to a FP might be ignored by pump side though message here are all good, so hardcode sleep a while. Thread.Sleep(1000); return changePriceSucceed; } public async Task FuelingRoundUpByAmountAsync(int amount) { logger.Info("Pump: " + this.pumpId + ", " + "start FuelingRoundUpByAmount with amount: " + amount); var isSucceed = false; //this.context.Outgoing.WriteAsync( // new PresetAmountRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId), amount), // (request, testResponse) => // testResponse.Adrs == this.dartPumpCommAddress && // testResponse.BlockSeqNumber == request.BlockSeqNumber, // (request, response) => // { // if (response != null) // { // if (response.ControlCharacter == ControlCharacter.ACK) // { // logger.Debug("Pump: " + this.pumpId + ", " + "PresetAmountRequest ACKed"); // isSucceed = true; // } // else // { // logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest NAKed"); // isSucceed = false; // } // } // else // { // logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest waiting ACK timed out"); // isSucceed = false; // } // }, 1500); return isSucceed; } public async Task FuelingRoundUpByVolumeAsync(int volume) { return false; } public async Task QueryStatusAsync() { return this.lastLogicalDeviceState; } public async Task> QueryTotalizerAsync(byte logicalNozzleId) { var result = new System.Tuple(-1, -1); try { logger.Info("Pump: " + this.pumpId + ", " + "start QueryTotalizer pump with nozzle: " + logicalNozzleId); if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED || this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE) { logger.Info("Pump: " + this.pumpId + ", " + " Pump is in state FDC_CLOSED or FDC_OFFLINE, totalizer will return -1, -1"); return new System.Tuple(-1, -1); } var nozzlePhysicalId = this.nozzles.FirstOrDefault(n => n.LogicalId == logicalNozzleId)?.PhysicalId; if (nozzlePhysicalId == null) { logger.Info("Pump: " + this.pumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " does not exists, totalizer will return -1, -1"); return new System.Tuple(-1, -1); } var response = await this.context.Outgoing.WriteAsync( new QueryTotalizerRequest(this.pumpId, logicalNozzleId), (request, testResponse) => { var parameters = testResponse.Parameters as StringDictionary; if (parameters == null || parameters["EventType"] != "TotalizerReceived") return false; string volumeTotal = parameters["VolumeTotalizer"]; string amountTotal = parameters["AmountTotalizer"]; logger.Info("Pump: " + this.pumpId + ", " + "QueryTotalizer for nozzle: " + logicalNozzleId + " succeed, volume total: " + volumeTotal + " , amount total: " + amountTotal); int amountTotalizer = 0; var amountTotalizerOriginal = decimal.Parse(amountTotal) * (decimal)Math.Pow(10, AmountDecimalDigits); if (amountTotalizerOriginal > int.MaxValue) { amountTotalizer = Convert.ToInt32(amountTotalizerOriginal / 10); } else { amountTotalizer = (int)(decimal.Parse(amountTotal) * (decimal)Math.Pow(10, AmountDecimalDigits)); } var volumeTotalizer = (int)(decimal.Parse(volumeTotal) * (decimal)Math.Pow(10, volumeTotalizerDecimalDigits)); result = new System.Tuple(amountTotalizer, volumeTotalizer); return true; }, 2000); } catch (Exception ex) { logger.Error("Pump: " + this.pumpId + ", " + ex); } //var response = await this.context.Outgoing.WriteAsync(new RequestTotalVolumeCountersRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId), // nozzlePhysicalId.Value), // (request, testResponse) => // testResponse.Adrs == this.dartPumpCommAddress && // testResponse.ControlCharacter == ControlCharacter.DATA, 3000); //if (response != null) //{ // this.context.Outgoing.Write(new ACK(this.dartPumpCommAddress, response.BlockSeqNumber)); // var totalCountersTrx = // new TotalCounters_TransactionData(response.TransactionDatas.First(f => f.TransactionNumber == 0x65)); // logger.Info("Pump: " + this.pumpId + ", " + "QueryTotalizer for nozzle: " + logicalNozzleId + " succeed, volume total: " + totalCountersTrx.TotalValue); // result = new System.Tuple(-1, totalCountersTrx.TotalValue); //} //else //{ // logger.Error("Pump: " + this.pumpId + ", " + "QueryTotalizer waiting Data timed out"); //} return result; } public async Task ResumeFuellingAsync() { throw new NotImplementedException(); } public async Task SuspendFuellingAsync() { throw new NotImplementedException(); } /// /// unauthorize the authed fueling point, will trigger wayne dart pump switched to `FILLING COMPLETE` state. /// /// wayne dart no need specify nozzle id /// public async Task UnAuthorizeAsync(byte logicalNozzleId) { var unauthorizeSucceed = false; //logger.Info("Pump: " + this.pumpId + ", " + "Start UnAuthorize pump with nozzle: " + logicalNozzleId); //var authorizedResponse = await this.context.Outgoing.WriteAsync( // new StopRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)), // (_, testResponse) => // testResponse.Adrs == this.dartPumpCommAddress && // testResponse.BlockSeqNumber == _.BlockSeqNumber, 3500); //if (authorizedResponse != null) //{ // if (authorizedResponse.ControlCharacter == ControlCharacter.ACK) // { // logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest ACKed and succeed"); // unauthorizeSucceed = true; // } // else // logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest NAKed (rejected?)"); //} //else //{ // logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest timed out"); //} return unauthorizeSucceed; } public async Task LockNozzleAsync(byte logicalNozzleId) { return false; } public async Task UnlockNozzleAsync(byte logicalNozzleId) { return false; } #endregion public PumpHandler(PumpGroupHandler parent, int pumpId, int amountDecimalDigits, int volumeDecimalDigits, int priceDecimalDigits, int volumeTotalizerDecimalDigits, string pumpXmlConfiguration) { this.parent = parent; this.pumpId = pumpId; this.amountDecimalDigits = amountDecimalDigits; this.volumeDecimalDigits = volumeDecimalDigits; this.priceDecimalDigits = priceDecimalDigits; this.volumeTotalizerDecimalDigits = volumeTotalizerDecimalDigits; // sample of pumpXmlConfiguration // // // // // // // var xmlDocument = new XmlDocument(); xmlDocument.LoadXml(pumpXmlConfiguration); var physicalPumpAddressConfiguratedInPump = byte.Parse(xmlDocument.SelectSingleNode("/Pump").Attributes["physicalId"].Value); if (physicalPumpAddressConfiguratedInPump > 0x20) throw new ArgumentOutOfRangeException("Wayne dart pump only accept pump address range from 1 to 32, make sure this value is correctly configurated in physical pump mother board"); this.dartPumpCommAddress = (byte)(0x4F + physicalPumpAddressConfiguratedInPump); foreach (var nozzleElement in xmlDocument.GetElementsByTagName("Nozzle").Cast()) { var nozzlePhysicalId = byte.Parse(nozzleElement.Attributes["physicalId"].Value); var nozzleLogicalId = byte.Parse(nozzleElement.Attributes["logicalId"].Value); var nozzleRawDefaultPriceWithoutDecimal = nozzleElement.Attributes["defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb"].Value; if (nozzlePhysicalId < 1 || nozzlePhysicalId > 8) throw new ArgumentOutOfRangeException("Wayne dart pump only accept nozzle physical id range in config from 1 to 8"); this.nozzles.Add(new LogicalNozzle(pumpId, nozzlePhysicalId, nozzleLogicalId, null) { ExpectingPriceOnFcSide = int.Parse(nozzleRawDefaultPriceWithoutDecimal) }); logger.Info("Pump: " + this.pumpId + ", created a nozzle with logicalId: " + nozzleLogicalId + ", physicalId: " + nozzlePhysicalId + ", default raw price without decimal points: " + nozzleRawDefaultPriceWithoutDecimal); } this.deviceOfflineCountdownTimer = new System.Timers.Timer(10000); this.deviceOfflineCountdownTimer.Elapsed += (_, __) => { if (DateTime.Now.Subtract(this.lastLogicalDeviceStateReceivedTime).TotalMinutes >= lastLogicalDeviceStateExpiredTime) { this.initialPumpStatueEverRetrieved = false; if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_OFFLINE) { this.lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE; logger.Info("Pump: " + this.pumpId + ", " + " State switched to FDC_OFFLINE due to long time no see pump data incoming"); var safe0 = this.OnStateChange; safe0?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_OFFLINE)); logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back"); } } }; this.deviceOfflineCountdownTimer.Start(); } public void OnFdcServerInit(Dictionary parameters) { /* Wayne Dart pump will miss last price when disconnected or power off * from FC a while(pump state `PUMP NOT PROGRAMMED` indicates this happened), so here * is trying to recover the price from Fdc database, and then push to Pump*/ if (parameters.ContainsKey("LastPriceChange")) { // nozzle logical id:rawPrice var lastPriceChanges = parameters["LastPriceChange"] as Dictionary; foreach (var priceChange in lastPriceChanges) { logger.Info("Pump: " + this.pumpId + ", " + "Pump " + this.pumpId + " OnFdcServerInit, load last price change " + "on logical nozzle: " + priceChange.Key + " with price: " + priceChange.Value); this.nozzles.First(n => n.LogicalId == priceChange.Key).ExpectingPriceOnFcSide = priceChange.Value; } } /* Load Last sale trx(from db) for void the case of FC accidently disconnect from Pump in fueling, and may cause a fueling trx gone from FC control */ if (parameters.ContainsKey("LastFuelSaleTrx")) { // nozzle logical id:LastSale var lastFuelSaleTrxes = parameters["LastFuelSaleTrx"] as Dictionary; foreach (var lastFuelSaleTrx in lastFuelSaleTrxes) { logger.Info("Pump: " + this.pumpId + ", OnFdcServerInit, load last volume Totalizer " + "on logical nozzle: " + lastFuelSaleTrx.Key + " with volume value: " + lastFuelSaleTrx.Value.VolumeTotalizer); this.nozzles.First(n => n.LogicalId == lastFuelSaleTrx.Key).VolumeTotalizer = lastFuelSaleTrx.Value.VolumeTotalizer; } } this.isOnFdcServerInitCalled = true; } public void Init(IContext context) { this.context = context; this.context.Communicator.OnConnected += (a, b) => { //this.context.Outgoing.Write(new ReturnStatusRequest(this.dartPumpCommAddress, 0)); }; //this.context.Communicator.OnDisconnected += (a, b) => this.pumpStatusEverReceived = false; } public Task Process(IContext context) { var parameters = context.Incoming.Message.Parameters as StringDictionary; if (parameters == null) { logger.Error("Read invalid FCC message" + context.Incoming.Message.Parameters); return Task.CompletedTask; } this.lastLogicalDeviceStateReceivedTime = DateTime.Now; string eventType = parameters["EventType"]; if (eventType == "FuellingStatusChange") { var logicalHoseId = byte.Parse(parameters["ho"]); var barcode = int.Parse(parameters["GR"]); var currentQty = double.Parse(parameters["VO"]) * Math.Pow(10, VolumeDecimalDigits); var currentAmount = decimal.Parse(parameters["AM"]) * Convert.ToDecimal(Math.Pow(10, AmountDecimalDigits)); var currentPrice = double.Parse(parameters["PU"]) * Math.Pow(10, PriceDecimalDigits); var finished = parameters["Finished"] == "true" ? true : false; var paid = parameters["State"] == "PayableTransaction" ? false : true; var sequenceNo = int.Parse(parameters["FuelingSequenceNo"]); foreach (var item in parameters.Keys) { logger.Debug($"key: {item}, value: {parameters[(string)item]}"); } logger.Debug(string.Format("currentQty: {0}, currentAmount: {1}, paid: {2}", currentQty, currentAmount, paid)); if (paid) return Task.CompletedTask; var safe = this.OnCurrentFuellingStatusChange; safe?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction() { Nozzle = this.nozzles.First(n => n.LogicalId == logicalHoseId), Barcode = barcode, Amount = (int)currentAmount, Volumn = (int)currentQty, Price = Convert.ToInt32(currentPrice), Finished = finished, SequenceNumberGeneratedOnPhysicalPump = sequenceNo })); } else if (eventType == "PumpStatusChange") { LogicalNozzle ln = null; var pumpStatusStr = parameters["ST"]; if (pumpStatusStr == "ERROR") { this.lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE; } else if (pumpStatusStr == "IDLE") { this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY; } else if (pumpStatusStr == "CALLING") { var logicalHoseId = byte.Parse(parameters["ho"]); ln = new LogicalNozzle(PumpId, logicalHoseId, logicalHoseId, null); this.lastLogicalDeviceState = LogicalDeviceState.FDC_CALLING; } else if (pumpStatusStr == "AUTHORIZED") { if (parameters.ContainsKey("ho")) { var logicalHoseId = byte.Parse(parameters["ho"]); ln = new LogicalNozzle(PumpId, logicalHoseId, logicalHoseId, null); } this.lastLogicalDeviceState = LogicalDeviceState.FDC_AUTHORISED; } else if (pumpStatusStr == "FUELLING") { var logicalHoseId = byte.Parse(parameters["ho"]); ln = new LogicalNozzle(PumpId, logicalHoseId, logicalHoseId, null); this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING; } else { this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING; } var safe = this.OnStateChange; var e = ln == null ? new FdcPumpControllerOnStateChangeEventArg(this.lastLogicalDeviceState) : new FdcPumpControllerOnStateChangeEventArg(this.lastLogicalDeviceState, ln); safe?.Invoke(this, e); } else { } return Task.CompletedTask; } private void UnAuthorizePumpAndSwithToFdcReady() { //this.context.Outgoing.WriteAsync( // new StopRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)), // (request, testResponse) => // testResponse.Adrs == this.dartPumpCommAddress && // testResponse.BlockSeqNumber == request.BlockSeqNumber, // (request, response) => // { // if (response != null) // { // if (response.ControlCharacter == ControlCharacter.ACK) // { // if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY) // { // logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_READY due to StopRequest acked."); // this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY; // var safe3 = this.OnStateChange; // safe3?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY)); // logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back"); // return; // } // } // else // logger.Error("Pump: " + this.pumpId + ", " + "StopRequest NAKed."); // } // else // logger.Error("Pump: " + this.pumpId + ", " + "StopRequest request timed out for ack."); // if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY) // { // logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_READY though StopRequest failed"); // this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY; // var safe2 = this.OnStateChange; // safe2?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY)); // logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back"); // } // }, 1500); } private System.Timers.Timer retryReadLastFillTimer; private int onReadingLastFill = 0; /// /// may get called via 2 route: /// 1. received a Filling_Complete from pump. /// 2. by a delay timer that fired by nozzle IN from FDC_FUELING state. /// /// private void LoopReadLastFill(byte nozzlePhysicalIdForTrxDoneOn) { if (0 == Interlocked.CompareExchange(ref this.onReadingLastFill, 1, 0)) { this.retryReadLastFillTimer?.Stop(); this.retryReadLastFillTimer?.Dispose(); // disable auth since last fill has not been read yet, this happens in real site with bad wire connection condition. //this.lastFillRetrieved = false; this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (t) => { if (t != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; } logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 1st time"); this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (tt) => { if (tt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; } logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 2nd time"); this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (ttt) => { if (ttt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; } logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 3rd time"); this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (tttt) => { if (tttt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; } logger.Error("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will NOT retry anymore (total read 4 times)"); // Have no way but release it to allow continue pump auth and fueling, But a fuel sale has been lost! this.onReadingLastFill = 0; this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; }); }); }); }); } } /// /// /// /// /// FdcTransaction is null when retrieved failed, like time out. otherwise, an object will return private void ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(byte targetNozzlePhysicalId, Action callback) { } } }