HengshanPumpHandler.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. using Edge.Core.IndustryStandardInterface.Pump.Fdc;
  2. using Edge.Core.IndustryStandardInterface.Pump;
  3. using HengshanPaymentTerminal;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using HengshanPaymentTerminal.MessageEntity.Outgoing;
  10. namespace HengshanPaymentTerminal
  11. {
  12. /// <summary>
  13. /// A neutral pump handler that represents a Hengshan pump regardless of its protocol (Hengshan or TQC).
  14. /// Actuall this is a fake pump handler since it doesn't have direct control over the pumps.
  15. /// </summary>
  16. public class HengshanPumpHandler : IFdcPumpController
  17. {
  18. #region Fields
  19. //The parent of the pump handler.
  20. private HengshanPayTermHandler terminalHandler;
  21. private LogicalDeviceState state = LogicalDeviceState.FDC_READY;
  22. //private SpsManager spsManager;
  23. private byte frameSqNo;
  24. private object syncObj = new object();
  25. private bool totalizerRequested = false;
  26. private byte totalizerRequestedNozzleLogicId = 0;
  27. private bool lockPumpRequested = false;
  28. private bool unlockPumpRequested = false;
  29. private LogicalNozzle nozzle;
  30. private List<LogicalNozzle> nozzleList;
  31. private List<int> siteNozzleNoList;
  32. #endregion
  33. #region Logger
  34. private static NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("IPosPlusApp");
  35. #endregion
  36. #region Constructor
  37. public HengshanPumpHandler(HengshanPayTermHandler terminalHandler, string name, int pumpId, List<int> nozzles, List<int> siteNozzleNoList)
  38. {
  39. this.terminalHandler = terminalHandler;
  40. this.terminalHandler.OnCheckCommandReceived += TerminalHandler_OnCheckCommandReceived;
  41. Name = name;
  42. PumpId = pumpId;
  43. nozzle = new LogicalNozzle(pumpId, Convert.ToByte(pumpId), 1, null);
  44. //spsManager = new SpsManager();
  45. this.siteNozzleNoList = siteNozzleNoList;
  46. nozzleList = new List<LogicalNozzle>();
  47. foreach (var nozzleNo in nozzles)
  48. {
  49. var logicalNozzle = new LogicalNozzle(PumpId, Convert.ToByte(nozzleNo), Convert.ToByte(nozzleNo), null);
  50. nozzleList.Add(logicalNozzle);
  51. }
  52. }
  53. #endregion
  54. #region Properties
  55. public string Name { get; }
  56. public int PumpId { get; }
  57. public int PumpPhysicalId => 1;
  58. public IEnumerable<LogicalNozzle> Nozzles
  59. {
  60. get
  61. {
  62. //For Hengshan pumps, there is only one logical nozzle per pump, no real price set.
  63. return nozzleList;//return new List<LogicalNozzle> { nozzle };
  64. }
  65. }
  66. //Decimal digit settings, China domestic standard
  67. //小数点后位数设定,中国国内标准,一般为2位
  68. public int AmountDecimalDigits => 2;
  69. public int VolumeDecimalDigits => 2;
  70. public int PriceDecimalDigits => 2;
  71. public int VolumeTotalizerDecimalDigits => 2;
  72. public byte FrameSqNo
  73. {
  74. get
  75. {
  76. lock (syncObj)
  77. {
  78. return frameSqNo;
  79. }
  80. }
  81. set
  82. {
  83. lock (syncObj)
  84. {
  85. frameSqNo = value;
  86. }
  87. }
  88. }
  89. #endregion
  90. #region Events
  91. public event EventHandler<FdcPumpControllerOnStateChangeEventArg> OnStateChange;
  92. public event EventHandler<FdcTransactionDoneEventArg> OnCurrentFuellingStatusChange;
  93. public event EventHandler<FuelPriceChangeRequestEventArgs> OnFuelPriceChangeRequested;
  94. #endregion
  95. #region Event handlers
  96. private byte GetSiteNozzleNoByLogicId(byte logicId)
  97. {
  98. foreach (var nozzleNo in siteNozzleNoList)
  99. {
  100. if (terminalHandler.NozzleLogicIdDict[nozzleNo] == logicId)
  101. return Convert.ToByte(nozzleNo);
  102. }
  103. return Convert.ToByte(PumpId);
  104. }
  105. private void TerminalHandler_OnCheckCommandReceived(object sender, CheckCommandEventArgs e)
  106. {
  107. if (totalizerRequested
  108. && e.CheckCommandRequest.SourceAddress == terminalHandler.GetSubAddressForPump(PumpId))
  109. {
  110. var request = new ReadVolumeTotal
  111. {
  112. Prefix = 0xFA,
  113. SourceAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)),
  114. DestinationAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)),
  115. FrameSqNoByte = e.CheckCommandRequest.FrameSqNoByte,
  116. Handle = (byte)MessageEntity.Command.ReadVolumeTotalizer,
  117. NozzleNo = GetSiteNozzleNoByLogicId(totalizerRequestedNozzleLogicId)//Convert.ToByte(PumpId)
  118. };
  119. terminalHandler.PendMessage(request);
  120. Log("Request to terminal to get totalizer, pending!");
  121. }
  122. else if ((lockPumpRequested || unlockPumpRequested)
  123. && e.CheckCommandRequest.SourceAddress == terminalHandler.GetSubAddressForPump(PumpId))
  124. {
  125. LockOrUnlockPumpRequest request = new LockOrUnlockPumpRequest
  126. {
  127. Prefix = 0xFA,
  128. SourceAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)),
  129. DestinationAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)),
  130. Handle = Convert.ToByte(MessageEntity.Command.LockOrUnlockPump),
  131. FrameSqNoByte = e.CheckCommandRequest.FrameSqNoByte,
  132. FPCode = EncodeFPCodeString(PumpId, PumpId),
  133. OperationType = lockPumpRequested ? Support.LockUnlockOperation.Lock : Support.LockUnlockOperation.Unlock
  134. };
  135. terminalHandler.PendMessage(request);
  136. var currentPump = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);
  137. if (currentPump == null)
  138. Log("Could not get the pump state holder");
  139. else
  140. Log($"Current pump state holder, pump no: {currentPump.PumpNo}");
  141. currentPump.OperationType = request.OperationType;
  142. Log($"Current pump state holder, operation type set to: {request.OperationType}");
  143. //terminalHandler.Write(request);
  144. if (lockPumpRequested)
  145. Log($"PumpNo: {PumpId}, Request to lock pump, pending!");
  146. if (unlockPumpRequested)
  147. Log($"PumpNo: {PumpId}, Request to unlock pump, pending!");
  148. }
  149. }
  150. public void FirePumpStateChange(LogicalDeviceState state, byte nozzeLogicId)
  151. {
  152. var logicalNozzle = new LogicalNozzle(PumpId, Convert.ToByte(PumpPhysicalId), nozzeLogicId, null);
  153. this.state = state;
  154. if (state == LogicalDeviceState.FDC_READY)
  155. OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
  156. else
  157. OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(state, logicalNozzle));
  158. }
  159. public void FireNozzleStateChange(LogicalDeviceState? state)
  160. {
  161. nozzle = new LogicalNozzle(PumpId, Convert.ToByte(PumpPhysicalId), 1, null);
  162. if (state.HasValue)
  163. nozzle.LogicalState = state;
  164. else
  165. nozzle.LogicalState = null;
  166. OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY, nozzle));
  167. }
  168. //2023-08-22 状态变更
  169. public void FireFuelingStatusChange(FdcTransaction fuelingTransaction)
  170. {
  171. OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(fuelingTransaction));
  172. }
  173. #endregion
  174. #region Pump interactions
  175. public LogicalDeviceState QueryStatus()
  176. {
  177. var stateHolder = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);
  178. if (stateHolder != null)
  179. {
  180. if (stateHolder.State.DispenserState == 3)
  181. {
  182. return LogicalDeviceState.FDC_READY;
  183. }
  184. else if (stateHolder.State.DispenserState == 2)
  185. {
  186. return LogicalDeviceState.FDC_LOCKED;
  187. }
  188. }
  189. return LogicalDeviceState.FDC_READY;
  190. }
  191. /// <summary>
  192. /// locks a nozzle
  193. /// </summary>
  194. /// <param name="logicalNozzleId"></param>
  195. /// <returns></returns>
  196. public Task<bool> LockNozzleAsync(byte logicalNozzleId)
  197. {
  198. var stateHolder = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);
  199. if (stateHolder != null)
  200. {
  201. if (stateHolder.State.DispenserState == 2)
  202. {
  203. // If the pump is already locked, reply with success immediately.
  204. return Task.FromResult(true);
  205. }
  206. else if (stateHolder.State.DispenserState != 3)
  207. {
  208. // Avoid handling locking when pump is fueling or in other states.
  209. return Task.FromResult(false);
  210. }
  211. }
  212. TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
  213. EventHandler<LockUnlockEventArgs> eventHandler = null;
  214. eventHandler += (o, e) =>
  215. {
  216. Log($"Locking pump, result received, success? {e.Result}");
  217. lockPumpRequested = false;
  218. tcs.SetResult(e.Result);
  219. if (e.Result)
  220. {
  221. FireNozzleStateChange(LogicalDeviceState.FDC_LOCKED);
  222. }
  223. terminalHandler.OnLockUnlockCompleted -= eventHandler;
  224. var currentPump = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);
  225. if (currentPump != null)
  226. {
  227. currentPump.OperationType = Support.LockUnlockOperation.None;
  228. }
  229. };
  230. terminalHandler.OnLockUnlockCompleted += eventHandler;
  231. lockPumpRequested = true;
  232. return tcs.Task;
  233. }
  234. /// <summary>
  235. /// unlock a locked nozzle
  236. /// </summary>
  237. /// <param name="logicalNozzleId"></param>
  238. /// <returns></returns>
  239. public Task<bool> UnlockNozzleAsync(byte logicalNozzleId)
  240. {
  241. var stateHolder = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);
  242. if (stateHolder != null)
  243. {
  244. if (stateHolder.State.DispenserState == 3)
  245. {
  246. // Need to do nothing when pump is not locked.
  247. return Task.FromResult(true);
  248. }
  249. }
  250. TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
  251. EventHandler<LockUnlockEventArgs> eventHandler = null;
  252. eventHandler += (o, e) =>
  253. {
  254. Log($"Unlocking pump, result received, success? {e.Result}");
  255. unlockPumpRequested = false;
  256. tcs.SetResult(e.Result);
  257. if (e.Result)
  258. {
  259. FireNozzleStateChange(null);
  260. }
  261. terminalHandler.OnLockUnlockCompleted -= eventHandler;
  262. var currentPump = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId);
  263. if (currentPump != null)
  264. {
  265. currentPump.OperationType = Support.LockUnlockOperation.None;
  266. }
  267. };
  268. terminalHandler.OnLockUnlockCompleted += eventHandler;
  269. unlockPumpRequested = true;
  270. return tcs.Task;
  271. }
  272. #endregion
  273. #region FDC Server Init
  274. public void OnFdcServerInit(Dictionary<string, object> parameters)
  275. {
  276. }
  277. public Task<LogicalDeviceState> QueryStatusAsync()
  278. {
  279. return Task.FromResult(state);
  280. }
  281. public void QueryTotalizerAsync(int pumpId, byte logicalNozzleId)
  282. {
  283. Log($"Query totalizer internally...");
  284. var request = new ReadVolumeTotal
  285. {
  286. Prefix = 0xFA,
  287. SourceAddress = Convert.ToByte(pumpId),
  288. DestinationAddress = Convert.ToByte(pumpId),
  289. FrameSqNoByte = frameSqNo,
  290. Handle = (byte)MessageEntity.Command.ReadVolumeTotalizer,
  291. NozzleNo = logicalNozzleId
  292. };
  293. terminalHandler.Write(request);
  294. }
  295. public Task<Tuple<long, long>> QueryTotalizerAsync(byte logicalNozzleId)
  296. {
  297. Log($"LogicalNozzle {logicalNozzleId} querying totalizer...");
  298. TaskCompletionSource<Tuple<long, long>> tcs = new TaskCompletionSource<Tuple<long, long>>();
  299. EventHandler<TotalizerDataEventArgs> eventHandler = null;
  300. eventHandler += (o, e) =>
  301. {
  302. Log($"Totalizer data for nozzle: {e.NozzleNo}, money acc: {e.Totalizer.Item1}, volume acc: {e.Totalizer.Item2}");
  303. totalizerRequested = false; //Reset the value so that no more request will be sent.
  304. if (e.NozzleNo == GetSiteNozzleNoByLogicId(logicalNozzleId))
  305. {
  306. Log("Totalizer data ready");
  307. tcs.SetResult(e.Totalizer);
  308. terminalHandler.OnTotalizerReceived -= eventHandler;
  309. }
  310. totalizerRequestedNozzleLogicId = 0;
  311. };
  312. terminalHandler.OnTotalizerReceived += eventHandler;
  313. totalizerRequestedNozzleLogicId = logicalNozzleId;
  314. totalizerRequested = true;
  315. return tcs.Task;
  316. }
  317. public Task<bool> ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId)
  318. {
  319. logger.Info($"Change price received, Pump {PumpId}, LogicalNozzle {logicalNozzleId}, Price {newPriceWithoutDecimalPoint}");
  320. //Let the iPOS App handle the fuel price change.
  321. OnFuelPriceChangeRequested?.Invoke(this, new FuelPriceChangeRequestEventArgs
  322. {
  323. NozzleId = logicalNozzleId,
  324. PumpId = (byte)PumpId,
  325. Price = newPriceWithoutDecimalPoint
  326. });
  327. Log($"application handled fuel price change");
  328. return Task.FromResult(true);
  329. //TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
  330. //EventHandler<FuelPriceDownloadRequestedEventArgs> eventHandler = null;
  331. //eventHandler += (o, e) =>
  332. //{
  333. // Log("Terminal requested fuel price");
  334. // tcs.SetResult(e.Requested);
  335. // terminalHandler.OnTerminalFuelPriceDownloadRequested -= eventHandler;
  336. //};
  337. //terminalHandler.OnTerminalFuelPriceDownloadRequested += eventHandler;
  338. //return tcs.Task;
  339. }
  340. public Task<bool> AuthorizeAsync(byte logicalNozzleId)
  341. {
  342. return Task.FromResult(false);
  343. }
  344. public Task<bool> UnAuthorizeAsync(byte logicalNozzleId)
  345. {
  346. return Task.FromResult(false);
  347. }
  348. public Task<bool> AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId)
  349. {
  350. return Task.FromResult(false);
  351. }
  352. public Task<bool> AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId)
  353. {
  354. return Task.FromResult(false);
  355. }
  356. public Task<bool> FuelingRoundUpByAmountAsync(int amount)
  357. {
  358. return Task.FromResult(false);
  359. }
  360. public Task<bool> FuelingRoundUpByVolumeAsync(int volume)
  361. {
  362. return Task.FromResult(false);
  363. }
  364. public Task<bool> SuspendFuellingAsync()
  365. {
  366. return Task.FromResult(false);
  367. }
  368. public Task<bool> ResumeFuellingAsync()
  369. {
  370. return Task.FromResult(false);
  371. }
  372. #endregion
  373. private string EncodeFPCodeString(int nozzleId, int pumpId)
  374. {
  375. return nozzleId.ToString("X").PadLeft(2, '0') + pumpId.ToString("X").PadLeft(2, '0');
  376. }
  377. #region Log
  378. private void Log(string message)
  379. {
  380. logger.Info($"Pump Handler: {PumpId}, {message}");
  381. }
  382. #endregion
  383. }
  384. }