123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- using Edge.Core.Processor;
- using System;
- using Microsoft.EntityFrameworkCore;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Logging;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text.Json;
- using Edge.Core.UniversalApi;
- using Edge.Core.Processor.Dispatcher.Attributes;
- using System.Threading.Tasks;
- using System.Net.Sockets;
- using Microsoft.Extensions.Logging.Abstractions;
- using System.Net;
- using System.Threading;
- using Edge.Core.Parser.BinaryParser.Util;
- using System.IO;
- using System.Text;
- namespace DispenserPartsInfoRetriever
- {
- /// <summary>
- /// 市场方面希望将我们广州团队当初在“广东中石化加油机信息在线项目”中所开发的加油机上信息参数
- /// </summary>
- [UniversalApi(Name = OnAppStateChangeEventName, EventDataType = typeof(OnAppStateChangeEventArg), Description = "When the App overall State Changed, like app starts or stops, the event will be fired")]
- [UniversalApi(Name = OnDispenserConnStateChangeEventName, EventDataType = typeof(OnDispenserConnStateChangeEventArg), Description = "When communication state changed for certain dispensere, like the connection between a dispenser and fcc is established or broken, the event will be fired")]
- [UniversalApi(Name = OnDispenserPartsAlarmRetrievedEventName, EventDataType = typeof(OnDispenserPartsAlarmRetrievedEventArg), Description = "When parts alarm info is read from dispenser message, the event will be fired")]
- [MetaPartsDescriptor(
- "油机配件信息获取器",
- "获取油机配件信息,它基于http协议与油机通讯,采集油机端的较底层设备配件信息,并开放API供业务端应用.日志标记为DynamicPrivate_DispenserPartsInfoRetriever"
- , new[] { "Pump" })]
- public class App_HttpServer : IAppProcessor
- {
- public const string OnAppStateChangeEventName = "OnAppStateChange";
- public const string OnDispenserConnStateChangeEventName = "OnDispenserConnStateChange";
- public const string OnDispenserPartsAlarmRetrievedEventName = "OnDispenserPartsAlarmRetrieved";
- public class OnAppStateChangeEventArg
- {
- public string State { get; set; }
- public string Reason { get; set; }
- }
- public class OnDispenserConnStateChangeEventArg
- {
- public string DispenserName { get; set; }
- public string Description { get; set; }
- public string State { get; set; }
- public string Reason { get; set; }
- }
- public class OnDispenserPartsAlarmRetrievedEventArg
- {
- public string DispenserName { get; set; }
- public string DispenserDescription { get; set; }
- public string AlarmName { get; set; }
- public string AlarmDetail { get; set; }
- }
- public class AppConfigV1
- {
- public int ServerListenPort { get; set; } = 7788;
- /// <summary>
- /// by seconds
- /// </summary>
- public int MaxPeriodNoSeeDispenserIncomingHttpRequest { get; set; } = 10;
- public List<DispenserInfoConfigV1> DispenserInfoConfigs { get; set; }
- }
- public class DispenserInfoConfigV1
- {
- public string IpAddress { get; set; }
- public string Name { get; set; }
- public string Description { get; set; }
- }
- private class HttpClientInfo
- {
- /// <summary>
- /// ip:port, like: 192.168.1.10:4567
- /// </summary>
- public string HttpClientEndPointIdentity { get; }
- public HttpListenerContext httpClientContext { get; }
- public DispenserInfoConfigV1 BoundDispenserInfoConfig { get; }
- public HttpClientInfo(HttpListenerContext httpClientContext, AppConfigV1 appConfig)
- {
- this.httpClientContext = httpClientContext;
- this.HttpClientEndPointIdentity = this.httpClientContext.Request.LocalEndPoint.ToString();
- this.BoundDispenserInfoConfig = appConfig.DispenserInfoConfigs.FirstOrDefault(c =>
- HttpClientEndPointIdentity.StartsWith(c.IpAddress))
- ?? new DispenserInfoConfigV1()
- {
- Name = $"未命名油机({this.HttpClientEndPointIdentity})",
- IpAddress = this.HttpClientEndPointIdentity,
- Description = "此油机未在本地WebConsole中定义和配置名称"
- };
- }
- public DateTime LastMessageReceivedTime { get; set; }
- }
- private System.Timers.Timer timelyCheckDispenserIfOnlineTimer = new System.Timers.Timer();
- /// <summary>
- /// the List is the message buffer, only a complete message is constructed, processing then start.
- /// </summary>
- private List<HttpClientInfo> clientInfos = new List<HttpClientInfo>();
- private AppConfigV1 appConfig;
- private IServiceProvider services;
- private HttpListener httpServer;
- private ILogger logger = NullLogger.Instance;
- protected CancellationTokenSource cancelServerListeningCts;
- public string MetaConfigName { get; set; }
- [ParamsJsonSchemas("appCtorParamsJsonSchema")]
- public App_HttpServer(AppConfigV1 appConfig, IServiceProvider services)
- {
- this.appConfig = appConfig;
- this.services = services;
- var loggerFactory = services.GetRequiredService<ILoggerFactory>();
- this.logger = loggerFactory.CreateLogger("DynamicPrivate_DispenserPartsInfoRetriever");
- }
- public void Init(IEnumerable<IProcessor> processors)
- {
- }
- public async Task<bool> Start()
- {
- this.cancelServerListeningCts = new CancellationTokenSource();
- this.httpServer = new HttpListener();
- this.httpServer.Prefixes.Add($"http://*:{this.appConfig.ServerListenPort}/");
- this.logger.LogInformation($"Starting HttpServer for listening for All IP on port: {this.appConfig.ServerListenPort}");
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- try
- {
- this.httpServer.Start();
- }
- catch (Exception eee)
- {
- this.logger.LogInformation($"Failed to start HttpServer on port: {this.appConfig.ServerListenPort}, detail: {eee}");
- await universalApiHub.FirePersistGenericAlarm(this,
- new GenericAlarm()
- {
- Title = $"主程序启动失败",
- Severity = GenericAlarmSeverity.Error,
- Detail = $"主程序启动失败, 详情: {eee}",
- }, ga => ga.Detail);
- throw new InvalidOperationException($"Failed to start HttpServer on port: {this.appConfig.ServerListenPort}, detail: {eee}");
- }
- this.logger.LogInformation($" HttpServer started");
- await universalApiHub.FireEvent(this, OnAppStateChangeEventName, new OnAppStateChangeEventArg() { State = "Started", Reason = "app is started" });
- await universalApiHub.FirePersistGenericAlarm(this,
- new GenericAlarm()
- {
- Title = $"主程序启动成功",
- Severity = GenericAlarmSeverity.Information,
- Detail = $"主程序启动成功",
- }, ga => ga.Detail);
- _ = Task.Run(async () =>
- {
- while (true)
- {
- HttpListenerContext newHttpClientContext = null;
- try
- {
- newHttpClientContext = await Task.Run(() => this.httpServer.GetContextAsync(), this.cancelServerListeningCts.Token);
- }
- catch (TaskCanceledException tce)
- {
- this.logger.LogInformation("Fcc as the Http server is canceled due to CancellationTokenSource was set.");
- try
- {
- this.httpServer?.Stop();
- }
- catch { return; }
- finally { }
- }
- var newClientInfo = new HttpClientInfo(newHttpClientContext, this.appConfig) { LastMessageReceivedTime = DateTime.Now };
- this.logger.LogDebug($"A http client: { newClientInfo.BoundDispenserInfoConfig.Name } is connecting and sending in msg.");
- var existedClientInfo = this.clientInfos.FirstOrDefault(k => k.HttpClientEndPointIdentity == newClientInfo.HttpClientEndPointIdentity.ToString());
- if (existedClientInfo != null)
- { }
- else
- this.clientInfos.Add(newClientInfo);
- //var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- //await universalApiHub.FirePersistGenericAlarm(this,
- // new GenericAlarm()
- // {
- // Title = $"{newClientInfo.BoundDispenserInfoConfig?.Name}",
- // Severity = GenericAlarmSeverity.Information,
- // Detail = $"{newClientInfo.BoundDispenserInfoConfig?.Name} 联机成功",
- // }, ga => ga.Detail);
- //await universalApiHub.FireEvent(this, OnDispenserConnStateChangeEventName,
- // new OnDispenserConnStateChangeEventArg() { State = "Connected", DispenserName = $"{newHttpClientContext.Request.RemoteEndPoint.ToString()}", Reason = $"The dispenser with idenity: {newHttpClientContext.Request.RemoteEndPoint} is tcp connected to fcc" });
- JsonDocument jsonDocument;
- try
- {
- jsonDocument = await JsonDocument.ParseAsync(newClientInfo.httpClientContext.Request.InputStream);
- }
- catch (Exception eee)
- {
- this.logger.LogInformation($"The http client: {newClientInfo.HttpClientEndPointIdentity.ToString()} has fatal invalid json content that can't even pased as json, exception: {eee}");
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FirePersistGenericAlarm(this,
- new GenericAlarm()
- {
- Title = $"{newClientInfo.BoundDispenserInfoConfig?.Name}",
- Severity = GenericAlarmSeverity.Information,
- Detail = $"{newClientInfo.BoundDispenserInfoConfig?.Name} 发送至FCC的内容非法(非法的Json数据), 详情: {eee}",
- }, ga => ga.Detail);
- newHttpClientContext.Response.StatusCode = 400;
- newHttpClientContext.Response.OutputStream.Write(Encoding.UTF8.GetBytes("<HTML><BODY>The data send in Fcc is an illegal Json content</BODY></HTML>"));
- newHttpClientContext.Response.OutputStream.Close();
- continue;
- }
- if (logger.IsEnabled(LogLevel.Trace))
- {
- using (MemoryStream ms = new MemoryStream())
- {
- using (var writer = new Utf8JsonWriter(ms))
- {
- jsonDocument.WriteTo(writer);
- writer.Flush();
- ms.Position = 0;
- this.logger.LogTrace($"{ newClientInfo.BoundDispenserInfoConfig.Name } incoming msg:{new StreamReader(ms).ReadToEnd()}");
- }
- }
- }
- try
- {
- #region parse json doc
- var rootEle = jsonDocument.RootElement.GetProperty("dispenserRecord");
- //加油机基本信息
- var dispenserBasicInfo_property = rootEle.GetProperty("dispenserBasicInfo");
- //关键部件信息
- var keycomponentInfo_property = rootEle.GetProperty("keycomponentInfo");
- #region 加油机部件寿命信息
- var dispenserLifecycleInfo_property = rootEle.GetProperty("dispenserLifecycleInfo");
- //PSAM有效期
- var PSAMvalidityDate_array = dispenserLifecycleInfo_property.GetProperty("PSAMvalidityDate").EnumerateArray();
- foreach (var el in PSAMvalidityDate_array)
- {
- var nozzleNO = el.GetProperty("nozzleNO").GetString();
- var validityDate = el.GetProperty("validityDate").GetString();
- }
- //读卡器开关次数
- var ReaderLifecycleInfo_array = dispenserLifecycleInfo_property.GetProperty("ReaderLifecycleInfo").EnumerateArray();
- foreach (var el in ReaderLifecycleInfo_array)
- {
- var nozzleNO = el.GetProperty("nozzleNO").GetString();
- var valveCycle = el.GetProperty("valveCycle").GetString();
- }
- //流量计升数
- var meterLifecycleInfo_array = dispenserLifecycleInfo_property.GetProperty("meterLifecycleInfo").EnumerateArray();
- foreach (var el in meterLifecycleInfo_array)
- {
- var nozzleNO = el.GetProperty("nozzleNO").GetString();
- var meterSN = el.GetProperty("meterSN").GetString();
- var meterVolume = el.GetProperty("meterVolume").GetString();
- }
- //电磁阀开关次数
- var valveLifecycleInfo_array = dispenserLifecycleInfo_property.GetProperty("valveLifecycleInfo").EnumerateArray();
- foreach (var el in valveLifecycleInfo_array)
- {
- var nozzleNO = el.GetProperty("nozzleNO").GetString();
- var valveNum = el.GetProperty("valveNum").GetString();
- var valveCycle = el.GetProperty("valveCycle").GetString();
- }
- #endregion
- #region 设备定期检修记录
- var periodicalmaintenanceInfo_property = rootEle.GetProperty("periodicalmaintenanceInfo");
- var hydraulicsystemPMDate = periodicalmaintenanceInfo_property.GetProperty("hydraulicsystemPMDate").GetString();
- var controlsystemPMDate = periodicalmaintenanceInfo_property.GetProperty("controlsystemPMDate").GetString();
- var gasrecoverysystemPMDate = periodicalmaintenanceInfo_property.GetProperty("gasrecoverysystemPMDate").GetString();
- #endregion
- #region 加油机报警信息
- var dispenserAlarm_property = rootEle.GetProperty("dispenserAlarm");
- var nozzleStatus_array = dispenserAlarm_property.GetProperty("nozzleStatus").EnumerateArray();
- foreach (var el in nozzleStatus_array)
- {
- var nozzleNO = el.GetProperty("nozzleNO").GetString();
- var zerotransactionAlarm = el.GetProperty("zerotransactionAlarm").GetString();
- var powerAlarm = el.GetProperty("powerAlarm").GetString();
- var fuelingspeedAlarm = el.GetProperty("fuelingspeedAlarm").GetString();
- var encoderAlarm = el.GetProperty("encoderAlarm").GetString();
- var prestovershootAlarm = el.GetProperty("prestovershootAlarm").GetString();
- var lockAlarm = el.GetProperty("lockAlarm").GetString();
- var valveAlarm = el.GetProperty("valveAlarm").GetString();
- var leakageAlarm = el.GetProperty("leakageAlarm").GetString();
- var nozzleoffbootAlarm = el.GetProperty("nozzleoffbootAlarm").GetString();
- var vaporrecoveryRatioAlarm = el.GetProperty("vaporrecoveryRatioAlarm").GetString();
- }
- var taxchipAlarm_array = dispenserAlarm_property.GetProperty("taxchipAlarm").EnumerateArray();
- foreach (var el in taxchipAlarm_array)
- {
- var nozzleNO = el.GetProperty("nozzleNO").GetString();
- var taxchipAlarmCode = el.GetProperty("taxchipAlarmCode").GetString();
- }
- #endregion
- //智能锁信息
- var locker_property = rootEle.GetProperty("locker");
- var lockerStatus_array = locker_property.GetProperty("lockerStatus").EnumerateArray();
- foreach (var el in lockerStatus_array)
- {
- }
- #endregion
- }
- catch (Exception eeee)
- {
- this.logger.LogInformation($"The tcp client: {newClientInfo.HttpClientEndPointIdentity.ToString()} has partial error json data, exception: {eeee}");
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FirePersistGenericAlarm(this,
- new GenericAlarm()
- {
- Title = $"{newClientInfo.BoundDispenserInfoConfig?.Name}",
- Severity = GenericAlarmSeverity.Information,
- Detail = $"{newClientInfo.BoundDispenserInfoConfig?.Name} 发送至FCC的内容缺失某些字段(Json格式整体合法,但缺少某些字段或者内部细节格式出错), 详情: {eeee}",
- }, ga => ga.Detail);
- }
- finally
- {
- newHttpClientContext.Response.StatusCode = 200;
- newHttpClientContext.Response.OutputStream.Write(Encoding.UTF8.GetBytes("<HTML><BODY>I got you</BODY></HTML>"));
- newHttpClientContext.Response.OutputStream.Close();
- }
- }
- });
- this.timelyCheckDispenserIfOnlineTimer.Elapsed += async (s, a) =>
- {
- var connectedDispenserInfo = this.appConfig.DispenserInfoConfigs.Join(this.clientInfos, diConfig => diConfig.IpAddress, tci => tci.HttpClientEndPointIdentity.Split(':').First(), (tci, diConfig) => tci);
- var disconnectedDispenserInfo = this.appConfig.DispenserInfoConfigs.Except(connectedDispenserInfo);
- foreach (var di in disconnectedDispenserInfo)
- {
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.CloseAndFirePersistGenericAlarm(this,
- new GenericAlarm()
- {
- Title = $"{di.Name ?? ""} 从未主动联入",
- Severity = GenericAlarmSeverity.Error,
- Detail = $"{di.Name ?? ""} ({di.Description}) 未见此油机(本地配置其IP为: {di.IpAddress})主动通过 HTTP 连入 FCC"
- }, ga => ga.Detail, "new one is on creating", ga => ga.Detail);
- await universalApiHub.FireEvent(this, OnDispenserConnStateChangeEventName,
- new OnDispenserConnStateChangeEventArg()
- {
- State = "Disconnected",
- DispenserName = $"{di.Name ?? $""}",
- Description = $"{di.Description ?? ""}",
- Reason = $"have not see the dispenser(local config its ip to: {di.IpAddress}) actively connected in for a while"
- });
- }
- if (!this.clientInfos.Any()) return;
- var longTimeNoSeeClientInfos =
- this.clientInfos.Where(tci => DateTime.Now.Subtract(tci.LastMessageReceivedTime).TotalSeconds >= this.appConfig.MaxPeriodNoSeeDispenserIncomingHttpRequest).ToArray();
- foreach (var ci in longTimeNoSeeClientInfos)
- {
- try
- {
- this.logger.LogInformation($"{ci.BoundDispenserInfoConfig?.Name} long time no see its msg(上次发入时间为:{ci.LastMessageReceivedTime})");
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.CloseAndFirePersistGenericAlarm(this,
- new GenericAlarm()
- {
- Title = $"{ci.BoundDispenserInfoConfig.Name} 一段时间未见其发入消息",
- Severity = GenericAlarmSeverity.Error,
- Detail = $"{ci.BoundDispenserInfoConfig.Name} ({ci.BoundDispenserInfoConfig.Description}) 很久未见此油机(本地配置其IP为: {ci.HttpClientEndPointIdentity})发入消息"
- }, ga => ga.Detail, "new one is on creating", ga => ga.Detail);
- await universalApiHub.FireEvent(this, OnDispenserConnStateChangeEventName,
- new OnDispenserConnStateChangeEventArg()
- {
- State = "Disconnected",
- DispenserName = $"{ci.BoundDispenserInfoConfig.Name ?? $""}",
- Description = $"{ci.BoundDispenserInfoConfig.Description ?? ""}",
- Reason = $"have not see the dispenser(local config its ip to: {ci.HttpClientEndPointIdentity}) actively send in msg for a while(上次发入时间为:{ci.LastMessageReceivedTime})"
- });
- }
- catch { }
- finally
- {
- //this.clientInfos.Remove(ci);
- }
- }
- };
- this.timelyCheckDispenserIfOnlineTimer.Interval = 30000;
- this.timelyCheckDispenserIfOnlineTimer.Start();
- return true;
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="value">like input "01234", will return byte[2]: 0x00, byte[1]: 0x12, byte[0]: 0x34</param>
- /// <returns></returns>
- public byte[] StringToBcdHex(string value)
- {
- if (value.Length % 2 != 0) value = "0" + value;
- List<byte> output = new List<byte>();
- for (int i = value.Length - 1; i >= 0; i = i - 2)
- {
- output.Add((byte)(byte.Parse(value[i - 1].ToString()) * 16 + byte.Parse(value[i].ToString())));
- }
- output.Reverse();
- return output.ToArray();
- }
- public async Task<bool> Stop()
- {
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FireEvent(this, OnAppStateChangeEventName, new OnAppStateChangeEventArg() { State = "Stopped", Reason = "app is stopping" });
- try { this.httpServer?.Stop(); } catch { }
- try { this.timelyCheckDispenserIfOnlineTimer?.Stop(); } catch { }
- return true;
- }
- }
- }
|