123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389 |
- using Edge.Core.Database.Models;
- using Edge.Core.Processor;
- using Edge.Core.IndustryStandardInterface.Pump;
- 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 Wayne_Pump_Dart.MessageEntity;
- using Wayne_Pump_Dart.MessageEntity.Incoming;
- using Wayne_Pump_Dart.MessageEntity.Outgoing;
- namespace Wayne_Pump_Dart
- {
- public class PumpHandler : IFdcPumpController, IDisposable//, IHandler<byte[], MessageBase>
- {
- //static ILog logger = log4net.LogManager.GetLogger("PumpHandler");
- static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("PumpHandler");
- 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<LogicalNozzle> nozzles = new List<LogicalNozzle>();
- private System.Timers.Timer deviceOfflineCountdownTimer;
- /// <summary>
- /// will set to true once nozzle change to OUT while internal fdc state is FDC_FUELING, indicates a fule is done.
- /// 'true' will block pump auth request from outer
- /// </summary>
- private bool lastFillRetrievedSinceNozzleDownAtFdcFueling = true;
- /// <summary>
- /// the pump calling, fuelling message does not contain nozzle info, need track the operating nozzle
- /// seperatly in other message which will be kept in this variable.
- /// default set to 0 which is an invalid nozzle id for wayne dart pump(valid should starts from 1).
- /// </summary>
- private byte operatingNozzlePhysicalId = 0;
- private IContext<byte[], MessageBase> context;
- private bool isOnFdcServerInitCalled = false;
- private int pumpId;
- private PumpGroupHandler parent;
- /// <summary>
- /// address used in wayne dart protocol to comm with physical pump.
- /// 0x4F + physical pump mother board side config address(range from 1-32)
- /// </summary>
- private byte dartPumpCommAddress;
- /// <summary>
- /// when first time connected with physical pump , in some case, the pump will not report any status actively,
- /// so need send a status query from FC.
- /// From then on, pump will actively notify FC when state changes, no need to send query anymore from FC.
- /// </summary>
- private bool initialPumpStatueEverRetrieved = false;
- private int amountDecimalDigits;
- private int volumeDecimalDigits;
- private int priceDecimalDigits;
- private int volumeTotalizerDecimalDigits;
- /// <summary>
- /// 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.
- /// </summary>
- //private System.Timers.Timer pollingPumpStatus;
- //private int pollingPumpStatusInterval = 30000;
- #region MyRegion
- public string Name => "Wayne_Pump_Dart";
- public int PumpId => this.pumpId;
- /// <summary>
- /// Gets the pump physical id.
- /// address used in wayne dart protocol to comm with physical pump.
- /// 0x4F + physical pump mother board side config address(range from 1-32)
- /// </summary>
- public int PumpPhysicalId => this.dartPumpCommAddress;
- public IEnumerable<LogicalNozzle> 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 event EventHandler<FdcPumpControllerOnStateChangeEventArg> OnStateChange;
- public event EventHandler<FdcTransactionDoneEventArg> OnCurrentFuellingStatusChange;
- private async Task<bool> InternalAuthorizeAsync()
- {
- if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
- {
- logger.Info("Pump: " + this.pumpId + ", "
- + "Start Authorize pump is denied by internal");
- return false;
- }
- var authorizeSucceed = false;
- var authroizedResponse = await this.context.Outgoing.WriteAsync(
- new AuthorizeRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
- (_, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.BlockSeqNumber == _.BlockSeqNumber, 3500);
- if (authroizedResponse != null)
- {
- if (authroizedResponse.ControlCharacter == ControlCharacter.ACK)
- {
- logger.Info("Pump: " + this.pumpId + ", " + "AuthorizeRequest ACKed");
- authorizeSucceed = true;
- }
- else
- {
- authorizeSucceed = false;
- logger.Info("Pump: " + this.pumpId + ", " + "AuthorizeRequest NAKed");
- }
- }
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + "failed in get AuthorizeRequest ACK(timed out)");
- }
- return authorizeSucceed;
- }
- public async Task<bool> AuthorizeAsync(byte logicalNozzleId)
- {
- if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
- {
- logger.Info("Pump: " + this.pumpId + ", "
- + "Start Authorize pump with logicalNozzle: " + logicalNozzleId + " is denied by internal");
- return false;
- }
- logger.Info("Pump: " + this.pumpId + ", " + "Start Authorize pump with logicalNozzle: " + logicalNozzleId + ", first send AllowedNozzleNumbersRequest(allow all)");
- // now always allow all nozzles
- this.context.Outgoing.Write(
- new AllowedNozzleNumbersRequest(this.dartPumpCommAddress,
- this.parent.GetNewMessageToken(this.pumpId),
- this.nozzles.Select(n => n.PhysicalId).ToArray()));
- return await this.InternalAuthorizeAsync();
- }
- public async Task<bool> AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId)
- {
- if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
- {
- logger.Info("Pump: " + this.pumpId + ", "
- + "Start AuthorizeWithAmount pump with logicalNozzle: " + logicalNozzleId + " is denied by internal");
- return false;
- }
- logger.Info("Pump: " + this.pumpId + ", " + "start AuthorizeWithAmount pump with logicalNozzle: " + logicalNozzleId + ", moneyAmount: " + moneyAmountWithoutDecimalPoint + ", first send AllowedNozzleNumbersRequest(allow all)");
- // now always allow all nozzles
- this.context.Outgoing.Write(
- new AllowedNozzleNumbersRequest(this.dartPumpCommAddress,
- this.parent.GetNewMessageToken(this.pumpId),
- this.nozzles.Select(n => n.PhysicalId).ToArray()));
- var authorizeSucceed = false;
- var response = await this.context.Outgoing.WriteAsync(
- new PresetAmountRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId), moneyAmountWithoutDecimalPoint),
- (request, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.BlockSeqNumber == request.BlockSeqNumber, 3500);
- if (response != null)
- {
- if (response.ControlCharacter == ControlCharacter.ACK)
- {
- logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest ACKed, will send InternalAuthorize()");
- //InternalAuthorize is a blocking call, should not stop the I/O thread for comm with pump device.
- //ThreadPool.QueueUserWorkItem(o =>
- //new Thread(() =>
- //{
- // if (this.InternalAuthorize())
- // authorizeSucceed = true;
- // else
- // authorizeSucceed = false;
- //}).Start();
- var _ = await this.InternalAuthorizeAsync();
- if (_) authorizeSucceed = true; else authorizeSucceed = false;
- }
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest NAKed");
- authorizeSucceed = false;
- }
- }
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest waiting ACK timed out");
- authorizeSucceed = false;
- }
- return authorizeSucceed;
- }
- public async Task<bool> AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId)
- {
- if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
- {
- logger.Info("Pump: " + this.pumpId + ", "
- + "Start AuthorizeWithVolumn pump with logicalNozzle: " + logicalNozzleId + " is denied by internal");
- return false;
- }
- logger.Info("Pump: " + this.pumpId + ", " + "start AuthorizeWithVolumn pump with logicalNozzle: " + logicalNozzleId + ", moneyAmount: " + volumnWithoutDecimalPoint + ", first send AllowedNozzleNumbersRequest(allow all)");
- // now always allow all nozzles
- this.context.Outgoing.Write(
- new AllowedNozzleNumbersRequest(this.dartPumpCommAddress,
- this.parent.GetNewMessageToken(this.pumpId),
- this.nozzles.Select(n => n.PhysicalId).ToArray()));
- var authorizeSucceed = false;
- var response = await this.context.Outgoing.WriteAsync(
- new PresetVolumeRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId), volumnWithoutDecimalPoint),
- (request, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.BlockSeqNumber == request.BlockSeqNumber, 3500);
- if (response != null)
- {
- if (response.ControlCharacter == ControlCharacter.ACK)
- {
- logger.Info("Pump: " + this.pumpId + ", " + "PresetVolumeRequest ACKed, will send InternalAuthorize()");
- //InternalAuthorize is a blocking call, should not stop the I/O thread for comm with pump device.
- //ThreadPool.QueueUserWorkItem(o =>
- //{
- // if (this.InternalAuthorize())
- // authorizeSucceed = true;
- // else
- // authorizeSucceed = false;
- // blocker.Set();
- //});
- //new Thread(() =>
- //{
- // if (this.InternalAuthorize())
- // authorizeSucceed = true;
- // else
- // authorizeSucceed = false;
- // blocker.Set();
- //}).Start();
- var _ = await this.InternalAuthorizeAsync();
- if (_) authorizeSucceed = true; else authorizeSucceed = false;
- }
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + "PresetVolumeRequest NAKed");
- authorizeSucceed = false;
- }
- }
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + "PresetVolumeRequest waiting ACK timed out");
- authorizeSucceed = false;
- }
- return authorizeSucceed;
- }
- public Task<bool> ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId)
- {
- try
- {
- var nozzlesPriceListAsendingByNozzlePhysicalId =
- this.nozzles.OrderBy(k => k.PhysicalId).Select(s => s.ExpectingPriceOnFcSide ?? 0);
- logger.Info("Pump: " + this.pumpId + ", " + "Change Fuel Price for LogicalNozzle: " + logicalNozzleId
- + ", physicalNozzle: " + this.nozzles.First(n => n.LogicalId == logicalNozzleId).PhysicalId
- + " with new price(without decimal): " + newPriceWithoutDecimalPoint
- + "(old price list for all nozzles based on physicalId from 1 to n: "
- + nozzlesPriceListAsendingByNozzlePhysicalId.Select(s => s.ToString()).Aggregate((n, acc) => n + ", " + acc) + ")");
- this.nozzles.First(n => n.LogicalId == logicalNozzleId).ExpectingPriceOnFcSide = newPriceWithoutDecimalPoint;
- return this.InternalChangeFuelPriceAsync(nozzlesPriceListAsendingByNozzlePhysicalId.ToList());
- }
- catch (Exception exxx)
- {
- logger.Error("Pump: " + this.pumpId + ", " + "Exceptioned in ChangeFuelPrice: " + exxx);
- return Task.FromResult(false);
- }
- }
- /// <summary>
- /// Wayne dart price change is always targeting all nozzles.
- /// </summary>
- /// <param name="newPricesFromPhysicalNozzleFirstToLast">price without decimal points from nozzle 1 to N</param>
- /// <returns></returns>
- private async Task<bool> InternalChangeFuelPriceAsync(List<int> newPricesFromPhysicalNozzleFirstToLast)
- {
- if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED
- || this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE)
- {
- logger.Info("Pump: " + this.pumpId + ", " + " Pump is in state FDC_CLOSED or FDC_OFFLINE, InternalChangeFuelPrice will return false");
- return false;
- }
- if (newPricesFromPhysicalNozzleFirstToLast.Count != this.nozzles.Count)
- throw new ArgumentException("Wayne dart pump price change must provide prices for total " + this.nozzles.Count
- + " nozzles, but now only pass in " + newPricesFromPhysicalNozzleFirstToLast.Count);
- bool changePriceSucceed = false;
- logger.Info("Pump: " + this.pumpId + ", " + " InternalChangeFuelPrice starting with prices: "
- + newPricesFromPhysicalNozzleFirstToLast.Select(p => p.ToString()).Aggregate((acc, n) => acc + ", " + n));
- var priceChangedResponse = await this.context.Outgoing.WriteAsync(
- new PriceUpdateRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId), newPricesFromPhysicalNozzleFirstToLast)
- , (_, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.BlockSeqNumber == _.BlockSeqNumber, 2000);
- if (priceChangedResponse != null && priceChangedResponse.ControlCharacter == ControlCharacter.ACK)
- {
- changePriceSucceed = true;
- try
- {
- for (int i = 0; i < newPricesFromPhysicalNozzleFirstToLast.Count; i++)
- {
- this.nozzles.First(n => n.PhysicalId == i + 1).RealPriceOnPhysicalPump = newPricesFromPhysicalNozzleFirstToLast[i];
- //this.nozzles.First(n => n.PhysicalId == i + 1).ExpectingPriceOnFcSide = newPricesFromPhysicalNozzleFirstToLast[i];
- }
- logger.Info("Pump: " + this.pumpId + ", " + " InternalChangeFuelPrice done succeed");
- }
- catch (Exception exxx)
- {
- logger.Info("InternalChangeFuelPrice partially succeed with exception:" + exxx);
- }
- }
- else if (priceChangedResponse != null && priceChangedResponse.ControlCharacter == ControlCharacter.NAK)
- {
- logger.Error("Pump: " + this.pumpId + ", " + "InternalChangeFuelPrice is denied (NAK) by wayne dart pump, will reset msg token to 0 for align.");
- //reset id to 0 to re-align, wayne dart have this check!
- this.parent.ResetMessageTokenToAlign(this.pumpId);
- }
- else
- {
- logger.Error("Pump: " + this.pumpId + ", " + "InternalChangeFuelPrice failed with timeout");
- }
- // this FC handle WayneDart pump price change one nozzle by nozzle, so change prices on single FuelPoint with multiple
- // nozzle will interpreted as multiple price change request, by testing, too fast send multiple price change request
- // to a FP might be ignored by pump side though message here are all good, so hardcode sleep a while.
- Thread.Sleep(1000);
- return changePriceSucceed;
- }
- public async Task<bool> FuelingRoundUpByAmountAsync(int amount)
- {
- logger.Info("Pump: " + this.pumpId + ", " + "start FuelingRoundUpByAmount with amount: " + amount);
- var isSucceed = false;
- this.context.Outgoing.WriteAsync(
- new PresetAmountRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId), amount),
- (request, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.BlockSeqNumber == request.BlockSeqNumber,
- (request, response) =>
- {
- if (response != null)
- {
- if (response.ControlCharacter == ControlCharacter.ACK)
- {
- logger.Debug("Pump: " + this.pumpId + ", " + "PresetAmountRequest ACKed");
- isSucceed = true;
- }
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest NAKed");
- isSucceed = false;
- }
- }
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + "PresetAmountRequest waiting ACK timed out");
- isSucceed = false;
- }
- }, 1500);
- return isSucceed;
- }
- public async Task<bool> FuelingRoundUpByVolumeAsync(int volume)
- {
- return false;
- }
- public async Task<global::Wayne.FDCPOSLibrary.LogicalDeviceState> QueryStatusAsync()
- {
- return this.lastLogicalDeviceState;
- }
- public async Task<System.Tuple<int, int>> QueryTotalizerAsync(byte logicalNozzleId)
- {
- var result = new System.Tuple<int, int>(-1, -1);
- logger.Info("Pump: " + this.pumpId + ", " + "start QueryTotalizer pump with nozzle: " + logicalNozzleId);
- if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CLOSED
- || this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE)
- {
- logger.Info("Pump: " + this.pumpId + ", " + " Pump is in state FDC_CLOSED or FDC_OFFLINE, totalizer will return -1, -1");
- return new System.Tuple<int, int>(-1, -1);
- }
- var nozzlePhysicalId = this.nozzles.FirstOrDefault(n => n.LogicalId == logicalNozzleId)?.PhysicalId;
- if (nozzlePhysicalId == null)
- {
- logger.Info("Pump: " + this.pumpId + ", " + " Nozzle with logicalId: " + logicalNozzleId + " does not exists, totalizer will return -1, -1");
- return new System.Tuple<int, int>(-1, -1);
- }
- var response = await this.context.Outgoing.WriteAsync(new RequestTotalVolumeCountersRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId),
- nozzlePhysicalId.Value),
- (request, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.ControlCharacter == ControlCharacter.DATA, 3000);
- if (response != null)
- {
- this.context.Outgoing.Write(new ACK(this.dartPumpCommAddress, response.BlockSeqNumber));
- var totalCountersTrx =
- new TotalCounters_TransactionData(response.TransactionDatas.First(f => f.TransactionNumber == 0x65));
- logger.Info("Pump: " + this.pumpId + ", " + "QueryTotalizer for nozzle: " + logicalNozzleId + " succeed, volume total: " + totalCountersTrx.TotalValue);
- result = new System.Tuple<int, int>(-1, totalCountersTrx.TotalValue);
- }
- else
- {
- logger.Error("Pump: " + this.pumpId + ", " + "QueryTotalizer waiting Data timed out");
- }
- return result;
- }
- public async Task<bool> ResumeFuellingAsync()
- {
- throw new NotImplementedException();
- }
- public async Task<bool> SuspendFuellingAsync()
- {
- throw new NotImplementedException();
- }
- /// <summary>
- /// unauthorize the authed fueling point, will trigger wayne dart pump switched to `FILLING COMPLETE` state.
- /// </summary>
- /// <param name="logicalNozzleId">wayne dart no need specify nozzle id</param>
- /// <returns></returns>
- public async Task<bool> UnAuthorizeAsync(byte logicalNozzleId)
- {
- var unauthorizeSucceed = false;
- logger.Info("Pump: " + this.pumpId + ", " + "Start UnAuthorize pump with nozzle: " + logicalNozzleId);
- var authorizedResponse = await this.context.Outgoing.WriteAsync(
- new StopRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
- (_, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.BlockSeqNumber == _.BlockSeqNumber, 3500);
- if (authorizedResponse != null)
- {
- if (authorizedResponse.ControlCharacter == ControlCharacter.ACK)
- {
- logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest ACKed and succeed");
- unauthorizeSucceed = true;
- }
- else
- logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest NAKed (rejected?)");
- }
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + "unAuthorizeRequest timed out");
- }
- return unauthorizeSucceed;
- }
- public async Task<bool> LockNozzleAsync(byte logicalNozzleId)
- {
- return false;
- }
- public async Task<bool> UnlockNozzleAsync(byte logicalNozzleId)
- {
- return false;
- }
- #endregion
- public PumpHandler(PumpGroupHandler parent, int pumpId,
- int amountDecimalDigits, int volumeDecimalDigits,
- int priceDecimalDigits, int volumeTotalizerDecimalDigits,
- string pumpXmlConfiguration)
- {
- this.parent = parent;
- this.pumpId = pumpId;
- this.amountDecimalDigits = amountDecimalDigits;
- this.volumeDecimalDigits = volumeDecimalDigits;
- this.priceDecimalDigits = priceDecimalDigits;
- this.volumeTotalizerDecimalDigits = volumeTotalizerDecimalDigits;
- // sample of pumpXmlConfiguration
- // <Pump pumpId='1' physicalId='1'>
- // <Nozzles>
- // <Nozzle logicalId='1' physicalId='1' defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb='2345'/>
- // <Nozzle logicalId='2' physicalId='2' defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb='2345'/>
- // <Nozzle logicalId='3' physicalId='3' defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb='2345'/>
- // </Nozzles>
- // </Pump>
- var xmlDocument = new XmlDocument();
- xmlDocument.LoadXml(pumpXmlConfiguration);
- var physicalPumpAddressConfiguratedInPump =
- byte.Parse(xmlDocument.SelectSingleNode("/Pump").Attributes["physicalId"].Value);
- if (physicalPumpAddressConfiguratedInPump > 0x20) 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");
- this.dartPumpCommAddress = (byte)(0x4F + physicalPumpAddressConfiguratedInPump);
- foreach (var nozzleElement in xmlDocument.GetElementsByTagName("Nozzle").Cast<XmlNode>())
- {
- var nozzlePhysicalId = byte.Parse(nozzleElement.Attributes["physicalId"].Value);
- var nozzleLogicalId = byte.Parse(nozzleElement.Attributes["logicalId"].Value);
- var nozzleRawDefaultPriceWithoutDecimal = nozzleElement.Attributes["defaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb"].Value;
- if (nozzlePhysicalId < 1 || nozzlePhysicalId > 8) throw new ArgumentOutOfRangeException("Wayne dart pump only accept nozzle physical id range in config from 1 to 8");
- this.nozzles.Add(new LogicalNozzle(pumpId, nozzlePhysicalId, nozzleLogicalId, null) { ExpectingPriceOnFcSide = int.Parse(nozzleRawDefaultPriceWithoutDecimal) });
- logger.Info("Pump: " + this.pumpId
- + ", created a nozzle with logicalId: " + nozzleLogicalId + ", physicalId: " + nozzlePhysicalId
- + ", default raw price without decimal points: " + nozzleRawDefaultPriceWithoutDecimal);
- }
- this.deviceOfflineCountdownTimer = new System.Timers.Timer(3000);
- this.deviceOfflineCountdownTimer.Elapsed += (_, __) =>
- {
- if (DateTime.Now.Subtract(this.lastLogicalDeviceStateReceivedTime).TotalSeconds
- >= lastLogicalDeviceStateExpiredTime)
- {
- this.initialPumpStatueEverRetrieved = false;
- if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_OFFLINE)
- {
- this.lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE;
- logger.Info("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.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
- }
- }
- };
- this.deviceOfflineCountdownTimer.Start();
- }
- public void OnFdcServerInit(Dictionary<string, object> parameters)
- {
- /* Wayne Dart pump will miss last price when disconnected or power off
- * from FC a while(pump state `PUMP NOT PROGRAMMED` indicates this happened), so here
- * is trying to recover the price from Fdc database, and then push to Pump*/
- if (parameters.ContainsKey("LastPriceChange"))
- {
- // nozzle logical id:rawPrice
- var lastPriceChanges = parameters["LastPriceChange"] as Dictionary<byte, int>;
- foreach (var priceChange in lastPriceChanges)
- {
- logger.Info("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<byte, FuelSaleTransaction>;
- foreach (var lastFuelSaleTrx in lastFuelSaleTrxes)
- {
- logger.Info("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<byte[], MessageBase> context)
- {
- this.context = context;
- this.context.Communicator.OnConnected += (a, b) =>
- {
- //this.context.Outgoing.Write(new ReturnStatusRequest(this.dartPumpCommAddress, 0));
- };
- //this.context.Communicator.OnDisconnected += (a, b) => this.pumpStatusEverReceived = false;
- }
- public async Task Process(IContext<byte[], MessageBase> context)
- {
- if (context.Incoming.Message.ControlCharacter == ControlCharacter.DATA)
- this.context.Outgoing.Write(new ACK(this.dartPumpCommAddress, context.Incoming.Message.BlockSeqNumber));
- if (!isOnFdcServerInitCalled) return;
- if (context.Incoming.Message.ControlCharacter == ControlCharacter.NAK)
- {
- logger.Info("Pump: " + this.pumpId + ", " + " received a NAK, will set msg token to 0 for re-align");
- this.parent.ResetMessageTokenToAlign(this.pumpId);
- }
- this.lastLogicalDeviceStateReceivedTime = DateTime.Now;
- if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE)
- {
- logger.Info("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;
- var safe = this.OnStateChange;
- safe?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
- }
- if (!this.initialPumpStatueEverRetrieved)
- {
- // mute for next incoming request
- this.initialPumpStatueEverRetrieved = true;
- logger.Info("Pump: " + this.pumpId + ", " + "Never received pump status, send ReturnStatusRequest right now");
- // capture the EOT response here for avoid infinite loop: FC send ReturnStatusRequest, FC received EOT, and send ReturnStatusRequest again...
- this.context.Outgoing.WriteAsync(
- new ReturnStatusRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
- (request, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.BlockSeqNumber == request.BlockSeqNumber,
- (request, statusResponse) =>
- {
- if (statusResponse == null)
- {
- this.initialPumpStatueEverRetrieved = false;
- logger.Info("Pump: " + this.pumpId + ", " + " ReturnStatusRequest timed out wait for pump EOT");
- }
- else if (statusResponse.ControlCharacter == ControlCharacter.NAK)
- {
- logger.Info("Pump: " + this.pumpId + ", " + " ReturnStatusRequest NAKed, will set msg token to 0 to align and send again");
- //reset id to 0 to re-align, iGEN board wayne dart have this check!
- this.parent.ResetMessageTokenToAlign(this.pumpId);
- this.initialPumpStatueEverRetrieved = false;
- }
- }, 1500);
- }
- if (context.Incoming.Message.ControlCharacter == ControlCharacter.DATA)
- {
- var nozzleStatusAndFillingPriceTrxData = context.Incoming.Message.TransactionDatas.LastOrDefault(d => d.TransactionNumber == 0x03);
- var pumpStatusTrxData = context.Incoming.Message.TransactionDatas.FirstOrDefault(d => d.TransactionNumber == 0x01);
- var filledVolumeAndAmountTrxData = context.Incoming.Message.TransactionDatas.FirstOrDefault(d => d.TransactionNumber == 0x02);
- NozzleStatusAndFillingPrice_TransactionData nozzleStatusTrx = null;
- PumpStatus_TransactionData pumpStatusTrx = null;
- FilledVolumeAndAmount_TransactionData filledVolAndAmtTrx = null;
- var overallStateLogStr = "Pump: " + this.pumpId + ", " + ">>>>>>>>>>===start====(internalFdcState: "
- + this.lastLogicalDeviceState + ")" + System.Environment.NewLine;
- if (nozzleStatusAndFillingPriceTrxData != null)
- {
- nozzleStatusTrx = new NozzleStatusAndFillingPrice_TransactionData(nozzleStatusAndFillingPriceTrxData);
- overallStateLogStr += "Nozzle with physical id: "
- + nozzleStatusTrx.Status.Key
- + " is in state: " + nozzleStatusTrx.Status.Value
- + ", filling price: " + nozzleStatusTrx.FillingPrice + System.Environment.NewLine;
- }
- if (pumpStatusTrxData != null)
- {
- pumpStatusTrx = new PumpStatus_TransactionData(pumpStatusTrxData);
- overallStateLogStr += "WayneDart State: "
- + pumpStatusTrx.Status + System.Environment.NewLine;
- }
- if (filledVolumeAndAmountTrxData != null)
- {
- filledVolAndAmtTrx = new FilledVolumeAndAmount_TransactionData(filledVolumeAndAmountTrxData);
- overallStateLogStr += "Filled vol "
- + filledVolAndAmtTrx.FilledVolume
- + ", amt " + filledVolAndAmtTrx.FilledAmount + System.Environment.NewLine;
- }
- logger.Info(overallStateLogStr + "<<<<<<<<<<===end====");
- if (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.PUMP_NOT_PROGRAMMED)
- {
- #region PUMP_NOT_PROGRAMMED
- if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_ERRORSTATE)
- {
- logger.Debug("Pump: " + this.pumpId + ", " + " NEW State is PUMP_NOT_PROGRAMMED, indicates price not set yet, State switched to FDC_ERRORSTATE");
- this.lastLogicalDeviceState = LogicalDeviceState.FDC_ERRORSTATE;
- var safe0 = this.OnStateChange;
- safe0?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_ERRORSTATE));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
- }
- logger.Debug("Pump: " + this.pumpId + ", " + " Pump status is PUMP_NOT_PROGRAMMED, will set price");
- //ThreadPool.QueueUserWorkItem(o =>
- //{
- try
- {
- var nozzlesPriceListAsendingByNozzlePhysicalId =
- this.nozzles.OrderBy(k => k.PhysicalId).Select(s => s.ExpectingPriceOnFcSide ?? 0).ToList();
- var cpResponse = await this.InternalChangeFuelPriceAsync(nozzlesPriceListAsendingByNozzlePhysicalId);
- if (cpResponse)
- logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) succeed");
- else
- {
- logger.Error("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) failed, will retry 1st time");
- cpResponse = await this.InternalChangeFuelPriceAsync(nozzlesPriceListAsendingByNozzlePhysicalId);
- if (cpResponse)
- logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) succeed in 1st retry");
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) failed, will retry 2nd time");
- cpResponse = await this.InternalChangeFuelPriceAsync(nozzlesPriceListAsendingByNozzlePhysicalId);
- if (cpResponse)
- logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) succeed in 2nd retry");
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) failed, will retry 3rd time");
- cpResponse = await this.InternalChangeFuelPriceAsync(nozzlesPriceListAsendingByNozzlePhysicalId);
- if (cpResponse)
- logger.Info("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) succeed in 3rd retry");
- else
- logger.Error("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) failed again in 3rd retry, will stop");
- }
- }
- this.context.Outgoing.Write(
- new ResetRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)));
- }
- }
- catch (Exception exx)
- {
- logger.Error("Pump: " + this.pumpId + ", " + " Price change(reason PUMP_NOT_PROGRAMMED) exceptioned: " + exx);
- }
- //}
- //);
- #endregion
- }
- else if (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.SWITCHED_OFF)
- {
- logger.Debug("Pump: " + this.pumpId + ", " + " NEW State is SWITCHED_OFF, will send RESET");
- this.context.Outgoing.WriteAsync(
- new ResetRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
- (request, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.BlockSeqNumber == request.BlockSeqNumber &&
- testResponse.ControlCharacter == ControlCharacter.ACK,
- (request, response) =>
- {
- if (response != null)
- {
- logger.Debug("Pump: " + this.pumpId + ", " + "RESET request acked.");
- }
- else
- logger.Error("Pump: " + this.pumpId + ", " + "RESET request failed(timed out) to ack.");
- }, 3500);
- }
- if (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.FILLING_COMPLETED)
- {
- byte nozzlePhysicalIdForTrxDoneOn = 0;
- #region determine target nozzle
- if (nozzleStatusTrx != null)
- {
- if (nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.IN)
- {
- if (nozzleStatusTrx.Status.Key == 0)
- {
- //logger.Debug("Pump: " + this.pumpId + ", " + " more like a China domestic wayne dart since FILLING_COMPLETED contains nozzleStatus but nozzle physicalId is 0");
- // nozzle number ==0 indicates all nozzles were put back(In)
- nozzlePhysicalIdForTrxDoneOn = this.operatingNozzlePhysicalId;
- }
- else
- nozzlePhysicalIdForTrxDoneOn = nozzleStatusTrx.Status.Key;
- }
- else if (nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.OUT)
- {
- // here happens in a case that shutdown the FC at a fueling for seconds,
- // and the pump will stop fueling by its design, and then start FC without put back nozzle,
- // then the nozzle will reported as OUT with FILLING_COMPLETED
- // for this case, FC still can recover the last fill since nozzle id is confirmed
- nozzlePhysicalIdForTrxDoneOn = nozzleStatusTrx.Status.Key;
- }
- }
- else
- {
- // igem wayne dart won't report nozzle number when entered FILLING_COMPLETED.
- //logger.Debug("Pump: " + this.pumpId + ", " + " more like a iGEM wayne dart since FILLING_COMPLETED does not contain nozzleStatus at all");
- nozzlePhysicalIdForTrxDoneOn = this.operatingNozzlePhysicalId;
- }
- if (nozzlePhysicalIdForTrxDoneOn == 0)
- {
- logger.Info("Pump: " + this.pumpId + ", " + " targetNozzlePhysicalId is 0, will not query last fill");
- return;
- }
- if (this.nozzles.FirstOrDefault(n => n.PhysicalId == nozzlePhysicalIdForTrxDoneOn) == null)
- {
- /* I do see a case that pump side report a nozzle physical id 15, possible wire issue?? here try to recover*/
- logger.Info("Pump: " + this.pumpId + ", " + " targetNozzlePhysicalId: " + nozzlePhysicalIdForTrxDoneOn
- + " is NOT bound to any physical nozzle, will use last operating nozzle physical Id: "
- + this.operatingNozzlePhysicalId);
- nozzlePhysicalIdForTrxDoneOn = this.operatingNozzlePhysicalId;
- }
- #endregion
- try
- {
- LoopReadLastFill(nozzlePhysicalIdForTrxDoneOn);
- }
- catch (Exception exxx)
- {
- logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill exceptioned: " + exxx);
- this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true;
- }
- }
- #region Trigger Fdc state change event
- if (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.AUTHORIZED)
- {
- // only WayneDart Authorized state received in FDC_CALLING is valid and expected.
- if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_CALLING)
- {
- logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_AUTHORISED");
- this.lastLogicalDeviceState = LogicalDeviceState.FDC_AUTHORISED;
- var safe1 = this.OnStateChange;
- safe1?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_AUTHORISED));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
- }
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + " Unexpected WayneDart state: AUTHORIZED, will stop the pump");
- this.UnAuthorizePumpAndSwithToFdcReady();
- }
- }
- if (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.FILLING)
- {
- if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_FUELLING)
- {
- logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_FUELLING");
- this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING;
- var safe2 = this.OnStateChange;
- safe2?.Invoke(this,
- new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_FUELLING, this.nozzles.FirstOrDefault(n => n.PhysicalId == this.operatingNozzlePhysicalId)));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
- }
- }
- if (filledVolAndAmtTrx != null)
- {
- if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_AUTHORISED)
- {
- if (nozzleStatusTrx != null
- && (nozzleStatusTrx.Status.Key == 0
- || nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.IN))
- {
- /* we do see a case:
- * >>>>>>>>>>===start====(internalFdcState: FDC_AUTHORISED)
- * Nozzle with physical id: 0 is in state: IN, filling price: 666
- * Filled vol 4, amt 26
- * <<<<<<<<<<===end====
- * this is abnormal that why nozzle is IN but still have a running fuel, this caused by leaking control missed
- * in domestic WayneDart pump.
- * ignore this small running fuel, it will NOT report filling_complete later, HengShanIC reader rely on a flow of fueling+filling_Complete
- * finally, FC will unAuth this pump.
- */
- logger.Info("Pump: " + this.pumpId + ", " + " Received a nozzle down with filling info in FDC_AUTHORISED state, " +
- "treat as fuel leak control malfunctioning in physical pump side case, will NOT switch to FDC_FUELLING");
- }
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + " State switched to FDC_FUELLING from FDC_AUTHORISED");
- this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING;
- var safe2 = this.OnStateChange;
- safe2?.Invoke(this,
- new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_FUELLING,
- this.nozzles.FirstOrDefault(n => n.PhysicalId == this.operatingNozzlePhysicalId)));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
- }
- }
- if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_FUELLING)
- {
- logger.Debug("Pump: " + this.pumpId + ", " + "Filled volume " + filledVolAndAmtTrx.FilledVolume
- + ", amount " + filledVolAndAmtTrx.FilledAmount + " in state: FDC_FUELLING, will fire fdcEvent for logicalNozzle: "
- + this.nozzles.First(n => n.PhysicalId == this.operatingNozzlePhysicalId).LogicalId
- + ", physicalNozzle: " + this.operatingNozzlePhysicalId);
- var safe = this.OnCurrentFuellingStatusChange;
- safe?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction()
- {
- Nozzle = this.nozzles.First(n => n.PhysicalId == this.operatingNozzlePhysicalId),
- Amount = filledVolAndAmtTrx.FilledAmount,
- Volumn = filledVolAndAmtTrx.FilledVolume,
- Price = this.nozzles.First(n => n.PhysicalId == this.operatingNozzlePhysicalId).RealPriceOnPhysicalPump ?? 0,
- Finished = false,
- }));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnCurrentFuellingStatusChange event fired and back");
- }
- else
- logger.Info("Pump: " + this.pumpId + ", " + "Filled volume " + filledVolAndAmtTrx.FilledVolume
- + ", amount " + filledVolAndAmtTrx.FilledAmount + " received in internal Fdc state: " + this.lastLogicalDeviceState + ", will do nothing");
- }
- #endregion
- if (nozzleStatusTrx != null)
- {
- #region Nozzle status and filling price
- var targetNozzle = this.nozzles.FirstOrDefault(n => n.PhysicalId == nozzleStatusTrx.Status.Key);
- if (targetNozzle != null)
- targetNozzle.RealPriceOnPhysicalPump = nozzleStatusTrx.FillingPrice;
- // nozzle number ==0 indicates all nozzles were put back(In)
- if (nozzleStatusTrx.Status.Key == 0
- || nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.IN)
- {
- if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_AUTHORISED)
- {
- /* here we just unauthorize the pump once nozzle down and pump get authorized previously,
- * some customers may want keep authorized state, but leave for future to implement */
- logger.Debug("Pump: " + this.pumpId + ", " + " PhysicalNozzle: " + nozzleStatusTrx.Status.Key + " is put back, " +
- "will `Stop`(Unauthorize) the pump");
- this.UnAuthorizePumpAndSwithToFdcReady();
- }
- else
- {
- // Nozzle-IN in FDC state FDC_FUELLING
- if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_FUELLING)
- {
- // if nozzle IN incoming with a FILLING_COMPLETED, then no need to fire extra read last fill process,
- // other parts of code will handle
- // here is trying to handle the case of FILLING_COMPLETED missed read in FC.
- if (pumpStatusTrx == null ||
- (pumpStatusTrx != null && pumpStatusTrx.Status != PumpStatus.FILLING_COMPLETED))
- {
- // will block pump auth from now by this flag set.
- this.lastFillRetrievedSinceNozzleDownAtFdcFueling = false;
- /* there's a case that phsycial pump missed to send Filling_complete to FC, cause miss to read last fill
- here bring in an extra read.
- */
- // filling complete typically incoming in 1 or 2 seconds, and read last fill and read totalizer
- // will cost another 2 seconds.
- // here, if filling complete have not incoming for 6 seconds, then extra read will happens
- // I do see cases of `filling complete not incoming into FC` in field, may the issue in wire or physical pump??
- this.retryReadLastFillTimer = new System.Timers.Timer(6000);
- this.retryReadLastFillTimer.Elapsed += (_, __) =>
- {
- byte physicalNozzlePlacedBack = (nozzleStatusTrx.Status.Key == 0 ?
- this.operatingNozzlePhysicalId : nozzleStatusTrx.Status.Key);
- if (this.nozzles.FirstOrDefault(n => n.PhysicalId == physicalNozzlePlacedBack) == null)
- {
- /* I do see a case that pump report a nozzle with physical id 15 here! try to recover*/
- logger.Info("Pump: " + this.pumpId + ", " + " targetNozzlePhysicalId: " + physicalNozzlePlacedBack
- + " is NOT bound to any physical nozzle, will use last operating nozzle physical Id: "
- + this.operatingNozzlePhysicalId);
- physicalNozzlePlacedBack = this.operatingNozzlePhysicalId;
- }
- logger.Info("Pump: " + this.pumpId + ", physicalNozzle: " + physicalNozzlePlacedBack
- + ", extra retrieving last fill is kicked off");
- LoopReadLastFill(physicalNozzlePlacedBack);
- };
- retryReadLastFillTimer.Start();
- }
- }
- if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY)
- {
- logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_READY due to nozzle IN.");
- this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
- var safe3 = this.OnStateChange;
- safe3?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
- return;
- }
- }
- }
- else
- {
- /* specific nozzle out */
- if ((this.lastLogicalDeviceState == LogicalDeviceState.FDC_AUTHORISED
- || (pumpStatusTrx != null && pumpStatusTrx.Status == PumpStatus.AUTHORIZED))
- && nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.OUT)
- {
- if (this.operatingNozzlePhysicalId != nozzleStatusTrx.Status.Key)
- {
- //I do see cases in lab, fast switch(put down and put on another) nozzle by initial calling nozzle get authorized,
- //but Pump side didn't send in down Nozzle's In message.
- // we don't allow this case
- logger.Info("Pump: " + this.pumpId + ", " + " Detect nozzle switch(from physicalId: "
- + this.operatingNozzlePhysicalId + " to " + nozzleStatusTrx.Status.Key + ") in WayneDart.AUTHORIZED or LogicalDeviceState.FDC_AUTHORISED state, illegal, will stop the pump");
- // how switched a nozzle in WayneDart in AUTHORIZED state??
- this.UnAuthorizePumpAndSwithToFdcReady();
- }
- // no need to calling anymore since it already in Authorized, this happens for WayneDart PumpSim software
- }
- else if ((this.lastLogicalDeviceState == LogicalDeviceState.FDC_FUELLING
- || this.lastLogicalDeviceState == LogicalDeviceState.FDC_AUTHORISED)
- && nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.OUT)
- {
- // we do see this case in domestic wayne dart pump, not sure how it happens.
- }
- else if (nozzleStatusTrx.Status.Value == NozzleStatusAndFillingPrice_TransactionData.NozzleStatus.OUT)
- {
- /* nozzle out */
- if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_CALLING)
- {
- // need make sure previous trx had been retrieved by FC, otherwise should not send RequestRequest which will
- // clear previous trx info in physical pump side.
- if (!this.lastFillRetrievedSinceNozzleDownAtFdcFueling)
- {
- logger.Info("Pump: " + this.pumpId + ", " + " will ignore this nozzle out due to last fill still in retrieving");
- return;
- }
- this.operatingNozzlePhysicalId = nozzleStatusTrx.Status.Key;
- logger.Debug("Pump: " + this.pumpId + ", " + " send RESET to clear previous trx info, prepare for new fuel");
- this.context.Outgoing.WriteAsync(
- new ResetRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
- (request, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.BlockSeqNumber == request.BlockSeqNumber,
- (request, ackResponse) =>
- {
- if (ackResponse == null)
- {
- logger.Error("Pump: " + this.pumpId + ", " + " RESET wait for ACK timedout, will not fire calling state");
- }
- else
- {
- logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_CALLING due to RESET ACKed or NAKed(physicalNozzle: " + nozzleStatusTrx.Status.Key + ")");
- // insert a Poll for poll and receive wayne dart pump entered RESET state msg, otherwise
- // after the Fdc_Calling event fired below, outer will send auth request
- // directly and then pump will report 2 states in one msg response after auth: RESET + AUTHORIZED,
- //this has no problem, only for better understanding and logging.
- this.context.Outgoing.Write(new Poll(this.dartPumpCommAddress, 0));
- //this.context.Outgoing.Write(new Poll(this.dartPumpCommAddress, 0));
- //this.context.Outgoing.Write(new Poll(this.dartPumpCommAddress, 0));
- //this.context.Outgoing.Write(new Poll(this.dartPumpCommAddress, 0));
- this.lastLogicalDeviceState = LogicalDeviceState.FDC_CALLING;
- var safe2 = this.OnStateChange;
- safe2?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(
- LogicalDeviceState.FDC_CALLING,
- this.nozzles.First(n => n.PhysicalId == nozzleStatusTrx.Status.Key)));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
- }
- }, 2000);
- }
- if (pumpStatusTrx != null
- && pumpStatusTrx.Status == PumpStatus.FILLING)
- {
- if (filledVolAndAmtTrx != null
- && this.lastLogicalDeviceState == LogicalDeviceState.FDC_FUELLING)
- {
- logger.Debug("Pump: " + this.pumpId + ", " + "Filled volume " + filledVolAndAmtTrx.FilledVolume
- + ", amount " + filledVolAndAmtTrx.FilledAmount + " in state: FDC_FUELLING, will fire fdcEvent1");
- var safe = this.OnCurrentFuellingStatusChange;
- safe?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction()
- {
- Nozzle = this.nozzles.First(n => n.PhysicalId == this.operatingNozzlePhysicalId),
- Amount = filledVolAndAmtTrx.FilledAmount,
- Volumn = filledVolAndAmtTrx.FilledVolume,
- Price = this.nozzles.First(n => n.PhysicalId == this.operatingNozzlePhysicalId).RealPriceOnPhysicalPump ?? 0,
- Finished = false,
- }));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnCurrentFuellingStatusChange event fired and back");
- }
- }
- }
- }
- #endregion
- }
- }
- }
- private void UnAuthorizePumpAndSwithToFdcReady()
- {
- this.context.Outgoing.WriteAsync(
- new StopRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
- (request, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.BlockSeqNumber == request.BlockSeqNumber,
- (request, response) =>
- {
- if (response != null)
- {
- if (response.ControlCharacter == ControlCharacter.ACK)
- {
- if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY)
- {
- logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_READY due to StopRequest acked.");
- this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
- var safe3 = this.OnStateChange;
- safe3?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
- return;
- }
- }
- else
- logger.Error("Pump: " + this.pumpId + ", " + "StopRequest NAKed.");
- }
- else
- logger.Error("Pump: " + this.pumpId + ", " + "StopRequest request timed out for ack.");
- if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY)
- {
- logger.Debug("Pump: " + this.pumpId + ", " + " State switched to FDC_READY though StopRequest failed");
- this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY;
- var safe2 = this.OnStateChange;
- safe2?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnStateChange event fired and back");
- }
- }, 1500);
- }
- private System.Timers.Timer retryReadLastFillTimer;
- private int onReadingLastFill = 0;
- /// <summary>
- /// may get called via 2 route:
- /// 1. received a Filling_Complete from pump.
- /// 2. by a delay timer that fired by nozzle IN from FDC_FUELING state.
- /// </summary>
- /// <param name="nozzlePhysicalIdForTrxDoneOn"></param>
- private void LoopReadLastFill(byte nozzlePhysicalIdForTrxDoneOn)
- {
- if (0 == Interlocked.CompareExchange(ref this.onReadingLastFill, 1, 0))
- {
- this.retryReadLastFillTimer?.Stop();
- this.retryReadLastFillTimer?.Dispose();
- // disable auth since last fill has not been read yet, this happens in real site with bad wire connection condition.
- //this.lastFillRetrieved = false;
- this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (t) =>
- {
- if (t != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
- logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 1st time");
- this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (tt) =>
- {
- if (tt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
- logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 2nd time");
- this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (ttt) =>
- {
- if (ttt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
- logger.Info("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will retry 3rd time");
- this.ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(nozzlePhysicalIdForTrxDoneOn, (tttt) =>
- {
- if (tttt != null) { this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true; this.onReadingLastFill = 0; return; }
- logger.Error("Pump: " + this.pumpId + ", " + " Read Last Fill failed on physicalNozzle: " + nozzlePhysicalIdForTrxDoneOn + ", will NOT retry anymore (total read 4 times)");
- // Have no way but release it to allow continue pump auth and fueling, But a fuel sale has been lost!
- this.onReadingLastFill = 0;
- this.lastFillRetrievedSinceNozzleDownAtFdcFueling = true;
- });
- });
- });
- });
- }
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="targetNozzlePhysicalId"></param>
- /// <param name="callback">FdcTransaction is null when retrieved failed, like time out. otherwise, an object will return</param>
- private void ReadLastFillAndUpdateLocalTotalizerAndFireFdcTrxDoneEvent(byte targetNozzlePhysicalId,
- Action<FdcTransaction> callback)
- {
- try
- {
- #region ReturnFillingInfomrationRequest
- var targetNozzle = this.nozzles.First(n => n.PhysicalId == targetNozzlePhysicalId);
- logger.Debug("Pump: " + this.pumpId + ", " + " filling is finished, will query last fill info for physicalNozzle: " + targetNozzlePhysicalId
- + ", logicalNozzle: " + targetNozzle.LogicalId);
- this.context.Outgoing.WriteAsync(
- new ReturnFillingInfomrationRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)),
- (request, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.ControlCharacter == ControlCharacter.DATA,
- (request, returnFillingInfoResponse) =>
- {
- try
- {
- #region ReturnFillingInfomration Response
- if (returnFillingInfoResponse == null ||
- (returnFillingInfoResponse != null && returnFillingInfoResponse.TransactionDatas.FirstOrDefault(f => f.TransactionNumber == 0x02) == null))
- {
- logger.Info("Pump: " + this.pumpId + ", " + " retrieve last fill info timed out or inner FilledVolumeAndAmount trxData is null");
- callback?.Invoke(null);
- }
- else
- {
- try
- {
- #region RequestTotalVolumeCountersRequest
- this.context.Outgoing.Write(new ACK(this.dartPumpCommAddress, returnFillingInfoResponse.BlockSeqNumber));
- /*note, the response would contain last sale vol and money, besides, all nozzle state, and pump state
- will be returned as well, we only interested on sale vol and money which trx number is 0x02 */
- FilledVolumeAndAmount_TransactionData lastFinishedTrx = null;
- try
- {
- lastFinishedTrx =
- new FilledVolumeAndAmount_TransactionData(
- returnFillingInfoResponse.TransactionDatas.FirstOrDefault(f => f.TransactionNumber == 0x02));
- }
- catch (Exception exx)
- {
- var safeLogStr = "exceptioned in catch: ";
- try
- {
- safeLogStr = returnFillingInfoResponse?.ToLogString() ?? "";
- }
- catch (Exception iiexx)
- {
- safeLogStr += iiexx.ToString();
- }
- logger.Error($"Pump: " + this.pumpId + ", " +
- $"Parse FilledVolumeAndAmount_TransactionData(from: {safeLogStr}) exceptioned: {exx}");
- }
- if (lastFinishedTrx != null && lastFinishedTrx.FilledVolume != 0)
- {
- #region concrete Non-Zero trx read
- logger.Info("Pump: " + this.pumpId + ", " + " Last fill retrieved, volume "
- + lastFinishedTrx.FilledVolume + ", amount " + lastFinishedTrx.FilledAmount
- + ", done in logical nozzle: " + targetNozzle.LogicalId
- + ", physical nozzle: " + targetNozzlePhysicalId
- + ", will read vol totalizer for compare");
- this.context.Outgoing.WriteAsync(
- new RequestTotalVolumeCountersRequest(this.dartPumpCommAddress, this.parent.GetNewMessageToken(this.pumpId)
- , targetNozzle.PhysicalId),
- (___, testResponse) =>
- testResponse.Adrs == this.dartPumpCommAddress &&
- testResponse.ControlCharacter == ControlCharacter.DATA,
- (_____, volTotalResponse) =>
- {
- FdcTransaction lastFillTrx = null;
- try
- {
- TotalCounters_TransactionData justReadVolumeTotalizerTrx = null;
- if (volTotalResponse != null)
- {
- this.context.Outgoing.Write(new ACK(this.dartPumpCommAddress, volTotalResponse.BlockSeqNumber));
- if (volTotalResponse.TransactionDatas.FirstOrDefault(f => f.TransactionNumber == 0x65) != null)
- justReadVolumeTotalizerTrx =
- new TotalCounters_TransactionData(
- volTotalResponse.TransactionDatas.FirstOrDefault(f => f.TransactionNumber == 0x65));
- }
- //any of null below will quit the last fill check, no worry about the trx miss, the caller will start retry
- if (volTotalResponse == null || justReadVolumeTotalizerTrx == null)
- {
- logger.Info("Pump: " + this.pumpId + ", " + " logical nozzle: " + targetNozzle.LogicalId
- + ", physicalNozzle: " + targetNozzlePhysicalId + ", read VolTotalizer failed");
- callback?.Invoke(null);
- return;
- }
- logger.Info("Pump: " + this.pumpId + ", " + " logical nozzle: " + targetNozzle.LogicalId
- + ", physicalNozzle: " + targetNozzlePhysicalId + ", Vol totalizer read value: " + ((justReadVolumeTotalizerTrx?.TotalValue.ToString()) ?? "null"));
- if (targetNozzle.VolumeTotalizer.HasValue)
- {
- /* compare last backup totalizer with just read value, if equals, should not rasie up new trx*/
- var lastVolumeTotalizerValue = targetNozzle.VolumeTotalizer.Value;
- logger.Info("Pump: " + this.pumpId + ", " + "logical nozzle: " + targetNozzle.LogicalId + ", last backup volume totalizer value: " + lastVolumeTotalizerValue
- + ", while now just read volume totalizer value: " + justReadVolumeTotalizerTrx.TotalValue
- + ", they're " +
- (lastVolumeTotalizerValue == justReadVolumeTotalizerTrx.TotalValue ?
- "Equal(no new trx)" :
- "Not Equal with diff: " + (justReadVolumeTotalizerTrx.TotalValue - lastVolumeTotalizerValue) + " (new trx detected and created with vol from LastFill data element: " + lastFinishedTrx.FilledVolume + ")"));
- targetNozzle.VolumeTotalizer = justReadVolumeTotalizerTrx.TotalValue;
- if (lastVolumeTotalizerValue == justReadVolumeTotalizerTrx.TotalValue)
- {
- // no new trx
- callback?.Invoke(new FdcTransaction());
- return;
- }
- }
- if (!targetNozzle.VolumeTotalizer.HasValue)
- targetNozzle.VolumeTotalizer = justReadVolumeTotalizerTrx.TotalValue;
- // at least within 65 years, exception will not throw here
- int newTrxSeqNumber = (int)(DateTime.Now.Subtract(new DateTime(2018, 5, 25)).TotalSeconds);
- logger.Info("Pump: " + this.pumpId + ", " + "logical nozzle: " + targetNozzle.LogicalId
- + ", physicalNozzle: " + targetNozzlePhysicalId
- + ", trx done with amt: " + lastFinishedTrx.FilledAmount
- + ", vol: " + lastFinishedTrx.FilledVolume
- + ", seqNo.: " + newTrxSeqNumber);
- lastFillTrx = new FdcTransaction()
- {
- Nozzle = this.nozzles.First(n => n.PhysicalId == targetNozzlePhysicalId),
- Amount = lastFinishedTrx.FilledAmount,
- Volumn = lastFinishedTrx.FilledVolume,
- Price = this.nozzles.First(n => n.PhysicalId == targetNozzlePhysicalId).RealPriceOnPhysicalPump ?? 0,
- SequenceNumberGeneratedOnPhysicalPump = newTrxSeqNumber,
- VolumeTotalizer = justReadVolumeTotalizerTrx?.TotalValue ?? 0,
- Finished = true,
- };
- callback?.Invoke(lastFillTrx);
- }
- catch
- {
- callback?.Invoke(null);
- }
- var safe6 = this.OnCurrentFuellingStatusChange;
- safe6?.Invoke(this, new FdcTransactionDoneEventArg(lastFillTrx));
- logger.Trace("Pump: " + this.pumpId + ", " + " OnCurrentFuellingStatusChange event fired and back");
- }, 3000);
- #endregion
- }
- else
- {
- logger.Info("Pump: " + this.pumpId + ", " + " Last filled info is null or volume is 0, will ignore");
- // zero amount trx
- callback?.Invoke(new FdcTransaction());
- }
- #endregion
- }
- catch (Exception exxxx)
- {
- callback?.Invoke(null);
- }
- }
- #endregion
- }
- catch
- {
- callback?.Invoke(null);
- }
- }, 2500);
- #endregion
- }
- catch
- {
- callback?.Invoke(null);
- }
- }
- public void Dispose()
- {
- this.deviceOfflineCountdownTimer.Stop();
- this.deviceOfflineCountdownTimer.Dispose();
- this.retryReadLastFillTimer?.Stop();
- this.retryReadLastFillTimer?.Dispose();
- }
- }
- }
|