HengshanPayTermHandler.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. using HengshanPaymentTerminal.MessageEntity.Incoming;
  2. using HengshanPaymentTerminal.MessageEntity;
  3. using HengshanPaymentTerminal.Support;
  4. using HengshanPaymentTerminal;
  5. using System;
  6. using System.Collections.Concurrent;
  7. using System.Collections;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Text;
  11. using System.Threading.Tasks;
  12. using Edge.Core.Processor.Dispatcher.Attributes;
  13. using Edge.Core.IndustryStandardInterface.Pump;
  14. using Edge.Core.IndustryStandardInterface.Pump.Fdc;
  15. using Edge.Core.Processor;
  16. namespace HengshanPaymentTerminal
  17. {
  18. /// <summary>
  19. /// Handler that communicates directly with the Hengshan Payment Terminal for card handling and pump handling via serial port.
  20. /// </summary>
  21. [MetaPartsDescriptor(
  22. "lang-zh-cn:恒山IC卡终端(UI板) App lang-en-us:Hengshan IC card terminal (UI Board)",
  23. "lang-zh-cn:用于与UI板通讯控制加油机" +
  24. "lang-en-us:Used for terminal communication to control pumps",
  25. new[]
  26. {
  27. "lang-zh-cn:恒山IC卡终端lang-en-us:HengshanICTerminal"
  28. })]
  29. public class HengshanPayTermHandler : IEnumerable<IFdcPumpController>, IDeviceHandler<byte[], CardMessageBase>
  30. {
  31. #region Fields
  32. private string pumpIds;
  33. private string pumpSubAddresses;
  34. private string pumpNozzles;
  35. private string pumpSiteNozzleNos;
  36. private string nozzleLogicIds;
  37. private IContext<byte[], CardMessageBase> _context;
  38. private List<HengshanPumpHandler> pumpHandlers = new List<HengshanPumpHandler>();
  39. public Queue<CardMessageBase> queue = new Queue<CardMessageBase>();
  40. private object syncObj = new object();
  41. private ConcurrentDictionary<int, PumpStateHolder> statusDict = new ConcurrentDictionary<int, PumpStateHolder>();
  42. public ConcurrentDictionary<int, PumpStateHolder> PumpStatusDict => statusDict;
  43. private Dictionary<int, int> pumpIdSubAddressDict;
  44. public Dictionary<int, List<int>> PumpNozzlesDict { get; private set; }
  45. public Dictionary<int, int> NozzleLogicIdDict { get; private set; }
  46. public Dictionary<int, List<int>> PumpSiteNozzleNoDict { get; private set; }
  47. #endregion
  48. #region Logger
  49. private static NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("IPosPlusApp");
  50. #endregion
  51. #region Constructor
  52. //private static List<object> ResolveCtorMetaPartsConfigCompatibility(string incompatibleCtorParamsJsonStr)
  53. //{
  54. // var jsonParams = JsonDocument.Parse(incompatibleCtorParamsJsonStr).RootElement.EnumerateArray().ToArray();
  55. // //sample: "UITemplateVersion":"1.0"
  56. // string uiTemplateVersionRegex = @"(?<=""UITemplateVersion""\:\"").+?(?="")";
  57. // var match = Regex.Match(jsonParams.First().GetRawText(), uiTemplateVersionRegex, RegexOptions.IgnoreCase | RegexOptions.Multiline);
  58. // if (match.Success)
  59. // {
  60. // var curVersion = match.Value;
  61. // if (curVersion == "1.0")
  62. // {
  63. // var existsAppConfigV1 = JsonSerializer.Deserialize(jsonParams.First().GetRawText(), typeof(HengshanPayTerminalHanlderGroupConfigV1));
  64. // }
  65. // else
  66. // {
  67. // }
  68. // }
  69. // return null;
  70. //}
  71. [ParamsJsonSchemas("TermHandlerGroupCtorParamsJsonSchemas")]
  72. public HengshanPayTermHandler(HengshanPayTerminalHanlderGroupConfigV2 config)
  73. : this(config.PumpIds,
  74. string.Join(";", config.PumpSubAddresses.Select(m => $"{m.PumpId}={m.SubAddress}")),
  75. string.Join(";", config.PumpNozzleLogicIds.Select(m => $"{m.PumpId}={m.LogicIds}")),
  76. string.Join(";", config.PumpSiteNozzleNos.Select(m => $"{m.PumpId}={m.SiteNozzleNos}")),
  77. string.Join(";", config.NozzleLogicIds.Select(m => $"{m.NozzleNo}={m.LogicId}")))
  78. {
  79. }
  80. public HengshanPayTermHandler(
  81. string pumpIds,
  82. string pumpSubAddresses,
  83. string pumpNozzles,
  84. string pumpSiteNozzleNos,
  85. string nozzleLogicIds)
  86. {
  87. this.pumpIds = pumpIds;
  88. this.pumpSubAddresses = pumpSubAddresses;
  89. this.pumpNozzles = pumpNozzles;
  90. this.pumpSiteNozzleNos = pumpSiteNozzleNos;
  91. this.nozzleLogicIds = nozzleLogicIds;
  92. AssociatedPumpIds = GetPumpIdList(pumpIds);
  93. pumpIdSubAddressDict = InitializePumpSubAddressMapping();
  94. PumpNozzlesDict = ParsePumpNozzlesList(pumpNozzles);
  95. PumpSiteNozzleNoDict = ParsePumpSiteNozzleNoList(pumpSiteNozzleNos);
  96. NozzleLogicIdDict = InitializeNozzleLogicIdMapping(nozzleLogicIds);
  97. InitializePumpHandlers();
  98. }
  99. #endregion
  100. public void OnFdcServerInit(Dictionary<string, object> parameters)
  101. {
  102. logger.Info("OnFdcServerInit called");
  103. if (parameters.ContainsKey("LastPriceChange"))
  104. {
  105. // nozzle logical id:rawPrice
  106. var lastPriceChanges = parameters["LastPriceChange"] as Dictionary<byte, int>;
  107. foreach (var priceChange in lastPriceChanges)
  108. {
  109. }
  110. }
  111. }
  112. #region Event handler
  113. public event EventHandler<TerminalMessageEventArgs> OnTerminalMessageReceived;
  114. public event EventHandler<TotalizerDataEventArgs> OnTotalizerReceived;
  115. public event EventHandler<FuelPriceChangeRequestEventArgs> OnFuelPriceChangeRequested;
  116. public event EventHandler<FuelPriceDownloadRequestedEventArgs> OnTerminalFuelPriceDownloadRequested;
  117. public event EventHandler<CheckCommandEventArgs> OnCheckCommandReceived;
  118. public event EventHandler<LockUnlockEventArgs> OnLockUnlockCompleted;
  119. #endregion
  120. #region Properties
  121. public List<int> AssociatedPumpIds { get; private set; }
  122. public IContext<byte[], CardMessageBase> Context
  123. {
  124. get { return _context; }
  125. }
  126. public string PumpIdList => pumpIds;
  127. //public LockUnlockOperation LockUnlockOperationType { get; set; } = LockUnlockOperation.Undefined;
  128. #endregion
  129. #region Methods
  130. public int GetSubAddressForPump(int pumpId)
  131. {
  132. return pumpIdSubAddressDict.First(d => d.Key == pumpId).Value;
  133. }
  134. private List<int> GetPumpIdList(string pumpIds)
  135. {
  136. var pumpIdList = new List<int>();
  137. if (!string.IsNullOrEmpty(pumpIds) && pumpIds.Contains(',')) //multiple pumps per serial port, Hengshan TQC pump
  138. {
  139. var arr = pumpIds.Split(',');
  140. foreach (var item in arr)
  141. {
  142. pumpIdList.Add(int.Parse(item));
  143. }
  144. return pumpIdList;
  145. }
  146. else if (!string.IsNullOrEmpty(pumpIds) && pumpIds.Length == 1 || pumpIds.Length == 2) //only 1 pump per serial port, Hengshan pump
  147. {
  148. return new List<int> { int.Parse(pumpIds) };
  149. }
  150. else
  151. {
  152. throw new ArgumentException("Pump id list not specified!");
  153. }
  154. }
  155. private Dictionary<int, int> InitializePumpSubAddressMapping()
  156. {
  157. var dict = new Dictionary<int, int>();
  158. if (!string.IsNullOrEmpty(pumpSubAddresses))
  159. {
  160. var sequence = pumpSubAddresses.Split(';')
  161. .Select(s => s.Split('='))
  162. .Select(a => new { PumpId = int.Parse(a[0]), SubAddress = int.Parse(a[1]) });
  163. foreach (var pair in sequence)
  164. {
  165. if (!dict.ContainsKey(pair.PumpId))
  166. {
  167. dict.Add(pair.PumpId, pair.SubAddress);
  168. }
  169. }
  170. return dict;
  171. }
  172. else
  173. {
  174. throw new ArgumentException("Pump id and sub address mapping does not exist");
  175. }
  176. }
  177. private Dictionary<int, List<int>> ParsePumpNozzlesList(string pumpNozzles)
  178. {
  179. Dictionary<int, List<int>> pumpNozzlesDict = new Dictionary<int, List<int>>();
  180. if (!string.IsNullOrEmpty(pumpNozzles) && pumpNozzles.Contains(';'))
  181. {
  182. var arr = pumpNozzles.Split(';');
  183. foreach (var subMapping in arr)
  184. {
  185. var pair = new KeyValuePair<int, int>(int.Parse(subMapping.Split('=')[0]), int.Parse(subMapping.Split('=')[1]));
  186. Console.WriteLine($"{pair.Key}, {pair.Value}");
  187. if (!pumpNozzlesDict.ContainsKey(pair.Key))
  188. {
  189. pumpNozzlesDict.Add(pair.Key, new List<int> { pair.Value });
  190. }
  191. else
  192. {
  193. List<int> nozzlesForThisPump;
  194. pumpNozzlesDict.TryGetValue(pair.Key, out nozzlesForThisPump);
  195. if (nozzlesForThisPump != null && !nozzlesForThisPump.Contains(pair.Value))
  196. {
  197. nozzlesForThisPump.Add(pair.Value);
  198. }
  199. }
  200. }
  201. }
  202. else if (!string.IsNullOrEmpty(pumpNozzles) && pumpNozzles.Count(c => c == '=') == 1) // only one pump per serial port
  203. {
  204. try
  205. {
  206. pumpNozzlesDict.Add(
  207. int.Parse(pumpNozzles.Split('=')[0]),
  208. new List<int> { int.Parse(pumpNozzles.Split('=')[1]) });
  209. }
  210. catch (Exception ex)
  211. {
  212. Console.WriteLine(ex);
  213. }
  214. }
  215. else
  216. {
  217. throw new ArgumentException("Wrong mapping between pump and its associated nozzles!");
  218. }
  219. return pumpNozzlesDict;
  220. }
  221. static Dictionary<int, List<int>> ParsePumpSiteNozzleNoList(string pumpSiteNozzleNos)
  222. {
  223. Dictionary<int, List<int>> pumpSiteNozzleNoDict = new Dictionary<int, List<int>>();
  224. if (!string.IsNullOrEmpty(pumpSiteNozzleNos) && pumpSiteNozzleNos.Contains(';'))
  225. {
  226. var arr = pumpSiteNozzleNos.Split(';');
  227. foreach (var subMapping in arr)
  228. {
  229. var pair = new KeyValuePair<int, List<int>>(
  230. int.Parse(subMapping.Split('=')[0]), subMapping.Split('=')[1].Split(',').Select(a => int.Parse(a)).ToList());
  231. Console.WriteLine($"{pair.Key}, {pair.Value}");
  232. if (!pumpSiteNozzleNoDict.ContainsKey(pair.Key))
  233. {
  234. pumpSiteNozzleNoDict.Add(pair.Key, pair.Value);
  235. }
  236. }
  237. }
  238. else if (!string.IsNullOrEmpty(pumpSiteNozzleNos) && pumpSiteNozzleNos.Count(c => c == '=') == 1)
  239. {
  240. try
  241. {
  242. string[] strArr = pumpSiteNozzleNos.Split('=');
  243. pumpSiteNozzleNoDict.Add(
  244. int.Parse(strArr[0]), new List<int> { int.Parse(strArr[1]) });
  245. }
  246. catch (Exception ex)
  247. {
  248. Console.WriteLine(ex);
  249. }
  250. }
  251. else
  252. {
  253. throw new ArgumentException("Wrong mapping between pump and its associated nozzles!");
  254. }
  255. return pumpSiteNozzleNoDict;
  256. }
  257. private Dictionary<int, int> InitializeNozzleLogicIdMapping(string nozzleLogicIds)
  258. {
  259. var dict = new Dictionary<int, int>();
  260. if (!string.IsNullOrEmpty(nozzleLogicIds))
  261. {
  262. var sequence = nozzleLogicIds.Split(';')
  263. .Select(s => s.Split('='))
  264. .Select(a => new { NozzleNo = int.Parse(a[0]), LogicId = int.Parse(a[1]) });
  265. foreach (var pair in sequence)
  266. {
  267. if (!dict.ContainsKey(pair.NozzleNo))
  268. {
  269. Console.WriteLine($"nozzle, logic id: {pair.NozzleNo} - {pair.LogicId}");
  270. dict.Add(pair.NozzleNo, pair.LogicId);
  271. }
  272. }
  273. return dict;
  274. }
  275. else if (!string.IsNullOrEmpty(nozzleLogicIds) && nozzleLogicIds.Count(c => c == '=') == 1)
  276. {
  277. try
  278. {
  279. string[] sequence = nozzleLogicIds.Split('=');
  280. dict.Add(int.Parse(sequence[0]), int.Parse(sequence[1]));
  281. }
  282. catch (Exception ex)
  283. {
  284. Console.WriteLine(ex);
  285. }
  286. return dict;
  287. }
  288. else
  289. {
  290. throw new ArgumentException("Pump id and sub address mapping does not exist");
  291. }
  292. }
  293. private void InitializePumpHandlers()
  294. {
  295. var pumpIdList = GetPumpIdList(pumpIds);
  296. foreach (var item in pumpIdList)
  297. {
  298. var nozzleList = GetNozzleListForPump(item);
  299. var siteNozzleNoList = PumpSiteNozzleNoDict[item];
  300. HengshanPumpHandler pumpHandler = new HengshanPumpHandler(this, $"Pump_{item}", item, nozzleList, siteNozzleNoList);
  301. pumpHandler.OnFuelPriceChangeRequested += PumpHandler_OnFuelPriceChangeRequested;
  302. pumpHandlers.Add(pumpHandler);
  303. }
  304. }
  305. private List<int> GetNozzleListForPump(int pumpId)
  306. {
  307. List<int> nozzles;
  308. PumpNozzlesDict.TryGetValue(pumpId, out nozzles);
  309. return nozzles;
  310. }
  311. private void PumpHandler_OnFuelPriceChangeRequested(object sender, FuelPriceChangeRequestEventArgs e)
  312. {
  313. InfoLog($"Change price, Pump {e.PumpId}, Nozzle {e.NozzleId}, Price {e.Price}");
  314. OnFuelPriceChangeRequested?.Invoke(sender, e);
  315. }
  316. IEnumerator<IFdcPumpController> IEnumerable<IFdcPumpController>.GetEnumerator()
  317. {
  318. return pumpHandlers.GetEnumerator();
  319. }
  320. #endregion
  321. #region IHandler implementation
  322. public void Init(IContext<byte[], CardMessageBase> context)
  323. {
  324. CommIdentity = context.Processor.Communicator.Identity;
  325. _context = context;
  326. }
  327. public string CommIdentity { get; private set; }
  328. public async Task Process(IContext<byte[], CardMessageBase> context)
  329. {
  330. }
  331. private void CheckStatus(CheckCmdRequest request)
  332. {
  333. if (!statusDict.ContainsKey(request.FuelingPoint.PumpNo))
  334. {
  335. var result = statusDict.TryAdd(request.FuelingPoint.PumpNo,
  336. new PumpStateHolder
  337. {
  338. PumpNo = request.FuelingPoint.PumpNo,
  339. NozzleNo = 1,
  340. State = request,
  341. OperationType = LockUnlockOperation.None
  342. });
  343. logger.Info($"Adding FuelingPoint {request.FuelingPoint.PumpNo} to dict");
  344. if (!result)
  345. {
  346. statusDict.TryAdd(request.FuelingPoint.PumpNo, null);
  347. }
  348. }
  349. else
  350. {
  351. PumpStateHolder stateHolder = null;
  352. statusDict.TryGetValue(request.FuelingPoint.PumpNo, out stateHolder);
  353. if (stateHolder != null)
  354. {
  355. logger.Debug($"State holder, PumpNo: {stateHolder.PumpNo}, dispenser state: {stateHolder.State.DispenserState}, " +
  356. $"operation: {stateHolder.OperationType}");
  357. }
  358. if (stateHolder != null && stateHolder.OperationType != LockUnlockOperation.None)
  359. {
  360. logger.Debug($"PumpNo: {request.FuelingPoint.PumpNo}, Last Dispenser State: {stateHolder.State.DispenserState}, " +
  361. $"Current Dispenser State: {request.DispenserState}");
  362. if (stateHolder.State.DispenserState == 3 && request.DispenserState == 2)
  363. {
  364. //Pump is locked due to lock operation
  365. if (stateHolder.OperationType != LockUnlockOperation.None)
  366. {
  367. logger.Info("Locking done!");
  368. stateHolder.State = request; //Update the state
  369. OnLockUnlockCompleted?.Invoke(this, new LockUnlockEventArgs(stateHolder.OperationType, true));
  370. }
  371. }
  372. else if (stateHolder.State.DispenserState == 2 && request.DispenserState == 3)
  373. {
  374. //Pump is unlocked due to unlock operation
  375. if (stateHolder.OperationType != LockUnlockOperation.None)
  376. {
  377. logger.Info($"Unlocking done!");
  378. stateHolder.State = request; //Update the state
  379. OnLockUnlockCompleted?.Invoke(this, new LockUnlockEventArgs(stateHolder.OperationType, true));
  380. }
  381. }
  382. }
  383. else if (stateHolder != null && stateHolder.OperationType == LockUnlockOperation.None)
  384. {
  385. if (stateHolder.State.DispenserState != request.DispenserState)
  386. {
  387. logger.Warn($"Observed a pump state change, {stateHolder.State.DispenserState} -> {request.DispenserState}");
  388. stateHolder.State = request; //Update the state.
  389. }
  390. }
  391. }
  392. }
  393. public void Write(CardMessageBase cardMessage)
  394. {
  395. _context.Outgoing.Write(cardMessage);
  396. }
  397. public async Task<CardMessageBase> WriteAsync(CardMessageBase request, Func<CardMessageBase, CardMessageBase, bool> responseCapture,
  398. int timeout)
  399. {
  400. var resp = await _context.Outgoing.WriteAsync(request, responseCapture, timeout);
  401. return resp;
  402. }
  403. #endregion
  404. #region IEnumerable<IFdcPumpController> implementation
  405. public IEnumerator<IFdcPumpController> GetEnumerator()
  406. {
  407. return pumpHandlers.GetEnumerator();
  408. }
  409. IEnumerator IEnumerable.GetEnumerator()
  410. {
  411. return pumpHandlers.GetEnumerator();
  412. }
  413. #endregion
  414. public void PendMessage(CardMessageBase message)
  415. {
  416. lock (syncObj)
  417. {
  418. queue.Enqueue(message);
  419. }
  420. }
  421. public bool TrySendNextMessage()
  422. {
  423. lock (syncObj)
  424. {
  425. if (queue.Count > 0)
  426. {
  427. DebugLog($"queue count: {queue.Count}");
  428. var message = queue.Dequeue();
  429. Write(message);
  430. return true;
  431. }
  432. }
  433. return false;
  434. }
  435. public void StoreLatestFrameSqNo(int pumpId, byte frameSqNo)
  436. {
  437. var pump = GetPump(pumpId);
  438. if (pump != null)
  439. {
  440. pump.FrameSqNo = frameSqNo;
  441. }
  442. }
  443. public void UpdatePumpState(int pumpId, int logicId, LogicalDeviceState state)
  444. {
  445. var currentPump = GetPump(pumpId);
  446. currentPump?.FirePumpStateChange(state, Convert.ToByte(logicId));
  447. }
  448. public void UpdateFuelingStatus(int pumpId, FdcTransaction fuelingTransaction)
  449. {
  450. var currentPump = GetPump(pumpId);
  451. currentPump?.FireFuelingStatusChange(fuelingTransaction);
  452. }
  453. private HengshanPumpHandler GetPump(int pumpId)
  454. {
  455. return pumpHandlers.FirstOrDefault(p => p.PumpId == pumpId);
  456. }
  457. public void SetRealPrice(int pumpId, int price)
  458. {
  459. var currentPump = GetPump(pumpId);
  460. var nozzle = currentPump?.Nozzles.FirstOrDefault();
  461. if (nozzle != null)
  462. nozzle.RealPriceOnPhysicalPump = price;
  463. }
  464. #region Log methods
  465. private void InfoLog(string info)
  466. {
  467. logger.Info("PayTermHdlr " + info);
  468. }
  469. private void DebugLog(string debugMsg)
  470. {
  471. logger.Debug("PayTermHdlr " + debugMsg);
  472. }
  473. #endregion
  474. }
  475. public class HengshanPayTerminalHanlderGroupConfigV1
  476. {
  477. public string PumpIds { get; set; }
  478. public List<PumpSubAddress> PumpSubAddresses { get; set; }
  479. }
  480. public class HengshanPayTerminalHanlderGroupConfigV2
  481. {
  482. public string PumpIds { get; set; }
  483. public List<PumpSubAddress> PumpSubAddresses { get; set; }
  484. public List<PumpNozzleLogicId> PumpNozzleLogicIds { get; set; }
  485. public List<PumpSiteNozzleNo> PumpSiteNozzleNos { get; set; }
  486. public List<NozzleLogicId> NozzleLogicIds { get; set; }
  487. }
  488. public class PumpSubAddress
  489. {
  490. public byte PumpId { get; set; }
  491. public byte SubAddress { get; set; }
  492. }
  493. public class PumpNozzleLogicId
  494. {
  495. public byte PumpId { get; set; }
  496. public string LogicIds { get; set; }
  497. }
  498. public class PumpSiteNozzleNo
  499. {
  500. public byte PumpId { get; set; }
  501. public string SiteNozzleNos { get; set; }
  502. }
  503. public class NozzleLogicId
  504. {
  505. public byte NozzleNo { get; set; }
  506. public byte LogicId { get; set; }
  507. }
  508. }