PumpHandler.cs 38 KB

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