using Edge.Core.Configuration; using Edge.Core.IndustryStandardInterface.Pump; using Edge.Core.Parser.BinaryParser.Util; using Edge.Core.Processor; using Edge.Core.Processor.Communicator; using Edge.Core.Processor.Dispatcher.Attributes; using HengShan_Pump_TQC_IFSF.MessageEntity; using HengShan_Pump_TQC_IFSF.MessageEntity.Incoming; using HengShan_Pump_TQC_IFSF.MessageEntity.Outgoing; using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using Wayne.FDCPOSLibrary; using Timer = System.Timers.Timer; namespace HengShan_Pump_TQC_IFSF { /// /// DIT ifsf pump has some different with TQC ifsf. /// 1. DIT support multiple ifsf node(one fp is a node) in single communicator, thus in a pumpGroup, multiple pumps could have different ifsf node value. /// 2. DIT pump's fuel product code is specified in a pump side. /// further more, there're 2 types of DIT, one can change code by web page, another one, looks pretty much the same as /// TCP IFSF TQC, could not support web page config, then have to do it at the dispenser keyboard, the FCC could not change it, /// sometimes, though the request and response can go through, actually no change in pump side. /// thus, need align FCC's product config before run FCC. /// [MetaPartsRequired(typeof(GenericDeviceProcessor<,>))] [MetaPartsRequired(typeof(HengShan_TQC_IFsfMessageTcpIpCommunicator<>))] [MetaPartsRequired(typeof(IfsfMessageBase))] [MetaPartsDescriptor( "lang-zh-cn:HS DIT TQC加油机lang-en-us:HS DIT TQC pump", "lang-zh-cn:用于驱动基于TCP连接的IFSF协议的 恒山-DIT 加油机lang-en-us:Used for driven HengShan-DIT dispensers which using IFSF protocol on TCP", new[] { "lang-zh-cn:加油机lang-en-us:Pump" })] public class DitPumpGroupHandler : IEnumerable, IDeviceHandler { #region Ctor parameters public class PumpGroupConfiguration { //public byte AmountDecimalDigits { get; set; } //public byte VolumeDecimalDigits { get; set; } //public byte PriceDecimalDigits { get; set; } //public byte VolumeTotalizerDecimalDigits { get; set; } public byte FccIfsfSubnetValue { get; set; } public byte FccIfsfNodeValue { get; set; } /// /// all pumps in this group share one SubNet value /// public byte PumpGroupIfsfSubnetValue { get; set; } public List PumpConfigurations { get; set; } } public class PumpConfiguration { public byte PumpIfsfNodeValue { get; set; } public byte PumpId { get; set; } /// /// 0x21 - 0x24 /// public byte PhysicalId { get; set; } public List NozzleConfigurations { get; set; } } public class NozzleConfiguration { public byte LogicalId { get; set; } /// /// 0x11 - 0x18 /// public byte PhysicalId { get; set; } } #endregion private object syncObject = new object(); private Guid uniqueId = Guid.NewGuid(); private IEnumerable nozzleExtraInfos; /// /// Each ifsf node device need a standalone initializer. /// internal IEnumerable tqcPumpGroupInitializers; //protected static ILog logger = log4net.LogManager.GetLogger("PumpHandler"); protected static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("PumpHandler"); protected IContext context; protected List pumpHandlers = new List(); /// /// the pump side ifsf Subnet value. /// protected byte recipientSubnet = 1; /// /// the pump side ifsf Node values under this PumpGroup. /// protected List recipientNodes = new List(); /// /// ifsf subnet value for FCC. /// public static byte originatorSubnet = 2; /// /// Ifsf node value for FCC. /// public static byte originatorNode = 1; [ParamsJsonSchemas("DitPumpGroupHandlerCtorParamsJsonSchemas")] public DitPumpGroupHandler(PumpGroupConfiguration pumpGroupConfiguration, IServiceProvider services) { originatorSubnet = pumpGroupConfiguration.FccIfsfSubnetValue; originatorNode = pumpGroupConfiguration.FccIfsfNodeValue; this.recipientSubnet = pumpGroupConfiguration.PumpGroupIfsfSubnetValue; this.recipientNodes.AddRange(pumpGroupConfiguration.PumpConfigurations.Select(pc => pc.PumpIfsfNodeValue)); logger.Info("node " + this.recipientNodes.Select(n => n.ToString()).Aggregate((acc, n) => acc + ", " + n) + ", Will create " + pumpGroupConfiguration.PumpConfigurations.Count + " pump handlers for this TQC connection according from pumpGroupConfiguration"); this.CreatePumpHandlers(pumpGroupConfiguration); } /// /// /// /// public DitPumpGroupHandler(int fccIfsfSubnetValue, int fccIfsfNodeValue, string pumpsXmlConfiguration) { originatorSubnet = (byte)fccIfsfSubnetValue; originatorNode = (byte)fccIfsfNodeValue; // sample, a PumpGroup has a shared(for all pumps) ifsfSubNet value, each pump has an ifsfNode value // // // // // // // // // // // // // // // // // // // // // // // // // // XmlDocument xmlDocument = new XmlDocument(); xmlDocument.LoadXml(pumpsXmlConfiguration); var pumpGroupElement = xmlDocument.GetElementsByTagName("PumpGroup").Cast().First(); this.recipientSubnet = byte.Parse(pumpGroupElement.Attributes["ifsfSubNet"].Value); var pumpElements = xmlDocument.GetElementsByTagName("Pump"); foreach (var pumpXmlNode in pumpElements.Cast()) if (pumpXmlNode.Attributes["ifsfNode"] != null) { this.recipientNodes.Add(byte.Parse(pumpXmlNode.Attributes["ifsfNode"].Value)); } else throw new ArgumentNullException("Must specify attribute: 'ifsfNode' for each pump XmlNode"); logger.Info("node " + this.recipientNodes.Select(n => n.ToString()).Aggregate((acc, n) => acc + ", " + n) + ", Will create " + pumpElements.Count + " pump handlers for this TQC connection according from local config"); this.CreatePumpHandlers(pumpElements); } protected virtual void CreatePumpHandlers(XmlNodeList pumpElements) { foreach (XmlNode pumpElement in pumpElements) { byte pumpPhysicalIdValue; var rawPumpPhysicalId = pumpElement.Attributes["physicalId"].Value;//.Substring(2); if (rawPumpPhysicalId.StartsWith("0x") || rawPumpPhysicalId.StartsWith("0X")) pumpPhysicalIdValue = byte.Parse(pumpElement.Attributes["physicalId"].Value.Substring(2), NumberStyles.HexNumber); else pumpPhysicalIdValue = byte.Parse(pumpElement.Attributes["physicalId"].Value); if (pumpPhysicalIdValue < 0x21 || pumpPhysicalIdValue > 0x24) throw new ArgumentException("ifsf fuel point id must be range from 0x21 to 0x24, while the configuration passed in " + rawPumpPhysicalId); DitPumpHandler pumpHandler = new DitPumpHandler(this, int.Parse(pumpElement.Attributes["pumpId"].Value), pumpPhysicalIdValue, this.recipientSubnet, byte.Parse(pumpElement.Attributes["ifsfNode"].Value), pumpElement.InnerXml, GetNewMessageToken); this.pumpHandlers.Add(pumpHandler); } } protected virtual void CreatePumpHandlers(PumpGroupConfiguration pumpGroupConfiguration) { foreach (var pumpConfig in pumpGroupConfiguration.PumpConfigurations) { DitPumpHandler pumpHandler = new DitPumpHandler(this, pumpConfig.PumpId, pumpConfig.PhysicalId, this.recipientSubnet, pumpConfig.PumpIfsfNodeValue, pumpConfig.NozzleConfigurations, GetNewMessageToken); this.pumpHandlers.Add(pumpHandler); } } /// /// will be called at the Init stage of FdcServerApp, that means before the calling the Start() for all the Processors. /// /// public void OnFdcServerInit(Dictionary parameters) { if (parameters != null && parameters.TryGetValue("NozzleProductMapping", out object param)) { this.nozzleExtraInfos = param as IEnumerable; } this.tqcPumpGroupInitializers = this.recipientNodes.Select(rcpNodeValue => { var phs = this.pumpHandlers.Where(ph => ph.recipientNode == rcpNodeValue); logger.Info("Creating a TqcPumpGroupInitializer for node: " + rcpNodeValue + ", managed pump Id(s): " + phs.Select(ph => ph.PumpId.ToString()).Aggregate((acc, n) => acc + ", " + n)); var initor = new TqcPumpGroupInitializerSimple(this.nozzleExtraInfos, originatorSubnet, originatorNode, this.recipientSubnet, rcpNodeValue, phs, this.GetNewMessageToken); initor.OnInitTimeout += (cc, dd) => { }; initor.OnInitDone += (ee, ff) => { }; //logger.Info(" created a TqcPumpGroupInitializer for node: " + rcpNodeValue); return initor; }).ToList(); this.pumpHandlers.ForEach(ph => ph.OnFdcServerInit(parameters)); } public virtual void Init(IContext context) { this.context = context; bool isCommConnected = false; this.context.Communicator.OnConnected += async (e, f) => { var comm = context.Communicator as HengShan_TQC_IFsfMessageTcpIpCommunicator; logger.Info("TqcPumpGroupInitializer, Communicator Connected(to pump server: " + (comm?.pumpServerTcpListeningIpAddress ?? "") + "), will start init each Initializer..."); isCommConnected = true; foreach (var initor in this.tqcPumpGroupInitializers) { while (isCommConnected) { initor.Reset(); if (initor.CurrentStatus == TqcPumpGroupInitializerSimple.Status.UnInit) initor.FeedIn(this.context); else logger.Info("TqcPumpGroupInitializer, node " + initor.ifsfRecipientNode + " is already in a initing process, just wait for finish..."); // may refine in future for wait the init Done event. var initResult = await initor.InitDoneTaskCompletionSource.Task; if (initResult) { logger.Info("TqcPumpGroupInitializer, node " + initor.ifsfRecipientNode + ", init done, will query all FP status and routed following msg to PumpHandlers"); // query all FP status, answer will be routed to each PumpHandler to handle. this.context.Outgoing.Write( new FuellingPointDbRequest_Read_FuelPointState( this.recipientSubnet, initor.ifsfRecipientNode, originatorSubnet, originatorNode, this.GetNewMessageToken(), 0x20)); //var nozzleProductConfig // = Configurator.Default.NozzleExtraInfoConfiguration.Mapping; initor.pumpHandlers.SelectMany(p => p.Nozzles).ToList().ForEach(n => { var boundProductNo = this.nozzleExtraInfos.First(c => c.PumpId == n.PumpId && c.NozzleLogicalId == n.LogicalId).ProductBarcode; logger.Info("TqcPumpGroupInitializer, node " + initor.ifsfRecipientNode + ", will write back price to nozzle on pump " + n.PumpId + ", logicalNozzleId: " + n.LogicalId + ", boundProductNo: " + boundProductNo + " to new Init Price(without decimal points): " + initor.PriceBook[boundProductNo]); n.RealPriceOnPhysicalPump = initor.PriceBook[boundProductNo]; }); break; } else { logger.Info("TqcPumpGroupInitializer, node " + initor.ifsfRecipientNode + ", init timed out, will start it over..."); //initor.Reset(); //initor.FeedIn(this.context); } } } }; this.context.Communicator.OnDisconnected += (e, f) => { var comm = context.Communicator as HengShan_TQC_IFsfMessageTcpIpCommunicator; logger.Info("TqcPumpGroupInitializer, Communicator Disconnected(to pump server: " + (comm?.pumpServerTcpListeningIpAddress ?? "") + ")..."); foreach (var initor in this.tqcPumpGroupInitializers) { initor.Reset(); isCommConnected = false; initor.pumpHandlers.ToList().ForEach(p => p.HandleFpStatusChange(FuellingPointStatus.Inoperative, null)); } }; this.pumpHandlers.ForEach(p => p.Init(this.context)); } private byte rotateMsgToken = 0; public byte GetNewMessageToken() { if (rotateMsgToken == (0xFF & 0x01F)) rotateMsgToken = 0; else rotateMsgToken++; return rotateMsgToken; } public virtual Task Process(IContext context) { this.context = context; var initor = this.tqcPumpGroupInitializers?.FirstOrDefault(i => i.ifsfRecipientNode == context.Incoming.Message.OriginatorNode); if (initor == null) { // must has initor created first return Task.CompletedTask; } if (!initor.IsInitDone) { initor.FeedIn(context); return Task.CompletedTask; } this.RouteMessageToHandlers(context); return Task.CompletedTask; } protected virtual void RouteMessageToHandlers(IContext context) { switch (context.Incoming.Message) { case FuellingPointDb_FpStatus_Event fpStatusEvent: this.pumpHandlers.Where(h => h.PumpPhysicalId == fpStatusEvent.TargetFuelPointId && h.recipientNode == fpStatusEvent.OriginatorNode).ToList().ForEach(p => p.Process(context)); break; case FuellingPointDb_FpStatus_Answer fpStatusAnswer: this.pumpHandlers.Where(h => h.PumpPhysicalId == fpStatusAnswer.TargetFuelPointId && h.recipientNode == fpStatusAnswer.OriginatorNode).ToList().ForEach(p => p.Process(context)); break; case FuellingPointDb_FpRunningTransaction_Event fpRunningTrxEvent: this.pumpHandlers.Where(h => h.PumpPhysicalId == fpRunningTrxEvent.TargetFuelPointId && h.recipientNode == fpRunningTrxEvent.OriginatorNode).ToList().ForEach(p => p.Process(context)); break; case FuellingPointDb_FpRunningTransaction_Event_ACK fpRunningTrxEvent: this.pumpHandlers.Where(h => h.PumpPhysicalId == fpRunningTrxEvent.TargetFuelPointId && h.recipientNode == fpRunningTrxEvent.OriginatorNode).ToList().ForEach(p => p.Process(context)); break; case FuellingTrxDb_TransactionBufferStatus_Event trxBufferStatusEvent: this.pumpHandlers.Where(h => h.PumpPhysicalId == trxBufferStatusEvent.TargetFuelPointId && h.recipientNode == trxBufferStatusEvent.OriginatorNode).ToList().ForEach(p => p.Process(context)); break; case FuellingTrxDb_TransactionBufferStatus_Event_ACK trxBufferStatusEvent: this.pumpHandlers.Where(h => h.PumpPhysicalId == trxBufferStatusEvent.TargetFuelPointId && h.recipientNode == trxBufferStatusEvent.OriginatorNode).ToList().ForEach(p => p.Process(context)); break; case FuellingTrxDb_TransactionBufferStatus_Answer trxBufferStatusAnswer: this.pumpHandlers.Where(h => h.PumpPhysicalId == trxBufferStatusAnswer.TargetFuelPointId && h.recipientNode == trxBufferStatusAnswer.OriginatorNode).ToList().ForEach(p => p.Process(context)); break; //default: this.pumpHandlers.ForEach(p => p.Process(context)); break; } } public IEnumerator GetEnumerator() { return this.pumpHandlers.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.pumpHandlers.GetEnumerator(); } public Guid Id => this.uniqueId; internal class TqcPumpGroupInitializerSimple { System.Timers.Timer initTimeoutTimer = new Timer(initTimeout); const int initTimeout = 10000; /// /// will fire when reached timed out time, and state is still not Done. /// public event EventHandler OnInitTimeout; /// /// false indicates init timed out, true indicates init done successfully. /// public TaskCompletionSource InitDoneTaskCompletionSource = new TaskCompletionSource(); public event EventHandler OnInitDone; /// /// init process will firstly read all necessary config values in TQC, and then execute the write operations. /// this event will be fired once those config values were read, and write operation is still not perform. /// public event EventHandler OnTqcExistedConfigRead; private List fpStatus_Answers = new List(); private List logicalNozzleDb_Nozzle_ProductInfo_PhyId_Answers = new List(); /// /// Gets pre-config value in TQC pump. /// public IEnumerable LogicalNozzleDb_Nozzle_ProductInfo_PhyId_Answers => this.logicalNozzleDb_Nozzle_ProductInfo_PhyId_Answers; private List productDb_ProductNo_Answers = new List(); /// /// Gets pre-config value in TQC pump. /// public IEnumerable ProductDb_ProductNo_Answers => this.productDb_ProductNo_Answers; private List productPerFuellingModeDb_ProductPrice_Answers = new List(); /// /// Gets pre-config value in TQC pump. /// public IEnumerable ProductPerFuellingModeDb_ProductPrice_Answers => this.productPerFuellingModeDb_ProductPrice_Answers; private Dictionary priceBook = new Dictionary(); /// /// Gets or set the init price for each product. /// default price init policy is read from pricebook, if found then apply, otherwise use pre-config value read from pump. /// should follow-> barcode:rawFormatPriceWithoutDecimalPoints /// public Dictionary PriceBook => this.priceBook; private byte ifsfSelfSubnet; private byte ifsfSelfNode; private byte ifsfRecipientSubnet; public byte ifsfRecipientNode; public IEnumerable pumpHandlers; Func msgTokenGenerator; private List thisTqcProductBarcodes; private Status currentStatus; public Status CurrentStatus { get { return this.currentStatus; } private set { //logger.Debug("PumpInitializer, node " + ifsfRecipientNode + ", Status switched from " + this.currentStatus + " to " + value); this.currentStatus = value; } } //private DateTime previousRequestingTime; private byte? pendingForAckMsgToken; public enum Status { UnInit = 0, Wait_Answer_Query_All_FuelPointState, Wait_Answer_Close_All_FuelPoint, Wait_Answer_Query_Caculator_Overall_Info, Wait_Answer_Read_All_Nozzle_ProductInfo_PhyId, Wait_Answer_Read_All_ProductNumber, Wait_Answer_Read_All_ProductPrice, //Sending_Add_Recipient_Addr, Wait_ACK_Add_Recipient_Addr, //Sending_Clear_ProductNumber_In_Caculator, Wait_ACK_Clear_ProductNumber_In_ProductDb, //Sending_Set_ProductNumber_In_Caculator, Wait_ACK_Set_ProductNumber_In_ProductDb, //Sending_Link_Meter_To_CaculatorSlot, Wait_ACK_Link_Meter_To_ProductDb_ProductNoSlot, //Sending_Set_AUTH_MODE, Wait_ACK_Set_AUTH_MODE, //Sending_Set_Price, Wait_ACK_Set_Price, //Sending_Set_Nozzle_To_CacalatorSlot, Wait_ACK_Set_Nozzle_To_ProductDb_ProductNoSlot, //Sending_Set_FP_Default_FuellingMode, Wait_ACK_Set_FP_Default_FuellingMode, //Send_Open_FP, //Wait_ACK_Open_FP, Done } /// /// /// /// fc ifsf subnet value /// fc ifsf node value /// remote ifsf recipient subnet value /// remote ifsf recipient node value /// pump handlers created from the pump Group handler /// public TqcPumpGroupInitializerSimple(IEnumerable nozzleExtraInfos, byte selfSubnet, byte selfNode, byte ifsfRecipientSubnet, byte ifsfRecipientNode, IEnumerable pumpHandlers, Func msgTokenGenerator) { this.ifsfSelfSubnet = selfSubnet; this.ifsfSelfNode = selfNode; this.ifsfRecipientSubnet = ifsfRecipientSubnet; this.ifsfRecipientNode = ifsfRecipientNode; this.pumpHandlers = pumpHandlers; this.msgTokenGenerator = msgTokenGenerator; this.thisTqcProductBarcodes = pumpHandlers.SelectMany(ph => ph.Nozzles) .Select(n => nozzleExtraInfos.FirstOrDefault(npc => npc.PumpId == n.PumpId && npc.NozzleLogicalId == n.LogicalId)?.ProductBarcode) .Where(n => n != null).Select(n => n.Value).Distinct().OrderBy(o => o).ToList(); //this.thisTqcProductBarcodes = nozzleProductConfig.Select(s => s.ProductBarcode).Distinct().OrderBy(o => o).ToList(); logger.Info("PumpInitializer, node " + ifsfRecipientNode + " current TQC local configuration indicates total have " + this.thisTqcProductBarcodes.Count + " product barcodes: " + this.thisTqcProductBarcodes.Select(s => s.ToString()).Aggregate((acc, n) => acc + ", " + n)); this.initTimeoutTimer.Elapsed += (a, b) => { this.initTimeoutTimer.Stop(); if (!this.IsInitDone) { this.InitDoneTaskCompletionSource.SetResult(false); var safe = this.OnInitTimeout; safe(this, null); } }; } private byte next_ActualInPump_ProductNumber_In_ProductDb_ProductNoSlot_Index = 1; private byte next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index = 1; //private byte next_Link_Meter_to_ProductNumber_Index = 1; private byte next_site_nozzle_Index = 1; private byte next_Fp_Index = 1; /// /// if it was Done, return false. otherwise, the init is kicking off, return true. /// /// /// public bool FeedIn(IContext context) { var comm = context.Communicator as HengShan_TQC_IFsfMessageTcpIpCommunicator; if (!this.initTimeoutTimer.Enabled) this.initTimeoutTimer.Start(); if (IsInitDone) return false; switch (this.CurrentStatus) { case Status.UnInit: { logger.Info("PumpInitializerSimple, node " + ifsfRecipientNode + " start Init(to pump server: " + (comm?.pumpServerTcpListeningIpAddress ?? "") + "), firstly Query_Caculator_Overall_Info..."); this.next_Fp_Index = 1; logger.Info("PumpInitializer, node " + ifsfRecipientNode + " sending CaculatorDbRequest_Read_FuelPoint_Product_FuelMode_Meter_Info..."); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new CaculatorDbRequest_Read_FuelPoint_Product_FuelMode_Meter_Info(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value )); this.CurrentStatus = Status.Wait_Answer_Query_Caculator_Overall_Info; break; } case Status.Wait_Answer_Query_Caculator_Overall_Info: { if (context.Incoming.Message is IfsfMessage ifsfMsg && ifsfMsg.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER && ifsfMsg.MessageToken == this.pendingForAckMsgToken) { var dataParser = DatabaseDataParser.New().Convert(ifsfMsg.RawDatabaseData.ToArray()); var numberOfProducts = dataParser.DataIds.First(i => i.Item1 == 0x02).Item2.ToInt32(); logger.Info("PumpInitializer, node " + ifsfRecipientNode + " CaculatorDbRequest_Read_FuelPoint_Product_FuelMode_Meter_Info Acked,\r\n" + "Read caculatorDbOverallInfo, No_Products: " + numberOfProducts + ", No_Fuelling_Modes: " + dataParser.DataIds.First(i => i.Item1 == 0x03).Item2.ToInt32() + ", No_Meters: " + (dataParser.DataIds.FirstOrDefault(i => i.Item1 == 0x04)?.Item2?.ToInt32() ?? -1) + ", No_FP: " + dataParser.DataIds.First(i => i.Item1 == 0x05).Item2.ToInt32() + ", Auth_State_Mode: " + dataParser.DataIds.First(i => i.Item1 == 0x0B).Item2.ToInt32()); if (numberOfProducts != this.thisTqcProductBarcodes.Count) { logger.Info("!!!!!!PumpInitializer, node " + ifsfRecipientNode + " This TQC MUST config " + numberOfProducts + " products, but now trying to load local config with " + this.thisTqcProductBarcodes.Count + " products"); } logger.Info("PumpInitializer, node " + ifsfRecipientNode + " Send LogicalNozzleDb_Read_Nozzle_ProductInfo_PhyId for FP: 0x" + pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId.ToHexLogString()); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new LogicalNozzleDb_Read_Nozzle_ProductInfo_PhyId(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, (byte)(pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId), 0x10)); this.CurrentStatus = Status.Wait_Answer_Read_All_Nozzle_ProductInfo_PhyId; } break; } case Status.Wait_Answer_Read_All_Nozzle_ProductInfo_PhyId: { if (context.Incoming.Message is IfsfMessage ifsfMsg && ifsfMsg.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER && ifsfMsg.MessageToken == this.pendingForAckMsgToken.Value) { var historyIncoming = context.Incoming as HistoryKeepIncoming; this.logicalNozzleDb_Nozzle_ProductInfo_PhyId_Answers.AddRange( historyIncoming.History.Where(h => (h.Item1 is LogicalNozzleDb_Nozzle_ProductInfo_PhyId_Answer im) && im.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER && im.MessageToken == this.pendingForAckMsgToken) .Select(s => s.Item1 as LogicalNozzleDb_Nozzle_ProductInfo_PhyId_Answer).OrderBy(a => a.LogicalNozzleId)); if (++next_Fp_Index <= pumpHandlers.Count()) { logger.Info("PumpInitializer, node " + ifsfRecipientNode + " LogicalNozzleDb_Read_Nozzle_ProductInfo_PhyId Acked"); logger.Info("PumpInitializer, node " + ifsfRecipientNode + " Send LogicalNozzleDb_Read_Nozzle_ProductInfo_PhyId for FP: 0x" + pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId.ToHexLogString()); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new LogicalNozzleDb_Read_Nozzle_ProductInfo_PhyId(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, (byte)(pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId), 0x10)); this.CurrentStatus = Status.Wait_Answer_Read_All_Nozzle_ProductInfo_PhyId; } else { foreach (var answer in this.logicalNozzleDb_Nozzle_ProductInfo_PhyId_Answers) logger.Info(" node " + ifsfRecipientNode + " Read nozzles info for FP: 0x" + answer.TargetFuelPointId.ToHexLogString() + ", with logicalId: 0x" + answer.LogicalNozzleId.ToHexLogString() + ", with physicalId: 0x" + answer.PhyscialNozzleId.ToHexLogString() + ", with product slotId: 0x" + answer.LinkedProductSlotId_InProductDbProdcutNoSlot.ToHexLogString()); next_Fp_Index = 1; logger.Info("PumpInitializer, node " + ifsfRecipientNode + " sending ProductDbRequest_Read_ProductNo"); this.pendingForAckMsgToken = this.msgTokenGenerator(); // will receive several answers and an ACK messages since this is a query all request. context.Outgoing.Write(new ProductDbRequest_Read_ProductNo(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, 0)); this.CurrentStatus = Status.Wait_Answer_Read_All_ProductNumber; } } break; } case Status.Wait_Answer_Read_All_ProductNumber: { if (context.Incoming.Message is IfsfMessage ifsfMsg && ifsfMsg.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER && ifsfMsg.MessageToken == this.pendingForAckMsgToken) { var historyIncoming = context.Incoming as HistoryKeepIncoming; //logger.Debug("************" + historyIncoming.History.Select(a => a.Item1.GetType().Name).Aggregate((acc, n) => acc + ", " + n)); //logger.Debug("************historyIncoming.History.Count: " + historyIncoming.History.Count); this.productDb_ProductNo_Answers.AddRange( historyIncoming.History.Where(h => (h.Item1 is ProductDb_ProductNo_Answer im) && im.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER && im.MessageToken == this.pendingForAckMsgToken).Select(s => s.Item1 as ProductDb_ProductNo_Answer).OrderBy(a => a.ProductSlotId)); if (this.productDb_ProductNo_Answers.Any()) { logger.Info(" node " + ifsfRecipientNode + " Read all existed product number in a TQC which are: " + this.productDb_ProductNo_Answers.Select(s => s.ProductNumber + "(in 0x" + s.ProductSlotId.ToHexLogString() + ")").Aggregate((acc, n) => acc + ", " + n)); string actualProductNumber = this.productDb_ProductNo_Answers.ElementAt(this.next_ActualInPump_ProductNumber_In_ProductDb_ProductNoSlot_Index - 1).ProductNumber;//this.siteProductBarcodes[next_Clear_ProductNumber_In_Caculator_SlotIndex - 1].ToString(); this.pendingForAckMsgToken = this.msgTokenGenerator(); // each Product will receive a serial of answers for its differnt FuelMode since we input 0x10 as query all FM. context.Outgoing.Write(new ProductPerFuellingModeDbRequest_Read_ProductPrice(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, actualProductNumber, 0x11)); this.CurrentStatus = Status.Wait_Answer_Read_All_ProductPrice; } else { /* TQC without any product configed, happened for fresh new TQC pump (get hardware reset or just shipped from factory??), handle here */ logger.Info(" node " + ifsfRecipientNode + " has no ProductDb_ProductNo_Answer received, seems there're no product configurated in this TQC yet, will directly Clear Set_ProductNumber_In_ProductDb"); logger.Info("PumpInitializer, node " + ifsfRecipientNode + " sending Clear ProductNumber in ProductDb slot index: " + next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new ProductDbRequest_Write_SetProductNumber(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index, null)); this.CurrentStatus = Status.Wait_ACK_Set_ProductNumber_In_ProductDb; } } break; } case Status.Wait_Answer_Read_All_ProductPrice: { if (context.Incoming.Message is IfsfMessage ifsfMsg && ifsfMsg.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER && ifsfMsg.MessageToken == this.pendingForAckMsgToken.Value) { var historyIncoming = context.Incoming as HistoryKeepIncoming; var resultSetsForOneProduct = historyIncoming.History.Where(h => (h.Item1 is ProductPerFuellingModeDb_ProductPrice_Answer im) && im.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER && im.MessageToken == this.pendingForAckMsgToken).Select(s => s.Item1 as ProductPerFuellingModeDb_ProductPrice_Answer); logger.Info(" node " + ifsfRecipientNode + ", Read one existed product price with product number: " + resultSetsForOneProduct.Last().ProductNumber + ", price are: " + resultSetsForOneProduct.OrderBy(a => a.FuelModeId).Select(s => s.Price + " in FM:0x" + s.FuelModeId.ToString("X")).Aggregate((acc, n) => acc + ", " + n)); this.productPerFuellingModeDb_ProductPrice_Answers.AddRange(resultSetsForOneProduct); if (this.productDb_ProductNo_Answers.Count() >= ++next_ActualInPump_ProductNumber_In_ProductDb_ProductNoSlot_Index) { string actualProductNumber = this.productDb_ProductNo_Answers.ElementAt(this.next_ActualInPump_ProductNumber_In_ProductDb_ProductNoSlot_Index - 1).ProductNumber;//this.siteProductBarcodes[next_Clear_ProductNumber_In_Caculator_SlotIndex - 1].ToString(); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write(new ProductPerFuellingModeDbRequest_Read_ProductPrice(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, actualProductNumber, 0x10)); this.CurrentStatus = Status.Wait_Answer_Read_All_ProductPrice; } else { next_ActualInPump_ProductNumber_In_ProductDb_ProductNoSlot_Index = 1; logger.Info("PumpInitializer, node " + ifsfRecipientNode + " Read_All_ProductPrice done!"); var safe = this.OnTqcExistedConfigRead; safe?.Invoke(this, null); next_site_nozzle_Index = 1; logger.Info("PumpInitializer, node " + ifsfRecipientNode + " sending CaculatorDbRequest_Write_Set_AuthStateMode_MinFuelingVol_MinDisplayVol"); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new CaculatorDbRequest_Write_Set_AuthStateMode_MinFuelingVol_MinDisplayVol(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, CaculatorDbRequest_Write_Set_AuthStateMode_MinFuelingVol_MinDisplayVol.Caculator_Auth_State_Mode.AUTH_State_Allowed)); this.CurrentStatus = Status.Wait_ACK_Set_AUTH_MODE; } } break; } case Status.Wait_ACK_Set_AUTH_MODE: { if (context.Incoming.Message is IfsfMessage ifsfMsg && ifsfMsg.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK && ifsfMsg.MessageToken == this.pendingForAckMsgToken.Value) { logger.Info("PumpInitializer, node " + ifsfRecipientNode + " CaculatorDbRequest_Write_Set_AuthStateMode_MinFuelingVol_MinDisplayVol Acked."); var targetBarcode = this.thisTqcProductBarcodes[next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index - 1]; int newInitPriceWithoutDecimalPoints; if (this.PriceBook.ContainsKey(targetBarcode)) newInitPriceWithoutDecimalPoints = this.PriceBook[targetBarcode]; else { // try load the existed pre-configed price value which just read from TQC pump. var preConfigedPriceOnPump = this.productPerFuellingModeDb_ProductPrice_Answers.FirstOrDefault(p => p.FuelModeId == 0x11 && p.ProductNumber == targetBarcode.ToString().PadLeft(8, '0'))?.Price; if (string.IsNullOrEmpty(preConfigedPriceOnPump)) newInitPriceWithoutDecimalPoints = 1883 + next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index; else newInitPriceWithoutDecimalPoints = int.Parse(preConfigedPriceOnPump); this.PriceBook.Add(targetBarcode, newInitPriceWithoutDecimalPoints); } logger.Info("PumpInitializer, node " + ifsfRecipientNode + " sending ProductPerFuellingModeDbRequest_Write_SetPrice, productNo: " + targetBarcode.ToString() + ", new price(without decimalPoints): " + newInitPriceWithoutDecimalPoints.ToString()); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new ProductPerFuellingModeDbRequest_Write_SetPrice(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, targetBarcode.ToString(), newInitPriceWithoutDecimalPoints.ToString() , 0x11) ); this.CurrentStatus = Status.Wait_ACK_Set_Price; } break; } case Status.Wait_ACK_Set_Price: { if (context.Incoming.Message is AcknowledgeMessage ifsfMsg && ifsfMsg.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK && ifsfMsg.MessageToken == this.pendingForAckMsgToken.Value) { if (ifsfMsg.OverallStatus != MessageAcknowledgeStatus.ACK_PositiveAcknowledgeDataReceived) logger.Error(" node " + ifsfRecipientNode + ", set price for product failed!"); if (this.thisTqcProductBarcodes.Count >= ++next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index) { logger.Info("PumpInitializer, node " + ifsfRecipientNode + " ProductPerFuellingModeDbRequest_Write_SetPrice Acked."); var targetBarcode = this.thisTqcProductBarcodes[next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index - 1]; int newInitPriceWithoutDecimalPoints; if (this.PriceBook != null && this.PriceBook.ContainsKey(targetBarcode)) newInitPriceWithoutDecimalPoints = this.PriceBook[targetBarcode]; else { // try load the existed pre-configed price value which just read from TQC pump. var preConfigedPriceOnPump = this.productPerFuellingModeDb_ProductPrice_Answers.FirstOrDefault(p => p.FuelModeId == 0x11 && p.ProductNumber == targetBarcode.ToString().PadLeft(8, '0'))?.Price; if (string.IsNullOrEmpty(preConfigedPriceOnPump)) newInitPriceWithoutDecimalPoints = 1883 + next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index; else newInitPriceWithoutDecimalPoints = int.Parse(preConfigedPriceOnPump); this.PriceBook.Add(targetBarcode, newInitPriceWithoutDecimalPoints); } logger.Info("PumpInitializer, node " + ifsfRecipientNode + " sending ProductPerFuellingModeDbRequest_Write_SetPrice, productNo: " + targetBarcode.ToString() + ", new price(without decimalPoints): " + newInitPriceWithoutDecimalPoints.ToString()); this.pendingForAckMsgToken = this.msgTokenGenerator(); //logger.Info("PumpInitializer, node "+ifsfRecipientNode+" will set product: " + this.siteProductBarcodes[next_Clear_ProductNumber_In_Caculator_SlotIndex - 1].ToString() + " with price " + (1883 + next_Clear_ProductNumber_In_Caculator_SlotIndex).ToString()); context.Outgoing.Write( new ProductPerFuellingModeDbRequest_Write_SetPrice(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, targetBarcode.ToString(), newInitPriceWithoutDecimalPoints.ToString(), 0x11) ); this.CurrentStatus = Status.Wait_ACK_Set_Price; } else { next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index = 1; logger.Info("PumpInitializer, node " + ifsfRecipientNode + " set product Price(8 products) in fuel mode 0x11 done!"); //this.CurrentStatus = Status.Sending_Set_Nozzle_To_CacalatorSlot; // now we only have fuelling mode 0x11. logger.Info("PumpInitializer, node " + ifsfRecipientNode + " Send FuellingPointDbRequest_Write_SetDefaultFuellingMode,\r\n" + "FP: 0x" + pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId.ToHexLogString() + " set to fuelling mode 0x11"); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new FuellingPointDbRequest_Write_SetDefaultFuellingMode(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, (byte)(pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId), 1)); this.CurrentStatus = Status.Wait_ACK_Set_FP_Default_FuellingMode; } } break; } case Status.Wait_ACK_Set_FP_Default_FuellingMode: { if (context.Incoming.Message is IfsfMessage ifsfMsg && ifsfMsg.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK && ifsfMsg.MessageToken == this.pendingForAckMsgToken.Value) { if (++next_Fp_Index <= pumpHandlers.Count()) { logger.Info("PumpInitializer, node " + ifsfRecipientNode + " FuellingPointDbRequest_Write_SetDefaultFuellingMode Acked"); // now we only have fuelling mode 0x11. logger.Info("PumpInitializer, node " + ifsfRecipientNode + " Send FuellingPointDbRequest_Write_SetDefaultFuellingMode,\r\n" + "FP Default fuelling mode, FP: 0x" + pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId.ToHexLogString()); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new FuellingPointDbRequest_Write_SetDefaultFuellingMode(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, (byte)(pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId), 1)); this.CurrentStatus = Status.Wait_ACK_Set_FP_Default_FuellingMode; } else { next_Fp_Index = 1; logger.Info("PumpInitializer, node " + ifsfRecipientNode + " set fp default fuelling mode all done!"); logger.Info("PumpInitializer, node " + ifsfRecipientNode + "(pump server ip: " + (comm?.pumpServerTcpListeningIpAddress ?? "") + ") DONE!!!"); this.CurrentStatus = Status.Done; this.InitDoneTaskCompletionSource.SetResult(true); var safe = this.OnInitDone; safe?.Invoke(this, null); } } break; } } return true; } public bool IsInitDone => this.CurrentStatus == Status.Done; public void Reset() { this.CurrentStatus = Status.UnInit; this.initTimeoutTimer.Stop(); this.InitDoneTaskCompletionSource = new TaskCompletionSource(); this.logicalNozzleDb_Nozzle_ProductInfo_PhyId_Answers.Clear(); this.productDb_ProductNo_Answers.Clear(); this.productPerFuellingModeDb_ProductPrice_Answers.Clear(); } } #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). } // 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. // ~PumpHandler() { // // 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); } IEnumerator IEnumerable.GetEnumerator() { return this.pumpHandlers.GetEnumerator(); } #endregion } }