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