using Edge.Core.Processor; using Edge.Core.IndustryStandardInterface.Pump; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; using Wayne_Pump_Dart.MessageEntity; using Wayne_Pump_Dart.MessageEntity.Outgoing; using Edge.Core.Processor.Dispatcher.Attributes; using Edge.Core.Processor.Communicator; using System.Xml.Serialization; using System.IO; namespace Wayne_Pump_Dart { [MetaPartsRequired(typeof(HalfDuplexActivePollingDeviceProcessor<,>))] [MetaPartsRequired(typeof(ComPortCommunicator<>))] [MetaPartsRequired(typeof(TcpClientCommunicator<>))] [MetaPartsRequired(typeof(TcpServerCommunicator<>))] [MetaPartsDescriptor( "lang-zh-cn:稳Dart加油机lang-en-us:WayneDartPump", "lang-zh-cn:用于驱动 Wayne Dart 协议的加油机lang-en-us:Used for driven Wayne Pump which use Dart Protocol", new[] { "lang-zh-cn:加油机lang-en-us:Pump" })] public class PumpGroupHandler : TestableActivePollingDeviceHandler, IEnumerable, IDisposable { //protected static ILog logger = log4net.LogManager.GetLogger("PumpHandler"); static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("PumpHandler"); protected IContext context; protected int pollingInterval; protected List pumpHandlers = new List(); /// /// if the underlying communicator connected to remote device. /// private bool isCommConnected = false; private int amountDecimalDigits; private int volumeDecimalDigits; private int priceDecimalDigits; private int volumeTotalizerDecimalDigits; #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 List PumpConfigurations { get; set; } } public class PumpConfiguration { public byte PumpId { get; set; } public byte PhysicalId { get; set; } public List NozzleConfigurations { get; set; } } public class NozzleConfiguration { public byte LogicalId { get; set; } public byte PhysicalId { get; set; } public int DefaultNoDecimalPointPriceIfNoHistoryPriceReadFromDb { get; set; } } #endregion [ParamsJsonSchemas("PumpGroupHandlerCtorParamsJsonSchemas")] public PumpGroupHandler(PumpGroupConfiguration pumpGroupConfiguration, IServiceProvider services) { this.amountDecimalDigits = pumpGroupConfiguration.AmountDecimalDigits; this.volumeDecimalDigits = pumpGroupConfiguration.VolumeDecimalDigits; this.priceDecimalDigits = pumpGroupConfiguration.PriceDecimalDigits; this.volumeTotalizerDecimalDigits = pumpGroupConfiguration.VolumeTotalizerDecimalDigits; logger.Info("Wayne Dart pump group, Will create " + pumpGroupConfiguration.PumpConfigurations.Count + " pump handlers for this Wayne Dart Group from config"); this.CreatePumpHandlers(pumpGroupConfiguration.PumpConfigurations.Select(pc => { XmlDocument doc = new XmlDocument(); var xml = "" + "" + pc.NozzleConfigurations.Select(nc => "").Aggregate((acc, n) => acc + n) + "" + ""; doc.LoadXml(xml); return doc.FirstChild; })); } //[MetaApiParamsJsonSchemaDescriptor("PumpGroupHandlerCtorSchema")] public PumpGroupHandler(int amountDecimalDigits, int volumeDecimalDigits, int priceDecimalDigits, int volumeTotalizerDecimalDigits, string pumpGroupXmlConfiguration) { this.amountDecimalDigits = amountDecimalDigits; this.volumeDecimalDigits = volumeDecimalDigits; this.priceDecimalDigits = priceDecimalDigits; this.volumeTotalizerDecimalDigits = volumeTotalizerDecimalDigits; //sample of pumpGroupXmlConfiguration, the address is a config value in physical wayne dart //pump mother board, 1 - 32 is the acceptable values. //the reason introduce PumpGroupHandler, because wayne dart pump may run multiple pumps in single //rs485 com port. // // // // // // // // // // // // // // // XmlDocument xmlDocument = new XmlDocument(); xmlDocument.LoadXml(pumpGroupXmlConfiguration); //var rootElement = xmlDocument.GetElementsByTagName("PumpGroup").Cast().First(); var pumpElements = xmlDocument.GetElementsByTagName("Pump").Cast(); logger.Info("Wayne Dart pump group, Will create " + pumpElements.Count() + " pump handlers for this Wayne Dart Group from local config"); this.CreatePumpHandlers(pumpElements); } protected virtual void CreatePumpHandlers(IEnumerable pumpElements) { foreach (XmlNode pumpElement in pumpElements) { var pumpId = int.Parse(pumpElement.Attributes["pumpId"].Value); var pumpHandler = new PumpHandler(this, pumpId, amountDecimalDigits, volumeDecimalDigits, priceDecimalDigits, volumeTotalizerDecimalDigits, pumpElement.OuterXml); this.pumpHandlers.Add(pumpHandler); } this.rotateMsgTokens.AddRange(Enumerable.Repeat(0, this.pumpHandlers.Select(s => s.PumpId).Max())); } public IEnumerator GetEnumerator() { return this.pumpHandlers.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.pumpHandlers.GetEnumerator(); } /// /// key:value = pumpId:MsgToken /// private List rotateMsgTokens = new List(); /// /// get a valid token which from >=1 and lessOrEqual F. /// NOTE, if ResetMessageTokenToAlign(pumpId) was called, the next token generated here will be 0, which is /// a special value used to align the comm with real pump side, and then if the comm eastablished, we /// should use 1 to F. /// /// /// public byte GetNewMessageToken(int pumpId) { lock (rotateMsgTokens) { //return 0; if (rotateMsgTokens[pumpId - 1] > 0x0E) this.rotateMsgTokens[pumpId - 1] = 0; //var returnValue = this.rotateMsgTokens[pumpId - 1]; //this.rotateMsgTokens[pumpId - 1] += 1; return (byte)(++this.rotateMsgTokens[pumpId - 1]); } } /// /// typically called after a data request was NAKed by pump side, 0 token used for /// re-align the comm from FC to real pump. /// /// public void ResetMessageTokenToAlign(int pumpId) { lock (rotateMsgTokens) this.rotateMsgTokens[pumpId - 1] = -1; } public override void Init(IContext context) { base.Init(context); this.context = context; this.context.Communicator.OnConnected += (object sender, EventArgs e) => { this.isCommConnected = true; }; this.context.Communicator.OnDisconnected += (object sender, EventArgs e) => { this.isCommConnected = false; }; this.pumpHandlers.ForEach(p => p.Init(this.context)); var timeWindowWithActivePollingOutgoing = this.context.Outgoing as TimeWindowWithActivePollingOutgoing; this.pollingInterval = timeWindowWithActivePollingOutgoing.PollingInterval; int previousPolledHandlerIndex = 0; timeWindowWithActivePollingOutgoing.PollingMsgProducer = () => { try { //if (!this.isCommConnected) return null; if (this.pumpHandlers.Count <= previousPolledHandlerIndex) previousPolledHandlerIndex = 0; var target = this.pumpHandlers[previousPolledHandlerIndex++]; return new Poll((byte)(target.PumpPhysicalId), 0);// this.GetNewMessageToken(target.PumpId)); } catch (Exception exxx) { logger.Error("Exceptioned in WayneDartPumpGroupHandler(previousPolledHandlerIndex: " + previousPolledHandlerIndex + "): " + exxx); return null; } }; } public override Task Process(IContext context) { this.context = context; var ph = this.pumpHandlers.FirstOrDefault(p => (byte)(p.PumpPhysicalId) == context.Incoming.Message.Adrs); if (ph == null) { logger.Error("PumpGroupHandler does not contain pumpHandler with physcialId: " + context.Incoming.Message.Adrs.ToString("X2")); return Task.CompletedTask; } return ph.Process(context); //return Task.CompletedTask; } public void Dispose() { this.pumpHandlers.ForEach(ph => ph.Dispose()); } } }