using HengshanPaymentTerminal.MessageEntity.Incoming; using HengshanPaymentTerminal.MessageEntity; using HengshanPaymentTerminal.Support; using HengshanPaymentTerminal; using System; using System.Collections.Concurrent; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Edge.Core.Processor.Dispatcher.Attributes; using Edge.Core.IndustryStandardInterface.Pump; using Edge.Core.IndustryStandardInterface.Pump.Fdc; using Edge.Core.Processor; namespace HengshanPaymentTerminal { /// /// Handler that communicates directly with the Hengshan Payment Terminal for card handling and pump handling via serial port. /// [MetaPartsDescriptor( "lang-zh-cn:恒山IC卡终端(UI板) App lang-en-us:Hengshan IC card terminal (UI Board)", "lang-zh-cn:用于与UI板通讯控制加油机" + "lang-en-us:Used for terminal communication to control pumps", new[] { "lang-zh-cn:恒山IC卡终端lang-en-us:HengshanICTerminal" })] public class HengshanPayTermHandler : IEnumerable, IDeviceHandler { #region Fields private string pumpIds; private string pumpSubAddresses; private string pumpNozzles; private string pumpSiteNozzleNos; private string nozzleLogicIds; private IContext _context; private List pumpHandlers = new List(); public Queue queue = new Queue(); private object syncObj = new object(); private ConcurrentDictionary statusDict = new ConcurrentDictionary(); public ConcurrentDictionary PumpStatusDict => statusDict; private Dictionary pumpIdSubAddressDict; public Dictionary> PumpNozzlesDict { get; private set; } public Dictionary NozzleLogicIdDict { get; private set; } public Dictionary> PumpSiteNozzleNoDict { get; private set; } #endregion #region Logger private static NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("IPosPlusApp"); #endregion #region Constructor //private static List ResolveCtorMetaPartsConfigCompatibility(string incompatibleCtorParamsJsonStr) //{ // var jsonParams = JsonDocument.Parse(incompatibleCtorParamsJsonStr).RootElement.EnumerateArray().ToArray(); // //sample: "UITemplateVersion":"1.0" // string uiTemplateVersionRegex = @"(?<=""UITemplateVersion""\:\"").+?(?="")"; // var match = Regex.Match(jsonParams.First().GetRawText(), uiTemplateVersionRegex, RegexOptions.IgnoreCase | RegexOptions.Multiline); // if (match.Success) // { // var curVersion = match.Value; // if (curVersion == "1.0") // { // var existsAppConfigV1 = JsonSerializer.Deserialize(jsonParams.First().GetRawText(), typeof(HengshanPayTerminalHanlderGroupConfigV1)); // } // else // { // } // } // return null; //} [ParamsJsonSchemas("TermHandlerGroupCtorParamsJsonSchemas")] public HengshanPayTermHandler(HengshanPayTerminalHanlderGroupConfigV2 config) : this(config.PumpIds, string.Join(";", config.PumpSubAddresses.Select(m => $"{m.PumpId}={m.SubAddress}")), string.Join(";", config.PumpNozzleLogicIds.Select(m => $"{m.PumpId}={m.LogicIds}")), string.Join(";", config.PumpSiteNozzleNos.Select(m => $"{m.PumpId}={m.SiteNozzleNos}")), string.Join(";", config.NozzleLogicIds.Select(m => $"{m.NozzleNo}={m.LogicId}"))) { } public HengshanPayTermHandler( string pumpIds, string pumpSubAddresses, string pumpNozzles, string pumpSiteNozzleNos, string nozzleLogicIds) { this.pumpIds = pumpIds; this.pumpSubAddresses = pumpSubAddresses; this.pumpNozzles = pumpNozzles; this.pumpSiteNozzleNos = pumpSiteNozzleNos; this.nozzleLogicIds = nozzleLogicIds; AssociatedPumpIds = GetPumpIdList(pumpIds); pumpIdSubAddressDict = InitializePumpSubAddressMapping(); PumpNozzlesDict = ParsePumpNozzlesList(pumpNozzles); PumpSiteNozzleNoDict = ParsePumpSiteNozzleNoList(pumpSiteNozzleNos); NozzleLogicIdDict = InitializeNozzleLogicIdMapping(nozzleLogicIds); InitializePumpHandlers(); } #endregion public void OnFdcServerInit(Dictionary parameters) { logger.Info("OnFdcServerInit called"); if (parameters.ContainsKey("LastPriceChange")) { // nozzle logical id:rawPrice var lastPriceChanges = parameters["LastPriceChange"] as Dictionary; foreach (var priceChange in lastPriceChanges) { } } } #region Event handler public event EventHandler OnTerminalMessageReceived; public event EventHandler OnTotalizerReceived; public event EventHandler OnFuelPriceChangeRequested; public event EventHandler OnTerminalFuelPriceDownloadRequested; public event EventHandler OnCheckCommandReceived; public event EventHandler OnLockUnlockCompleted; #endregion #region Properties public List AssociatedPumpIds { get; private set; } public IContext Context { get { return _context; } } public string PumpIdList => pumpIds; //public LockUnlockOperation LockUnlockOperationType { get; set; } = LockUnlockOperation.Undefined; #endregion #region Methods public int GetSubAddressForPump(int pumpId) { return pumpIdSubAddressDict.First(d => d.Key == pumpId).Value; } private List GetPumpIdList(string pumpIds) { var pumpIdList = new List(); if (!string.IsNullOrEmpty(pumpIds) && pumpIds.Contains(',')) //multiple pumps per serial port, Hengshan TQC pump { var arr = pumpIds.Split(','); foreach (var item in arr) { pumpIdList.Add(int.Parse(item)); } return pumpIdList; } else if (!string.IsNullOrEmpty(pumpIds) && pumpIds.Length == 1 || pumpIds.Length == 2) //only 1 pump per serial port, Hengshan pump { return new List { int.Parse(pumpIds) }; } else { throw new ArgumentException("Pump id list not specified!"); } } private Dictionary InitializePumpSubAddressMapping() { var dict = new Dictionary(); if (!string.IsNullOrEmpty(pumpSubAddresses)) { var sequence = pumpSubAddresses.Split(';') .Select(s => s.Split('=')) .Select(a => new { PumpId = int.Parse(a[0]), SubAddress = int.Parse(a[1]) }); foreach (var pair in sequence) { if (!dict.ContainsKey(pair.PumpId)) { dict.Add(pair.PumpId, pair.SubAddress); } } return dict; } else { throw new ArgumentException("Pump id and sub address mapping does not exist"); } } private Dictionary> ParsePumpNozzlesList(string pumpNozzles) { Dictionary> pumpNozzlesDict = new Dictionary>(); if (!string.IsNullOrEmpty(pumpNozzles) && pumpNozzles.Contains(';')) { var arr = pumpNozzles.Split(';'); foreach (var subMapping in arr) { var pair = new KeyValuePair(int.Parse(subMapping.Split('=')[0]), int.Parse(subMapping.Split('=')[1])); Console.WriteLine($"{pair.Key}, {pair.Value}"); if (!pumpNozzlesDict.ContainsKey(pair.Key)) { pumpNozzlesDict.Add(pair.Key, new List { pair.Value }); } else { List nozzlesForThisPump; pumpNozzlesDict.TryGetValue(pair.Key, out nozzlesForThisPump); if (nozzlesForThisPump != null && !nozzlesForThisPump.Contains(pair.Value)) { nozzlesForThisPump.Add(pair.Value); } } } } else if (!string.IsNullOrEmpty(pumpNozzles) && pumpNozzles.Count(c => c == '=') == 1) // only one pump per serial port { try { pumpNozzlesDict.Add( int.Parse(pumpNozzles.Split('=')[0]), new List { int.Parse(pumpNozzles.Split('=')[1]) }); } catch (Exception ex) { Console.WriteLine(ex); } } else { throw new ArgumentException("Wrong mapping between pump and its associated nozzles!"); } return pumpNozzlesDict; } static Dictionary> ParsePumpSiteNozzleNoList(string pumpSiteNozzleNos) { Dictionary> pumpSiteNozzleNoDict = new Dictionary>(); if (!string.IsNullOrEmpty(pumpSiteNozzleNos) && pumpSiteNozzleNos.Contains(';')) { var arr = pumpSiteNozzleNos.Split(';'); foreach (var subMapping in arr) { var pair = new KeyValuePair>( int.Parse(subMapping.Split('=')[0]), subMapping.Split('=')[1].Split(',').Select(a => int.Parse(a)).ToList()); Console.WriteLine($"{pair.Key}, {pair.Value}"); if (!pumpSiteNozzleNoDict.ContainsKey(pair.Key)) { pumpSiteNozzleNoDict.Add(pair.Key, pair.Value); } } } else if (!string.IsNullOrEmpty(pumpSiteNozzleNos) && pumpSiteNozzleNos.Count(c => c == '=') == 1) { try { string[] strArr = pumpSiteNozzleNos.Split('='); pumpSiteNozzleNoDict.Add( int.Parse(strArr[0]), new List { int.Parse(strArr[1]) }); } catch (Exception ex) { Console.WriteLine(ex); } } else { throw new ArgumentException("Wrong mapping between pump and its associated nozzles!"); } return pumpSiteNozzleNoDict; } private Dictionary InitializeNozzleLogicIdMapping(string nozzleLogicIds) { var dict = new Dictionary(); if (!string.IsNullOrEmpty(nozzleLogicIds)) { var sequence = nozzleLogicIds.Split(';') .Select(s => s.Split('=')) .Select(a => new { NozzleNo = int.Parse(a[0]), LogicId = int.Parse(a[1]) }); foreach (var pair in sequence) { if (!dict.ContainsKey(pair.NozzleNo)) { Console.WriteLine($"nozzle, logic id: {pair.NozzleNo} - {pair.LogicId}"); dict.Add(pair.NozzleNo, pair.LogicId); } } return dict; } else if (!string.IsNullOrEmpty(nozzleLogicIds) && nozzleLogicIds.Count(c => c == '=') == 1) { try { string[] sequence = nozzleLogicIds.Split('='); dict.Add(int.Parse(sequence[0]), int.Parse(sequence[1])); } catch (Exception ex) { Console.WriteLine(ex); } return dict; } else { throw new ArgumentException("Pump id and sub address mapping does not exist"); } } private void InitializePumpHandlers() { var pumpIdList = GetPumpIdList(pumpIds); foreach (var item in pumpIdList) { var nozzleList = GetNozzleListForPump(item); var siteNozzleNoList = PumpSiteNozzleNoDict[item]; HengshanPumpHandler pumpHandler = new HengshanPumpHandler(this, $"Pump_{item}", item, nozzleList, siteNozzleNoList); pumpHandler.OnFuelPriceChangeRequested += PumpHandler_OnFuelPriceChangeRequested; pumpHandlers.Add(pumpHandler); } } private List GetNozzleListForPump(int pumpId) { List nozzles; PumpNozzlesDict.TryGetValue(pumpId, out nozzles); return nozzles; } private void PumpHandler_OnFuelPriceChangeRequested(object sender, FuelPriceChangeRequestEventArgs e) { InfoLog($"Change price, Pump {e.PumpId}, Nozzle {e.NozzleId}, Price {e.Price}"); OnFuelPriceChangeRequested?.Invoke(sender, e); } IEnumerator IEnumerable.GetEnumerator() { return pumpHandlers.GetEnumerator(); } #endregion #region IHandler implementation public void Init(IContext context) { CommIdentity = context.Processor.Communicator.Identity; _context = context; } public string CommIdentity { get; private set; } public async Task Process(IContext context) { } private void CheckStatus(CheckCmdRequest request) { if (!statusDict.ContainsKey(request.FuelingPoint.PumpNo)) { var result = statusDict.TryAdd(request.FuelingPoint.PumpNo, new PumpStateHolder { PumpNo = request.FuelingPoint.PumpNo, NozzleNo = 1, State = request, OperationType = LockUnlockOperation.None }); logger.Info($"Adding FuelingPoint {request.FuelingPoint.PumpNo} to dict"); if (!result) { statusDict.TryAdd(request.FuelingPoint.PumpNo, null); } } else { PumpStateHolder stateHolder = null; statusDict.TryGetValue(request.FuelingPoint.PumpNo, out stateHolder); if (stateHolder != null) { logger.Debug($"State holder, PumpNo: {stateHolder.PumpNo}, dispenser state: {stateHolder.State.DispenserState}, " + $"operation: {stateHolder.OperationType}"); } if (stateHolder != null && stateHolder.OperationType != LockUnlockOperation.None) { logger.Debug($"PumpNo: {request.FuelingPoint.PumpNo}, Last Dispenser State: {stateHolder.State.DispenserState}, " + $"Current Dispenser State: {request.DispenserState}"); if (stateHolder.State.DispenserState == 3 && request.DispenserState == 2) { //Pump is locked due to lock operation if (stateHolder.OperationType != LockUnlockOperation.None) { logger.Info("Locking done!"); stateHolder.State = request; //Update the state OnLockUnlockCompleted?.Invoke(this, new LockUnlockEventArgs(stateHolder.OperationType, true)); } } else if (stateHolder.State.DispenserState == 2 && request.DispenserState == 3) { //Pump is unlocked due to unlock operation if (stateHolder.OperationType != LockUnlockOperation.None) { logger.Info($"Unlocking done!"); stateHolder.State = request; //Update the state OnLockUnlockCompleted?.Invoke(this, new LockUnlockEventArgs(stateHolder.OperationType, true)); } } } else if (stateHolder != null && stateHolder.OperationType == LockUnlockOperation.None) { if (stateHolder.State.DispenserState != request.DispenserState) { logger.Warn($"Observed a pump state change, {stateHolder.State.DispenserState} -> {request.DispenserState}"); stateHolder.State = request; //Update the state. } } } } public void Write(CardMessageBase cardMessage) { _context.Outgoing.Write(cardMessage); } public async Task WriteAsync(CardMessageBase request, Func responseCapture, int timeout) { var resp = await _context.Outgoing.WriteAsync(request, responseCapture, timeout); return resp; } #endregion #region IEnumerable implementation public IEnumerator GetEnumerator() { return pumpHandlers.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return pumpHandlers.GetEnumerator(); } #endregion public void PendMessage(CardMessageBase message) { lock (syncObj) { queue.Enqueue(message); } } public bool TrySendNextMessage() { lock (syncObj) { if (queue.Count > 0) { DebugLog($"queue count: {queue.Count}"); var message = queue.Dequeue(); Write(message); return true; } } return false; } public void StoreLatestFrameSqNo(int pumpId, byte frameSqNo) { var pump = GetPump(pumpId); if (pump != null) { pump.FrameSqNo = frameSqNo; } } public void UpdatePumpState(int pumpId, int logicId, LogicalDeviceState state) { var currentPump = GetPump(pumpId); currentPump?.FirePumpStateChange(state, Convert.ToByte(logicId)); } public void UpdateFuelingStatus(int pumpId, FdcTransaction fuelingTransaction) { var currentPump = GetPump(pumpId); currentPump?.FireFuelingStatusChange(fuelingTransaction); } private HengshanPumpHandler GetPump(int pumpId) { return pumpHandlers.FirstOrDefault(p => p.PumpId == pumpId); } public void SetRealPrice(int pumpId, int price) { var currentPump = GetPump(pumpId); var nozzle = currentPump?.Nozzles.FirstOrDefault(); if (nozzle != null) nozzle.RealPriceOnPhysicalPump = price; } #region Log methods private void InfoLog(string info) { logger.Info("PayTermHdlr " + info); } private void DebugLog(string debugMsg) { logger.Debug("PayTermHdlr " + debugMsg); } #endregion } public class HengshanPayTerminalHanlderGroupConfigV1 { public string PumpIds { get; set; } public List PumpSubAddresses { get; set; } } public class HengshanPayTerminalHanlderGroupConfigV2 { public string PumpIds { get; set; } public List PumpSubAddresses { get; set; } public List PumpNozzleLogicIds { get; set; } public List PumpSiteNozzleNos { get; set; } public List NozzleLogicIds { get; set; } } public class PumpSubAddress { public byte PumpId { get; set; } public byte SubAddress { get; set; } } public class PumpNozzleLogicId { public byte PumpId { get; set; } public string LogicIds { get; set; } } public class PumpSiteNozzleNo { public byte PumpId { get; set; } public string SiteNozzleNos { get; set; } } public class NozzleLogicId { public byte NozzleNo { get; set; } public byte LogicId { get; set; } } }