PumpHandler.cs 83 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389
  1. using Edge.Core.Database.Models;
  2. using Edge.Core.Processor;
  3. using Edge.Core.IndustryStandardInterface.Pump;
  4. using Edge.Core.Parser.BinaryParser.Util;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using System.Xml;
  12. using Wayne.FDCPOSLibrary;
  13. using Wayne_Pump_Dart.MessageEntity;
  14. using Wayne_Pump_Dart.MessageEntity.Incoming;
  15. using Wayne_Pump_Dart.MessageEntity.Outgoing;
  16. namespace Wayne_Pump_Dart
  17. {
  18. public class PumpHandler : IFdcPumpController, IDisposable//, IHandler<byte[], MessageBase>
  19. {
  20. //static ILog logger = log4net.LogManager.GetLogger("PumpHandler");
  21. static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("PumpHandler");
  22. private LogicalDeviceState lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE;
  23. private DateTime lastLogicalDeviceStateReceivedTime;
  24. // by seconds, change this value need change the correlated deviceOfflineCountdownTimer's interval as well
  25. public const int lastLogicalDeviceStateExpiredTime = 9;
  26. private List<LogicalNozzle> nozzles = new List<LogicalNozzle>();
  27. private System.Timers.Timer deviceOfflineCountdownTimer;
  28. /// <summary>
  29. /// will set to true once nozzle change to OUT while internal fdc state is FDC_FUELING, indicates a fule is done.
  30. /// 'true' will block pump auth request from outer
  31. /// </summary>
  32. private bool lastFillRetrievedSinceNozzleDownAtFdcFueling = true;
  33. /// <summary>
  34. /// the pump calling, fuelling message does not contain nozzle info, need track the operating nozzle
  35. /// seperatly in other message which will be kept in this variable.
  36. /// default set to 0 which is an invalid nozzle id for wayne dart pump(valid should starts from 1).
  37. /// </summary>
  38. private byte operatingNozzlePhysicalId = 0;
  39. private IContext<byte[], MessageBase> context;
  40. private bool isOnFdcServerInitCalled = false;
  41. private int pumpId;
  42. private PumpGroupHandler parent;
  43. /// <summary>
  44. /// address used in wayne dart protocol to comm with physical pump.
  45. /// 0x4F + physical pump mother board side config address(range from 1-32)
  46. /// </summary>
  47. private byte dartPumpCommAddress;
  48. /// <summary>
  49. /// when first time connected with physical pump , in some case, the pump will not report any status actively,
  50. /// so need send a status query from FC.
  51. /// From then on, pump will actively notify FC when state changes, no need to send query anymore from FC.
  52. /// </summary>
  53. private bool initialPumpStatueEverRetrieved = false;
  54. private int amountDecimalDigits;
  55. private int volumeDecimalDigits;
  56. private int priceDecimalDigits;
  57. private int volumeTotalizerDecimalDigits;
  58. /// <summary>
  59. /// for avoid a case that FC may miss a pump status change event(pump side issue? or wire issue?),
  60. /// we timely actively request the pump state.
  61. /// </summary>
  62. //private System.Timers.Timer pollingPumpStatus;
  63. //private int pollingPumpStatusInterval = 30000;
  64. #region MyRegion
  65. public string Name => "Wayne_Pump_Dart";
  66. public int PumpId => this.pumpId;
  67. /// <summary>
  68. /// Gets the pump physical id.
  69. /// address used in wayne dart protocol to comm with physical pump.
  70. /// 0x4F + physical pump mother board side config address(range from 1-32)
  71. /// </summary>
  72. public int PumpPhysicalId => this.dartPumpCommAddress;
  73. public IEnumerable<LogicalNozzle> Nozzles => this.nozzles;
  74. public int AmountDecimalDigits => this.amountDecimalDigits;
  75. public int VolumeDecimalDigits => this.volumeDecimalDigits;
  76. public int PriceDecimalDigits => this.priceDecimalDigits;
  77. public int VolumeTotalizerDecimalDigits => this.volumeDecimalDigits;
  78. public Guid Id => Guid.NewGuid();
  79. public event EventHandler<FdcPumpControllerOnStateChangeEventArg> OnStateChange;
  80. public event EventHandler<FdcTransactionDoneEventArg> OnCurrentFuellingStatusChange;
  81. private async Task<bool> InternalAuthorizeAsync()
  82. {
  83. if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
  84. {
  85. logger.Info("Pump: " + this.pumpId + ", "
  86. + "Start Authorize pump is denied by internal");
  87. return false;
  88. }
  89. var authorizeSucceed = false;
  90. var authroizedResponse = await this.context.Outgoing.WriteAsync(
  91. new AuthorizeRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
  92. (_, testResponse) =>
  93. testResponse.Adrs == this.dartPumpCommAddress &&
  94. testResponse.BlockSeqNumber == _.BlockSeqNumber, 3500);
  95. if (authroizedResponse != null)
  96. {
  97. if (authroizedResponse.ControlCharacter == ControlCharacter.ACK)
  98. {
  99. logger.Info("Pump: " + this.pumpId + ", " + "AuthorizeRequest ACKed");
  100. authorizeSucceed = true;
  101. }
  102. else
  103. {
  104. authorizeSucceed = false;
  105. logger.Info("Pump: " + this.pumpId + ", " + "AuthorizeRequest NAKed");
  106. }
  107. }
  108. else
  109. {
  110. logger.Info("Pump: " + this.pumpId + ", " + "failed in get AuthorizeRequest ACK(timed out)");
  111. }
  112. return authorizeSucceed;
  113. }
  114. public async Task<bool> AuthorizeAsync(byte logicalNozzleId)
  115. {
  116. if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
  117. {
  118. logger.Info("Pump: " + this.pumpId + ", "
  119. + "Start Authorize pump with logicalNozzle: " + logicalNozzleId + " is denied by internal");
  120. return false;
  121. }
  122. logger.Info("Pump: " + this.pumpId + ", " + "Start Authorize pump with logicalNozzle: " + logicalNozzleId + ", first send AllowedNozzleNumbersRequest(allow all)");
  123. // now always allow all nozzles
  124. this.context.Outgoing.Write(
  125. new AllowedNozzleNumbersRequest(this.dartPumpCommAddress,
  126. this.parent.GetNewMessageToken(this.pumpId),
  127. this.nozzles.Select(n => n.PhysicalId).ToArray()));
  128. return await this.InternalAuthorizeAsync();
  129. }
  130. public async Task<bool> AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId)
  131. {
  132. if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
  133. {
  134. logger.Info("Pump: " + this.pumpId + ", "
  135. + "Start AuthorizeWithAmount pump with logicalNozzle: " + logicalNozzleId + " is denied by internal");
  136. return false;
  137. }
  138. logger.Info("Pump: " + this.pumpId + ", " + "start AuthorizeWithAmount pump with logicalNozzle: " + logicalNozzleId + ", moneyAmount: " + moneyAmountWithoutDecimalPoint + ", first send AllowedNozzleNumbersRequest(allow all)");
  139. // now always allow all nozzles
  140. this.context.Outgoing.Write(
  141. new AllowedNozzleNumbersRequest(this.dartPumpCommAddress,
  142. this.parent.GetNewMessageToken(this.pumpId),
  143. this.nozzles.Select(n => n.PhysicalId).ToArray()));
  144. var authorizeSucceed = false;
  145. var response = await this.context.Outgoing.WriteAsync(
  146. new PresetAmountRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId), moneyAmountWithoutDecimalPoint),
  147. (request, testResponse) =>
  148. testResponse.Adrs == this.dartPumpCommAddress &&
  149. testResponse.BlockSeqNumber == request.BlockSeqNumber, 3500);
  150. if (response != null)
  151. {
  152. if (response.ControlCharacter == ControlCharacter.ACK)
  153. {
  154. logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest ACKed, will send InternalAuthorize()");
  155. //InternalAuthorize is a blocking call, should not stop the I/O thread for comm with pump device.
  156. //ThreadPool.QueueUserWorkItem(o =>
  157. //new Thread(() =>
  158. //{
  159. // if (this.InternalAuthorize())
  160. // authorizeSucceed = true;
  161. // else
  162. // authorizeSucceed = false;
  163. //}).Start();
  164. var _ = await this.InternalAuthorizeAsync();
  165. if (_) authorizeSucceed = true; else authorizeSucceed = false;
  166. }
  167. else
  168. {
  169. logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest NAKed");
  170. authorizeSucceed = false;
  171. }
  172. }
  173. else
  174. {
  175. logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest waiting ACK timed out");
  176. authorizeSucceed = false;
  177. }
  178. return authorizeSucceed;
  179. }
  180. public async Task<bool> AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId)
  181. {
  182. if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
  183. {
  184. logger.Info("Pump: " + this.pumpId + ", "
  185. + "Start AuthorizeWithVolumn pump with logicalNozzle: " + logicalNozzleId + " is denied by internal");
  186. return false;
  187. }
  188. logger.Info("Pump: " + this.pumpId + ", " + "start AuthorizeWithVolumn pump with logicalNozzle: " + logicalNozzleId + ", moneyAmount: " + volumnWithoutDecimalPoint + ", first send AllowedNozzleNumbersRequest(allow all)");
  189. // now always allow all nozzles
  190. this.context.Outgoing.Write(
  191. new AllowedNozzleNumbersRequest(this.dartPumpCommAddress,
  192. this.parent.GetNewMessageToken(this.pumpId),
  193. this.nozzles.Select(n => n.PhysicalId).ToArray()));
  194. var authorizeSucceed = false;
  195. var response = await this.context.Outgoing.WriteAsync(
  196. new PresetVolumeRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId), volumnWithoutDecimalPoint),
  197. (request, testResponse) =>
  198. testResponse.Adrs == this.dartPumpCommAddress &&
  199. testResponse.BlockSeqNumber == request.BlockSeqNumber, 3500);
  200. if (response != null)
  201. {
  202. if (response.ControlCharacter == ControlCharacter.ACK)
  203. {
  204. logger.Info("Pump: " + this.pumpId + ", " + "PresetVolumeRequest ACKed, will send InternalAuthorize()");
  205. //InternalAuthorize is a blocking call, should not stop the I/O thread for comm with pump device.
  206. //ThreadPool.QueueUserWorkItem(o =>
  207. //{
  208. // if (this.InternalAuthorize())
  209. // authorizeSucceed = true;
  210. // else
  211. // authorizeSucceed = false;
  212. // blocker.Set();
  213. //});
  214. //new Thread(() =>
  215. //{
  216. // if (this.InternalAuthorize())
  217. // authorizeSucceed = true;
  218. // else
  219. // authorizeSucceed = false;
  220. // blocker.Set();
  221. //}).Start();
  222. var _ = await this.InternalAuthorizeAsync();
  223. if (_) authorizeSucceed = true; else authorizeSucceed = false;
  224. }
  225. else
  226. {
  227. logger.Info("Pump: " + this.pumpId + ", " + "PresetVolumeRequest NAKed");
  228. authorizeSucceed = false;
  229. }
  230. }
  231. else
  232. {
  233. logger.Info("Pump: " + this.pumpId + ", " + "PresetVolumeRequest waiting ACK timed out");
  234. authorizeSucceed = false;
  235. }
  236. return authorizeSucceed;
  237. }
  238. public Task<bool> ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId)
  239. {
  240. try
  241. {
  242. var nozzlesPriceListAsendingByNozzlePhysicalId =
  243. this.nozzles.OrderBy(k => k.PhysicalId).Select(s => s.ExpectingPriceOnFcSide ?? 0);
  244. logger.Info("Pump: " + this.pumpId + ", " + "Change Fuel Price for LogicalNozzle: " + logicalNozzleId
  245. + ", physicalNozzle: " + this.nozzles.First(n => n.LogicalId == logicalNozzleId).PhysicalId
  246. + " with new price(without decimal): " + newPriceWithoutDecimalPoint
  247. + "(old price list for all nozzles based on physicalId from 1 to n: "
  248. + nozzlesPriceListAsendingByNozzlePhysicalId.Select(s => s.ToString()).Aggregate((n, acc) => n + ", " + acc) + ")");
  249. this.nozzles.First(n => n.LogicalId == logicalNozzleId).ExpectingPriceOnFcSide = newPriceWithoutDecimalPoint;
  250. return this.InternalChangeFuelPriceAsync(nozzlesPriceListAsendingByNozzlePhysicalId.ToList());
  251. }
  252. catch (Exception exxx)
  253. {
  254. logger.Error("Pump: " + this.pumpId + ", " + "Exceptioned in ChangeFuelPrice: " + exxx);
  255. return Task.FromResult(false);
  256. }
  257. }
  258. /// <summary>
  259. /// Wayne dart price change is always targeting all nozzles.
  260. /// </summary>
  261. /// <param name="newPricesFromPhysicalNozzleFirstToLast">price without decimal points from nozzle 1 to N</param>
  262. /// <returns></returns>
  263. private async Task<bool> InternalChangeFuelPriceAsync(List<int> newPricesFromPhysicalNozzleFirstToLast)
  264. {
  265. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED
  266. || this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE)
  267. {
  268. logger.Info("Pump: " + this.pumpId + ", " + " Pump is in state FDC_CLOSED or FDC_OFFLINE, InternalChangeFuelPrice will return false");
  269. return false;
  270. }
  271. if (newPricesFromPhysicalNozzleFirstToLast.Count != this.nozzles.Count)
  272. throw new ArgumentException("Wayne dart pump price change must provide prices for total " + this.nozzles.Count
  273. + " nozzles, but now only pass in " + newPricesFromPhysicalNozzleFirstToLast.Count);
  274. bool changePriceSucceed = false;
  275. logger.Info("Pump: " + this.pumpId + ", " + " InternalChangeFuelPrice starting with prices: "
  276. + newPricesFromPhysicalNozzleFirstToLast.Select(p => p.ToString()).Aggregate((acc, n) => acc + ", " + n));
  277. var priceChangedResponse = await this.context.Outgoing.WriteAsync(
  278. new PriceUpdateRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId), newPricesFromPhysicalNozzleFirstToLast)
  279. , (_, testResponse) =>
  280. testResponse.Adrs == this.dartPumpCommAddress &&
  281. testResponse.BlockSeqNumber == _.BlockSeqNumber, 2000);
  282. if (priceChangedResponse != null && priceChangedResponse.ControlCharacter == ControlCharacter.ACK)
  283. {
  284. changePriceSucceed = true;
  285. try
  286. {
  287. for (int i = 0; i < newPricesFromPhysicalNozzleFirstToLast.Count; i++)
  288. {
  289. this.nozzles.First(n => n.PhysicalId == i + 1).RealPriceOnPhysicalPump = newPricesFromPhysicalNozzleFirstToLast[i];
  290. //this.nozzles.First(n => n.PhysicalId == i + 1).ExpectingPriceOnFcSide = newPricesFromPhysicalNozzleFirstToLast[i];
  291. }
  292. logger.Info("Pump: " + this.pumpId + ", " + " InternalChangeFuelPrice done succeed");
  293. }
  294. catch (Exception exxx)
  295. {
  296. logger.Info("InternalChangeFuelPrice partially succeed with exception:" + exxx);
  297. }
  298. }
  299. else if (priceChangedResponse != null && priceChangedResponse.ControlCharacter == ControlCharacter.NAK)
  300. {
  301. logger.Error("Pump: " + this.pumpId + ", " + "InternalChangeFuelPrice is denied (NAK) by wayne dart pump, will reset msg token to 0 for align.");
  302. //reset id to 0 to re-align, wayne dart have this check!
  303. this.parent.ResetMessageTokenToAlign(this.pumpId);
  304. }
  305. else
  306. {
  307. logger.Error("Pump: " + this.pumpId + ", " + "InternalChangeFuelPrice failed with timeout");
  308. }
  309. // this FC handle WayneDart pump price change one nozzle by nozzle, so change prices on single FuelPoint with multiple
  310. // nozzle will interpreted as multiple price change request, by testing, too fast send multiple price change request
  311. // to a FP might be ignored by pump side though message here are all good, so hardcode sleep a while.
  312. Thread.Sleep(1000);
  313. return changePriceSucceed;
  314. }
  315. public async Task<bool> FuelingRoundUpByAmountAsync(int amount)
  316. {
  317. logger.Info("Pump: " + this.pumpId + ", " + "start FuelingRoundUpByAmount with amount: " + amount);
  318. var isSucceed = false;
  319. this.context.Outgoing.WriteAsync(
  320. new PresetAmountRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId), amount),
  321. (request, testResponse) =>
  322. testResponse.Adrs == this.dartPumpCommAddress &&
  323. testResponse.BlockSeqNumber == request.BlockSeqNumber,
  324. (request, response) =>
  325. {
  326. if (response != null)
  327. {
  328. if (response.ControlCharacter == ControlCharacter.ACK)
  329. {
  330. logger.Debug("Pump: " + this.pumpId + ", " + "PresetAmountRequest ACKed");
  331. isSucceed = true;
  332. }
  333. else
  334. {
  335. logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest NAKed");
  336. isSucceed = false;
  337. }
  338. }
  339. else
  340. {
  341. logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest waiting ACK timed out");
  342. isSucceed = false;
  343. }
  344. }, 1500);
  345. return isSucceed;
  346. }
  347. public async Task<bool> FuelingRoundUpByVolumeAsync(int volume)
  348. {
  349. return false;
  350. }
  351. public async Task<global::Wayne.FDCPOSLibrary.LogicalDeviceState> QueryStatusAsync()
  352. {
  353. return this.lastLogicalDeviceState;
  354. }
  355. public async Task<System.Tuple<int, int>> QueryTotalizerAsync(byte logicalNozzleId)
  356. {
  357. var result = new System.Tuple<int, int>(-1, -1);
  358. logger.Info("Pump: " + this.pumpId + ", " + "start QueryTotalizer pump with nozzle: " + logicalNozzleId);
  359. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED
  360. || this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE)
  361. {
  362. logger.Info("Pump: " + this.pumpId + ", " + " Pump is in state FDC_CLOSED or FDC_OFFLINE, totalizer will return -1, -1");
  363. return new System.Tuple<int, int>(-1, -1);
  364. }
  365. var nozzlePhysicalId = this.nozzles.FirstOrDefault(n => n.LogicalId == logicalNozzleId)?.PhysicalId;
  366. if (nozzlePhysicalId == null)
  367. {
  368. logger.Info("Pump: " + this.pumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " does not exists, totalizer will return -1, -1");
  369. return new System.Tuple<int, int>(-1, -1);
  370. }
  371. var response = await this.context.Outgoing.WriteAsync(new RequestTotalVolumeCountersRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId),
  372. nozzlePhysicalId.Value),
  373. (request, testResponse) =>
  374. testResponse.Adrs == this.dartPumpCommAddress &&
  375. testResponse.ControlCharacter == ControlCharacter.DATA, 3000);
  376. if (response != null)
  377. {
  378. this.context.Outgoing.Write(new ACK(this.dartPumpCommAddress, response.BlockSeqNumber));
  379. var totalCountersTrx =
  380. new TotalCounters_TransactionData(response.TransactionDatas.First(f => f.TransactionNumber == 0x65));
  381. logger.Info("Pump: " + this.pumpId + ", " + "QueryTotalizer for nozzle: " + logicalNozzleId + " succeed, volume total: " + totalCountersTrx.TotalValue);
  382. result = new System.Tuple<int, int>(-1, totalCountersTrx.TotalValue);
  383. }
  384. else
  385. {
  386. logger.Error("Pump: " + this.pumpId + ", " + "QueryTotalizer waiting Data timed out");
  387. }
  388. return result;
  389. }
  390. public async Task<bool> ResumeFuellingAsync()
  391. {
  392. throw new NotImplementedException();
  393. }
  394. public async Task<bool> SuspendFuellingAsync()
  395. {
  396. throw new NotImplementedException();
  397. }
  398. /// <summary>
  399. /// unauthorize the authed fueling point, will trigger wayne dart pump switched to `FILLING COMPLETE` state.
  400. /// </summary>
  401. /// <param name="logicalNozzleId">wayne dart no need specify nozzle id</param>
  402. /// <returns></returns>
  403. public async Task<bool> UnAuthorizeAsync(byte logicalNozzleId)
  404. {
  405. var unauthorizeSucceed = false;
  406. logger.Info("Pump: " + this.pumpId + ", " + "Start UnAuthorize pump with nozzle: " + logicalNozzleId);
  407. var authorizedResponse = await this.context.Outgoing.WriteAsync(
  408. new StopRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
  409. (_, testResponse) =>
  410. testResponse.Adrs == this.dartPumpCommAddress &&
  411. testResponse.BlockSeqNumber == _.BlockSeqNumber, 3500);
  412. if (authorizedResponse != null)
  413. {
  414. if (authorizedResponse.ControlCharacter == ControlCharacter.ACK)
  415. {
  416. logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest ACKed and succeed");
  417. unauthorizeSucceed = true;
  418. }
  419. else
  420. logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest NAKed (rejected?)");
  421. }
  422. else
  423. {
  424. logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest timed out");
  425. }
  426. return unauthorizeSucceed;
  427. }
  428. public async Task<bool> LockNozzleAsync(byte logicalNozzleId)
  429. {
  430. return false;
  431. }
  432. public async Task<bool> UnlockNozzleAsync(byte logicalNozzleId)
  433. {
  434. return false;
  435. }
  436. #endregion
  437. public PumpHandler(PumpGroupHandler parent, int pumpId,
  438. int amountDecimalDigits, int volumeDecimalDigits,
  439. int priceDecimalDigits, int volumeTotalizerDecimalDigits,
  440. string pumpXmlConfiguration)
  441. {
  442. this.parent = parent;
  443. this.pumpId = pumpId;
  444. this.amountDecimalDigits = amountDecimalDigits;
  445. this.volumeDecimalDigits = volumeDecimalDigits;
  446. this.priceDecimalDigits = priceDecimalDigits;
  447. this.volumeTotalizerDecimalDigits = volumeTotalizerDecimalDigits;
  448. // sample of pumpXmlConfiguration
  449. // <Pump pumpId='1' physicalId='1'>
  450. // <Nozzles>
  451. // <Nozzle logicalId='1' physicalId='1' defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb='2345'/>
  452. // <Nozzle logicalId='2' physicalId='2' defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb='2345'/>
  453. // <Nozzle logicalId='3' physicalId='3' defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb='2345'/>
  454. // </Nozzles>
  455. // </Pump>
  456. var xmlDocument = new XmlDocument();
  457. xmlDocument.LoadXml(pumpXmlConfiguration);
  458. var physicalPumpAddressConfiguratedInPump =
  459. byte.Parse(xmlDocument.SelectSingleNode("/Pump").Attributes["physicalId"].Value);
  460. if (physicalPumpAddressConfiguratedInPump > 0x20) throw new ArgumentOutOfRangeException("Wayne dart pump only accept pump address range from 1 to 32, make sure this value is correctly configurated in physical pump mother board");
  461. this.dartPumpCommAddress = (byte)(0x4F + physicalPumpAddressConfiguratedInPump);
  462. foreach (var nozzleElement in xmlDocument.GetElementsByTagName("Nozzle").Cast<XmlNode>())
  463. {
  464. var nozzlePhysicalId = byte.Parse(nozzleElement.Attributes["physicalId"].Value);
  465. var nozzleLogicalId = byte.Parse(nozzleElement.Attributes["logicalId"].Value);
  466. var nozzleRawDefaultPriceWithoutDecimal = nozzleElement.Attributes["defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb"].Value;
  467. if (nozzlePhysicalId < 1 || nozzlePhysicalId > 8) throw new ArgumentOutOfRangeException("Wayne dart pump only accept nozzle physical id range in config from 1 to 8");
  468. this.nozzles.Add(new LogicalNozzle(pumpId, nozzlePhysicalId, nozzleLogicalId, null) { ExpectingPriceOnFcSide = int.Parse(nozzleRawDefaultPriceWithoutDecimal) });
  469. logger.Info("Pump: " + this.pumpId
  470. + ", created a nozzle with logicalId: " + nozzleLogicalId + ", physicalId: " + nozzlePhysicalId
  471. + ", default raw price without decimal points: " + nozzleRawDefaultPriceWithoutDecimal);
  472. }
  473. this.deviceOfflineCountdownTimer = new System.Timers.Timer(3000);
  474. this.deviceOfflineCountdownTimer.Elapsed += (_, __) =>
  475. {
  476. if (DateTime.Now.Subtract(this.lastLogicalDeviceStateReceivedTime).TotalSeconds
  477. >= lastLogicalDeviceStateExpiredTime)
  478. {
  479. this.initialPumpStatueEverRetrieved = false;
  480. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_OFFLINE)
  481. {
  482. this.lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE;
  483. logger.Info("Pump: " + this.pumpId + ", " + " State switched to FDC_OFFLINE due to long time no see pump data incoming");
  484. var safe0 = this.OnStateChange;
  485. safe0?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_OFFLINE, null));
  486. logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  487. }
  488. }
  489. };
  490. this.deviceOfflineCountdownTimer.Start();
  491. }
  492. public void OnFdcServerInit(Dictionary<string, object> parameters)
  493. {
  494. /* Wayne Dart pump will miss last price when disconnected or power off
  495. * from FC a while(pump state `PUMP NOT PROGRAMMED` indicates this happened), so here
  496. * is trying to recover the price from Fdc database, and then push to Pump*/
  497. if (parameters.ContainsKey("LastPriceChange"))
  498. {
  499. // nozzle logical id:rawPrice
  500. var lastPriceChanges = parameters["LastPriceChange"] as Dictionary<byte, int>;
  501. foreach (var priceChange in lastPriceChanges)
  502. {
  503. logger.Info("Pump: " + this.pumpId + ", " + "Pump " + this.pumpId + " OnFdcServerInit, load last price change " +
  504. "on logical nozzle: " + priceChange.Key + " with price: " + priceChange.Value);
  505. this.nozzles.First(n => n.LogicalId == priceChange.Key).ExpectingPriceOnFcSide = priceChange.Value;
  506. }
  507. }
  508. /* Load Last sale trx(from db) for void the case of FC accidently disconnect from Pump in fueling,
  509. and may cause a fueling trx gone from FC control */
  510. if (parameters.ContainsKey("LastFuelSaleTrx"))
  511. {
  512. // nozzle logical id:LastSale
  513. var lastFuelSaleTrxes = parameters["LastFuelSaleTrx"] as Dictionary<byte, FuelSaleTransaction>;
  514. foreach (var lastFuelSaleTrx in lastFuelSaleTrxes)
  515. {
  516. logger.Info("Pump: " + this.pumpId + ", OnFdcServerInit, load last volume Totalizer " +
  517. "on logical nozzle: " + lastFuelSaleTrx.Key + " with volume value: " + lastFuelSaleTrx.Value.VolumeTotalizer);
  518. this.nozzles.First(n => n.LogicalId == lastFuelSaleTrx.Key).VolumeTotalizer = lastFuelSaleTrx.Value.VolumeTotalizer;
  519. }
  520. }
  521. this.isOnFdcServerInitCalled = true;
  522. }
  523. public void Init(IContext<byte[], MessageBase> context)
  524. {
  525. this.context = context;
  526. this.context.Communicator.OnConnected += (a, b) =>
  527. {
  528. //this.context.Outgoing.Write(new ReturnStatusRequest(this.dartPumpCommAddress, 0));
  529. };
  530. //this.context.Communicator.OnDisconnected += (a, b) => this.pumpStatusEverReceived = false;
  531. }
  532. public async Task Process(IContext<byte[], MessageBase> context)
  533. {
  534. if (context.Incoming.Message.ControlCharacter == ControlCharacter.DATA)
  535. this.context.Outgoing.Write(new ACK(this.dartPumpCommAddress, context.Incoming.Message.BlockSeqNumber));
  536. if (!isOnFdcServerInitCalled) return;
  537. if (context.Incoming.Message.ControlCharacter == ControlCharacter.NAK)
  538. {
  539. logger.Info("Pump: " + this.pumpId + ", " + " received a NAK, will set msg token to 0 for re-align");
  540. this.parent.ResetMessageTokenToAlign(this.pumpId);
  541. }
  542. this.lastLogicalDeviceStateReceivedTime = DateTime.Now;
  543. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE)
  544. {
  545. logger.Info("Pump: " + this.pumpId + ", " + "Recevied an Pump Msg in FDC_OFFLINE state, " +
  546. "indicates the underlying connection is established, switch to FDC_READY");
  547. this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
  548. var safe = this.OnStateChange;
  549. safe?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
  550. logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  551. }
  552. if (!this.initialPumpStatueEverRetrieved)
  553. {
  554. // mute for next incoming request
  555. this.initialPumpStatueEverRetrieved = true;
  556. logger.Info("Pump: " + this.pumpId + ", " + "Never received pump status, send ReturnStatusRequest right now");
  557. // capture the EOT response here for avoid infinite loop: FC send ReturnStatusRequest, FC received EOT, and send ReturnStatusRequest again...
  558. this.context.Outgoing.WriteAsync(
  559. new ReturnStatusRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
  560. (request, testResponse) =>
  561. testResponse.Adrs == this.dartPumpCommAddress &&
  562. testResponse.BlockSeqNumber == request.BlockSeqNumber,
  563. (request, statusResponse) =>
  564. {
  565. if (statusResponse == null)
  566. {
  567. this.initialPumpStatueEverRetrieved = false;
  568. logger.Info("Pump: " + this.pumpId + ", " + " ReturnStatusRequest timed out wait for pump EOT");
  569. }
  570. else if (statusResponse.ControlCharacter == ControlCharacter.NAK)
  571. {
  572. logger.Info("Pump: " + this.pumpId + ", " + " ReturnStatusRequest NAKed, will set msg token to 0 to align and send again");
  573. //reset id to 0 to re-align, iGEN board wayne dart have this check!
  574. this.parent.ResetMessageTokenToAlign(this.pumpId);
  575. this.initialPumpStatueEverRetrieved = false;
  576. }
  577. }, 1500);
  578. }
  579. if (context.Incoming.Message.ControlCharacter == ControlCharacter.DATA)
  580. {
  581. var nozzleStatusAndFillingPriceTrxData = context.Incoming.Message.TransactionDatas.LastOrDefault(d => d.TransactionNumber == 0x03);
  582. var pumpStatusTrxData = context.Incoming.Message.TransactionDatas.FirstOrDefault(d => d.TransactionNumber == 0x01);
  583. var filledVolumeAndAmountTrxData = context.Incoming.Message.TransactionDatas.FirstOrDefault(d => d.TransactionNumber == 0x02);
  584. NozzleStatusAndFillingPrice_TransactionData nozzleStatusTrx = null;
  585. PumpStatus_TransactionData pumpStatusTrx = null;
  586. FilledVolumeAndAmount_TransactionData filledVolAndAmtTrx = null;
  587. var overallStateLogStr = "Pump: " + this.pumpId + ", " + ">>>>>>>>>>===start====(internalFdcState: "
  588. + this.lastLogicalDeviceState + ")" + System.Environment.NewLine;
  589. if (nozzleStatusAndFillingPriceTrxData != null)
  590. {
  591. nozzleStatusTrx = new NozzleStatusAndFillingPrice_TransactionData(nozzleStatusAndFillingPriceTrxData);
  592. overallStateLogStr += "Nozzle with physical id: "
  593. + nozzleStatusTrx.Status.Key
  594. + " is in state: " + nozzleStatusTrx.Status.Value
  595. + ", filling price: " + nozzleStatusTrx.FillingPrice + System.Environment.NewLine;
  596. }
  597. if (pumpStatusTrxData != null)
  598. {
  599. pumpStatusTrx = new PumpStatus_TransactionData(pumpStatusTrxData);
  600. overallStateLogStr += "WayneDart State: "
  601. + pumpStatusTrx.Status + System.Environment.NewLine;
  602. }
  603. if (filledVolumeAndAmountTrxData != null)
  604. {
  605. filledVolAndAmtTrx = new FilledVolumeAndAmount_TransactionData(filledVolumeAndAmountTrxData);
  606. overallStateLogStr += "Filled vol "
  607. + filledVolAndAmtTrx.FilledVolume
  608. + ", amt " + filledVolAndAmtTrx.FilledAmount + System.Environment.NewLine;
  609. }
  610. logger.Info(overallStateLogStr + "<<<<<<<<<<===end====");
  611. if (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.PUMP_NOT_PROGRAMMED)
  612. {
  613. #region PUMP_NOT_PROGRAMMED
  614. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_ERRORSTATE)
  615. {
  616. logger.Debug("Pump: " + this.pumpId + ", " + " NEW State is PUMP_NOT_PROGRAMMED, indicates price not set yet, State switched to FDC_ERRORSTATE");
  617. this.lastLogicalDeviceState = LogicalDeviceState.FDC_ERRORSTATE;
  618. var safe0 = this.OnStateChange;
  619. safe0?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_ERRORSTATE));
  620. logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  621. }
  622. logger.Debug("Pump: " + this.pumpId + ", " + " Pump status is PUMP_NOT_PROGRAMMED, will set price");
  623. //ThreadPool.QueueUserWorkItem(o =>
  624. //{
  625. try
  626. {
  627. var nozzlesPriceListAsendingByNozzlePhysicalId =
  628. this.nozzles.OrderBy(k => k.PhysicalId).Select(s => s.ExpectingPriceOnFcSide ?? 0).ToList();
  629. var cpResponse = await this.InternalChangeFuelPriceAsync(nozzlesPriceListAsendingByNozzlePhysicalId);
  630. if (cpResponse)
  631. logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) succeed");
  632. else
  633. {
  634. logger.Error("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) failed, will retry 1st time");
  635. cpResponse = await this.InternalChangeFuelPriceAsync(nozzlesPriceListAsendingByNozzlePhysicalId);
  636. if (cpResponse)
  637. logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) succeed in 1st retry");
  638. else
  639. {
  640. logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) failed, will retry 2nd time");
  641. cpResponse = await this.InternalChangeFuelPriceAsync(nozzlesPriceListAsendingByNozzlePhysicalId);
  642. if (cpResponse)
  643. logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) succeed in 2nd retry");
  644. else
  645. {
  646. logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) failed, will retry 3rd time");
  647. cpResponse = await this.InternalChangeFuelPriceAsync(nozzlesPriceListAsendingByNozzlePhysicalId);
  648. if (cpResponse)
  649. logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) succeed in 3rd retry");
  650. else
  651. logger.Error("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) failed again in 3rd retry, will stop");
  652. }
  653. }
  654. this.context.Outgoing.Write(
  655. new ResetRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)));
  656. }
  657. }
  658. catch (Exception exx)
  659. {
  660. logger.Error("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) exceptioned: " + exx);
  661. }
  662. //}
  663. //);
  664. #endregion
  665. }
  666. else if (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.SWITCHED_OFF)
  667. {
  668. logger.Debug("Pump: " + this.pumpId + ", " + " NEW State is SWITCHED_OFF, will send RESET");
  669. this.context.Outgoing.WriteAsync(
  670. new ResetRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
  671. (request, testResponse) =>
  672. testResponse.Adrs == this.dartPumpCommAddress &&
  673. testResponse.BlockSeqNumber == request.BlockSeqNumber &&
  674. testResponse.ControlCharacter == ControlCharacter.ACK,
  675. (request, response) =>
  676. {
  677. if (response != null)
  678. {
  679. logger.Debug("Pump: " + this.pumpId + ", " + "RESET request acked.");
  680. }
  681. else
  682. logger.Error("Pump: " + this.pumpId + ", " + "RESET request failed(timed out) to ack.");
  683. }, 3500);
  684. }
  685. if (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.FILLING_COMPLETED)
  686. {
  687. byte nozzlePhysicalIdForTrxDoneOn = 0;
  688. #region determine target nozzle
  689. if (nozzleStatusTrx != null)
  690. {
  691. if (nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.IN)
  692. {
  693. if (nozzleStatusTrx.Status.Key == 0)
  694. {
  695. //logger.Debug("Pump: " + this.pumpId + ", " + " more like a China domestic wayne dart since FILLING_COMPLETED contains nozzleStatus but nozzle physicalId is 0");
  696. // nozzle number ==0 indicates all nozzles were put back(In)
  697. nozzlePhysicalIdForTrxDoneOn = this.operatingNozzlePhysicalId;
  698. }
  699. else
  700. nozzlePhysicalIdForTrxDoneOn = nozzleStatusTrx.Status.Key;
  701. }
  702. else if (nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.OUT)
  703. {
  704. // here happens in a case that shutdown the FC at a fueling for seconds,
  705. // and the pump will stop fueling by its design, and then start FC without put back nozzle,
  706. // then the nozzle will reported as OUT with FILLING_COMPLETED
  707. // for this case, FC still can recover the last fill since nozzle id is confirmed
  708. nozzlePhysicalIdForTrxDoneOn = nozzleStatusTrx.Status.Key;
  709. }
  710. }
  711. else
  712. {
  713. // igem wayne dart won't report nozzle number when entered FILLING_COMPLETED.
  714. //logger.Debug("Pump: " + this.pumpId + ", " + " more like a iGEM wayne dart since FILLING_COMPLETED does not contain nozzleStatus at all");
  715. nozzlePhysicalIdForTrxDoneOn = this.operatingNozzlePhysicalId;
  716. }
  717. if (nozzlePhysicalIdForTrxDoneOn == 0)
  718. {
  719. logger.Info("Pump: " + this.pumpId + ", " + " targetNozzlePhysicalId is 0, will not query last fill");
  720. return;
  721. }
  722. if (this.nozzles.FirstOrDefault(n => n.PhysicalId == nozzlePhysicalIdForTrxDoneOn) == null)
  723. {
  724. /* I do see a case that pump side report a nozzle physical id 15, possible wire issue?? here try to recover*/
  725. logger.Info("Pump: " + this.pumpId + ", " + " targetNozzlePhysicalId: " + nozzlePhysicalIdForTrxDoneOn
  726. + " is NOT bound to any physical nozzle, will use last operating nozzle physical Id: "
  727. + this.operatingNozzlePhysicalId);
  728. nozzlePhysicalIdForTrxDoneOn = this.operatingNozzlePhysicalId;
  729. }
  730. #endregion
  731. try
  732. {
  733. LoopReadLastFill(nozzlePhysicalIdForTrxDoneOn);
  734. }
  735. catch (Exception exxx)
  736. {
  737. logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill exceptioned: " + exxx);
  738. this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true;
  739. }
  740. }
  741. #region Trigger Fdc state change event
  742. if (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.AUTHORIZED)
  743. {
  744. // only WayneDart Authorized state received in FDC_CALLING is valid and expected.
  745. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CALLING)
  746. {
  747. logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_AUTHORISED");
  748. this.lastLogicalDeviceState = LogicalDeviceState.FDC_AUTHORISED;
  749. var safe1 = this.OnStateChange;
  750. safe1?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_AUTHORISED));
  751. logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  752. }
  753. else
  754. {
  755. logger.Info("Pump: " + this.pumpId + ", " + " Unexpected WayneDart state: AUTHORIZED, will stop the pump");
  756. this.UnAuthorizePumpAndSwithToFdcReady();
  757. }
  758. }
  759. if (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.FILLING)
  760. {
  761. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_FUELLING)
  762. {
  763. logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_FUELLING");
  764. this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING;
  765. var safe2 = this.OnStateChange;
  766. safe2?.Invoke(this,
  767. new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_FUELLING, this.nozzles.FirstOrDefault(n => n.PhysicalId == this.operatingNozzlePhysicalId)));
  768. logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  769. }
  770. }
  771. if (filledVolAndAmtTrx != null)
  772. {
  773. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_AUTHORISED)
  774. {
  775. if (nozzleStatusTrx != null
  776. && (nozzleStatusTrx.Status.Key == 0
  777. || nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.IN))
  778. {
  779. /* we do see a case:
  780. * >>>>>>>>>>===start====(internalFdcState: FDC_AUTHORISED)
  781. * Nozzle with physical id: 0 is in state: IN, filling price: 666
  782. * Filled vol 4, amt 26
  783. * <<<<<<<<<<===end====
  784. * this is abnormal that why nozzle is IN but still have a running fuel, this caused by leaking control missed
  785. * in domestic WayneDart pump.
  786. * ignore this small running fuel, it will NOT report filling_complete later, HengShanIC reader rely on a flow of fueling+filling_Complete
  787. * finally, FC will unAuth this pump.
  788. */
  789. logger.Info("Pump: " + this.pumpId + ", " + " Received a nozzle down with filling info in FDC_AUTHORISED state, " +
  790. "treat as fuel leak control malfunctioning in physical pump side case, will NOT switch to FDC_FUELLING");
  791. }
  792. else
  793. {
  794. logger.Info("Pump: " + this.pumpId + ", " + " State switched to FDC_FUELLING from FDC_AUTHORISED");
  795. this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING;
  796. var safe2 = this.OnStateChange;
  797. safe2?.Invoke(this,
  798. new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_FUELLING,
  799. this.nozzles.FirstOrDefault(n => n.PhysicalId == this.operatingNozzlePhysicalId)));
  800. logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  801. }
  802. }
  803. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_FUELLING)
  804. {
  805. logger.Debug("Pump: " + this.pumpId + ", " + "Filled volume " + filledVolAndAmtTrx.FilledVolume
  806. + ", amount " + filledVolAndAmtTrx.FilledAmount + " in state: FDC_FUELLING, will fire fdcEvent for logicalNozzle: "
  807. + this.nozzles.First(n => n.PhysicalId == this.operatingNozzlePhysicalId).LogicalId
  808. + ", physicalNozzle: " + this.operatingNozzlePhysicalId);
  809. var safe = this.OnCurrentFuellingStatusChange;
  810. safe?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction()
  811. {
  812. Nozzle = this.nozzles.First(n => n.PhysicalId == this.operatingNozzlePhysicalId),
  813. Amount = filledVolAndAmtTrx.FilledAmount,
  814. Volumn = filledVolAndAmtTrx.FilledVolume,
  815. Price = this.nozzles.First(n => n.PhysicalId == this.operatingNozzlePhysicalId).RealPriceOnPhysicalPump ?? 0,
  816. Finished = false,
  817. }));
  818. logger.Trace("Pump: " + this.pumpId + ", " + " OnCurrentFuellingStatusChange event fired and back");
  819. }
  820. else
  821. logger.Info("Pump: " + this.pumpId + ", " + "Filled volume " + filledVolAndAmtTrx.FilledVolume
  822. + ", amount " + filledVolAndAmtTrx.FilledAmount + " received in internal Fdc state: " + this.lastLogicalDeviceState + ", will do nothing");
  823. }
  824. #endregion
  825. if (nozzleStatusTrx != null)
  826. {
  827. #region Nozzle status and filling price
  828. var targetNozzle = this.nozzles.FirstOrDefault(n => n.PhysicalId == nozzleStatusTrx.Status.Key);
  829. if (targetNozzle != null)
  830. targetNozzle.RealPriceOnPhysicalPump = nozzleStatusTrx.FillingPrice;
  831. // nozzle number ==0 indicates all nozzles were put back(In)
  832. if (nozzleStatusTrx.Status.Key == 0
  833. || nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.IN)
  834. {
  835. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_AUTHORISED)
  836. {
  837. /* here we just unauthorize the pump once nozzle down and pump get authorized previously,
  838. * some customers may want keep authorized state, but leave for future to implement */
  839. logger.Debug("Pump: " + this.pumpId + ", " + " PhysicalNozzle: " + nozzleStatusTrx.Status.Key + " is put back, " +
  840. "will `Stop`(Unauthorize) the pump");
  841. this.UnAuthorizePumpAndSwithToFdcReady();
  842. }
  843. else
  844. {
  845. // Nozzle-IN in FDC state FDC_FUELLING
  846. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_FUELLING)
  847. {
  848. // if nozzle IN incoming with a FILLING_COMPLETED, then no need to fire extra read last fill process,
  849. // other parts of code will handle
  850. // here is trying to handle the case of FILLING_COMPLETED missed read in FC.
  851. if (pumpStatusTrx == null ||
  852. (pumpStatusTrx != null && pumpStatusTrx.Status != PumpStatus.FILLING_COMPLETED))
  853. {
  854. // will block pump auth from now by this flag set.
  855. this.lastFillRetrievedSinceNozzleDownAtFdcFueling = false;
  856. /* there's a case that phsycial pump missed to send Filling_complete to FC, cause miss to read last fill
  857. here bring in an extra read.
  858. */
  859. // filling complete typically incoming in 1 or 2 seconds, and read last fill and read totalizer
  860. // will cost another 2 seconds.
  861. // here, if filling complete have not incoming for 6 seconds, then extra read will happens
  862. // I do see cases of `filling complete not incoming into FC` in field, may the issue in wire or physical pump??
  863. this.retryReadLastFillTimer = new System.Timers.Timer(6000);
  864. this.retryReadLastFillTimer.Elapsed += (_, __) =>
  865. {
  866. byte physicalNozzlePlacedBack = (nozzleStatusTrx.Status.Key == 0 ?
  867. this.operatingNozzlePhysicalId : nozzleStatusTrx.Status.Key);
  868. if (this.nozzles.FirstOrDefault(n => n.PhysicalId == physicalNozzlePlacedBack) == null)
  869. {
  870. /* I do see a case that pump report a nozzle with physical id 15 here! try to recover*/
  871. logger.Info("Pump: " + this.pumpId + ", " + " targetNozzlePhysicalId: " + physicalNozzlePlacedBack
  872. + " is NOT bound to any physical nozzle, will use last operating nozzle physical Id: "
  873. + this.operatingNozzlePhysicalId);
  874. physicalNozzlePlacedBack = this.operatingNozzlePhysicalId;
  875. }
  876. logger.Info("Pump: " + this.pumpId + ", physicalNozzle: " + physicalNozzlePlacedBack
  877. + ", extra retrieving last fill is kicked off");
  878. LoopReadLastFill(physicalNozzlePlacedBack);
  879. };
  880. retryReadLastFillTimer.Start();
  881. }
  882. }
  883. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY)
  884. {
  885. logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_READY due to nozzle IN.");
  886. this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
  887. var safe3 = this.OnStateChange;
  888. safe3?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
  889. logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  890. return;
  891. }
  892. }
  893. }
  894. else
  895. {
  896. /* specific nozzle out */
  897. if ((this.lastLogicalDeviceState == LogicalDeviceState.FDC_AUTHORISED
  898. || (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.AUTHORIZED))
  899. && nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.OUT)
  900. {
  901. if (this.operatingNozzlePhysicalId != nozzleStatusTrx.Status.Key)
  902. {
  903. //I do see cases in lab, fast switch(put down and put on another) nozzle by initial calling nozzle get authorized,
  904. //but Pump side didn't send in down Nozzle's In message.
  905. // we don't allow this case
  906. logger.Info("Pump: " + this.pumpId + ", " + " Detect nozzle switch(from physicalId: "
  907. + this.operatingNozzlePhysicalId + " to " + nozzleStatusTrx.Status.Key + ") in WayneDart.AUTHORIZED or LogicalDeviceState.FDC_AUTHORISED state, illegal, will stop the pump");
  908. // how switched a nozzle in WayneDart in AUTHORIZED state??
  909. this.UnAuthorizePumpAndSwithToFdcReady();
  910. }
  911. // no need to calling anymore since it already in Authorized, this happens for WayneDart PumpSim software
  912. }
  913. else if ((this.lastLogicalDeviceState == LogicalDeviceState.FDC_FUELLING
  914. || this.lastLogicalDeviceState == LogicalDeviceState.FDC_AUTHORISED)
  915. && nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.OUT)
  916. {
  917. // we do see this case in domestic wayne dart pump, not sure how it happens.
  918. }
  919. else if (nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.OUT)
  920. {
  921. /* nozzle out */
  922. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_CALLING)
  923. {
  924. // need make sure previous trx had been retrieved by FC, otherwise should not send RequestRequest which will
  925. // clear previous trx info in physical pump side.
  926. if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
  927. {
  928. logger.Info("Pump: " + this.pumpId + ", " + " will ignore this nozzle out due to last fill still in retrieving");
  929. return;
  930. }
  931. this.operatingNozzlePhysicalId = nozzleStatusTrx.Status.Key;
  932. logger.Debug("Pump: " + this.pumpId + ", " + " send RESET to clear previous trx info, prepare for new fuel");
  933. this.context.Outgoing.WriteAsync(
  934. new ResetRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
  935. (request, testResponse) =>
  936. testResponse.Adrs == this.dartPumpCommAddress &&
  937. testResponse.BlockSeqNumber == request.BlockSeqNumber,
  938. (request, ackResponse) =>
  939. {
  940. if (ackResponse == null)
  941. {
  942. logger.Error("Pump: " + this.pumpId + ", " + " RESET wait for ACK timedout, will not fire calling state");
  943. }
  944. else
  945. {
  946. logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_CALLING due to RESET ACKed or NAKed(physicalNozzle: " + nozzleStatusTrx.Status.Key + ")");
  947. // insert a Poll for poll and receive wayne dart pump entered RESET state msg, otherwise
  948. // after the Fdc_Calling event fired below, outer will send auth request
  949. // directly and then pump will report 2 states in one msg response after auth: RESET + AUTHORIZED,
  950. //this has no problem, only for better understanding and logging.
  951. this.context.Outgoing.Write(new Poll(this.dartPumpCommAddress, 0));
  952. //this.context.Outgoing.Write(new Poll(this.dartPumpCommAddress, 0));
  953. //this.context.Outgoing.Write(new Poll(this.dartPumpCommAddress, 0));
  954. //this.context.Outgoing.Write(new Poll(this.dartPumpCommAddress, 0));
  955. this.lastLogicalDeviceState = LogicalDeviceState.FDC_CALLING;
  956. var safe2 = this.OnStateChange;
  957. safe2?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(
  958. LogicalDeviceState.FDC_CALLING,
  959. this.nozzles.First(n => n.PhysicalId == nozzleStatusTrx.Status.Key)));
  960. logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  961. }
  962. }, 2000);
  963. }
  964. if (pumpStatusTrx != null
  965. && pumpStatusTrx.Status == PumpStatus.FILLING)
  966. {
  967. if (filledVolAndAmtTrx != null
  968. && this.lastLogicalDeviceState == LogicalDeviceState.FDC_FUELLING)
  969. {
  970. logger.Debug("Pump: " + this.pumpId + ", " + "Filled volume " + filledVolAndAmtTrx.FilledVolume
  971. + ", amount " + filledVolAndAmtTrx.FilledAmount + " in state: FDC_FUELLING, will fire fdcEvent1");
  972. var safe = this.OnCurrentFuellingStatusChange;
  973. safe?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction()
  974. {
  975. Nozzle = this.nozzles.First(n => n.PhysicalId == this.operatingNozzlePhysicalId),
  976. Amount = filledVolAndAmtTrx.FilledAmount,
  977. Volumn = filledVolAndAmtTrx.FilledVolume,
  978. Price = this.nozzles.First(n => n.PhysicalId == this.operatingNozzlePhysicalId).RealPriceOnPhysicalPump ?? 0,
  979. Finished = false,
  980. }));
  981. logger.Trace("Pump: " + this.pumpId + ", " + " OnCurrentFuellingStatusChange event fired and back");
  982. }
  983. }
  984. }
  985. }
  986. #endregion
  987. }
  988. }
  989. }
  990. private void UnAuthorizePumpAndSwithToFdcReady()
  991. {
  992. this.context.Outgoing.WriteAsync(
  993. new StopRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
  994. (request, testResponse) =>
  995. testResponse.Adrs == this.dartPumpCommAddress &&
  996. testResponse.BlockSeqNumber == request.BlockSeqNumber,
  997. (request, response) =>
  998. {
  999. if (response != null)
  1000. {
  1001. if (response.ControlCharacter == ControlCharacter.ACK)
  1002. {
  1003. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY)
  1004. {
  1005. logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_READY due to StopRequest acked.");
  1006. this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
  1007. var safe3 = this.OnStateChange;
  1008. safe3?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
  1009. logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  1010. return;
  1011. }
  1012. }
  1013. else
  1014. logger.Error("Pump: " + this.pumpId + ", " + "StopRequest NAKed.");
  1015. }
  1016. else
  1017. logger.Error("Pump: " + this.pumpId + ", " + "StopRequest request timed out for ack.");
  1018. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY)
  1019. {
  1020. logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_READY though StopRequest failed");
  1021. this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
  1022. var safe2 = this.OnStateChange;
  1023. safe2?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
  1024. logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  1025. }
  1026. }, 1500);
  1027. }
  1028. private System.Timers.Timer retryReadLastFillTimer;
  1029. private int onReadingLastFill = 0;
  1030. /// <summary>
  1031. /// may get called via 2 route:
  1032. /// 1. received a Filling_Complete from pump.
  1033. /// 2. by a delay timer that fired by nozzle IN from FDC_FUELING state.
  1034. /// </summary>
  1035. /// <param name="nozzlePhysicalIdForTrxDoneOn"></param>
  1036. private void LoopReadLastFill(byte nozzlePhysicalIdForTrxDoneOn)
  1037. {
  1038. if (0 == Interlocked.CompareExchange(ref this.onReadingLastFill, 1, 0))
  1039. {
  1040. this.retryReadLastFillTimer?.Stop();
  1041. this.retryReadLastFillTimer?.Dispose();
  1042. // disable auth since last fill has not been read yet, this happens in real site with bad wire connection condition.
  1043. //this.lastFillRetrieved = false;
  1044. this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (t) =>
  1045. {
  1046. if (t != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
  1047. logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 1st time");
  1048. this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (tt) =>
  1049. {
  1050. if (tt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
  1051. logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 2nd time");
  1052. this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (ttt) =>
  1053. {
  1054. if (ttt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
  1055. logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 3rd time");
  1056. this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (tttt) =>
  1057. {
  1058. if (tttt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
  1059. logger.Error("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will NOT retry anymore (total read 4 times)");
  1060. // Have no way but release it to allow continue pump auth and fueling, But a fuel sale has been lost!
  1061. this.onReadingLastFill = 0;
  1062. this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true;
  1063. });
  1064. });
  1065. });
  1066. });
  1067. }
  1068. }
  1069. /// <summary>
  1070. ///
  1071. /// </summary>
  1072. /// <param name="targetNozzlePhysicalId"></param>
  1073. /// <param name="callback">FdcTransaction is null when retrieved failed, like time out. otherwise, an object will return</param>
  1074. private void ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(byte targetNozzlePhysicalId,
  1075. Action<FdcTransaction> callback)
  1076. {
  1077. try
  1078. {
  1079. #region ReturnFillingInfomrationRequest
  1080. var targetNozzle = this.nozzles.First(n => n.PhysicalId == targetNozzlePhysicalId);
  1081. logger.Debug("Pump: " + this.pumpId + ", " + " filling is finished, will query last fill info for physicalNozzle: " + targetNozzlePhysicalId
  1082. + ", logicalNozzle: " + targetNozzle.LogicalId);
  1083. this.context.Outgoing.WriteAsync(
  1084. new ReturnFillingInfomrationRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
  1085. (request, testResponse) =>
  1086. testResponse.Adrs == this.dartPumpCommAddress &&
  1087. testResponse.ControlCharacter == ControlCharacter.DATA,
  1088. (request, returnFillingInfoResponse) =>
  1089. {
  1090. try
  1091. {
  1092. #region ReturnFillingInfomration Response
  1093. if (returnFillingInfoResponse == null ||
  1094. (returnFillingInfoResponse != null && returnFillingInfoResponse.TransactionDatas.FirstOrDefault(f => f.TransactionNumber == 0x02) == null))
  1095. {
  1096. logger.Info("Pump: " + this.pumpId + ", " + " retrieve last fill info timed out or inner FilledVolumeAndAmount trxData is null");
  1097. callback?.Invoke(null);
  1098. }
  1099. else
  1100. {
  1101. try
  1102. {
  1103. #region RequestTotalVolumeCountersRequest
  1104. this.context.Outgoing.Write(new ACK(this.dartPumpCommAddress, returnFillingInfoResponse.BlockSeqNumber));
  1105. /*note, the response would contain last sale vol and money, besides, all nozzle state, and pump state
  1106. will be returned as well, we only interested on sale vol and money which trx number is 0x02 */
  1107. FilledVolumeAndAmount_TransactionData lastFinishedTrx = null;
  1108. try
  1109. {
  1110. lastFinishedTrx =
  1111. new FilledVolumeAndAmount_TransactionData(
  1112. returnFillingInfoResponse.TransactionDatas.FirstOrDefault(f => f.TransactionNumber == 0x02));
  1113. }
  1114. catch (Exception exx)
  1115. {
  1116. var safeLogStr = "exceptioned in catch: ";
  1117. try
  1118. {
  1119. safeLogStr = returnFillingInfoResponse?.ToLogString() ?? "";
  1120. }
  1121. catch (Exception iiexx)
  1122. {
  1123. safeLogStr += iiexx.ToString();
  1124. }
  1125. logger.Error($"Pump: " + this.pumpId + ", " +
  1126. $"Parse FilledVolumeAndAmount_TransactionData(from: {safeLogStr}) exceptioned: {exx}");
  1127. }
  1128. if (lastFinishedTrx != null && lastFinishedTrx.FilledVolume != 0)
  1129. {
  1130. #region concrete Non-Zero trx read
  1131. logger.Info("Pump: " + this.pumpId + ", " + " Last fill retrieved, volume "
  1132. + lastFinishedTrx.FilledVolume + ", amount " + lastFinishedTrx.FilledAmount
  1133. + ", done in logical nozzle: " + targetNozzle.LogicalId
  1134. + ", physical nozzle: " + targetNozzlePhysicalId
  1135. + ", will read vol totalizer for compare");
  1136. this.context.Outgoing.WriteAsync(
  1137. new RequestTotalVolumeCountersRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)
  1138. , targetNozzle.PhysicalId),
  1139. (___, testResponse) =>
  1140. testResponse.Adrs == this.dartPumpCommAddress &&
  1141. testResponse.ControlCharacter == ControlCharacter.DATA,
  1142. (_____, volTotalResponse) =>
  1143. {
  1144. FdcTransaction lastFillTrx = null;
  1145. try
  1146. {
  1147. TotalCounters_TransactionData justReadVolumeTotalizerTrx = null;
  1148. if (volTotalResponse != null)
  1149. {
  1150. this.context.Outgoing.Write(new ACK(this.dartPumpCommAddress, volTotalResponse.BlockSeqNumber));
  1151. if (volTotalResponse.TransactionDatas.FirstOrDefault(f => f.TransactionNumber == 0x65) != null)
  1152. justReadVolumeTotalizerTrx =
  1153. new TotalCounters_TransactionData(
  1154. volTotalResponse.TransactionDatas.FirstOrDefault(f => f.TransactionNumber == 0x65));
  1155. }
  1156. //any of null below will quit the last fill check, no worry about the trx miss, the caller will start retry
  1157. if (volTotalResponse == null || justReadVolumeTotalizerTrx == null)
  1158. {
  1159. logger.Info("Pump: " + this.pumpId + ", " + " logical nozzle: " + targetNozzle.LogicalId
  1160. + ", physicalNozzle: " + targetNozzlePhysicalId + ", read VolTotalizer failed");
  1161. callback?.Invoke(null);
  1162. return;
  1163. }
  1164. logger.Info("Pump: " + this.pumpId + ", " + " logical nozzle: " + targetNozzle.LogicalId
  1165. + ", physicalNozzle: " + targetNozzlePhysicalId + ", Vol totalizer read value: " + ((justReadVolumeTotalizerTrx?.TotalValue.ToString()) ?? "null"));
  1166. if (targetNozzle.VolumeTotalizer.HasValue)
  1167. {
  1168. /* compare last backup totalizer with just read value, if equals, should not rasie up new trx*/
  1169. var lastVolumeTotalizerValue = targetNozzle.VolumeTotalizer.Value;
  1170. logger.Info("Pump: " + this.pumpId + ", " + "logical nozzle: " + targetNozzle.LogicalId + ", last backup volume totalizer value: " + lastVolumeTotalizerValue
  1171. + ", while now just read volume totalizer value: " + justReadVolumeTotalizerTrx.TotalValue
  1172. + ", they're " +
  1173. (lastVolumeTotalizerValue == justReadVolumeTotalizerTrx.TotalValue ?
  1174. "Equal(no new trx)" :
  1175. "Not Equal with diff: " + (justReadVolumeTotalizerTrx.TotalValue - lastVolumeTotalizerValue) + " (new trx detected and created with vol from LastFill data element: " + lastFinishedTrx.FilledVolume + ")"));
  1176. targetNozzle.VolumeTotalizer = justReadVolumeTotalizerTrx.TotalValue;
  1177. if (lastVolumeTotalizerValue == justReadVolumeTotalizerTrx.TotalValue)
  1178. {
  1179. // no new trx
  1180. callback?.Invoke(new FdcTransaction());
  1181. return;
  1182. }
  1183. }
  1184. if (!targetNozzle.VolumeTotalizer.HasValue)
  1185. targetNozzle.VolumeTotalizer = justReadVolumeTotalizerTrx.TotalValue;
  1186. // at least within 65 years, exception will not throw here
  1187. int newTrxSeqNumber = (int)(DateTime.Now.Subtract(new DateTime(2018, 5, 25)).TotalSeconds);
  1188. logger.Info("Pump: " + this.pumpId + ", " + "logical nozzle: " + targetNozzle.LogicalId
  1189. + ", physicalNozzle: " + targetNozzlePhysicalId
  1190. + ", trx done with amt: " + lastFinishedTrx.FilledAmount
  1191. + ", vol: " + lastFinishedTrx.FilledVolume
  1192. + ", seqNo.: " + newTrxSeqNumber);
  1193. lastFillTrx = new FdcTransaction()
  1194. {
  1195. Nozzle = this.nozzles.First(n => n.PhysicalId == targetNozzlePhysicalId),
  1196. Amount = lastFinishedTrx.FilledAmount,
  1197. Volumn = lastFinishedTrx.FilledVolume,
  1198. Price = this.nozzles.First(n => n.PhysicalId == targetNozzlePhysicalId).RealPriceOnPhysicalPump ?? 0,
  1199. SequenceNumberGeneratedOnPhysicalPump = newTrxSeqNumber,
  1200. VolumeTotalizer = justReadVolumeTotalizerTrx?.TotalValue ?? 0,
  1201. Finished = true,
  1202. };
  1203. callback?.Invoke(lastFillTrx);
  1204. }
  1205. catch
  1206. {
  1207. callback?.Invoke(null);
  1208. }
  1209. var safe6 = this.OnCurrentFuellingStatusChange;
  1210. safe6?.Invoke(this, new FdcTransactionDoneEventArg(lastFillTrx));
  1211. logger.Trace("Pump: " + this.pumpId + ", " + " OnCurrentFuellingStatusChange event fired and back");
  1212. }, 3000);
  1213. #endregion
  1214. }
  1215. else
  1216. {
  1217. logger.Info("Pump: " + this.pumpId + ", " + " Last filled info is null or volume is 0, will ignore");
  1218. // zero amount trx
  1219. callback?.Invoke(new FdcTransaction());
  1220. }
  1221. #endregion
  1222. }
  1223. catch (Exception exxxx)
  1224. {
  1225. callback?.Invoke(null);
  1226. }
  1227. }
  1228. #endregion
  1229. }
  1230. catch
  1231. {
  1232. callback?.Invoke(null);
  1233. }
  1234. }, 2500);
  1235. #endregion
  1236. }
  1237. catch
  1238. {
  1239. callback?.Invoke(null);
  1240. }
  1241. }
  1242. public void Dispose()
  1243. {
  1244. this.deviceOfflineCountdownTimer.Stop();
  1245. this.deviceOfflineCountdownTimer.Dispose();
  1246. this.retryReadLastFillTimer?.Stop();
  1247. this.retryReadLastFillTimer?.Dispose();
  1248. }
  1249. }
  1250. }