DitPumpHandler.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. using Edge.Core.Configuration;
  2. using Edge.Core.IndustryStandardInterface.Pump;
  3. using Edge.Core.Parser.BinaryParser.Util;
  4. using Edge.Core.Processor;
  5. using HengShan_Pump_TQC_IFSF.MessageEntity;
  6. using HengShan_Pump_TQC_IFSF.MessageEntity.Incoming;
  7. using HengShan_Pump_TQC_IFSF.MessageEntity.Outgoing;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Globalization;
  11. using System.Linq;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using System.Xml;
  16. using Wayne.FDCPOSLibrary;
  17. using Timer = System.Timers.Timer;
  18. namespace HengShan_Pump_TQC_IFSF
  19. {
  20. public class DitPumpHandler : IFdcPumpController//, IDeviceHandler<byte[], IfsfMessageBase>
  21. {
  22. private object syncObject = new object();
  23. private IEnumerable<NozzleExtraInfo> nozzleExtraInfos = null;
  24. //protected static ILog logger = log4net.LogManager.GetLogger("PumpHandler");
  25. protected static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("PumpHandler");
  26. protected IContext<byte[], IfsfMessageBase> context;
  27. /// <summary>
  28. /// default to first nozzle
  29. /// </summary>
  30. protected byte currentCallingNozzleLogicalId = 1;
  31. protected LogicalDeviceState lastLogicalDeviceState = LogicalDeviceState.FDC_CLOSED;
  32. private DateTime lastLogicalDeviceStateReceivedTime;
  33. // by seconds
  34. public const int lastLogicalDeviceStateExpiredTime = 6;
  35. private Guid uniqueId = Guid.NewGuid();
  36. private DitPumpGroupHandler parent;
  37. protected int pumpId = -1;
  38. protected List<LogicalNozzle> nozzles = new List<LogicalNozzle>();
  39. private byte recipientSubnet = 1;
  40. /// <summary>
  41. /// the pump ifsf Node value.
  42. /// </summary>
  43. public byte recipientNode = 1;
  44. private byte ifsfFuelPointId;
  45. Func<byte> msgTokenGenerator;
  46. /// <summary>
  47. ///
  48. /// </summary>
  49. /// <param name="pumpId"></param>
  50. /// <param name="ifsfFuelPointId">range from 0x21 to 0x24</param>
  51. /// <param name="recipientSubnet">used to locate the remote tqc pump, must a value different from FC</param>
  52. /// <param name="recipientNode">used to locate the remote tqc pump</param>
  53. /// <param name="nozzlesXmlConfiguration"></param>
  54. public DitPumpHandler(DitPumpGroupHandler parent, int pumpId, byte ifsfFuelPointId, byte recipientSubnet, byte recipientNode, string nozzlesXmlConfiguration, Func<byte> msgTokenGenerator)
  55. {
  56. //sample of nozzlesXmlConfiguration
  57. // <Pump id='3'>
  58. // <Nozzles>
  59. // <Nozzle logicalId='1' physicalId='0x11' />
  60. // <Nozzle logicalId='2' physicalId='0x12' />
  61. // </Nozzles>
  62. // </Pump>
  63. this.pumpId = pumpId;
  64. this.parent = parent;
  65. this.ifsfFuelPointId = ifsfFuelPointId;
  66. this.recipientSubnet = recipientSubnet;
  67. this.recipientNode = recipientNode;
  68. this.msgTokenGenerator = msgTokenGenerator;
  69. var xmlDocument = new XmlDocument();
  70. xmlDocument.LoadXml(nozzlesXmlConfiguration);
  71. foreach (var nozzleElement in xmlDocument.GetElementsByTagName("Nozzle").Cast<XmlNode>())
  72. {
  73. byte physicalIdValue;
  74. var rawPhysicalIdValue = nozzleElement.Attributes["physicalId"].Value;
  75. if (rawPhysicalIdValue.StartsWith("0x") || rawPhysicalIdValue.StartsWith("0X"))
  76. physicalIdValue = byte.Parse(nozzleElement.Attributes["physicalId"].Value.Substring(2), NumberStyles.HexNumber);
  77. else
  78. physicalIdValue = byte.Parse(nozzleElement.Attributes["physicalId"].Value);
  79. if (physicalIdValue < 0x11 || physicalIdValue > 0x18)
  80. throw new ArgumentException("ifsf nozzle id must be range from 0x11 to 0x18, while the configuration passed in " + rawPhysicalIdValue);
  81. this.nozzles.Add(new LogicalNozzle(pumpId, physicalIdValue, byte.Parse(nozzleElement.Attributes["logicalId"].Value), null));
  82. //if (nozzleElement.Attributes["ifsfLogicalNozzleId"] != null)
  83. // this.logicalNozzleIdMappingToIfsfLogicalNozzlelId.Add(
  84. // byte.Parse(nozzleElement.Attributes["ifsfLogicalNozzleId"].Value.Substring(2), NumberStyles.HexNumber),
  85. // byte.Parse(nozzleElement.Attributes["logicalId"].Value));
  86. }
  87. }
  88. public DitPumpHandler(DitPumpGroupHandler parent, byte pumpId, byte ifsfFuelPointId, byte recipientSubnet, byte recipientNode,
  89. IEnumerable<DitPumpGroupHandler.NozzleConfiguration> nozzlesConfigs, Func<byte> msgTokenGenerator)
  90. {
  91. this.pumpId = pumpId;
  92. this.parent = parent;
  93. this.ifsfFuelPointId = ifsfFuelPointId;
  94. this.recipientSubnet = recipientSubnet;
  95. this.recipientNode = recipientNode;
  96. this.msgTokenGenerator = msgTokenGenerator;
  97. foreach (var nc in nozzlesConfigs)
  98. {
  99. this.nozzles.Add(new LogicalNozzle(pumpId, nc.PhysicalId, nc.LogicalId, null));
  100. }
  101. }
  102. public string Name => this.GetType().FullName;
  103. public int PumpId => this.pumpId;
  104. /// <summary>
  105. /// range from 0x21 to 0x24 since it's a IFSF pump
  106. /// </summary>
  107. public int PumpPhysicalId => this.ifsfFuelPointId;
  108. public IEnumerable<LogicalNozzle> Nozzles => this.nozzles;
  109. public int AmountDecimalDigits => 2;
  110. public int VolumeDecimalDigits => 2;
  111. public int PriceDecimalDigits => 2;
  112. public int VolumeTotalizerDecimalDigits => 2;
  113. public Guid Id => this.uniqueId;
  114. public event EventHandler<FdcPumpControllerOnStateChangeEventArg> OnStateChange;
  115. public event EventHandler<FdcTransactionDoneEventArg> OnCurrentFuellingStatusChange;
  116. protected void FireOnStateChangeEvent(LogicalDeviceState state, LogicalNozzle stateChangedNozzle)
  117. {
  118. try
  119. {
  120. var safe = this.OnStateChange;
  121. safe?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(state, stateChangedNozzle));
  122. }
  123. catch (Exception exx)
  124. {
  125. logger.Info("exceptioned when FireOnStateChangeEvent: " + exx + Environment.NewLine + "Will Ignore and do nothing");
  126. }
  127. }
  128. protected void FireOnCurrentFuellingStatusChangeEvent(FdcTransaction trx)
  129. {
  130. try
  131. {
  132. var safe = this.OnCurrentFuellingStatusChange;
  133. safe?.Invoke(this, new FdcTransactionDoneEventArg(trx));
  134. }
  135. catch (Exception exx)
  136. {
  137. logger.Info("exceptioned when FireOnCurrentFuellingStatusChangeEvent: " + exx + Environment.NewLine + "Will Ignore and do nothing");
  138. }
  139. }
  140. public virtual void Init(IContext<byte[], IfsfMessageBase> context)
  141. {
  142. this.context = context;
  143. }
  144. public virtual Task Process(IContext<byte[], IfsfMessageBase> context)
  145. {
  146. this.context = context;
  147. switch (context.Incoming.Message)
  148. {
  149. case FuellingPointDb_FpStatus_Event fpStatusEvent:
  150. {
  151. logger.Info("Pump " + this.pumpId + ", receiving Ifsf PumpState(by fpStatusEvent): "
  152. + fpStatusEvent.FuelPointState
  153. + "(internal FdcState: " + this.lastLogicalDeviceState + ")");
  154. // send query only when at cold start.
  155. if (fpStatusEvent.FuelPointState == FuellingPointStatus.Idle
  156. && this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED)
  157. {
  158. logger.Info("Pump " + this.pumpId + ", State changing from Fdc_CLOSED to Ifsf IDLE, always send a query to see any open trx stacked in TQC pump side");
  159. context.Outgoing.Write(
  160. new FuelTrxDb_Read_QueryTransaction(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode,
  161. this.msgTokenGenerator(), this.ifsfFuelPointId, null));
  162. }
  163. // when trx event piped in, there's no nozzle info linked it within the event(ifsf bad design?), so here always track
  164. // and keep the state of the last calling or fuelling nozzle.
  165. if (fpStatusEvent.FuelPointState == FuellingPointStatus.CALLING
  166. || fpStatusEvent.FuelPointState == FuellingPointStatus.FUELLING)
  167. {
  168. // pump may not reporting nozzle id, so here assgin a default value of 1
  169. var operatingNozzlePhysicalId =
  170. (byte)(
  171. ((fpStatusEvent.RemovedNozzleIndexes?.Any() ?? false) ?
  172. fpStatusEvent.RemovedNozzleIndexes.First() : 1)
  173. + 0x10);
  174. if (this.nozzles.Any(n => n.PhysicalId == operatingNozzlePhysicalId))
  175. this.currentCallingNozzleLogicalId = this.nozzles.First(n => n.PhysicalId == operatingNozzlePhysicalId).LogicalId;
  176. else
  177. this.currentCallingNozzleLogicalId = fpStatusEvent.RemovedNozzleIndexes.FirstOrDefault();
  178. logger.Info("Pump " + this.pumpId + ", reporting removed(lifted) ifsf NozzleId is 0x"
  179. + operatingNozzlePhysicalId.ToHexLogString()
  180. + ", correlated Fdc Nozzle logical Id is " + this.currentCallingNozzleLogicalId);
  181. }
  182. this.HandleFpStatusChange(fpStatusEvent.FuelPointState,
  183. this.nozzles.FirstOrDefault(n => n.LogicalId == this.currentCallingNozzleLogicalId));
  184. break;
  185. }
  186. case FuellingPointDb_FpStatus_Answer fpStatusAnswer:
  187. {
  188. // send query only when at cold start.
  189. if (fpStatusAnswer.FuelPointState == FuellingPointStatus.Idle && this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED)
  190. {
  191. logger.Info("Pump " + this.pumpId + ", State changed from CLOSED to IDLE, likely a cold start, send query to see any open trx");
  192. context.Outgoing.Write(
  193. new FuelTrxDb_Read_QueryTransaction(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode,
  194. this.msgTokenGenerator(), this.ifsfFuelPointId, null));
  195. }
  196. this.HandleFpStatusChange(fpStatusAnswer.FuelPointState, this.nozzles.FirstOrDefault(n => n.LogicalId == this.currentCallingNozzleLogicalId));
  197. break;
  198. }
  199. case FuellingPointDb_FpRunningTransaction_Event fpRunningTrxEvent:
  200. {
  201. logger.Debug("Pump " + this.pumpId + ", fpRunningTrxEvent ifsf FpId: 0x" + fpRunningTrxEvent.TargetFuelPointId.ToHexLogString()
  202. + ", CurAmt and CurVol(no decimal points): " + fpRunningTrxEvent.CurrentAmount
  203. + " - " + fpRunningTrxEvent.CurrentVolume);
  204. var operatingNozzle = this.nozzles.First(f => f.LogicalId == this.currentCallingNozzleLogicalId);
  205. var operatingNozzleProductPrice =
  206. this.parent.tqcPumpGroupInitializers.First(i => i.ifsfRecipientNode == this.recipientNode).PriceBook[
  207. this.nozzleExtraInfos.First(c => c.PumpId == operatingNozzle.PumpId
  208. && c.NozzleLogicalId == operatingNozzle.LogicalId).ProductBarcode];
  209. //fire fuelling progress.
  210. var safe1 = this.OnCurrentFuellingStatusChange;
  211. safe1?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction()
  212. {
  213. Nozzle = operatingNozzle,
  214. Amount = fpRunningTrxEvent.CurrentAmount,
  215. Volumn = fpRunningTrxEvent.CurrentVolume,
  216. Price = operatingNozzleProductPrice,
  217. Finished = false,
  218. }));
  219. break;
  220. }
  221. case FuellingTrxDb_TransactionBufferStatus_Event trxBufferStatusEvent:
  222. {
  223. if (trxBufferStatusEvent.State == FuellingTrxDb_TransactionBufferStatus_Event.TransactionState.PAYABLE
  224. && (trxBufferStatusEvent.Volume != 0 || trxBufferStatusEvent.Amount != 0))
  225. {
  226. logger.Debug("Pump " + this.pumpId + ", received a Payable trx from TQC with Vol and Amount(without decimal points): "
  227. + trxBufferStatusEvent.Volume + " - " + trxBufferStatusEvent.Amount
  228. + ", will send clear to clear it in pump side anyway");
  229. context.Outgoing.WriteAsync(
  230. new FuellingPointDbRequest_Write_ClearTransaction(this.recipientSubnet,
  231. this.recipientNode,
  232. PumpGroupHandler.originatorSubnet,
  233. PumpGroupHandler.originatorNode,
  234. this.msgTokenGenerator(), this.ifsfFuelPointId, trxBufferStatusEvent.TransactionSeqNumber.ToString()),
  235. (request, response) => response is AcknowledgeMessage rp && request is IfsfMessage rq && rp.MessageToken == rp.MessageToken,
  236. (_, expectedResponse) =>
  237. {
  238. if (expectedResponse != null)
  239. { }
  240. else logger.Info("Pump " + this.pumpId + ", Clear transaction in TQC pump side failed by response time out");
  241. var operatingNozzle = this.nozzles.First(f => f.LogicalId == this.currentCallingNozzleLogicalId);
  242. var operatingNozzleProductPrice = this.parent.tqcPumpGroupInitializers.First(i => i.ifsfRecipientNode == this.recipientNode).PriceBook[this.nozzleExtraInfos.First(c => c.PumpId == operatingNozzle.PumpId && c.NozzleLogicalId == operatingNozzle.LogicalId).ProductBarcode];
  243. var safe1 = this.OnCurrentFuellingStatusChange;
  244. safe1?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction()
  245. {
  246. Nozzle = operatingNozzle,
  247. Amount = trxBufferStatusEvent.Amount,
  248. Volumn = trxBufferStatusEvent.Volume,
  249. Price = operatingNozzleProductPrice,
  250. SequenceNumberGeneratedOnPhysicalPump = trxBufferStatusEvent.TransactionSeqNumber,
  251. Finished = true,
  252. }));
  253. }, 12000);
  254. }
  255. break;
  256. }
  257. case FuellingTrxDb_TransactionBufferStatus_Answer trxBufferStatusAnswer:
  258. {
  259. if (trxBufferStatusAnswer.State.HasValue
  260. && trxBufferStatusAnswer.State == FuellingTrxDb_TransactionBufferStatus_Event.TransactionState.PAYABLE
  261. //&& (trxBufferStatusAnswer.Volume != 0 || trxBufferStatusAnswer.Amount != 0)
  262. )
  263. {
  264. logger.Debug("Pump " + this.pumpId + ", received a Payable trx from TQC with Vol and Amount(without decimal points): "
  265. + trxBufferStatusAnswer.Volume + " - " + trxBufferStatusAnswer.Amount
  266. + ", sequenceNo.: " + trxBufferStatusAnswer.TransactionSeqNumber
  267. + ", will send ClearTransaction to clear it now");
  268. context.Outgoing.WriteAsync(
  269. new FuellingPointDbRequest_Write_ClearTransaction(this.recipientSubnet,
  270. this.recipientNode,
  271. PumpGroupHandler.originatorSubnet,
  272. PumpGroupHandler.originatorNode,
  273. this.msgTokenGenerator(), this.ifsfFuelPointId, trxBufferStatusAnswer.TransactionSeqNumber.ToString()),
  274. (request, response) => response is AcknowledgeMessage rp && request is IfsfMessage rq && rp.MessageToken == rp.MessageToken,
  275. (_, expectedResponse) =>
  276. {
  277. if (expectedResponse != null)
  278. { }
  279. else logger.Info("Pump " + this.pumpId + ", Clear transaction in TQC pump side failed by response time out");
  280. var operatingNozzle = this.nozzles.First(f => f.LogicalId == this.currentCallingNozzleLogicalId);
  281. var operatingNozzleProductPrice = this.parent.tqcPumpGroupInitializers.First(i => i.ifsfRecipientNode == this.recipientNode).PriceBook[this.nozzleExtraInfos.First(c => c.PumpId == operatingNozzle.PumpId && c.NozzleLogicalId == operatingNozzle.LogicalId).ProductBarcode];
  282. var safe1 = this.OnCurrentFuellingStatusChange;
  283. safe1?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction()
  284. {
  285. Nozzle = operatingNozzle,
  286. Amount = trxBufferStatusAnswer.Amount,
  287. Volumn = trxBufferStatusAnswer.Volume,
  288. Price = operatingNozzleProductPrice,
  289. SequenceNumberGeneratedOnPhysicalPump = trxBufferStatusAnswer.TransactionSeqNumber,
  290. Finished = true,
  291. }));
  292. }, 8000);
  293. }
  294. break;
  295. }
  296. case IfsfMessage ifsfMessage:
  297. {
  298. var dbParser = DatabaseDataParser.New().Convert(ifsfMessage.RawDatabaseData.ToArray());
  299. break;
  300. }
  301. }
  302. //if (context.Incoming.Message is FuellingPointDb_FpStatus_Event fpStatusChangeEvent && fpStatusChangeEvent.TargetFuelPointId == this.ifsfFuelPointId)
  303. //{
  304. // this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
  305. //}
  306. return Task.CompletedTask;
  307. }
  308. public virtual void HandleFpStatusChange(FuellingPointStatus newStatus, LogicalNozzle stateChangedNozzle)
  309. {
  310. this.lastLogicalDeviceStateReceivedTime = DateTime.Now;
  311. switch (newStatus)
  312. {
  313. case FuellingPointStatus.Idle:
  314. {
  315. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY)
  316. {
  317. this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
  318. this.FireOnStateChangeEvent(LogicalDeviceState.FDC_READY, null);
  319. }
  320. break;
  321. }
  322. case FuellingPointStatus.CALLING:
  323. {
  324. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_CALLING)
  325. {
  326. this.lastLogicalDeviceState = LogicalDeviceState.FDC_CALLING;
  327. this.FireOnStateChangeEvent(LogicalDeviceState.FDC_CALLING, stateChangedNozzle);
  328. }
  329. break;
  330. }
  331. case FuellingPointStatus.AUTHORISED:
  332. {
  333. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_AUTHORISED)
  334. {
  335. this.lastLogicalDeviceState = LogicalDeviceState.FDC_AUTHORISED;
  336. this.FireOnStateChangeEvent(LogicalDeviceState.FDC_AUTHORISED, stateChangedNozzle);
  337. }
  338. break;
  339. }
  340. case FuellingPointStatus.STARTED:
  341. {
  342. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_STARTED)
  343. {
  344. // simulate a FDC_AUTHORISED event since TQC will not send this state in.
  345. this.lastLogicalDeviceState = LogicalDeviceState.FDC_AUTHORISED;
  346. this.FireOnStateChangeEvent(LogicalDeviceState.FDC_AUTHORISED, stateChangedNozzle);
  347. this.lastLogicalDeviceState = LogicalDeviceState.FDC_STARTED;
  348. this.FireOnStateChangeEvent(LogicalDeviceState.FDC_STARTED, stateChangedNozzle);
  349. }
  350. break;
  351. }
  352. case FuellingPointStatus.FUELLING:
  353. {
  354. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_FUELLING)
  355. {
  356. this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING;
  357. this.FireOnStateChangeEvent(LogicalDeviceState.FDC_FUELLING, stateChangedNozzle);
  358. }
  359. break;
  360. }
  361. case FuellingPointStatus.Inoperative:
  362. {
  363. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_OFFLINE)
  364. {
  365. this.lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE;
  366. this.FireOnStateChangeEvent(LogicalDeviceState.FDC_OFFLINE, null);
  367. }
  368. break;
  369. }
  370. case FuellingPointStatus.Closed:
  371. {
  372. this.lastLogicalDeviceState = LogicalDeviceState.FDC_CLOSED;
  373. logger.Info("Pump " + this.pumpId + " with ifsf fuelPoint id: 0x" + this.ifsfFuelPointId.ToHexLogString() + " is in Closed state, will open it...");
  374. this.FireOnStateChangeEvent(LogicalDeviceState.FDC_CLOSED, null);
  375. context.Outgoing.WriteAsync(
  376. new FuellingPointDbRequest_Write_OpenFuelPoint(this.recipientSubnet,
  377. this.recipientNode,
  378. PumpGroupHandler.originatorSubnet,
  379. PumpGroupHandler.originatorNode,
  380. this.msgTokenGenerator(), this.ifsfFuelPointId),
  381. (request, response) => (response is IfsfMessage rp) && (request is IfsfMessage rq) && rp.MessageToken == rq.MessageToken
  382. && rp.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK, (_, response) =>
  383. {
  384. if (response != null)
  385. logger.Debug("Pump " + this.pumpId + ", Open FP 0x" + this.ifsfFuelPointId.ToHexLogString() + " Acked succeed");
  386. else
  387. logger.Info("Pump " + this.pumpId + ", Open FP 0x" + this.ifsfFuelPointId.ToHexLogString() + " timedout for Ack, failed");
  388. }, 8000);
  389. break;
  390. }
  391. }
  392. }
  393. public virtual async Task<bool> AuthorizeAsync(byte logicalNozzleId)
  394. {
  395. //bool authSucceed = false;
  396. //AutoResetEvent blocker = new AutoResetEvent(false);
  397. var authResponse = await this.context.Outgoing.WriteAsync(new FuellingPointDbRequest_Write_AuthorizeFuelPoint(this.recipientSubnet, this.recipientNode,
  398. PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId, null),
  399. (request, response) =>
  400. (response is AcknowledgeMessage rp) && (request is IfsfMessage rq)
  401. && rp.MessageToken == rq.MessageToken
  402. && rp.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK, 6000);
  403. var authSucceed = false;
  404. if (authResponse is AcknowledgeMessage ackMsg)
  405. {
  406. if (ackMsg.OverallStatus == MessageAcknowledgeStatus.ACK_PositiveAcknowledgeDataReceived)
  407. {
  408. //context.Outgoing.Write(new FuellingPointDbRequest_Read_FuelPointState(this.recipientSubnet,
  409. // this.recipientNode,
  410. // PumpGroupHandler.originatorSubnet,
  411. // PumpGroupHandler.originatorNode,
  412. // this.msgTokenGenerator(), this.ifsfFuelPointId));
  413. logger.Debug("Pump " + this.pumpId + ", Auth FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + " ACKed with succeed");
  414. authSucceed = true;
  415. }
  416. else
  417. {
  418. logger.Info("Pump " + this.pumpId + ", Auth FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + " ACKed but failed with internal reason: \r\n" + ackMsg.ToLogString()
  419. + "\r\n it may caused by unpaid trx still stacked in TQC pump side and then refuse to be authorized, here send query payable trx request anyway");
  420. context.Outgoing.Write(
  421. new FuelTrxDb_Read_QueryTransaction(this.recipientSubnet, this.recipientNode, PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode,
  422. this.msgTokenGenerator(), this.ifsfFuelPointId, null));
  423. authSucceed = false;
  424. }
  425. }
  426. else
  427. logger.Info("Pump " + this.pumpId + ", Auth FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + " timed out for ACK");
  428. return authSucceed;
  429. }
  430. public virtual async Task<bool> AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId)
  431. {
  432. // temp fake change!!!
  433. return await this.AuthorizeAsync(logicalNozzleId);
  434. }
  435. public virtual async Task<bool> AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId)
  436. {
  437. throw new NotImplementedException();
  438. }
  439. public virtual async Task<bool> ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId)
  440. {
  441. var productNo = this.nozzleExtraInfos.FirstOrDefault(c => c.PumpId == this.pumpId && c.NozzleLogicalId == logicalNozzleId)?.ProductBarcode;
  442. if (productNo == null)
  443. {
  444. logger.Error("Pump " + this.pumpId + ", Change Price for productNo: " + productNo + " with new price: " + newPriceWithoutDecimalPoint + " failed due to product not found in config for target pump");
  445. return false;
  446. }
  447. logger.Info("Pump: " + this.pumpId + ", " + "Change Fuel Price for LogicalNozzle: "
  448. + logicalNozzleId + " with newPriceWithoutDecimalPoint: " + newPriceWithoutDecimalPoint);
  449. var priceChangeResponse = await this.context.Outgoing.WriteAsync(new ProductPerFuellingModeDbRequest_Write_SetPrice(this.recipientSubnet, this.recipientNode,
  450. PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(),
  451. productNo.ToString(),
  452. newPriceWithoutDecimalPoint.ToString(),
  453. 0x11),
  454. (request, response) =>
  455. (response is AcknowledgeMessage rp) && (request is IfsfMessage rq)
  456. && rp.MessageToken == rq.MessageToken
  457. && rp.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK,
  458. 8000);
  459. bool changePriceSucceed = false;
  460. if (priceChangeResponse is AcknowledgeMessage ackMsg
  461. && ackMsg.OverallStatus == MessageAcknowledgeStatus.ACK_PositiveAcknowledgeDataReceived)
  462. {
  463. logger.Info("Pump " + this.pumpId + ", Change Price for productNo: " + productNo + " with new price: " + newPriceWithoutDecimalPoint + " ACKed with Positive but still querying the price again for double confirm...");
  464. var doubleConfirmQueryPriceResponse = await this.context.Outgoing.WriteAsync(
  465. new ProductPerFuellingModeDbRequest_Read_ProductPrice(this.recipientSubnet, this.recipientNode,
  466. PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(),
  467. productNo.ToString(), 0x11),
  468. (readPriceRequest, readPriceResponse) =>
  469. (readPriceResponse is ProductPerFuellingModeDb_ProductPrice_Answer rp)
  470. && (readPriceRequest is IfsfMessage rq)
  471. && rp.MessageToken == rq.MessageToken
  472. && rp.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER
  473. && rp.FuelModeId == 0x11,
  474. 5000) as ProductPerFuellingModeDb_ProductPrice_Answer;
  475. if (doubleConfirmQueryPriceResponse != null)
  476. {
  477. logger.Info("Pump " + this.pumpId + ", doubleConfirmQueryPrice, Price for productNo: " + productNo
  478. + " queried with price(without decimal points): " + doubleConfirmQueryPriceResponse.Price);
  479. int queriedPumpSidePrice;
  480. if (!int.TryParse(doubleConfirmQueryPriceResponse.Price, out queriedPumpSidePrice))
  481. {
  482. logger.Info("Pump " + this.pumpId + ", Change Price for productNo: " + productNo
  483. + " with new price(without decimal points): "
  484. + newPriceWithoutDecimalPoint + " failed due to double confirm price response price value is un-recognize: " + (doubleConfirmQueryPriceResponse.Price ?? ""));
  485. changePriceSucceed = false;
  486. }
  487. else
  488. {
  489. if (queriedPumpSidePrice != newPriceWithoutDecimalPoint)
  490. {
  491. logger.Info("Pump " + this.pumpId + ", doubleConfirmQueryPrice, Price for productNo: " + productNo
  492. + " queried with price(without decimal points): " + doubleConfirmQueryPriceResponse.Price
  493. + " does not match expected new price: " + newPriceWithoutDecimalPoint);
  494. changePriceSucceed = false;
  495. }
  496. else
  497. {
  498. logger.Info("Pump " + this.pumpId + ", doubleConfirmQueryPrice, Price for productNo: " + productNo
  499. + " has been double confirmed and it's aligned with TQC side, change price succeed.");
  500. changePriceSucceed = true;
  501. this.nozzles.First(n => n.LogicalId == logicalNozzleId).RealPriceOnPhysicalPump = newPriceWithoutDecimalPoint;
  502. this.parent.tqcPumpGroupInitializers.First(i => i.ifsfRecipientNode == this.recipientNode).PriceBook[productNo.Value] = newPriceWithoutDecimalPoint;
  503. logger.Info("Pump " + this.pumpId + ", PriceBook updated to: \r\n"
  504. + this.parent.tqcPumpGroupInitializers.First(i => i.ifsfRecipientNode == this.recipientNode).PriceBook.Keys.Select(s => "productNo: " + s + ", price: " + this.parent.tqcPumpGroupInitializers.First(i => i.ifsfRecipientNode == this.recipientNode).PriceBook[s])
  505. .Aggregate((acc, n) => acc + "\r\n" + n));
  506. }
  507. }
  508. }
  509. else
  510. {
  511. logger.Info("Pump " + this.pumpId + ", doubleConfirmQueryPrice timedout, but still treat this Price change as succeed since Acked with Positive");
  512. changePriceSucceed = true;
  513. this.nozzles.First(n => n.LogicalId == logicalNozzleId).RealPriceOnPhysicalPump = newPriceWithoutDecimalPoint;
  514. this.parent.tqcPumpGroupInitializers.First(i => i.ifsfRecipientNode == this.recipientNode).PriceBook[productNo.Value] = newPriceWithoutDecimalPoint;
  515. logger.Info("Pump " + this.pumpId + ", PriceBook updated to: \r\n"
  516. + this.parent.tqcPumpGroupInitializers.First(i => i.ifsfRecipientNode == this.recipientNode).PriceBook.Keys.Select(s => "productNo: " + s + ", price: " + this.parent.tqcPumpGroupInitializers.First(i => i.ifsfRecipientNode == this.recipientNode).PriceBook[s])
  517. .Aggregate((acc, n) => acc + "\r\n" + n));
  518. }
  519. }
  520. else
  521. {
  522. logger.Info("Pump " + this.pumpId + ", Change Price for productNo: " + productNo + " with new price(without decimal points): "
  523. + newPriceWithoutDecimalPoint + " ACK timed out or ACKed with non-positive, price change failed!");
  524. changePriceSucceed = false;
  525. }
  526. return changePriceSucceed;
  527. }
  528. public virtual async Task<bool> FuelingRoundUpByAmountAsync(int amount)
  529. {
  530. // temp fake change!!!
  531. return await Task.FromResult(true);
  532. }
  533. public virtual async Task<bool> FuelingRoundUpByVolumeAsync(int volume)
  534. {
  535. // temp fake change!!!
  536. return await Task.FromResult(true);
  537. }
  538. public virtual async Task<LogicalDeviceState> QueryStatusAsync()
  539. {
  540. if (!this.parent.tqcPumpGroupInitializers.First(i => i.ifsfRecipientNode == this.recipientNode).IsInitDone) return LogicalDeviceState.FDC_CLOSED;
  541. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED)
  542. {
  543. logger.Debug("Pump " + this.pumpId + ", QueryStatus(), FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + " still in Closed state, will send a real status query to TQC");
  544. this.context.Outgoing.Write(
  545. new FuellingPointDbRequest_Read_FuelPointState(this.recipientSubnet,
  546. this.recipientNode,
  547. PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(),
  548. this.ifsfFuelPointId));
  549. }
  550. return this.lastLogicalDeviceState;
  551. }
  552. /// <summary>
  553. /// no decimal point value.
  554. /// </summary>
  555. /// <returns>MoneyTotalizer:VolumnTotalizer</returns>
  556. public virtual async Task<System.Tuple<int, int>> QueryTotalizerAsync(byte logicalNozzleId)
  557. {
  558. System.Tuple<int, int> result = new System.Tuple<int, int>(-1, -1);
  559. if (!this.parent.tqcPumpGroupInitializers.First(i => i.ifsfRecipientNode == this.recipientNode).IsInitDone)
  560. return result;
  561. var tqcLogicalNozzleId = this.nozzles.First(n => n.LogicalId == logicalNozzleId).PhysicalId;
  562. var queryResponse = await this.context.Outgoing.WriteAsync(
  563. new LogicalNozzleDb_Read_QueryTotalizer(this.recipientSubnet, this.recipientNode,
  564. PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId, tqcLogicalNozzleId)
  565. , (request, response) =>
  566. (response is LogicalNozzleDb_NozzleTotalizer_Answer rp) && (request is IfsfMessage rq)
  567. && rp.MessageToken == rq.MessageToken
  568. && rp.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER, 8000);
  569. if (queryResponse != null && queryResponse is LogicalNozzleDb_NozzleTotalizer_Answer ___)
  570. {
  571. result = new System.Tuple<int, int>(Convert.ToInt32(___.MoneyTotal * Math.Pow(10, this.AmountDecimalDigits)), Convert.ToInt32(___.VolumeTotal * Math.Pow(10, this.VolumeDecimalDigits)));
  572. logger.Debug("Pump " + this.pumpId + ", QueryTotalizer(), FP: 0x" + this.ifsfFuelPointId.ToHexLogString()
  573. + ", FC logicalNozzleId: " + logicalNozzleId + ", Tqc ifsf logicalNozzleId: 0x" + tqcLogicalNozzleId.ToHexLogString()
  574. + " succeed, no decimal point money and vol acc: " + result.Item1 + " - " + result.Item2);
  575. }
  576. else
  577. {
  578. }
  579. return result;
  580. }
  581. public virtual async Task<bool> ResumeFuellingAsync()
  582. {
  583. throw new NotImplementedException();
  584. }
  585. public virtual async Task<bool> SuspendFuellingAsync()
  586. {
  587. throw new NotImplementedException();
  588. }
  589. public virtual async Task<bool> UnAuthorizeAsync(byte logicalNozzleId)
  590. {
  591. var terminateFpResponse = await this.context.Outgoing.WriteAsync(new FuellingPointDbRequest_Write_TerminateFuelPoint(this.recipientSubnet, this.recipientNode,
  592. PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode, this.msgTokenGenerator(), this.ifsfFuelPointId, null),
  593. (request, response) =>
  594. (response is AcknowledgeMessage rp) && (request is IfsfMessage rq)
  595. && rp.MessageToken == rq.MessageToken
  596. && rp.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK, 6000);
  597. var terminateFpSucceed = false;
  598. if (terminateFpResponse is AcknowledgeMessage ackMsg)
  599. {
  600. if (ackMsg.OverallStatus == MessageAcknowledgeStatus.ACK_PositiveAcknowledgeDataReceived)
  601. {
  602. //context.Outgoing.Write(new FuellingPointDbRequest_Read_FuelPointState(this.recipientSubnet,
  603. // this.recipientNode,
  604. // PumpGroupHandler.originatorSubnet,
  605. // PumpGroupHandler.originatorNode,
  606. // this.msgTokenGenerator(), this.ifsfFuelPointId));
  607. logger.Debug("Pump " + this.pumpId + ", Terminate FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + " ACKed with succeed");
  608. terminateFpSucceed = true;
  609. }
  610. else
  611. {
  612. logger.Info("Pump " + this.pumpId + ", Terminate FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + " ACKed but failed with internal reason: \r\n" + ackMsg.ToLogString());
  613. terminateFpSucceed = false;
  614. }
  615. }
  616. else
  617. logger.Info("Pump " + this.pumpId + ", Terminate FP: 0x" + this.ifsfFuelPointId.ToHexLogString() + " timed out for ACK");
  618. return terminateFpSucceed;
  619. }
  620. public async Task<bool> LockNozzleAsync(byte logicalNozzleId)
  621. {
  622. return false;
  623. }
  624. public async Task<bool> UnlockNozzleAsync(byte logicalNozzleId)
  625. {
  626. return false;
  627. }
  628. #region IDisposable Support
  629. private bool disposedValue = false; // To detect redundant calls
  630. protected virtual void Dispose(bool disposing)
  631. {
  632. if (!disposedValue)
  633. {
  634. if (disposing)
  635. {
  636. // TODO: dispose managed state (managed objects).
  637. }
  638. // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
  639. // TODO: set large fields to null.
  640. disposedValue = true;
  641. }
  642. }
  643. // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
  644. // ~PumpHandler() {
  645. // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  646. // Dispose(false);
  647. // }
  648. // This code added to correctly implement the disposable pattern.
  649. public void Dispose()
  650. {
  651. // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  652. Dispose(true);
  653. // TODO: uncomment the following line if the finalizer is overridden above.
  654. // GC.SuppressFinalize(this);
  655. }
  656. public void OnFdcServerInit(Dictionary<string, object> parameters)
  657. {
  658. if (parameters != null && parameters.TryGetValue("NozzleProductMapping", out object param))
  659. {
  660. this.nozzleExtraInfos = param as IEnumerable<NozzleExtraInfo>;
  661. }
  662. }
  663. #endregion
  664. }
  665. }