StatePumpHandler.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. using Censtar_31064V105OrV106_Pump.MessageEntity.Incoming;
  2. using Censtar_31064V105OrV106_Pump.MessageEntity.Outgoing;
  3. using Edge.Core.IndustryStandardInterface.Pump;
  4. using Edge.Core.Parser.BinaryParser.MessageEntity;
  5. using Edge.Core.Processor;
  6. using Microsoft.Extensions.DependencyInjection;
  7. using Microsoft.Extensions.Logging;
  8. using Microsoft.Extensions.Logging.Abstractions;
  9. using Stateless;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Linq;
  13. using System.Text.Json;
  14. using System.Text.Json.Serialization;
  15. using System.Threading.Tasks;
  16. using Wayne.FDCPOSLibrary;
  17. using static Censtar_31064V105OrV106_Pump.PumpGroupHandler;
  18. using static Censtar_31064V105OrV106_Pump.PumpGroupHandler.DispenserParameter;
  19. namespace Censtar_31064V105OrV106_Pump
  20. {
  21. public class StatePumpHandler : IFdcPumpController
  22. {
  23. public DispenserModelEnum DispenserModel { get; set; }
  24. public DispenserAuthorizeModeEnum DispenserAuthorizeMode { get; set; }
  25. public SetPostFuelingLockTypeRequest.LockTypeEnum DispenserPostFuelingLockMode { get; set; }
  26. private IServiceProvider services;
  27. static ILogger logger = NullLogger.Instance;
  28. private LogicalDeviceState lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE;
  29. private DateTime lastLogicalDeviceStateReceivedTime;
  30. // by seconds, change this value need change the correlated deviceOfflineCountdownTimer's interval as well
  31. public const int lastLogicalDeviceStateExpiredTime = 6;
  32. private List<LogicalNozzle> nozzles = new List<LogicalNozzle>();
  33. private System.Timers.Timer deviceOfflineCountdownTimer;
  34. private IContext<byte[], MessageTemplateBase> context;
  35. private bool isOnFdcServerInitCalled = false;
  36. StateMachine<LogicalDeviceState, Trigger> stateless
  37. = new StateMachine<LogicalDeviceState, Trigger>(LogicalDeviceState.FDC_OFFLINE);
  38. private int amountDecimalDigits;
  39. private int volumeDecimalDigits;
  40. private int priceDecimalDigits;
  41. private int volumeTotalizerDecimalDigits;
  42. private StateMachine<LogicalDeviceState, Trigger>.TriggerWithParameters<FdcTransaction> nozzleFuelNumbersIsRunningTrigger;
  43. private int pumpId;
  44. public string Name => "Censtar_31064V105OrV106_Pump";
  45. public int PumpId => this.pumpId;
  46. public IEnumerable<LogicalNozzle> Nozzles => this.nozzles;
  47. public int AmountDecimalDigits { get; set; }
  48. public int VolumeDecimalDigits { get; set; }
  49. public int PriceDecimalDigits { get; set; }
  50. public int VolumeTotalizerDecimalDigits { get; set; }
  51. public Guid Id => Guid.NewGuid();
  52. public int PumpPhysicalId { get; set; }
  53. public event EventHandler<FdcPumpControllerOnStateChangeEventArg> OnStateChange;
  54. public event EventHandler<FdcTransactionDoneEventArg> OnCurrentFuellingStatusChange;
  55. private enum Trigger
  56. {
  57. //AnyPumpMsgReceived,
  58. AnyPumpMsgHaveNotReceivedForWhile,
  59. 关机_解锁_无交易可读,
  60. 关机_解锁_有交易可读,
  61. 关机_上锁_无交易可读,
  62. 关机_上锁_有交易可读,
  63. 加油_非总台预置加油,
  64. 加油_总台预置加油,
  65. NozzleFuelNumbersIsRunning
  66. }
  67. public StatePumpHandler(PumpParameter pumpParameter,
  68. IServiceProvider services)
  69. {
  70. this.services = services;
  71. var loggerFactory = services.GetRequiredService<ILoggerFactory>();
  72. logger = loggerFactory.CreateLogger("PumpHandler");
  73. var jsonSerializerOptions = new JsonSerializerOptions()
  74. {
  75. WriteIndented = true,
  76. };
  77. jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
  78. this.pumpId = pumpParameter.PumpId;
  79. if (pumpParameter.NozzleParameters == null || !pumpParameter.NozzleParameters.Any())
  80. throw new ArgumentException("Pump must has nozzle configs");
  81. for (byte i = 0; i < pumpParameter.NozzleParameters.Count(); i++)
  82. {
  83. this.nozzles.Add(
  84. new LogicalNozzle(this.pumpId,
  85. pumpParameter.NozzleParameters.ElementAt(i).NozzleNumber,
  86. (byte)(i + 1), null));
  87. logger.LogInformation("Pump: " + this.pumpId + " created a nozzle with NozzlePhysicalId: " +
  88. pumpParameter.NozzleParameters.ElementAt(i).NozzleNumber +
  89. ", NozzleLogicalId: " + (i + 1));
  90. }
  91. this.deviceOfflineCountdownTimer = new System.Timers.Timer(2000);
  92. this.deviceOfflineCountdownTimer.Elapsed += async (_, __) =>
  93. {
  94. if (DateTime.Now.Subtract(this.lastLogicalDeviceStateReceivedTime).TotalSeconds
  95. >= lastLogicalDeviceStateExpiredTime)
  96. await this.stateless.FireAsync(Trigger.AnyPumpMsgHaveNotReceivedForWhile);
  97. };
  98. this.deviceOfflineCountdownTimer.Start();
  99. this.stateless.OnTransitioned(async (transition) =>
  100. {
  101. if (transition.Destination != transition.Source)
  102. {
  103. this.lastLogicalDeviceState = transition.Destination;
  104. logger.LogInformation("Pump: " + this.pumpId + ", " + " State switched from: " + transition.Source + " to " + transition.Destination);
  105. this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(transition.Destination, this.nozzles.FirstOrDefault()));
  106. }
  107. if (transition.Source == LogicalDeviceState.FDC_OFFLINE)
  108. {
  109. this.context.Outgoing.Write(new ReadVersionRequest() { NozzleNumber = this.nozzles.First().PhysicalId });
  110. this.context.Outgoing.Write(new SetDateAndTimeRequest(DateTime.Now) { NozzleNumber = this.nozzles.First().PhysicalId });
  111. this.context.Outgoing.Write(new ReadUnitOfMeasurementRequest() { NozzleNumber = this.nozzles.First().PhysicalId });
  112. logger.LogInformation("Pump: " + this.pumpId + ", will Set AuthorizeMode to: " + this.DispenserAuthorizeMode);
  113. var response = await this.context.Outgoing.WriteAsync(
  114. this.DispenserAuthorizeMode == DispenserAuthorizeModeEnum.开放 ?
  115. new SetMonitoringModeRequest(SetMonitoringModeRequest.MonitoringModeEnum.开放) { NozzleNumber = this.nozzles.First().PhysicalId }
  116. : new SetMonitoringModeRequest(SetMonitoringModeRequest.MonitoringModeEnum.监控) { NozzleNumber = this.nozzles.First().PhysicalId },
  117. (_, testResponse) => testResponse is SetMonitoringModeAnswer, 3000);
  118. logger.LogInformation("Pump: " + this.pumpId + ", Set AuthorizeMode " + (response == null ? "failed" : "succeed"));
  119. logger.LogInformation("Pump: " + this.pumpId + ", will Set PostFuelingLockType to: " + this.DispenserPostFuelingLockMode);
  120. response = await this.context.Outgoing.WriteAsync(
  121. new SetPostFuelingLockTypeRequest(this.DispenserPostFuelingLockMode, "123") { NozzleNumber = this.nozzles.First().PhysicalId },
  122. (_, testResponse) => testResponse is SetPostFuelingLockTypeAnswer, 3000);
  123. logger.LogInformation("Pump: " + this.pumpId + ", Set PostFuelingLockType " + (response == null ? "failed" : "succeed"));
  124. }
  125. });
  126. this.stateless.Configure(LogicalDeviceState.FDC_OFFLINE)
  127. .OnEntryAsync(async () =>
  128. {
  129. if (this.DispenserAuthorizeMode == DispenserAuthorizeModeEnum.监控)
  130. {
  131. logger.LogInformation("Pump: " + this.pumpId + ", 送开机信号... due to DispenserAuthorizeMode==监控");
  132. var response = await this.context.Outgoing.WriteAsync(
  133. new StartRequest() { NozzleNumber = this.nozzles.First().PhysicalId },
  134. (_, testResponse) => testResponse is StartAnswer, 3000);
  135. logger.LogInformation("Pump: " + this.pumpId + ", 送开机信号 acked with " + (response == null ? "failed" : "succeed"));
  136. }
  137. })
  138. //.Ignore(Trigger.AnyPumpMsgReceived)
  139. .Ignore(Trigger.AnyPumpMsgHaveNotReceivedForWhile)
  140. .PermitIf(Trigger.加油_总台预置加油, LogicalDeviceState.FDC_CALLING, () => true)
  141. .PermitIf(Trigger.加油_非总台预置加油, LogicalDeviceState.FDC_CALLING, () => true)
  142. .PermitIf(Trigger.关机_上锁_无交易可读, LogicalDeviceState.FDC_READY, () => true)
  143. // use FDC_OUTOFORDER for reading the trx
  144. .PermitIf(Trigger.关机_上锁_有交易可读, LogicalDeviceState.FDC_OUTOFORDER, () => true)
  145. .PermitIf(Trigger.关机_解锁_无交易可读, LogicalDeviceState.FDC_READY, () => true)
  146. .PermitIf(Trigger.关机_解锁_有交易可读, LogicalDeviceState.FDC_OUTOFORDER, () => true);
  147. this.stateless.Configure(LogicalDeviceState.FDC_CALLING)
  148. .OnEntryAsync(async () => { })
  149. .Ignore(Trigger.AnyPumpMsgHaveNotReceivedForWhile)
  150. .PermitIf(Trigger.加油_总台预置加油, LogicalDeviceState.FDC_AUTHORISED, () => true)
  151. .PermitIf(Trigger.加油_非总台预置加油, LogicalDeviceState.FDC_AUTHORISED, () => true);
  152. this.stateless.Configure(LogicalDeviceState.FDC_AUTHORISED)
  153. .OnEntryAsync(async () => { })
  154. .Ignore(Trigger.AnyPumpMsgHaveNotReceivedForWhile)
  155. .PermitIf(Trigger.加油_总台预置加油, LogicalDeviceState.FDC_FUELLING, () => true)
  156. .PermitIf(Trigger.加油_非总台预置加油, LogicalDeviceState.FDC_FUELLING, () => true);
  157. this.nozzleFuelNumbersIsRunningTrigger =
  158. this.stateless.SetTriggerParameters<FdcTransaction>(Trigger.NozzleFuelNumbersIsRunning);
  159. this.stateless.Configure(LogicalDeviceState.FDC_FUELLING)
  160. .OnEntryFromAsync(this.nozzleFuelNumbersIsRunningTrigger, async (arg) =>
  161. {
  162. this.OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(arg));
  163. })
  164. .OnEntryAsync(async () => { })
  165. .OnExitAsync(async () => { })
  166. .PermitReentry(Trigger.NozzleFuelNumbersIsRunning)
  167. .Ignore(Trigger.AnyPumpMsgHaveNotReceivedForWhile)
  168. .PermitIf(Trigger.关机_上锁_有交易可读, LogicalDeviceState.FDC_OUTOFORDER, () => true)
  169. .PermitIf(Trigger.关机_解锁_有交易可读, LogicalDeviceState.FDC_OUTOFORDER, () => true)
  170. .PermitIf(Trigger.关机_上锁_无交易可读, LogicalDeviceState.FDC_READY, () => true)
  171. .PermitIf(Trigger.关机_解锁_无交易可读, LogicalDeviceState.FDC_READY, () => true);
  172. this.stateless.Configure(LogicalDeviceState.FDC_OUTOFORDER)
  173. .OnEntryAsync(async () =>
  174. {
  175. logger.LogInformation("Pump: " + this.pumpId + ", Reading last sale...");
  176. var response = await this.context.Outgoing.WriteAsync(
  177. new ReadTransactionDataRequest() { NozzleNumber = this.nozzles.First().PhysicalId },
  178. (_, testResponse) =>
  179. (testResponse is ReadTransactionDataAndHasNoDataAnswer)
  180. || (testResponse is ReadTransactionDataAndHasDataAnswer),
  181. 3000);
  182. if (response is ReadTransactionDataAndHasDataAnswer hasDataAnswer)
  183. {
  184. logger.LogDebug("Pump: " + this.pumpId + ", Retrieved fuel sale trx, amt: " +
  185. hasDataAnswer.金额 + ", vol: " + hasDataAnswer.升或公斤 +
  186. ", seqNo.: " + hasDataAnswer.成交链号_SequenceNumber);
  187. var newTrx = new FdcTransaction()
  188. {
  189. // 只有一把枪
  190. Nozzle = this.nozzles.First(),
  191. Amount = hasDataAnswer.金额,
  192. Volumn = hasDataAnswer.升或公斤,
  193. Price = this.nozzles.First().RealPriceOnPhysicalPump ?? -1,
  194. SequenceNumberGeneratedOnPhysicalPump = hasDataAnswer.成交链号_SequenceNumber,
  195. Finished = true,
  196. };
  197. this.OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(newTrx));
  198. }
  199. else if(response is ReadTransactionDataAndHasNoDataAnswer hasNoDataAnswer)
  200. {
  201. // should switch to FdcReady?
  202. }
  203. else
  204. {
  205. logger.LogWarning("Pump: " + this.pumpId + ", fuel sale trx is missing for final read(auto tried one more time and still failed), trx will lost");
  206. return;
  207. }
  208. })
  209. .Ignore(Trigger.AnyPumpMsgHaveNotReceivedForWhile)
  210. .PermitIf(Trigger.关机_上锁_无交易可读, LogicalDeviceState.FDC_READY, () => true)
  211. .PermitIf(Trigger.关机_解锁_无交易可读, LogicalDeviceState.FDC_READY, () => true);
  212. }
  213. public async Task Process(IContext<byte[], MessageTemplateBase> context)
  214. {
  215. if (context.Incoming.Message is ReadRealTimeFuelingDataInPumpFuelingStateAnswer
  216. fuelingStateDataResponse)
  217. {
  218. await this.stateless.FireAsync(Trigger.加油_非总台预置加油);
  219. //await this.stateless.FireAsync(Trigger.NozzleFuelNumbersIsRunning);
  220. await this.stateless.FireAsync(this.nozzleFuelNumbersIsRunningTrigger,
  221. new FdcTransaction()
  222. {
  223. // 只有一把枪
  224. Nozzle = this.nozzles.First(),
  225. //Amount = readFuelingDataResponse.Amount,
  226. Volumn = fuelingStateDataResponse.数量,
  227. Price = this.nozzles.First().RealPriceOnPhysicalPump ?? -1,
  228. Finished = false,
  229. });
  230. }
  231. else if (context.Incoming.Message is ReadRealTimeFuelingAmountDataInPumpNonFuelingStateAnswer
  232. IdleStateAmountDataResponse)
  233. {
  234. if (this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.加油后加锁_不可解锁
  235. || this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.加油后加锁_可解锁
  236. || this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.室外解锁密码)
  237. await this.stateless.FireAsync(Trigger.关机_上锁_有交易可读);
  238. else
  239. await this.stateless.FireAsync(Trigger.关机_解锁_有交易可读);
  240. }
  241. else if (context.Incoming.Message is ReadRealTimeFuelingVolumeDataInPumpNonFuelingStateAnswer
  242. IdleStateVolumeDataResponse)
  243. {
  244. if (this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.加油后加锁_不可解锁
  245. || this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.加油后加锁_可解锁
  246. || this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.室外解锁密码)
  247. await this.stateless.FireAsync(Trigger.关机_上锁_有交易可读);
  248. else
  249. await this.stateless.FireAsync(Trigger.关机_解锁_有交易可读);
  250. }
  251. }
  252. public Task<bool> AuthorizeAsync(byte logicalNozzleId)
  253. {
  254. throw new NotImplementedException();
  255. }
  256. public Task<bool> AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId)
  257. {
  258. throw new NotImplementedException();
  259. }
  260. public Task<bool> AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId)
  261. {
  262. throw new NotImplementedException();
  263. }
  264. public Task<bool> ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId)
  265. {
  266. throw new NotImplementedException();
  267. }
  268. public Task<bool> FuelingRoundUpByAmountAsync(int amount)
  269. {
  270. throw new NotImplementedException();
  271. }
  272. public Task<bool> FuelingRoundUpByVolumeAsync(int volume)
  273. {
  274. throw new NotImplementedException();
  275. }
  276. public void Init(IContext<byte[], MessageTemplateBase> context)
  277. {
  278. throw new NotImplementedException();
  279. }
  280. public Task<bool> LockNozzleAsync(byte logicalNozzleId)
  281. {
  282. throw new NotImplementedException();
  283. }
  284. public void OnFdcServerInit(Dictionary<string, object> parameters)
  285. {
  286. throw new NotImplementedException();
  287. }
  288. public Task<LogicalDeviceState> QueryStatusAsync()
  289. {
  290. throw new NotImplementedException();
  291. }
  292. public Task<Tuple<int, int>> QueryTotalizerAsync(byte logicalNozzleId)
  293. {
  294. throw new NotImplementedException();
  295. }
  296. public Task<bool> ResumeFuellingAsync()
  297. {
  298. throw new NotImplementedException();
  299. }
  300. public Task<bool> SuspendFuellingAsync()
  301. {
  302. throw new NotImplementedException();
  303. }
  304. public Task<bool> UnAuthorizeAsync(byte logicalNozzleId)
  305. {
  306. throw new NotImplementedException();
  307. }
  308. public Task<bool> UnlockNozzleAsync(byte logicalNozzleId)
  309. {
  310. throw new NotImplementedException();
  311. }
  312. }
  313. }