PumpGroupHandler.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. using Edge.Core.Processor;
  2. using Edge.Core.IndustryStandardInterface.Pump;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Threading.Tasks;
  8. using System.Xml;
  9. using Edge.Core.Processor.Dispatcher.Attributes;
  10. using Edge.Core.Processor.Communicator;
  11. using Edge.Core.Parser.BinaryParser.MessageEntity;
  12. using HengShan_Pump_NonIC_Plus.MessageEntity;
  13. using Microsoft.Extensions.Logging;
  14. using Microsoft.Extensions.DependencyInjection;
  15. namespace HengShan_Pump_NonIC_Plus
  16. {
  17. [MetaPartsRequired(typeof(HalfDuplexActivePollingDeviceProcessor<,>))]
  18. [MetaPartsRequired(typeof(ComPortCommunicator<>))]
  19. [MetaPartsRequired(typeof(TcpClientCommunicator<>))]
  20. [MetaPartsDescriptor(
  21. "lang-zh-cn:HSC+ 恒山非ic卡加油机lang-en-us:HSC+ HengShan Pump NonIC",
  22. "lang-zh-cn:用于驱动 HSC+ 恒山非ic卡加油机lang-en-us:Used for driven HSC+ HengShan Pump NonIC Protocol",
  23. new[] { "lang-zh-cn:加油机lang-en-us:Pump" })]
  24. public class PumpGroupHandler : TestableActivePollingDeviceHandler<byte[], MessageTemplateBase>, IEnumerable<IFdcPumpController>, IDisposable
  25. {
  26. protected IContext<byte[], MessageTemplateBase> context;
  27. protected int pollingInterval;
  28. protected List<PumpHandler> pumpHandlers = new List<PumpHandler>();
  29. /// <summary>
  30. /// if the underlying communicator connected to remote device.
  31. /// </summary>
  32. private bool isCommConnected = false;
  33. private ILogger logger = null;
  34. private int amountDecimalDigits;
  35. private int volumeDecimalDigits;
  36. private int priceDecimalDigits;
  37. private int volumeTotalizerDecimalDigits;
  38. #region Ctor parameters
  39. public class PumpGroupConfiguration
  40. {
  41. public byte AmountDecimalDigits { get; set; }
  42. public byte VolumeDecimalDigits { get; set; }
  43. public byte PriceDecimalDigits { get; set; }
  44. public byte VolumeTotalizerDecimalDigits { get; set; }
  45. public List<PumpConfiguration> PumpConfigurations { get; set; }
  46. }
  47. public class PumpConfiguration
  48. {
  49. public byte PumpId { get; set; }
  50. public byte PhysicalId { get; set; }
  51. public List<NozzleConfiguration> NozzleConfigurations { get; set; }
  52. }
  53. public class NozzleConfiguration
  54. {
  55. public byte LogicalId { get; set; }
  56. public byte PhysicalId { get; set; }
  57. public int DefaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb { get; set; }
  58. }
  59. #endregion
  60. [ParamsJsonSchemas("PumpGroupCtorParamsJsonSchemas")]
  61. public PumpGroupHandler(PumpGroupConfiguration pumpGroupConfiguration, IServiceProvider services)
  62. {
  63. if (services != null)
  64. {
  65. var loggerFactory = services.GetRequiredService<ILoggerFactory>();
  66. logger = loggerFactory.CreateLogger("DynamicPrivate_PumpHandler");
  67. }
  68. this.amountDecimalDigits = pumpGroupConfiguration.AmountDecimalDigits;
  69. this.volumeDecimalDigits = pumpGroupConfiguration.VolumeDecimalDigits;
  70. this.priceDecimalDigits = pumpGroupConfiguration.PriceDecimalDigits;
  71. this.volumeTotalizerDecimalDigits = pumpGroupConfiguration.VolumeTotalizerDecimalDigits;
  72. logger.LogInformation("HSC+ pump group, Will create " + pumpGroupConfiguration.PumpConfigurations.Count + " pump handlers for this HSC+ Group from config");
  73. this.CreatePumpHandlers(pumpGroupConfiguration.PumpConfigurations.Select(pc =>
  74. {
  75. XmlDocument doc = new XmlDocument();
  76. var xml =
  77. "<Pump pumpId='" + pc.PumpId + "' physicalId='" + pc.PhysicalId + "'>" +
  78. "<Nozzles>" +
  79. pc.NozzleConfigurations.Select(nc =>
  80. "<Nozzle logicalId='" + nc.LogicalId + "' physicalId='" + nc.PhysicalId +
  81. "' defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb='" +
  82. nc.DefaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb + "' />").Aggregate((acc, n) => acc + n) +
  83. "</Nozzles>" +
  84. "</Pump>";
  85. doc.LoadXml(xml);
  86. return doc.FirstChild;
  87. }));
  88. }
  89. public PumpGroupHandler(int amountDecimalDigits, int volumeDecimalDigits,
  90. int priceDecimalDigits, int volumeTotalizerDecimalDigits,
  91. string pumpGroupXmlConfiguration)
  92. {
  93. this.amountDecimalDigits = amountDecimalDigits;
  94. this.volumeDecimalDigits = volumeDecimalDigits;
  95. this.priceDecimalDigits = priceDecimalDigits;
  96. this.volumeTotalizerDecimalDigits = volumeTotalizerDecimalDigits;
  97. //sample of pumpGroupXmlConfiguration, the address is a config value in physical wayne dart
  98. //pump mother board, 1 - 32 is the acceptable values.
  99. //the reason introduce PumpGroupHandler, because wayne dart pump may run multiple pumps in single
  100. //rs485 com port.
  101. //<PumpGroup>
  102. // <Pump pumpId='1' physicalId='1'>
  103. // <Nozzles>
  104. // <Nozzle logicalId='1' physicalId='1' />
  105. // <Nozzle logicalId='2' physicalId='2' />
  106. // <Nozzle logicalId='3' physicalId='3' />
  107. // </Nozzles>
  108. // </Pump>
  109. // <Pump pumpId='2' physicalId='2'>
  110. // <Nozzles>
  111. // <Nozzle logicalId='1' physicalId='1' />
  112. // <Nozzle logicalId='2' physicalId='2' />
  113. // </Nozzles>
  114. // </Pump>
  115. //</PumpGroup>
  116. XmlDocument xmlDocument = new XmlDocument();
  117. xmlDocument.LoadXml(pumpGroupXmlConfiguration);
  118. //var rootElement = xmlDocument.GetElementsByTagName("PumpGroup").Cast<XmlElement>().First();
  119. var pumpElements = xmlDocument.GetElementsByTagName("Pump").Cast<XmlNode>();
  120. logger.LogInformation("HSC+ pump group, Will create " + pumpElements.Count() + " pump handlers for this HSC+ Group from local config");
  121. this.CreatePumpHandlers(pumpElements);
  122. }
  123. protected virtual void CreatePumpHandlers(IEnumerable<XmlNode> pumpElements)
  124. {
  125. foreach (XmlNode pumpElement in pumpElements)
  126. {
  127. var pumpId =
  128. int.Parse(pumpElement.Attributes["pumpId"].Value);
  129. var pumpHandler = new PumpHandler(this, pumpId,
  130. amountDecimalDigits, volumeDecimalDigits,
  131. priceDecimalDigits, volumeTotalizerDecimalDigits,
  132. pumpElement.OuterXml, logger);
  133. this.pumpHandlers.Add(pumpHandler);
  134. }
  135. this.rotateMsgTokens.AddRange(Enumerable.Repeat(0, this.pumpHandlers.Select(s => s.PumpId).Max()));
  136. }
  137. public IEnumerator<IFdcPumpController> GetEnumerator()
  138. {
  139. return this.pumpHandlers.GetEnumerator();
  140. }
  141. IEnumerator IEnumerable.GetEnumerator()
  142. {
  143. return this.pumpHandlers.GetEnumerator();
  144. }
  145. /// <summary>
  146. /// key:value = pumpId:MsgToken
  147. /// </summary>
  148. private List<int> rotateMsgTokens = new List<int>();
  149. /// <summary>
  150. /// get a valid token which from >=1 and lessOrEqual F.
  151. /// NOTE, if ResetMessageTokenToAlign(pumpId) was called, the next token generated here will be 0, which is
  152. /// a special value used to align the comm with real pump side, and then if the comm eastablished, we
  153. /// should use 1 to F.
  154. /// </summary>
  155. /// <param name="pumpId"></param>
  156. /// <returns></returns>
  157. public byte GetNewMessageToken(int pumpId)
  158. {
  159. lock (rotateMsgTokens)
  160. {
  161. if (rotateMsgTokens[pumpId - 1] > 0x0E)
  162. this.rotateMsgTokens[pumpId - 1] = 0;
  163. //var returnValue = this.rotateMsgTokens[pumpId - 1];
  164. //this.rotateMsgTokens[pumpId - 1] += 1;
  165. return (byte)(++this.rotateMsgTokens[pumpId - 1]);
  166. }
  167. }
  168. /// <summary>
  169. /// typically called after a data request was NAKed by pump side, 0 token used for
  170. /// re-align the comm from FC to real pump.
  171. /// </summary>
  172. /// <param name="pumpId"></param>
  173. public void ResetMessageTokenToAlign(int pumpId)
  174. {
  175. lock (rotateMsgTokens)
  176. this.rotateMsgTokens[pumpId - 1] = -1;
  177. }
  178. public override void Init(IContext<byte[], MessageTemplateBase> context)
  179. {
  180. base.Init(context);
  181. this.context = context;
  182. this.context.Communicator.OnConnected += (object sender, EventArgs e) => { this.isCommConnected = true; };
  183. this.context.Communicator.OnDisconnected += (object sender, EventArgs e) => { this.isCommConnected = false; };
  184. this.pumpHandlers.ForEach(p => p.Init(this.context));
  185. var timeWindowWithActivePollingOutgoing =
  186. this.context.Outgoing as TimeWindowWithActivePollingOutgoing<byte[], MessageTemplateBase>;
  187. this.pollingInterval = timeWindowWithActivePollingOutgoing.PollingInterval;
  188. int previousPolledHandlerIndex = 0;
  189. timeWindowWithActivePollingOutgoing.PollingMsgProducer =
  190. () =>
  191. {
  192. try
  193. {
  194. if (this.pumpHandlers.Count <= previousPolledHandlerIndex)
  195. previousPolledHandlerIndex = 0;
  196. var target = this.pumpHandlers[previousPolledHandlerIndex++];
  197. return target.GetRequest();
  198. //return new AckActivePushTransactionRequest(3);
  199. //return new SetFuelPriceRequest(3) { FuelPrice = 703 };
  200. }
  201. catch (Exception exxx)
  202. {
  203. logger.LogError("Exceptioned in HengShan_NonIC_Plus PumpGroupHandler(previousPolledHandlerIndex: " + previousPolledHandlerIndex + "): " + exxx);
  204. return null;
  205. }
  206. };
  207. }
  208. public override Task Process(IContext<byte[], MessageTemplateBase> context)
  209. {
  210. this.context = context;
  211. byte nozzle = (context.Incoming.Message as NonICMessageTemplateResponseBase).Nozzle;
  212. var ph = this.pumpHandlers.First(p => p.Nozzles.Any(n => n.PhysicalId == nozzle));
  213. if (ph == null)
  214. {
  215. logger.LogError("PumpGroupHandler does not contain pumpHandler.");
  216. return Task.CompletedTask;
  217. }
  218. return ph.Process(context);
  219. }
  220. public void Dispose()
  221. {
  222. this.pumpHandlers.ForEach(ph => ph.Dispose());
  223. }
  224. }
  225. }