using Edge.Core.Database.Models; using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump; using LanTian_Pump_664_Or_886.MessageEntity; using LanTian_Pump_664_Or_886.MessageEntity.Incoming; using LanTian_Pump_664_Or_886.MessageEntity.Outgoing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Edge.Core.Parser.BinaryParser.Util; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using Wayne.FDCPOSLibrary; using Edge.Core.IndustryStandardInterface.Pump; namespace LanTian_Pump_664_Or_886 { public class PumpHandler : IFdcPumpController, IDeviceHandler { public enum PumpModel { // for all cases, like totalizer, amount, price data fields and etc., has smaller value range Model_664 = 0, // for all cases, like totalizer, amount, price data fields and etc., has wider value range Model_886 = 1 } public enum PumpAuthorizeMode { FC_Authorize = 0, Pump_Self_Authorize = 1, } 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 = 9; private List nozzles = new List(); private System.Timers.Timer deviceOfflineCountdownTimer; private IContext context; private bool isOnFdcServerInitCalled = false; private int pumpId; private PumpModel pumpModel; private PumpAuthorizeMode pumpAuthorizeMode; private int amountDecimalDigits; private int volumeDecimalDigits; private int priceDecimalDigits; private int volumeTotalizerDecimalDigits; /// /// for avoid a case that FC may miss a pump status change event(pump side issue? or wire issue?), /// we timely actively request the pump state. /// //private System.Timers.Timer pollingPumpStatus; //private int pollingPumpStatusInterval = 30000; #region public string Name => "LanTian_Pump_664_Or_886"; public int PumpId => this.pumpId; public IEnumerable Nozzles => this.nozzles; public int AmountDecimalDigits => this.amountDecimalDigits; public int VolumeDecimalDigits => this.volumeDecimalDigits; public int PriceDecimalDigits => this.priceDecimalDigits; public int VolumeTotalizerDecimalDigits => this.volumeDecimalDigits; public Guid Id => Guid.NewGuid(); public int PumpPhysicalId => throw new NotImplementedException(); public event EventHandler OnStateChange; public event EventHandler OnCurrentFuellingStatusChange; public async Task QueryStatusAsync() { return this.lastLogicalDeviceState; } public async Task LockNozzleAsync(byte logicalNozzleId) { return false; } public async Task UnlockNozzleAsync(byte logicalNozzleId) { return false; } #endregion public PumpHandler(int pumpId) : this(pumpId, PumpModel.Model_664, PumpAuthorizeMode.FC_Authorize, 2, 2, 2, 2) { } public PumpHandler(int pumpId, PumpModel pumpModel, PumpAuthorizeMode pumpAuthorizeMode, int amountDecimalDigits, int volumeDecimalDigits, int priceDecimalDigits, int volumeTotalizerDecimalDigits) { this.pumpId = pumpId; this.pumpModel = pumpModel; this.pumpAuthorizeMode = pumpAuthorizeMode; this.amountDecimalDigits = amountDecimalDigits; this.volumeDecimalDigits = volumeDecimalDigits; this.priceDecimalDigits = priceDecimalDigits; this.volumeTotalizerDecimalDigits = volumeTotalizerDecimalDigits; this.pumpId = pumpId; // real nozzle Id put hardcode 1 here since HengShan_pump_nonIC only have 1 nozzle per pump. // price is not dectected yet, put Null. this.nozzles.Add(new LogicalNozzle(pumpId, 1, 1, null)); this.deviceOfflineCountdownTimer = new System.Timers.Timer(3000); this.deviceOfflineCountdownTimer.Elapsed += (_, __) => { if (DateTime.Now.Subtract(this.lastLogicalDeviceStateReceivedTime).TotalSeconds >= lastLogicalDeviceStateExpiredTime) { if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_OFFLINE) { this.lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE; logger.LogInformation("Pump: " + this.pumpId + ", " + " State switched to FDC_OFFLINE due to long time no see pump data incoming"); var safe0 = this.OnStateChange; safe0?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_OFFLINE, null)); logger.LogTrace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back"); } } }; this.deviceOfflineCountdownTimer.Start(); } public void OnFdcServerInit(Dictionary parameters) { if (parameters.ContainsKey("LastPriceChange")) { // nozzle logical id:rawPrice var lastPriceChanges = parameters["LastPriceChange"] as Dictionary; foreach (var priceChange in lastPriceChanges) { logger.LogInformation("Pump: " + this.pumpId + ", " + "Pump " + this.pumpId + " OnFdcServerInit, load last price change " + "on logical nozzle: " + priceChange.Key + " with price: " + priceChange.Value); this.nozzles.First(n => n.LogicalId == priceChange.Key).ExpectingPriceOnFcSide = priceChange.Value; } } /* Load Last sale trx(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.LogInformation("Pump: " + this.pumpId + ", OnFdcServerInit, load last volume Totalizer " + "on logical nozzle: " + lastFuelSaleTrx.Key + " with volume value: " + lastFuelSaleTrx.Value.VolumeTotalizer); this.nozzles.First(n => n.LogicalId == lastFuelSaleTrx.Key).VolumeTotalizer = lastFuelSaleTrx.Value.VolumeTotalizer; } } this.isOnFdcServerInitCalled = true; } public void Init(IContext context) { this.context = context; var timeWindowWithActivePollingOutgoing = this.context.Outgoing as TimeWindowWithActivePollingOutgoing; timeWindowWithActivePollingOutgoing.PollingMsgProducer = () => new ReadPumpStateRequest(); } public async Task Process(IContext context) { if (!isOnFdcServerInitCalled) return; this.lastLogicalDeviceStateReceivedTime = DateTime.Now; if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE) { logger.LogInformation("Pump: " + this.pumpId + ", " + "Recevied an Pump Msg in FDC_OFFLINE state, " + "indicates the underlying connection is established, switch to FDC_READY"); this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY)); logger.LogTrace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back"); #region Start the Initial pump change price process var readPriceResponse = await this.context.Outgoing.WriteAsync(new ReadPriceRequest(), (request, response) => response is ReadPriceResponse, 3000).ConfigureAwait(false) as ReadPriceResponse; if (readPriceResponse == null) { logger.LogInformation("Pump: " + this.pumpId + ", " + " Reading initial pump side price timedout, will ignore this error and won't send a ChangePriceRequest here but may need a manual Price change if find price discrepancy"); return; } this.nozzles.First().RealPriceOnPhysicalPump = readPriceResponse.Price; if (this.nozzles.First().ExpectingPriceOnFcSide.HasValue && readPriceResponse.Price != this.nozzles.First().ExpectingPriceOnFcSide.Value) { logger.LogInformation("Pump: " + this.pumpId + ", " + " Read the initial pump side price(without decimal points): " + readPriceResponse.Price + " which NOT Equals with the price as FC expect, will start a change price internally..."); var changePriceResponse = await this.context.Outgoing.WriteAsync( new ChangePriceRequest(this.nozzles.First().ExpectingPriceOnFcSide.Value, this.pumpModel == PumpModel.Model_664 ? (byte)2 : (byte)3), (request, response) => response is ChangePriceResponse, 3000).ConfigureAwait(false) as ChangePriceResponse; if (changePriceResponse == null || !changePriceResponse.Succeed) { logger.LogInformation("Pump: " + this.pumpId + ", " + " Initial change price timedout or failed, may need a manual price change later if find price discrepancy"); return; } var doubleConfirm_readPriceResponse = await this.context.Outgoing.WriteAsync(new ReadPriceRequest(), (request, response) => response is ReadPriceResponse, 3000).ConfigureAwait(false) as ReadPriceResponse; if (doubleConfirm_readPriceResponse == null) logger.LogInformation("Pump: " + this.pumpId + ", " + " Double confirm Initial change price timedout, so FC can't sure if the new price applied, or a manual Price change is needed if find price discrepancy"); else if (doubleConfirm_readPriceResponse.Price == this.nozzles.First().ExpectingPriceOnFcSide.Value) logger.LogInformation("Pump: " + this.pumpId + ", " + " Double confirm succeed for new FC price(without decimal points): " + this.nozzles.First().ExpectingPriceOnFcSide.Value + " have been applied to pump."); else logger.LogInformation("Pump: " + this.pumpId + ", " + " Double confirm failed for new FC price(without decimal points): " + this.nozzles.First().ExpectingPriceOnFcSide.Value + " failed applying to pump as pump side still report its price(without decimal point): " + doubleConfirm_readPriceResponse.Price + ", a manual Change Price is needed"); } else logger.LogInformation("Pump: " + this.pumpId + ", " + " Read the initial pump side price(without decimal points): " + readPriceResponse.Price + ", no need to align with FC(FC has no expecting price)"); #endregion } if (context.Incoming.Message is ReadPumpStateResponse readPumpStateResponse) { #region AcquireControl if pump authorize mode is FC_Authorize if (readPumpStateResponse.ControlState == ReadPumpStateResponse.ControlStateEnum.自控 && this.pumpAuthorizeMode == PumpAuthorizeMode.FC_Authorize) { var acquireControlResponse = await this.context.Outgoing.WriteAsync( new AcquireControlRequest(), (request, response) => response is AcquireControlResponse, 3000).ConfigureAwait(false) as AcquireControlResponse; if (acquireControlResponse == null) { logger.LogInformation("Pump: " + this.pumpId + ", " + " AcquireControlResponse first time timedout, will send 2nd time..."); acquireControlResponse = await this.context.Outgoing.WriteAsync( new AcquireControlRequest(), (request, response) => response is AcquireControlResponse, 3000).ConfigureAwait(false) as AcquireControlResponse; if (acquireControlResponse == null) logger.LogInformation("Pump: " + this.pumpId + ", " + " AcquireControlResponse 2nd time timedout, will ignore this error, then the pump may have wrong authorize mode"); } } #endregion if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_FUELLING && readPumpStateResponse.NozzleState == ReadPumpStateResponse.NozzleStateEnum.提枪) { logger.LogDebug("Pump: " + this.pumpId + ", " + " Nozzle state report as NozzleStateEnum.提枪, State switched to FDC_CALLING"); this.lastLogicalDeviceState = LogicalDeviceState.FDC_CALLING; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_CALLING, this.nozzles.First())); } if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_AUTHORISED && readPumpStateResponse.NozzleState == ReadPumpStateResponse.NozzleStateEnum.提枪) { logger.LogDebug("Pump: " + this.pumpId + ", " + " internal Fdc State: " + this.lastLogicalDeviceState + ", Nozzle State switched to FDC_FUELLING"); this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_FUELLING, this.nozzles.First())); while (true) { var readFuelDataResponse = await this.context.Outgoing.WriteAsync( new ReadFuelDataRequest(), (request, response) => response is ReadFuelDataResponse, 3000).ConfigureAwait(false) as ReadFuelDataResponse; if (readFuelDataResponse == null) { } else { logger.LogDebug("Pump: " + this.pumpId + ", " + " fueling in progress with amt: " + readFuelDataResponse.Amount + ", vol: " + readFuelDataResponse.Volume); // at least within 65 years, exception will not throw here //int newTrxSeqNumber = (int)(DateTime.Now.Subtract(new DateTime(2018, 5, 25)).TotalSeconds); //fire fuelling progress. this.OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction() { // 恒山油机只有一把枪 Nozzle = this.nozzles.First(), Amount = readFuelDataResponse.Amount, Volumn = readFuelDataResponse.Volume, Price = this.nozzles.First().RealPriceOnPhysicalPump ?? -1, Finished = false, })); } } } if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_FUELLING && readPumpStateResponse.NozzleState == ReadPumpStateResponse.NozzleStateEnum.挂枪) { logger.LogDebug("Pump: " + this.pumpId + ", " + "internal Fdc State: " + this.lastLogicalDeviceState + ", Nozzle state report as NozzleStateEnum.挂枪 ,State switched to FDC_READY"); this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY, this.nozzles.First())); } } } public Task> QueryTotalizerAsync(byte logicalNozzleId) { throw new NotImplementedException(); } public Task SuspendFuellingAsync() { throw new NotImplementedException(); } public Task ResumeFuellingAsync() { throw new NotImplementedException(); } public Task ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId) { throw new NotImplementedException(); } public Task AuthorizeAsync(byte logicalNozzleId) { throw new NotImplementedException(); } public Task UnAuthorizeAsync(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 FuelingRoundUpByAmountAsync(int amount) { throw new NotImplementedException(); } public Task FuelingRoundUpByVolumeAsync(int volume) { throw new NotImplementedException(); } } }