using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Configuration; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading; using Timer = System.Timers.Timer; using System.Collections; using Edge.Core.Processor; using Edge.Core.IndustryStandardInterface.Pump; using Wayne.FDCPOSLibrary; using System.Xml; using Edge.Core.Database.Models; using System.Threading.Tasks; using Edge.Core.Processor.Communicator; using Edge.Core.Processor.Dispatcher.Attributes; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Stateless; using Microsoft.Extensions.DependencyInjection; namespace ZhongSheng_NonIC_Pump { public class StatePumpHandler : IFdcPumpController//, IDeviceHandler { private IServiceProvider services; private IContext context; private ILogger logger = NullLogger.Instance; private PumpGroupHandler parent; private LogicalDeviceState currentState = LogicalDeviceState.FDC_OFFLINE; public event EventHandler OnStateChange; /// /// fired on fueling process is on going, the fuel amount should keep changing. /// public event EventHandler OnCurrentFuellingStatusChange; StateMachine stateless = new StateMachine(LogicalDeviceState.FDC_OFFLINE); protected List nozzles; public IEnumerable Nozzles => this.nozzles; protected enum Trigger { //AnyPumpMsgReceived, AnyPumpMsgHaveNotReceivedForWhile, NozzleLifted_And_开机, NozzleLifted_And_停机, NozzleReplaced_And_开机, NozzleReplaced_And_停机, NozzleFuelNumbersIsRunning, //PumpAuthorizedByFC, } public int AmountDecimalDigits => 2; public int VolumeDecimalDigits => 2; public int PriceDecimalDigits => 2; public int VolumeTotalizerDecimalDigits => 2; /// /// public int PumpId { get; private set; } /// /// will set it to 油机端的配置值,枪号为以整个加油站为基础的油枪顺序编号, 就是全站枪号 /// public int PumpPhysicalId { get; private set; } public string Name => "ZhongSheng_NonIC_Pump"; /// /// 每把枪就是一个Pump即一个加油点 /// /// /// 油机端的配置值,枪号为以整个加油站为基础的油枪顺序编号, 就是全站枪号? public StatePumpHandler(PumpGroupHandler parent, byte pumpId, byte dispenserSideNozzleId, IServiceProvider services) { this.parent = parent; this.services = services; var loggerFactory = services.GetRequiredService(); this.logger = loggerFactory.CreateLogger("PumpHandler"); this.PumpId = pumpId; this.PumpPhysicalId = dispenserSideNozzleId; //每把枪就是一个Pump即一个加油点 this.nozzles = new List() { new LogicalNozzle(pumpId, dispenserSideNozzleId, 1, null) }; } internal void TriggerPumpOffline() { if (this.currentState != LogicalDeviceState.FDC_OFFLINE) { this.currentState = LogicalDeviceState.FDC_OFFLINE; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_OFFLINE)); } } public void Init(IContext context) { this.context = context; } public async Task Process(IContext context) { this.context = context; if (context.Incoming.Message is PumpInIdleResponse) { if (this.currentState != LogicalDeviceState.FDC_READY) { logger.LogDebug("Pump: " + this.PumpId + ", Switched to FDC_READY from " + this.currentState + " by received a PumpInIdleResponse"); this.currentState = LogicalDeviceState.FDC_READY; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY)); } } else if (context.Incoming.Message is PumpInOperationResponse pumpInOperation) { if (pumpInOperation.PumpStateBlocks?.Any(s => s.NozzleNumber == this.PumpPhysicalId) ?? false) { var pumpStateBlock = pumpInOperation.PumpStateBlocks.First(s => s.NozzleNumber == this.PumpPhysicalId); if (pumpStateBlock.NozzleLiftState == NozzleLiftStateEnum.未提) { if (this.currentState != LogicalDeviceState.FDC_READY) { logger.LogDebug("Pump: " + this.PumpId + ", Switched to FDC_READY from " + this.currentState + " by PumpState-> " + pumpStateBlock.ToString()); //if (this.currentState == LogicalDeviceState.FDC_OFFLINE) // logger.LogInformation("Pump: " + this.PumpId + ", Pump side 加油机状态信息配置: " + pumpStateBlock.ToString()); //if (this.currentState == LogicalDeviceState.FDC_FUELLING) // logger.LogDebug("Pump: " + this.PumpId + ", Pump side 加油机状态信息配置: " + pumpStateBlock.ToString()); this.currentState = LogicalDeviceState.FDC_READY; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY)); } } else if (pumpStateBlock.NozzleLiftState == NozzleLiftStateEnum.已提) { if (pumpStateBlock.AuthMode == PumpAuthModeEnum.自授权模式) { /*in this mode, pump will auth itself and directly go into Athorized state, and then fuelling state. */ if (this.currentState != LogicalDeviceState.FDC_AUTHORISED) { //simulate the Calling state by request from vendor for a temp solution, this will let the FdcServer trigger a authorize calling, but later he said this is not necessary. //logger.LogDebug("Pump: " + this.PumpId + ", Switched to simulated FDC_CALLING from " + this.currentState + " by PumpState-> " + pumpStateBlock.ToString()); //this.currentState = LogicalDeviceState.FDC_CALLING; //this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_CALLING, this.nozzles.First())); logger.LogDebug("Pump: " + this.PumpId + ", Switched to FDC_AUTHORISED from " + this.currentState + " by PumpState-> " + pumpStateBlock.ToString()); this.currentState = LogicalDeviceState.FDC_AUTHORISED; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_AUTHORISED, this.nozzles.First())); } } else { if (pumpStateBlock.PumpAuthorizeState == PumpAuthorizeStateEnum.未授权 && this.currentState != LogicalDeviceState.FDC_CALLING) { logger.LogDebug("Pump: " + this.PumpId + ", Switched to FDC_CALLING from " + this.currentState + " by PumpState-> " + pumpStateBlock.ToString()); this.currentState = LogicalDeviceState.FDC_CALLING; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_CALLING, this.nozzles.First())); } else if (pumpStateBlock.PumpAuthorizeState == PumpAuthorizeStateEnum.授权成功 && this.currentState != LogicalDeviceState.FDC_AUTHORISED) { logger.LogDebug("Pump: " + this.PumpId + ", Switched to FDC_AUTHORISED from " + this.currentState + " by PumpState-> " + pumpStateBlock.ToString()); this.currentState = LogicalDeviceState.FDC_AUTHORISED; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_AUTHORISED, this.nozzles.First())); } } } else if (pumpStateBlock.NozzleLiftState == NozzleLiftStateEnum.第二条枪提起) { this.logger.LogCritical("根据油机方的技术人员说明,对于此项目,我们中控这边对接的油机,都会是单枪单显,或者是2枪2显,不会有单面双单价(2枪共一个加油点)的型号,但是为什么现在收到这样的数据?"); //if (this.currentState != LogicalDeviceState.FDC_CALLING) //{ // this.currentState = LogicalDeviceState.FDC_CALLING; // this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_CALLING, this.nozzles.First())); //} } } if (pumpInOperation.FuellingDataBlocks?.Any(s => s.NozzleNumber == this.PumpPhysicalId) ?? false) { var fuellingDataBlock = pumpInOperation.FuellingDataBlocks.First(s => s.NozzleNumber == this.PumpPhysicalId); if (this.currentState != LogicalDeviceState.FDC_FUELLING) { //sometimes the pump will not report authorized state and directly go into fuelling, so here add one from air for process completion. if (this.currentState != LogicalDeviceState.FDC_AUTHORISED) { logger.LogDebug("Pump: " + this.PumpId + ", Switched to FDC_AUTHORISED from " + this.currentState + " by manual inserting"); this.currentState = LogicalDeviceState.FDC_AUTHORISED; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_AUTHORISED, this.nozzles.First())); } logger.LogDebug("Pump: " + this.PumpId + ", Switched to FDC_FUELLING from " + this.currentState + " by received a FuellingDataBlock-> " + fuellingDataBlock.ToString()); this.currentState = LogicalDeviceState.FDC_FUELLING; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_FUELLING, this.nozzles.First())); } if (this.logger.IsEnabled(LogLevel.Trace)) this.logger.LogTrace("Pump " + this.PumpId + ": Reporting Fuelling in progress: " + fuellingDataBlock.ToString()); this.nozzles.First().RealPriceOnPhysicalPump = fuellingDataBlock.Price; this.OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction() { Amount = fuellingDataBlock.Amount, Volumn = fuellingDataBlock.Volume, Finished = false, Nozzle = this.nozzles.First(), Price = fuellingDataBlock.Price, })); } } else if (context.Incoming.Message is PumpNotifyTransactionDoneEvent trxDoneEvent && trxDoneEvent.交易枪号 == this.PumpPhysicalId) { logger.LogDebug("Pump " + this.PumpId + ": " + "Report a trx done, 交易金额: " + trxDoneEvent.交易金额 + ", 油量总量: " + trxDoneEvent.油量总量 + ", 交易单价: " + trxDoneEvent.交易单价); this.context.Outgoing.Write(new AckPumpTransactionRequest(trxDoneEvent.FrameSeqNo) { SourceAddress = this.parent.pumpGroupConfig.FccCommAddress, TargetAddress = this.parent.pumpGroupConfig.DispenserCommBoardAddress, }); // at least within 62 years, exception will not throw here int newTrxSeqNumber = (int)(DateTime.Now.Subtract(new DateTime(2020, 1, 1)).TotalSeconds); this.OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction() { Amount = trxDoneEvent.交易金额, Volumn = trxDoneEvent.交易油量, AmountTotalizer = trxDoneEvent.金额总量, VolumeTotalizer = trxDoneEvent.油量总量, Finished = true, Nozzle = this.nozzles.First(), Price = trxDoneEvent.交易单价, SequenceNumberGeneratedOnPhysicalPump = newTrxSeqNumber, })); } } public virtual async Task QueryStatusAsync() { return this.currentState; } /// /// /// /// MoneyTotalizer:VolumnTotalizer public async Task> QueryTotalizerAsync(byte logicalNozzleId) { var result = new System.Tuple(-1, -1); logger.LogDebug("Pump: " + this.PumpId + ", " + "Start QueryTotalizer pump with logical nozzle: " + logicalNozzleId); var nozzlePhysicalId = this.nozzles.FirstOrDefault(n => n.LogicalId == logicalNozzleId)?.PhysicalId; if (nozzlePhysicalId == null) { logger.LogDebug("Pump: " + this.PumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " does not exists, totalizer will return -1, -1"); return new System.Tuple(-1, -1); } var frameSeqNo = this.parent.GenerateNewFrameNo(); var response = await this.context.Outgoing.WriteAsync( new ReadPumpAccumulatorRequest() { SourceAddress = this.parent.pumpGroupConfig.FccCommAddress, TargetAddress = this.parent.pumpGroupConfig.DispenserCommBoardAddress, FrameSeqNo = frameSeqNo }, (request, testResponse) => testResponse is ReadPumpAccumulatorResponse r && (r.NozzleAndAccumulators?.Exists(d => d.枪号 == this.PumpPhysicalId) ?? false), 3000) as ReadPumpAccumulatorResponse; if (response != null) { logger.LogDebug("Pump: " + this.PumpId + ", " + " Queried Totalizer for logical nozzle: " + logicalNozzleId + ", response detail-> 油量累计: " + (response.NozzleAndAccumulators?.FirstOrDefault(n => n.枪号 == this.PumpPhysicalId)?.油量累计 ?? -1) + ", 金额累计: " + (response.NozzleAndAccumulators?.FirstOrDefault(n => n.枪号 == this.PumpPhysicalId)?.金额累计 ?? -1)); var t = response.NozzleAndAccumulators.First(d => d.枪号 == this.PumpPhysicalId); result = new System.Tuple((int)t.金额累计, t.油量累计); } else { logger.LogError("Pump: " + this.PumpId + ", " + "QueryTotalizer timed out"); } return result; } public virtual async Task ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId) { logger.LogDebug("Pump: " + this.PumpId + ", " + "start ChangeFuelPriceAsync pump with nozzle: " + logicalNozzleId); var nozzlePhysicalId = this.nozzles.FirstOrDefault(n => n.LogicalId == logicalNozzleId)?.PhysicalId; if (nozzlePhysicalId == null) { logger.LogDebug("Pump: " + this.PumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " does not exists, ChangeFuelPriceAsync failed"); return false; } var frameSeqNo = this.parent.GenerateNewFrameNo(); var response = await this.context.Outgoing.WriteAsync( new ChangePumpPriceRequest((byte)this.PumpPhysicalId, newPriceWithoutDecimalPoint) { SourceAddress = this.parent.pumpGroupConfig.FccCommAddress, TargetAddress = this.parent.pumpGroupConfig.DispenserCommBoardAddress, FrameSeqNo = frameSeqNo }, (request, testResponse) => testResponse is AckChangePumpPriceResponse r && r.FrameSeqNo == frameSeqNo, 3000) as AckChangePumpPriceResponse; if (response != null) { if (response.参数 == 0x00) { logger.LogDebug("Pump: " + this.PumpId + ", " + " ChangeFuelPrice is Acked."); return true; } else { logger.LogDebug("Pump: " + this.PumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " change price response received with an error code: " + response.参数 + ", ChangeFuelPriceAsync failed"); return false; } } return false; } /// /// /// /// useless for this type of pump, it always one pump one nozzle /// public virtual async Task AuthorizeAsync(byte logicalNozzleId) { logger.LogDebug("Pump: " + this.PumpId + ", " + "start AuthorizeAsync pump with nozzle: " + logicalNozzleId); var nozzlePhysicalId = this.nozzles.FirstOrDefault(n => n.LogicalId == logicalNozzleId)?.PhysicalId; if (nozzlePhysicalId == null) { logger.LogDebug("Pump: " + this.PumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " does not exists, AuthorizeAsync failed"); return false; } var frameSeqNo = this.parent.GenerateNewFrameNo(); var response = await this.context.Outgoing.WriteAsync( new StartPumpRequest((byte)this.PumpPhysicalId, StartPumpRequest.PresetType.随意加油, 987699) { SourceAddress = this.parent.pumpGroupConfig.FccCommAddress, TargetAddress = this.parent.pumpGroupConfig.DispenserCommBoardAddress, FrameSeqNo = frameSeqNo }, (request, testResponse) => testResponse is AckStartPumpResponse r, 3000) as AckStartPumpResponse; if (response != null) { if (response.参数 == 0x00) return true; else { logger.LogDebug("Pump: " + this.PumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " auth response received with an error code: " + response.参数 + ", AuthorizeAsync failed"); return false; } } return false; } /// /// /// /// /// useless for this type of pump, it always one pump one nozzle /// public virtual async Task AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId) { logger.LogDebug("Pump: " + this.PumpId + ", " + "start AuthorizeWithAmountAsync pump with amount: " + moneyAmountWithoutDecimalPoint + ", nozzle: " + logicalNozzleId); var nozzlePhysicalId = this.nozzles.FirstOrDefault(n => n.LogicalId == logicalNozzleId)?.PhysicalId; if (nozzlePhysicalId == null) { logger.LogDebug("Pump: " + this.PumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " does not exists, AuthorizeWithAmountAsync failed"); return false; } var frameSeqNo = this.parent.GenerateNewFrameNo(); var response = await this.context.Outgoing.WriteAsync( new StartPumpRequest((byte)this.PumpPhysicalId, StartPumpRequest.PresetType.定金额加油, moneyAmountWithoutDecimalPoint) { SourceAddress = this.parent.pumpGroupConfig.FccCommAddress, TargetAddress = this.parent.pumpGroupConfig.DispenserCommBoardAddress, FrameSeqNo = frameSeqNo }, (request, testResponse) => testResponse is AckStartPumpResponse r, 3000) as AckStartPumpResponse; if (response != null) { if (response.参数 == 0x00) return true; else { logger.LogDebug("Pump: " + this.PumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " auth response received with an error code: " + response.参数 + ", AuthorizeAsync failed"); return false; } } return false; } /// /// /// /// /// useless for this type of pump, it always one pump one nozzle /// public virtual async Task AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId) { logger.LogDebug("Pump: " + this.PumpId + ", " + "start AuthorizeWithVolumeAsync pump with volume: " + volumnWithoutDecimalPoint + ", nozzle: " + logicalNozzleId); var nozzlePhysicalId = this.nozzles.FirstOrDefault(n => n.LogicalId == logicalNozzleId)?.PhysicalId; if (nozzlePhysicalId == null) { logger.LogDebug("Pump: " + this.PumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " does not exists, AuthorizeWithVolumeAsync failed"); return false; } var frameSeqNo = this.parent.GenerateNewFrameNo(); var response = await this.context.Outgoing.WriteAsync( new StartPumpRequest((byte)this.PumpPhysicalId, StartPumpRequest.PresetType.定油量体积加油, volumnWithoutDecimalPoint) { SourceAddress = this.parent.pumpGroupConfig.FccCommAddress, TargetAddress = this.parent.pumpGroupConfig.DispenserCommBoardAddress, FrameSeqNo = frameSeqNo }, (request, testResponse) => testResponse is AckStartPumpResponse r, 3000) as AckStartPumpResponse; if (response != null) { if (response.参数 == 0x00) return true; else { logger.LogDebug("Pump: " + this.PumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " auth response received with an error code: " + response.参数 + ", AuthorizeAsync failed"); return false; } } return false; } public virtual async Task FuelingRoundUpByAmountAsync(int amount) { throw new NotImplementedException(); } #region not implemented public async Task UnAuthorizeAsync(byte logicalNozzleId) { logger.LogDebug("Pump: " + this.PumpId + ", " + "start UnAuthorizeAsync pump with nozzle: " + logicalNozzleId); var nozzlePhysicalId = this.nozzles.FirstOrDefault(n => n.LogicalId == logicalNozzleId)?.PhysicalId; if (nozzlePhysicalId == null) { logger.LogDebug("Pump: " + this.PumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " does not exists, UnAuthorizeAsync failed"); return false; } var frameSeqNo = this.parent.GenerateNewFrameNo(); var response = await this.context.Outgoing.WriteAsync( new StopPumpRequest((byte)this.PumpPhysicalId) { SourceAddress = this.parent.pumpGroupConfig.FccCommAddress, TargetAddress = this.parent.pumpGroupConfig.DispenserCommBoardAddress, FrameSeqNo = frameSeqNo }, (request, testResponse) => testResponse is AckStopPumpResponse r, 3000) as AckStopPumpResponse; if (response != null) { if (response.参数 == 0x00) return true; else { logger.LogDebug("Pump: " + this.PumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " auth response received with an error code: " + response.参数 + ", UnAuthorizeAsync failed"); return false; } } return false; } public async Task SuspendFuellingAsync() { throw new NotImplementedException(); } public async Task ResumeFuellingAsync() { throw new NotImplementedException(); } public async Task FuelingRoundUpByVolumeAsync(int volume) { throw new NotImplementedException(); } #endregion /// /// protected Dictionary logicalNozzleIdToLastFuelSaleTrxMapping = new Dictionary(); public void OnFdcServerInit(Dictionary parameters) { if (parameters.ContainsKey("LastPriceChange")) { } /* Load Last sale(from db) for void the case of FC accidently disconnect from Pump in fueling, and may cause a fueling trx gone from FC control */ if (parameters.ContainsKey("LastFuelSaleTrx")) { // nozzle logical id:lastSale //var lastFuelSaleTrxes = parameters["LastFuelSaleTrx"] as Dictionary; //foreach (var lastFuelSaleTrx in lastFuelSaleTrxes) //{ // logger.Info("Pump: " + this.pumpId + ", OnFdcServerInit, load last fuel sale " + // "on logical nozzle: " + lastFuelSaleTrx.Key + " with value: " + lastFuelSaleTrx.Value); // this.logicalNozzleIdToLastFuelSaleTrxMapping.Remove(lastFuelSaleTrx.Key); // this.logicalNozzleIdToLastFuelSaleTrxMapping.Add(lastFuelSaleTrx.Key, lastFuelSaleTrx.Value); //} } } public async Task LockNozzleAsync(byte logicalNozzleId) { return false; } public async Task UnlockNozzleAsync(byte logicalNozzleId) { return false; } } }