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
{
///
/// 市场方面希望将我们广州团队当初在“广东中石化加油机信息在线项目”中所开发的加油机上信息参数
///
[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;
///
/// by seconds
///
public int MaxPeriodNoSeeDispenserIncomingHttpRequest { get; set; } = 10;
public List DispenserInfoConfigs { get; set; }
}
public class DispenserInfoConfigV1
{
public string IpAddress { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
private class HttpClientInfo
{
///
/// ip:port, like: 192.168.1.10:4567
///
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();
///
/// the List is the message buffer, only a complete message is constructed, processing then start.
///
private List clientInfos = new List();
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();
this.logger = loggerFactory.CreateLogger("DynamicPrivate_DispenserPartsInfoRetriever");
}
public void Init(IEnumerable processors)
{
}
public async Task 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();
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();
//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();
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("The data send in Fcc is an illegal Json content"));
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();
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("I got you"));
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();
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();
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;
}
///
///
///
/// like input "01234", will return byte[2]: 0x00, byte[1]: 0x12, byte[0]: 0x34
///
public byte[] StringToBcdHex(string value)
{
if (value.Length % 2 != 0) value = "0" + value;
List output = new List();
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 Stop()
{
var universalApiHub = this.services.GetRequiredService();
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;
}
}
}