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;
}
}
}