using Edge.Core.IndustryStandardInterface.Pump.Fdc; using Edge.Core.IndustryStandardInterface.Pump; using HengshanPaymentTerminal; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using HengshanPaymentTerminal.MessageEntity.Outgoing; namespace HengshanPaymentTerminal { /// /// A neutral pump handler that represents a Hengshan pump regardless of its protocol (Hengshan or TQC). /// Actuall this is a fake pump handler since it doesn't have direct control over the pumps. /// public class HengshanPumpHandler : IFdcPumpController { #region Fields //The parent of the pump handler. private HengshanPayTermHandler terminalHandler; private LogicalDeviceState state = LogicalDeviceState.FDC_READY; //private SpsManager spsManager; private byte frameSqNo; private object syncObj = new object(); private bool totalizerRequested = false; private byte totalizerRequestedNozzleLogicId = 0; private bool lockPumpRequested = false; private bool unlockPumpRequested = false; private LogicalNozzle nozzle; private List nozzleList; private List siteNozzleNoList; #endregion #region Logger private static NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("IPosPlusApp"); #endregion #region Constructor public HengshanPumpHandler(HengshanPayTermHandler terminalHandler, string name, int pumpId, List nozzles, List siteNozzleNoList) { this.terminalHandler = terminalHandler; this.terminalHandler.OnCheckCommandReceived += TerminalHandler_OnCheckCommandReceived; Name = name; PumpId = pumpId; nozzle = new LogicalNozzle(pumpId, Convert.ToByte(pumpId), 1, null); //spsManager = new SpsManager(); this.siteNozzleNoList = siteNozzleNoList; nozzleList = new List(); foreach (var nozzleNo in nozzles) { var logicalNozzle = new LogicalNozzle(PumpId, Convert.ToByte(nozzleNo), Convert.ToByte(nozzleNo), null); nozzleList.Add(logicalNozzle); } } #endregion #region Properties public string Name { get; } public int PumpId { get; } public int PumpPhysicalId => 1; public IEnumerable Nozzles { get { //For Hengshan pumps, there is only one logical nozzle per pump, no real price set. return nozzleList;//return new List { nozzle }; } } //Decimal digit settings, China domestic standard //小数点后位数设定,中国国内标准,一般为2位 public int AmountDecimalDigits => 2; public int VolumeDecimalDigits => 2; public int PriceDecimalDigits => 2; public int VolumeTotalizerDecimalDigits => 2; public byte FrameSqNo { get { lock (syncObj) { return frameSqNo; } } set { lock (syncObj) { frameSqNo = value; } } } #endregion #region Events public event EventHandler OnStateChange; public event EventHandler OnCurrentFuellingStatusChange; public event EventHandler OnFuelPriceChangeRequested; #endregion #region Event handlers private byte GetSiteNozzleNoByLogicId(byte logicId) { foreach (var nozzleNo in siteNozzleNoList) { if (terminalHandler.NozzleLogicIdDict[nozzleNo] == logicId) return Convert.ToByte(nozzleNo); } return Convert.ToByte(PumpId); } private void TerminalHandler_OnCheckCommandReceived(object sender, CheckCommandEventArgs e) { if (totalizerRequested && e.CheckCommandRequest.SourceAddress == terminalHandler.GetSubAddressForPump(PumpId)) { var request = new ReadVolumeTotal { Prefix = 0xFA, SourceAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)), DestinationAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)), FrameSqNoByte = e.CheckCommandRequest.FrameSqNoByte, Handle = (byte)MessageEntity.Command.ReadVolumeTotalizer, NozzleNo = GetSiteNozzleNoByLogicId(totalizerRequestedNozzleLogicId)//Convert.ToByte(PumpId) }; terminalHandler.PendMessage(request); Log("Request to terminal to get totalizer, pending!"); } else if ((lockPumpRequested || unlockPumpRequested) && e.CheckCommandRequest.SourceAddress == terminalHandler.GetSubAddressForPump(PumpId)) { LockOrUnlockPumpRequest request = new LockOrUnlockPumpRequest { Prefix = 0xFA, SourceAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)), DestinationAddress = Convert.ToByte(terminalHandler.GetSubAddressForPump(PumpId)), Handle = Convert.ToByte(MessageEntity.Command.LockOrUnlockPump), FrameSqNoByte = e.CheckCommandRequest.FrameSqNoByte, FPCode = EncodeFPCodeString(PumpId, PumpId), OperationType = lockPumpRequested ? Support.LockUnlockOperation.Lock : Support.LockUnlockOperation.Unlock }; terminalHandler.PendMessage(request); var currentPump = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId); if (currentPump == null) Log("Could not get the pump state holder"); else Log($"Current pump state holder, pump no: {currentPump.PumpNo}"); currentPump.OperationType = request.OperationType; Log($"Current pump state holder, operation type set to: {request.OperationType}"); //terminalHandler.Write(request); if (lockPumpRequested) Log($"PumpNo: {PumpId}, Request to lock pump, pending!"); if (unlockPumpRequested) Log($"PumpNo: {PumpId}, Request to unlock pump, pending!"); } } public void FirePumpStateChange(LogicalDeviceState state, byte nozzeLogicId) { var logicalNozzle = new LogicalNozzle(PumpId, Convert.ToByte(PumpPhysicalId), nozzeLogicId, null); this.state = state; if (state == LogicalDeviceState.FDC_READY) OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY)); else OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(state, logicalNozzle)); } public void FireNozzleStateChange(LogicalDeviceState? state) { nozzle = new LogicalNozzle(PumpId, Convert.ToByte(PumpPhysicalId), 1, null); if (state.HasValue) nozzle.LogicalState = state; else nozzle.LogicalState = null; OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY, nozzle)); } //2023-08-22 状态变更 public void FireFuelingStatusChange(FdcTransaction fuelingTransaction) { OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(fuelingTransaction)); } #endregion #region Pump interactions public LogicalDeviceState QueryStatus() { var stateHolder = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId); if (stateHolder != null) { if (stateHolder.State.DispenserState == 3) { return LogicalDeviceState.FDC_READY; } else if (stateHolder.State.DispenserState == 2) { return LogicalDeviceState.FDC_LOCKED; } } return LogicalDeviceState.FDC_READY; } /// /// locks a nozzle /// /// /// public Task LockNozzleAsync(byte logicalNozzleId) { var stateHolder = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId); if (stateHolder != null) { if (stateHolder.State.DispenserState == 2) { // If the pump is already locked, reply with success immediately. return Task.FromResult(true); } else if (stateHolder.State.DispenserState != 3) { // Avoid handling locking when pump is fueling or in other states. return Task.FromResult(false); } } TaskCompletionSource tcs = new TaskCompletionSource(); EventHandler eventHandler = null; eventHandler += (o, e) => { Log($"Locking pump, result received, success? {e.Result}"); lockPumpRequested = false; tcs.SetResult(e.Result); if (e.Result) { FireNozzleStateChange(LogicalDeviceState.FDC_LOCKED); } terminalHandler.OnLockUnlockCompleted -= eventHandler; var currentPump = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId); if (currentPump != null) { currentPump.OperationType = Support.LockUnlockOperation.None; } }; terminalHandler.OnLockUnlockCompleted += eventHandler; lockPumpRequested = true; return tcs.Task; } /// /// unlock a locked nozzle /// /// /// public Task UnlockNozzleAsync(byte logicalNozzleId) { var stateHolder = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId); if (stateHolder != null) { if (stateHolder.State.DispenserState == 3) { // Need to do nothing when pump is not locked. return Task.FromResult(true); } } TaskCompletionSource tcs = new TaskCompletionSource(); EventHandler eventHandler = null; eventHandler += (o, e) => { Log($"Unlocking pump, result received, success? {e.Result}"); unlockPumpRequested = false; tcs.SetResult(e.Result); if (e.Result) { FireNozzleStateChange(null); } terminalHandler.OnLockUnlockCompleted -= eventHandler; var currentPump = terminalHandler.PumpStatusDict.GetValueOrDefault(PumpId); if (currentPump != null) { currentPump.OperationType = Support.LockUnlockOperation.None; } }; terminalHandler.OnLockUnlockCompleted += eventHandler; unlockPumpRequested = true; return tcs.Task; } #endregion #region FDC Server Init public void OnFdcServerInit(Dictionary parameters) { } public Task QueryStatusAsync() { return Task.FromResult(state); } public void QueryTotalizerAsync(int pumpId, byte logicalNozzleId) { Log($"Query totalizer internally..."); var request = new ReadVolumeTotal { Prefix = 0xFA, SourceAddress = Convert.ToByte(pumpId), DestinationAddress = Convert.ToByte(pumpId), FrameSqNoByte = frameSqNo, Handle = (byte)MessageEntity.Command.ReadVolumeTotalizer, NozzleNo = logicalNozzleId }; terminalHandler.Write(request); } public Task> QueryTotalizerAsync(byte logicalNozzleId) { Log($"LogicalNozzle {logicalNozzleId} querying totalizer..."); TaskCompletionSource> tcs = new TaskCompletionSource>(); EventHandler eventHandler = null; eventHandler += (o, e) => { Log($"Totalizer data for nozzle: {e.NozzleNo}, money acc: {e.Totalizer.Item1}, volume acc: {e.Totalizer.Item2}"); totalizerRequested = false; //Reset the value so that no more request will be sent. if (e.NozzleNo == GetSiteNozzleNoByLogicId(logicalNozzleId)) { Log("Totalizer data ready"); tcs.SetResult(e.Totalizer); terminalHandler.OnTotalizerReceived -= eventHandler; } totalizerRequestedNozzleLogicId = 0; }; terminalHandler.OnTotalizerReceived += eventHandler; totalizerRequestedNozzleLogicId = logicalNozzleId; totalizerRequested = true; return tcs.Task; } public Task ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId) { logger.Info($"Change price received, Pump {PumpId}, LogicalNozzle {logicalNozzleId}, Price {newPriceWithoutDecimalPoint}"); //Let the iPOS App handle the fuel price change. OnFuelPriceChangeRequested?.Invoke(this, new FuelPriceChangeRequestEventArgs { NozzleId = logicalNozzleId, PumpId = (byte)PumpId, Price = newPriceWithoutDecimalPoint }); Log($"application handled fuel price change"); return Task.FromResult(true); //TaskCompletionSource tcs = new TaskCompletionSource(); //EventHandler eventHandler = null; //eventHandler += (o, e) => //{ // Log("Terminal requested fuel price"); // tcs.SetResult(e.Requested); // terminalHandler.OnTerminalFuelPriceDownloadRequested -= eventHandler; //}; //terminalHandler.OnTerminalFuelPriceDownloadRequested += eventHandler; //return tcs.Task; } public Task AuthorizeAsync(byte logicalNozzleId) { return Task.FromResult(false); } public Task UnAuthorizeAsync(byte logicalNozzleId) { return Task.FromResult(false); } public Task AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId) { return Task.FromResult(false); } public Task AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId) { return Task.FromResult(false); } public Task FuelingRoundUpByAmountAsync(int amount) { return Task.FromResult(false); } public Task FuelingRoundUpByVolumeAsync(int volume) { return Task.FromResult(false); } public Task SuspendFuellingAsync() { return Task.FromResult(false); } public Task ResumeFuellingAsync() { return Task.FromResult(false); } #endregion private string EncodeFPCodeString(int nozzleId, int pumpId) { return nozzleId.ToString("X").PadLeft(2, '0') + pumpId.ToString("X").PadLeft(2, '0'); } #region Log private void Log(string message) { logger.Info($"Pump Handler: {PumpId}, {message}"); } #endregion } }