using Censtar_31064V105OrV106_Pump.MessageEntity.Incoming; using Censtar_31064V105OrV106_Pump.MessageEntity.Outgoing; using Edge.Core.IndustryStandardInterface.Pump; using Edge.Core.Parser.BinaryParser.MessageEntity; using Edge.Core.Processor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Stateless; using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using Wayne.FDCPOSLibrary; using static Censtar_31064V105OrV106_Pump.PumpGroupHandler; using static Censtar_31064V105OrV106_Pump.PumpGroupHandler.DispenserParameter; namespace Censtar_31064V105OrV106_Pump { public class StatePumpHandler : IFdcPumpController { public DispenserModelEnum DispenserModel { get; set; } public DispenserAuthorizeModeEnum DispenserAuthorizeMode { get; set; } public SetPostFuelingLockTypeRequest.LockTypeEnum DispenserPostFuelingLockMode { get; set; } private IServiceProvider services; static ILogger logger = NullLogger.Instance; private LogicalDeviceState lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE; private DateTime lastLogicalDeviceStateReceivedTime; // by seconds, change this value need change the correlated deviceOfflineCountdownTimer's interval as well public const int lastLogicalDeviceStateExpiredTime = 6; private List nozzles = new List(); private System.Timers.Timer deviceOfflineCountdownTimer; private IContext context; private bool isOnFdcServerInitCalled = false; StateMachine stateless = new StateMachine(LogicalDeviceState.FDC_OFFLINE); private int amountDecimalDigits; private int volumeDecimalDigits; private int priceDecimalDigits; private int volumeTotalizerDecimalDigits; private StateMachine.TriggerWithParameters nozzleFuelNumbersIsRunningTrigger; private int pumpId; public string Name => "Censtar_31064V105OrV106_Pump"; public int PumpId => this.pumpId; public IEnumerable Nozzles => this.nozzles; public int AmountDecimalDigits { get; set; } public int VolumeDecimalDigits { get; set; } public int PriceDecimalDigits { get; set; } public int VolumeTotalizerDecimalDigits { get; set; } public Guid Id => Guid.NewGuid(); public int PumpPhysicalId { get; set; } public event EventHandler OnStateChange; public event EventHandler OnCurrentFuellingStatusChange; private enum Trigger { //AnyPumpMsgReceived, AnyPumpMsgHaveNotReceivedForWhile, 关机_解锁_无交易可读, 关机_解锁_有交易可读, 关机_上锁_无交易可读, 关机_上锁_有交易可读, 加油_非总台预置加油, 加油_总台预置加油, NozzleFuelNumbersIsRunning } public StatePumpHandler(PumpParameter pumpParameter, IServiceProvider services) { this.services = services; var loggerFactory = services.GetRequiredService(); logger = loggerFactory.CreateLogger("PumpHandler"); var jsonSerializerOptions = new JsonSerializerOptions() { WriteIndented = true, }; jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); this.pumpId = pumpParameter.PumpId; if (pumpParameter.NozzleParameters == null || !pumpParameter.NozzleParameters.Any()) throw new ArgumentException("Pump must has nozzle configs"); for (byte i = 0; i < pumpParameter.NozzleParameters.Count(); i++) { this.nozzles.Add( new LogicalNozzle(this.pumpId, pumpParameter.NozzleParameters.ElementAt(i).NozzleNumber, (byte)(i + 1), null)); logger.LogInformation("Pump: " + this.pumpId + " created a nozzle with NozzlePhysicalId: " + pumpParameter.NozzleParameters.ElementAt(i).NozzleNumber + ", NozzleLogicalId: " + (i + 1)); } this.deviceOfflineCountdownTimer = new System.Timers.Timer(2000); this.deviceOfflineCountdownTimer.Elapsed += async (_, __) => { if (DateTime.Now.Subtract(this.lastLogicalDeviceStateReceivedTime).TotalSeconds >= lastLogicalDeviceStateExpiredTime) await this.stateless.FireAsync(Trigger.AnyPumpMsgHaveNotReceivedForWhile); }; this.deviceOfflineCountdownTimer.Start(); this.stateless.OnTransitioned(async (transition) => { if (transition.Destination != transition.Source) { this.lastLogicalDeviceState = transition.Destination; logger.LogInformation("Pump: " + this.pumpId + ", " + " State switched from: " + transition.Source + " to " + transition.Destination); this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(transition.Destination, this.nozzles.FirstOrDefault())); } if (transition.Source == LogicalDeviceState.FDC_OFFLINE) { this.context.Outgoing.Write(new ReadVersionRequest() { NozzleNumber = this.nozzles.First().PhysicalId }); this.context.Outgoing.Write(new SetDateAndTimeRequest(DateTime.Now) { NozzleNumber = this.nozzles.First().PhysicalId }); this.context.Outgoing.Write(new ReadUnitOfMeasurementRequest() { NozzleNumber = this.nozzles.First().PhysicalId }); logger.LogInformation("Pump: " + this.pumpId + ", will Set AuthorizeMode to: " + this.DispenserAuthorizeMode); var response = await this.context.Outgoing.WriteAsync( this.DispenserAuthorizeMode == DispenserAuthorizeModeEnum.开放 ? new SetMonitoringModeRequest(SetMonitoringModeRequest.MonitoringModeEnum.开放) { NozzleNumber = this.nozzles.First().PhysicalId } : new SetMonitoringModeRequest(SetMonitoringModeRequest.MonitoringModeEnum.监控) { NozzleNumber = this.nozzles.First().PhysicalId }, (_, testResponse) => testResponse is SetMonitoringModeAnswer, 3000); logger.LogInformation("Pump: " + this.pumpId + ", Set AuthorizeMode " + (response == null ? "failed" : "succeed")); logger.LogInformation("Pump: " + this.pumpId + ", will Set PostFuelingLockType to: " + this.DispenserPostFuelingLockMode); response = await this.context.Outgoing.WriteAsync( new SetPostFuelingLockTypeRequest(this.DispenserPostFuelingLockMode, "123") { NozzleNumber = this.nozzles.First().PhysicalId }, (_, testResponse) => testResponse is SetPostFuelingLockTypeAnswer, 3000); logger.LogInformation("Pump: " + this.pumpId + ", Set PostFuelingLockType " + (response == null ? "failed" : "succeed")); } }); this.stateless.Configure(LogicalDeviceState.FDC_OFFLINE) .OnEntryAsync(async () => { if (this.DispenserAuthorizeMode == DispenserAuthorizeModeEnum.监控) { logger.LogInformation("Pump: " + this.pumpId + ", 送开机信号... due to DispenserAuthorizeMode==监控"); var response = await this.context.Outgoing.WriteAsync( new StartRequest() { NozzleNumber = this.nozzles.First().PhysicalId }, (_, testResponse) => testResponse is StartAnswer, 3000); logger.LogInformation("Pump: " + this.pumpId + ", 送开机信号 acked with " + (response == null ? "failed" : "succeed")); } }) //.Ignore(Trigger.AnyPumpMsgReceived) .Ignore(Trigger.AnyPumpMsgHaveNotReceivedForWhile) .PermitIf(Trigger.加油_总台预置加油, LogicalDeviceState.FDC_CALLING, () => true) .PermitIf(Trigger.加油_非总台预置加油, LogicalDeviceState.FDC_CALLING, () => true) .PermitIf(Trigger.关机_上锁_无交易可读, LogicalDeviceState.FDC_READY, () => true) // use FDC_OUTOFORDER for reading the trx .PermitIf(Trigger.关机_上锁_有交易可读, LogicalDeviceState.FDC_OUTOFORDER, () => true) .PermitIf(Trigger.关机_解锁_无交易可读, LogicalDeviceState.FDC_READY, () => true) .PermitIf(Trigger.关机_解锁_有交易可读, LogicalDeviceState.FDC_OUTOFORDER, () => true); this.stateless.Configure(LogicalDeviceState.FDC_CALLING) .OnEntryAsync(async () => { }) .Ignore(Trigger.AnyPumpMsgHaveNotReceivedForWhile) .PermitIf(Trigger.加油_总台预置加油, LogicalDeviceState.FDC_AUTHORISED, () => true) .PermitIf(Trigger.加油_非总台预置加油, LogicalDeviceState.FDC_AUTHORISED, () => true); this.stateless.Configure(LogicalDeviceState.FDC_AUTHORISED) .OnEntryAsync(async () => { }) .Ignore(Trigger.AnyPumpMsgHaveNotReceivedForWhile) .PermitIf(Trigger.加油_总台预置加油, LogicalDeviceState.FDC_FUELLING, () => true) .PermitIf(Trigger.加油_非总台预置加油, LogicalDeviceState.FDC_FUELLING, () => true); this.nozzleFuelNumbersIsRunningTrigger = this.stateless.SetTriggerParameters(Trigger.NozzleFuelNumbersIsRunning); this.stateless.Configure(LogicalDeviceState.FDC_FUELLING) .OnEntryFromAsync(this.nozzleFuelNumbersIsRunningTrigger, async (arg) => { this.OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(arg)); }) .OnEntryAsync(async () => { }) .OnExitAsync(async () => { }) .PermitReentry(Trigger.NozzleFuelNumbersIsRunning) .Ignore(Trigger.AnyPumpMsgHaveNotReceivedForWhile) .PermitIf(Trigger.关机_上锁_有交易可读, LogicalDeviceState.FDC_OUTOFORDER, () => true) .PermitIf(Trigger.关机_解锁_有交易可读, LogicalDeviceState.FDC_OUTOFORDER, () => true) .PermitIf(Trigger.关机_上锁_无交易可读, LogicalDeviceState.FDC_READY, () => true) .PermitIf(Trigger.关机_解锁_无交易可读, LogicalDeviceState.FDC_READY, () => true); this.stateless.Configure(LogicalDeviceState.FDC_OUTOFORDER) .OnEntryAsync(async () => { logger.LogInformation("Pump: " + this.pumpId + ", Reading last sale..."); var response = await this.context.Outgoing.WriteAsync( new ReadTransactionDataRequest() { NozzleNumber = this.nozzles.First().PhysicalId }, (_, testResponse) => (testResponse is ReadTransactionDataAndHasNoDataAnswer) || (testResponse is ReadTransactionDataAndHasDataAnswer), 3000); if (response is ReadTransactionDataAndHasDataAnswer hasDataAnswer) { logger.LogDebug("Pump: " + this.pumpId + ", Retrieved fuel sale trx, amt: " + hasDataAnswer.金额 + ", vol: " + hasDataAnswer.升或公斤 + ", seqNo.: " + hasDataAnswer.成交链号_SequenceNumber); var newTrx = new FdcTransaction() { // 只有一把枪 Nozzle = this.nozzles.First(), Amount = hasDataAnswer.金额, Volumn = hasDataAnswer.升或公斤, Price = this.nozzles.First().RealPriceOnPhysicalPump ?? -1, SequenceNumberGeneratedOnPhysicalPump = hasDataAnswer.成交链号_SequenceNumber, Finished = true, }; this.OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(newTrx)); } else if(response is ReadTransactionDataAndHasNoDataAnswer hasNoDataAnswer) { // should switch to FdcReady? } else { logger.LogWarning("Pump: " + this.pumpId + ", fuel sale trx is missing for final read(auto tried one more time and still failed), trx will lost"); return; } }) .Ignore(Trigger.AnyPumpMsgHaveNotReceivedForWhile) .PermitIf(Trigger.关机_上锁_无交易可读, LogicalDeviceState.FDC_READY, () => true) .PermitIf(Trigger.关机_解锁_无交易可读, LogicalDeviceState.FDC_READY, () => true); } public async Task Process(IContext context) { if (context.Incoming.Message is ReadRealTimeFuelingDataInPumpFuelingStateAnswer fuelingStateDataResponse) { await this.stateless.FireAsync(Trigger.加油_非总台预置加油); //await this.stateless.FireAsync(Trigger.NozzleFuelNumbersIsRunning); await this.stateless.FireAsync(this.nozzleFuelNumbersIsRunningTrigger, new FdcTransaction() { // 只有一把枪 Nozzle = this.nozzles.First(), //Amount = readFuelingDataResponse.Amount, Volumn = fuelingStateDataResponse.数量, Price = this.nozzles.First().RealPriceOnPhysicalPump ?? -1, Finished = false, }); } else if (context.Incoming.Message is ReadRealTimeFuelingAmountDataInPumpNonFuelingStateAnswer IdleStateAmountDataResponse) { if (this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.加油后加锁_不可解锁 || this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.加油后加锁_可解锁 || this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.室外解锁密码) await this.stateless.FireAsync(Trigger.关机_上锁_有交易可读); else await this.stateless.FireAsync(Trigger.关机_解锁_有交易可读); } else if (context.Incoming.Message is ReadRealTimeFuelingVolumeDataInPumpNonFuelingStateAnswer IdleStateVolumeDataResponse) { if (this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.加油后加锁_不可解锁 || this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.加油后加锁_可解锁 || this.DispenserPostFuelingLockMode == SetPostFuelingLockTypeRequest.LockTypeEnum.室外解锁密码) await this.stateless.FireAsync(Trigger.关机_上锁_有交易可读); else await this.stateless.FireAsync(Trigger.关机_解锁_有交易可读); } } public Task AuthorizeAsync(byte logicalNozzleId) { throw new NotImplementedException(); } public Task AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId) { throw new NotImplementedException(); } public Task AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId) { throw new NotImplementedException(); } public Task ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId) { throw new NotImplementedException(); } public Task FuelingRoundUpByAmountAsync(int amount) { throw new NotImplementedException(); } public Task FuelingRoundUpByVolumeAsync(int volume) { throw new NotImplementedException(); } public void Init(IContext context) { throw new NotImplementedException(); } public Task LockNozzleAsync(byte logicalNozzleId) { throw new NotImplementedException(); } public void OnFdcServerInit(Dictionary parameters) { throw new NotImplementedException(); } public Task QueryStatusAsync() { throw new NotImplementedException(); } public Task> QueryTotalizerAsync(byte logicalNozzleId) { throw new NotImplementedException(); } public Task ResumeFuellingAsync() { throw new NotImplementedException(); } public Task SuspendFuellingAsync() { throw new NotImplementedException(); } public Task UnAuthorizeAsync(byte logicalNozzleId) { throw new NotImplementedException(); } public Task UnlockNozzleAsync(byte logicalNozzleId) { throw new NotImplementedException(); } } }