using Edge.Core.Database; using Edge.Core.Processor; using Edge.Core.IndustryStandardInterface.Pump; using HengShan_Pump_TQC_IFSF.MessageEntity; using HengShan_Pump_TQC_IFSF.MessageEntity.Incoming; using HengShan_Pump_TQC_IFSF.MessageEntity.Outgoing; using Edge.Core.Parser.BinaryParser.Util; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using Wayne.FDCPOSLibrary; using Timer = System.Timers.Timer; using Edge.Core.Configuration; namespace HengShan_Pump_TQC_IFSF { public class PumpHandler : IFdcPumpController//, IDeviceHandler { private object syncObject = new object(); private IEnumerable nozzleExtraInfos = null; //protected static ILog logger = log4net.LogManager.GetLogger("PumpHandler"); protected static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("PumpHandler"); protected IContext context; /// /// default to first nozzle /// protected byte currentCallingNozzleLogicalId = 1; protected LogicalDeviceState lastLogicalDeviceState = LogicalDeviceState.FDC_CLOSED; private DateTime lastLogicalDeviceStateReceivedTime; // by seconds public const int lastLogicalDeviceStateExpiredTime = 6; private Guid uniqueId = Guid.NewGuid(); private PumpGroupHandler parent; protected int pumpId = -1; protected List nozzles = new List(); private byte recipientSubnet = 1; private byte recipientNode = 1; private byte ifsfFuelPointId; Func msgTokenGenerator; /// /// /// /// /// range from 0x21 to 0x24 /// used to locate the remote tqc pump, must a value different from FC /// used to locate the remote tqc pump /// public PumpHandler(PumpGroupHandler parent, int pumpId, byte ifsfFuelPointId, byte recipientSubnet, byte recipientNode, string nozzlesXmlConfiguration, Func msgTokenGenerator) { //sample of nozzlesXmlConfiguration // // // // // // this.pumpId = pumpId; this.parent = parent; this.ifsfFuelPointId = ifsfFuelPointId; this.recipientSubnet = recipientSubnet; this.recipientNode = recipientNode; this.msgTokenGenerator = msgTokenGenerator; var xmlDocument = new XmlDocument(); xmlDocument.LoadXml(nozzlesXmlConfiguration); foreach (var nozzleElement in xmlDocument.GetElementsByTagName("Nozzle").Cast()) { byte physicalIdValue; var rawPhysicalIdValue = nozzleElement.Attributes["physicalId"].Value; if (rawPhysicalIdValue.StartsWith("0x") || rawPhysicalIdValue.StartsWith("0X")) physicalIdValue = byte.Parse(nozzleElement.Attributes["physicalId"].Value.Substring(2), NumberStyles.HexNumber); else physicalIdValue = byte.Parse(nozzleElement.Attributes["physicalId"].Value); if (physicalIdValue < 0x11 || physicalIdValue > 0x18) throw new ArgumentException("ifsf nozzle id must be range from 0x11 to 0x18, while the configuration passed in " + rawPhysicalIdValue); this.nozzles.Add(new LogicalNozzle(pumpId, physicalIdValue, byte.Parse(nozzleElement.Attributes["logicalId"].Value), null)); //if (nozzleElement.Attributes["ifsfLogicalNozzleId"] != null) // this.logicalNozzleIdMappingToIfsfLogicalNozzlelId.Add( // byte.Parse(nozzleElement.Attributes["ifsfLogicalNozzleId"].Value.Substring(2), NumberStyles.HexNumber), // byte.Parse(nozzleElement.Attributes["logicalId"].Value)); } } public string Name => this.GetType().FullName; public int PumpId => this.pumpId; /// /// range from 0x21 to 0x24 since it's a IFSF pump /// public int PumpPhysicalId => this.ifsfFuelPointId; public IEnumerable Nozzles => this.nozzles; public int AmountDecimalDigits => 2; public int VolumeDecimalDigits => 2; public int PriceDecimalDigits => 2; public int VolumeTotalizerDecimalDigits => 2; public Guid Id => this.uniqueId; public event EventHandler OnStateChange; public event EventHandler OnCurrentFuellingStatusChange; protected void FireOnStateChangeEvent(LogicalDeviceState state, LogicalNozzle stateChangedNozzle) { try { var safe = this.OnStateChange; safe?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(state, stateChangedNozzle)); } catch (Exception exx) { logger.Info("exceptioned when FireOnStateChangeEvent: " + exx + Environment.NewLine + "Will Ignore and do nothing"); } } protected void FireOnCurrentFuellingStatusChangeEvent(FdcTransaction trx) { try { var safe = this.OnCurrentFuellingStatusChange; safe?.Invoke(this, new FdcTransactionDoneEventArg(trx)); } catch (Exception exx) { logger.Info("exceptioned when FireOnCurrentFuellingStatusChangeEvent: " + exx + Environment.NewLine + "Will Ignore and do nothing"); } } public virtual void Init(IContext context) { this.context = context; } public virtual Task Process(IContext context) { this.context = context; switch (context.Incoming.Message) { case FuellingPointDb_FpStatus_Event fpStatusEvent: { logger.Info("Pump " + this.pumpId + ", receiving Ifsf PumpState(by fpStatusEvent): " + fpStatusEvent.FuelPointState + "(internal FdcState: " + this.lastLogicalDeviceState + ")"); // send query only when at cold start. if (fpStatusEvent.FuelPointState == FuellingPointStatus.Idle && this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED) { logger.Info("Pump " + this.pumpId + ", State changing from Fdc_CLOSED to Ifsf IDLE, always send a query to see any open trx stacked in TQC pump side"); context.Outgoing.Write( new FuelTrxDb_Read_QueryTransaction(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId, null)); } // when trx event piped in, there's no nozzle info linked it within the event(ifsf bad design?), so here always track // and keep the state of the last calling or fuelling nozzle. if (fpStatusEvent.FuelPointState == FuellingPointStatus.CALLING || fpStatusEvent.FuelPointState == FuellingPointStatus.FUELLING) { var operatingNozzlePhysicalId = (byte)(fpStatusEvent.RemovedNozzleIndexes.First() + 0x10); if (this.nozzles.Any(n => n.PhysicalId == operatingNozzlePhysicalId)) this.currentCallingNozzleLogicalId = this.nozzles.First(n => n.PhysicalId == operatingNozzlePhysicalId).LogicalId; else this.currentCallingNozzleLogicalId = fpStatusEvent.RemovedNozzleIndexes.FirstOrDefault(); logger.Info("Pump " + this.pumpId + ", reporting removed(lifted) ifsf NozzleId is 0x" + operatingNozzlePhysicalId.ToHexLogString() + ", correlated Fdc Nozzle logical Id is " + this.currentCallingNozzleLogicalId); } this.HandleFpStatusChange(fpStatusEvent.FuelPointState, this.nozzles.FirstOrDefault(n => n.LogicalId == this.currentCallingNozzleLogicalId)); break; } case FuellingPointDb_FpStatus_Answer fpStatusAnswer: { // send query only when at cold start. if (fpStatusAnswer.FuelPointState == FuellingPointStatus.Idle && this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED) { logger.Info("Pump " + this.pumpId + ", State changed from CLOSED to IDLE, likely a cold start, send query to see any open trx"); context.Outgoing.Write( new FuelTrxDb_Read_QueryTransaction(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId, null)); } this.HandleFpStatusChange(fpStatusAnswer.FuelPointState, this.nozzles.FirstOrDefault(n => n.LogicalId == this.currentCallingNozzleLogicalId)); break; } case FuellingPointDb_FpRunningTransaction_Event fpRunningTrxEvent: { logger.Debug("Pump " + this.pumpId + ", fpRunningTrxEvent ifsf FpId: 0x" + fpRunningTrxEvent.TargetFuelPointId.ToHexLogString() + ", CurAmt and CurVol(no decimal points): " + fpRunningTrxEvent.CurrentAmount + " - " + fpRunningTrxEvent.CurrentVolume); var operatingNozzle = this.nozzles.First(f => f.LogicalId == this.currentCallingNozzleLogicalId); var operatingNozzleProductPrice = this.parent.tqcPumpGroupInitializer.PriceBook[ this.nozzleExtraInfos.First(c => c.PumpId == operatingNozzle.PumpId && c.NozzleLogicalId == operatingNozzle.LogicalId).ProductBarcode]; //fire fuelling progress. var safe1 = this.OnCurrentFuellingStatusChange; safe1?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction() { Nozzle = operatingNozzle, Amount = fpRunningTrxEvent.CurrentAmount, Volumn = fpRunningTrxEvent.CurrentVolume, Price = operatingNozzleProductPrice, Finished = false, })); break; } case FuellingTrxDb_TransactionBufferStatus_Event trxBufferStatusEvent: { if (trxBufferStatusEvent.State == FuellingTrxDb_TransactionBufferStatus_Event.TransactionState.PAYABLE && (trxBufferStatusEvent.Volume != 0 || trxBufferStatusEvent.Amount != 0)) { logger.Debug("Pump " + this.pumpId + ", received a Payable trx from TQC with Vol and Amount(without decimal points): " + trxBufferStatusEvent.Volume + " - " + trxBufferStatusEvent.Amount + ", will send clear to clear it in pump side anyway"); context.Outgoing.WriteAsync( new FuellingPointDbRequest_Write_ClearTransaction(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId, trxBufferStatusEvent.TransactionSeqNumber.ToString()), (request, response) => response is AcknowledgeMessage rp && request is IfsfMessage rq && rp.MessageToken == rp.MessageToken, (_, expectedResponse) => { if (expectedResponse != null) { } else logger.Info("Pump " + this.pumpId + ", Clear transaction in TQC side failed"); var operatingNozzle = this.nozzles.First(f => f.LogicalId == this.currentCallingNozzleLogicalId); var operatingNozzleProductPrice = this.parent.tqcPumpGroupInitializer.PriceBook[this.nozzleExtraInfos.First(c => c.PumpId == operatingNozzle.PumpId && c.NozzleLogicalId == operatingNozzle.LogicalId).ProductBarcode]; var safe1 = this.OnCurrentFuellingStatusChange; safe1?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction() { Nozzle = operatingNozzle, Amount = trxBufferStatusEvent.Amount, Volumn = trxBufferStatusEvent.Volume, Price = operatingNozzleProductPrice, SequenceNumberGeneratedOnPhysicalPump = trxBufferStatusEvent.TransactionSeqNumber, Finished = true, })); }, 12000); } break; } case FuellingTrxDb_TransactionBufferStatus_Answer trxBufferStatusAnswer: { if (trxBufferStatusAnswer.State.HasValue && trxBufferStatusAnswer.State == FuellingTrxDb_TransactionBufferStatus_Event.TransactionState.PAYABLE) { logger.Debug("Pump " + this.pumpId + ", received a Payable trx from TQC with Vol and Amount(without decimal points): " + trxBufferStatusAnswer.Volume + " - " + trxBufferStatusAnswer.Amount + ", sequenceNo.: " + trxBufferStatusAnswer.TransactionSeqNumber + ", will send ClearTransaction to clear it now"); context.Outgoing.WriteAsync( new FuellingPointDbRequest_Write_ClearTransaction(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId, trxBufferStatusAnswer.TransactionSeqNumber.ToString()), (request, response) => response is AcknowledgeMessage rp && request is IfsfMessage rq && rp.MessageToken == rp.MessageToken, (_, expectedResponse) => { if (expectedResponse != null) { var operatingNozzle = this.nozzles.First(f => f.LogicalId == this.currentCallingNozzleLogicalId); var operatingNozzleProductPrice = this.parent.tqcPumpGroupInitializer.PriceBook[this.nozzleExtraInfos.First(c => c.PumpId == operatingNozzle.PumpId && c.NozzleLogicalId == operatingNozzle.LogicalId).ProductBarcode]; var safe1 = this.OnCurrentFuellingStatusChange; safe1?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction() { Nozzle = operatingNozzle, Amount = trxBufferStatusAnswer.Amount, Volumn = trxBufferStatusAnswer.Volume, Price = operatingNozzleProductPrice, SequenceNumberGeneratedOnPhysicalPump = trxBufferStatusAnswer.TransactionSeqNumber, Finished = true, })); } else logger.Error("Clear transaction in TQC side failed"); }, 8000); } break; } case IfsfMessage ifsfMessage: { var dbParser = DatabaseDataParser.New().Convert(ifsfMessage.RawDatabaseData.ToArray()); break; } } //if (context.Incoming.Message is FuellingPointDb_FpStatus_Event fpStatusChangeEvent && fpStatusChangeEvent.TargetFuelPointId == this.ifsfFuelPointId) //{ // this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY; //} return Task.CompletedTask; } public virtual void HandleFpStatusChange(FuellingPointStatus newStatus, LogicalNozzle stateChangedNozzle) { this.lastLogicalDeviceStateReceivedTime = DateTime.Now; switch (newStatus) { case FuellingPointStatus.Idle: { if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY) { this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY; this.FireOnStateChangeEvent(LogicalDeviceState.FDC_READY, null); } break; } case FuellingPointStatus.CALLING: { if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_CALLING) { this.lastLogicalDeviceState = LogicalDeviceState.FDC_CALLING; this.FireOnStateChangeEvent(LogicalDeviceState.FDC_CALLING, stateChangedNozzle); } break; } case FuellingPointStatus.AUTHORISED: { if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_AUTHORISED) { this.lastLogicalDeviceState = LogicalDeviceState.FDC_AUTHORISED; this.FireOnStateChangeEvent(LogicalDeviceState.FDC_AUTHORISED, stateChangedNozzle); } break; } case FuellingPointStatus.STARTED: { if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_STARTED) { // simulate a FDC_AUTHORISED event since TQC will not send this state in. this.lastLogicalDeviceState = LogicalDeviceState.FDC_AUTHORISED; this.FireOnStateChangeEvent(LogicalDeviceState.FDC_AUTHORISED, stateChangedNozzle); this.lastLogicalDeviceState = LogicalDeviceState.FDC_STARTED; this.FireOnStateChangeEvent(LogicalDeviceState.FDC_STARTED, stateChangedNozzle); } break; } case FuellingPointStatus.FUELLING: { if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_FUELLING) { this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING; this.FireOnStateChangeEvent(LogicalDeviceState.FDC_FUELLING, stateChangedNozzle); } break; } case FuellingPointStatus.Inoperative: { if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_OFFLINE) { this.lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE; this.FireOnStateChangeEvent(LogicalDeviceState.FDC_OFFLINE, null); } break; } case FuellingPointStatus.Closed: { this.lastLogicalDeviceState = LogicalDeviceState.FDC_CLOSED; logger.Info("Pump " + this.pumpId + " with ifsf fuelPoint id: 0x" + this.ifsfFuelPointId.ToHexLogString() + " is in Closed state, will open it..."); this.FireOnStateChangeEvent(LogicalDeviceState.FDC_CLOSED, null); context.Outgoing.WriteAsync( new FuellingPointDbRequest_Write_OpenFuelPoint(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId), (request, response) => (response is IfsfMessage rp) && (request is IfsfMessage rq) && rp.MessageToken == rq.MessageToken && rp.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK, (_, response) => { if (response != null) logger.Debug("Pump " + this.pumpId + ", Open FP 0x" + this.ifsfFuelPointId.ToHexLogString() + " Acked succeed"); else logger.Info("Pump " + this.pumpId + ", Open FP 0x" + this.ifsfFuelPointId.ToHexLogString() + " timedout for Ack, failed"); }, 8000); break; } } } public virtual async Task AuthorizeAsync(byte logicalNozzleId) { //bool authSucceed = false; //AutoResetEvent blocker = new AutoResetEvent(false); var authResponse = await this.context.Outgoing.WriteAsync(new FuellingPointDbRequest_Write_AuthorizeFuelPoint(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId, null), (request, response) => (response is AcknowledgeMessage rp) && (request is IfsfMessage rq) && rp.MessageToken == rq.MessageToken && rp.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK, 6000); var authSucceed = false; if (authResponse is AcknowledgeMessage ackMsg) { if (ackMsg.OverallStatus == MessageAcknowledgeStatus.ACK_PositiveAcknowledgeDataReceived) { //context.Outgoing.Write(new FuellingPointDbRequest_Read_FuelPointState(this.recipientSubnet, // this.recipientNode, // PumpGroupHandler.originatorSubnet, // PumpGroupHandler.originatorNode, // this.msgTokenGenerator(), this.ifsfFuelPointId)); logger.Debug("Pump " + this.pumpId + ", Auth FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + " ACKed with succeed"); authSucceed = true; } else { logger.Info("Pump " + this.pumpId + ", Auth FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + " ACKed but failed with internal reason: \r\n" + ackMsg.ToLogString() + "\r\n it may caused by unpaid trx still stacked in TQC pump side and then refuse to be authorized, here send query payable trx request anyway"); context.Outgoing.Write( new FuelTrxDb_Read_QueryTransaction(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId, null)); authSucceed = false; } } else logger.Info("Pump " + this.pumpId + ", Auth FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + " timed out for ACK"); return authSucceed; } public virtual async Task AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId) { // temp fake change!!! return await this.AuthorizeAsync(logicalNozzleId); } public virtual async Task AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId) { throw new NotImplementedException(); } public virtual async Task ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId) { var productNo = this.nozzleExtraInfos.FirstOrDefault(c => c.PumpId == this.pumpId && c.NozzleLogicalId == logicalNozzleId)?.ProductBarcode; if (productNo == null) { logger.Error("Pump " + this.pumpId + ", Change Price for productNo: " + productNo + " with new price: " + newPriceWithoutDecimalPoint + " failed due to product not found in config for target pump"); return false; } logger.Info("Pump: " + this.pumpId + ", " + "Change Fuel Price for LogicalNozzle: " + logicalNozzleId + " with newPriceWithoutDecimalPoint: " + newPriceWithoutDecimalPoint); var priceChangeResponse = await this.context.Outgoing.WriteAsync(new ProductPerFuellingModeDbRequest_Write_SetPrice(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), productNo.ToString(), newPriceWithoutDecimalPoint.ToString(), 0x11), (request, response) => (response is AcknowledgeMessage rp) && (request is IfsfMessage rq) && rp.MessageToken == rq.MessageToken && rp.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK, 8000); bool changePriceSucceed = false; if (priceChangeResponse is AcknowledgeMessage ackMsg && ackMsg.OverallStatus == MessageAcknowledgeStatus.ACK_PositiveAcknowledgeDataReceived) { logger.Info("Pump " + this.pumpId + ", Change Price for productNo: " + productNo + " with new price: " + newPriceWithoutDecimalPoint + " ACKed with Positive but still querying the price again for double confirm..."); var doubleConfirmQueryPriceResponse = await this.context.Outgoing.WriteAsync( new ProductPerFuellingModeDbRequest_Read_ProductPrice(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), productNo.ToString(), 0x11), (readPriceRequest, readPriceResponse) => (readPriceResponse is ProductPerFuellingModeDb_ProductPrice_Answer rp) && (readPriceRequest is IfsfMessage rq) && rp.MessageToken == rq.MessageToken && rp.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER && rp.FuelModeId == 0x11, 5000) as ProductPerFuellingModeDb_ProductPrice_Answer; if (doubleConfirmQueryPriceResponse != null) { logger.Info("Pump " + this.pumpId + ", doubleConfirmQueryPrice, Price for productNo: " + productNo + " queried with price(without decimal points): " + doubleConfirmQueryPriceResponse.Price); int queriedPumpSidePrice; if (!int.TryParse(doubleConfirmQueryPriceResponse.Price, out queriedPumpSidePrice)) { logger.Info("Pump " + this.pumpId + ", Change Price for productNo: " + productNo + " with new price(without decimal points): " + newPriceWithoutDecimalPoint + " failed due to double confirm price response price value is un-recognize: " + (doubleConfirmQueryPriceResponse.Price ?? "")); changePriceSucceed = false; } else { if (queriedPumpSidePrice != newPriceWithoutDecimalPoint) { logger.Info("Pump " + this.pumpId + ", doubleConfirmQueryPrice, Price for productNo: " + productNo + " queried with price(without decimal points): " + doubleConfirmQueryPriceResponse.Price + " does not match expected new price: " + newPriceWithoutDecimalPoint); changePriceSucceed = false; } else { logger.Info("Pump " + this.pumpId + ", doubleConfirmQueryPrice, Price for productNo: " + productNo + " has been double confirmed and it's aligned with TQC side, change price succeed."); changePriceSucceed = true; this.nozzles.First(n => n.LogicalId == logicalNozzleId).RealPriceOnPhysicalPump = newPriceWithoutDecimalPoint; this.parent.tqcPumpGroupInitializer.PriceBook[productNo.Value] = newPriceWithoutDecimalPoint; logger.Info("Pump " + this.pumpId + ", PriceBook updated to: \r\n" + this.parent.tqcPumpGroupInitializer.PriceBook.Keys.Select(s => "productNo: " + s + ", price: " + this.parent.tqcPumpGroupInitializer.PriceBook[s]) .Aggregate((acc, n) => acc + "\r\n" + n)); } } } else { logger.Info("Pump " + this.pumpId + ", doubleConfirmQueryPrice timedout, but still treat this Price change as succeed since Acked with Positive"); changePriceSucceed = true; this.nozzles.First(n => n.LogicalId == logicalNozzleId).RealPriceOnPhysicalPump = newPriceWithoutDecimalPoint; this.parent.tqcPumpGroupInitializer.PriceBook[productNo.Value] = newPriceWithoutDecimalPoint; logger.Info("Pump " + this.pumpId + ", PriceBook updated to: \r\n" + this.parent.tqcPumpGroupInitializer.PriceBook.Keys.Select(s => "productNo: " + s + ", price: " + this.parent.tqcPumpGroupInitializer.PriceBook[s]) .Aggregate((acc, n) => acc + "\r\n" + n)); } } else { logger.Info("Pump " + this.pumpId + ", Change Price for productNo: " + productNo + " with new price(without decimal points): " + newPriceWithoutDecimalPoint + " ACK timed out or ACKed with non-positive, price change failed!"); changePriceSucceed = false; } return changePriceSucceed; } public virtual async Task FuelingRoundUpByAmountAsync(int amount) { // temp fake change!!! return await Task.FromResult(true); } public virtual async Task FuelingRoundUpByVolumeAsync(int volume) { // temp fake change!!! return await Task.FromResult(true); } public virtual async Task QueryStatusAsync() { if (!this.parent.tqcPumpGroupInitializer.IsInitDone) return LogicalDeviceState.FDC_CLOSED; if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED) { logger.Debug("Pump " + this.pumpId + ", QueryStatus(), FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + " still in Closed state, will send a real status query to TQC"); this.context.Outgoing.Write( new FuellingPointDbRequest_Read_FuelPointState(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId)); } return this.lastLogicalDeviceState; } /// /// no decimal point value. /// /// MoneyTotalizer:VolumnTotalizer public virtual async Task> QueryTotalizerAsync(byte logicalNozzleId) { System.Tuple result = new System.Tuple(-1, -1); if (!this.parent.tqcPumpGroupInitializer.IsInitDone) return result; var tqcLogicalNozzleId = this.nozzles.First(n => n.LogicalId == logicalNozzleId).PhysicalId; var queryResponse = await this.context.Outgoing.WriteAsync( new LogicalNozzleDb_Read_QueryTotalizer(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId, tqcLogicalNozzleId) , (request, response) => (response is LogicalNozzleDb_NozzleTotalizer_Answer rp) && (request is IfsfMessage rq) && rp.MessageToken == rq.MessageToken && rp.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER, 8000); if (queryResponse != null && queryResponse is LogicalNozzleDb_NozzleTotalizer_Answer ___) { result = new System.Tuple(Convert.ToInt32(___.MoneyTotal * Math.Pow(10, this.AmountDecimalDigits)), Convert.ToInt32(___.VolumeTotal * Math.Pow(10, this.VolumeDecimalDigits))); logger.Debug("Pump " + this.pumpId + ", QueryTotalizer(), FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + ", FC logicalNozzleId: " + logicalNozzleId + ", Tqc ifsf logicalNozzleId: 0x" + tqcLogicalNozzleId.ToHexLogString() + " succeed, no decimal point money and vol acc: " + result.Item1 + " - " + result.Item2); } else { } return result; } public virtual async Task ResumeFuellingAsync() { throw new NotImplementedException(); } public virtual async Task SuspendFuellingAsync() { throw new NotImplementedException(); } public virtual async Task UnAuthorizeAsync(byte logicalNozzleId) { // temp fake change!!! return await Task.FromResult(true); //throw new NotImplementedException(); } public async Task LockNozzleAsync(byte logicalNozzleId) { return false; } public async Task UnlockNozzleAsync(byte logicalNozzleId) { return false; } #region IDisposable Support private bool disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects). } // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. // TODO: set large fields to null. disposedValue = true; } } // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. // ~PumpHandler() { // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. // Dispose(false); // } // This code added to correctly implement the disposable pattern. public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); // TODO: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); } public void OnFdcServerInit(Dictionary parameters) { if (parameters != null && parameters.TryGetValue("NozzleProductMapping", out object param)) { this.nozzleExtraInfos = param as IEnumerable; } } #endregion } }