HengshanPayTermHandler.cs 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131
  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. using Edge.Core.Core.database;
  17. using Edge.Core.Domain.FccStationInfo.Output;
  18. using Edge.Core.Domain.FccNozzleInfo;
  19. using Edge.Core.Domain.FccNozzleInfo.Output;
  20. using System.Net.Sockets;
  21. using Edge.Core.Domain.FccOrderInfo;
  22. using Microsoft.EntityFrameworkCore;
  23. using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities;
  24. using static Microsoft.AspNetCore.Hosting.Internal.HostingApplication;
  25. using HengshanPaymentTerminal.Mqtt.Request;
  26. using HengshanPaymentTerminal.Http;
  27. using HengshanPaymentTerminal.Http.Request;
  28. using System.Text.Json;
  29. using Newtonsoft.Json;
  30. using HengshanPaymentTerminal.Http.Response;
  31. namespace HengshanPaymentTerminal
  32. {
  33. /// <summary>
  34. /// Handler that communicates directly with the Hengshan Payment Terminal for card handling and pump handling via serial port.
  35. /// </summary>
  36. [MetaPartsDescriptor(
  37. "lang-zh-cn:恒山IC卡终端(UI板) App lang-en-us:Hengshan IC card terminal (UI Board)",
  38. "lang-zh-cn:用于与UI板通讯控制加油机" +
  39. "lang-en-us:Used for terminal communication to control pumps",
  40. new[]
  41. {
  42. "lang-zh-cn:恒山IC卡终端lang-en-us:HengshanICTerminal"
  43. })]
  44. public class HengshanPayTermHandler : IEnumerable<IFdcPumpController>, IDeviceHandler<byte[], CommonMessage>
  45. {
  46. #region Fields
  47. private string pumpIds;
  48. private string pumpSubAddresses;
  49. private string pumpNozzles;
  50. private string pumpSiteNozzleNos;
  51. private string nozzleLogicIds;
  52. private IContext<byte[], CommonMessage> _context;
  53. private List<HengshanPumpHandler> pumpHandlers = new List<HengshanPumpHandler>();
  54. public Queue<CardMessageBase> queue = new Queue<CardMessageBase>();
  55. public Queue<CommonMessage> commonQueue = new Queue<CommonMessage>();
  56. private object syncObj = new object();
  57. private ConcurrentDictionary<int, PumpStateHolder> statusDict = new ConcurrentDictionary<int, PumpStateHolder>();
  58. public ConcurrentDictionary<int, PumpStateHolder> PumpStatusDict => statusDict;
  59. private Dictionary<int, int> pumpIdSubAddressDict;
  60. public Dictionary<int, List<int>> PumpNozzlesDict { get; private set; }
  61. public Dictionary<int, int> NozzleLogicIdDict { get; private set; }
  62. public Dictionary<int, List<int>> PumpSiteNozzleNoDict { get; private set; }
  63. public MysqlDbContext MysqlDbContext { get; private set; }
  64. public StationInfo stationInfo { get; set; }
  65. public List<DetailsNozzleInfoOutput> nozzleInfoList { get; private set; }
  66. public TcpClient? client { get; set; }
  67. private readonly ConcurrentDictionary<string,TaskCompletionSource<CommonMessage>> _tcsDictionary = new ConcurrentDictionary<string, TaskCompletionSource<CommonMessage>>();
  68. private byte frame = 0x00;
  69. private object lockFrame = new object();
  70. private readonly IHttpClientUtil httpClientUtil;
  71. #endregion
  72. #region Logger
  73. private static NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("IPosPlusApp");
  74. #endregion
  75. #region Constructor
  76. //private static List<object> ResolveCtorMetaPartsConfigCompatibility(string incompatibleCtorParamsJsonStr)
  77. //{
  78. // var jsonParams = JsonDocument.Parse(incompatibleCtorParamsJsonStr).RootElement.EnumerateArray().ToArray();
  79. // //sample: "UITemplateVersion":"1.0"
  80. // string uiTemplateVersionRegex = @"(?<=""UITemplateVersion""\:\"").+?(?="")";
  81. // var match = Regex.Match(jsonParams.First().GetRawText(), uiTemplateVersionRegex, RegexOptions.IgnoreCase | RegexOptions.Multiline);
  82. // if (match.Success)
  83. // {
  84. // var curVersion = match.Value;
  85. // if (curVersion == "1.0")
  86. // {
  87. // var existsAppConfigV1 = JsonSerializer.Deserialize(jsonParams.First().GetRawText(), typeof(HengshanPayTerminalHanlderGroupConfigV1));
  88. // }
  89. // else
  90. // {
  91. // }
  92. // }
  93. // return null;
  94. //}
  95. [ParamsJsonSchemas("TermHandlerGroupCtorParamsJsonSchemas")]
  96. public HengshanPayTermHandler(HengshanPayTerminalHanlderGroupConfigV2 config)
  97. : this(config.PumpIds,
  98. string.Join(";", config.PumpSubAddresses.Select(m => $"{m.PumpId}={m.SubAddress}")),
  99. string.Join(";", config.PumpNozzleLogicIds.Select(m => $"{m.PumpId}={m.LogicIds}")),
  100. string.Join(";", config.PumpSiteNozzleNos.Select(m => $"{m.PumpId}={m.SiteNozzleNos}")),
  101. string.Join(";", config.NozzleLogicIds.Select(m => $"{m.NozzleNo}={m.LogicId}")))
  102. //clientUtil)
  103. {
  104. }
  105. public HengshanPayTermHandler(
  106. string pumpIds,
  107. string pumpSubAddresses,
  108. string pumpNozzles,
  109. string pumpSiteNozzleNos,
  110. string nozzleLogicIds)
  111. //IHttpClientUtil clientUtil)
  112. {
  113. this.pumpIds = pumpIds;
  114. this.pumpSubAddresses = pumpSubAddresses;
  115. this.pumpNozzles = pumpNozzles;
  116. this.pumpSiteNozzleNos = pumpSiteNozzleNos;
  117. this.nozzleLogicIds = nozzleLogicIds;
  118. this.MysqlDbContext = new MysqlDbContext();
  119. this.httpClientUtil = new HttpClientUtils();
  120. GetInfo();
  121. AssociatedPumpIds = GetPumpIdList(pumpIds);
  122. pumpIdSubAddressDict = InitializePumpSubAddressMapping();
  123. PumpNozzlesDict = ParsePumpNozzlesList(pumpNozzles);
  124. PumpSiteNozzleNoDict = ParsePumpSiteNozzleNoList(pumpSiteNozzleNos);
  125. NozzleLogicIdDict = InitializeNozzleLogicIdMapping(nozzleLogicIds);
  126. InitializePumpHandlers();
  127. }
  128. #endregion
  129. public void OnFdcServerInit(Dictionary<string, object> parameters)
  130. {
  131. logger.Info("OnFdcServerInit called");
  132. if (parameters.ContainsKey("LastPriceChange"))
  133. {
  134. // nozzle logical id:rawPrice
  135. var lastPriceChanges = parameters["LastPriceChange"] as Dictionary<byte, int>;
  136. foreach (var priceChange in lastPriceChanges)
  137. {
  138. }
  139. }
  140. }
  141. #region Event handler
  142. public event EventHandler<TerminalMessageEventArgs> OnTerminalMessageReceived;
  143. public event EventHandler<TotalizerDataEventArgs> OnTotalizerReceived;
  144. public event EventHandler<FuelPriceChangeRequestEventArgs> OnFuelPriceChangeRequested;
  145. public event EventHandler<FuelPriceDownloadRequestedEventArgs> OnTerminalFuelPriceDownloadRequested;
  146. public event EventHandler<CheckCommandEventArgs> OnCheckCommandReceived;
  147. public event EventHandler<LockUnlockEventArgs> OnLockUnlockCompleted;
  148. #endregion
  149. #region Properties
  150. public List<int> AssociatedPumpIds { get; private set; }
  151. public IContext<byte[], CommonMessage> Context
  152. {
  153. get { return _context; }
  154. }
  155. public string PumpIdList => pumpIds;
  156. //public LockUnlockOperation LockUnlockOperationType { get; set; } = LockUnlockOperation.Undefined;
  157. #endregion
  158. #region Methods
  159. public int GetSubAddressForPump(int pumpId)
  160. {
  161. return pumpIdSubAddressDict.First(d => d.Key == pumpId).Value;
  162. }
  163. private List<int> GetPumpIdList(string pumpIds)
  164. {
  165. var pumpIdList = new List<int>();
  166. if (!string.IsNullOrEmpty(pumpIds) && pumpIds.Contains(',')) //multiple pumps per serial port, Hengshan TQC pump
  167. {
  168. var arr = pumpIds.Split(',');
  169. foreach (var item in arr)
  170. {
  171. pumpIdList.Add(int.Parse(item));
  172. }
  173. return pumpIdList;
  174. }
  175. else if (!string.IsNullOrEmpty(pumpIds) && pumpIds.Length == 1 || pumpIds.Length == 2) //only 1 pump per serial port, Hengshan pump
  176. {
  177. return new List<int> { int.Parse(pumpIds) };
  178. }
  179. else
  180. {
  181. throw new ArgumentException("Pump id list not specified!");
  182. }
  183. }
  184. private Dictionary<int, int> InitializePumpSubAddressMapping()
  185. {
  186. var dict = new Dictionary<int, int>();
  187. if (!string.IsNullOrEmpty(pumpSubAddresses))
  188. {
  189. var sequence = pumpSubAddresses.Split(';')
  190. .Select(s => s.Split('='))
  191. .Select(a => new { PumpId = int.Parse(a[0]), SubAddress = int.Parse(a[1]) });
  192. foreach (var pair in sequence)
  193. {
  194. if (!dict.ContainsKey(pair.PumpId))
  195. {
  196. dict.Add(pair.PumpId, pair.SubAddress);
  197. }
  198. }
  199. return dict;
  200. }
  201. else
  202. {
  203. throw new ArgumentException("Pump id and sub address mapping does not exist");
  204. }
  205. }
  206. private Dictionary<int, List<int>> ParsePumpNozzlesList(string pumpNozzles)
  207. {
  208. Dictionary<int, List<int>> pumpNozzlesDict = new Dictionary<int, List<int>>();
  209. if (!string.IsNullOrEmpty(pumpNozzles) && pumpNozzles.Contains(';'))
  210. {
  211. var arr = pumpNozzles.Split(';');
  212. foreach (var subMapping in arr)
  213. {
  214. var pair = new KeyValuePair<int, int>(int.Parse(subMapping.Split('=')[0]), int.Parse(subMapping.Split('=')[1]));
  215. Console.WriteLine($"{pair.Key}, {pair.Value}");
  216. if (!pumpNozzlesDict.ContainsKey(pair.Key))
  217. {
  218. pumpNozzlesDict.Add(pair.Key, new List<int> { pair.Value });
  219. }
  220. else
  221. {
  222. List<int> nozzlesForThisPump;
  223. pumpNozzlesDict.TryGetValue(pair.Key, out nozzlesForThisPump);
  224. if (nozzlesForThisPump != null && !nozzlesForThisPump.Contains(pair.Value))
  225. {
  226. nozzlesForThisPump.Add(pair.Value);
  227. }
  228. }
  229. }
  230. }
  231. else if (!string.IsNullOrEmpty(pumpNozzles) && pumpNozzles.Count(c => c == '=') == 1) // only one pump per serial port
  232. {
  233. try
  234. {
  235. pumpNozzlesDict.Add(
  236. int.Parse(pumpNozzles.Split('=')[0]),
  237. new List<int> { int.Parse(pumpNozzles.Split('=')[1]) });
  238. }
  239. catch (Exception ex)
  240. {
  241. Console.WriteLine(ex);
  242. }
  243. }
  244. else
  245. {
  246. throw new ArgumentException("Wrong mapping between pump and its associated nozzles!");
  247. }
  248. return pumpNozzlesDict;
  249. }
  250. static Dictionary<int, List<int>> ParsePumpSiteNozzleNoList(string pumpSiteNozzleNos)
  251. {
  252. Dictionary<int, List<int>> pumpSiteNozzleNoDict = new Dictionary<int, List<int>>();
  253. if (!string.IsNullOrEmpty(pumpSiteNozzleNos) && pumpSiteNozzleNos.Contains(';'))
  254. {
  255. var arr = pumpSiteNozzleNos.Split(';');
  256. foreach (var subMapping in arr)
  257. {
  258. var pair = new KeyValuePair<int, List<int>>(
  259. int.Parse(subMapping.Split('=')[0]), subMapping.Split('=')[1].Split(',').Select(a => int.Parse(a)).ToList());
  260. Console.WriteLine($"{pair.Key}, {pair.Value}");
  261. if (!pumpSiteNozzleNoDict.ContainsKey(pair.Key))
  262. {
  263. pumpSiteNozzleNoDict.Add(pair.Key, pair.Value);
  264. }
  265. }
  266. }
  267. else if (!string.IsNullOrEmpty(pumpSiteNozzleNos) && pumpSiteNozzleNos.Count(c => c == '=') == 1)
  268. {
  269. try
  270. {
  271. string[] strArr = pumpSiteNozzleNos.Split('=');
  272. pumpSiteNozzleNoDict.Add(
  273. int.Parse(strArr[0]), new List<int> { int.Parse(strArr[1]) });
  274. }
  275. catch (Exception ex)
  276. {
  277. Console.WriteLine(ex);
  278. }
  279. }
  280. else
  281. {
  282. throw new ArgumentException("Wrong mapping between pump and its associated nozzles!");
  283. }
  284. return pumpSiteNozzleNoDict;
  285. }
  286. private Dictionary<int, int> InitializeNozzleLogicIdMapping(string nozzleLogicIds)
  287. {
  288. var dict = new Dictionary<int, int>();
  289. if (!string.IsNullOrEmpty(nozzleLogicIds))
  290. {
  291. var sequence = nozzleLogicIds.Split(';')
  292. .Select(s => s.Split('='))
  293. .Select(a => new { NozzleNo = int.Parse(a[0]), LogicId = int.Parse(a[1]) });
  294. foreach (var pair in sequence)
  295. {
  296. if (!dict.ContainsKey(pair.NozzleNo))
  297. {
  298. Console.WriteLine($"nozzle, logic id: {pair.NozzleNo} - {pair.LogicId}");
  299. dict.Add(pair.NozzleNo, pair.LogicId);
  300. }
  301. }
  302. return dict;
  303. }
  304. else if (!string.IsNullOrEmpty(nozzleLogicIds) && nozzleLogicIds.Count(c => c == '=') == 1)
  305. {
  306. try
  307. {
  308. string[] sequence = nozzleLogicIds.Split('=');
  309. dict.Add(int.Parse(sequence[0]), int.Parse(sequence[1]));
  310. }
  311. catch (Exception ex)
  312. {
  313. Console.WriteLine(ex);
  314. }
  315. return dict;
  316. }
  317. else
  318. {
  319. throw new ArgumentException("Pump id and sub address mapping does not exist");
  320. }
  321. }
  322. private void InitializePumpHandlers()
  323. {
  324. var pumpIdList = GetPumpIdList(pumpIds);
  325. foreach (var item in pumpIdList)
  326. {
  327. var nozzleList = GetNozzleListForPump(item);
  328. var siteNozzleNoList = PumpSiteNozzleNoDict[item];
  329. HengshanPumpHandler pumpHandler = new HengshanPumpHandler(this, $"Pump_{item}", item, nozzleList, siteNozzleNoList);
  330. pumpHandler.OnFuelPriceChangeRequested += PumpHandler_OnFuelPriceChangeRequested;
  331. pumpHandlers.Add(pumpHandler);
  332. }
  333. }
  334. private List<int> GetNozzleListForPump(int pumpId)
  335. {
  336. List<int> nozzles;
  337. PumpNozzlesDict.TryGetValue(pumpId, out nozzles);
  338. return nozzles;
  339. }
  340. private void PumpHandler_OnFuelPriceChangeRequested(object sender, FuelPriceChangeRequestEventArgs e)
  341. {
  342. InfoLog($"Change price, Pump {e.PumpId}, Nozzle {e.NozzleId}, Price {e.Price}");
  343. OnFuelPriceChangeRequested?.Invoke(sender, e);
  344. }
  345. IEnumerator<IFdcPumpController> IEnumerable<IFdcPumpController>.GetEnumerator()
  346. {
  347. return pumpHandlers.GetEnumerator();
  348. }
  349. #endregion
  350. #region IHandler implementation
  351. public void Init(IContext<byte[], CommonMessage> context)
  352. {
  353. CommIdentity = context.Processor.Communicator.Identity;
  354. _context = context;
  355. }
  356. public string CommIdentity { get; private set; }
  357. public async Task Process(IContext<byte[], CommonMessage> context)
  358. {
  359. switch(context.Incoming.Message.Handle)
  360. {
  361. //订单
  362. case 0x18:
  363. {
  364. //添加或修改数据库订单
  365. OrderFromMachine orderFromMachine = (OrderFromMachine)context.Incoming.Message;
  366. FccOrderInfo fccOrderInfo = UpLoadOrder(orderFromMachine);
  367. logger.Info($"receive order from machine,database had change");
  368. CreateTransaction(fccOrderInfo);
  369. break;
  370. }
  371. //普通应答
  372. case 0x55:
  373. {
  374. CommonAnswerBack commonAnswerBack = (CommonAnswerBack)context.Incoming.Message;
  375. if (commonAnswerBack.Command == 0x63) //二维码回复
  376. {
  377. byte[] keyBytes = { commonAnswerBack.Command, (byte)commonAnswerBack.NozzleNum };
  378. var key = BitConverter.ToString(keyBytes).Replace("-", "");
  379. if (_tcsDictionary.TryGetValue(key, out var value))
  380. {
  381. value.SetResult(commonAnswerBack);
  382. }
  383. else
  384. {
  385. logger.Info($"qrcode response:can not get tcs for dictionary");
  386. }
  387. }
  388. break;
  389. }
  390. // 授权回复
  391. case 0x65:
  392. {
  393. AuthorizationResponse authorizationResponse = (AuthorizationResponse)context.Incoming.Message;
  394. byte[] keyBytes = { authorizationResponse.Handle, (byte)authorizationResponse.NozzleNum };
  395. var key = BitConverter.ToString(keyBytes).Replace("-", "");
  396. if (_tcsDictionary.TryGetValue(key, out var value))
  397. {
  398. value.SetResult(authorizationResponse);
  399. }
  400. else
  401. {
  402. logger.Info($"authorization response:can not get tcs for dictionary");
  403. }
  404. break;
  405. }
  406. // 取消授权回复
  407. case 0x66:
  408. {
  409. UnAhorizationResponse unauthorizationResponse = (UnAhorizationResponse)context.Incoming.Message;
  410. byte[] keyBytes = { unauthorizationResponse.Handle, (byte)unauthorizationResponse.NozzleNum };
  411. var key = BitConverter.ToString(keyBytes).Replace("-", "");
  412. if (_tcsDictionary.TryGetValue(key, out var value))
  413. {
  414. value.SetResult(unauthorizationResponse);
  415. }
  416. else
  417. {
  418. logger.Info($"unauthorization response:can not get tcs for dictionary");
  419. }
  420. break;
  421. }
  422. }
  423. context.Outgoing.Write(context.Incoming.Message);
  424. }
  425. private void CheckStatus(CheckCmdRequest request)
  426. {
  427. if (!statusDict.ContainsKey(request.FuelingPoint.PumpNo))
  428. {
  429. var result = statusDict.TryAdd(request.FuelingPoint.PumpNo,
  430. new PumpStateHolder
  431. {
  432. PumpNo = request.FuelingPoint.PumpNo,
  433. NozzleNo = 1,
  434. State = request,
  435. OperationType = LockUnlockOperation.None
  436. });
  437. logger.Info($"Adding FuelingPoint {request.FuelingPoint.PumpNo} to dict");
  438. if (!result)
  439. {
  440. statusDict.TryAdd(request.FuelingPoint.PumpNo, null);
  441. }
  442. }
  443. else
  444. {
  445. PumpStateHolder stateHolder = null;
  446. statusDict.TryGetValue(request.FuelingPoint.PumpNo, out stateHolder);
  447. if (stateHolder != null)
  448. {
  449. logger.Debug($"State holder, PumpNo: {stateHolder.PumpNo}, dispenser state: {stateHolder.State.DispenserState}, " +
  450. $"operation: {stateHolder.OperationType}");
  451. }
  452. if (stateHolder != null && stateHolder.OperationType != LockUnlockOperation.None)
  453. {
  454. logger.Debug($"PumpNo: {request.FuelingPoint.PumpNo}, Last Dispenser State: {stateHolder.State.DispenserState}, " +
  455. $"Current Dispenser State: {request.DispenserState}");
  456. if (stateHolder.State.DispenserState == 3 && request.DispenserState == 2)
  457. {
  458. //Pump is locked due to lock operation
  459. if (stateHolder.OperationType != LockUnlockOperation.None)
  460. {
  461. logger.Info("Locking done!");
  462. stateHolder.State = request; //Update the state
  463. OnLockUnlockCompleted?.Invoke(this, new LockUnlockEventArgs(stateHolder.OperationType, true));
  464. }
  465. }
  466. else if (stateHolder.State.DispenserState == 2 && request.DispenserState == 3)
  467. {
  468. //Pump is unlocked due to unlock operation
  469. if (stateHolder.OperationType != LockUnlockOperation.None)
  470. {
  471. logger.Info($"Unlocking done!");
  472. stateHolder.State = request; //Update the state
  473. OnLockUnlockCompleted?.Invoke(this, new LockUnlockEventArgs(stateHolder.OperationType, true));
  474. }
  475. }
  476. }
  477. else if (stateHolder != null && stateHolder.OperationType == LockUnlockOperation.None)
  478. {
  479. if (stateHolder.State.DispenserState != request.DispenserState)
  480. {
  481. logger.Warn($"Observed a pump state change, {stateHolder.State.DispenserState} -> {request.DispenserState}");
  482. stateHolder.State = request; //Update the state.
  483. }
  484. }
  485. }
  486. }
  487. public void Write(CommonMessage cardMessage)
  488. {
  489. _context.Outgoing.Write(cardMessage);
  490. }
  491. public async Task<CommonMessage> WriteAsync(CommonMessage request, Func<CommonMessage, CommonMessage, bool> responseCapture,
  492. int timeout)
  493. {
  494. var resp = await _context.Outgoing.WriteAsync(request, responseCapture, timeout);
  495. return resp;
  496. }
  497. #endregion
  498. #region IEnumerable<IFdcPumpController> implementation
  499. public IEnumerator<IFdcPumpController> GetEnumerator()
  500. {
  501. return pumpHandlers.GetEnumerator();
  502. }
  503. IEnumerator IEnumerable.GetEnumerator()
  504. {
  505. return pumpHandlers.GetEnumerator();
  506. }
  507. #endregion
  508. public void PendMessage(CardMessageBase message)
  509. {
  510. lock (syncObj)
  511. {
  512. queue.Enqueue(message);
  513. }
  514. }
  515. public bool TrySendNextMessage()
  516. {
  517. lock (syncObj)
  518. {
  519. if (queue.Count > 0)
  520. {
  521. DebugLog($"queue count: {queue.Count}");
  522. var message = commonQueue.Dequeue();
  523. Write(message);
  524. return true;
  525. }
  526. }
  527. return false;
  528. }
  529. public void StoreLatestFrameSqNo(int pumpId, byte frameSqNo)
  530. {
  531. var pump = GetPump(pumpId);
  532. if (pump != null)
  533. {
  534. pump.FrameSqNo = frameSqNo;
  535. }
  536. }
  537. public void UpdatePumpState(int pumpId, int logicId, LogicalDeviceState state)
  538. {
  539. var currentPump = GetPump(pumpId);
  540. currentPump?.FirePumpStateChange(state, Convert.ToByte(logicId));
  541. }
  542. public void UpdateFuelingStatus(int pumpId, FdcTransaction fuelingTransaction)
  543. {
  544. var currentPump = GetPump(pumpId);
  545. currentPump?.FireFuelingStatusChange(fuelingTransaction);
  546. }
  547. private HengshanPumpHandler GetPump(int pumpId)
  548. {
  549. return pumpHandlers.FirstOrDefault(p => p.PumpId == pumpId);
  550. }
  551. public void SetRealPrice(int pumpId, int price)
  552. {
  553. var currentPump = GetPump(pumpId);
  554. var nozzle = currentPump?.Nozzles.FirstOrDefault();
  555. if (nozzle != null)
  556. nozzle.RealPriceOnPhysicalPump = price;
  557. }
  558. #region Log methods
  559. private void InfoLog(string info)
  560. {
  561. logger.Info("PayTermHdlr " + info);
  562. }
  563. private void DebugLog(string debugMsg)
  564. {
  565. logger.Debug("PayTermHdlr " + debugMsg);
  566. }
  567. #endregion
  568. #region 二维码加油机相关方法
  569. /// <summary>
  570. /// 获取站点信息
  571. /// </summary>
  572. private void GetInfo()
  573. {
  574. Edge.Core.Domain.FccStationInfo.FccStationInfo? fccStationInfo = MysqlDbContext.FccStationInfos.FirstOrDefault();
  575. if(fccStationInfo != null) stationInfo = new StationInfo(fccStationInfo);
  576. nozzleInfoList = MysqlDbContext.NozzleInfos.ToList().Select(n => new DetailsNozzleInfoOutput(n)).ToList();
  577. }
  578. /// <summary>
  579. /// 发送二维码信息给油机
  580. /// </summary>
  581. /// <param name="tcpClient"></param>
  582. public async void SendQRCodeAsync()
  583. {
  584. string? smallProgram = stationInfo?.SmallProgram;
  585. if (smallProgram == null)
  586. {
  587. logger.Info($"can not get smallProgram link");
  588. return;
  589. }
  590. System.Net.EndPoint? remoteEndPoint = this.client?.Client.RemoteEndPoint;
  591. if (remoteEndPoint == null)
  592. {
  593. logger.Info($"can not get client");
  594. return;
  595. }
  596. string[] remoteAddr = remoteEndPoint.ToString().Split(":");
  597. string ip = remoteAddr[0];
  598. List<DetailsNozzleInfoOutput> nozzles = nozzleInfoList.FindAll(nozzle => nozzle.Ip == ip);
  599. foreach (var item in nozzles)
  600. {
  601. List<Byte> list = new List<Byte>();
  602. byte[] commandAndNozzle = { 0x63, (byte)item.NozzleNum };
  603. string qrCode = smallProgram + "/" + item.NozzleNum;
  604. byte[] qrCodeBytes = Encoding.ASCII.GetBytes(qrCode);
  605. list.AddRange(commandAndNozzle);
  606. list.Add((byte)qrCodeBytes.Length);
  607. list.AddRange(qrCodeBytes);
  608. byte[] sendBytes = content2data(list.ToArray(),null);
  609. await SendRequestToMachine("发送二维码", BitConverter.ToString(commandAndNozzle).Replace("-", ""), sendBytes);
  610. }
  611. }
  612. /// <summary>
  613. /// 发送实付金额给油机
  614. /// </summary>
  615. /// <param name="orderInfo"></param>
  616. public async void SendActuallyPaid(FccOrderInfo orderInfo)
  617. {
  618. List<Byte> list = new List<Byte>();
  619. byte[] commandAndNozzle = { 0x19, (byte)orderInfo.NozzleNum };
  620. byte[] ttcBytes = NumberToByteArrayWithPadding(orderInfo.Ttc, 4);
  621. byte[] amountPayableBytes = FormatDecimal(orderInfo.AmountPayable ?? orderInfo.Amount);
  622. list.AddRange(commandAndNozzle); //添加命令字和枪号
  623. list.AddRange(ttcBytes); //添加流水号
  624. list.Add(0x21); //由fcc推送实付金额表示该订单是二维码小程序支付的
  625. list.AddRange(amountPayableBytes); //添加实付金额
  626. //添加3位交易金额1,3位交易金额2,2位优惠规则代码,10位卡应用号,4位消息鉴别码
  627. list.AddRange(new byte[] { 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 });
  628. byte[] sendBytes = content2data(list.ToArray(), null);
  629. await SendRequestToMachine("发送实付金额", BitConverter.ToString(commandAndNozzle).Replace("-", ""), sendBytes);
  630. }
  631. public async Task<CommonMessage> SendAuthorization(MqttAuthorizationRequest request)
  632. {
  633. List<Byte> list = new List<Byte>();
  634. byte[] commandAndNozzle = { 0x65, (byte)request.NozzleNum };
  635. byte[] authorizationTimeBytes = ConvertDateTimeToByteArray(request.AuthorizationTime);
  636. //将小数点后移两位,因为油机只支持两位小数点,这边传过去的3位字节转为int后取后两位为十分位和百分位
  637. int value = (int)request.Value * 100;
  638. byte[] valueBytes = NumberToByteArrayWithPadding(value, 3);
  639. list.AddRange(commandAndNozzle);
  640. list.AddRange(authorizationTimeBytes);
  641. list.Add((byte)request.AuthorizationType);
  642. list.AddRange(valueBytes);
  643. byte[] sendBytes = content2data(list.ToArray(), null);
  644. return await SendRequestToMachine("发送授权请求", BitConverter.ToString(commandAndNozzle).Replace("-", ""), sendBytes);
  645. }
  646. public async Task<CommonMessage> SendUnAuthorizartion(MqttUnAhorizationRequest request)
  647. {
  648. List<Byte> list = new List<Byte>();
  649. byte[] commandAndNozzle = { 0x66, (byte)request.NozzleNum };
  650. byte[] authorizationTimeBytes = ConvertDateTimeToByteArray(request.AuthorizationTime);
  651. byte[] ttcBytes = NumberToByteArrayWithPadding(request.Ttc, 4);
  652. list.AddRange(commandAndNozzle);
  653. list.AddRange(authorizationTimeBytes);
  654. list.AddRange(ttcBytes);
  655. byte[] sendBytes = content2data(list.ToArray(), null);
  656. return await SendRequestToMachine("发送取消授权请求", BitConverter.ToString(commandAndNozzle).Replace("-", ""), sendBytes);
  657. }
  658. public void SetTcpClinet(TcpClient? tcpClient)
  659. {
  660. this.client = tcpClient;
  661. }
  662. /// <summary>
  663. /// 发送消息到油机,3秒的超时,重试三次
  664. /// </summary>
  665. /// <param name="sendTag">发送的消息类型,用于日志记录</param>
  666. /// <param name="sendKey">发送的消息key,用于存储 TaskCompletionSource</param>
  667. /// <param name="requestBytes">实际发送消息</param>
  668. /// <returns></returns>
  669. /// <exception cref="TimeoutException"></exception>
  670. private async Task<CommonMessage> SendRequestToMachine(string sendTag,string sendKey, byte[] requestBytes)
  671. {
  672. int retryCount = 0;
  673. while(retryCount < 3)
  674. {
  675. var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
  676. bool isAdd = _tcsDictionary.TryAdd(sendKey, new TaskCompletionSource<CommonMessage>());
  677. logger.Info($"{sendTag}: add request {sendKey} to dic is {isAdd}");
  678. client?.Client.Send(requestBytes);
  679. try
  680. {
  681. TaskCompletionSource<CommonMessage>? value;
  682. TaskCompletionSource<CommonMessage> tcs;
  683. if(_tcsDictionary.TryGetValue(sendKey, out value))
  684. {
  685. tcs = value;
  686. } else
  687. {
  688. tcs = new TaskCompletionSource<CommonMessage>();
  689. }
  690. CommonMessage response = await tcs.Task.WaitAsync(cts.Token);
  691. return response;
  692. } catch (OperationCanceledException)
  693. {
  694. retryCount++;
  695. logger.Info($"{sendTag}: time out,retrying... ({retryCount} / 3)");
  696. } finally
  697. {
  698. if(retryCount >= 3)
  699. {
  700. logger.Info($"{sendTag}: is time out add retry 3 time");
  701. _tcsDictionary.TryRemove(sendKey,out _);
  702. }
  703. }
  704. }
  705. return new ErrorMessage()
  706. {
  707. IsError = true,
  708. ErrorMessage = $"{sendTag}: can not receive response after 3 retries"
  709. };
  710. }
  711. /// <summary>
  712. /// 添加或修改订单
  713. /// </summary>
  714. /// <param name="order">接收到油机的订单信息</param>
  715. /// <returns></returns>
  716. public FccOrderInfo UpLoadOrder(OrderFromMachine order)
  717. {
  718. //接收到油机发送过来的订单信息
  719. OrderFromMachine orderFromMachine = (OrderFromMachine)order;
  720. FccOrderInfo orderByMessage = orderFromMachine.ToComponent();
  721. /** 根据枪号+流水号+授权时间来确定订单,因为冷启动后流水号会从头开始计算
  722. * 后支付时直接将数据库直接插入
  723. * 预支付时由于是云端先创建订单,发起授权响应成功后会插入数据库,响应成功时会回复授权时间,枪号,流水号
  724. */
  725. FccOrderInfo? fccOrderInfo = MysqlDbContext.fccOrderInfos
  726. .Where(order =>
  727. order.NozzleNum == orderFromMachine.nozzleNum && order.Ttc == orderFromMachine.ttc
  728. && order.AuthorizationTime == orderFromMachine.dispenserTime)
  729. .FirstOrDefault();
  730. if (fccOrderInfo == null)
  731. {
  732. logger.Info($"receive order from machine,find order from database is null");
  733. MysqlDbContext.fccOrderInfos.Add(orderByMessage);
  734. MysqlDbContext.SaveChanges();
  735. return orderByMessage;
  736. }
  737. else
  738. {
  739. logger.Info($"receive order from machine,padding data right now");
  740. orderFromMachine.PaddingAuthorizationOrderData(fccOrderInfo);
  741. MysqlDbContext.SaveChanges();
  742. return fccOrderInfo;
  743. }
  744. }
  745. private async void CreateTransaction(FccOrderInfo fccOrderInfo)
  746. {
  747. CreateTransaction createTransaction = new CreateTransaction(fccOrderInfo,stationInfo.SecretId);
  748. logger.Info($"create transaction,type is {createTransaction.type}");
  749. HttpResponseMessage httpResponseMessage = await httpClientUtil.CreateTransaction(JsonConvert.SerializeObject(createTransaction));
  750. Response<long>? response = JsonConvert.DeserializeObject<Response<long>>(await httpResponseMessage.Content.ReadAsStringAsync());
  751. logger.Info($"reveice create transaction response:{JsonConvert.SerializeObject(response)}");
  752. // 后支付填充云端id
  753. if(response != null && createTransaction.type == 2)
  754. {
  755. FccOrderInfo? currentOrder = MysqlDbContext.fccOrderInfos
  756. .Where(order =>
  757. order.NozzleNum == fccOrderInfo.NozzleNum && order.Ttc == fccOrderInfo.Ttc
  758. && order.AuthorizationTime == fccOrderInfo.AuthorizationTime)
  759. .FirstOrDefault();
  760. if(currentOrder != null)
  761. {
  762. currentOrder.CloundOrderId = response.data;
  763. MysqlDbContext.SaveChanges();
  764. }
  765. }
  766. }
  767. /// <summary>
  768. /// 传入有效数据,拼接为要发送给油机包
  769. /// </summary>
  770. /// <param name="content"></param>
  771. /// <returns></returns>
  772. public byte[] content2data(byte[] content,byte? sendFrame)
  773. {
  774. List<byte> list = new List<byte>();
  775. //目标地址,源地址,帧号
  776. byte frameNo = 0x00;
  777. if(sendFrame == null)
  778. {
  779. lock (lockFrame)
  780. {
  781. if (frame == 0x3f)
  782. {
  783. frameNo = 0x00;
  784. }
  785. else
  786. {
  787. frameNo = (byte)(frame + 1);
  788. }
  789. }
  790. } else
  791. {
  792. frameNo = sendFrame.Value;
  793. }
  794. byte[] head = new byte[] { 0xFF, 0xE0, frameNo };
  795. byte[] length = Int2BCD(content.Length);
  796. list.AddRange(head);
  797. list.AddRange(length);
  798. list.AddRange(content);
  799. byte[] crc = HengshanCRC16.ComputeChecksumToBytes(list.ToArray());
  800. list.AddRange(crc);
  801. List<byte> addFAList = addFA(list);
  802. addFAList.Insert(0, 0xFA);
  803. return addFAList.ToArray();
  804. }
  805. public int Bcd2Int(byte byte1, byte byte2)
  806. {
  807. // 提取第一个字节的高四位和低四位
  808. int digit1 = (byte1 >> 4) & 0x0F; // 高四位
  809. int digit2 = byte1 & 0x0F; // 低四位
  810. // 提取第二个字节的高四位和低四位
  811. int digit3 = (byte2 >> 4) & 0x0F; // 高四位
  812. int digit4 = byte2 & 0x0F; // 低四位
  813. // 组合成一个整数
  814. int result = digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4;
  815. return result;
  816. }
  817. public byte[] Int2BCD(int number)
  818. {
  819. // 提取千位、百位、十位和个位
  820. int thousands = number / 1000;
  821. int hundreds = (number / 100) % 10;
  822. int tens = (number / 10) % 10;
  823. int units = number % 10;
  824. // 将千位和百位组合成一个字节(千位在高四位,百位在低四位)
  825. byte firstByte = (byte)((thousands * 16) + hundreds); // 乘以16相当于左移4位
  826. // 将十位和个位组合成一个字节(十位在高四位,个位在低四位)
  827. byte secondByte = (byte)((tens * 16) + units);
  828. // 返回结果数组
  829. return new byte[] { firstByte, secondByte };
  830. }
  831. public List<Byte> addFA(List<Byte> list)
  832. {
  833. List<byte> result = new List<byte>();
  834. foreach (byte b in list)
  835. {
  836. if (b == 0xFA)
  837. {
  838. result.Add(0xFA);
  839. result.Add(0xFA);
  840. }
  841. else
  842. {
  843. result.Add(b);
  844. }
  845. }
  846. return result;
  847. }
  848. /// <summary>
  849. /// 将数值转为byte[]
  850. /// </summary>
  851. /// <param name="value">数值</param>
  852. /// <param name="length">数组长度,不够高位补0</param>
  853. /// <returns></returns>
  854. /// <exception cref="ArgumentException"></exception>
  855. public static byte[] NumberToByteArrayWithPadding(int value, int length)
  856. {
  857. if (length < 0)
  858. {
  859. throw new ArgumentException("Length must be non-negative.");
  860. }
  861. // 创建一个指定长度的字节数组
  862. byte[] paddedBytes = new byte[length];
  863. // 确保是大端序
  864. for (int i = 0; i < length && i < 4; i++)
  865. {
  866. paddedBytes[length - 1 - i] = (byte)(value >> (i * 8));
  867. }
  868. return paddedBytes;
  869. }
  870. public static byte[] FormatDecimal(decimal value)
  871. {
  872. // 四舍五入到两位小数
  873. decimal roundedValue = Math.Round(value, 2, MidpointRounding.AwayFromZero);
  874. int valueInt = (int)(roundedValue * 100m);
  875. return NumberToByteArrayWithPadding(valueInt, 3); ;
  876. }
  877. /// <summary>
  878. /// 将时间转为 BCD
  879. /// </summary>
  880. /// <param name="dateTime"></param>
  881. /// <returns></returns>
  882. public static byte[] ConvertDateTimeToByteArray(DateTime dateTime)
  883. {
  884. // 创建byte数组
  885. byte[] result = new byte[7];
  886. // 年份处理
  887. int year = dateTime.Year;
  888. result[0] = (byte)((year / 1000) * 16 + (year / 100) % 10); // 千年和百年
  889. result[1] = (byte)((year / 10) % 10 * 16 + year % 10); // 十年和个年
  890. // 月、日、小时、分钟、秒直接转换为BCD
  891. result[2] = (byte)(dateTime.Month / 10 * 16 + dateTime.Month % 10);
  892. result[3] = (byte)(dateTime.Day / 10 * 16 + dateTime.Day % 10);
  893. result[4] = (byte)(dateTime.Hour / 10 * 16 + dateTime.Hour % 10);
  894. result[5] = (byte)(dateTime.Minute / 10 * 16 + dateTime.Minute % 10);
  895. result[6] = (byte)(dateTime.Second / 10 * 16 + dateTime.Second % 10);
  896. return result;
  897. }
  898. // CRC16 constants
  899. const ushort CRC_ORDER16 = 16;
  900. const ushort CRC_POLYNOM16 = 0x1021;
  901. const ushort CRC_CRCINIT16 = 0xFFFF;
  902. const ushort CRC_CRCXOR16 = 0x0000;
  903. const ushort CRC_MASK = 0xFFFF;
  904. const ushort CRC_HIGHEST_BIT = (ushort)(1 << (CRC_ORDER16 - 1));
  905. const ushort TGT_CRC_DEFAULT_INIT = 0xFFFF;
  906. public static ushort Crc16(byte[] buffer, ushort length)
  907. {
  908. ushort crc_rc = TGT_CRC_DEFAULT_INIT;
  909. for (int i = 0; i < length; i++)
  910. {
  911. byte c = buffer[i];
  912. for (ushort j = 0x80; j != 0; j >>= 1)
  913. {
  914. ushort crc_bit = (ushort)((crc_rc & CRC_HIGHEST_BIT) != 0 ? 1 : 0);
  915. crc_rc <<= 1;
  916. if ((c & j) != 0)
  917. {
  918. crc_bit = (ushort)((crc_bit == 0) ? 1 : 0);
  919. }
  920. if (crc_bit != 0)
  921. {
  922. crc_rc ^= CRC_POLYNOM16;
  923. }
  924. }
  925. }
  926. return (ushort)((crc_rc ^ CRC_CRCXOR16) & CRC_MASK);
  927. }
  928. #endregion
  929. }
  930. public class HengshanPayTerminalHanlderGroupConfigV1
  931. {
  932. public string PumpIds { get; set; }
  933. public List<PumpSubAddress> PumpSubAddresses { get; set; }
  934. }
  935. public class HengshanPayTerminalHanlderGroupConfigV2
  936. {
  937. public string PumpIds { get; set; }
  938. public List<PumpSubAddress> PumpSubAddresses { get; set; }
  939. public List<PumpNozzleLogicId> PumpNozzleLogicIds { get; set; }
  940. public List<PumpSiteNozzleNo> PumpSiteNozzleNos { get; set; }
  941. public List<NozzleLogicId> NozzleLogicIds { get; set; }
  942. }
  943. public class PumpSubAddress
  944. {
  945. public byte PumpId { get; set; }
  946. public byte SubAddress { get; set; }
  947. }
  948. public class PumpNozzleLogicId
  949. {
  950. public byte PumpId { get; set; }
  951. public string LogicIds { get; set; }
  952. }
  953. public class PumpSiteNozzleNo
  954. {
  955. public byte PumpId { get; set; }
  956. public string SiteNozzleNos { get; set; }
  957. }
  958. public class NozzleLogicId
  959. {
  960. public byte NozzleNo { get; set; }
  961. public byte LogicId { get; set; }
  962. }
  963. }