Eps.cs 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261
  1. using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump;
  2. using NozzleLockConfiguration;
  3. using SinochemCarplateService.Models;
  4. using SinochemCloudClient.Models;
  5. using SinochemInternetPlusApp.EpsTrxCleanup;
  6. using SinochemPosClient.Models;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Threading;
  12. using System.Xml.Serialization;
  13. using Wayne.FDCPOSLibrary;
  14. using Wayne.Lib.Log;
  15. using WayneChina_IcCardReader_SinoChem.MessageEntity;
  16. using WayneChina_IcCardReader_SinoChem.MessageEntity.Incoming;
  17. using Global_Pump_Fdc;
  18. using System.Threading.Tasks;
  19. using Wayne.Lib;
  20. namespace SinochemInternetPlusApp
  21. {
  22. public class Eps : IDisposable
  23. {
  24. private List<FuelingPoint> fuelingPoints;
  25. private Dictionary<int, IEnumerable<int>> fuelingPointNozzlesDict = new Dictionary<int, IEnumerable<int>>();
  26. private Dictionary<int, Dictionary<int, string>> openTrxNozzleDict = new Dictionary<int, Dictionary<int, string>>();
  27. private EpsTrxCleanupManager epsTrxCleanupManager;
  28. protected DebugLogger debugLogger;
  29. private int requestId;
  30. private object requestSyncObj = new object();
  31. #region devices and services
  32. private Sinochem_CarPlateRecognizeCamera_HuLianWangJia.Handler carPlateServer;
  33. private IEnumerable<WayneChina_IcCardReader_SinoChem.Handler> cardReaderHandlers;
  34. private PumpGroupHandler globalPumpGroup;
  35. //private FdcCommunicator fccClient;
  36. private CloudManager cloudManager;
  37. private PosManager posManger;
  38. private object openTrxSyncObj = new object();
  39. private Dictionary<int, byte> fpSqNoDict = new Dictionary<int, byte>();
  40. private IEnumerable<IProcessor> processors;
  41. private IEnumerable<IFdcPumpController> pumpControllers;
  42. #endregion
  43. static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("SinochemEpsApp");
  44. #region constants
  45. public int InvalidNozzleId { get { return 9999; } }
  46. #endregion
  47. ForecourtTrxManager forecourtTrxManager;
  48. private Dictionary<int, string> nozzleMappingGradeName { get; set; } = new Dictionary<int, string>();
  49. #region Constructor
  50. public Eps(IEnumerable<int> fpIds, Dictionary<int, IEnumerable<int>> fpNozzlesDict, IEnumerable<IProcessor> processors)
  51. {
  52. var identifiableEntity = new IdentifiableEntity(0, "EpsMain", "", null);
  53. debugLogger = new DebugLogger(identifiableEntity);
  54. fuelingPoints = new List<FuelingPoint>(fpIds.Count());
  55. foreach (int fpId in fpIds)
  56. {
  57. fuelingPoints.Add(new FuelingPoint(fpId, fpNozzlesDict[fpId], this));
  58. }
  59. fuelingPointNozzlesDict = fpNozzlesDict;
  60. epsTrxCleanupManager = new EpsTrxCleanupManager(this);
  61. cloudManager = new CloudManager();
  62. posManger = new PosManager();
  63. NozzleLockAccessor.FillinNozzles(fpNozzlesDict);
  64. this.processors = processors;
  65. var pumpControllerList = new List<IFdcPumpController>();
  66. foreach (dynamic processor in processors)
  67. {
  68. if (processor is IAppProcessor)
  69. continue;
  70. var handler = processor.Context.Handler;
  71. if (handler is IEnumerable<IFdcPumpController>)
  72. {
  73. pumpControllerList.AddRange(handler);
  74. }
  75. }
  76. pumpControllers = pumpControllerList;
  77. debugLogger.Add("Eps has been constructed");
  78. //logger.Info("Eps has been constructed");
  79. logger.Info(string.Format($"{this.processors.Count()} processors were loaded."));
  80. foreach (var proc in this.processors)
  81. {
  82. logger.Info(proc.GetType().ToString());
  83. }
  84. forecourtTrxManager = new ForecourtTrxManager(pumpControllers, GenericSinochemEpsApp.PumpSideMapping,
  85. GenericSinochemEpsApp.ForceMappingFusionHoseToHuiTianHoseStr,GenericSinochemEpsApp.PosDatabaseConnString,
  86. GenericSinochemEpsApp.RawProductNameToPosProductNameStr);
  87. forecourtTrxManager.Init();
  88. nozzleMappingGradeName = forecourtTrxManager.GetNozzleFuelMapping();
  89. }
  90. internal TrxNotificationResponse NotifySuccessfulTrxToPos(EpsTransactionModel model, DebugLogger debugLogger)
  91. {
  92. return posManger.NotifyPosSuccessfulTrx(model, debugLogger);
  93. }
  94. internal PaymentResponse SendPaymentToCloud(EpsTransactionModel currentEpsTrxModel, DebugLogger debugLogger)
  95. {
  96. return cloudManager.Payment(currentEpsTrxModel, debugLogger);
  97. }
  98. internal BalanceInquiryResponse SendBalanceInquiryToCloud(string cardNo, string encryptedPin, string tid, int nozzleId, DebugLogger debugLogger)
  99. {
  100. return cloudManager.BalanceInquiry(cardNo, encryptedPin, tid, nozzleId, debugLogger);
  101. }
  102. #endregion
  103. public void Run()
  104. {
  105. fpSqNoDict.Clear();
  106. SetupICCardReaderHandler();
  107. if (fuelingPoints != null)
  108. {
  109. foreach (var fp in fuelingPoints)
  110. {
  111. if (!fpSqNoDict.ContainsKey(fp.FuelingPointId))
  112. {
  113. fpSqNoDict.Add(fp.FuelingPointId, 0);
  114. }
  115. fp.Start();
  116. }
  117. }
  118. epsTrxCleanupManager.Start();
  119. SetupFccClient();
  120. SetupCarplateServer();
  121. }
  122. private void SetupFccClient()
  123. {
  124. foreach (var pumpController in pumpControllers)
  125. {
  126. pumpController.OnStateChange += (s, a) =>
  127. {
  128. var pump = s as IFdcPumpController;
  129. if (a.NewPumpState == LogicalDeviceState.FDC_CALLING)
  130. {
  131. logger.Info($"Pump {pump.PumpId} at state CALLING");
  132. int sitewiseNozzleId =
  133. SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a.StateChangedNozzles.FirstOrDefault()?.LogicalId ?? 0);
  134. FccClient_NozzleLifted(sitewiseNozzleId, pump);
  135. }
  136. else if (a.NewPumpState == LogicalDeviceState.FDC_READY)
  137. {
  138. int sitewiseNozzleId = 0;
  139. if (a.StateChangedNozzles != null)
  140. {
  141. SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a.StateChangedNozzles.FirstOrDefault()?.LogicalId ?? 0);
  142. }
  143. else
  144. {
  145. logger.Info("StateChangeNozzles null, use sitewiseNozzleId 0");
  146. }
  147. logger.Info($"Pump {pump.PumpId} at state: FDC_READY, siteWiseNozzleId: {sitewiseNozzleId}");
  148. FccClient_NozzleReplaced(sitewiseNozzleId, pump.PumpId);
  149. }
  150. else if (a.NewPumpState == LogicalDeviceState.FDC_AUTHORISED)
  151. {
  152. int sitewiseNozzleId =
  153. SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId,
  154. a.StateChangedNozzles.FirstOrDefault()?.LogicalId ?? 0);
  155. logger.Info($"Pump {pump.PumpId} at state FDC_AUTHORISED, siteWiseNozzleId: {sitewiseNozzleId}");
  156. FccClient_AuthOk(sitewiseNozzleId, 1999);
  157. }
  158. else if (a.NewPumpState == LogicalDeviceState.FDC_FUELLING)
  159. {
  160. logger.Info($"Pump {pump.PumpId} is fueling");
  161. }
  162. };
  163. pumpController.OnCurrentFuellingStatusChange += (s, a) =>
  164. {
  165. var pump = s as IFdcPumpController;
  166. if (a.Transaction.Finished)
  167. {
  168. int sitewiseNozzleId =
  169. SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a.Transaction.Nozzle.LogicalId);
  170. logger.Info($"Pump {pump.PumpId} fueling finished, siteWiseNozzleId: {sitewiseNozzleId}, amount: {a.Transaction.Amount}");
  171. FccClient_NozzleReplaced(sitewiseNozzleId, pump.PumpId);
  172. FccClient_FuelingDone(sitewiseNozzleId, a.Transaction.SequenceNumberGeneratedOnPhysicalPump,
  173. Convert.ToDecimal(a.Transaction.Amount / Math.Pow(10, pump.VolumeDecimalDigits)),
  174. Convert.ToDecimal(a.Transaction.Volumn / Math.Pow(10, pump.VolumeDecimalDigits)),
  175. 1999);
  176. }
  177. };
  178. Console.WriteLine($"setup fcc client pump done for {pumpController.PumpId}");
  179. }
  180. }
  181. private void Pump_OnStateChange(object sender, FdcPumpControllerOnStateChangeEventArg e)
  182. {
  183. throw new NotImplementedException();
  184. }
  185. private void Pump_OnCurrentFuellingStatusChange(object sender, FdcTransactionDoneEventArg e)
  186. {
  187. throw new NotImplementedException();
  188. }
  189. // find car plate handler and listen to the new car plate event
  190. private void SetupCarplateServer()
  191. {
  192. var carPlateProcessors =
  193. processors.OfType<Edge.Core.Processor.GenericDeviceProcessor<System.String, Edge.Core.Parser.HttpMessageParser.BaseHttpMessage<System.String>>>();
  194. carPlateServer = carPlateProcessors.Select(proc => (Sinochem_CarPlateRecognizeCamera_HuLianWangJia.Handler)proc.Context.Handler)
  195. .FirstOrDefault();
  196. if (carPlateServer != null)
  197. {
  198. carPlateServer.NewCarPlateScanned += CarPlateServer_NewCarPlateScanned;
  199. carPlateServer.QRCodePaid += CarPlateServer_QRCodePaid;
  200. carPlateServer.IndoorPaid += CarPlateServer_IndoorPaid;
  201. carPlateServer.OnMessageReceivedViaFdc += (msg) =>
  202. {
  203. logger.Info($"fdc message:\n {msg}");
  204. if (msg.Length > 10 && msg.StartsWith("<Tran"))
  205. {
  206. var serializer = new XmlSerializer(typeof(TransactionOperation));
  207. using (var reader = new StringReader(msg))
  208. {
  209. var trxOpRequest = (TransactionOperation)serializer.Deserialize(reader);
  210. if (trxOpRequest != null)
  211. {
  212. //trxOpRequest.NozzleNo
  213. //FuelingPoint fp = GetFp(trxOpRequest.NozzleNo ?? default(int));
  214. if (!string.IsNullOrEmpty(trxOpRequest.NozzleNo))
  215. {
  216. int nozzleNo = int.Parse(trxOpRequest.NozzleNo);
  217. FuelingPoint fp = GetFp(nozzleNo);
  218. if (fp != null)
  219. fp.SignalTrxOpRequest(trxOpRequest);
  220. }
  221. else
  222. {
  223. foreach (FuelingPoint fp in fuelingPoints)
  224. {
  225. if (fp.SignalTrxOpRequest(trxOpRequest))
  226. break;
  227. }
  228. }
  229. }
  230. }
  231. }
  232. else
  233. {
  234. var serializer = new XmlSerializer(typeof(DisplayResponse));
  235. using (var reader = new StringReader(msg))
  236. {
  237. var displayResp = (DisplayResponse)serializer.Deserialize(reader);
  238. logger.Info($"Display Response, id = {displayResp.RequestId}");
  239. if (displayResp != null && displayResp.OverallResult == ResultType.Success)
  240. {
  241. foreach (var fp in fuelingPoints)
  242. {
  243. if (fp.ContainsRequestId(displayResp.RequestId))
  244. fp.SignalDisplayResponseReceived(displayResp);
  245. }
  246. }
  247. }
  248. }
  249. return new Tuple<string, OverallResult>("DisplayResponse ACK", OverallResult.Success);
  250. };
  251. }
  252. else
  253. {
  254. Console.WriteLine("carPlateServer not found!!!");
  255. debugLogger.Add("carPlateServer not found!!!");
  256. }
  257. }
  258. /// <summary>
  259. ///
  260. /// </summary>
  261. /// <param name="request"></param>
  262. private void CarPlateServer_NewCarPlateScanned(CarPlateTrxRequest request)
  263. {
  264. int sitewiseNozzleId = InvalidNozzleId;
  265. try
  266. {
  267. sitewiseNozzleId = int.Parse(request.gun);
  268. }
  269. catch(Exception ex)
  270. {
  271. logger.Error(ex.ToString());
  272. }
  273. if (IsNozzleConfiguredOpen(sitewiseNozzleId))
  274. {
  275. var fp = GetFp(sitewiseNozzleId);
  276. fp?.SignalNewCarplate(request);
  277. }
  278. else
  279. {
  280. debugLogger.Add
  281. (string.Format($"Physical nozzle# {sitewiseNozzleId} is closed, so we ignore the car plate event!!!"));
  282. }
  283. }
  284. private void CarPlateServer_QRCodePaid(QRCodePayResultRequest request)
  285. {
  286. logger.Info("CarPlateServer_QRCodePaid");
  287. ThreadPool.QueueUserWorkItem(o =>
  288. {
  289. if (request != null)
  290. {
  291. logger.Info($"CarPlateServer_QRCodePaid, ttc:{request.ttc}, nozzleId:{request.nozzleId}, status: {request.status}, openId:{request.openId}");
  292. EpsTransactionModel epsTrxModel = EpsTransactionQuery.GetEpsTrxByTTC(request.ttc, request.nozzleId);
  293. if (epsTrxModel != null && epsTrxModel.trx_status != EpsTrxStatus.PaymentOk)
  294. {
  295. EpsTransaction epsTrx = EpsTransaction.RestroeEpsTrxFrom(epsTrxModel);
  296. if (request.status == "1")
  297. {
  298. EpsTransactionQuery.UpdateEpsTrxByttc(request.ttc, request.nozzleId, request.amount, request.openId, "07_pos", EpsTrxStatus.PaymentOk);
  299. //delete xiaofei2
  300. EpsTransactionQuery.DeleteXiaoFei2(epsTrxModel.liushuino, request.nozzleId);
  301. MultiFusionsSupport.DeleteXiaofei2FromTargetFusion(request.nozzleId, epsTrxModel.liushuino, debugLogger);
  302. //notify POS
  303. TrxNotificationResponse response = NotifySuccessfulTrxToPos(EpsTransactionQuery.RefreshEpsTrx(epsTrx.Model.id), debugLogger);
  304. if (response != null && response.IsSuccessful())
  305. {
  306. epsTrx.UpdateNotifyPosFlagToDb(NotifyPosFlag.NotifyOk);
  307. }
  308. }
  309. else
  310. {
  311. epsTrx.UpdateTrxStatusToDb(EpsTrxStatus.PaymentFailed);
  312. }
  313. //broadcast the trx to big screen
  314. BroadCastTrxCompleteToBigScreen(request.nozzleId);
  315. }
  316. }
  317. });
  318. }
  319. private void CarPlateServer_IndoorPaid(IndoorPayResultRequest request)
  320. {
  321. logger.Info("CarPlateServer_IndoorPaid");
  322. ThreadPool.QueueUserWorkItem(o =>
  323. {
  324. if (request != null)
  325. {
  326. logger.Info($"CarPlateServer_IndoorPaid, ttc:{request.liushuino}, nozzleId:{request.nozzleId}");
  327. EpsTransactionModel epsTrxModel = EpsTransactionQuery.GetEpsTrxByLiuShuiNO(request.liushuino, request.nozzleId);
  328. if (epsTrxModel != null)
  329. {
  330. EpsTransactionQuery.UpdateEpsTrxByliushuino(request.liushuino, request.nozzleId, request.amount, EpsTrxStatus.PaymentOk);
  331. BroadCastTrxCompleteToBigScreen(request.nozzleId);
  332. }
  333. }
  334. });
  335. }
  336. private void BroadCastTrxCompleteToBigScreen(int nozzleId)
  337. {
  338. //broadcast to big screen
  339. var fp = GetFp(nozzleId);
  340. var validTrx = EpsTransactionQuery.GetValidCarPlateEpsTrxModels(
  341. fp.GetAllNozzlesOnThisSide(),
  342. ConfigurationValues.AlreadyDoneEpsTrxCountPerDisplay,
  343. fp.DebugLogger);
  344. var availableNozzleInfo = fp.GetAvailableNozzleInfo();
  345. if (availableNozzleInfo != null)
  346. {
  347. foreach (var trx in validTrx)
  348. {
  349. if (trx.trx_status == EpsTrxStatus.BeforeFueling || trx.trx_status == EpsTrxStatus.Fueling)
  350. {
  351. trx.AvailableNozzleGrade = availableNozzleInfo;
  352. }
  353. }
  354. }
  355. var text = fp.Eps.CreateDisplayTrxCommand(validTrx, out requestId, 0);
  356. if (fp.Eps.CarPlateHandler != null)
  357. {
  358. fp.Eps.CarPlateHandler.BroadcastMessageViaFdc(text);
  359. }
  360. }
  361. /// <summary>
  362. /// Get the pairs "nozzleId, gradeName" for the available FP on this side
  363. /// </summary>
  364. public Dictionary<int, string> NozzleMappingGradeName()
  365. {
  366. return nozzleMappingGradeName; //Mapping grade name & nozzleId
  367. }
  368. public PumpState GetFPState(int fpId)
  369. {
  370. foreach (var pump in pumpControllers)
  371. {
  372. if (pump.PumpId == fpId)
  373. {
  374. var state = pump.QueryStatusAsync().Result;
  375. if (state == LogicalDeviceState.FDC_CALLING)
  376. return PumpState.Calling;
  377. else if (state == LogicalDeviceState.FDC_AUTHORISED)
  378. return PumpState.Authorized;
  379. else if (state == LogicalDeviceState.FDC_FUELLING)
  380. return PumpState.Fuelling;
  381. else if (state == LogicalDeviceState.FDC_READY)
  382. return PumpState.Idle;
  383. else if (state == LogicalDeviceState.FDC_CLOSED)
  384. return PumpState.Closed;
  385. }
  386. }
  387. return PumpState.Unknown;
  388. }
  389. private void SetupICCardReaderHandler()
  390. {
  391. var icCardReaderProcessors =
  392. processors.OfType<GenericDeviceProcessor<System.Byte[], WayneChina_IcCardReader_SinoChem.MessageEntity.IcCardReaderMessageBase>>();
  393. cardReaderHandlers = icCardReaderProcessors.Select(proc => (WayneChina_IcCardReader_SinoChem.Handler)proc.Context.Handler);
  394. if (cardReaderHandlers != null && cardReaderHandlers.Count() > 0)
  395. {
  396. Console.WriteLine($"Card reader handlers count: {cardReaderHandlers.Count()} ");
  397. foreach (var handler in cardReaderHandlers)
  398. {
  399. handler.OnCardReaderMessageReceived += Handler_OnCardReaderMessageReceived;
  400. }
  401. }
  402. else
  403. {
  404. Console.WriteLine("card reader handlers not found!!");
  405. logger.Info("card reader handlers not found!!");
  406. }
  407. }
  408. private void Handler_OnCardReaderMessageReceived(object sender, WayneChina_IcCardReader_SinoChem.CardReaderMessageEventArgs e)
  409. {
  410. //debugLogger.AddIfActive($"Receiving Card Reader message {e.CardReaderMessage.GetType().ToString()} for FP id = {e.FuelingPointId}");
  411. var latestSqNo = GetCurrentSqNoForFp(e.FuelingPointId);
  412. //debugLogger.AddIfActive($"latestSqNo {latestSqNo}");
  413. if (latestSqNo.HasValue)
  414. {
  415. var currentSqNo = e.CardReaderMessage.MessageSeqNumber;
  416. if (latestSqNo.Value != currentSqNo || currentSqNo == 0)
  417. {
  418. //Update the current sequence number for the designated fueling point.
  419. fpSqNoDict[e.FuelingPointId] = currentSqNo;
  420. var targetFP = fuelingPoints.First(_ => _.FuelingPointId == e.FuelingPointId);//GetFp(e.NozzleId);
  421. if (e.CardReaderMessage is ACK)
  422. {
  423. targetFP?.SingalAckReceived((ACK)e.CardReaderMessage);
  424. }
  425. else if (e.CardReaderMessage is SignDataResponse)
  426. {
  427. targetFP?.SignalSignedDataArrived((SignDataResponse)e.CardReaderMessage);
  428. }
  429. else if (e.CardReaderMessage is CardReaderStateEvent)
  430. {
  431. targetFP?.SingalCardReaderStateEvent((CardReaderStateEvent)e.CardReaderMessage);
  432. }
  433. else if (e.CardReaderMessage is CardExternalCheckErrorRequest)
  434. {
  435. targetFP?.SignalCardExternalCheckFailure((CardExternalCheckErrorRequest)e.CardReaderMessage);
  436. }
  437. else if (e.CardReaderMessage is CardOnlineVerificationRequest)
  438. {
  439. targetFP?.SignalCardOnlineVerification((CardOnlineVerificationRequest)e.CardReaderMessage);
  440. }
  441. else if (e.CardReaderMessage is HeartBeat)
  442. {
  443. targetFP?.SignalCardReaderHeartbeat((HeartBeat)e.CardReaderMessage);
  444. }
  445. }
  446. //If the sequence number of the received message is the same as the latest stored sequence number,
  447. //it's probably a re-send from the terminal.
  448. else
  449. {
  450. //2019-06-25, Card reader side updated SqNo as error code in Heartbeat.
  451. if (e.CardReaderMessage is HeartBeat)
  452. {
  453. var targetFP = fuelingPoints.First(_ => _.FuelingPointId == e.FuelingPointId);//GetFp(e.NozzleId);
  454. targetFP?.SignalCardReaderHeartbeat((HeartBeat)e.CardReaderMessage);
  455. }
  456. else
  457. {
  458. logger.Info($"A repeated message from terminal: {e}");
  459. var targetFP = fuelingPoints.First(_ => _.FuelingPointId == e.FuelingPointId);//GetFp(e.NozzleId);
  460. targetFP?.SignalRepeatedMessageRecieved(e.CardReaderMessage);
  461. return;
  462. }
  463. }
  464. }
  465. }
  466. private byte? GetCurrentSqNoForFp(int fpId)
  467. {
  468. if (fpSqNoDict.ContainsKey(fpId))
  469. {
  470. return fpSqNoDict[fpId];
  471. }
  472. else
  473. {
  474. return null;
  475. }
  476. }
  477. #region Cloud interactions
  478. internal RefundResponse SendRefundToCloud(EpsTransactionModel trxModel, DebugLogger debugLogger)
  479. {
  480. return cloudManager.Refund(trxModel, debugLogger);
  481. }
  482. internal TrxStatusInquiryResponse SendTrxQueryToCloud(EpsTransactionModel trxModel, DebugLogger debugLogger)
  483. {
  484. return cloudManager.TrxStatusInquiry(trxModel, debugLogger);
  485. }
  486. #endregion
  487. #region FDC client interactions
  488. private void FccClient_NozzleReplaced(int sitewiseNozzleId, int pumpId)
  489. {
  490. if (sitewiseNozzleId != 0)
  491. {
  492. var fp = GetFp(sitewiseNozzleId);
  493. fp?.SignalNozzleReplaced(sitewiseNozzleId, pumpId);
  494. }
  495. else
  496. {
  497. var fp = GetFuelingPoint(pumpId);
  498. fp?.SignalNozzleReplaced(sitewiseNozzleId, pumpId);
  499. }
  500. }
  501. private void FccClient_AuthFailed(int sitewiseNozzleId)
  502. {
  503. var fp = GetFp(sitewiseNozzleId);
  504. fp?.SignalAuthFailed();
  505. }
  506. private void FccClient_AuthOk(int sitewiseNozzleId, long? authId)
  507. {
  508. var fp = GetFp(sitewiseNozzleId);
  509. fp?.SignalAuthOk(authId);
  510. }
  511. private void FccClient_NozzleLifted(int sitewiseNozzleId, IFdcPumpController callingPump)
  512. {
  513. logger.Info($"SiteNozzle number: {sitewiseNozzleId}, from Pump {callingPump.PumpId} lifted");
  514. if (IsNozzleConfiguredOpen(sitewiseNozzleId))
  515. {
  516. var fp = GetFp(sitewiseNozzleId);
  517. fp?.SignalNozzleLifted(callingPump, sitewiseNozzleId);
  518. }
  519. else
  520. {
  521. logger.Info(string.Format($"Physical nozzle# {sitewiseNozzleId} is closed, so we ignore the nozzle lift event!!!"));
  522. }
  523. }
  524. private bool IsNozzleConfiguredOpen(int sitewiseNozzleId)
  525. {
  526. return !NozzleLockAccessor.IsNozzleClosedInTermsOfSale(sitewiseNozzleId);
  527. }
  528. private void FccClient_FuelingDone(int sitewiseNozzleId, int seqNum, decimal fuelAmount, decimal fuelingQty, long authId)
  529. {
  530. var fp = GetFp(sitewiseNozzleId);
  531. fp?.SignalFuelingDone(sitewiseNozzleId, seqNum, fuelAmount, fuelingQty, authId);
  532. }
  533. public void AuthorizePumpAsync(IFdcPumpController callingPump, int sitewiseNozzleId, decimal authAmount)
  534. {
  535. //Have to put pump auth in a different thread due to FC reasons, 2019/06/19
  536. Task.Run(() =>
  537. {
  538. LogicalNozzle logicalNozzle = callingPump.Nozzles.First();
  539. foreach (var nozzle in callingPump.Nozzles)
  540. {
  541. if (SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(callingPump.PumpId, Convert.ToInt32(nozzle.LogicalId)) == sitewiseNozzleId)
  542. {
  543. logicalNozzle = nozzle;
  544. }
  545. }
  546. callingPump.AuthorizeWithAmountAsync((int)((double)authAmount * Math.Pow(10, callingPump.AmountDecimalDigits)), logicalNozzle.LogicalId);
  547. });
  548. }
  549. #endregion
  550. private void TeardownFccClient()
  551. {
  552. //if (fccClient != null)
  553. //{
  554. // fccClient.NozzleLifted -= FccClient_NozzleLifted;
  555. // fccClient.NozzleReplaced -= FccClient_NozzleReplaced;
  556. // fccClient.AuthOk -= FccClient_AuthOk;
  557. // fccClient.AuthFailed -= FccClient_AuthFailed;
  558. // fccClient.FuelingDone -= FccClient_FuelingDone;
  559. //}
  560. }
  561. public void Shutdown()
  562. {
  563. TeardownCarplateServer();
  564. TeardownCardReaderHandler();
  565. if (fuelingPoints != null)
  566. {
  567. foreach (var fp in fuelingPoints)
  568. {
  569. fp.SignalShutdown();
  570. }
  571. }
  572. }
  573. public WayneChina_IcCardReader_SinoChem.Handler GetHandler(int fuelingPointId)
  574. {
  575. //debugLogger.AddIfActive($"Trying to get card reader handler for fueling point: {fuelingPointId}");
  576. foreach (var handler in cardReaderHandlers)
  577. {
  578. if (handler.SupportedNozzles.Contains(fuelingPointId))
  579. return handler;
  580. }
  581. return null;
  582. }
  583. private void TeardownCarplateServer()
  584. {
  585. if (carPlateServer != null)
  586. {
  587. carPlateServer.NewCarPlateScanned -= CarPlateServer_NewCarPlateScanned;
  588. carPlateServer.QRCodePaid -= CarPlateServer_QRCodePaid;
  589. carPlateServer.IndoorPaid -= CarPlateServer_IndoorPaid;
  590. }
  591. }
  592. private void TeardownCardReaderHandler()
  593. {
  594. if (cardReaderHandlers != null)
  595. {
  596. foreach (var handler in cardReaderHandlers)
  597. {
  598. handler.OnCardReaderMessageReceived -= Handler_OnCardReaderMessageReceived;
  599. }
  600. }
  601. }
  602. public Sinochem_CarPlateRecognizeCamera_HuLianWangJia.Handler CarPlateHandler => carPlateServer;
  603. public WayneChina_IcCardReader_SinoChem.Handler GetCardReader(int nozzleId)
  604. {
  605. return cardReaderHandlers.FirstOrDefault(h => h.SupportedNozzles.Contains(nozzleId));
  606. }
  607. public FuelingPoint GetFp(int nozzleId)
  608. {
  609. foreach (var fp in fuelingPoints)
  610. {
  611. if (fp.AssociatedNozzles != null && fp.AssociatedNozzles.Contains(nozzleId))
  612. return fp;
  613. }
  614. return null;
  615. }
  616. public FuelingPoint GetFuelingPoint(int fpId)
  617. {
  618. return fuelingPoints?.FirstOrDefault(_ => fpId == _.FuelingPointId);
  619. }
  620. public int GetNextRequestId()
  621. {
  622. lock (requestSyncObj)
  623. {
  624. if (requestId == int.MaxValue)
  625. {
  626. requestId = 0;
  627. }
  628. return requestId++;
  629. }
  630. }
  631. /// <summary>
  632. ///
  633. /// </summary>
  634. /// <param name="epsTrxList"></param>
  635. /// <param name="requestId"></param>
  636. /// <param name="i"></param>
  637. /// <returns></returns>
  638. public string CreateDisplayTrxCommand(IEnumerable<EpsTransactionModel> epsTrxList, out int requestId, int i)
  639. {
  640. Display display;
  641. string cmdText = "";
  642. XmlSerializer serializer = new XmlSerializer(typeof(Display));
  643. MemoryStream ms;
  644. StreamReader sr;
  645. display = new Display();
  646. display.ScreenType = ScreenType.ShowTrxList;
  647. display.RequestId = GetNextRequestId();
  648. display.TimeoutSpecified = true;
  649. display.Timeout = 300;
  650. requestId = display.RequestId;
  651. display.PumpInfo = new DisplayPumpInfo
  652. {
  653. Id = 1,
  654. NozzleId = 1
  655. };
  656. int count = epsTrxList.Count();
  657. display.TrxList = new DisplayTrx[epsTrxList.Count()];
  658. for (int index = 0; index < count; index++)
  659. {
  660. display.TrxList[index] = ConvertEpsTrxModelToDisplayTrx(epsTrxList.ToArray()[index]);
  661. }
  662. ms = new MemoryStream();
  663. serializer.Serialize(ms, display);
  664. ms.Position = 0;
  665. sr = new StreamReader(ms, true);
  666. cmdText = sr.ReadToEnd();
  667. ms.Close();
  668. sr.Close();
  669. return cmdText;
  670. }
  671. public DisplayTrx ConvertEpsTrxModelToDisplayTrx(EpsTransactionModel epsTrxModel)
  672. {
  673. TrxStatus state = TrxStatus.ReadyForFillingStart;
  674. if (epsTrxModel.trx_status == EpsTrxStatus.BeforeFueling)
  675. state = TrxStatus.ReadyForFillingStart;
  676. else if (epsTrxModel.trx_status == EpsTrxStatus.BeforePayment)
  677. state = TrxStatus.PendingForPayment;
  678. else if (epsTrxModel.trx_status == EpsTrxStatus.Fueling)
  679. state = TrxStatus.FillingOngoing;
  680. else if (epsTrxModel.trx_status == EpsTrxStatus.FuelingDone)
  681. state = TrxStatus.PendingForPayment;
  682. else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentOk)
  683. state = TrxStatus.Success;
  684. else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentFailed)
  685. state = TrxStatus.Failed;
  686. else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentNeedConfirm)
  687. state = TrxStatus.Failed;
  688. else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentOkButNeedRefund)
  689. state = TrxStatus.Failed;
  690. else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentRefunded)
  691. state = TrxStatus.Failed;
  692. var displayTrx = new DisplayTrx
  693. {
  694. Id = epsTrxModel.id.ToString(),
  695. State = state,
  696. TimeStamp = new DisplayTrxTimeStamp
  697. {
  698. StartTime = epsTrxModel.created_time,
  699. FinishTime = string.IsNullOrEmpty(epsTrxModel.xf_time) ?
  700. epsTrxModel.created_time :
  701. Utilities.CombineDateAndTime(epsTrxModel.xf_date, epsTrxModel.xf_time)
  702. },
  703. MemberInfo = new DisplayTrxMemberInfo
  704. {
  705. Id = epsTrxModel.cardNo_masked,
  706. LicensePlateNo = epsTrxModel.car_number
  707. },
  708. FillingInfo = new DisplayTrxFillingInfo
  709. {
  710. PumpId = 1,
  711. NozzleId = epsTrxModel.jihao,
  712. ProductNo = 1,
  713. UnitPrice = (decimal)epsTrxModel.danjia,
  714. ProductType = epsTrxModel.youpin,
  715. ProductDiscription = "汽油",
  716. Amount = (decimal)epsTrxModel.amount,
  717. AmountPaid = (decimal)epsTrxModel.real_pay_amount,
  718. Volume = (decimal)epsTrxModel.qty,
  719. NozzleSelected = epsTrxModel.nozzleSelected,
  720. }
  721. };
  722. if (epsTrxModel.AvailableNozzleGrade != null)
  723. {
  724. displayTrx.NozzleList = new DisplayTrxNozzle[epsTrxModel.AvailableNozzleGrade.Count];
  725. int index = 0;
  726. foreach (var pair in epsTrxModel.AvailableNozzleGrade)
  727. {
  728. displayTrx.NozzleList[index] = new DisplayTrxNozzle
  729. {
  730. NozzleId = pair.Key,
  731. Prompt = pair.Key.ToString() + "号枪" +pair.Value,
  732. };
  733. index++;
  734. }
  735. }
  736. return displayTrx;
  737. }
  738. public string CreateDisplayCommand(ScreenType screenType)
  739. {
  740. Display display;
  741. string cmdText = "";
  742. XmlSerializer serializer = new XmlSerializer(typeof(Display));
  743. MemoryStream ms;
  744. StreamReader sr;
  745. switch (screenType)
  746. {
  747. case ScreenType.Idle:
  748. display = new Display();
  749. display.ScreenType = screenType;
  750. display.CompanyContactInfo = new DisplayCompanyContactInfo
  751. {
  752. Tel = "010-59569575",
  753. Address = "世界500强企业\n中国第四大国家石油公司"
  754. };
  755. display.StationInfo = new DisplayStationInfo
  756. {
  757. StationNo = "000001",
  758. StationName = "沈阳望花中街加油加气站"
  759. };
  760. display.PumpInfo = new DisplayPumpInfo
  761. {
  762. Id = 1,
  763. NozzleId = 1
  764. };
  765. ms = new MemoryStream();
  766. serializer.Serialize(ms, display);
  767. ms.Position = 0;
  768. sr = new StreamReader(ms, true);
  769. cmdText = sr.ReadToEnd();
  770. break;
  771. case ScreenType.Welcome:
  772. display = new Display();
  773. display.ScreenType = screenType;
  774. display.CompanyContactInfo = new DisplayCompanyContactInfo
  775. {
  776. Tel = "010-59569575",
  777. Address = "世界500强企业\n中国第四大国家石油公司"
  778. };
  779. display.MemberInfo = new DisplayMemberInfo
  780. {
  781. LicensePlateNo = "京A88888",
  782. Id = "1234567"
  783. };
  784. display.PumpInfo = new DisplayPumpInfo
  785. {
  786. Id = 1,
  787. NozzleId = 1
  788. };
  789. ms = new MemoryStream();
  790. serializer.Serialize(ms, display);
  791. ms.Position = 0;
  792. sr = new StreamReader(ms, true);
  793. cmdText = sr.ReadToEnd();
  794. break;
  795. case ScreenType.ShowTrxList:
  796. display = new Display();
  797. display.ScreenType = screenType;
  798. display.PumpInfo = new DisplayPumpInfo
  799. {
  800. Id = 1,
  801. NozzleId = 1
  802. };
  803. display.TrxList = new DisplayTrx[]
  804. {
  805. new DisplayTrx
  806. {
  807. Id = "100",
  808. FillingInfo = new DisplayTrxFillingInfo
  809. {
  810. Amount = 200,
  811. NozzleId = 1,
  812. ProductType = "95#",
  813. UnitPrice = 6.78m,
  814. Volume = 35,
  815. ProductDiscription = "汽油"
  816. },
  817. MemberInfo = new DisplayTrxMemberInfo
  818. {
  819. LicensePlateNo = "京A88888",
  820. Id = "1234567"
  821. },
  822. State = TrxStatus.Success,
  823. TimeStamp = new DisplayTrxTimeStamp
  824. {
  825. StartTime = DateTime.Now,
  826. FinishTime =DateTime.Now
  827. }
  828. },
  829. new DisplayTrx
  830. {
  831. Id = "101",
  832. FillingInfo = new DisplayTrxFillingInfo
  833. {
  834. Amount = 211,
  835. NozzleId = 1,
  836. ProductType = "95#",
  837. UnitPrice = 6.78m,
  838. Volume = 35,
  839. ProductDiscription = "汽油"
  840. },
  841. MemberInfo = new DisplayTrxMemberInfo
  842. {
  843. LicensePlateNo = "京A88888",
  844. Id = "1234567"
  845. },
  846. State = TrxStatus.Success,
  847. TimeStamp = new DisplayTrxTimeStamp
  848. {
  849. StartTime = DateTime.Now,
  850. FinishTime =DateTime.Now
  851. }
  852. },
  853. new DisplayTrx
  854. {
  855. Id = "102",
  856. FillingInfo = new DisplayTrxFillingInfo
  857. {
  858. Amount = 222,
  859. NozzleId = 1,
  860. ProductType = "95#",
  861. UnitPrice = 6.78m,
  862. Volume = 35,
  863. ProductDiscription = "汽油"
  864. },
  865. MemberInfo = new DisplayTrxMemberInfo
  866. {
  867. LicensePlateNo = "京A88888",
  868. Id = "1234567"
  869. },
  870. State = TrxStatus.Success,
  871. TimeStamp = new DisplayTrxTimeStamp
  872. {
  873. StartTime = DateTime.Now,
  874. FinishTime =DateTime.Now
  875. }
  876. },
  877. new DisplayTrx
  878. {
  879. Id = "103",
  880. FillingInfo = new DisplayTrxFillingInfo
  881. {
  882. Amount = 233,
  883. NozzleId = 1,
  884. ProductType = "95#",
  885. UnitPrice = 6.78m,
  886. Volume = 35,
  887. ProductDiscription = "汽油"
  888. },
  889. MemberInfo = new DisplayTrxMemberInfo
  890. {
  891. LicensePlateNo = "京A88888",
  892. Id = "1234567"
  893. },
  894. State = TrxStatus.Success,
  895. TimeStamp = new DisplayTrxTimeStamp
  896. {
  897. StartTime = DateTime.Now,
  898. FinishTime =DateTime.Now
  899. }
  900. },
  901. new DisplayTrx
  902. {
  903. Id = "104",
  904. FillingInfo = new DisplayTrxFillingInfo
  905. {
  906. Amount = 244,
  907. NozzleId = 1,
  908. ProductType = "95#",
  909. UnitPrice = 6.78m,
  910. Volume = 35,
  911. ProductDiscription = "汽油"
  912. },
  913. MemberInfo = new DisplayTrxMemberInfo
  914. {
  915. LicensePlateNo = "京A88888",
  916. Id = "1234567"
  917. },
  918. State = TrxStatus.Success,
  919. TimeStamp = new DisplayTrxTimeStamp
  920. {
  921. StartTime = DateTime.Now,
  922. FinishTime =DateTime.Now
  923. }
  924. }
  925. };
  926. ms = new MemoryStream();
  927. serializer.Serialize(ms, display);
  928. ms.Position = 0;
  929. sr = new StreamReader(ms, true);
  930. cmdText = sr.ReadToEnd();
  931. break;
  932. case ScreenType.TrxResult:
  933. display = new Display();
  934. display.ScreenType = screenType;
  935. display.PumpInfo = new DisplayPumpInfo
  936. {
  937. Id = 1,
  938. NozzleId = 1
  939. };
  940. display.TrxList = new DisplayTrx[]
  941. {
  942. new DisplayTrx
  943. {
  944. Id = "100",
  945. FillingInfo = new DisplayTrxFillingInfo
  946. {
  947. Amount = 200,
  948. NozzleId = 1,
  949. ProductType = "95#",
  950. UnitPrice = 6.78m,
  951. Volume = 35,
  952. ProductDiscription = "汽油"
  953. },
  954. MemberInfo = new DisplayTrxMemberInfo
  955. {
  956. LicensePlateNo = "京A88888",
  957. Id = "1234567"
  958. },
  959. State = TrxStatus.Success,
  960. TimeStamp = new DisplayTrxTimeStamp
  961. {
  962. StartTime = DateTime.Now,
  963. FinishTime =DateTime.Now
  964. }
  965. }
  966. };
  967. ms = new MemoryStream();
  968. serializer.Serialize(ms, display);
  969. ms.Position = 0;
  970. sr = new StreamReader(ms, true);
  971. cmdText = sr.ReadToEnd();
  972. break;
  973. default:
  974. throw new Exception("Unsupported screen type");
  975. }
  976. return cmdText;
  977. }
  978. #region IDisposable Support
  979. private bool disposedValue = false; // To detect redundant calls
  980. protected virtual void Dispose(bool disposing)
  981. {
  982. if (!disposedValue)
  983. {
  984. if (disposing)
  985. {
  986. // TODO: dispose managed state (managed objects).
  987. if (fuelingPoints != null)
  988. {
  989. foreach (var fp in fuelingPoints)
  990. {
  991. fp.Dispose();
  992. }
  993. }
  994. if(epsTrxCleanupManager != null)
  995. {
  996. epsTrxCleanupManager.Dispose();
  997. }
  998. if(debugLogger != null)
  999. {
  1000. debugLogger.Dispose();
  1001. }
  1002. }
  1003. // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
  1004. // TODO: set large fields to null.
  1005. disposedValue = true;
  1006. }
  1007. }
  1008. // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
  1009. // ~Eps() {
  1010. // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  1011. // Dispose(false);
  1012. // }
  1013. // This code added to correctly implement the disposable pattern.
  1014. public void Dispose()
  1015. {
  1016. // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  1017. Dispose(true);
  1018. // TODO: uncomment the following line if the finalizer is overridden above.
  1019. // GC.SuppressFinalize(this);
  1020. }
  1021. #endregion
  1022. }
  1023. public enum PumpState
  1024. {
  1025. /// <summary>
  1026. /// Pump is closed.
  1027. /// </summary>
  1028. Closed,
  1029. /// <summary>
  1030. /// Pump is inoperative
  1031. /// </summary>
  1032. Inoperative,
  1033. /// <summary>
  1034. /// Pump is Idle
  1035. /// </summary>
  1036. Idle,
  1037. /// <summary>
  1038. /// Pump is Calling for authorization
  1039. /// </summary>
  1040. Calling,
  1041. /// <summary>
  1042. /// Pump is authorised and ready to begin fuelling.
  1043. /// </summary>
  1044. Authorized,
  1045. /// <summary>
  1046. /// Pump is fuelling
  1047. /// </summary>
  1048. Fuelling,
  1049. /// <summary>
  1050. /// pump is Suspended
  1051. /// </summary>
  1052. Suspended,
  1053. /// <summary>
  1054. /// Unknown state
  1055. /// </summary>
  1056. Unknown,
  1057. /// <summary>
  1058. /// Error state
  1059. /// </summary>
  1060. Error,
  1061. /// <summary>
  1062. /// Offline state
  1063. /// </summary>
  1064. Offline,
  1065. /// <summary>
  1066. /// Starting state
  1067. /// </summary>
  1068. Starting,
  1069. /// <summary>
  1070. /// Stopped state
  1071. /// </summary>
  1072. Stopped,
  1073. }
  1074. }