PetroChinaSilentPumpGroupHandler.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using Edge.Core.Processor;
  2. using Edge.Core.IndustryStandardInterface.Pump;
  3. using HengShan_Pump_TQC_IFSF.MessageEntity;
  4. using HengShan_Pump_TQC_IFSF.MessageEntity.Incoming;
  5. using HengShan_Pump_TQC_IFSF.MessageEntity.Outgoing;
  6. using Edge.Core.Parser.BinaryParser.Util;
  7. using System;
  8. using System.Collections;
  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.Processor.Dispatcher.Attributes;
  19. using Edge.Core.Processor.Communicator;
  20. using Edge.Core.UniversalApi;
  21. namespace HengShan_Pump_TQC_IFSF
  22. {
  23. public class PetroChinaSilentPumpGroupConfiguration : PumpGroupConfiguration
  24. {
  25. /// <summary>
  26. /// this feature is used in 中油站, this site support 2 kinds of payment way for fueling:
  27. /// 1. insert driver card, pump get authorized, when done fuelling, charged from card.
  28. /// 2. lift nozzle directly to start fueling, when done fuelling, driver pay at indoor.
  29. ///
  30. /// to determine which payment way is currectly used by only using IFSF is by checking Fdc pump state change sequence.
  31. /// Calling->Fuelling means the pump didn't get authorized, the attandant just directly lift nozzle and fueling
  32. /// Authorized->Fuelling means the driver inserted card firstly and then start fueling.
  33. /// </summary>
  34. public bool HiddenIcCardAuthorizedFuelTrxToFdcServerApp { get; set; }
  35. }
  36. /// <summary>
  37. /// silent means this pump group handler never actively send data to TQC, this is a solution for
  38. /// a special TQC pump only passthrough sales data to FC, because it's controlled by other party FC.
  39. /// </summary>
  40. [UniversalApi(Name = OnPetroChinaIcCardAuthorizedFuelTrxDone_EventName, EventDataType = typeof(FdcTransactionDoneEventArg),
  41. Description = "When a pump trx was started by user inserted IC card on dispenser card reader first, this is called of type IC card authorized trx, this event is for popup these trx(the only other type of lifting nozzle and start fueling is not in this case)")]
  42. [MetaPartsRequired(typeof(GenericDeviceProcessor<,>))]
  43. [MetaPartsRequired(typeof(HengShan_TQC_IFsfMessageTcpIpCommunicator_Silent<>))]
  44. [MetaPartsDescriptor("中油 HS TQC 只读模式加油机",
  45. "用于中油站中驱动只读模式下的基于TCP连接的IFSF协议的恒山TQC加油机",
  46. new[] { "lang-zh-cn:加油机lang-en-us:Pump" })]
  47. public class PetroChinaSilentPumpGroupHandler : PumpGroupHandler
  48. {
  49. public const string OnPetroChinaIcCardAuthorizedFuelTrxDone_EventName = "OnPetroChinaIcCardAuthorizedFuelTrxDone";
  50. public event EventHandler<FdcTransactionDoneEventArg> OnPetroChinaIcCardAuthorizedFuelTrxDone;
  51. private bool hiddenIcCardAuthorizedFuelTrxToFdcServerApp;
  52. [ParamsJsonSchemas("PetroChinaSilentPumpGroupHandlerCtorParamsJsonSchemas")]
  53. public PetroChinaSilentPumpGroupHandler(PetroChinaSilentPumpGroupConfiguration pumpGroupConfiguration, IServiceProvider services)
  54. : base(pumpGroupConfiguration, services)
  55. {
  56. this.hiddenIcCardAuthorizedFuelTrxToFdcServerApp = pumpGroupConfiguration.HiddenIcCardAuthorizedFuelTrxToFdcServerApp;
  57. }
  58. public override void Init(IContext<byte[], IfsfMessageBase> context)
  59. {
  60. base.context = context;
  61. this.context.Communicator.OnConnected += (e, f) =>
  62. {
  63. // each time communicator get disconnect, will trigger a re-init.
  64. logger.Info("node " + this.tqcPumpGroupInitializer.ifsfRecipientNode + ", OnCommunicator Connected, will hard set all pumps to Idle state anyway");
  65. this.pumpHandlers.ForEach(p => p.HandleFpStatusChange(FuellingPointStatus.Idle, null));
  66. };
  67. this.context.Communicator.OnDisconnected += (e, f) =>
  68. {
  69. logger.Info("node " + this.tqcPumpGroupInitializer.ifsfRecipientNode + ", OnCommunicator Disconnected, will hard set all pumps to Closed state anyway");
  70. this.pumpHandlers.ForEach(p => p.HandleFpStatusChange(FuellingPointStatus.Closed, null));
  71. };
  72. base.pumpHandlers.ForEach(p => p.Init(this.context));
  73. //this.timelyCheckConnection = new Timer(5000);
  74. //this.timelyCheckConnection.Elapsed += (_, __) =>
  75. //{
  76. // if (DateTime.Now.Subtract(this.lastTimeRecieveHearbeat).TotalSeconds >= 12)
  77. // {
  78. // //logger.Debug("Heartbeat timed out, will turn all pumps("
  79. // // + this.pumpHandlers.Select(p => p.QueryStatus().ToString()).Aggregate((acc, n) => acc + "," + n)
  80. // // + ") to Closed state");
  81. // logger.Info("node " + this.tqcPumpGroupInitializer.ifsfRecipientNode + ", have not seen heartbeat for >=12 seconds, will local set all pumps to Closed state anyway");
  82. // this.pumpHandlers.ForEach(p => p.HandleFpStatusChange(FuellingPointStatus.Closed, null));
  83. // }
  84. //};
  85. //this.timelyCheckConnection.Start();
  86. }
  87. protected override void CreatePumpHandlers(IEnumerable<XmlNode> pumpElements)
  88. {
  89. foreach (XmlNode pumpElement in pumpElements)
  90. {
  91. byte pumpPhysicalIdValue;
  92. var rawPumpPhysicalId = pumpElement.Attributes["physicalId"].Value;
  93. if (rawPumpPhysicalId.StartsWith("0x") || rawPumpPhysicalId.StartsWith("0X"))
  94. pumpPhysicalIdValue = byte.Parse(pumpElement.Attributes["physicalId"].Value.Substring(2), NumberStyles.HexNumber);
  95. else
  96. pumpPhysicalIdValue = byte.Parse(pumpElement.Attributes["physicalId"].Value);
  97. if (pumpPhysicalIdValue < 0x21 || pumpPhysicalIdValue > 0x24)
  98. throw new ArgumentException("ifsf fuel point id must be range from 0x21 to 0x24, while the configuration passed in " + rawPumpPhysicalId);
  99. PetroChinaSilentPumpHandler pumpHandler = new PetroChinaSilentPumpHandler(this, int.Parse(pumpElement.Attributes["pumpId"].Value),
  100. pumpPhysicalIdValue,
  101. base.recipientSubnet, base.recipientNode,
  102. pumpElement.InnerXml, GetNewMessageToken);
  103. base.pumpHandlers.Add(pumpHandler);
  104. }
  105. }
  106. private Dictionary<byte, List<System.Tuple<DateTime, FuellingPointDb_FpStatus_Event>>> fp_cached_states
  107. = new Dictionary<byte, List<System.Tuple<DateTime, FuellingPointDb_FpStatus_Event>>>();
  108. public override Task Process(IContext<byte[], IfsfMessageBase> context)
  109. {
  110. base.context = context;
  111. if (context.Incoming.Message is FuellingPointDb_FpStatus_Event fp_status_evt)
  112. {
  113. List<System.Tuple<DateTime, FuellingPointDb_FpStatus_Event>> cached_fp_states;
  114. if (fp_cached_states.TryGetValue(fp_status_evt.TargetFuelPointId, out cached_fp_states))
  115. cached_fp_states.Add(new System.Tuple<DateTime, FuellingPointDb_FpStatus_Event>(DateTime.Now, fp_status_evt));
  116. else
  117. {
  118. cached_fp_states = new List<System.Tuple<DateTime, FuellingPointDb_FpStatus_Event>>() {
  119. new System.Tuple<DateTime, FuellingPointDb_FpStatus_Event>(DateTime.Now, fp_status_evt) };
  120. fp_cached_states.Add(fp_status_evt.TargetFuelPointId, cached_fp_states);
  121. }
  122. }
  123. //if (context.Incoming.Message is FuellingTrxDb_TransactionBufferStatus_Event trx_evt)
  124. if (context.Incoming.Message is FuellingTrxDb_TransactionBufferStatus_Answer trx_answer)
  125. {
  126. List<System.Tuple<DateTime, FuellingPointDb_FpStatus_Event>> cached_fp_states;
  127. if (fp_cached_states.TryGetValue(trx_answer.TargetFuelPointId, out cached_fp_states))
  128. {
  129. var authorizedState_tp = cached_fp_states.OrderByDescending(t => t.Item1).FirstOrDefault(t => t.Item2.FuelPointState == FuellingPointStatus.AUTHORISED);
  130. cached_fp_states.Clear();
  131. if (authorizedState_tp != null && this.hiddenIcCardAuthorizedFuelTrxToFdcServerApp)
  132. {
  133. if (trx_answer.State.HasValue && trx_answer.State != FuellingTrxDb_TransactionBufferStatus_Event.TransactionState.PAYABLE)
  134. return Task.CompletedTask;
  135. logger.Debug($"node { this.tqcPumpGroupInitializer.ifsfRecipientNode}, FpId: 0x{trx_answer.TargetFuelPointId.ToHexLogString()} " +
  136. $"reports a trx done Answer with state: {(trx_answer.State != null ? trx_answer.State.ToString() : "")}, amt: {trx_answer.Amount}, vol: {trx_answer.Volume}, price: {trx_answer.Price}, seqNo.: {trx_answer.TransactionSeqNumber}, " +
  137. $"will be muted to FdcServerApp since a historical IfsfFp.AUTHORISED was detected " +
  138. $"with timestamp: {authorizedState_tp.Item1.ToString("MM-dd HH:mm:ss fff")}");
  139. //+
  140. //$"(other cached states are: " +
  141. //$"{cached_fp_states.Select(s => s.Item2.FuelPointState + "-" + s.Item1.ToString("MM-dd HH:mm:ss fff")).Aggregate((acc, n) => acc + "; " + n)})");
  142. //context.Outgoing.Write(new AcknowledgeMessage(
  143. // trx_answer.OriginatorSubnet, trx_answer.OriginatorNode,
  144. // PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode,
  145. // trx_answer.MessageToken,
  146. // trx_answer.DatabaseId,
  147. // MessageAcknowledgeStatus.ACK_PositiveAcknowledgeDataReceived));
  148. var targetPumpHandler = this.pumpHandlers.FirstOrDefault(ph => ph.PumpPhysicalId == trx_answer.TargetFuelPointId);
  149. var operatingFdcLogicalNozzle = targetPumpHandler?.Nozzles?.FirstOrDefault(n => n.LogicalId == trx_answer.LogicalNozzleId);
  150. this.OnPetroChinaIcCardAuthorizedFuelTrxDone?.Invoke(this,
  151. new FdcTransactionDoneEventArg((new FdcTransaction()
  152. {
  153. Nozzle = operatingFdcLogicalNozzle,
  154. Amount = trx_answer.Amount,
  155. Volumn = trx_answer.Volume,
  156. Price = int.Parse(trx_answer.Price),
  157. SequenceNumberGeneratedOnPhysicalPump = trx_answer.TransactionSeqNumber,
  158. VolumeTotalizer = Convert.ToInt32(trx_answer.VolumeTotalizerAfterTrx ?? 0 * Math.Pow(10, targetPumpHandler?.VolumeTotalizerDecimalDigits ?? 2)),
  159. Finished = true,
  160. })));
  161. return Task.CompletedTask;
  162. }
  163. }
  164. else
  165. {
  166. logger.Info($"node { this.tqcPumpGroupInitializer.ifsfRecipientNode}, FpId: 0x{trx_answer.TargetFuelPointId.ToHexLogString()} " +
  167. $"reports a trx done with state: {trx_answer.State}, amt: {trx_answer.Amount}, vol: {trx_answer.Volume}, seqNo.: {trx_answer.TransactionSeqNumber}, " +
  168. $"no historical FpState were cached for this Fp yet, will send to FdcServerApp anyway(possible duplicate, but leave FdcServerApp to filter)");
  169. }
  170. }
  171. base.RouteMessageToHandlers(context);
  172. // if (context.Incoming.Message is FuellingTrxDb_TransactionBufferStatus_Event trx_evt__)
  173. // context.Outgoing.Write(new AcknowledgeMessage(
  174. // trx_evt__.OriginatorSubnet, trx_evt__.OriginatorNode,
  175. // PumpGroupHandler.originatorSubnet, PumpGroupHandler.originatorNode,
  176. // trx_evt__.MessageToken,
  177. // trx_evt__.DatabaseId,
  178. // MessageAcknowledgeStatus.ACK_PositiveAcknowledgeDataReceived
  179. //));
  180. return Task.CompletedTask;
  181. }
  182. }
  183. }