using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Configuration; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading; using Timer = System.Timers.Timer; using System.Collections; using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump; using Wayne.FDCPOSLibrary; using System.Xml; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Edge.Core.Database.Models; using LanTian_Sinopec_PumpIcCardReader.MessageEntity.Outgoing; using Edge.Core.UniversalApi; namespace LanTian_Sinopec_PumpIcCardReader { /// /// PC主动 /// public class Handler : IFdcPumpController, IDeviceHandler { private IServiceProvider services; static ILogger logger = NullLogger.Instance; private LogicalDeviceState lastLogicalDeviceState = LogicalDeviceState.FDC_OFFLINE; private DateTime lastLogicalDeviceStateReceivedTime; // by seconds, change this value need change the correlated deviceOfflineCountdownTimer's interval as well public const int lastLogicalDeviceStateExpiredTime = 6; private List nozzles = new List(); private System.Timers.Timer deviceOfflineCountdownTimer; private bool isOnFdcServerInitCalled = false; private int pumpId; protected IContext context; private byte rotateMsgSeqNo_Seed = 0; /// /// 0<= value <=63 /// private byte NextMsgSeqNo { get { var cur = this.rotateMsgSeqNo_Seed; if (cur > 63) cur = this.rotateMsgSeqNo_Seed = 0; this.rotateMsgSeqNo_Seed++; return cur; } } public event EventHandler OnStateChange; /// /// fired on fueling process is on going, the fuel amount should keep changing. /// public event EventHandler OnCurrentFuellingStatusChange; public IEnumerable Nozzles => this.nozzles; /// /// /// /// the ic card also be treated as a logical pump /// 加液机的通讯终端的逻辑编号POS-P, 通讯终端出厂时POS-P 设为0xFF /// PC 机的地址范围:0xE0~0xF9 /// public Handler(int pumpId, int terminalPhysicalAddress, int fcPhysicalAddress, IServiceProvider services) { this.services = services; var loggerFactory = services.GetRequiredService(); logger = loggerFactory.CreateLogger("PumpHandler"); this.pumpId = pumpId; this.terminalPhysicalAddress = (byte)terminalPhysicalAddress; this.fcPhysicalAddress = (byte)fcPhysicalAddress; //对于这个品牌,一个IC卡键盘仅可控制一把枪 this.nozzles.Add(new LogicalNozzle(pumpId, (byte)terminalPhysicalAddress, 1, null)); } public void Init(IContext context) { this.context = context; var timeWindowWithActivePollingOutgoing = this.context.Outgoing as TimeWindowWithActivePollingOutgoing; timeWindowWithActivePollingOutgoing.PollingMsgProducer = () => { return new PcGenericInquiryRequest() { SourceAddress = this.fcPhysicalAddress, TargetAddress = this.terminalPhysicalAddress, MessageSequenceNumber = this.NextMsgSeqNo, PC_TIME = DateTime.Now, }; }; } public virtual async Task Process(IContext context) { this.context = context; this.lastLogicalDeviceStateReceivedTime = DateTime.Now; if (this.lastLogicalDeviceState == LogicalDeviceState.FDC_OFFLINE) { logger.LogInformation("Pump: " + this.pumpId + ", " + "Recevied an IcCard Msg in FDC_OFFLINE state, " + "indicates the underlying connection is established, switch to FDC_READY"); this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg(LogicalDeviceState.FDC_READY)); } if (context.Incoming.Message is PumpRealTimeStateEvent pumpRealTimeStateResponse) { if (pumpRealTimeStateResponse.NozzleOperatingStates == null) { if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_READY) { this.lastLogicalDeviceState = LogicalDeviceState.FDC_READY; this.OnStateChange?.Invoke(this, new FdcPumpControllerOnStateChangeEventArg( LogicalDeviceState.FDC_READY, this.nozzles.FirstOrDefault())); } } if (pumpRealTimeStateResponse.NozzleOperatingStates != null && pumpRealTimeStateResponse.NozzleOperatingStates.Any(n => n.St状态字 == NozzleOperatingState.PumpStateChangeCode.抬枪或加油中)) { this.nozzles.First().RealPriceOnPhysicalPump = pumpRealTimeStateResponse.NozzleOperatingStates.First().PRC价格; if (this.lastLogicalDeviceState != LogicalDeviceState.FDC_FUELLING) { this.lastLogicalDeviceState = LogicalDeviceState.FDC_FUELLING; this.OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction() { Nozzle = this.nozzles.First(), Amount = pumpRealTimeStateResponse.NozzleOperatingStates.First().AMN数额, Volumn = pumpRealTimeStateResponse.NozzleOperatingStates.First().VOL升数, Price = pumpRealTimeStateResponse.NozzleOperatingStates.First().PRC价格, Finished = false, })); } } } else if (context.Incoming.Message is PumpNotifyTransactionDoneRequest pumpNotifyTransactionDoneRequest) { this.nozzles.First().RealPriceOnPhysicalPump = pumpNotifyTransactionDoneRequest.PRC_成交价格; this.OnCurrentFuellingStatusChange?.Invoke(this, new FdcTransactionDoneEventArg(new FdcTransaction() { Nozzle = this.nozzles.First(), Amount = pumpNotifyTransactionDoneRequest.AMN数额, Volumn = pumpNotifyTransactionDoneRequest.VOL_升数, Price = pumpNotifyTransactionDoneRequest.PRC_成交价格, SequenceNumberGeneratedOnPhysicalPump = pumpNotifyTransactionDoneRequest.POS_TTC, //AmountTotalizer = , VolumeTotalizer = pumpNotifyTransactionDoneRequest.V_TOT_升累计, Finished = true, })); #warning should send a request to external module to validate the T-MAC this.context.Outgoing.Write(new PumpNotifyTransactionDoneResponse() { SourceAddress = this.fcPhysicalAddress, TargetAddress = this.terminalPhysicalAddress, MessageSequenceNumber = this.NextMsgSeqNo, Result = PumpNotifyTransactionDoneResponse.ResultEnum.正确 }); } else if (context.Incoming.Message is PumpAskDataDownloadRequest pumpAskDataDownloadRequest) { #warning should query cloud side to get the BL_LEN_长度 this.context.Outgoing.Write(new PumpAskDataDownloadResponse() { SourceAddress = this.fcPhysicalAddress, TargetAddress = this.terminalPhysicalAddress, MessageSequenceNumber = this.NextMsgSeqNo, BL_LEN_长度 = 0, Content_内容 = pumpAskDataDownloadRequest.Content_内容, }); } else if (context.Incoming.Message is PumpStartDataDownloadRequest pumpStartDataDownloadRequest) { #warning should query cloud side to get the real data for downloading to pump this.context.Outgoing.Write(new PumpStartDataDownloadResponse() { SourceAddress = this.fcPhysicalAddress, TargetAddress = this.terminalPhysicalAddress, MessageSequenceNumber = this.NextMsgSeqNo, Content_内容 = pumpStartDataDownloadRequest.Content_内容, S_OFFSET_段偏移 = pumpStartDataDownloadRequest.S_OFFSET_段偏移, SEG_段数_segs = 0, DATA_Content_数据内容 = new List() { 0x00 } }); } else if (context.Incoming.Message is PumpInquiryGrayCardRecordRequest pumpInquiryGrayCardRecordRequest) { #warning should query cloud side to get the real data this.context.Outgoing.Write(new PumpInquiryGrayCardRecordResponse() { SourceAddress = this.fcPhysicalAddress, TargetAddress = this.terminalPhysicalAddress, MessageSequenceNumber = this.NextMsgSeqNo, M_FLAG_匹配标志 = PumpInquiryGrayCardRecordResponse.PumpInquiryGrayCardRecordResult.匹配, ASN卡应用号 = pumpInquiryGrayCardRecordRequest.ASN卡应用号, BAL_余额 = 999911, AMN_交易额 = 183, AMN_优惠价 = 123, AMN_折扣 = 45, AMN_优惠后金额 = 97, CTC_交易计数器 = 765, DS_扣款来源 = 0, TIME_日期及时间 = DateTime.Now, GMAC电子签名 = 23192394, PSAM_TID_PSAM编号 = new List() { 0x01, 0x02 }, PSAM_TTC = 654 }); } else if (context.Incoming.Message is PumpInquiryCreditCardRecordRequest pumpInquiryCreditCardRecordRequest) { #warning should query cloud side to get the real data this.context.Outgoing.Write(new PumpInquiryCreditCardRecordResponse() { SourceAddress = this.fcPhysicalAddress, TargetAddress = this.terminalPhysicalAddress, MessageSequenceNumber = this.NextMsgSeqNo, S_DATA_记帐卡状态 = PumpInquiryCreditCardRecordResponse.CardStateEnum.可以加油_无里程限制, }); } else if (context.Incoming.Message is PumpInquiryBlackAndWhiteListRequest pumpInquiryBlackAndWhiteListRequest) { #warning should query cloud side to get the real data this.context.Outgoing.Write(new PumpInquiryBlackAndWhiteListResponse() { SourceAddress = this.fcPhysicalAddress, TargetAddress = this.terminalPhysicalAddress, MessageSequenceNumber = this.NextMsgSeqNo, M_FLAG_匹配标志 = PumpInquiryBlackAndWhiteListResponse.ResultEnum.不匹配, ASN卡应用号 = pumpInquiryBlackAndWhiteListRequest.ASN卡应用号, }); } else if (context.Incoming.Message is PumpInternalErrorRequest pumpInternalErrorRequest) { #warning may need notify someone that error happened?? this.context.Outgoing.Write(new PumpInternalErrorResponse() { SourceAddress = this.fcPhysicalAddress, TargetAddress = this.terminalPhysicalAddress, MessageSequenceNumber = this.NextMsgSeqNo, COMMAND_通讯的命令字 = 0, FLAG_成功标志 = 0, }); } else { if (logger.IsEnabled(LogLevel.Debug)) logger.LogDebug("Incoming an unknown message, will do nothing and ignore."); } } public string Name => this.GetType().FullName; public Guid Id => new Guid(); /// /// Gets the Identification of the pump for the system. Is the logical number of the pump /// public int PumpId => this.pumpId; /// /// PC 机的地址范围:0xE0~0xF9 /// private byte fcPhysicalAddress; /// /// 加液机的通讯终端的逻辑编号POS-P, 通讯终端出厂时POS-P 设为0xFF /// private byte terminalPhysicalAddress; public int PumpPhysicalId => this.terminalPhysicalAddress; public int AmountDecimalDigits => 2; public int VolumeDecimalDigits => 2; public int PriceDecimalDigits => 2; public int VolumeTotalizerDecimalDigits => 2; [UniversalApi] public virtual async Task QueryStatusAsync() { return this.lastLogicalDeviceState; } /// /// /// /// MoneyTotalizer:VolumnTotalizer [UniversalApi] public async Task> QueryTotalizerAsync(byte logicalNozzleId) { var response = await this.context.Outgoing.WriteAsync(new PcReadPumpAccumulatorRequest() { SourceAddress = this.fcPhysicalAddress, TargetAddress = this.terminalPhysicalAddress, MessageSequenceNumber = this.NextMsgSeqNo, }, (_, testResponse) => testResponse is PcReadPumpAccumulatorResponse, 4000) as PcReadPumpAccumulatorResponse; if (response == null) return new Tuple(-1, -1); var volTotalizerValue = response?.MZN_单个油枪.FirstOrDefault()?.V_TOT_升累计; return new Tuple(-1, volTotalizerValue ?? -1); } [UniversalApi] public async Task PC机读取加液机信息Async() { var response = await this.context.Outgoing.WriteAsync(new PcReadPumpInfoRequest() { SourceAddress = this.fcPhysicalAddress, TargetAddress = this.terminalPhysicalAddress, MessageSequenceNumber = this.NextMsgSeqNo, }, (_, testResponse) => testResponse is PcReadPumpInfoResponse, 4000) as PcReadPumpInfoResponse; return response; } [UniversalApi] public async Task PC机主动读取加液记录Async(int pos_TTC_终端交易号) { var response = await this.context.Outgoing.WriteAsync(new PcReadTransactionRequest(pos_TTC_终端交易号) { SourceAddress = this.fcPhysicalAddress, TargetAddress = this.terminalPhysicalAddress, MessageSequenceNumber = this.NextMsgSeqNo, }, (_, testResponse) => testResponse is PumpNotifyTransactionDoneRequest || testResponse is PcReadTransactionButDoesNotFindResponse, 4000); return response; } public virtual async Task ChangeFuelPriceAsync(int newPriceWithoutDecimalPoint, byte logicalNozzleId) { throw new NotImplementedException(); } /// /// /// /// useless for this type of pump, it always one pump one nozzle /// public virtual async Task AuthorizeAsync(byte logicalNozzleId) { throw new NotImplementedException(); } /// /// /// /// /// useless for this type of pump, it always one pump one nozzle /// public virtual async Task AuthorizeWithAmountAsync(int moneyAmountWithoutDecimalPoint, byte logicalNozzleId) { throw new NotImplementedException(); } /// /// /// /// /// useless for this type of pump, it always one pump one nozzle /// public virtual async Task AuthorizeWithVolumeAsync(int volumnWithoutDecimalPoint, byte logicalNozzleId) { throw new NotImplementedException(); } public virtual async Task FuelingRoundUpByAmountAsync(int amount) { throw new NotImplementedException(); } #region not implemented public async Task UnAuthorizeAsync(byte logicalNozzleId) { throw new NotImplementedException(); } public async Task SuspendFuellingAsync() { throw new NotImplementedException(); } public async Task ResumeFuellingAsync() { throw new NotImplementedException(); } public async Task FuelingRoundUpByVolumeAsync(int volume) { throw new NotImplementedException(); } #endregion /// /// protected Dictionary logicalNozzleIdToLastFuelSaleTrxMapping = new Dictionary(); public void OnFdcServerInit(Dictionary parameters) { if (parameters.ContainsKey("LastPriceChange")) { } /* Load Last sale(from db) for void the case of FC accidently disconnect from Pump in fueling, and may cause a fueling trx gone from FC control */ if (parameters.ContainsKey("LastFuelSaleTrx")) { // nozzle logical id:lastSale //var lastFuelSaleTrxes = parameters["LastFuelSaleTrx"] as Dictionary; //foreach (var lastFuelSaleTrx in lastFuelSaleTrxes) //{ // logger.Info("Pump: " + this.pumpId + ", OnFdcServerInit, load last fuel sale " + // "on logical nozzle: " + lastFuelSaleTrx.Key + " with value: " + lastFuelSaleTrx.Value); // this.logicalNozzleIdToLastFuelSaleTrxMapping.Remove(lastFuelSaleTrx.Key); // this.logicalNozzleIdToLastFuelSaleTrxMapping.Add(lastFuelSaleTrx.Key, lastFuelSaleTrx.Value); //} } } public async Task LockNozzleAsync(byte logicalNozzleId) { return false; } public async Task UnlockNozzleAsync(byte logicalNozzleId) { return false; } } }