Handler.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Configuration;
  5. using System.IO.Ports;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading;
  9. using Timer = System.Timers.Timer;
  10. using System.Collections;
  11. using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump;
  12. using Wayne.FDCPOSLibrary;
  13. using System.Xml;
  14. using System.Threading.Tasks;
  15. using Microsoft.Extensions.Logging.Abstractions;
  16. using Microsoft.Extensions.Logging;
  17. using Microsoft.Extensions.DependencyInjection;
  18. using Edge.Core.Database.Models;
  19. using LanTian_Sinopec_PumpIcCardReader.MessageEntity.Outgoing;
  20. using Edge.Core.UniversalApi;
  21. namespace LanTian_Sinopec_PumpIcCardReader
  22. {
  23. /// <summary>
  24. /// PC主动
  25. /// </summary>
  26. public class Handler : IFdcPumpController, IDeviceHandler<byte[], KaJiLianDongV11MessageTemplateBase>
  27. {
  28. private IServiceProvider services;
  29. static ILogger logger = NullLogger.Instance;
  30. private LogicalDeviceState lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE;
  31. private DateTime lastLogicalDeviceStateReceivedTime;
  32. // by seconds, change this value need change the correlated deviceOfflineCountdownTimer's interval as well
  33. public const int lastLogicalDeviceStateExpiredTime = 6;
  34. private List<LogicalNozzle> nozzles = new List<LogicalNozzle>();
  35. private System.Timers.Timer deviceOfflineCountdownTimer;
  36. private bool isOnFdcServerInitCalled = false;
  37. private int pumpId;
  38. protected IContext<byte[], KaJiLianDongV11MessageTemplateBase> context;
  39. private byte rotateMsgSeqNo_Seed = 0;
  40. /// <summary>
  41. /// 0<= value <=63
  42. /// </summary>
  43. private byte NextMsgSeqNo
  44. {
  45. get
  46. {
  47. var cur = this.rotateMsgSeqNo_Seed;
  48. if (cur > 63) cur = this.rotateMsgSeqNo_Seed = 0;
  49. this.rotateMsgSeqNo_Seed++;
  50. return cur;
  51. }
  52. }
  53. public event EventHandler<FdcPumpControllerOnStateChangeEventArg> OnStateChange;
  54. /// <summary>
  55. /// fired on fueling process is on going, the fuel amount should keep changing.
  56. /// </summary>
  57. public event EventHandler<FdcTransactionDoneEventArg> OnCurrentFuellingStatusChange;
  58. public IEnumerable<LogicalNozzle> Nozzles => this.nozzles;
  59. /// <summary>
  60. ///
  61. /// </summary>
  62. /// <param name="pumpId">the ic card also be treated as a logical pump</param>
  63. /// <param name="terminalPhysicalAddress">加液机的通讯终端的逻辑编号POS-P, 通讯终端出厂时POS-P 设为0xFF</param>
  64. /// <param name="fcPhysicalAddress">PC 机的地址范围:0xE0~0xF9</param>
  65. /// <param name="services"></param>
  66. public Handler(int pumpId,
  67. int terminalPhysicalAddress, int fcPhysicalAddress, IServiceProvider services)
  68. {
  69. this.services = services;
  70. var loggerFactory = services.GetRequiredService<ILoggerFactory>();
  71. logger = loggerFactory.CreateLogger("PumpHandler");
  72. this.pumpId = pumpId;
  73. this.terminalPhysicalAddress = (byte)terminalPhysicalAddress;
  74. this.fcPhysicalAddress = (byte)fcPhysicalAddress;
  75. //对于这个品牌,一个IC卡键盘仅可控制一把枪
  76. this.nozzles.Add(new LogicalNozzle(pumpId, (byte)terminalPhysicalAddress, 1, null));
  77. }
  78. public void Init(IContext<byte[], KaJiLianDongV11MessageTemplateBase> context)
  79. {
  80. this.context = context;
  81. var timeWindowWithActivePollingOutgoing =
  82. this.context.Outgoing as TimeWindowWithActivePollingOutgoing<byte[], KaJiLianDongV11MessageTemplateBase>;
  83. timeWindowWithActivePollingOutgoing.PollingMsgProducer = () =>
  84. {
  85. return new PcGenericInquiryRequest()
  86. {
  87. SourceAddress = this.fcPhysicalAddress,
  88. TargetAddress = this.terminalPhysicalAddress,
  89. MessageSequenceNumber = this.NextMsgSeqNo,
  90. PC_TIME = DateTime.Now,
  91. };
  92. };
  93. }
  94. public virtual async Task Process(IContext<byte[], KaJiLianDongV11MessageTemplateBase> context)
  95. {
  96. this.context = context;
  97. this.lastLogicalDeviceStateReceivedTime = DateTime.Now;
  98. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE)
  99. {
  100. logger.LogInformation("Pump: " + this.pumpId + ", " + "Recevied an IcCard Msg in FDC_OFFLINE state, " +
  101. "indicates the underlying connection is established, switch to FDC_READY");
  102. this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
  103. this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
  104. }
  105. if (context.Incoming.Message is PumpRealTimeStateEvent pumpRealTimeStateResponse)
  106. {
  107. if (pumpRealTimeStateResponse.NozzleOperatingStates == null)
  108. {
  109. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY)
  110. {
  111. this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
  112. this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(
  113. LogicalDeviceState.FDC_READY,
  114. this.nozzles.FirstOrDefault()));
  115. }
  116. }
  117. if (pumpRealTimeStateResponse.NozzleOperatingStates != null
  118. && pumpRealTimeStateResponse.NozzleOperatingStates.Any(n =>
  119. n.St状态字 == NozzleOperatingState.PumpStateChangeCode.抬枪或加油中))
  120. {
  121. this.nozzles.First().RealPriceOnPhysicalPump =
  122. pumpRealTimeStateResponse.NozzleOperatingStates.First().PRC价格;
  123. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_FUELLING)
  124. {
  125. this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING;
  126. this.OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction()
  127. {
  128. Nozzle = this.nozzles.First(),
  129. Amount = pumpRealTimeStateResponse.NozzleOperatingStates.First().AMN数额,
  130. Volumn = pumpRealTimeStateResponse.NozzleOperatingStates.First().VOL升数,
  131. Price = pumpRealTimeStateResponse.NozzleOperatingStates.First().PRC价格,
  132. Finished = false,
  133. }));
  134. }
  135. }
  136. }
  137. else if (context.Incoming.Message is PumpNotifyTransactionDoneRequest pumpNotifyTransactionDoneRequest)
  138. {
  139. this.nozzles.First().RealPriceOnPhysicalPump =
  140. pumpNotifyTransactionDoneRequest.PRC_成交价格;
  141. this.OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction()
  142. {
  143. Nozzle = this.nozzles.First(),
  144. Amount = pumpNotifyTransactionDoneRequest.AMN数额,
  145. Volumn = pumpNotifyTransactionDoneRequest.VOL_升数,
  146. Price = pumpNotifyTransactionDoneRequest.PRC_成交价格,
  147. SequenceNumberGeneratedOnPhysicalPump = pumpNotifyTransactionDoneRequest.POS_TTC,
  148. //AmountTotalizer = ,
  149. VolumeTotalizer = pumpNotifyTransactionDoneRequest.V_TOT_升累计,
  150. Finished = true,
  151. }));
  152. #warning should send a request to external module to validate the T-MAC
  153. this.context.Outgoing.Write(new PumpNotifyTransactionDoneResponse()
  154. {
  155. SourceAddress = this.fcPhysicalAddress,
  156. TargetAddress = this.terminalPhysicalAddress,
  157. MessageSequenceNumber = this.NextMsgSeqNo,
  158. Result = PumpNotifyTransactionDoneResponse.ResultEnum.正确
  159. });
  160. }
  161. else if (context.Incoming.Message is PumpAskDataDownloadRequest pumpAskDataDownloadRequest)
  162. {
  163. #warning should query cloud side to get the BL_LEN_长度
  164. this.context.Outgoing.Write(new PumpAskDataDownloadResponse()
  165. {
  166. SourceAddress = this.fcPhysicalAddress,
  167. TargetAddress = this.terminalPhysicalAddress,
  168. MessageSequenceNumber = this.NextMsgSeqNo,
  169. BL_LEN_长度 = 0,
  170. Content_内容 = pumpAskDataDownloadRequest.Content_内容,
  171. });
  172. }
  173. else if (context.Incoming.Message is PumpStartDataDownloadRequest pumpStartDataDownloadRequest)
  174. {
  175. #warning should query cloud side to get the real data for downloading to pump
  176. this.context.Outgoing.Write(new PumpStartDataDownloadResponse()
  177. {
  178. SourceAddress = this.fcPhysicalAddress,
  179. TargetAddress = this.terminalPhysicalAddress,
  180. MessageSequenceNumber = this.NextMsgSeqNo,
  181. Content_内容 = pumpStartDataDownloadRequest.Content_内容,
  182. S_OFFSET_段偏移 = pumpStartDataDownloadRequest.S_OFFSET_段偏移,
  183. SEG_段数_segs = 0,
  184. DATA_Content_数据内容 = new List<byte>() { 0x00 }
  185. });
  186. }
  187. else if (context.Incoming.Message is PumpInquiryGrayCardRecordRequest pumpInquiryGrayCardRecordRequest)
  188. {
  189. #warning should query cloud side to get the real data
  190. this.context.Outgoing.Write(new PumpInquiryGrayCardRecordResponse()
  191. {
  192. SourceAddress = this.fcPhysicalAddress,
  193. TargetAddress = this.terminalPhysicalAddress,
  194. MessageSequenceNumber = this.NextMsgSeqNo,
  195. M_FLAG_匹配标志 = PumpInquiryGrayCardRecordResponse.PumpInquiryGrayCardRecordResult.匹配,
  196. ASN卡应用号 = pumpInquiryGrayCardRecordRequest.ASN卡应用号,
  197. BAL_余额 = 999911,
  198. AMN_交易额 = 183,
  199. AMN_优惠价 = 123,
  200. AMN_折扣 = 45,
  201. AMN_优惠后金额 = 97,
  202. CTC_交易计数器 = 765,
  203. DS_扣款来源 = 0,
  204. TIME_日期及时间 = DateTime.Now,
  205. GMAC电子签名 = 23192394,
  206. PSAM_TID_PSAM编号 = new List<byte>() { 0x01, 0x02 },
  207. PSAM_TTC = 654
  208. });
  209. }
  210. else if (context.Incoming.Message is PumpInquiryCreditCardRecordRequest pumpInquiryCreditCardRecordRequest)
  211. {
  212. #warning should query cloud side to get the real data
  213. this.context.Outgoing.Write(new PumpInquiryCreditCardRecordResponse()
  214. {
  215. SourceAddress = this.fcPhysicalAddress,
  216. TargetAddress = this.terminalPhysicalAddress,
  217. MessageSequenceNumber = this.NextMsgSeqNo,
  218. S_DATA_记帐卡状态 = PumpInquiryCreditCardRecordResponse.CardStateEnum.可以加油_无里程限制,
  219. });
  220. }
  221. else if (context.Incoming.Message is PumpInquiryBlackAndWhiteListRequest pumpInquiryBlackAndWhiteListRequest)
  222. {
  223. #warning should query cloud side to get the real data
  224. this.context.Outgoing.Write(new PumpInquiryBlackAndWhiteListResponse()
  225. {
  226. SourceAddress = this.fcPhysicalAddress,
  227. TargetAddress = this.terminalPhysicalAddress,
  228. MessageSequenceNumber = this.NextMsgSeqNo,
  229. M_FLAG_匹配标志 = PumpInquiryBlackAndWhiteListResponse.ResultEnum.不匹配,
  230. ASN卡应用号 = pumpInquiryBlackAndWhiteListRequest.ASN卡应用号,
  231. });
  232. }
  233. else if (context.Incoming.Message is PumpInternalErrorRequest pumpInternalErrorRequest)
  234. {
  235. #warning may need notify someone that error happened??
  236. this.context.Outgoing.Write(new PumpInternalErrorResponse()
  237. {
  238. SourceAddress = this.fcPhysicalAddress,
  239. TargetAddress = this.terminalPhysicalAddress,
  240. MessageSequenceNumber = this.NextMsgSeqNo,
  241. COMMAND_通讯的命令字 = 0,
  242. FLAG_成功标志 = 0,
  243. });
  244. }
  245. else
  246. {
  247. if (logger.IsEnabled(LogLevel.Debug))
  248. logger.LogDebug("Incoming an unknown message, will do nothing and ignore.");
  249. }
  250. }
  251. public string Name => this.GetType().FullName;
  252. public Guid Id => new Guid();
  253. /// <summary>
  254. /// Gets the Identification of the pump for the system. Is the logical number of the pump
  255. /// </summary>
  256. public int PumpId => this.pumpId;
  257. /// <summary>
  258. /// PC 机的地址范围:0xE0~0xF9
  259. /// </summary>
  260. private byte fcPhysicalAddress;
  261. /// <summary>
  262. /// 加液机的通讯终端的逻辑编号POS-P, 通讯终端出厂时POS-P 设为0xFF
  263. /// </summary>
  264. private byte terminalPhysicalAddress;
  265. public int PumpPhysicalId => this.terminalPhysicalAddress;
  266. public int AmountDecimalDigits => 2;
  267. public int VolumeDecimalDigits => 2;
  268. public int PriceDecimalDigits => 2;
  269. public int VolumeTotalizerDecimalDigits => 2;
  270. [UniversalApi]
  271. public virtual async Task<LogicalDeviceState> QueryStatusAsync()
  272. {
  273. return this.lastLogicalDeviceState;
  274. }
  275. /// <summary>
  276. ///
  277. /// </summary>
  278. /// <returns>MoneyTotalizer:VolumnTotalizer</returns>
  279. [UniversalApi]
  280. public async Task<Tuple<int, int>> QueryTotalizerAsync(byte logicalNozzleId)
  281. {
  282. var response = await this.context.Outgoing.WriteAsync(new PcReadPumpAccumulatorRequest()
  283. {
  284. SourceAddress = this.fcPhysicalAddress,
  285. TargetAddress = this.terminalPhysicalAddress,
  286. MessageSequenceNumber = this.NextMsgSeqNo,
  287. }, (_, testResponse) => testResponse is PcReadPumpAccumulatorResponse, 4000) as PcReadPumpAccumulatorResponse;
  288. if (response == null) return new Tuple<int, int>(-1, -1);
  289. var volTotalizerValue = response?.MZN_单个油枪.FirstOrDefault()?.V_TOT_升累计;
  290. return new Tuple<int, int>(-1, volTotalizerValue ?? -1);
  291. }
  292. [UniversalApi]
  293. public async Task<PcReadPumpInfoResponse> PC机读取加液机信息Async()
  294. {
  295. var response = await this.context.Outgoing.WriteAsync(new PcReadPumpInfoRequest()
  296. {
  297. SourceAddress = this.fcPhysicalAddress,
  298. TargetAddress = this.terminalPhysicalAddress,
  299. MessageSequenceNumber = this.NextMsgSeqNo,
  300. }, (_, testResponse) => testResponse is PcReadPumpInfoResponse, 4000) as PcReadPumpInfoResponse;
  301. return response;
  302. }
  303. [UniversalApi]
  304. public async Task<KaJiLianDongV11MessageTemplateBase> PC机主动读取加液记录Async(int pos_TTC_终端交易号)
  305. {
  306. var response = await this.context.Outgoing.WriteAsync(new PcReadTransactionRequest(pos_TTC_终端交易号)
  307. {
  308. SourceAddress = this.fcPhysicalAddress,
  309. TargetAddress = this.terminalPhysicalAddress,
  310. MessageSequenceNumber = this.NextMsgSeqNo,
  311. }, (_, testResponse) => testResponse is PumpNotifyTransactionDoneRequest || testResponse is PcReadTransactionButDoesNotFindResponse, 4000);
  312. return response;
  313. }
  314. public virtual async Task<bool> ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId)
  315. {
  316. throw new NotImplementedException();
  317. }
  318. /// <summary>
  319. ///
  320. /// </summary>
  321. /// <param name="logicalNozzleId">useless for this type of pump, it always one pump one nozzle</param>
  322. /// <returns></returns>
  323. public virtual async Task<bool> AuthorizeAsync(byte logicalNozzleId)
  324. {
  325. throw new NotImplementedException();
  326. }
  327. /// <summary>
  328. ///
  329. /// </summary>
  330. /// <param name="moneyAmount"></param>
  331. /// <param name="logicalNozzleId">useless for this type of pump, it always one pump one nozzle</param>
  332. /// <returns></returns>
  333. public virtual async Task<bool> AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId)
  334. {
  335. throw new NotImplementedException();
  336. }
  337. /// <summary>
  338. ///
  339. /// </summary>
  340. /// <param name="volumn"></param>
  341. /// <param name="logicalNozzleId">useless for this type of pump, it always one pump one nozzle</param>
  342. /// <returns></returns>
  343. public virtual async Task<bool> AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId)
  344. {
  345. throw new NotImplementedException();
  346. }
  347. public virtual async Task<bool> FuelingRoundUpByAmountAsync(int amount)
  348. {
  349. throw new NotImplementedException();
  350. }
  351. #region not implemented
  352. public async Task<bool> UnAuthorizeAsync(byte logicalNozzleId)
  353. {
  354. throw new NotImplementedException();
  355. }
  356. public async Task<bool> SuspendFuellingAsync()
  357. {
  358. throw new NotImplementedException();
  359. }
  360. public async Task<bool> ResumeFuellingAsync()
  361. {
  362. throw new NotImplementedException();
  363. }
  364. public async Task<bool> FuelingRoundUpByVolumeAsync(int volume)
  365. { throw new NotImplementedException(); }
  366. #endregion
  367. /// <summary>
  368. /// </summary>
  369. protected Dictionary<byte, FuelSaleTransaction> logicalNozzleIdToLastFuelSaleTrxMapping = new Dictionary<byte, FuelSaleTransaction>();
  370. public void OnFdcServerInit(Dictionary<string, object> parameters)
  371. {
  372. if (parameters.ContainsKey("LastPriceChange"))
  373. {
  374. }
  375. /* Load Last sale(from db) for void the case of FC accidently disconnect from Pump in fueling,
  376. and may cause a fueling trx gone from FC control */
  377. if (parameters.ContainsKey("LastFuelSaleTrx"))
  378. {
  379. // nozzle logical id:lastSale
  380. //var lastFuelSaleTrxes = parameters["LastFuelSaleTrx"] as Dictionary<byte, FuelSaleTransaction>;
  381. //foreach (var lastFuelSaleTrx in lastFuelSaleTrxes)
  382. //{
  383. // logger.Info("Pump: " + this.pumpId + ", OnFdcServerInit, load last fuel sale " +
  384. // "on logical nozzle: " + lastFuelSaleTrx.Key + " with value: " + lastFuelSaleTrx.Value);
  385. // this.logicalNozzleIdToLastFuelSaleTrxMapping.Remove(lastFuelSaleTrx.Key);
  386. // this.logicalNozzleIdToLastFuelSaleTrxMapping.Add(lastFuelSaleTrx.Key, lastFuelSaleTrx.Value);
  387. //}
  388. }
  389. }
  390. public async Task<bool> LockNozzleAsync(byte logicalNozzleId)
  391. {
  392. return false;
  393. }
  394. public async Task<bool> UnlockNozzleAsync(byte logicalNozzleId)
  395. {
  396. return false;
  397. }
  398. }
  399. }