using Edge.Core.Database; using Edge.Core.Processor; using Edge.Core.IndustryStandardInterface.Pump; using HengShan_Pump_TQC_IFSF.MessageEntity; using HengShan_Pump_TQC_IFSF.MessageEntity.Incoming; using HengShan_Pump_TQC_IFSF.MessageEntity.Outgoing; using Edge.Core.Parser.BinaryParser.Util; 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; using Edge.Core.Processor.Dispatcher.Attributes; using Edge.Core.Processor.Communicator; using Edge.Core.Configuration; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; namespace HengShan_Pump_TQC_IFSF { /// /// one TQC connection controls a full physical pump, typically 2 sides. /// [MetaPartsRequired(typeof(GenericDeviceProcessor<,>))] [MetaPartsRequired(typeof(TcpClientCommunicator<>))] [MetaPartsRequired(typeof(ComPortCommunicator<>))] [MetaPartsRequired(typeof(IfsfMessageBase))] [MetaPartsDescriptor( "lang-zh-cn:LonWorks HS TQC加油机lang-en-us:LonWorks HS TQC pump", "lang-zh-cn:用于驱动基于 LonWorks Card 连接的IFSF协议的 稳牌-WT30 或 恒山-TH22 加油机lang-en-us:Used for driven Wayne-WT30 or HengShan-TH22 dispensers which using IFSF protocol on LonWorks Card", new[] { "lang-zh-cn:加油机lang-en-us:Pump" })] public class LonWorksPumpGroupHandler : IEnumerable, IDeviceHandler, IDisposable { #region Ctor parameters public class PumpIfsfNodeGroupConfig { //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; } public List IfsfNodeConfigs { get; set; } } public class IfsfNodeConfig { /// /// public byte IfsfSubnetValue { get; set; } public byte IfsfNodeValue { get; set; } public List IfsfFpConfigs { get; set; } } public class IfsfFpConfig { public byte PumpId { get; set; } /// /// 0x21 - 0x24 /// public byte PhysicalId { get; set; } public List IfsfNozzleConfigs { get; set; } } public class IfsfNozzleConfig { public byte LogicalId { get; set; } /// /// 0x11 - 0x18 /// public byte PhysicalId { get; set; } } #endregion private IServiceProvider services; private static ILogger logger = NullLogger.Instance; private Timer fccHeartbeatSendingTimer; private Timer pumpIfsfNodeOfflineCheckTimer; /// /// treat pump ifsf node to offline if this time period have not recieve any of its message. /// private int pumpIfsfNodeOfflineTimeThresholdByMs = 25000; public PumpIfsfNodeGroupConfig deviceConfig; private object syncObject = new object(); protected IEnumerable nozzleExtraInfos; private Guid uniqueId = Guid.NewGuid(); internal List tqcPumpGroupInitializers; //protected static ILog logger = log4net.LogManager.GetLogger("PumpHandler"); protected IContext context; protected List pumpHandlers = new List(); //// for test, the tqc configured as subnet 1, node 1. //protected byte recipientSubnet = 1; //protected byte recipientNode = 1; //// originator, it's the FC self. public static byte originatorSubnet = 2; public static byte originatorNode = 1; public LonWorksPumpGroupHandler(PumpIfsfNodeGroupConfig pumpIfsfNodeGroupConfig, IServiceProvider services) { this.services = services; var loggerFactory = services.GetRequiredService(); logger = loggerFactory.CreateLogger("PumpHandler"); this.deviceConfig = pumpIfsfNodeGroupConfig; originatorSubnet = pumpIfsfNodeGroupConfig.FccIfsfSubnetValue; originatorNode = pumpIfsfNodeGroupConfig.FccIfsfNodeValue; //this.recipientSubnet = pumpIfsfNodeGroupConfig.IfsfSubnetValue; //this.recipientNodes.AddRange(pumpIfsfNodeGroupConfig.IfsfNodeConfigs.Select(pc => pc.PumpIfsfNodeValue)); //logger.LogInformation("node " + this.recipientNodes.Select(n => n.ToString()).Aggregate((acc, n) => acc + ", " + n) + ", Will create " + pumpIfsfNodeGroupConfig.IfsfNodeConfigs.Count // + " pump handlers for this TQC connection according from pumpGroupConfiguration"); if (pumpIfsfNodeGroupConfig.IfsfNodeConfigs.SelectMany(nc => nc.IfsfFpConfigs).Any(fpConfig => fpConfig.PhysicalId < 0x21 || fpConfig.PhysicalId > 0x24)) throw new ArgumentException("Ifsf fuel point id must be range from 0x21 to 0x24"); foreach (var nodeConfig in pumpIfsfNodeGroupConfig.IfsfNodeConfigs) { var initor = new TqcPumpGroupInitializer(originatorSubnet, originatorNode, nodeConfig.IfsfSubnetValue, nodeConfig.IfsfNodeValue, this.pumpHandlers, this.GetNewMessageToken); initor.OnInitTimeout += (cc, dd) => { logger.LogInformation("TqcPumpGroupInitializer, node " + initor.ifsfRecipientNode + ", init timed out, will start it over..."); initor.Reset(); initor.FeedIn(this.context); }; initor.OnInitDone += (ee, ff) => { logger.LogInformation("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(nodeConfig.IfsfSubnetValue, nodeConfig.IfsfNodeValue, originatorSubnet, originatorNode, this.GetNewMessageToken(), 0x20)); 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.LogInformation("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]; }); }; this.tqcPumpGroupInitializers.Add(initor); foreach (var fpConfig in nodeConfig.IfsfFpConfigs) { var pumpHandler = new LonWorksPumpHandler(initor, fpConfig.PumpId, fpConfig.PhysicalId, nodeConfig.IfsfSubnetValue, nodeConfig.IfsfNodeValue, fpConfig.IfsfNozzleConfigs, 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.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) => { logger.LogInformation("TqcPumpGroupInitializer, Communicator Connected, will start init each Initializer..."); isCommConnected = true; foreach (var initor in this.tqcPumpGroupInitializers) { while (isCommConnected) { initor.Reset(); if (initor.CurrentStatus == TqcPumpGroupInitializer.Status.UnInit) initor.FeedIn(this.context); else logger.LogInformation("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.LogInformation("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( initor.ifsfRecipientSubnet, 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.LogInformation("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.LogInformation("TqcPumpGroupInitializer, node " + initor.ifsfRecipientNode + ", init timed out, will start it over..."); //initor.Reset(); //initor.FeedIn(this.context); } } } }; this.pumpHandlers.ForEach(p => p.Init(this.context)); //accuracy is 1000ms this.pumpIfsfNodeOfflineCheckTimer = new Timer(1000); this.pumpIfsfNodeOfflineCheckTimer.Elapsed += async (a, b) => { var offlinePumps = this.pumpHandlers.Where(b => DateTime.Now.Subtract(b.LastIncomingMsgReceivedTime ?? DateTime.MinValue).TotalMilliseconds >= pumpIfsfNodeOfflineTimeThresholdByMs); if (offlinePumps.Any()) { logger.LogError($"Long time no see any msg incoming for {offlinePumps.Select(p => $"Pump {p.PumpId}").Aggregate((acc, n) => acc + ", " + n)}, will trigger them to Fdc_Offline state."); } foreach (var p in offlinePumps) { p.HandleFpStatusChange(FuellingPointStatus.Inoperative, null); } }; this.pumpIfsfNodeOfflineCheckTimer.Start(); this.fccHeartbeatSendingTimer = new Timer(10000); this.fccHeartbeatSendingTimer.Elapsed += async (a, b) => { context.Outgoing.Write(new LonWorksHeartbeat(1, originatorSubnet, originatorNode)); }; this.fccHeartbeatSendingTimer.Start(); } 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).ToList().ForEach(p => p.Process(context)); break; case FuellingPointDb_FpStatus_Answer fpStatusAnswer: this.pumpHandlers.Where(h => h.PumpPhysicalId == fpStatusAnswer.TargetFuelPointId).ToList().ForEach(p => p.Process(context)); break; case FuellingPointDb_FpRunningTransaction_Event fpRunningTrxEvent: this.pumpHandlers.Where(h => h.PumpPhysicalId == fpRunningTrxEvent.TargetFuelPointId).ToList().ForEach(p => p.Process(context)); break; case FuellingPointDb_FpRunningTransaction_Event_ACK fpRunningTrxEvent: this.pumpHandlers.Where(h => h.PumpPhysicalId == fpRunningTrxEvent.TargetFuelPointId).ToList().ForEach(p => p.Process(context)); break; case FuellingTrxDb_TransactionBufferStatus_Event trxBufferStatusEvent: this.pumpHandlers.Where(h => h.PumpPhysicalId == trxBufferStatusEvent.TargetFuelPointId).ToList().ForEach(p => p.Process(context)); break; case FuellingTrxDb_TransactionBufferStatus_Event_ACK trxBufferStatusEvent: this.pumpHandlers.Where(h => h.PumpPhysicalId == trxBufferStatusEvent.TargetFuelPointId).ToList().ForEach(p => p.Process(context)); break; case FuellingTrxDb_TransactionBufferStatus_Answer trxBufferStatusAnswer: this.pumpHandlers.Where(h => h.PumpPhysicalId == trxBufferStatusAnswer.TargetFuelPointId).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; /// /// Initalizer for init default values into TQC pump. /// especially for default product price: it will read pre-config values for each product in pump first, /// public class TqcPumpGroupInitializer { 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; public event EventHandler OnInitError; /// /// 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 IEnumerable nozzleProductConfig; public IEnumerable NozzleProductConfig { get { return this.nozzleProductConfig; } set { this.nozzleProductConfig = value; this.thisTqcProductBarcodes = this.nozzleProductConfig.Where(n => this.pumpHandlers.Select(p => p.PumpId).Contains(n.PumpId)).Select(s => s.ProductBarcode).Distinct().OrderBy(o => o).ToList(); //this.thisTqcProductBarcodes = nozzleProductConfig.Select(s => s.ProductBarcode).Distinct().OrderBy(o => o).ToList(); logger.LogInformation("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)); } } //= Configurator.Default.NozzleProductConfiguration.Mapping; private byte ifsfSelfSubnet; private byte ifsfSelfNode; public byte ifsfRecipientSubnet; public byte ifsfRecipientNode; public IEnumerable pumpHandlers; Func msgTokenGenerator; private List thisTqcProductBarcodes; private Status currentStatus; public Status CurrentStatus { get { return this.currentStatus; } 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 TqcPumpGroupInitializer(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.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) { if (!this.initTimeoutTimer.Enabled) this.initTimeoutTimer.Start(); if (IsInitDone) return false; switch (this.CurrentStatus) { case Status.UnInit: { logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " start Init, firstly Query&Close all FuelPoints..."); logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " Send QueryAllFp state(first time)"); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new FuellingPointDbRequest_Read_FuelPointState(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, 0x20 )); this.CurrentStatus = Status.Wait_Answer_Query_All_FuelPointState; break; } case Status.Wait_Answer_Query_All_FuelPointState: { if (context.Incoming.Message is IfsfMessage ifsfMsg && ifsfMsg.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK && ifsfMsg.MessageToken == this.pendingForAckMsgToken.Value) { var historyIncoming = context.Incoming as HistoryKeepIncoming; this.fpStatus_Answers = historyIncoming.History.Where(h => (h.Item1 is FuellingPointDb_FpStatus_Answer im) && im.MessageType == MessageType.IFSF_MESSAGE_TYPE_ANSWER && im.MessageToken == this.pendingForAckMsgToken) .Select(s => (FuellingPointDb_FpStatus_Answer)s.Item1).ToList(); logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + ", read total " + fpStatus_Answers.Count + " Fp status, they're " + this.fpStatus_Answers.Select(s => "Fp 0x" + s.TargetFuelPointId.ToHexLogString() + " is " + s.FuelPointState) .Aggregate((n, acc) => n + ", " + acc)); this.pendingForAckMsgToken = this.msgTokenGenerator(); logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " Send CloseFuelPoint(first time) for FP: 0x" + this.pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId.ToHexLogString()); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new FuellingPointDbRequest_Write_CloseFuelPoint(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, (byte)(this.pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId) )); this.CurrentStatus = Status.Wait_Answer_Close_All_FuelPoint; } break; } case Status.Wait_Answer_Close_All_FuelPoint: { if (context.Incoming.Message is IfsfMessage ifsfMsg && ifsfMsg.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK && ifsfMsg.MessageToken == this.pendingForAckMsgToken) { if (++next_Fp_Index <= this.pumpHandlers.Count()) { logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " CloseFuelPoint Acked"); logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " Send CloseFuelPoint for FP: 0x" + this.pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId.ToHexLogString()); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new FuellingPointDbRequest_Write_CloseFuelPoint(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, (byte)(this.pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId) )); this.CurrentStatus = Status.Wait_Answer_Close_All_FuelPoint; } else { this.next_Fp_Index = 1; logger.LogInformation("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.LogInformation("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.First(i => i.Item1 == 0x04).Item2.ToInt32() + ", 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.LogInformation("!!!!!!PumpInitializer, node " + ifsfRecipientNode + " This TQC MUST config " + numberOfProducts + " products, but now trying to load local config with " + this.thisTqcProductBarcodes.Count + " products"); } logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " sending CommunicationServiceDbRequest_Write_RecipientAddressTable..."); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new CommunicationServiceDbRequest_Write_RecipientAddressTable(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, this.ifsfSelfSubnet, this.ifsfSelfNode)); this.CurrentStatus = Status.Wait_ACK_Add_Recipient_Addr; } break; } case Status.Wait_ACK_Add_Recipient_Addr: { if (context.Incoming.Message is IfsfMessage ifsfMsg && ifsfMsg.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK && ifsfMsg.MessageToken == this.pendingForAckMsgToken) { logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " CommunicationServiceDbRequest_Write_RecipientAddressTable Acked."); logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " Send LogicalNozzleDb_Read_Nozzle_ProductInfo_PhyId for FP: 0x" + this.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)(this.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_ACK && 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 <= this.pumpHandlers.Count()) { logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " LogicalNozzleDb_Read_Nozzle_ProductInfo_PhyId Acked"); logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " Send LogicalNozzleDb_Read_Nozzle_ProductInfo_PhyId for FP: 0x" + this.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)(this.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.LogInformation(" 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.LogInformation("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_ACK && 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.LogInformation(" 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, 0x10)); 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.LogInformation(" 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.LogInformation("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 AcknowledgeMessage ifsfMsg && ifsfMsg.MessageType == MessageType.IFSF_MESSAGE_TYPE_ACK && 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.LogInformation(" 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.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " Read_All_ProductPrice done!"); var safe = this.OnTqcExistedConfigRead; safe?.Invoke(this, null); logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " sending Clear productNo(1st time) in ProductDb with 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_Clear_ProductNumber_In_ProductDb; } } break; } case Status.Wait_ACK_Clear_ProductNumber_In_ProductDb: { 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.LogError(" node " + ifsfRecipientNode + ", clear product from ProductDb failed!"); logger.LogInformation(" PumpInitializer, node " + ifsfRecipientNode + ", clear product no. from ProductDb with msgToken:" + this.pendingForAckMsgToken.Value + " Acked."); if (++next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index <= 8) { logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " sending Clear productNo in ProductDb with 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_Clear_ProductNumber_In_ProductDb; } else { logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " clear product number on all productDb productNo slots(total:" + (next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index - 1) + ") done!"); next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index = 1; var next_site_nozzle = this.pumpHandlers.SelectMany(p => p.Nozzles) .OrderBy(n => n.PumpId).OrderBy(n => n.LogicalId).ElementAt(next_site_nozzle_Index - 1); var nozzleProductConfig = this.nozzleProductConfig .First(c => c.PumpId == next_site_nozzle.PumpId && c.NozzleLogicalId == next_site_nozzle.LogicalId); var ifsfNozzle = this.logicalNozzleDb_Nozzle_ProductInfo_PhyId_Answers .First(ifsfNzl => ifsfNzl.TargetFuelPointId == this.pumpHandlers.First(p => p.PumpId == next_site_nozzle.PumpId).PumpPhysicalId && ifsfNzl.LogicalNozzleId == next_site_nozzle.PhysicalId); var nozzleBoundToProductDbProductSlot = ifsfNozzle.LinkedProductSlotId_InProductDbProdcutNoSlot; logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " pump " + next_site_nozzle.PumpId + ", nozzle logicalId " + next_site_nozzle.LogicalId + "(ifsf FpId 0x" + ifsfNozzle.TargetFuelPointId.ToHexLogString() + ", ifsf nozzle logicalId 0x" + ifsfNozzle.LogicalNozzleId.ToHexLogString() + ")" + " is hardware-bound to productDb product No. slot 0x" + nozzleBoundToProductDbProductSlot.ToHexLogString() + ", will(1st time) set 0x" + nozzleBoundToProductDbProductSlot.ToHexLogString() + " to product No.: " + nozzleProductConfig.ProductBarcode); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new ProductDbRequest_Write_SetProductNumber(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, (byte)(nozzleBoundToProductDbProductSlot - 0x40), nozzleProductConfig.ProductBarcode.ToString())); this.CurrentStatus = Status.Wait_ACK_Set_ProductNumber_In_ProductDb; } } break; } case Status.Wait_ACK_Set_ProductNumber_In_ProductDb: { 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.LogError(" node " + ifsfRecipientNode + ", set product to ProductDb failed!"); logger.LogInformation(" PumpInitializer, node " + ifsfRecipientNode + " ProductDbRequest_Write_SetProductNumber with msgToken:" + this.pendingForAckMsgToken.Value + " Acked."); if (++next_site_nozzle_Index <= this.pumpHandlers.SelectMany(p => p.Nozzles).Count()) { var next_site_nozzle = this.pumpHandlers.SelectMany(p => p.Nozzles) .OrderBy(n => n.PumpId).OrderBy(n => n.LogicalId).ElementAt(next_site_nozzle_Index - 1); var nozzleProductConfig = this.nozzleProductConfig .First(c => c.PumpId == next_site_nozzle.PumpId && c.NozzleLogicalId == next_site_nozzle.LogicalId); var ifsfNozzle = this.logicalNozzleDb_Nozzle_ProductInfo_PhyId_Answers .First(ifsfNzl => ifsfNzl.TargetFuelPointId == this.pumpHandlers.First(p => p.PumpId == next_site_nozzle.PumpId).PumpPhysicalId && ifsfNzl.LogicalNozzleId == next_site_nozzle.PhysicalId); var nozzleBoundToProductDbProductSlot = ifsfNozzle.LinkedProductSlotId_InProductDbProdcutNoSlot; logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " pump " + next_site_nozzle.PumpId + ", nozzle logicalId " + next_site_nozzle.LogicalId + "(ifsf FpId 0x" + ifsfNozzle.TargetFuelPointId.ToHexLogString() + ", ifsf nozzle logicalId 0x" + ifsfNozzle.LogicalNozzleId.ToHexLogString() + ")" + " is hardware-bound to productDb product No. slot 0x" + nozzleBoundToProductDbProductSlot.ToHexLogString() + ", will set 0x" + nozzleBoundToProductDbProductSlot.ToHexLogString() + " to product No.: " + nozzleProductConfig.ProductBarcode); this.pendingForAckMsgToken = this.msgTokenGenerator(); context.Outgoing.Write( new ProductDbRequest_Write_SetProductNumber(this.ifsfRecipientSubnet, this.ifsfRecipientNode, this.ifsfSelfSubnet, this.ifsfSelfNode, this.pendingForAckMsgToken.Value, (byte)(nozzleBoundToProductDbProductSlot - 0x40), nozzleProductConfig.ProductBarcode.ToString())); this.CurrentStatus = Status.Wait_ACK_Set_ProductNumber_In_ProductDb; } else { next_site_nozzle_Index = 1; logger.LogInformation("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; } //if (++next_Clear_ProductNumber_In_Caculator_SlotIndex <= 8) //this.CurrentStatus = Status.Sending_Link_Meter_To_CaculatorSlot; //logger.Info("PumpInitializer, node " + ifsfRecipientNode + " sending MeterDbRequest_Write_SetMesureToProduct(first), meterId: " // + next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index + ", 2nd arg: " + next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index); //this.pendingForAckMsgToken = this.msgTokenGenerator(); //context.Outgoing.Write( // new MeterDbRequest_Write_SetMesureToProduct(this.ifsfRecipientSubnet, // this.ifsfRecipientNode, // this.ifsfSelfSubnet, // this.ifsfSelfNode, // this.pendingForAckMsgToken.Value, // next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index, // next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index)); //this.CurrentStatus = Status.Wait_ACK_Link_Meter_To_ProductDb_ProductNoSlot; } break; } //case Status.Wait_ACK_Link_Meter_To_ProductDb_ProductNoSlot: // { // 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 + ", link meter to ProductDb failed!"); // logger.Info(" PumpInitializer, MeterDbRequest_Write_SetMesureToProduct with msgToken:" + this.pendingForAckMsgToken.Value + " Acked."); // if (this.thisTqcProductBarcodes.Count >= ++next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index) // { // //this.CurrentStatus = Status.Sending_Set_ProductNumber_In_Caculator; // this.pendingForAckMsgToken = this.msgTokenGenerator(); // // put 91 to 98 to caculator 8 slots. // string operatingProductNumber = this.thisTqcProductBarcodes[next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index - 1].ToString(); // logger.Info("PumpInitializer, node " + ifsfRecipientNode + " sending ProductDbRequest_Write_SetProductNumber, ProductDb ProductNoSlot: " // + next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index + ", productNo: " + operatingProductNumber); // 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, operatingProductNumber)); // this.CurrentStatus = Status.Wait_ACK_Set_ProductNumber_In_ProductDb; // } // else // { // next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index = 1; // logger.Info("PumpInitializer, node " + ifsfRecipientNode + " Link_Meter_To_ProductDb_ProductNoSlot done!"); // //this.CurrentStatus = Status.Sending_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.LogInformation("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.LogInformation("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.LogError(" node " + ifsfRecipientNode + ", set price for product failed!"); if (this.thisTqcProductBarcodes.Count >= ++next_Clear_ProductNumber_In_ProductDb_ProductNoSlot_Index) { logger.LogInformation("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.LogInformation("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.LogInformation("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.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " Send FuellingPointDbRequest_Write_SetDefaultFuellingMode,\r\n" + "FP: 0x" + this.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)(this.pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId), 1)); this.CurrentStatus = Status.Wait_ACK_Set_FP_Default_FuellingMode; } } break; } //case Status.Wait_ACK_Set_Nozzle_To_ProductDb_ProductNoSlot: // { // 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 nozzle to ProductDb failed:\r\n " + ifsfMsg.ToLogString()); // if (++next_site_nozzle_Index <= this.pumpHandlers.SelectMany(p => p.Nozzles).Count()) // { // logger.Info("PumpInitializer, node " + ifsfRecipientNode + " LogicalNozzleDb_Write_SetNozzleProductAndMeter Acked."); // var next_site_nozzle = this.pumpHandlers.SelectMany(p => p.Nozzles).ElementAt(next_site_nozzle_Index - 1); // var productOnThisNozzleSitsInWhichCaculatorSlot = // thisTqcProductBarcodes.IndexOf(nozzleProductConfig.First(p => p.NozzleLogicalId == next_site_nozzle.LogicalId && p.PumpId == next_site_nozzle.PumpId).ProductBarcode) + 1; // this.pendingForAckMsgToken = this.msgTokenGenerator(); // byte targetIfsfFpId = this.pumpHandlers.First(p => p.PumpId == next_site_nozzle.PumpId).ifsfFuelPointId; // byte targetIfsfNozzleId = ((byte)(0x11 + next_site_nozzle.LogicalId - 1)); // logger.Info("PumpInitializer, node " + ifsfRecipientNode + " sending LogicalNozzleDb_Write_SetNozzleProductAndMeter,\r\n" + // "set product to nozzle, FP: 0x" + targetIfsfFpId.ToHexLogString() // + ", nozzle: 0x" + targetIfsfNozzleId.ToHexLogString() // + ", PumpId: " + next_site_nozzle.PumpId + " map to ProductDb ProductNo Slot: " + productOnThisNozzleSitsInWhichCaculatorSlot); // context.Outgoing.Write( // new LogicalNozzleDb_Write_SetNozzleProductAndMeter(this.ifsfRecipientSubnet, // this.ifsfRecipientNode, // this.ifsfSelfSubnet, // this.ifsfSelfNode, // this.pendingForAckMsgToken.Value, // targetIfsfFpId // //assume logical id is 1 based. // , targetIfsfNozzleId, // (byte)productOnThisNozzleSitsInWhichCaculatorSlot, 1) // ); // this.CurrentStatus = Status.Wait_ACK_Set_Nozzle_To_ProductDb_ProductNoSlot; // } // else // { // next_site_nozzle_Index = 1; // logger.Info("PumpInitializer, node " + ifsfRecipientNode + " set fp nozzles to ProductDb ProductNo Slot done!"); // } // } // 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 <= this.pumpHandlers.Count()) { logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " FuellingPointDbRequest_Write_SetDefaultFuellingMode Acked"); // now we only have fuelling mode 0x11. logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " Send FuellingPointDbRequest_Write_SetDefaultFuellingMode,\r\n" + "FP Default fuelling mode, FP: 0x" + this.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)(this.pumpHandlers.ElementAt(next_Fp_Index - 1).PumpPhysicalId), 1)); this.CurrentStatus = Status.Wait_ACK_Set_FP_Default_FuellingMode; } else { next_Fp_Index = 1; logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " set fp default fuelling mode all done!"); logger.LogInformation("PumpInitializer, node " + ifsfRecipientNode + " DONE!!!"); this.InitDoneTaskCompletionSource.SetResult(true); this.CurrentStatus = Status.Done; 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). this.pumpIfsfNodeOfflineCheckTimer?.Stop(); this.fccHeartbeatSendingTimer?.Stop(); } // 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 } }