using System; using System.Linq; using System.Collections.Generic; using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump; using Dfs.WayneChina.SpsDbModels.Models; using Dfs.WayneChina.HengshanTerminalWrapper; using Dfs.WayneChina.HengshanTerminalWrapper.MessageEntity.Base; using Dfs.WayneChina.HengshanTerminalWrapper.MessageEntity.Incoming; using Dfs.WayneChina.HengshanTerminalWrapper.MessageEntity.Outgoing; using Edge.Core.Database; using Edge.Core.Database.Models; using Wayne.FDCPOSLibrary; using Dfs.WayneChina.CardTrxManager.TrxScanner; using Dfs.WayneChina.CardTrxManager.TrxSubmitter; using Dfs.WayneChina.HengshanPos.Model; using Applications.FDC; using Dfs.WayneChina.HengshanFPos.FPosDbManager.Model; using Dfs.WayneChina.HengshanFPos.FPosDbManager; using FdcServerHost; using Dfs.WayneChina.CardTrxManager; using System.Timers; using System.Threading; using Dfs.WayneChina.HyperPrinterHandler; using System.Threading.Tasks; namespace Dfs.WayneChina.HengshanPos { /// /// A retrofit of Hengshan iPOS (or EPS/后台), manages forecourt fuel sale transactions. /// public class HengshanPosApp : IAppProcessor { #region Properties //IProcessor property public Guid Id => Guid.Parse("D7E68991-E77A-432C-A2ED-19541FE45E7C"); public string MetaConfigName { get; set; } public CampaignEngine CampaignEngine { get; private set; } #endregion #region Fields private FdcServerHostApp fdcServerApp; private IEnumerable fdcPumpControllers; private IEnumerable> hengshanTerminalHandlers; private FPosDbManager fPosDbManager; private TrxScanner trxScanner; private TrxSubmitter trxSubmitter; private static NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("HengshanPos"); private static NLog.Logger alarmLogger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("Alarm"); private Dictionary<(int, int), int> nozzles = new Dictionary<(int, int), int>(); private Dictionary fuelingManagers = new Dictionary(); private List trxSubmitters = new List(); private SqliteDbContext fdcDbContext; private FPosDbContext fPosDbContext; private bool started = false; private object syncObjQueue = new object(); private Queue trxQueue = new Queue(); private CloudCredential cloudCredential; private System.Timers.Timer sfScanTimer; private PrinterHandler printerHandler; private Dictionary mreDict = new Dictionary(); #endregion #region Constructor public HengshanPosApp(string otherParameter, string discountConfig, string enableSFScan, string username, string password, string authServiceBaseUrl, string transactionServiceBaseUrl, string deviceSN) { //this.logger = LogManager.GetLogger("DynamicPrivate_" + privateLogFileName); Console.ForegroundColor = ConsoleColor.Green; CampaignEngine = new CampaignEngine(discountConfig); cloudCredential = new CloudCredential { UserName = username, Password = password, AuthServiceBaseUrl = authServiceBaseUrl, TransactionServiceBaseUrl = transactionServiceBaseUrl, DeviceSN = deviceSN }; fdcDbContext = new SqliteDbContext(); fPosDbContext = new FPosDbContext(); fPosDbManager = new FPosDbManager(fPosDbContext); trxScanner = new TrxScanner(fPosDbManager); trxSubmitter = new TrxSubmitter(0, fPosDbManager, cloudCredential); //Startup check IEnumerable toBeSubmittedTrxs; if (!started) { toBeSubmittedTrxs = GetUnsubmittedTrxsAfterStartup(); foreach (var t in toBeSubmittedTrxs) { lock (syncObjQueue) { logger.Info("Enqueue trx after startup"); trxQueue.Enqueue(t); } } started = true; } //sfScanTimer = new System.Timers.Timer(); //sfScanTimer.Elapsed += new ElapsedEventHandler(Eplased); //// Set it to go off every 20 seconds //sfScanTimer.Interval = 10 * 1000; //sfScanTimer.Enabled = true; bool sfScanEnabled = Convert.ToBoolean(enableSFScan); if (sfScanEnabled) { RegularScanTask.Run(new Action(PeriodicScan), TimeSpan.FromSeconds(20)); } Console.WriteLine("HengshanPos APP STARTED..."); } private void Eplased(object sender, ElapsedEventArgs e) { string receipt = "\r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\t\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\t\r\n\t \r\n\t \r\n\t\t \r\n\t\r\n\t \r\n\t \r\n\t \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
小票编号:I1204102726
日期时间2018-12-04 10:27:25
------------------------------
名称数量单价总额
92#0.397.703.00
枪号:\r\n 4\r\n\t
折扣:0.06
共计:2.94
------------------------------
支付方式收款找零
充值卡2.940.00
卡号:10010557
卡余额:6085.28
------------------------------
祝您愉快
欢迎下次光临
\r\n\r\n"; if (printerHandler != null) printerHandler.SendFormattedReceipt(4, receipt); } private IEnumerable GetUnsubmittedTrxsAfterStartup() { return fPosDbContext.FPosTransactions.Where(t => t.Submitted == false && t.CardType == 3); } #endregion #region Background scanning private void PeriodicScan() { logger.Info("Periodic scanning started..."); lock (syncObjQueue) { if (trxQueue.Count > 0) { logger.Info("Pending transaction to be submitted to cloud!"); var outTrx = trxQueue.Dequeue(); var fdcTransaction = fdcDbContext.PumpTransactionModels.FirstOrDefault(m => m.ReleaseToken == outTrx.ReleaseToken); if (fdcTransaction != null) { logger.Info($"Fdc Transaction exists, ask scanner to find it." + $" pumpId: {outTrx.PumpId}," + $" nozzleId:{outTrx.NozzleId}," + $" FPos SqNo:{outTrx.FPosSqNo}," + $" FC SqNo:{outTrx.FcSqNo}"); trxScanner.StartScanAsync(outTrx.PumpId, outTrx.FPosSqNo, outTrx.FcSqNo, outTrx.ReleaseToken, new FdcTransaction { Finished = true, Nozzle = new LogicalNozzle(outTrx.PumpId, 0, Convert.ToByte(outTrx.NozzleId), null), Barcode = Convert.ToInt32(fdcTransaction.ProductBarcode), Volumn = fdcTransaction.Volumn, Price = fdcTransaction.UnitPrice, Amount = fdcTransaction.Amount, VolumeTotalizer = fdcTransaction.VolumeTotalizer, AmountTotalizer = fdcTransaction.AmountTotalizer, SequenceNumberGeneratedOnPhysicalPump = Convert.ToInt32(fdcTransaction.TransactionSeqNumberFromPhysicalPump), }); } } } var unsubmittedTransaction = fPosDbContext.FPosTransactions .Where(t => t.Reserved == false && t.Submitted == false && t.CardType == 3); if (unsubmittedTransaction.Count() > 0) { logger.Info("Found not reserved and unsubmitted transactions"); var oldestTrx = unsubmittedTransaction.OrderBy(t => t.Id).FirstOrDefault(); if (oldestTrx != null) { var fdcTransaction = fdcDbContext.PumpTransactionModels.FirstOrDefault(m => m.ReleaseToken == oldestTrx.ReleaseToken); if (fdcTransaction != null) { logger.Info($"Fdc Transaction exists, ask scanner to find it." + $" pumpId: {oldestTrx.PumpId}," + $" nozzleId:{oldestTrx.NozzleId}," + $" FPos SqNo:{oldestTrx.FPosSqNo}," + $" FC SqNo:{oldestTrx.FcSqNo}"); trxScanner.StartScanAsync(oldestTrx.PumpId, oldestTrx.FPosSqNo, oldestTrx.FcSqNo, oldestTrx.ReleaseToken, new FdcTransaction { Finished = true, Nozzle = new LogicalNozzle(oldestTrx.PumpId, 0, Convert.ToByte(oldestTrx.NozzleId), null), Barcode = Convert.ToInt32(fdcTransaction.ProductBarcode), Volumn = fdcTransaction.Volumn, Price = fdcTransaction.UnitPrice, Amount = fdcTransaction.Amount, VolumeTotalizer = fdcTransaction.VolumeTotalizer, AmountTotalizer = fdcTransaction.AmountTotalizer, SequenceNumberGeneratedOnPhysicalPump = Convert.ToInt32(fdcTransaction.TransactionSeqNumberFromPhysicalPump), }); } } } else { logger.Info("Have not found any transaction that are NOT reserved and unsbumitted"); } } #endregion #region Init //IApplication method implementation public void Init(IEnumerable processors) { InfoLog("HengshanPosApp Init begins..."); var pumpControllers = new List(); var communicableControllers = new List(); var hengshanTerminals = new List>(); foreach (dynamic processor in processors) { if (processor is IAppProcessor) { FdcServerHostApp fdcServer = processor as FdcServerHostApp; if (fdcServer != null) { logger.Info("FdcServerHostApp retrieved as an IApplication instance!"); fdcServerApp = processor; } PrinterHandler printer = processor as PrinterHandler; if (printer != null) { InfoLog("Printer handler is available"); printerHandler = printer; } continue; } var handler = processor.Context.Handler; if (handler is IFdcPumpController) pumpControllers.Add(handler); else if (handler is IEnumerable) pumpControllers.AddRange(handler); else if (handler is IDeviceHandler) hengshanTerminals.Add(handler); } fdcPumpControllers = pumpControllers; //all pumps hengshanTerminalHandlers = hengshanTerminals; //all Hengshan IC Terminals InfoLog("HengshanPosApp init ends..."); } #endregion #region IProcessor implementation public Task Start() { InfoLog("HengshanPosApp Start begins..."); SetupSiteNozzles(fdcPumpControllers); SetupFuelingManagers(fdcPumpControllers); SetupTransactionSubmitters(fdcPumpControllers); SetupMRE(); foreach (var termHanlder in hengshanTerminalHandlers) { var icTermHandler = termHanlder as HengshanICTermHandler; if (icTermHandler != null) { icTermHandler.OnTerminalMessageReceived += IcTermHandler_OnTerminalMessageReceived; } } foreach (var fdcPumpController in fdcPumpControllers) { fdcPumpController.OnStateChange += FdcPumpController_OnStateChange; } if (fdcServerApp != null) { FdcServerHostApp fdcServer = fdcServerApp as FdcServerHostApp; fdcServer.OnCurrentFuellingStatusChange += FdcServer_OnCurrentFuellingStatusChange; } if (trxScanner != null) { trxScanner.OnMatchingTrxFound += TrxScanner_OnMatchingTrxFound; trxScanner.OnNoMatchFound += TrxScanner_OnNoMatchFound; } foreach (var transactionSubmitter in trxSubmitters) { transactionSubmitter.OnReceiptReceived += TransactionSubmitter_OnReceiptReceived; } InfoLog("HengshanPosApp Start ends..."); return Task.FromResult(true); } private void TransactionSubmitter_OnReceiptReceived(object sender, ReceiptReceivedEventArgs e) { InfoLog("TrxSubmitter begins sending receipt data to printer..."); if (printerHandler != null) printerHandler.SendFormattedReceipt(e.NozzleNo, e.ReceiptData); } public Task Stop() { foreach (var termHanlder in hengshanTerminalHandlers) { var icTermHandler = termHanlder as HengshanICTermHandler; if (icTermHandler != null) { icTermHandler.OnTerminalMessageReceived -= IcTermHandler_OnTerminalMessageReceived; } } foreach (var fdcPumpController in fdcPumpControllers) { fdcPumpController.OnStateChange -= FdcPumpController_OnStateChange; } fdcServerApp.OnCurrentFuellingStatusChange -= FdcServer_OnCurrentFuellingStatusChange; if (trxScanner != null) { trxScanner.OnMatchingTrxFound -= TrxScanner_OnMatchingTrxFound; trxScanner.OnNoMatchFound -= TrxScanner_OnNoMatchFound; } if (mreDict!= null && mreDict.Count > 0) { foreach (var mre in mreDict) { mre.Value?.Dispose(); } } return Task.FromResult(true); } #endregion #region Event handlers private void FdcServer_OnCurrentFuellingStatusChange(object sender, FdcServerTransactionDoneEventArg e) { var fuelingManager = GetFuelingManager(e.Transaction.Nozzle.PumpId); if (fuelingManager != null) { //Update the fueling fuelingManager.UpdateFueling(e.Transaction); fuelingManager.UpdateFuelingState(FuelingState.Running); if (e.Transaction.Finished) { HandleFinishedFueling(fuelingManager, e); } } else { InfoLog(e.Transaction.Nozzle.PumpId, "could not get the fueling manager on fuelling state change"); } } private void FdcPumpController_OnStateChange(object sender, FdcPumpControllerOnStateChangeEventArg e) { IFdcPumpController currentPump = sender as IFdcPumpController; if (currentPump != null) { var pumpState = e.NewPumpState; var fuelingManager = GetFuelingManager(currentPump.PumpId); if (fuelingManager != null) { if (pumpState == LogicalDeviceState.FDC_CALLING) { var callingNozzle = e.StateChangedNozzles.First().LogicalId; InfoLog(currentPump.PumpId, $"calling nozzle id: {callingNozzle}"); if (fuelingManager.ActiveFueling != null && fuelingManager.ActiveFueling.ActiveNozzle != callingNozzle) { //calling nozzle changed, unauthorize the pump. var result = fuelingManager.UnauthorizePump(); fuelingManager.ActiveFueling = null; } else { //Prepare the Active fueling. fuelingManager.CreateFueling(callingNozzle); } } } else { InfoLog($"Fueling manager is null on PumpState change for pump id: {currentPump.PumpId}"); } } else { InfoLog($"Sender is not an IFdcPumpController, PumpStateChange, New State: {e.NewPumpState}"); return; } } private void LockFuelSale(int clientId, int pumpId, int fdcSeqNo, int releaseToken) { mreDict[pumpId].Reset(); InfoLog("Synchronizing locking"); fdcServerApp.LockFuelSaleTrxAndNotifyAllFdcClientsAsync(clientId, pumpId, fdcSeqNo, releaseToken).GetAwaiter().GetResult(); mreDict[pumpId].Set(); InfoLog("Locking done, continue"); } private void HandleFinishedFueling(FuelingManager fuelingManager, FdcServerTransactionDoneEventArg e) { //Lock the card transaction first if (fuelingManager.ActiveFueling != null && fuelingManager.ActiveFueling.AuthorizedByCard) { InfoLog($"Locking fuel sale transaction, " + $"Pump id: {e.Transaction.Nozzle.PumpId}" + $" FdcTrx SqNo: {e.Transaction.SequenceNumberGeneratedOnPhysicalPump}," + $" Trx amount: {e.Transaction.Amount}"); ThreadPool.QueueUserWorkItem( new WaitCallback(delegate (object state) { LockFuelSale(100, e.Transaction.Nozzle.PumpId, e.Transaction.SequenceNumberGeneratedOnPhysicalPump, e.ReleaseToken.Value); }), null); } fuelingManager.UpdateFuelingState(FuelingState.Completed); InfoLog($"Fueling done on" + $" pump id: {e.Transaction.Nozzle.PumpId}," + $" nozzle id: {e.Transaction.Nozzle.LogicalId}," + $" PumpSqNo: {e.Transaction.SequenceNumberGeneratedOnPhysicalPump}"); fPosDbManager.AddMapping(fuelingManager.ActiveFueling.FuelingSqNo, e.Transaction.SequenceNumberGeneratedOnPhysicalPump, e.Transaction.Nozzle.PumpId, e.Transaction.Nozzle.LogicalId, e.ReleaseToken.Value, (decimal)e.Transaction.Amount / 100); InfoLog(e.Transaction.Nozzle.PumpId, "start to search the matching IC card trx..."); trxScanner.StartScanAsync(e.Transaction.Nozzle.PumpId, fuelingManager.ActiveFueling.FuelingSqNo, e.Transaction.SequenceNumberGeneratedOnPhysicalPump, e.ReleaseToken.Value, e.Transaction); //Active fueling is completed, then store it. fuelingManager.SetFuelingCompleted(e.Transaction); fuelingManager.StoreLastFueling(e.Transaction); } private void TrxScanner_OnMatchingTrxFound(object sender, MatchingTrxFoundEventArgs e) { //No need to lock non-customer card transactions. if (e.TrdInfo.CardType != (byte)ICCardType.Customer) { ThreadPool.QueueUserWorkItem(new WaitCallback(delegate (object state) { UnlockFuelSaleTransaction(100, e.FdcTrx.Nozzle.PumpId, (ushort)e.TrdInfo.SeqNo, e.FdcSqNo, e.ReleaseToken); }), null); //var fuelSaleTrx = fdcServerApp.UnlockFuelSaleTrxAndNotifyAllFdcClients( // 100, e.FdcTrx.Nozzle.PumpId, e.FdcSqNo, e.ReleaseToken); //InfoLog($"Not a customer transaction, SqNo: {e.TrdInfo.SeqNo}, " // + $"unlock it for further processing by POS, Success? {fuelSaleTrx != null}"); return; } var submitter = GetTrxSubmitter(e.FdcTrx.Nozzle.PumpId); bool submitSuccess = submitter.SubmitTrx(e.TrdInfo, e.FdcSqNo, e.FdcTrx); DebugLog(e.FdcTrx.Nozzle.PumpId, $" Submitting FPOS transaction of SeqNo: {e.FdcSqNo} success: {submitSuccess}"); //Clear the fuel sale transaction regardless of submission status ClearFuelSaleTransaction(e); } private void UnlockFuelSaleTransaction(int clientId, int pumpId, ushort fPosSqNo, int fdcTrxNo, int releaseToken) { InfoLog("Wait until locking is done"); mreDict[pumpId].WaitOne(); var fuelSaleTrx = fdcServerApp.UnlockFuelSaleTrxAndNotifyAllFdcClientsAsync(clientId, pumpId, fdcTrxNo, releaseToken).Result; InfoLog($"Not a customer transaction, SqNo: {fPosSqNo}, " + $"unlock it for further processing by POS, Success? {fuelSaleTrx != null}"); } private async void TrxScanner_OnNoMatchFound(object sender, NoMatchFoundEventArgs e) { alarmLogger.Info($"\n No matching IC card record found\n Fdc SqNo: {e.FdcSqNo}," + $" on pump: {e.FdcTrx.Nozzle.PumpId}, nozzle: {e.FdcTrx.Nozzle.LogicalId}, amount: {e.FdcTrx.Amount} unlocking fuel sale transaction"); await fdcServerApp.UnlockFuelSaleTrxAndNotifyAllFdcClientsAsync(100, e.FdcTrx.Nozzle.PumpId, e.FdcSqNo, e.ReleaseToken); } private void ClearFuelSaleTransaction(MatchingTrxFoundEventArgs e) { FdcServerHostApp fdcServer = fdcServerApp as FdcServerHostApp; if (fdcServer != null) { var fuelSaleTrx = fdcServer.ClearFuelSaleTrxAndNotifyAllFdcClientsAsync( e.TrdInfo.PumpNo, Convert.ToString(e.FdcSqNo), e.ReleaseToken, "100").Result; InfoLog($"Fuel sale transaction state: {fuelSaleTrx.State.ToString()}"); } } private void IcTermHandler_OnTerminalMessageReceived(object sender, HengshanTerminalMessageEventArgs e) { #region GetNozzleStatus if (e.Message is GetNozzleStatusRequest) { HandleGetNozzleStatusRequest(e); return; } #endregion //fuelingManager and terminal for all request/response handling var fuelingManager = GetFuelingManager(e.PumpId); var terminal = GetTerminalById(e.PumpId); #region Reserve pump if (e.Message is ReservePumpWithAmountRequest) { InfoLog(e.PumpId, "ReservePumpWithAmountRequest" + $" amount: {(e.Message as ReservePumpWithAmountRequest).Amount}"); if (fuelingManager != null) { var reservePumpWithAmountResponse = fuelingManager.ReservePumpWithAmount((e.Message as ReservePumpWithAmountRequest).Amount); terminal?.Write(reservePumpWithAmountResponse); } else { ErrorLog(e.PumpId, "ReservePumpWithAmountRequest, no pump found, respond with failure!"); ReservePumpWithAmountResponse failedResponse = new ReservePumpWithAmountResponse { EnumResult = NonCardDispenserMessageTemplateBase.Result.失败 }; terminal?.Write(failedResponse); return; } } #endregion else if (e.Message is ReservePumpWithGallonRequest) { if (fuelingManager != null) { var reservePumpWithGallonResponse = fuelingManager.ReservePumpWithGallon((e.Message as ReservePumpWithGallonRequest).Gallon); terminal?.Write(reservePumpWithGallonResponse); } else { logger.Info($"ReservePumpWithGallonRequest: No pump found for this id - {e.PumpId}, respond with failure!"); ReservePumpWithGallonResponse failedResponse = new ReservePumpWithGallonResponse { EnumResult = NonCardDispenserMessageTemplateBase.Result.失败 }; terminal?.Write(failedResponse); return; } } else if (e.Message is RoundUpByVolumeRequest) { if (fuelingManager != null) { var roundUpByVolumeResponse = fuelingManager.HandleVolumeRounding(); terminal?.Write(roundUpByVolumeResponse); } else { logger.Error($"RoundUpByVolumeRequest: No pump found for pump {e.PumpId}, respond with failure!"); RoundUpByVolumeResponse failureResponse = new RoundUpByVolumeResponse { EnumResult = NonCardDispenserMessageTemplateBase.Result.失败 }; terminal?.Write(failureResponse); return; } } #region Pump authorization // // Authorize pump with preset amount // else if (e.Message is AuthPumpWithAmountRequest) { #region Old way auth pump with amount request var fdcPump = GetPumpById(e.PumpId); if (fdcPump == null) { logger.Info($"AuthPumpWithAmountRequest: No pump found for this id - {e.PumpId}, respond with failure!"); SendPumpAuthResponse(e.PumpId, false, PumpAuthType.Amount); return; } bool success = fdcPump.AuthorizeWithAmountAsync((e.Message as AuthPumpWithAmountRequest).Amount, (byte)e.PumpId).Result; SendPumpAuthResponse(e.PumpId, success, PumpAuthType.Amount); #endregion } // // Authorize pump with preset volume // else if (e.Message is AuthPumpWithGallonRequest) { #region old way auth pump with gallon request var fdcPump = GetPumpById(e.PumpId); if (fdcPump == null) { logger.Info($"AuthPumpWithGallonRequest: No pump found for this id - {e.PumpId}, respond with failure!"); SendPumpAuthResponse(e.PumpId, false, PumpAuthType.Volume); return; } bool success = fdcPump.AuthorizeWithAmountAsync((e.Message as AuthPumpWithGallonRequest).Gallon, (byte)e.PumpId).Result; SendPumpAuthResponse(e.PumpId, success, PumpAuthType.Volume); #endregion } else if (e.Message is AuthPumpWithKiloRequest) { //To be implemented when it's required. } // // Hengshan terminal command, Command 'Open' equals 'authorize pump' // else if (e.Message is OpenRequest) { InfoLog($"OpenRequest for pump {e.PumpId}"); OpenResponse openResponse = new OpenResponse { EnumResult = NonCardDispenserMessageTemplateBase.Result.成功 }; terminal?.Write(openResponse); if (fuelingManager != null) { fuelingManager.ActiveFueling.FuelingState = FuelingState.Authorized; fuelingManager.ActiveFueling.AuthorizedByCard = true; bool authResult = fuelingManager.AuthorizePump(); logger.Info($"Authorizing pump {e.PumpId}, success? {authResult}"); } else { logger.Info($"No fueling manager for this OpenRequest with pump id:{e.PumpId}"); } } #endregion #region Pump un-authorization else if (e.Message is UnAuthorizePumpRequest) { #region old way un-authorize pump request var fdcPump = GetPumpById(e.PumpId); if (fdcPump == null) { logger.Info($"UnAuthorizePumpRequest: No pump found for this id - {e.PumpId}, respond with failure!"); SendPumpUnauthResponse(e.PumpId, false, PumpUnauthType.Unauthorize); return; } #endregion } else if (e.Message is CloseRequest) { logger.Debug($"CloseRequest for Pump {e.PumpId}"); if (fuelingManager != null) { bool unauthResult = fuelingManager.UnauthorizePump(); logger.Debug($"Close resposne, success? {unauthResult}"); SendPumpUnauthResponse(e.PumpId, unauthResult, PumpUnauthType.General); } else { logger.Info($"CloseRequest: No pump found for this id - {e.PumpId}, respond with failure!"); SendPumpUnauthResponse(e.PumpId, false, PumpUnauthType.General); return; } } #endregion #region Pump Accumulator else if (e.Message is GetAccumulateRequest) { var terminalHandler = GetTerminalById(e.PumpId); if (fuelingManager != null) { InfoLog(e.PumpId, $"GetAccumulateRequest"); var response = fuelingManager.GetAccumulator(1); terminalHandler?.Write(response); } else { GetAccumulateResponse getAccumulateResponse = new GetAccumulateResponse { 升累计 = 0, 金额累计 = 0 }; terminalHandler?.Write(getAccumulateResponse); } } #endregion #region Rounding request else if (e.Message is RoundingRequest) { DebugLog(e.PumpId, "RoundingRequest"); if (fuelingManager != null) { SendRoundingResponse(e.PumpId, fuelingManager.GetRoundingResult()); } else { var roundingResponse = new RoundingResponse { EnumResult = NonCardDispenserMessageTemplateBase.Result.失败 }; SendRoundingResponse(e.PumpId, roundingResponse); } } #endregion #region Cancel full monitor else if (e.Message is CancelFullMonitorRequest) { InfoLog(e.PumpId, "CancelFuellMonitorRequest"); var response = new CancelFullMonitorResponse { EnumResult = NonCardDispenserMessageTemplateBase.Result.成功 }; terminal?.Write(response); InfoLog(e.PumpId, "CancelFullMonitorResponse"); } #endregion } #endregion #region Send rounding response /// /// Sends the rounding (凑整) response to IC card reader. /// /// Current pump id. /// The rounding response. private void SendRoundingResponse(int pumpId, RoundingResponse response) { var terminal = GetTerminalById(pumpId); terminal?.Write(response); } #endregion #region Authorization /// /// Sends pump authorization response to Hengshan EPS. /// /// Current pump id. /// Authorization result, success or failure. /// Pump authorization type. private void SendPumpAuthResponse(int pumpId, bool success, PumpAuthType authType) { var terminal = GetTerminalById(pumpId); NonCardDispenserMessageTemplateBase authResponse = null; if (authType == PumpAuthType.Amount) //Authorize pump with preset amount { authResponse = new AuthPumpWithAmountResponse { EnumResult = success ? NonCardDispenserMessageTemplateBase.Result.成功 : NonCardDispenserMessageTemplateBase.Result.失败 }; } else if (authType == PumpAuthType.Volume) //Authorize pump with preset volume { authResponse = new AuthPumpWithGallonResponse { EnumResult = success ? NonCardDispenserMessageTemplateBase.Result.成功 : NonCardDispenserMessageTemplateBase.Result.失败 }; } else if (authType == PumpAuthType.General) //Open request { authResponse = new OpenResponse { EnumResult = success ? NonCardDispenserMessageTemplateBase.Result.成功 : NonCardDispenserMessageTemplateBase.Result.失败 }; } terminal?.Write(authResponse); } /// /// Send pump un-authoirzation response to Hengshan EPS. /// /// Current pump id. /// Un-authorization result, success or failure. /// Un-authorization type. private void SendPumpUnauthResponse(int pumpId, bool success, PumpUnauthType unauthType) { var terminal = GetTerminalById(pumpId); NonCardDispenserMessageTemplateBase unauthResponse = null; if (unauthType == PumpUnauthType.Unauthorize) //Unauthorize pump, regardless of amount or volume preset { unauthResponse = new UnAuthorizePumpResponse { EnumResult = success ? NonCardDispenserMessageTemplateBase.Result.成功 : NonCardDispenserMessageTemplateBase.Result.失败 }; } else if (unauthType == PumpUnauthType.General) //Close request { unauthResponse = new CloseResponse { EnumResult = success ? NonCardDispenserMessageTemplateBase.Result.成功 : NonCardDispenserMessageTemplateBase.Result.失败 }; } terminal?.Write(unauthResponse); } #endregion #region Handle NozzleStatusRequest /// /// Handles the GetNozzleStatusRequest sent from Hengshan IC Terminal. /// /// The request message event. private void HandleGetNozzleStatusRequest(HengshanTerminalMessageEventArgs e) { if (logger.IsDebugEnabled) logger.Debug("\n"); DebugLog(e.PumpId, $"GetNozzleStatusRequest for pump id: {e.PumpId}"); DebugLog(e.PumpId, "GNS Req"); var fuelingManager = GetFuelingManager(e.PumpId); if (fuelingManager != null) { DebugLog(e.PumpId, $"Start to handle nozzle status request"); var currentNozzleStatus = fuelingManager.GetNozzleStatus(); SendNozzleStatusResponse(e.PumpId, currentNozzleStatus); var currentFdcPump = fdcPumpControllers.FirstOrDefault(p => p.PumpId == e.PumpId); if (currentFdcPump != null && currentFdcPump.QueryStatusAsync().Result == LogicalDeviceState.FDC_CALLING) { InfoLog(e.PumpId, $"{currentNozzleStatus.ToLogString()}"); } DebugLog(e.PumpId, $"{currentNozzleStatus.ToLogString()}"); } else { InfoLog($"Could not get the FuelingManager for pump id = {e.PumpId}"); SendNozzleStatusResponse(e.PumpId, GetNozzleStatusResponse.PumpStatus.不允许加油); return; } } #endregion #region Send NozzleStatusResponse private void SendNozzleStatusResponse(int pumpId, GetNozzleStatusResponse response) { var terminal = GetTerminalById(pumpId); if (terminal == null) { throw new NullReferenceException("Hengshan Terminal could not be found!"); } terminal.Write(response); } private void SendNozzleStatusResponse(int pumpId, GetNozzleStatusResponse.PumpStatus pumpStatus) { var getNozzleStatusResponse = new GetNozzleStatusResponse(); getNozzleStatusResponse.AddPumpStatus(pumpStatus); SendNozzleStatusResponse(pumpId, getNozzleStatusResponse); } #endregion #region Get Pump /// /// Gets the FDC pump controller. /// /// The pump id. /// private IFdcPumpController GetPumpById(int pumpId) { return fdcPumpControllers?.FirstOrDefault(f => f.PumpId == pumpId); } #endregion #region Get Terminal /// /// Gets the Hengshan IC terminal handler /// /// The pump id. /// private HengshanICTermHandler GetTerminalById(int pumpId) { foreach (var termHandler in hengshanTerminalHandlers) { var terminal = termHandler as HengshanICTermHandler; if (terminal != null && terminal.AssociatedPumpId.Equals(pumpId)) return terminal; } return null; } #endregion #region Site nozzles setup /// /// Set up the site nozzles, in particular the nozzle number /// Strategy: /// 1. Sort the Pumps by id /// 2. Sort the nozzles by logical id /// 3. The site nozzle number then will be (Pump id, Logical nozzle id) => site nozzle number /// e.g. (1, 1) => 1, (1, 2) => 2, (2, 1) => 3, (3, 1) => 4, (3, 2) => 5, (4, 1) => 6 ... /// /// The Fdc pump instance collection. private void SetupSiteNozzles(IEnumerable fdcPumpControllers) { nozzles.Clear(); int nozzleNo = 1; //Site nozzle number starts with 1 var pumps = fdcPumpControllers.OrderBy(c => c.PumpId); foreach (var pump in pumps) { var pumpNozzles = pump.Nozzles.OrderBy(n => n.LogicalId); foreach (var nozzle in pumpNozzles) { InfoLog($"({pump.PumpId}, {nozzle.LogicalId}) => {nozzleNo}"); nozzles.Add((pump.PumpId, nozzle.LogicalId), nozzleNo); nozzleNo++; } } } #endregion #region Fueling manager setup private void SetupFuelingManagers(IEnumerable fdcPumpControllers) { fuelingManagers.Clear(); foreach (var fdcPump in fdcPumpControllers) { fuelingManagers.Add(fdcPump.PumpId, new FuelingManager(fdcPump.PumpId, fdcPump, fdcServerApp, fdcDbContext, fPosDbManager, CampaignEngine)); InfoLog($"Fueling manager for pump id: {fdcPump.PumpId} added."); } } private FuelingManager GetFuelingManager(int pumpId) { DebugLog(pumpId, "get fueling manager"); FuelingManager fuelingManager = null; fuelingManagers.TryGetValue(pumpId, out fuelingManager); return fuelingManager; } #endregion #region Set up transaction sumbitters private void SetupTransactionSubmitters(IEnumerable fdcPumpControllers) { trxSubmitters.Clear(); foreach (var fdcPump in fdcPumpControllers) { trxSubmitters.Add( new TrxSubmitter(fdcPump.PumpId, fPosDbManager, cloudCredential) { SiteNozzles = nozzles }); InfoLog($"Transaction submitter for pump id: {fdcPump.PumpId} added."); } } private TrxSubmitter GetTrxSubmitter(int pumpId) { return trxSubmitters.FirstOrDefault(t => t.Id == pumpId); } #endregion #region Setup MRE private void SetupMRE() { foreach (var fdcPump in fdcPumpControllers) { mreDict.Add(fdcPump.PumpId, new ManualResetEvent(false)); } } #endregion #region Log methods private void InfoLog(string log) { if (logger.IsInfoEnabled) logger.Info(log); } private void InfoLog(int pumpId, string log) { if (logger.IsInfoEnabled) logger.Info($"Pump {pumpId}, {log}"); } private void DebugLog(int pumpId, string log) { if (logger.IsDebugEnabled) logger.Debug($"Pump {pumpId}, {log}"); } private void ErrorLog(int pumpId, string log) { if (logger.IsErrorEnabled) logger.Error($"Pump {pumpId}, {log}"); } #endregion } }