123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 |
- 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
- {
- /// <summary>
- /// Handler that communicates directly with the Hengshan Payment Terminal for card handling and pump handling via serial port.
- /// </summary>
- [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<IFdcPumpController>, IDeviceHandler<byte[], CardMessageBase>
- {
- #region Fields
- private string pumpIds;
- private string pumpSubAddresses;
- private string pumpNozzles;
- private string pumpSiteNozzleNos;
- private string nozzleLogicIds;
- private IContext<byte[], CardMessageBase> _context;
- private List<HengshanPumpHandler> pumpHandlers = new List<HengshanPumpHandler>();
- public Queue<CardMessageBase> queue = new Queue<CardMessageBase>();
- private object syncObj = new object();
- private ConcurrentDictionary<int, PumpStateHolder> statusDict = new ConcurrentDictionary<int, PumpStateHolder>();
- public ConcurrentDictionary<int, PumpStateHolder> PumpStatusDict => statusDict;
- private Dictionary<int, int> pumpIdSubAddressDict;
- public Dictionary<int, List<int>> PumpNozzlesDict { get; private set; }
- public Dictionary<int, int> NozzleLogicIdDict { get; private set; }
- public Dictionary<int, List<int>> 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<object> 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<string, object> parameters)
- {
- logger.Info("OnFdcServerInit called");
- if (parameters.ContainsKey("LastPriceChange"))
- {
- // nozzle logical id:rawPrice
- var lastPriceChanges = parameters["LastPriceChange"] as Dictionary<byte, int>;
- foreach (var priceChange in lastPriceChanges)
- {
- }
- }
- }
- #region Event handler
- public event EventHandler<TerminalMessageEventArgs> OnTerminalMessageReceived;
- public event EventHandler<TotalizerDataEventArgs> OnTotalizerReceived;
- public event EventHandler<FuelPriceChangeRequestEventArgs> OnFuelPriceChangeRequested;
- public event EventHandler<FuelPriceDownloadRequestedEventArgs> OnTerminalFuelPriceDownloadRequested;
- public event EventHandler<CheckCommandEventArgs> OnCheckCommandReceived;
- public event EventHandler<LockUnlockEventArgs> OnLockUnlockCompleted;
- #endregion
- #region Properties
- public List<int> AssociatedPumpIds { get; private set; }
- public IContext<byte[], CardMessageBase> 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<int> GetPumpIdList(string pumpIds)
- {
- var pumpIdList = new List<int>();
- 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> { int.Parse(pumpIds) };
- }
- else
- {
- throw new ArgumentException("Pump id list not specified!");
- }
- }
- private Dictionary<int, int> InitializePumpSubAddressMapping()
- {
- var dict = new Dictionary<int, int>();
- 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<int, List<int>> ParsePumpNozzlesList(string pumpNozzles)
- {
- Dictionary<int, List<int>> pumpNozzlesDict = new Dictionary<int, List<int>>();
- if (!string.IsNullOrEmpty(pumpNozzles) && pumpNozzles.Contains(';'))
- {
- var arr = pumpNozzles.Split(';');
- foreach (var subMapping in arr)
- {
- var pair = new KeyValuePair<int, int>(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<int> { pair.Value });
- }
- else
- {
- List<int> 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> { 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<int, List<int>> ParsePumpSiteNozzleNoList(string pumpSiteNozzleNos)
- {
- Dictionary<int, List<int>> pumpSiteNozzleNoDict = new Dictionary<int, List<int>>();
- if (!string.IsNullOrEmpty(pumpSiteNozzleNos) && pumpSiteNozzleNos.Contains(';'))
- {
- var arr = pumpSiteNozzleNos.Split(';');
- foreach (var subMapping in arr)
- {
- var pair = new KeyValuePair<int, List<int>>(
- 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> { 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<int, int> InitializeNozzleLogicIdMapping(string nozzleLogicIds)
- {
- var dict = new Dictionary<int, int>();
- 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<int> GetNozzleListForPump(int pumpId)
- {
- List<int> 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<IFdcPumpController> IEnumerable<IFdcPumpController>.GetEnumerator()
- {
- return pumpHandlers.GetEnumerator();
- }
- #endregion
- #region IHandler implementation
- public void Init(IContext<byte[], CardMessageBase> context)
- {
- CommIdentity = context.Processor.Communicator.Identity;
- _context = context;
- }
- public string CommIdentity { get; private set; }
- public async Task Process(IContext<byte[], CardMessageBase> 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<CardMessageBase> WriteAsync(CardMessageBase request, Func<CardMessageBase, CardMessageBase, bool> responseCapture,
- int timeout)
- {
- var resp = await _context.Outgoing.WriteAsync(request, responseCapture, timeout);
- return resp;
- }
- #endregion
- #region IEnumerable<IFdcPumpController> implementation
- public IEnumerator<IFdcPumpController> 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<PumpSubAddress> PumpSubAddresses { get; set; }
- }
- public class HengshanPayTerminalHanlderGroupConfigV2
- {
- public string PumpIds { get; set; }
- public List<PumpSubAddress> PumpSubAddresses { get; set; }
- public List<PumpNozzleLogicId> PumpNozzleLogicIds { get; set; }
- public List<PumpSiteNozzleNo> PumpSiteNozzleNos { get; set; }
- public List<NozzleLogicId> 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; }
- }
- }
|