PumpHandler.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. using Edge.Core.Database.Models;
  2. using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump;
  3. using Edge.Core.Parser.BinaryParser.Util;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using System.Xml;
  11. using Wayne.FDCPOSLibrary;
  12. using Global_Pump_Fdc.MessageEntity;
  13. using Global_Pump_Fdc.MessageEntity.Outgoing;
  14. using System.Collections.Specialized;
  15. using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump;
  16. namespace Global_Pump_Fdc
  17. {
  18. public class PumpHandler : IFdcPumpController, IDeviceHandler<object, FccMessageBase>
  19. {
  20. static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("PumpHandler");
  21. private LogicalDeviceState lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE;
  22. private DateTime lastLogicalDeviceStateReceivedTime;
  23. // by minutes, change this value need change the correlated deviceOfflineCountdownTimer's interval as well
  24. private const int lastLogicalDeviceStateExpiredTime = 3;
  25. private List<LogicalNozzle> nozzles = new List<LogicalNozzle>();
  26. private System.Timers.Timer deviceOfflineCountdownTimer;
  27. /// <summary>
  28. /// will set to true once nozzle change to OUT while internal fdc state is FDC_FUELING, indicates a fule is done.
  29. /// 'true' will block pump auth request from outer
  30. /// </summary>
  31. private bool lastFillRetrievedSinceNozzleDownAtFdcFueling = true;
  32. /// <summary>
  33. /// the pump calling, fuelling message does not contain nozzle info, need track the operating nozzle
  34. /// seperatly in other message which will be kept in this variable.
  35. /// default set to 0 which is an invalid nozzle id for wayne dart pump(valid should starts from 1).
  36. /// </summary>
  37. private byte operatingNozzlePhysicalId = 0;
  38. private IContext<object, FccMessageBase> context;
  39. private bool isOnFdcServerInitCalled = false;
  40. private int pumpId;
  41. private PumpGroupHandler parent;
  42. /// <summary>
  43. /// address used in wayne dart protocol to comm with physical pump.
  44. /// 0x4F + physical pump mother board side config address(range from 1-32)
  45. /// </summary>
  46. private byte dartPumpCommAddress;
  47. /// <summary>
  48. /// when first time connected with physical pump , in some case, the pump will not report any status actively,
  49. /// so need send a status query from FC.
  50. /// From then on, pump will actively notify FC when state changes, no need to send query anymore from FC.
  51. /// </summary>
  52. private bool initialPumpStatueEverRetrieved = false;
  53. private int amountDecimalDigits;
  54. private int volumeDecimalDigits;
  55. private int priceDecimalDigits;
  56. private int volumeTotalizerDecimalDigits;
  57. /// <summary>
  58. /// for avoid a case that FC may miss a pump status change event(pump side issue? or wire issue?),
  59. /// we timely actively request the pump state.
  60. /// </summary>
  61. //private System.Timers.Timer pollingPumpStatus;
  62. //private int pollingPumpStatusInterval = 30000;
  63. #region MyRegion
  64. public string Name => "Global_Pump_Fdc";
  65. public int PumpId => this.pumpId;
  66. /// <summary>
  67. /// Gets the pump physical id.
  68. /// address used in wayne dart protocol to comm with physical pump.
  69. /// 0x4F + physical pump mother board side config address(range from 1-32)
  70. /// </summary>
  71. public int PumpPhysicalId => this.dartPumpCommAddress;
  72. public IEnumerable<LogicalNozzle> Nozzles => this.nozzles;
  73. public int AmountDecimalDigits => this.amountDecimalDigits;
  74. public int VolumeDecimalDigits => this.volumeDecimalDigits;
  75. public int PriceDecimalDigits => this.priceDecimalDigits;
  76. public int VolumeTotalizerDecimalDigits => this.volumeDecimalDigits;
  77. public Guid Id => Guid.NewGuid();
  78. public event EventHandler<FdcPumpControllerOnStateChangeEventArg> OnStateChange;
  79. public event EventHandler<FdcTransactionDoneEventArg> OnCurrentFuellingStatusChange;
  80. public async Task<bool> AuthorizeAsync(byte logicalNozzleId)
  81. {
  82. if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
  83. {
  84. logger.Info("Pump: " + this.pumpId + ", "
  85. + "Start Authorize pump with logicalNozzle: " + logicalNozzleId + " is denied by internal");
  86. return false;
  87. }
  88. var authorizeSucceed = false;
  89. logger.Info("Pump: " + this.pumpId + ", " + "Start Authorize pump with logicalNozzle: " + logicalNozzleId + ", first send AllowedNozzleNumbersRequest(allow all)");
  90. // now always allow all nozzles
  91. var response = await this.context.Outgoing.WriteAsync(
  92. new AuthorizeRequest(this.pumpId, this.nozzles.First().LogicalId),
  93. (request, testResponse) =>
  94. {
  95. var parameters = testResponse.Parameters as StringDictionary;
  96. if (parameters == null || parameters["EventType"] != "AuthorizePump")
  97. return false;
  98. string pumpId = parameters["PumpID"];
  99. string success = parameters["Success"];
  100. authorizeSucceed = int.Parse(pumpId) == this.PumpId;
  101. logger.Info("Pump: " + pumpId + ", " + "AuthorizeWithAmountAsync for nozzle: " + logicalNozzleId +
  102. " , success: " + success);
  103. return authorizeSucceed;
  104. },
  105. 3500);
  106. return authorizeSucceed;
  107. }
  108. public async Task<bool> AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId)
  109. {
  110. if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
  111. {
  112. logger.Info("Pump: " + this.pumpId + ", "
  113. + "Start AuthorizeWithAmount pump with logicalNozzle: " + logicalNozzleId + " is denied by internal");
  114. return false;
  115. }
  116. moneyAmountWithoutDecimalPoint = (int)(moneyAmountWithoutDecimalPoint / Math.Pow(10, AmountDecimalDigits));
  117. logger.Info("Pump: " + this.pumpId + ", " + "start AuthorizeWithAmount pump with logicalNozzle: " + logicalNozzleId + ", moneyAmount: " +
  118. moneyAmountWithoutDecimalPoint + ", first send AllowedNozzleNumbersRequest(allow all)");
  119. var authorizeSucceed = false;
  120. // now always allow all nozzles
  121. var response = await this.context.Outgoing.WriteAsync(
  122. new PresetAmountRequest(this.pumpId, this.nozzles.First().LogicalId, moneyAmountWithoutDecimalPoint),
  123. (request, testResponse) =>
  124. {
  125. var parameters = testResponse.Parameters as StringDictionary;
  126. if (parameters == null || parameters["EventType"] != "AuthorizePump")
  127. return false;
  128. string pumpId = parameters["PumpID"];
  129. string success = parameters["Success"];
  130. authorizeSucceed = int.Parse(pumpId) == this.PumpId;
  131. logger.Info("Pump: " + pumpId + ", " + "AuthorizeWithAmountAsync for nozzle: " + logicalNozzleId +
  132. " , success: " + success);
  133. return authorizeSucceed;
  134. },
  135. 3500);
  136. return authorizeSucceed;
  137. }
  138. public async Task<bool> AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId)
  139. {
  140. if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
  141. {
  142. logger.Info("Pump: " + this.pumpId + ", "
  143. + "Start AuthorizeWithVolumn pump with logicalNozzle: " + logicalNozzleId + " is denied by internal");
  144. return false;
  145. }
  146. var authorizeSucceed = false;
  147. logger.Info("Pump: " + this.pumpId + ", " + "start AuthorizeWithVolumn pump with logicalNozzle: " + logicalNozzleId + ", volumn: " + volumnWithoutDecimalPoint + ", first send AllowedNozzleNumbersRequest(allow all)");
  148. // now always allow all nozzles
  149. var response = await this.context.Outgoing.WriteAsync(
  150. new PresetVolumeRequest(this.pumpId, this.nozzles.First().LogicalId, volumnWithoutDecimalPoint),
  151. (request, testResponse) =>
  152. {
  153. var parameters = testResponse.Parameters as StringDictionary;
  154. if (parameters == null || parameters["EventType"] != "AuthorizePump")
  155. return false;
  156. string pumpId = parameters["PumpID"];
  157. string success = parameters["Success"];
  158. authorizeSucceed = int.Parse(pumpId) == this.PumpId;
  159. logger.Info("Pump: " + pumpId + ", " + "AuthorizeWithVolumn for nozzle: " + logicalNozzleId +
  160. " , success: " + success);
  161. return authorizeSucceed;
  162. },
  163. 3500);
  164. return authorizeSucceed;
  165. }
  166. public Task<bool> SetFuelPrice(int fuelGrade, double newFuelPrice)
  167. {
  168. return Task.FromResult(true);
  169. }
  170. public Task<bool> ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId)
  171. {
  172. try
  173. {
  174. if (newPriceWithoutDecimalPoint > 10000)
  175. {
  176. int fuelGrade = logicalNozzleId;
  177. double newFuelPrice = (double)(newPriceWithoutDecimalPoint - 10000) / 1000;
  178. return this.InternalChangeFuelPriceAsync(fuelGrade, newFuelPrice);
  179. }
  180. }
  181. catch (Exception exxx)
  182. {
  183. logger.Error("Pump: " + this.pumpId + ", " + "Exceptioned in ChangeFuelPrice: " + exxx);
  184. return Task.FromResult(false);
  185. }
  186. return Task.FromResult(true);
  187. }
  188. private async Task<bool> InternalChangeFuelPriceAsync(int fuelGrade, double newFuelPrice)
  189. {
  190. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED
  191. || this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE)
  192. {
  193. logger.Info("Pump: " + this.pumpId + ", " + " Pump is in state FDC_CLOSED or FDC_OFFLINE, InternalChangeFuelPrice will return false");
  194. return false;
  195. }
  196. bool changePriceSucceed = false;
  197. logger.Info("Pump: " + this.pumpId + ", " + " InternalChangeFuelPrice starting with prices: " + newFuelPrice.ToString());
  198. var priceChangedResponse = await this.context.Outgoing.WriteAsync(
  199. new PriceUpdateRequest(fuelGrade, newFuelPrice),
  200. null,
  201. 2000);
  202. // this FC handle WayneDart pump price change one nozzle by nozzle, so change prices on single FuelPoint with multiple
  203. // nozzle will interpreted as multiple price change request, by testing, too fast send multiple price change request
  204. // to a FP might be ignored by pump side though message here are all good, so hardcode sleep a while.
  205. Thread.Sleep(1000);
  206. return changePriceSucceed;
  207. }
  208. public async Task<bool> FuelingRoundUpByAmountAsync(int amount)
  209. {
  210. logger.Info("Pump: " + this.pumpId + ", " + "start FuelingRoundUpByAmount with amount: " + amount);
  211. var isSucceed = false;
  212. //this.context.Outgoing.WriteAsync(
  213. // new PresetAmountRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId), amount),
  214. // (request, testResponse) =>
  215. // testResponse.Adrs == this.dartPumpCommAddress &&
  216. // testResponse.BlockSeqNumber == request.BlockSeqNumber,
  217. // (request, response) =>
  218. // {
  219. // if (response != null)
  220. // {
  221. // if (response.ControlCharacter == ControlCharacter.ACK)
  222. // {
  223. // logger.Debug("Pump: " + this.pumpId + ", " + "PresetAmountRequest ACKed");
  224. // isSucceed = true;
  225. // }
  226. // else
  227. // {
  228. // logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest NAKed");
  229. // isSucceed = false;
  230. // }
  231. // }
  232. // else
  233. // {
  234. // logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest waiting ACK timed out");
  235. // isSucceed = false;
  236. // }
  237. // }, 1500);
  238. return isSucceed;
  239. }
  240. public async Task<bool> FuelingRoundUpByVolumeAsync(int volume)
  241. {
  242. return false;
  243. }
  244. public async Task<global::Wayne.FDCPOSLibrary.LogicalDeviceState> QueryStatusAsync()
  245. {
  246. return this.lastLogicalDeviceState;
  247. }
  248. public async Task<System.Tuple<int, int>> QueryTotalizerAsync(byte logicalNozzleId)
  249. {
  250. var result = new System.Tuple<int, int>(-1, -1);
  251. try
  252. {
  253. logger.Info("Pump: " + this.pumpId + ", " + "start QueryTotalizer pump with nozzle: " + logicalNozzleId);
  254. if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED
  255. || this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE)
  256. {
  257. logger.Info("Pump: " + this.pumpId + ", " + " Pump is in state FDC_CLOSED or FDC_OFFLINE, totalizer will return -1, -1");
  258. return new System.Tuple<int, int>(-1, -1);
  259. }
  260. var nozzlePhysicalId = this.nozzles.FirstOrDefault(n => n.LogicalId == logicalNozzleId)?.PhysicalId;
  261. if (nozzlePhysicalId == null)
  262. {
  263. logger.Info("Pump: " + this.pumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " does not exists, totalizer will return -1, -1");
  264. return new System.Tuple<int, int>(-1, -1);
  265. }
  266. var response = await this.context.Outgoing.WriteAsync(
  267. new QueryTotalizerRequest(this.pumpId, logicalNozzleId),
  268. (request, testResponse) =>
  269. {
  270. var parameters = testResponse.Parameters as StringDictionary;
  271. if (parameters == null || parameters["EventType"] != "TotalizerReceived")
  272. return false;
  273. string volumeTotal = parameters["VolumeTotalizer"];
  274. string amountTotal = parameters["AmountTotalizer"];
  275. logger.Info("Pump: " + this.pumpId + ", " + "QueryTotalizer for nozzle: " + logicalNozzleId +
  276. " succeed, volume total: " + volumeTotal + " , amount total: " + amountTotal);
  277. int amountTotalizer = 0;
  278. var amountTotalizerOriginal = decimal.Parse(amountTotal) * (decimal)Math.Pow(10, AmountDecimalDigits);
  279. if (amountTotalizerOriginal > int.MaxValue)
  280. {
  281. amountTotalizer = Convert.ToInt32(amountTotalizerOriginal / 10);
  282. }
  283. else
  284. {
  285. amountTotalizer = (int)(decimal.Parse(amountTotal) * (decimal)Math.Pow(10, AmountDecimalDigits));
  286. }
  287. var volumeTotalizer = (int)(decimal.Parse(volumeTotal) * (decimal)Math.Pow(10, volumeTotalizerDecimalDigits));
  288. result = new System.Tuple<int, int>(amountTotalizer, volumeTotalizer);
  289. return true;
  290. },
  291. 2000);
  292. }
  293. catch (Exception ex)
  294. {
  295. logger.Error("Pump: " + this.pumpId + ", " + ex);
  296. }
  297. //var response = await this.context.Outgoing.WriteAsync(new RequestTotalVolumeCountersRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId),
  298. // nozzlePhysicalId.Value),
  299. // (request, testResponse) =>
  300. // testResponse.Adrs == this.dartPumpCommAddress &&
  301. // testResponse.ControlCharacter == ControlCharacter.DATA, 3000);
  302. //if (response != null)
  303. //{
  304. // this.context.Outgoing.Write(new ACK(this.dartPumpCommAddress, response.BlockSeqNumber));
  305. // var totalCountersTrx =
  306. // new TotalCounters_TransactionData(response.TransactionDatas.First(f => f.TransactionNumber == 0x65));
  307. // logger.Info("Pump: " + this.pumpId + ", " + "QueryTotalizer for nozzle: " + logicalNozzleId + " succeed, volume total: " + totalCountersTrx.TotalValue);
  308. // result = new System.Tuple<int, int>(-1, totalCountersTrx.TotalValue);
  309. //}
  310. //else
  311. //{
  312. // logger.Error("Pump: " + this.pumpId + ", " + "QueryTotalizer waiting Data timed out");
  313. //}
  314. return result;
  315. }
  316. public async Task<bool> ResumeFuellingAsync()
  317. {
  318. throw new NotImplementedException();
  319. }
  320. public async Task<bool> SuspendFuellingAsync()
  321. {
  322. throw new NotImplementedException();
  323. }
  324. /// <summary>
  325. /// unauthorize the authed fueling point, will trigger wayne dart pump switched to `FILLING COMPLETE` state.
  326. /// </summary>
  327. /// <param name="logicalNozzleId">wayne dart no need specify nozzle id</param>
  328. /// <returns></returns>
  329. public async Task<bool> UnAuthorizeAsync(byte logicalNozzleId)
  330. {
  331. var unauthorizeSucceed = false;
  332. //logger.Info("Pump: " + this.pumpId + ", " + "Start UnAuthorize pump with nozzle: " + logicalNozzleId);
  333. //var authorizedResponse = await this.context.Outgoing.WriteAsync(
  334. // new StopRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
  335. // (_, testResponse) =>
  336. // testResponse.Adrs == this.dartPumpCommAddress &&
  337. // testResponse.BlockSeqNumber == _.BlockSeqNumber, 3500);
  338. //if (authorizedResponse != null)
  339. //{
  340. // if (authorizedResponse.ControlCharacter == ControlCharacter.ACK)
  341. // {
  342. // logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest ACKed and succeed");
  343. // unauthorizeSucceed = true;
  344. // }
  345. // else
  346. // logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest NAKed (rejected?)");
  347. //}
  348. //else
  349. //{
  350. // logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest timed out");
  351. //}
  352. return unauthorizeSucceed;
  353. }
  354. public async Task<bool> LockNozzleAsync(byte logicalNozzleId)
  355. {
  356. return false;
  357. }
  358. public async Task<bool> UnlockNozzleAsync(byte logicalNozzleId)
  359. {
  360. return false;
  361. }
  362. #endregion
  363. public PumpHandler(PumpGroupHandler parent, int pumpId,
  364. int amountDecimalDigits, int volumeDecimalDigits,
  365. int priceDecimalDigits, int volumeTotalizerDecimalDigits,
  366. string pumpXmlConfiguration)
  367. {
  368. this.parent = parent;
  369. this.pumpId = pumpId;
  370. this.amountDecimalDigits = amountDecimalDigits;
  371. this.volumeDecimalDigits = volumeDecimalDigits;
  372. this.priceDecimalDigits = priceDecimalDigits;
  373. this.volumeTotalizerDecimalDigits = volumeTotalizerDecimalDigits;
  374. // sample of pumpXmlConfiguration
  375. // <Pump pumpId='1' physicalId='1'>
  376. // <Nozzles>
  377. // <Nozzle logicalId='1' physicalId='1' defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb='2345'/>
  378. // <Nozzle logicalId='2' physicalId='2' defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb='2345'/>
  379. // <Nozzle logicalId='3' physicalId='3' defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb='2345'/>
  380. // </Nozzles>
  381. // </Pump>
  382. var xmlDocument = new XmlDocument();
  383. xmlDocument.LoadXml(pumpXmlConfiguration);
  384. var physicalPumpAddressConfiguratedInPump = byte.Parse(xmlDocument.SelectSingleNode("/Pump").Attributes["physicalId"].Value);
  385. if (physicalPumpAddressConfiguratedInPump > 0x20)
  386. 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");
  387. this.dartPumpCommAddress = (byte)(0x4F + physicalPumpAddressConfiguratedInPump);
  388. foreach (var nozzleElement in xmlDocument.GetElementsByTagName("Nozzle").Cast<XmlNode>())
  389. {
  390. var nozzlePhysicalId = byte.Parse(nozzleElement.Attributes["physicalId"].Value);
  391. var nozzleLogicalId = byte.Parse(nozzleElement.Attributes["logicalId"].Value);
  392. var nozzleRawDefaultPriceWithoutDecimal = nozzleElement.Attributes["defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb"].Value;
  393. if (nozzlePhysicalId < 1 || nozzlePhysicalId > 8)
  394. throw new ArgumentOutOfRangeException("Wayne dart pump only accept nozzle physical id range in config from 1 to 8");
  395. this.nozzles.Add(new LogicalNozzle(pumpId, nozzlePhysicalId, nozzleLogicalId, null) { ExpectingPriceOnFcSide = int.Parse(nozzleRawDefaultPriceWithoutDecimal) });
  396. logger.Info("Pump: " + this.pumpId
  397. + ", created a nozzle with logicalId: " + nozzleLogicalId + ", physicalId: " + nozzlePhysicalId
  398. + ", default raw price without decimal points: " + nozzleRawDefaultPriceWithoutDecimal);
  399. }
  400. this.deviceOfflineCountdownTimer = new System.Timers.Timer(10000);
  401. this.deviceOfflineCountdownTimer.Elapsed += (_, __) =>
  402. {
  403. if (DateTime.Now.Subtract(this.lastLogicalDeviceStateReceivedTime).TotalMinutes
  404. >= lastLogicalDeviceStateExpiredTime)
  405. {
  406. this.initialPumpStatueEverRetrieved = false;
  407. if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_OFFLINE)
  408. {
  409. this.lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE;
  410. logger.Info("Pump: " + this.pumpId + ", " + " State switched to FDC_OFFLINE due to long time no see pump data incoming");
  411. var safe0 = this.OnStateChange;
  412. safe0?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_OFFLINE));
  413. logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  414. }
  415. }
  416. };
  417. this.deviceOfflineCountdownTimer.Start();
  418. }
  419. public void OnFdcServerInit(Dictionary<string, object> parameters)
  420. {
  421. /* Wayne Dart pump will miss last price when disconnected or power off
  422. * from FC a while(pump state `PUMP NOT PROGRAMMED` indicates this happened), so here
  423. * is trying to recover the price from Fdc database, and then push to Pump*/
  424. if (parameters.ContainsKey("LastPriceChange"))
  425. {
  426. // nozzle logical id:rawPrice
  427. var lastPriceChanges = parameters["LastPriceChange"] as Dictionary<byte, int>;
  428. foreach (var priceChange in lastPriceChanges)
  429. {
  430. logger.Info("Pump: " + this.pumpId + ", " + "Pump " + this.pumpId + " OnFdcServerInit, load last price change " +
  431. "on logical nozzle: " + priceChange.Key + " with price: " + priceChange.Value);
  432. this.nozzles.First(n => n.LogicalId == priceChange.Key).ExpectingPriceOnFcSide = priceChange.Value;
  433. }
  434. }
  435. /* Load Last sale trx(from db) for void the case of FC accidently disconnect from Pump in fueling,
  436. and may cause a fueling trx gone from FC control */
  437. if (parameters.ContainsKey("LastFuelSaleTrx"))
  438. {
  439. // nozzle logical id:LastSale
  440. var lastFuelSaleTrxes = parameters["LastFuelSaleTrx"] as Dictionary<byte, FuelSaleTransaction>;
  441. foreach (var lastFuelSaleTrx in lastFuelSaleTrxes)
  442. {
  443. logger.Info("Pump: " + this.pumpId + ", OnFdcServerInit, load last volume Totalizer " +
  444. "on logical nozzle: " + lastFuelSaleTrx.Key + " with volume value: " + lastFuelSaleTrx.Value.VolumeTotalizer);
  445. this.nozzles.First(n => n.LogicalId == lastFuelSaleTrx.Key).VolumeTotalizer = lastFuelSaleTrx.Value.VolumeTotalizer;
  446. }
  447. }
  448. this.isOnFdcServerInitCalled = true;
  449. }
  450. public void Init(IContext<object, FccMessageBase> context)
  451. {
  452. this.context = context;
  453. this.context.Communicator.OnConnected += (a, b) =>
  454. {
  455. //this.context.Outgoing.Write(new ReturnStatusRequest(this.dartPumpCommAddress, 0));
  456. };
  457. //this.context.Communicator.OnDisconnected += (a, b) => this.pumpStatusEverReceived = false;
  458. }
  459. public Task Process(IContext<object, FccMessageBase> context)
  460. {
  461. var parameters = context.Incoming.Message.Parameters as StringDictionary;
  462. if (parameters == null)
  463. {
  464. logger.Error("Read invalid FCC message" + context.Incoming.Message.Parameters);
  465. return Task.CompletedTask;
  466. }
  467. this.lastLogicalDeviceStateReceivedTime = DateTime.Now;
  468. string eventType = parameters["EventType"];
  469. if (eventType == "FuellingStatusChange")
  470. {
  471. var logicalHoseId = byte.Parse(parameters["ho"]);
  472. var barcode = int.Parse(parameters["GR"]);
  473. var currentQty = double.Parse(parameters["VO"]) * Math.Pow(10, VolumeDecimalDigits);
  474. var currentAmount = decimal.Parse(parameters["AM"]) * Convert.ToDecimal(Math.Pow(10, AmountDecimalDigits));
  475. var currentPrice = double.Parse(parameters["PU"]) * Math.Pow(10, PriceDecimalDigits);
  476. var finished = parameters["Finished"] == "true" ? true : false;
  477. var paid = parameters["State"] == "PayableTransaction" ? false : true;
  478. var sequenceNo = int.Parse(parameters["FuelingSequenceNo"]);
  479. foreach (var item in parameters.Keys)
  480. {
  481. logger.Debug($"key: {item}, value: {parameters[(string)item]}");
  482. }
  483. logger.Debug(string.Format("currentQty: {0}, currentAmount: {1}, paid: {2}", currentQty, currentAmount, paid));
  484. if (paid) return Task.CompletedTask;
  485. var safe = this.OnCurrentFuellingStatusChange;
  486. safe?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction()
  487. {
  488. Nozzle = this.nozzles.First(n => n.LogicalId == logicalHoseId),
  489. Barcode = barcode,
  490. Amount = (int)currentAmount,
  491. Volumn = (int)currentQty,
  492. Price = Convert.ToInt32(currentPrice),
  493. Finished = finished,
  494. SequenceNumberGeneratedOnPhysicalPump = sequenceNo
  495. }));
  496. }
  497. else if (eventType == "PumpStatusChange")
  498. {
  499. LogicalNozzle ln = null;
  500. var pumpStatusStr = parameters["ST"];
  501. if (pumpStatusStr == "ERROR")
  502. {
  503. this.lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE;
  504. }
  505. else if (pumpStatusStr == "IDLE")
  506. {
  507. this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
  508. }
  509. else if (pumpStatusStr == "CALLING")
  510. {
  511. var logicalHoseId = byte.Parse(parameters["ho"]);
  512. ln = new LogicalNozzle(PumpId, logicalHoseId, logicalHoseId, null);
  513. this.lastLogicalDeviceState = LogicalDeviceState.FDC_CALLING;
  514. }
  515. else if (pumpStatusStr == "AUTHORIZED")
  516. {
  517. if (parameters.ContainsKey("ho"))
  518. {
  519. var logicalHoseId = byte.Parse(parameters["ho"]);
  520. ln = new LogicalNozzle(PumpId, logicalHoseId, logicalHoseId, null);
  521. }
  522. this.lastLogicalDeviceState = LogicalDeviceState.FDC_AUTHORISED;
  523. }
  524. else if (pumpStatusStr == "FUELLING")
  525. {
  526. var logicalHoseId = byte.Parse(parameters["ho"]);
  527. ln = new LogicalNozzle(PumpId, logicalHoseId, logicalHoseId, null);
  528. this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING;
  529. }
  530. else
  531. {
  532. this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING;
  533. }
  534. var safe = this.OnStateChange;
  535. var e = ln == null ? new FdcPumpControllerOnStateChangeEventArg(this.lastLogicalDeviceState) : new FdcPumpControllerOnStateChangeEventArg(this.lastLogicalDeviceState, ln);
  536. safe?.Invoke(this, e);
  537. }
  538. else
  539. { }
  540. return Task.CompletedTask;
  541. }
  542. private void UnAuthorizePumpAndSwithToFdcReady()
  543. {
  544. //this.context.Outgoing.WriteAsync(
  545. // new StopRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
  546. // (request, testResponse) =>
  547. // testResponse.Adrs == this.dartPumpCommAddress &&
  548. // testResponse.BlockSeqNumber == request.BlockSeqNumber,
  549. // (request, response) =>
  550. // {
  551. // if (response != null)
  552. // {
  553. // if (response.ControlCharacter == ControlCharacter.ACK)
  554. // {
  555. // if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY)
  556. // {
  557. // logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_READY due to StopRequest acked.");
  558. // this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
  559. // var safe3 = this.OnStateChange;
  560. // safe3?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
  561. // logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  562. // return;
  563. // }
  564. // }
  565. // else
  566. // logger.Error("Pump: " + this.pumpId + ", " + "StopRequest NAKed.");
  567. // }
  568. // else
  569. // logger.Error("Pump: " + this.pumpId + ", " + "StopRequest request timed out for ack.");
  570. // if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY)
  571. // {
  572. // logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_READY though StopRequest failed");
  573. // this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
  574. // var safe2 = this.OnStateChange;
  575. // safe2?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
  576. // logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
  577. // }
  578. // }, 1500);
  579. }
  580. private System.Timers.Timer retryReadLastFillTimer;
  581. private int onReadingLastFill = 0;
  582. /// <summary>
  583. /// may get called via 2 route:
  584. /// 1. received a Filling_Complete from pump.
  585. /// 2. by a delay timer that fired by nozzle IN from FDC_FUELING state.
  586. /// </summary>
  587. /// <param name="nozzlePhysicalIdForTrxDoneOn"></param>
  588. private void LoopReadLastFill(byte nozzlePhysicalIdForTrxDoneOn)
  589. {
  590. if (0 == Interlocked.CompareExchange(ref this.onReadingLastFill, 1, 0))
  591. {
  592. this.retryReadLastFillTimer?.Stop();
  593. this.retryReadLastFillTimer?.Dispose();
  594. // disable auth since last fill has not been read yet, this happens in real site with bad wire connection condition.
  595. //this.lastFillRetrieved = false;
  596. this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (t) =>
  597. {
  598. if (t != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
  599. logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 1st time");
  600. this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (tt) =>
  601. {
  602. if (tt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
  603. logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 2nd time");
  604. this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (ttt) =>
  605. {
  606. if (ttt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
  607. logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 3rd time");
  608. this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (tttt) =>
  609. {
  610. if (tttt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
  611. logger.Error("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will NOT retry anymore (total read 4 times)");
  612. // Have no way but release it to allow continue pump auth and fueling, But a fuel sale has been lost!
  613. this.onReadingLastFill = 0;
  614. this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true;
  615. });
  616. });
  617. });
  618. });
  619. }
  620. }
  621. /// <summary>
  622. ///
  623. /// </summary>
  624. /// <param name="targetNozzlePhysicalId"></param>
  625. /// <param name="callback">FdcTransaction is null when retrieved failed, like time out. otherwise, an object will return</param>
  626. private void ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(byte targetNozzlePhysicalId,
  627. Action<FdcTransaction> callback)
  628. {
  629. }
  630. }
  631. }