HengshanPosApp.cs 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131
  1. using System;
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump;
  5. using Dfs.WayneChina.SpsDbModels.Models;
  6. using Dfs.WayneChina.HengshanTerminalWrapper;
  7. using Dfs.WayneChina.HengshanTerminalWrapper.MessageEntity.Base;
  8. using Dfs.WayneChina.HengshanTerminalWrapper.MessageEntity.Incoming;
  9. using Dfs.WayneChina.HengshanTerminalWrapper.MessageEntity.Outgoing;
  10. using Edge.Core.Database;
  11. using Edge.Core.Database.Models;
  12. using Wayne.FDCPOSLibrary;
  13. using Dfs.WayneChina.CardTrxManager.TrxScanner;
  14. using Dfs.WayneChina.CardTrxManager.TrxSubmitter;
  15. using Dfs.WayneChina.HengshanPos.Model;
  16. using Applications.FDC;
  17. using Dfs.WayneChina.HengshanFPos.FPosDbManager.Model;
  18. using Dfs.WayneChina.HengshanFPos.FPosDbManager;
  19. using FdcServerHost;
  20. using Dfs.WayneChina.CardTrxManager;
  21. using System.Timers;
  22. using System.Threading;
  23. using Dfs.WayneChina.HyperPrinterHandler;
  24. using System.Threading.Tasks;
  25. namespace Dfs.WayneChina.HengshanPos
  26. {
  27. /// <summary>
  28. /// A retrofit of Hengshan iPOS (or EPS/后台), manages forecourt fuel sale transactions.
  29. /// </summary>
  30. public class HengshanPosApp : IAppProcessor
  31. {
  32. #region Properties
  33. //IProcessor property
  34. public Guid Id => Guid.Parse("D7E68991-E77A-432C-A2ED-19541FE45E7C");
  35. public string MetaConfigName { get; set; }
  36. public CampaignEngine CampaignEngine { get; private set; }
  37. #endregion
  38. #region Fields
  39. private FdcServerHostApp fdcServerApp;
  40. private IEnumerable<IFdcPumpController> fdcPumpControllers;
  41. private IEnumerable<IDeviceHandler<byte[], NonCardDispenserMessageTemplateBase>> hengshanTerminalHandlers;
  42. private FPosDbManager fPosDbManager;
  43. private TrxScanner trxScanner;
  44. private TrxSubmitter trxSubmitter;
  45. private static NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("HengshanPos");
  46. private static NLog.Logger alarmLogger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("Alarm");
  47. private Dictionary<(int, int), int> nozzles = new Dictionary<(int, int), int>();
  48. private Dictionary<int, FuelingManager> fuelingManagers = new Dictionary<int, FuelingManager>();
  49. private List<TrxSubmitter> trxSubmitters = new List<TrxSubmitter>();
  50. private SqliteDbContext fdcDbContext;
  51. private FPosDbContext fPosDbContext;
  52. private bool started = false;
  53. private object syncObjQueue = new object();
  54. private Queue<FPosTransaction> trxQueue = new Queue<FPosTransaction>();
  55. private CloudCredential cloudCredential;
  56. private System.Timers.Timer sfScanTimer;
  57. private PrinterHandler printerHandler;
  58. private Dictionary<int, ManualResetEvent> mreDict = new Dictionary<int, ManualResetEvent>();
  59. #endregion
  60. #region Constructor
  61. public HengshanPosApp(string otherParameter, string discountConfig, string enableSFScan,
  62. string username, string password, string authServiceBaseUrl, string transactionServiceBaseUrl, string deviceSN)
  63. {
  64. //this.logger = LogManager.GetLogger("DynamicPrivate_" + privateLogFileName);
  65. Console.ForegroundColor = ConsoleColor.Green;
  66. CampaignEngine = new CampaignEngine(discountConfig);
  67. cloudCredential = new CloudCredential
  68. {
  69. UserName = username,
  70. Password = password,
  71. AuthServiceBaseUrl = authServiceBaseUrl,
  72. TransactionServiceBaseUrl = transactionServiceBaseUrl,
  73. DeviceSN = deviceSN
  74. };
  75. fdcDbContext = new SqliteDbContext();
  76. fPosDbContext = new FPosDbContext();
  77. fPosDbManager = new FPosDbManager(fPosDbContext);
  78. trxScanner = new TrxScanner(fPosDbManager);
  79. trxSubmitter = new TrxSubmitter(0, fPosDbManager, cloudCredential);
  80. //Startup check
  81. IEnumerable<FPosTransaction> toBeSubmittedTrxs;
  82. if (!started)
  83. {
  84. toBeSubmittedTrxs = GetUnsubmittedTrxsAfterStartup();
  85. foreach (var t in toBeSubmittedTrxs)
  86. {
  87. lock (syncObjQueue)
  88. {
  89. logger.Info("Enqueue trx after startup");
  90. trxQueue.Enqueue(t);
  91. }
  92. }
  93. started = true;
  94. }
  95. //sfScanTimer = new System.Timers.Timer();
  96. //sfScanTimer.Elapsed += new ElapsedEventHandler(Eplased);
  97. //// Set it to go off every 20 seconds
  98. //sfScanTimer.Interval = 10 * 1000;
  99. //sfScanTimer.Enabled = true;
  100. bool sfScanEnabled = Convert.ToBoolean(enableSFScan);
  101. if (sfScanEnabled)
  102. {
  103. RegularScanTask.Run(new Action(PeriodicScan), TimeSpan.FromSeconds(20));
  104. }
  105. Console.WriteLine("HengshanPos APP STARTED...");
  106. }
  107. private void Eplased(object sender, ElapsedEventArgs e)
  108. {
  109. string receipt = "\r\n\r\n<table width=\"95%\" border=\"0\" align=\"center\" style=\"table-layout:fixed;margin-top:50px;font-family:monospace;font-size:15px\">\r\n <tr>\r\n <td colspan=\"4\" align=\"center\"></td>\r\n </tr>\r\n <tr>\r\n <td colspan=\"2\" align=\"left\">小票编号:</td>\r\n <td colspan=\"2\" align=\"left\">I1204102726</td>\r\n </tr>\r\n <tr>\r\n <td colspan=\"1\" align=\"left\">日期时间</td>\r\n <td colspan=\"3\" align=\"right\">2018-12-04 10:27:25</td>\r\n </tr>\r\n <tr>\r\n <td colspan=\"4\" align=\"center\" style=\"word-break:keep-all;white-space:nowrap;overflow:hidden\">------------------------------</td>\r\n </tr>\r\n <tr>\r\n <td width=\"25%\" align=\"left\">名称</td>\r\n <td width=\"25%\" align=\"left\">数量</td>\r\n <td width=\"25%\" align=\"left\">单价</td>\r\n <td width=\"25%\" align=\"left\">总额</td>\r\n </tr>\r\n <tr>\r\n <td width=\"25%\" align=\"left\">92#</td>\r\n <td width=\"25%\" align=\"left\">0.39</td>\r\n <td width=\"25%\" align=\"left\">7.70</td>\r\n <td width=\"25%\" align=\"left\">3.00</td>\r\n </tr>\r\n <tr> \r\n\t<td colspan=\"4\" align=\"left\">枪号:\r\n 4\r\n\t</td>\r\n </tr>\r\n <tr>\r\n <td colspan=\"4\" align=\"center\"> </td>\r\n </tr>\r\n <tr>\r\n <td width=\"75%\" align=\"left\">折扣:</td>\r\n <td width=\"25%\" align=\"left\">0.06</td>\r\n </tr>\r\n <tr>\r\n <td width=\"75%\" align=\"left\">共计:</td>\r\n <td width=\"25%\" align=\"left\">2.94</td>\r\n </tr>\r\n <tr>\r\n <td colspan=\"4\" align=\"center\" style=\"word-break:keep-all;white-space:nowrap;overflow:hidden\">------------------------------</td>\r\n </tr>\r\n <tr>\r\n <td colspan=\"2\" align=\"left\">支付方式</td>\r\n <td colspan=\"1\" align=\"center\">收款</td>\r\n <td colspan=\"1\" align=\"right\">找零</td>\r\n </tr>\r\n <tr>\r\n <td colspan=\"2\" align=\"left\">充值卡</td>\r\n <td colspan=\"1\" align=\"center\">2.94</td>\r\n <td colspan=\"1\" align=\"right\">0.00</td>\r\n </tr>\r\n\t<tr>\r\n\t <td colspan=\"2\" align=\"left\">卡号:</td>\r\n\t <td colspan=\"2\" align=\"left\">10010557</td>\r\n\t</tr>\t \r\n\t<tr>\r\n\t <td colspan=\"2\" align=\"left\">卡余额:</td>\r\n\t <td colspan=\"2\" align=\"left\">6085.28</td>\r\n\t</tr> \r\n <tr>\r\n <td colspan=\"4\" align=\"center\" style=\"word-break:keep-all;white-space:nowrap;overflow:hidden\">------------------------------</td>\r\n </tr>\r\n <tr>\r\n <td colspan=\"4\" align=\"center\">祝您愉快</td>\r\n </tr>\r\n <tr>\r\n <td colspan=\"4\" align=\"center\">欢迎下次光临</td>\r\n </tr>\r\n</table>\r\n\r\n";
  110. if (printerHandler != null)
  111. printerHandler.SendFormattedReceipt(4, receipt);
  112. }
  113. private IEnumerable<FPosTransaction> GetUnsubmittedTrxsAfterStartup()
  114. {
  115. return fPosDbContext.FPosTransactions.Where(t => t.Submitted == false && t.CardType == 3);
  116. }
  117. #endregion
  118. #region Background scanning
  119. private void PeriodicScan()
  120. {
  121. logger.Info("Periodic scanning started...");
  122. lock (syncObjQueue)
  123. {
  124. if (trxQueue.Count > 0)
  125. {
  126. logger.Info("Pending transaction to be submitted to cloud!");
  127. var outTrx = trxQueue.Dequeue();
  128. var fdcTransaction = fdcDbContext.PumpTransactionModels.FirstOrDefault(m => m.ReleaseToken == outTrx.ReleaseToken);
  129. if (fdcTransaction != null)
  130. {
  131. logger.Info($"Fdc Transaction exists, ask scanner to find it."
  132. + $" pumpId: {outTrx.PumpId},"
  133. + $" nozzleId:{outTrx.NozzleId},"
  134. + $" FPos SqNo:{outTrx.FPosSqNo},"
  135. + $" FC SqNo:{outTrx.FcSqNo}");
  136. trxScanner.StartScanAsync(outTrx.PumpId, outTrx.FPosSqNo, outTrx.FcSqNo, outTrx.ReleaseToken,
  137. new FdcTransaction
  138. {
  139. Finished = true,
  140. Nozzle = new LogicalNozzle(outTrx.PumpId, 0, Convert.ToByte(outTrx.NozzleId), null),
  141. Barcode = Convert.ToInt32(fdcTransaction.ProductBarcode),
  142. Volumn = fdcTransaction.Volumn,
  143. Price = fdcTransaction.UnitPrice,
  144. Amount = fdcTransaction.Amount,
  145. VolumeTotalizer = fdcTransaction.VolumeTotalizer,
  146. AmountTotalizer = fdcTransaction.AmountTotalizer,
  147. SequenceNumberGeneratedOnPhysicalPump = Convert.ToInt32(fdcTransaction.TransactionSeqNumberFromPhysicalPump),
  148. });
  149. }
  150. }
  151. }
  152. var unsubmittedTransaction = fPosDbContext.FPosTransactions
  153. .Where(t => t.Reserved == false && t.Submitted == false && t.CardType == 3);
  154. if (unsubmittedTransaction.Count() > 0)
  155. {
  156. logger.Info("Found not reserved and unsubmitted transactions");
  157. var oldestTrx = unsubmittedTransaction.OrderBy(t => t.Id).FirstOrDefault();
  158. if (oldestTrx != null)
  159. {
  160. var fdcTransaction = fdcDbContext.PumpTransactionModels.FirstOrDefault(m => m.ReleaseToken == oldestTrx.ReleaseToken);
  161. if (fdcTransaction != null)
  162. {
  163. logger.Info($"Fdc Transaction exists, ask scanner to find it."
  164. + $" pumpId: {oldestTrx.PumpId},"
  165. + $" nozzleId:{oldestTrx.NozzleId},"
  166. + $" FPos SqNo:{oldestTrx.FPosSqNo},"
  167. + $" FC SqNo:{oldestTrx.FcSqNo}");
  168. trxScanner.StartScanAsync(oldestTrx.PumpId, oldestTrx.FPosSqNo, oldestTrx.FcSqNo, oldestTrx.ReleaseToken,
  169. new FdcTransaction
  170. {
  171. Finished = true,
  172. Nozzle = new LogicalNozzle(oldestTrx.PumpId, 0, Convert.ToByte(oldestTrx.NozzleId), null),
  173. Barcode = Convert.ToInt32(fdcTransaction.ProductBarcode),
  174. Volumn = fdcTransaction.Volumn,
  175. Price = fdcTransaction.UnitPrice,
  176. Amount = fdcTransaction.Amount,
  177. VolumeTotalizer = fdcTransaction.VolumeTotalizer,
  178. AmountTotalizer = fdcTransaction.AmountTotalizer,
  179. SequenceNumberGeneratedOnPhysicalPump = Convert.ToInt32(fdcTransaction.TransactionSeqNumberFromPhysicalPump),
  180. });
  181. }
  182. }
  183. }
  184. else
  185. {
  186. logger.Info("Have not found any transaction that are NOT reserved and unsbumitted");
  187. }
  188. }
  189. #endregion
  190. #region Init
  191. //IApplication method implementation
  192. public void Init(IEnumerable<IProcessor> processors)
  193. {
  194. InfoLog("HengshanPosApp Init begins...");
  195. var pumpControllers = new List<IFdcPumpController>();
  196. var communicableControllers = new List<IFdcCommunicableController>();
  197. var hengshanTerminals = new List<IDeviceHandler<byte[], NonCardDispenserMessageTemplateBase>>();
  198. foreach (dynamic processor in processors)
  199. {
  200. if (processor is IAppProcessor)
  201. {
  202. FdcServerHostApp fdcServer = processor as FdcServerHostApp;
  203. if (fdcServer != null)
  204. {
  205. logger.Info("FdcServerHostApp retrieved as an IApplication instance!");
  206. fdcServerApp = processor;
  207. }
  208. PrinterHandler printer = processor as PrinterHandler;
  209. if (printer != null)
  210. {
  211. InfoLog("Printer handler is available");
  212. printerHandler = printer;
  213. }
  214. continue;
  215. }
  216. var handler = processor.Context.Handler;
  217. if (handler is IFdcPumpController)
  218. pumpControllers.Add(handler);
  219. else if (handler is IEnumerable<IFdcPumpController>)
  220. pumpControllers.AddRange(handler);
  221. else if (handler is IDeviceHandler<byte[], NonCardDispenserMessageTemplateBase>)
  222. hengshanTerminals.Add(handler);
  223. }
  224. fdcPumpControllers = pumpControllers; //all pumps
  225. hengshanTerminalHandlers = hengshanTerminals; //all Hengshan IC Terminals
  226. InfoLog("HengshanPosApp init ends...");
  227. }
  228. #endregion
  229. #region IProcessor implementation
  230. public Task<bool> Start()
  231. {
  232. InfoLog("HengshanPosApp Start begins...");
  233. SetupSiteNozzles(fdcPumpControllers);
  234. SetupFuelingManagers(fdcPumpControllers);
  235. SetupTransactionSubmitters(fdcPumpControllers);
  236. SetupMRE();
  237. foreach (var termHanlder in hengshanTerminalHandlers)
  238. {
  239. var icTermHandler = termHanlder as HengshanICTermHandler;
  240. if (icTermHandler != null)
  241. {
  242. icTermHandler.OnTerminalMessageReceived += IcTermHandler_OnTerminalMessageReceived;
  243. }
  244. }
  245. foreach (var fdcPumpController in fdcPumpControllers)
  246. {
  247. fdcPumpController.OnStateChange += FdcPumpController_OnStateChange;
  248. }
  249. if (fdcServerApp != null)
  250. {
  251. FdcServerHostApp fdcServer = fdcServerApp as FdcServerHostApp;
  252. fdcServer.OnCurrentFuellingStatusChange += FdcServer_OnCurrentFuellingStatusChange;
  253. }
  254. if (trxScanner != null)
  255. {
  256. trxScanner.OnMatchingTrxFound += TrxScanner_OnMatchingTrxFound;
  257. trxScanner.OnNoMatchFound += TrxScanner_OnNoMatchFound;
  258. }
  259. foreach (var transactionSubmitter in trxSubmitters)
  260. {
  261. transactionSubmitter.OnReceiptReceived += TransactionSubmitter_OnReceiptReceived;
  262. }
  263. InfoLog("HengshanPosApp Start ends...");
  264. return Task.FromResult(true);
  265. }
  266. private void TransactionSubmitter_OnReceiptReceived(object sender, ReceiptReceivedEventArgs e)
  267. {
  268. InfoLog("TrxSubmitter begins sending receipt data to printer...");
  269. if (printerHandler != null)
  270. printerHandler.SendFormattedReceipt(e.NozzleNo, e.ReceiptData);
  271. }
  272. public Task<bool> Stop()
  273. {
  274. foreach (var termHanlder in hengshanTerminalHandlers)
  275. {
  276. var icTermHandler = termHanlder as HengshanICTermHandler;
  277. if (icTermHandler != null)
  278. {
  279. icTermHandler.OnTerminalMessageReceived -= IcTermHandler_OnTerminalMessageReceived;
  280. }
  281. }
  282. foreach (var fdcPumpController in fdcPumpControllers)
  283. {
  284. fdcPumpController.OnStateChange -= FdcPumpController_OnStateChange;
  285. }
  286. fdcServerApp.OnCurrentFuellingStatusChange -= FdcServer_OnCurrentFuellingStatusChange;
  287. if (trxScanner != null)
  288. {
  289. trxScanner.OnMatchingTrxFound -= TrxScanner_OnMatchingTrxFound;
  290. trxScanner.OnNoMatchFound -= TrxScanner_OnNoMatchFound;
  291. }
  292. if (mreDict!= null && mreDict.Count > 0)
  293. {
  294. foreach (var mre in mreDict)
  295. {
  296. mre.Value?.Dispose();
  297. }
  298. }
  299. return Task.FromResult(true);
  300. }
  301. #endregion
  302. #region Event handlers
  303. private void FdcServer_OnCurrentFuellingStatusChange(object sender, FdcServerTransactionDoneEventArg e)
  304. {
  305. var fuelingManager = GetFuelingManager(e.Transaction.Nozzle.PumpId);
  306. if (fuelingManager != null)
  307. {
  308. //Update the fueling
  309. fuelingManager.UpdateFueling(e.Transaction);
  310. fuelingManager.UpdateFuelingState(FuelingState.Running);
  311. if (e.Transaction.Finished)
  312. {
  313. HandleFinishedFueling(fuelingManager, e);
  314. }
  315. }
  316. else
  317. {
  318. InfoLog(e.Transaction.Nozzle.PumpId, "could not get the fueling manager on fuelling state change");
  319. }
  320. }
  321. private void FdcPumpController_OnStateChange(object sender, FdcPumpControllerOnStateChangeEventArg e)
  322. {
  323. IFdcPumpController currentPump = sender as IFdcPumpController;
  324. if (currentPump != null)
  325. {
  326. var pumpState = e.NewPumpState;
  327. var fuelingManager = GetFuelingManager(currentPump.PumpId);
  328. if (fuelingManager != null)
  329. {
  330. if (pumpState == LogicalDeviceState.FDC_CALLING)
  331. {
  332. var callingNozzle = e.StateChangedNozzles.First().LogicalId;
  333. InfoLog(currentPump.PumpId, $"calling nozzle id: {callingNozzle}");
  334. if (fuelingManager.ActiveFueling != null && fuelingManager.ActiveFueling.ActiveNozzle != callingNozzle)
  335. {
  336. //calling nozzle changed, unauthorize the pump.
  337. var result = fuelingManager.UnauthorizePump();
  338. fuelingManager.ActiveFueling = null;
  339. }
  340. else
  341. {
  342. //Prepare the Active fueling.
  343. fuelingManager.CreateFueling(callingNozzle);
  344. }
  345. }
  346. }
  347. else
  348. {
  349. InfoLog($"Fueling manager is null on PumpState change for pump id: {currentPump.PumpId}");
  350. }
  351. }
  352. else
  353. {
  354. InfoLog($"Sender is not an IFdcPumpController, PumpStateChange, New State: {e.NewPumpState}");
  355. return;
  356. }
  357. }
  358. private void LockFuelSale(int clientId, int pumpId, int fdcSeqNo, int releaseToken)
  359. {
  360. mreDict[pumpId].Reset();
  361. InfoLog("Synchronizing locking");
  362. fdcServerApp.LockFuelSaleTrxAndNotifyAllFdcClientsAsync(clientId, pumpId, fdcSeqNo, releaseToken).GetAwaiter().GetResult();
  363. mreDict[pumpId].Set();
  364. InfoLog("Locking done, continue");
  365. }
  366. private void HandleFinishedFueling(FuelingManager fuelingManager, FdcServerTransactionDoneEventArg e)
  367. {
  368. //Lock the card transaction first
  369. if (fuelingManager.ActiveFueling != null && fuelingManager.ActiveFueling.AuthorizedByCard)
  370. {
  371. InfoLog($"Locking fuel sale transaction, "
  372. + $"Pump id: {e.Transaction.Nozzle.PumpId}"
  373. + $" FdcTrx SqNo: {e.Transaction.SequenceNumberGeneratedOnPhysicalPump},"
  374. + $" Trx amount: {e.Transaction.Amount}");
  375. ThreadPool.QueueUserWorkItem(
  376. new WaitCallback(delegate (object state)
  377. {
  378. LockFuelSale(100,
  379. e.Transaction.Nozzle.PumpId,
  380. e.Transaction.SequenceNumberGeneratedOnPhysicalPump,
  381. e.ReleaseToken.Value);
  382. }), null);
  383. }
  384. fuelingManager.UpdateFuelingState(FuelingState.Completed);
  385. InfoLog($"Fueling done on"
  386. + $" pump id: {e.Transaction.Nozzle.PumpId},"
  387. + $" nozzle id: {e.Transaction.Nozzle.LogicalId},"
  388. + $" PumpSqNo: {e.Transaction.SequenceNumberGeneratedOnPhysicalPump}");
  389. fPosDbManager.AddMapping(fuelingManager.ActiveFueling.FuelingSqNo,
  390. e.Transaction.SequenceNumberGeneratedOnPhysicalPump,
  391. e.Transaction.Nozzle.PumpId, e.Transaction.Nozzle.LogicalId,
  392. e.ReleaseToken.Value, (decimal)e.Transaction.Amount / 100);
  393. InfoLog(e.Transaction.Nozzle.PumpId, "start to search the matching IC card trx...");
  394. trxScanner.StartScanAsync(e.Transaction.Nozzle.PumpId,
  395. fuelingManager.ActiveFueling.FuelingSqNo,
  396. e.Transaction.SequenceNumberGeneratedOnPhysicalPump,
  397. e.ReleaseToken.Value,
  398. e.Transaction);
  399. //Active fueling is completed, then store it.
  400. fuelingManager.SetFuelingCompleted(e.Transaction);
  401. fuelingManager.StoreLastFueling(e.Transaction);
  402. }
  403. private void TrxScanner_OnMatchingTrxFound(object sender, MatchingTrxFoundEventArgs e)
  404. {
  405. //No need to lock non-customer card transactions.
  406. if (e.TrdInfo.CardType != (byte)ICCardType.Customer)
  407. {
  408. ThreadPool.QueueUserWorkItem(new WaitCallback(delegate (object state)
  409. {
  410. UnlockFuelSaleTransaction(100, e.FdcTrx.Nozzle.PumpId, (ushort)e.TrdInfo.SeqNo, e.FdcSqNo, e.ReleaseToken);
  411. }), null);
  412. //var fuelSaleTrx = fdcServerApp.UnlockFuelSaleTrxAndNotifyAllFdcClients(
  413. // 100, e.FdcTrx.Nozzle.PumpId, e.FdcSqNo, e.ReleaseToken);
  414. //InfoLog($"Not a customer transaction, SqNo: {e.TrdInfo.SeqNo}, "
  415. // + $"unlock it for further processing by POS, Success? {fuelSaleTrx != null}");
  416. return;
  417. }
  418. var submitter = GetTrxSubmitter(e.FdcTrx.Nozzle.PumpId);
  419. bool submitSuccess = submitter.SubmitTrx(e.TrdInfo, e.FdcSqNo, e.FdcTrx);
  420. DebugLog(e.FdcTrx.Nozzle.PumpId, $" Submitting FPOS transaction of SeqNo: {e.FdcSqNo} success: {submitSuccess}");
  421. //Clear the fuel sale transaction regardless of submission status
  422. ClearFuelSaleTransaction(e);
  423. }
  424. private void UnlockFuelSaleTransaction(int clientId, int pumpId, ushort fPosSqNo, int fdcTrxNo, int releaseToken)
  425. {
  426. InfoLog("Wait until locking is done");
  427. mreDict[pumpId].WaitOne();
  428. var fuelSaleTrx = fdcServerApp.UnlockFuelSaleTrxAndNotifyAllFdcClientsAsync(clientId, pumpId, fdcTrxNo, releaseToken).Result;
  429. InfoLog($"Not a customer transaction, SqNo: {fPosSqNo}, "
  430. + $"unlock it for further processing by POS, Success? {fuelSaleTrx != null}");
  431. }
  432. private async void TrxScanner_OnNoMatchFound(object sender, NoMatchFoundEventArgs e)
  433. {
  434. alarmLogger.Info($"\n No matching IC card record found\n Fdc SqNo: {e.FdcSqNo},"
  435. + $" on pump: {e.FdcTrx.Nozzle.PumpId}, nozzle: {e.FdcTrx.Nozzle.LogicalId}, amount: {e.FdcTrx.Amount} unlocking fuel sale transaction");
  436. await fdcServerApp.UnlockFuelSaleTrxAndNotifyAllFdcClientsAsync(100, e.FdcTrx.Nozzle.PumpId, e.FdcSqNo, e.ReleaseToken);
  437. }
  438. private void ClearFuelSaleTransaction(MatchingTrxFoundEventArgs e)
  439. {
  440. FdcServerHostApp fdcServer = fdcServerApp as FdcServerHostApp;
  441. if (fdcServer != null)
  442. {
  443. var fuelSaleTrx = fdcServer.ClearFuelSaleTrxAndNotifyAllFdcClientsAsync(
  444. e.TrdInfo.PumpNo, Convert.ToString(e.FdcSqNo), e.ReleaseToken, "100").Result;
  445. InfoLog($"Fuel sale transaction state: {fuelSaleTrx.State.ToString()}");
  446. }
  447. }
  448. private void IcTermHandler_OnTerminalMessageReceived(object sender, HengshanTerminalMessageEventArgs e)
  449. {
  450. #region GetNozzleStatus
  451. if (e.Message is GetNozzleStatusRequest)
  452. {
  453. HandleGetNozzleStatusRequest(e);
  454. return;
  455. }
  456. #endregion
  457. //fuelingManager and terminal for all request/response handling
  458. var fuelingManager = GetFuelingManager(e.PumpId);
  459. var terminal = GetTerminalById(e.PumpId);
  460. #region Reserve pump
  461. if (e.Message is ReservePumpWithAmountRequest)
  462. {
  463. InfoLog(e.PumpId, "ReservePumpWithAmountRequest" + $" amount: {(e.Message as ReservePumpWithAmountRequest).Amount}");
  464. if (fuelingManager != null)
  465. {
  466. var reservePumpWithAmountResponse = fuelingManager.ReservePumpWithAmount((e.Message as ReservePumpWithAmountRequest).Amount);
  467. terminal?.Write(reservePumpWithAmountResponse);
  468. }
  469. else
  470. {
  471. ErrorLog(e.PumpId, "ReservePumpWithAmountRequest, no pump found, respond with failure!");
  472. ReservePumpWithAmountResponse failedResponse = new ReservePumpWithAmountResponse
  473. {
  474. EnumResult = NonCardDispenserMessageTemplateBase.Result.失败
  475. };
  476. terminal?.Write(failedResponse);
  477. return;
  478. }
  479. }
  480. #endregion
  481. else if (e.Message is ReservePumpWithGallonRequest)
  482. {
  483. if (fuelingManager != null)
  484. {
  485. var reservePumpWithGallonResponse = fuelingManager.ReservePumpWithGallon((e.Message as ReservePumpWithGallonRequest).Gallon);
  486. terminal?.Write(reservePumpWithGallonResponse);
  487. }
  488. else
  489. {
  490. logger.Info($"ReservePumpWithGallonRequest: No pump found for this id - {e.PumpId}, respond with failure!");
  491. ReservePumpWithGallonResponse failedResponse = new ReservePumpWithGallonResponse
  492. {
  493. EnumResult = NonCardDispenserMessageTemplateBase.Result.失败
  494. };
  495. terminal?.Write(failedResponse);
  496. return;
  497. }
  498. }
  499. else if (e.Message is RoundUpByVolumeRequest)
  500. {
  501. if (fuelingManager != null)
  502. {
  503. var roundUpByVolumeResponse = fuelingManager.HandleVolumeRounding();
  504. terminal?.Write(roundUpByVolumeResponse);
  505. }
  506. else
  507. {
  508. logger.Error($"RoundUpByVolumeRequest: No pump found for pump {e.PumpId}, respond with failure!");
  509. RoundUpByVolumeResponse failureResponse = new RoundUpByVolumeResponse
  510. {
  511. EnumResult = NonCardDispenserMessageTemplateBase.Result.失败
  512. };
  513. terminal?.Write(failureResponse);
  514. return;
  515. }
  516. }
  517. #region Pump authorization
  518. //
  519. // Authorize pump with preset amount
  520. //
  521. else if (e.Message is AuthPumpWithAmountRequest)
  522. {
  523. #region Old way auth pump with amount request
  524. var fdcPump = GetPumpById(e.PumpId);
  525. if (fdcPump == null)
  526. {
  527. logger.Info($"AuthPumpWithAmountRequest: No pump found for this id - {e.PumpId}, respond with failure!");
  528. SendPumpAuthResponse(e.PumpId, false, PumpAuthType.Amount);
  529. return;
  530. }
  531. bool success = fdcPump.AuthorizeWithAmountAsync((e.Message as AuthPumpWithAmountRequest).Amount, (byte)e.PumpId).Result;
  532. SendPumpAuthResponse(e.PumpId, success, PumpAuthType.Amount);
  533. #endregion
  534. }
  535. //
  536. // Authorize pump with preset volume
  537. //
  538. else if (e.Message is AuthPumpWithGallonRequest)
  539. {
  540. #region old way auth pump with gallon request
  541. var fdcPump = GetPumpById(e.PumpId);
  542. if (fdcPump == null)
  543. {
  544. logger.Info($"AuthPumpWithGallonRequest: No pump found for this id - {e.PumpId}, respond with failure!");
  545. SendPumpAuthResponse(e.PumpId, false, PumpAuthType.Volume);
  546. return;
  547. }
  548. bool success = fdcPump.AuthorizeWithAmountAsync((e.Message as AuthPumpWithGallonRequest).Gallon, (byte)e.PumpId).Result;
  549. SendPumpAuthResponse(e.PumpId, success, PumpAuthType.Volume);
  550. #endregion
  551. }
  552. else if (e.Message is AuthPumpWithKiloRequest)
  553. {
  554. //To be implemented when it's required.
  555. }
  556. //
  557. // Hengshan terminal command, Command 'Open' equals 'authorize pump'
  558. //
  559. else if (e.Message is OpenRequest)
  560. {
  561. InfoLog($"OpenRequest for pump {e.PumpId}");
  562. OpenResponse openResponse = new OpenResponse
  563. {
  564. EnumResult = NonCardDispenserMessageTemplateBase.Result.成功
  565. };
  566. terminal?.Write(openResponse);
  567. if (fuelingManager != null)
  568. {
  569. fuelingManager.ActiveFueling.FuelingState = FuelingState.Authorized;
  570. fuelingManager.ActiveFueling.AuthorizedByCard = true;
  571. bool authResult = fuelingManager.AuthorizePump();
  572. logger.Info($"Authorizing pump {e.PumpId}, success? {authResult}");
  573. }
  574. else
  575. {
  576. logger.Info($"No fueling manager for this OpenRequest with pump id:{e.PumpId}");
  577. }
  578. }
  579. #endregion
  580. #region Pump un-authorization
  581. else if (e.Message is UnAuthorizePumpRequest)
  582. {
  583. #region old way un-authorize pump request
  584. var fdcPump = GetPumpById(e.PumpId);
  585. if (fdcPump == null)
  586. {
  587. logger.Info($"UnAuthorizePumpRequest: No pump found for this id - {e.PumpId}, respond with failure!");
  588. SendPumpUnauthResponse(e.PumpId, false, PumpUnauthType.Unauthorize);
  589. return;
  590. }
  591. #endregion
  592. }
  593. else if (e.Message is CloseRequest)
  594. {
  595. logger.Debug($"CloseRequest for Pump {e.PumpId}");
  596. if (fuelingManager != null)
  597. {
  598. bool unauthResult = fuelingManager.UnauthorizePump();
  599. logger.Debug($"Close resposne, success? {unauthResult}");
  600. SendPumpUnauthResponse(e.PumpId, unauthResult, PumpUnauthType.General);
  601. }
  602. else
  603. {
  604. logger.Info($"CloseRequest: No pump found for this id - {e.PumpId}, respond with failure!");
  605. SendPumpUnauthResponse(e.PumpId, false, PumpUnauthType.General);
  606. return;
  607. }
  608. }
  609. #endregion
  610. #region Pump Accumulator
  611. else if (e.Message is GetAccumulateRequest)
  612. {
  613. var terminalHandler = GetTerminalById(e.PumpId);
  614. if (fuelingManager != null)
  615. {
  616. InfoLog(e.PumpId, $"GetAccumulateRequest");
  617. var response = fuelingManager.GetAccumulator(1);
  618. terminalHandler?.Write(response);
  619. }
  620. else
  621. {
  622. GetAccumulateResponse getAccumulateResponse = new GetAccumulateResponse
  623. {
  624. 升累计 = 0,
  625. 金额累计 = 0
  626. };
  627. terminalHandler?.Write(getAccumulateResponse);
  628. }
  629. }
  630. #endregion
  631. #region Rounding request
  632. else if (e.Message is RoundingRequest)
  633. {
  634. DebugLog(e.PumpId, "RoundingRequest");
  635. if (fuelingManager != null)
  636. {
  637. SendRoundingResponse(e.PumpId, fuelingManager.GetRoundingResult());
  638. }
  639. else
  640. {
  641. var roundingResponse = new RoundingResponse
  642. {
  643. EnumResult = NonCardDispenserMessageTemplateBase.Result.失败
  644. };
  645. SendRoundingResponse(e.PumpId, roundingResponse);
  646. }
  647. }
  648. #endregion
  649. #region Cancel full monitor
  650. else if (e.Message is CancelFullMonitorRequest)
  651. {
  652. InfoLog(e.PumpId, "CancelFuellMonitorRequest");
  653. var response = new CancelFullMonitorResponse
  654. {
  655. EnumResult = NonCardDispenserMessageTemplateBase.Result.成功
  656. };
  657. terminal?.Write(response);
  658. InfoLog(e.PumpId, "CancelFullMonitorResponse");
  659. }
  660. #endregion
  661. }
  662. #endregion
  663. #region Send rounding response
  664. /// <summary>
  665. /// Sends the rounding (凑整) response to IC card reader.
  666. /// </summary>
  667. /// <param name="pumpId">Current pump id.</param>
  668. /// <param name="response">The rounding response.</param>
  669. private void SendRoundingResponse(int pumpId, RoundingResponse response)
  670. {
  671. var terminal = GetTerminalById(pumpId);
  672. terminal?.Write(response);
  673. }
  674. #endregion
  675. #region Authorization
  676. /// <summary>
  677. /// Sends pump authorization response to Hengshan EPS.
  678. /// </summary>
  679. /// <param name="pumpId">Current pump id.</param>
  680. /// <param name="success">Authorization result, success or failure.</param>
  681. /// <param name="authType">Pump authorization type.</param>
  682. private void SendPumpAuthResponse(int pumpId, bool success, PumpAuthType authType)
  683. {
  684. var terminal = GetTerminalById(pumpId);
  685. NonCardDispenserMessageTemplateBase authResponse = null;
  686. if (authType == PumpAuthType.Amount) //Authorize pump with preset amount
  687. {
  688. authResponse = new AuthPumpWithAmountResponse
  689. {
  690. EnumResult = success ?
  691. NonCardDispenserMessageTemplateBase.Result.成功 : NonCardDispenserMessageTemplateBase.Result.失败
  692. };
  693. }
  694. else if (authType == PumpAuthType.Volume) //Authorize pump with preset volume
  695. {
  696. authResponse = new AuthPumpWithGallonResponse
  697. {
  698. EnumResult = success ?
  699. NonCardDispenserMessageTemplateBase.Result.成功 : NonCardDispenserMessageTemplateBase.Result.失败
  700. };
  701. }
  702. else if (authType == PumpAuthType.General) //Open request
  703. {
  704. authResponse = new OpenResponse
  705. {
  706. EnumResult = success ?
  707. NonCardDispenserMessageTemplateBase.Result.成功 : NonCardDispenserMessageTemplateBase.Result.失败
  708. };
  709. }
  710. terminal?.Write(authResponse);
  711. }
  712. /// <summary>
  713. /// Send pump un-authoirzation response to Hengshan EPS.
  714. /// </summary>
  715. /// <param name="pumpId">Current pump id.</param>
  716. /// <param name="success">Un-authorization result, success or failure.</param>
  717. /// <param name="unauthType">Un-authorization type.</param>
  718. private void SendPumpUnauthResponse(int pumpId, bool success, PumpUnauthType unauthType)
  719. {
  720. var terminal = GetTerminalById(pumpId);
  721. NonCardDispenserMessageTemplateBase unauthResponse = null;
  722. if (unauthType == PumpUnauthType.Unauthorize) //Unauthorize pump, regardless of amount or volume preset
  723. {
  724. unauthResponse = new UnAuthorizePumpResponse
  725. {
  726. EnumResult = success ?
  727. NonCardDispenserMessageTemplateBase.Result.成功 : NonCardDispenserMessageTemplateBase.Result.失败
  728. };
  729. }
  730. else if (unauthType == PumpUnauthType.General) //Close request
  731. {
  732. unauthResponse = new CloseResponse
  733. {
  734. EnumResult = success ?
  735. NonCardDispenserMessageTemplateBase.Result.成功 : NonCardDispenserMessageTemplateBase.Result.失败
  736. };
  737. }
  738. terminal?.Write(unauthResponse);
  739. }
  740. #endregion
  741. #region Handle NozzleStatusRequest
  742. /// <summary>
  743. /// Handles the GetNozzleStatusRequest sent from Hengshan IC Terminal.
  744. /// </summary>
  745. /// <param name="e">The request message event.</param>
  746. private void HandleGetNozzleStatusRequest(HengshanTerminalMessageEventArgs e)
  747. {
  748. if (logger.IsDebugEnabled)
  749. logger.Debug("\n");
  750. DebugLog(e.PumpId, $"GetNozzleStatusRequest for pump id: {e.PumpId}");
  751. DebugLog(e.PumpId, "GNS Req");
  752. var fuelingManager = GetFuelingManager(e.PumpId);
  753. if (fuelingManager != null)
  754. {
  755. DebugLog(e.PumpId, $"Start to handle nozzle status request");
  756. var currentNozzleStatus = fuelingManager.GetNozzleStatus();
  757. SendNozzleStatusResponse(e.PumpId, currentNozzleStatus);
  758. var currentFdcPump = fdcPumpControllers.FirstOrDefault(p => p.PumpId == e.PumpId);
  759. if (currentFdcPump != null && currentFdcPump.QueryStatusAsync().Result == LogicalDeviceState.FDC_CALLING)
  760. {
  761. InfoLog(e.PumpId, $"{currentNozzleStatus.ToLogString()}");
  762. }
  763. DebugLog(e.PumpId, $"{currentNozzleStatus.ToLogString()}");
  764. }
  765. else
  766. {
  767. InfoLog($"Could not get the FuelingManager for pump id = {e.PumpId}");
  768. SendNozzleStatusResponse(e.PumpId, GetNozzleStatusResponse.PumpStatus.不允许加油);
  769. return;
  770. }
  771. }
  772. #endregion
  773. #region Send NozzleStatusResponse
  774. private void SendNozzleStatusResponse(int pumpId, GetNozzleStatusResponse response)
  775. {
  776. var terminal = GetTerminalById(pumpId);
  777. if (terminal == null)
  778. {
  779. throw new NullReferenceException("Hengshan Terminal could not be found!");
  780. }
  781. terminal.Write(response);
  782. }
  783. private void SendNozzleStatusResponse(int pumpId, GetNozzleStatusResponse.PumpStatus pumpStatus)
  784. {
  785. var getNozzleStatusResponse = new GetNozzleStatusResponse();
  786. getNozzleStatusResponse.AddPumpStatus(pumpStatus);
  787. SendNozzleStatusResponse(pumpId, getNozzleStatusResponse);
  788. }
  789. #endregion
  790. #region Get Pump
  791. /// <summary>
  792. /// Gets the FDC pump controller.
  793. /// </summary>
  794. /// <param name="pumpId">The pump id.</param>
  795. /// <returns></returns>
  796. private IFdcPumpController GetPumpById(int pumpId)
  797. {
  798. return fdcPumpControllers?.FirstOrDefault(f => f.PumpId == pumpId);
  799. }
  800. #endregion
  801. #region Get Terminal
  802. /// <summary>
  803. /// Gets the Hengshan IC terminal handler
  804. /// </summary>
  805. /// <param name="pumpId">The pump id.</param>
  806. /// <returns></returns>
  807. private HengshanICTermHandler GetTerminalById(int pumpId)
  808. {
  809. foreach (var termHandler in hengshanTerminalHandlers)
  810. {
  811. var terminal = termHandler as HengshanICTermHandler;
  812. if (terminal != null && terminal.AssociatedPumpId.Equals(pumpId))
  813. return terminal;
  814. }
  815. return null;
  816. }
  817. #endregion
  818. #region Site nozzles setup
  819. /// <summary>
  820. /// Set up the site nozzles, in particular the nozzle number
  821. /// Strategy:
  822. /// 1. Sort the Pumps by id
  823. /// 2. Sort the nozzles by logical id
  824. /// 3. The site nozzle number then will be (Pump id, Logical nozzle id) => site nozzle number
  825. /// e.g. (1, 1) => 1, (1, 2) => 2, (2, 1) => 3, (3, 1) => 4, (3, 2) => 5, (4, 1) => 6 ...
  826. /// </summary>
  827. /// <param name="fdcPumpControllers">The Fdc pump instance collection.</param>
  828. private void SetupSiteNozzles(IEnumerable<IFdcPumpController> fdcPumpControllers)
  829. {
  830. nozzles.Clear();
  831. int nozzleNo = 1; //Site nozzle number starts with 1
  832. var pumps = fdcPumpControllers.OrderBy(c => c.PumpId);
  833. foreach (var pump in pumps)
  834. {
  835. var pumpNozzles = pump.Nozzles.OrderBy(n => n.LogicalId);
  836. foreach (var nozzle in pumpNozzles)
  837. {
  838. InfoLog($"({pump.PumpId}, {nozzle.LogicalId}) => {nozzleNo}");
  839. nozzles.Add((pump.PumpId, nozzle.LogicalId), nozzleNo);
  840. nozzleNo++;
  841. }
  842. }
  843. }
  844. #endregion
  845. #region Fueling manager setup
  846. private void SetupFuelingManagers(IEnumerable<IFdcPumpController> fdcPumpControllers)
  847. {
  848. fuelingManagers.Clear();
  849. foreach (var fdcPump in fdcPumpControllers)
  850. {
  851. fuelingManagers.Add(fdcPump.PumpId,
  852. new FuelingManager(fdcPump.PumpId, fdcPump, fdcServerApp, fdcDbContext, fPosDbManager, CampaignEngine));
  853. InfoLog($"Fueling manager for pump id: {fdcPump.PumpId} added.");
  854. }
  855. }
  856. private FuelingManager GetFuelingManager(int pumpId)
  857. {
  858. DebugLog(pumpId, "get fueling manager");
  859. FuelingManager fuelingManager = null;
  860. fuelingManagers.TryGetValue(pumpId, out fuelingManager);
  861. return fuelingManager;
  862. }
  863. #endregion
  864. #region Set up transaction sumbitters
  865. private void SetupTransactionSubmitters(IEnumerable<IFdcPumpController> fdcPumpControllers)
  866. {
  867. trxSubmitters.Clear();
  868. foreach (var fdcPump in fdcPumpControllers)
  869. {
  870. trxSubmitters.Add(
  871. new TrxSubmitter(fdcPump.PumpId, fPosDbManager, cloudCredential) { SiteNozzles = nozzles });
  872. InfoLog($"Transaction submitter for pump id: {fdcPump.PumpId} added.");
  873. }
  874. }
  875. private TrxSubmitter GetTrxSubmitter(int pumpId)
  876. {
  877. return trxSubmitters.FirstOrDefault(t => t.Id == pumpId);
  878. }
  879. #endregion
  880. #region Setup MRE
  881. private void SetupMRE()
  882. {
  883. foreach (var fdcPump in fdcPumpControllers)
  884. {
  885. mreDict.Add(fdcPump.PumpId, new ManualResetEvent(false));
  886. }
  887. }
  888. #endregion
  889. #region Log methods
  890. private void InfoLog(string log)
  891. {
  892. if (logger.IsInfoEnabled)
  893. logger.Info(log);
  894. }
  895. private void InfoLog(int pumpId, string log)
  896. {
  897. if (logger.IsInfoEnabled)
  898. logger.Info($"Pump {pumpId}, {log}");
  899. }
  900. private void DebugLog(int pumpId, string log)
  901. {
  902. if (logger.IsDebugEnabled)
  903. logger.Debug($"Pump {pumpId}, {log}");
  904. }
  905. private void ErrorLog(int pumpId, string log)
  906. {
  907. if (logger.IsErrorEnabled)
  908. logger.Error($"Pump {pumpId}, {log}");
  909. }
  910. #endregion
  911. }
  912. }