using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump; using NozzleLockConfiguration; using SinochemCarplateService.Models; using SinochemCloudClient.Models; using SinoChemFC2PosProxy; using SinochemInternetPlusApp.EpsTrxCleanup; using SinochemPosClient.Models; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; using Wayne.FDCPOSLibrary; using Wayne.Lib; using Wayne.Lib.Log; using WayneChina_IcCardReader_SinoChem.MessageEntity; using WayneChina_IcCardReader_SinoChem.MessageEntity.Incoming; namespace SinochemInternetPlusApp { public class Eps : IDisposable { static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("Application"); private IEnumerable>> carPlateProcessors; private IEnumerable> icCardReaderProcessors; private List fuelingPoints; private EpsTrxCleanupManager epsTrxCleanupManager; //protected DebugLogger debugLogger; private int requestId; private object requestSyncObj = new object(); #region devices and services private IEnumerable pumpControllers; private Sinochem_CarPlateRecognizeCamera_HuLianWangJia.Handler carPlateServer; private IEnumerable cardReaderHandlers; //private FdcCommunicator fccClient; private CloudManager cloudManager; private PosManager posManger; #endregion #region constants public int InvalidNozzleId { get { return 9999; } } #endregion #region Constructor public Eps(IEnumerable fpIds, Dictionary> fpNozzlesDict, IEnumerable pumpControllers, IEnumerable>> carPlateProcessors, IEnumerable> icCardReaderProcessors) { var identifiableEntity = new IdentifiableEntity(0, "EpsMain", "", null); fuelingPoints = new List(fpIds.Count()); foreach (int nozzleId in fpIds) { fuelingPoints.Add(new FuelingPoint(nozzleId, fpNozzlesDict[nozzleId], this)); } epsTrxCleanupManager = new EpsTrxCleanupManager(this); cloudManager = new CloudManager(); posManger = new PosManager(); NozzleLockAccessor.FillinNozzles(fpNozzlesDict); this.pumpControllers = pumpControllers; this.carPlateProcessors = carPlateProcessors; this.icCardReaderProcessors = icCardReaderProcessors; logger.Info("eps has been constructed"); logger.Info(string.Format($"{this.pumpControllers.Count()} processors were loaded.")); foreach (var proc in this.pumpControllers) { logger.Debug(proc.GetType().ToString()); } } internal TrxNotificationResponse NotifySuccessfulTrxToPos(EpsTransactionModel model, DebugLogger debugLogger) { return posManger.NotifyPosSuccessfulTrx(model, debugLogger); } internal PaymentResponse SendPaymentToCloud(EpsTransactionModel currentEpsTrxModel, DebugLogger debugLogger) { return cloudManager.Payment(currentEpsTrxModel, debugLogger); } internal BalanceInquiryResponse SendBalanceInquiryToCloud(string cardNo, string encryptedPin, string tid, int nozzleId, DebugLogger debugLogger) { return cloudManager.BalanceInquiry(cardNo, encryptedPin, tid, nozzleId, debugLogger); } #endregion public void Run() { SetupICCardReaderHandler(); if (fuelingPoints != null) { foreach (var fp in fuelingPoints) { fp.Start(); } } epsTrxCleanupManager.Start(); SetupFccClient(); SetupCarplateServer(); } private void SetupFccClient() { //SinoChemFC2PosProxy.EntryPoint.Main(null); //fccClient = SinoChemFC2PosProxy.EntryPoint.FdcComm; foreach (var pumpController in this.pumpControllers) { pumpController.OnStateChange += (s, a) => { var pump = s as IFdcPumpController; if (a.NewPumpState == LogicalDeviceState.FDC_CALLING) { int sitewiseNozzleId = SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a.StateChangedNozzles.FirstOrDefault()?.LogicalId ?? 0); this.FccClient_NozzleLifted(sitewiseNozzleId, pump); } else if (a.NewPumpState == LogicalDeviceState.FDC_READY) { int sitewiseNozzleId = SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a.StateChangedNozzles.FirstOrDefault()?.LogicalId ?? 0); this.FccClient_NozzleReplaced(sitewiseNozzleId); } else if (a.NewPumpState == LogicalDeviceState.FDC_AUTHORISED) { int sitewiseNozzleId = SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a.StateChangedNozzles.FirstOrDefault()?.LogicalId ?? 0); this.FccClient_AuthOk(sitewiseNozzleId, 1999); } }; } foreach (var pumpController in this.pumpControllers) { pumpController.OnCurrentFuellingStatusChange += (s, a) => { var pump = s as IFdcPumpController; if (a.Transaction.Finished) { int sitewiseNozzleId = SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a.Transaction.Nozzle.LogicalId); this.FccClient_FuelingDone(sitewiseNozzleId, a.Transaction.SequenceNumberGeneratedOnPhysicalPump, a.Transaction.Amount, a.Transaction.Volumn, 1999); } }; } //if (fccClient != null) //{ // //fccClient.NozzleLifted += FccClient_NozzleLifted; // //fccClient.NozzleReplaced += FccClient_NozzleReplaced; // fccClient.AuthOk += FccClient_AuthOk; // fccClient.AuthFailed += FccClient_AuthFailed; // fccClient.FuelingDone += FccClient_FuelingDone; //} //else //{ // Console.WriteLine("FdcClient not available!!"); //} } // find car plate handler and listen to the new car plate event private void SetupCarplateServer() { carPlateServer = carPlateProcessors.Select(proc => (Sinochem_CarPlateRecognizeCamera_HuLianWangJia.Handler)proc.Context.Handler) .FirstOrDefault(); if (carPlateServer != null) { carPlateServer.NewCarPlateScanned += CarPlateServer_NewCarPlateScanned; carPlateServer.OnMessageReceivedViaFdc += (msg) => { logger.Debug($"fdc message:\n {msg}"); if (msg.Length > 10 && msg.StartsWith("("DisplayResponse ACK", OverallResult.Success); }; } else { Console.WriteLine("carPlateServer not found!!!"); logger.Info("carPlateServer not found!!!"); } } private void CarPlateServer_NewCarPlateScanned(CarPlateTrxRequest request) { int sitewiseNozzleId = InvalidNozzleId; try { sitewiseNozzleId = int.Parse(request.gun); } catch (Exception ex) { } if (IsNozzleConfiguredOpen(sitewiseNozzleId)) { var fp = GetFp(sitewiseNozzleId); fp?.SignalNewCarplate(request); } else { logger.Info (string.Format($"Physical nozzle# {sitewiseNozzleId} is closed, so we ignore the car plate event!!!")); } } private void SetupICCardReaderHandler() { cardReaderHandlers = icCardReaderProcessors.Select(proc => (WayneChina_IcCardReader_SinoChem.Handler)proc.Context.Handler); if (cardReaderHandlers != null && cardReaderHandlers.Count() > 0) { foreach (var handler in cardReaderHandlers) { handler.OnCardReaderMessageReceived += Handler_OnCardReaderMessageReceived; } } else { Console.WriteLine("card reader handlers not found!!"); logger.Info("card reader handlers not found!!"); } } private void Handler_OnCardReaderMessageReceived(object sender, WayneChina_IcCardReader_SinoChem.CardReaderMessageEventArgs e) { logger.Info($"Receiving Card Reader message {e.CardReaderMessage.GetType().ToString()} for FP id = {e.FuelingPointId}"); var targetFP = fuelingPoints.First(_ => _.FuelingPointId == e.FuelingPointId);//GetFp(e.NozzleId); if (e.CardReaderMessage is ACK) { targetFP?.SingalAckReceived((ACK)e.CardReaderMessage); } else if (e.CardReaderMessage is SignDataResponse) { targetFP?.SignalSignedDataArrived((SignDataResponse)e.CardReaderMessage); } else if (e.CardReaderMessage is CardReaderStateEvent) { targetFP?.SingalCardReaderStateEvent((CardReaderStateEvent)e.CardReaderMessage); } else if (e.CardReaderMessage is CardExternalCheckErrorRequest) { targetFP?.SignalCardExternalCheckFailure((CardExternalCheckErrorRequest)e.CardReaderMessage); } else if (e.CardReaderMessage is CardOnlineVerificationRequest) { targetFP?.SignalCardOnlineVerification((CardOnlineVerificationRequest)e.CardReaderMessage); } else if (e.CardReaderMessage is HeartBeat) { targetFP?.SignalCardReaderHeartbeat((HeartBeat)e.CardReaderMessage); } } #region Cloud interactions internal RefundResponse SendRefundToCloud(EpsTransactionModel trxModel, DebugLogger debugLogger) { return cloudManager.Refund(trxModel, debugLogger); } internal TrxStatusInquiryResponse SendTrxQueryToCloud(EpsTransactionModel trxModel, DebugLogger debugLogger) { return cloudManager.TrxStatusInquiry(trxModel, debugLogger); } #endregion #region FDC client interactions private void FccClient_NozzleReplaced(int sitewiseNozzleId) { var fp = GetFp(sitewiseNozzleId); fp?.SignalNozzleReplaced(sitewiseNozzleId); } private void FccClient_AuthFailed(int sitewiseNozzleId) { var fp = GetFp(sitewiseNozzleId); fp?.SignalAuthFailed(); } private void FccClient_AuthOk(int sitewiseNozzleId, long? authId) { var fp = GetFp(sitewiseNozzleId); fp?.SignalAuthOk(authId); } private void FccClient_NozzleLifted(int sitewiseNozzleId, IFdcPumpController callingPump) { if (IsNozzleConfiguredOpen(sitewiseNozzleId)) { var fp = GetFp(sitewiseNozzleId); fp?.SignalNozzleLifted(callingPump, sitewiseNozzleId); } else { logger.Info(string.Format($"Physical nozzle# {sitewiseNozzleId} is closed, so we ignore the nozzle lift event!!!")); } } private bool IsNozzleConfiguredOpen(int sitewiseNozzleId) { return !NozzleLockAccessor.IsNozzleClosedInTermsOfSale(sitewiseNozzleId); } private void FccClient_FuelingDone(int sitewiseNozzleId, int seqNum, decimal fuelAmount, decimal fuelingQty, long authId) { var fp = GetFp(sitewiseNozzleId); fp?.SignalFuelingDone(sitewiseNozzleId, seqNum, fuelAmount, fuelingQty, authId); } public void AuthorizePumpAsync(IFdcPumpController callingPump, int sitewiseNozzleId, decimal authAmount) { callingPump.AuthorizeWithAmountAsync((int)((double)authAmount * Math.Pow(10, callingPump.AmountDecimalDigits)), 1); } #endregion private void TeardownFccClient() { //if (fccClient != null) //{ // fccClient.NozzleLifted -= FccClient_NozzleLifted; // fccClient.NozzleReplaced -= FccClient_NozzleReplaced; // fccClient.AuthOk -= FccClient_AuthOk; // fccClient.AuthFailed -= FccClient_AuthFailed; // fccClient.FuelingDone -= FccClient_FuelingDone; //} } public void Shutdown() { TeardownCarplateServer(); TeardownCardReaderHandler(); if (fuelingPoints != null) { foreach (var fp in fuelingPoints) { fp.SignalShutdown(); } } } public WayneChina_IcCardReader_SinoChem.Handler GetHandler(int nozzleId) { logger.Info($"Trying to get card reader handler for fueling point: {nozzleId}"); foreach (var handler in cardReaderHandlers) { if (handler.SupportedNozzles.Contains(nozzleId)) return handler; } return null; } private void TeardownCarplateServer() { if (carPlateServer != null) { carPlateServer.NewCarPlateScanned -= CarPlateServer_NewCarPlateScanned; } } private void TeardownCardReaderHandler() { if (cardReaderHandlers != null) { foreach (var handler in cardReaderHandlers) { handler.OnCardReaderMessageReceived -= Handler_OnCardReaderMessageReceived; } } } public Sinochem_CarPlateRecognizeCamera_HuLianWangJia.Handler CarPlateHandler => carPlateServer; public WayneChina_IcCardReader_SinoChem.Handler GetCardReader(int nozzleId) { return cardReaderHandlers.FirstOrDefault(h => h.SupportedNozzles.Contains(nozzleId)); } public FuelingPoint GetFp(int nozzleId) { foreach (var fp in fuelingPoints) { if (fp.AssociatedNozzles != null && fp.AssociatedNozzles.Contains(nozzleId)) return fp; } return null; } public FuelingPoint GetFuelingPoint(int fpId) { return fuelingPoints?.FirstOrDefault(_ => fpId == _.FuelingPointId); } public int GetNextRequestId() { lock (requestSyncObj) { if (requestId == int.MaxValue) { requestId = 0; } return requestId++; } } public string CreateDisplayTrxCommand(IEnumerable epsTrxList, out int requestId, int i) { Display display; string cmdText = ""; XmlSerializer serializer = new XmlSerializer(typeof(Display)); MemoryStream ms; StreamReader sr; display = new Display(); display.ScreenType = ScreenType.ShowTrxList; display.RequestId = GetNextRequestId(); display.TimeoutSpecified = true; display.Timeout = 300; requestId = display.RequestId; display.PumpInfo = new DisplayPumpInfo { Id = 1, NozzleId = 1 }; int count = epsTrxList.Count(); display.TrxList = new DisplayTrx[epsTrxList.Count()]; for (int index = 0; index < count; index++) { display.TrxList[index] = ConvertEpsTrxModelToDisplayTrx(epsTrxList.ToArray()[index]); } ms = new MemoryStream(); serializer.Serialize(ms, display); ms.Position = 0; sr = new StreamReader(ms, true); cmdText = sr.ReadToEnd(); ms.Close(); sr.Close(); return cmdText; } public DisplayTrx ConvertEpsTrxModelToDisplayTrx(EpsTransactionModel epsTrxModel) { TrxStatus state = TrxStatus.ReadyForFillingStart; if (epsTrxModel.trx_status == EpsTrxStatus.BeforeFueling) state = TrxStatus.ReadyForFillingStart; else if (epsTrxModel.trx_status == EpsTrxStatus.BeforePayment) state = TrxStatus.PendingForPayment; else if (epsTrxModel.trx_status == EpsTrxStatus.Fueling) state = TrxStatus.FillingOngoing; else if (epsTrxModel.trx_status == EpsTrxStatus.FuelingDone) state = TrxStatus.PendingForPayment; else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentOk) state = TrxStatus.Success; else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentFailed) state = TrxStatus.Failed; else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentNeedConfirm) state = TrxStatus.Failed; else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentOkButNeedRefund) state = TrxStatus.Failed; else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentRefunded) state = TrxStatus.Failed; var displayTrx = new DisplayTrx { Id = epsTrxModel.id.ToString(), State = state, TimeStamp = new DisplayTrxTimeStamp { StartTime = epsTrxModel.created_time, FinishTime = string.IsNullOrEmpty(epsTrxModel.xf_time) ? epsTrxModel.created_time : Utilities.CombineDateAndTime(epsTrxModel.xf_date, epsTrxModel.xf_time) }, MemberInfo = new DisplayTrxMemberInfo { Id = epsTrxModel.cardNo_masked, LicensePlateNo = epsTrxModel.car_number }, FillingInfo = new DisplayTrxFillingInfo { PumpId = 1, NozzleId = epsTrxModel.jihao, ProductNo = 1, UnitPrice = (decimal)epsTrxModel.danjia, ProductType = epsTrxModel.youpin, ProductDiscription = "汽油", Amount = (decimal)epsTrxModel.real_pay_amount, Volume = (decimal)epsTrxModel.qty, } }; return displayTrx; } public string CreateDisplayCommand(ScreenType screenType) { Display display; string cmdText = ""; XmlSerializer serializer = new XmlSerializer(typeof(Display)); MemoryStream ms; StreamReader sr; switch (screenType) { case ScreenType.Idle: display = new Display(); display.ScreenType = screenType; display.CompanyContactInfo = new DisplayCompanyContactInfo { Tel = "010-59569575", Address = "世界500强企业\n中国第四大国家石油公司" }; display.StationInfo = new DisplayStationInfo { StationNo = "000001", StationName = "沈阳望花中街加油加气站" }; display.PumpInfo = new DisplayPumpInfo { Id = 1, NozzleId = 1 }; ms = new MemoryStream(); serializer.Serialize(ms, display); ms.Position = 0; sr = new StreamReader(ms, true); cmdText = sr.ReadToEnd(); break; case ScreenType.Welcome: display = new Display(); display.ScreenType = screenType; display.CompanyContactInfo = new DisplayCompanyContactInfo { Tel = "010-59569575", Address = "世界500强企业\n中国第四大国家石油公司" }; display.MemberInfo = new DisplayMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }; display.PumpInfo = new DisplayPumpInfo { Id = 1, NozzleId = 1 }; ms = new MemoryStream(); serializer.Serialize(ms, display); ms.Position = 0; sr = new StreamReader(ms, true); cmdText = sr.ReadToEnd(); break; case ScreenType.ShowTrxList: display = new Display(); display.ScreenType = screenType; display.PumpInfo = new DisplayPumpInfo { Id = 1, NozzleId = 1 }; display.TrxList = new DisplayTrx[] { new DisplayTrx { Id = "100", FillingInfo = new DisplayTrxFillingInfo { Amount = 200, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } }, new DisplayTrx { Id = "101", FillingInfo = new DisplayTrxFillingInfo { Amount = 211, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } }, new DisplayTrx { Id = "102", FillingInfo = new DisplayTrxFillingInfo { Amount = 222, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } }, new DisplayTrx { Id = "103", FillingInfo = new DisplayTrxFillingInfo { Amount = 233, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } }, new DisplayTrx { Id = "104", FillingInfo = new DisplayTrxFillingInfo { Amount = 244, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } } }; ms = new MemoryStream(); serializer.Serialize(ms, display); ms.Position = 0; sr = new StreamReader(ms, true); cmdText = sr.ReadToEnd(); break; case ScreenType.TrxResult: display = new Display(); display.ScreenType = screenType; display.PumpInfo = new DisplayPumpInfo { Id = 1, NozzleId = 1 }; display.TrxList = new DisplayTrx[] { new DisplayTrx { Id = "100", FillingInfo = new DisplayTrxFillingInfo { Amount = 200, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } } }; ms = new MemoryStream(); serializer.Serialize(ms, display); ms.Position = 0; sr = new StreamReader(ms, true); cmdText = sr.ReadToEnd(); break; default: throw new Exception("Unsupported screen type"); } return cmdText; } #region IDisposable Support private bool disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects). if (fuelingPoints != null) { foreach (var fp in fuelingPoints) { fp.Dispose(); } } if (epsTrxCleanupManager != null) { epsTrxCleanupManager.Dispose(); } } // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. // TODO: set large fields to null. disposedValue = true; } } // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. // ~Eps() { // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. // Dispose(false); // } // This code added to correctly implement the disposable pattern. public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); // TODO: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); } #endregion } }