12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679 |
- using Application.VaporRecoveryOnlineWatchHubApp.UnversalApiModels;
- using AutoMapper;
- using Edge.Core.Database;
- using Edge.Core.Processor;
- using Edge.Core.IndustryStandardInterface.Pump;
- using Edge.Core.UniversalApi;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Logging;
- using Microsoft.Extensions.Logging.Abstractions;
- using Wayne_VaporRecoveryDataCollectorBoard;
- using Wayne_VaporRecoveryDataCollectorBoard.MessageEntity;
- using Wayne_VaporRecoveryDataCollectorBoard.MessageEntity.Incoming;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text.Json;
- using System.Threading;
- using System.Threading.Tasks;
- using VaporRecoveryOnlineWatchHubApp;
- using VaporRecoveryOnlineWatchHubApp.Config;
- using VaporRecoveryOnlineWatchHubApp.UnversalApiModels;
- using Edge.Core.Database.Models;
- using Edge.Core.Processor.Dispatcher.Attributes;
- using System.Collections.Concurrent;
- using Aliyun.Acs.Core.Profile;
- using Aliyun.Acs.Core;
- using Aliyun.Acs.Dm.Model.V20151123;
- using Aliyun.Acs.Core.Exceptions;
- using Edge.Core;
- using System.Collections.Specialized;
- using static Wayne_VaporRecoveryDataCollectorBoard.GroupHandler;
- namespace Application.VaporRecoveryOnlineWatchHubApp
- {
- [UniversalApi(Name = OnVaporRecoveryDataCollectorBoardStateChangeEventName, EventDataType = typeof(Board), Description = "Subscribe this event for High level state of the board, if it's get initialized or not.")]
- [UniversalApi(Name = OnVaporRecoveryDataCollectorBoardNozzleFlowDataReadEventName, EventDataType = typeof(VRBoardNozzleTrxFlowData),
- Description = "2 cases of this event will fire: " +
- "1. when VR board detect the nozzle is on fuelling, it'll periodically report the real time vapor and liquid values, but the property 'FuellingStartTime' and 'FuellingEndTime' are of course null." +
- "2. when VR board detect a trx has done on certain nozzle, it'll report a single flow data for it, with property 'FuellingStartTime' and 'FuellingEndTime' with concrete values.")]
- [UniversalApi(Name = OnVaporRecoveryDataCollectorBoardNozzleStateChangeEventName, EventDataType = typeof(VRBoardNozzle), Description = "Notify the nozzle fuelling state change.")]
- [UniversalApi(Name = OnVaporRecoveryDataCollectorBoardAlarmsEventName, EventDataType = typeof(VRBoardAlarmListEventArgs), Description = "Indeed the App alarms, with alarm details.")]
- //[UniversalApi(Name = GenericAlarm.UniversalApiEventName, EventDataType = typeof(GenericAlarm[]), Description = "Fire GenericAlarms to AlarmBar for attracting users.")]
- [MetaPartsDescriptor(
- "lang-zh-cn:油气回收在线监测系统lang-zh-tw:油氣回收在線監測系統lang-en-us:Vapor recovery online watch system",
- "lang-zh-cn:油气回收在线监测系统, 用于链接多个气液比收集板,以及多个压力表和多个气体浓度表。日志名称: DynamicPrivate_VaporRecoveryOnlineWatchHubApp" +
- "lang-zh-tw:油氣回收在線監測系統, 用於鏈接多個氣液比收集板,以及多個壓力表和多個氣體濃度表" +
- "lang-en-us:油气回收在线监测系统, 用于链接多个气液比收集板,以及多个压力表和多个气体浓度表。日志名称: DynamicPrivate_VaporRecoveryOnlineWatchHubApp",
- new[] { "lang-zh-cn:在线监测lang-zh-tw:在線監測lang-en-us:OnlineWatch" })]
- public class App : IAppProcessor
- {
- public const string OnVaporRecoveryDataCollectorBoardStateChangeEventName = "OnVaporRecoveryDataCollectorBoardStateChange";
- public const string OnVaporRecoveryDataCollectorBoardNozzleStateChangeEventName = "OnVaporRecoveryDataCollectorBoardNozzleStateChange";
- public const string OnVaporRecoveryDataCollectorBoardNozzleFlowDataReadEventName = "OnVaporRecoveryDataCollectorBoardNozzleFlowDataRead";
- public const string OnVaporRecoveryDataCollectorBoardAlarmsEventName = "OnVaporRecoveryDataCollectorBoardAlarms";
- public string MetaConfigName { get; set; }
- private IServiceProvider services { get; }
- private ILogger logger = NullLogger.Instance;
- /// <summary>
- /// device handlers for fuel air&liquid 气液比控制板 data collector boards.
- /// </summary>
- public IEnumerable<Wayne_VaporRecoveryDataCollectorBoard.GroupHandler> vrBoardDeviceHandlers { get; set; }
- private AppConfigV1 appConfig;
- private IMapper objMapper;
- //private FdcServerHostApp fdcServerHostApp;
- //private bool isStopped = false;
- //private UploadingConfig uploadingConfig;
- private Timer dailyVRBoardNotifyWarningTimer;
- private Timer dailyVRBoardCheckingStateTimer;
- private Timer minutelySaveDbTimer;
- /// <summary>
- /// VR Board will watch on each nozzels, and for user side, they care about the nozzles, thus here bring in a logical vr nozzle concept.
- /// </summary>
- private List<VRBoardNozzle> vrBoardNozzles = null;
- private VRBoardDbHelper dbHelperForVRBoard;
- private readonly ConcurrentDictionary<string, Dictionary<string, object>> jsonDataDic = new ConcurrentDictionary<string, Dictionary<string, object>>();
- private Timer dailyTankPressureWarningTimer;
- private DateTime lastTankPressureWarningTime;
- private DateTime lastTankPressureWarningDate;
- private DateTime lastLowTankPressureWarningTime;
- private DateTime lastHighTankPressureWarningTime;
- private int tankPressureWarningTotalDays = 0;
- private DateTime lastLiquidPressureWarningTime;
- private DateTime lastGasConcentrationWarningTime;
- private StringDictionary alarmEmailsDic = new StringDictionary();
- #region app config
- public class StationMiscConfigV1 //: BaseConfig
- {
- public string StationName { get; set; }
- public string StationAddress { get; set; }
- public NotificationMailConfigV1 NotificationMailConfig { get; set; }
- }
- public class PerNozzleVRConfigV1
- {
- public PerNozzleVRGroupQualificationDefinitionV1[] QualificationDefinitions { get; set; }
- }
- //public enum PerNozzleVRGroupQualificationTypeEnum
- //{
- // 汽車,
- // 摩托車
- //}
- public class PerNozzleVRGroupQualificationDefinitionV1
- {
- public string GroupName { get; set; }
- public string NozzleIdsString { get; set; }
- public double LiquidVolumeMinThreshold { get; set; }
- public double QualifiedAirLiquidRatioMin { get; set; }
- public double QualifiedAirLiquidRatioMax { get; set; }
- public double UnqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold { get; set; }
- }
- public class NotificationMailConfigV1
- {
- public string Host { get; set; }
- public string RegionId { get; set; }
- public string AccessKeyId { get; set; }
- public string AccessKeySecret { get; set; }
- public string ApiVersion { get; set; }
- public string[] ReceiverAddresses { get; set; }
- }
- public class VaporRecoveryConfigV1 //: BaseConfig
- {
- /// <summary>
- /// only liquid volume read from board has its value >= this value will be treat as a valid read, and will be persist to db.
- /// the value is with decimal points, by Litre.
- /// </summary>
- public double LiquidVolumeMinThreshold { get; set; }
- /// <summary>
- /// the decimal point value for liquid volume read from board.
- /// </summary>
- public byte LiquidVolumeDigits { get; set; }
- /// <summary>
- /// the decimal point value for air volume read from board.
- /// </summary>
- public byte AirVolumeDigits { get; set; }
- /// <summary>
- /// 位于此值范围内的气液比值则认为是工作正常
- /// 下限
- /// </summary>
- public double QualifiedAirLiquidRatioMin { get; set; } = 0.83;
- /// <summary>
- /// 位于此值范围内的气液比值则认为是工作正常
- /// 上限
- /// </summary>
- public double QualifiedAirLiquidRatioMax { get; set; } = 1.35;
- /// <summary>
- /// 在最近的时间段time_range_by_hour内,当“非正常”气液比值 占比超过e%,将触发状态由 正常 转换至 预警
- /// </summary>
- public double TurnToWarningStateInRecentHoursThreshold { get; set; } = 24;
- /// <summary>
- /// 当“非正常”气液比值 占比超过 WarningThresthold%,将触发状态由 正常 转换至 预警, 默认25%
- /// </summary>
- public double UnqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold { get; set; } = 25;
- /// <summary>
- /// 当某把处于“预警状态”warning的加油枪持续保持了此值表示的天数后,将使此把油枪的状态转换至”报警”alarm.
- /// </summary>
- public double WarningToAlarmStateLastingDaysThreshold { get; set; } = 5;
- /// <summary>
- /// 即在时间time_in_day_with_24hourStyle时,如果存在预、报警枪,则播放相应的语音或者音乐。
- /// 可输入值是24小时制中的小时以及分,比如:13.10即代表13点10进行播报。
- /// </summary>
- public string AlarmPlayingTimeInDayWith24HourStyle { get; set; } = "13.10";
- /// <summary>
- /// 预警:在 24h(自然日,即过0点,则重新从此0点开始读数,此0点前的数据不再计入分析)内,在线监控设备监测每条加油枪的有效气液比(每次连续加油量大 于 15L)小于 1.00 或大于 1.20 的次数超过总次数的 25%(此比例值应可以配置,后续章节将提到)时,在线监控设备应预警。
- /// 应注意的是,对某天进行预警、报警状态判断的时间应是每天的23:59:59秒
- /// 检测是每天定点检查一次,而不是实时的
- /// </summary>
- public string RetrospectiveWarningCheckingTime { get; set; } = "24.00";
- /// <summary>
- /// 气液比最多保存最近多少天的。
- /// </summary>
- public int RecordMaxKeepDays { get; set; } = 800;
- public PerNozzleVRConfigV1 PerNozzleVRConfig { get; set; }
- public override string ToString()
- {
- return GetType().GetProperties().Aggregate(string.Empty,
- (result, next) => result + $"{next.Name} : {(next.GetValue(this)?.ToString()) ?? string.Empty} ");
- }
- }
- public class TankPressureGaugeMeterConfigV1
- {
- /// <summary>
- /// link to the device handler as there could have multiple PressureGauge Meters in system,
- /// so the device handler must have a correlated data for linkage.
- /// </summary>
- public string MeterDeviceHandlerIdentity { get; set; }
- /// <summary>
- /// name of this meter, will displayed in UI for help the user know what's this meter for.
- /// </summary>
- public string MeterName { get; set; }
- /// <summary>
- /// detail purpose of this meter, will displayed in UI for help the user know what's this meter for.
- /// </summary>
- public string Description { get; set; }
- /// <summary>
- /// 低压报警阈值
- /// </summary>
- public double LowPressureWarningThreshold { get; set; }
- /// <summary>
- /// 高压报警阈值
- /// </summary>
- public double HighPressureWarningTheshold { get; set; }
- /// <summary>
- /// 0 压报警阈值
- /// </summary>
- public string ZeroPressureWarningThreshold { get; set; }
- /// <summary>
- /// 0 压转换至预警状态的连续时间窗口大小。
- /// </summary>
- public int TurnToWarningStateInRecentHoursThreshold { get; set; } = 12;
- /// <summary>
- /// 高压预警转报警天数。
- /// </summary>
- public int WarningToAlarmStateLastingDaysThreshold { get; set; } = 7;
- /// <summary>
- /// 油罐压力报警开关。
- /// </summary>
- public bool AlarmIsEnabled { get; set; } = true;
- /// <summary>
- /// 压力值最多保存最近多少天的。
- /// </summary>
- public int RecordMaxKeepDays { get; set; } = 800;
- }
- public class LiquidPressureGaugeMeterConfigV1
- {
- /// <summary>
- /// link to a device handler as there could have multiple LiquidPressureGauge Meters in system,
- /// so the device handler must have a correlated data for linkage.
- /// </summary>
- public string MeterDeviceHandlerIdentity { get; set; }
- /// <summary>
- /// name of this meter, will displayed in UI for help the user know what's this meter for.
- /// </summary>
- public string MeterName { get; set; }
- /// <summary>
- /// detail purpose of this meter, will displayed in UI for help the user know what's this meter for.
- /// </summary>
- public string Description { get; set; }
- /// <summary>
- /// 高压预警阈值
- /// </summary>
- public double HighPressureWarningTheshold { get; set; }
- /// <summary>
- /// 高压报警阈值
- /// </summary>
- public double HighPressureAlarmTheshold { get; set; }
- /// <summary>
- /// 高压预警转报警天数。
- /// </summary>
- public int WarningToAlarmStateLastingDaysThreshold { get; set; } = 5;
- }
- public class GasConcentrationGaugeMeterConfigV1
- {
- /// <summary>
- /// link to a device handler as there could have multiple GasConcentrationGauge Meters in system,
- /// so the device handler must have a correlated data for linkage.
- /// </summary>
- public string MeterDeviceHandlerIdentity { get; set; }
- /// <summary>
- /// name of this meter, will displayed in UI for help the user know what's this meter for.
- /// </summary>
- public string MeterName { get; set; }
- /// <summary>
- /// detail purpose of this meter, will displayed in UI for help the user know what's this meter for.
- /// </summary>
- public string Description { get; set; }
- /// <summary>
- /// 高浓度预警阈值
- /// </summary>
- public double HighConcentrationWarningTheshold { get; set; }
- /// <summary>
- /// 高浓度报警阈值
- /// </summary>
- public double HighConcentrationAlarmTheshold { get; set; }
- /// <summary>
- /// 高浓度预警转报警天数。
- /// </summary>
- public int WarningToAlarmStateLastingDaysThreshold { get; set; } = 7;
- }
- public class AppConfigV1
- {
- public StationMiscConfigV1 StationMiscConfig { get; set; }
- public VaporRecoveryConfigV1 VaporRecoveryConfig { get; set; }
- public TankPressureGaugeMeterConfigV1 TankPressureGaugeMeterConfig { get; set; }
- public LiquidPressureGaugeMeterConfigV1 LiquidPressureGaugeMeterConfig { get; set; }
- public List<GasConcentrationGaugeMeterConfigV1> GasConcentrationGaugeMeterConfigs { get; set; }
- }
- #endregion
- [ParamsJsonSchemas("appCtorParamsJsonSchema")]
- public App(AppConfigV1 appConfig, IServiceProvider services)
- {
- this.services = services;
- this.appConfig = appConfig;
- if (services != null)
- {
- var loggerFactory = services.GetRequiredService<ILoggerFactory>();
- logger = loggerFactory.CreateLogger("DynamicPrivate_VaporRecoveryOnlineWatchApp");
- objMapper = this.services.GetRequiredService<IMapper>();
- dbHelperForVRBoard = new VRBoardDbHelper(services);
- }
- }
- [UniversalApi(Description = "Get the Pressure data of the pressure gage.")]
- public async Task<ConcurrentDictionary<string, Dictionary<string, object>>> GetPressureAsync()
- {
- return await Task.FromResult(jsonDataDic);
- }
- private async Task FireOrCloseAlarm(IProcessor source, string title, string category, GenericAlarmSeverity severity, string detail, bool isClose = false)
- {
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- if (isClose)
- {
- await universalApiHub?.ClosePersistGenericAlarms(source, detail, "问题已解决, 所以关闭。");
- }
- else
- {
- await universalApiHub?.FirePersistGenericAlarm(source, new GenericAlarm()
- {
- Title = title,
- Category = category,
- Severity = severity,
- Detail = detail
- }, ga => ga.Title);
- }
- }
- private async Task ProcessPressureDataAsync(object sender, string message)
- {
- var source = sender as IProcessor;
- var responseDic = JsonSerializer.Deserialize(message, typeof(Dictionary<string, object>)) as Dictionary<string, object>;
- if (responseDic.ContainsKey("Pressure"))
- {
- double pressure = double.Parse(responseDic["Pressure"].ToString());
- string id = responseDic["DeviceAddress"].ToString();
- string key = string.Empty;
- var tankPressureConfig = this.appConfig.TankPressureGaugeMeterConfig;
- var liquidPressureConfig = this.appConfig.LiquidPressureGaugeMeterConfig;
- //在 24 小时(自然天)内,在线监控系统监测到的系统压力与大气压差 值(表压)处于(-50~50)Pa 范围内的连续时间超过 12 小时,系统应预警
- if (tankPressureConfig != null && id == tankPressureConfig.MeterDeviceHandlerIdentity)
- {
- var zeros = tankPressureConfig.ZeroPressureWarningThreshold.Split('~');
- responseDic["CurrentPointer"] = "high";
- if (pressure >= double.Parse(zeros[0]) && pressure <= double.Parse(zeros[1]))
- {
- responseDic["CurrentPointer"] = "zero";
- if (lastTankPressureWarningTime.Year == 1)
- {
- lastTankPressureWarningTime = DateTime.Now;
- await SaveOrUpdateDbAsync("LastTankPressureWarningTime", new GenericData()
- {
- Type = "LastTankPressureWarningTime",
- CreatedTimeStamp = lastTankPressureWarningTime
- });
- }
- else if ((DateTime.Now - lastTankPressureWarningTime).TotalHours > tankPressureConfig.TurnToWarningStateInRecentHoursThreshold)
- {
- if (!lastTankPressureWarningDate.Equals(DateTime.Today))
- {
- tankPressureWarningTotalDays++;
- lastTankPressureWarningDate = DateTime.Today;
- await SaveOrUpdateDbAsync("LastTankPressureWarningDate", new GenericData()
- {
- Type = "LastTankPressureWarningDate",
- CreatedTimeStamp = lastTankPressureWarningDate,
- IntProperty0 = tankPressureWarningTotalDays
- });
- }
- if (tankPressureWarningTotalDays < tankPressureConfig.WarningToAlarmStateLastingDaysThreshold)
- {
- await FireOrCloseAlarm(source, "lang-zh-cn:油罐压力零压lang-zh-tw:油罐壓力零壓", "lang-zh-cn:油罐压力lang-zh-tw:油罐壓力",
- GenericAlarmSeverity.Warning, $"lang-zh-cn:油罐压力零压预警lang-zh-tw:油罐壓力零壓預警 {pressure:F2}");
- }
- else
- {
- if (tankPressureConfig.AlarmIsEnabled)
- {
- await FireOrCloseAlarm(source, "lang-zh-cn:油罐压力零压lang-zh-tw:油罐壓力零壓", "lang-zh-cn:油罐压力lang-zh-tw:油罐壓力",
- GenericAlarmSeverity.Error, $"lang-zh-cn:油罐压力零压报警lang-zh-tw:油罐壓力零壓報警 {pressure:F2}");
- if (!alarmEmailsDic.ContainsKey("油罐压力零压"))
- {
- alarmEmailsDic["油罐压力零压"] = "";
- await TrySendAlarmEmailsAsync(
- $"lang-zh-cn:在线监测系统异常情况报告 - {this.appConfig.StationMiscConfig?.StationName ?? ""}lang-zh-tw:在線監測系統異常情況報告 - {this.appConfig.StationMiscConfig?.StationName ?? ""}".LocalizedContent("zh-tw"),
- $"lang-zh-cn:油罐压力零压 {pressure:F2}<b>连续多日</b>不合格lang-zh-tw:油罐壓力零壓 {pressure:F2}<b>連續多日</b>不合格".LocalizedContent("zh-tw"));
- }
- }
- }
- }
- }
- else if (pressure < tankPressureConfig.LowPressureWarningThreshold)
- {
- if (lastLowTankPressureWarningTime.Year == 1)
- {
- lastLowTankPressureWarningTime = DateTime.Now;
- await SaveOrUpdateDbAsync("LastLowTankPressureWarningTime", new GenericData
- {
- Type = "LastLowTankPressureWarningTime",
- CreatedTimeStamp = lastLowTankPressureWarningTime
- });
- }
- else if ((DateTime.Now - lastLowTankPressureWarningTime).TotalMinutes > 5.0)
- {
- responseDic["CurrentPointer"] = "low";
- await FireOrCloseAlarm(source, "lang-zh-cn:油罐压力负压lang-zh-tw:油罐壓力負壓", "lang-zh-cn:油罐压力lang-zh-tw:油罐壓力",
- GenericAlarmSeverity.Error, $"lang-zh-cn:油罐压力负压报警lang-zh-tw:油罐壓力負壓報警 {pressure:F2}");
- if (!alarmEmailsDic.ContainsKey("油罐压力负压"))
- {
- alarmEmailsDic["油罐压力负压"] = "";
- await TrySendAlarmEmailsAsync(
- $"lang-zh-cn:在线监测系统异常情况报告 - {this.appConfig.StationMiscConfig?.StationName ?? ""}lang-zh-tw:在線監測系統異常情況報告 - {this.appConfig.StationMiscConfig?.StationName ?? ""}".LocalizedContent("zh-tw"),
- $"lang-zh-cn:油罐压力负压 {pressure:F2}<b>超过5分钟</b>不合格lang-zh-tw:油罐壓力負壓 {pressure:F2}<b>超過5分鐘</b>不合格".LocalizedContent("zh-tw"));
- }
- }
- else
- {
- responseDic["CurrentPointer"] = "zero";
- await FireOrCloseAlarm(source, "lang-zh-cn:油罐压力负压lang-zh-tw:油罐壓力負壓", "lang-zh-cn:油罐压力lang-zh-tw:油罐壓力",
- GenericAlarmSeverity.Warning, $"lang-zh-cn:油罐压力负压预警lang-zh-tw:油罐壓力負壓預警 {pressure:F2}");
- }
- }
- else if (pressure > tankPressureConfig.HighPressureWarningTheshold)
- {
- if (lastHighTankPressureWarningTime.Year == 1)
- {
- lastHighTankPressureWarningTime = DateTime.Now;
- await SaveOrUpdateDbAsync("LastHighTankPressureWarningTime", new GenericData
- {
- Type = "LastHighTankPressureWarningTime",
- CreatedTimeStamp = lastHighTankPressureWarningTime
- });
- }
- else if ((DateTime.Now - lastHighTankPressureWarningTime).TotalMinutes > 5.0)
- {
- responseDic["CurrentPointer"] = "alarm";
- await FireOrCloseAlarm(source, "lang-zh-cn:油罐压力过高lang-zh-tw:油罐壓力過高", "lang-zh-cn:油罐压力lang-zh-tw:油罐壓力",
- GenericAlarmSeverity.Error, $"lang-zh-cn:油罐压力过高报警lang-zh-tw:油罐壓力過高報警 {pressure:F2}");
- if (!alarmEmailsDic.ContainsKey("油罐压力过高"))
- {
- alarmEmailsDic["油罐压力过高"] = "";
- await TrySendAlarmEmailsAsync(
- $"lang-zh-cn:在线监测系统异常情况报告 - {this.appConfig.StationMiscConfig?.StationName ?? ""}lang-zh-tw:在線監測系統異常情況報告 - {this.appConfig.StationMiscConfig?.StationName ?? ""}".LocalizedContent("zh-tw"),
- $"lang-zh-cn:油罐压力过高 {pressure:F2}<b>超过5分钟</b>不合格lang-zh-tw:油罐壓力過高 {pressure:F2}<b>超過5分鐘</b>不合格".LocalizedContent("zh-tw"));
- }
- }
- else
- {
- responseDic["CurrentPointer"] = "zero";
- await FireOrCloseAlarm(source, "lang-zh-cn:油罐压力过高lang-zh-tw:油罐壓力過高", "lang-zh-cn:油罐压力lang-zh-tw:油罐壓力",
- GenericAlarmSeverity.Warning, $"lang-zh-cn:油罐压力过高预警lang-zh-tw:油罐壓力過高預警 {pressure:F2}");
- }
- }
- else
- {
- if (lastTankPressureWarningTime.Year != 1)
- {
- lastTankPressureWarningTime = new DateTime();
- await SaveOrUpdateDbAsync("LastTankPressureWarningTime", new GenericData()
- {
- Type = "LastTankPressureWarningTime",
- CreatedTimeStamp = lastTankPressureWarningTime
- });
- }
- if (lastLowTankPressureWarningTime.Year != 1)
- {
- lastLowTankPressureWarningTime = new DateTime();
- await SaveOrUpdateDbAsync("LastLowTankPressureWarningTime", new GenericData
- {
- Type = "LastLowTankPressureWarningTime",
- CreatedTimeStamp = lastLowTankPressureWarningTime
- });
- }
- if (lastHighTankPressureWarningTime.Year != 1)
- {
- lastHighTankPressureWarningTime = new DateTime();
- await SaveOrUpdateDbAsync("LastHighTankPressureWarningTime", new GenericData
- {
- Type = "LastHighTankPressureWarningTime",
- CreatedTimeStamp = lastHighTankPressureWarningTime
- });
- }
- }
- key = "TankPressure";
- if (alarmEmailsDic.ContainsKey("油罐压力零压") && !(pressure >= double.Parse(zeros[0]) && pressure <= double.Parse(zeros[1])))
- {
- alarmEmailsDic.Remove("油罐压力零压");
- }
- else if (alarmEmailsDic.ContainsKey("油罐压力负压") && !(pressure < tankPressureConfig.LowPressureWarningThreshold))
- {
- alarmEmailsDic.Remove("油罐压力负压");
- }
- else if (alarmEmailsDic.ContainsKey("油罐压力过高") && !(pressure > tankPressureConfig.HighPressureWarningTheshold))
- {
- alarmEmailsDic.Remove("油罐压力过高");
- }
- }
- if (liquidPressureConfig != null && id == liquidPressureConfig.MeterDeviceHandlerIdentity)
- {
- if (pressure < liquidPressureConfig.HighPressureWarningTheshold)
- {
- responseDic["CurrentPointer"] = "3low";
- if (lastLiquidPressureWarningTime.Year != 1)
- {
- lastLiquidPressureWarningTime = new DateTime();
- //await RemoveLastWarningTimeData("lastLiquidPressureWarningTime");
- }
- }
- else if (pressure < liquidPressureConfig.HighPressureAlarmTheshold)
- {
- responseDic["CurrentPointer"] = "3zero";
- if (lastLiquidPressureWarningTime.Year == 1)
- {
- lastLiquidPressureWarningTime = DateTime.Now;
- await SaveOrUpdateDbAsync("LastLiquidPressureWarningTime", new GenericData()
- {
- Type = "LastLiquidPressureWarningTime",
- CreatedTimeStamp = lastLiquidPressureWarningTime
- });
- }
- else if ((DateTime.Now - lastLiquidPressureWarningTime).TotalDays > liquidPressureConfig.WarningToAlarmStateLastingDaysThreshold)
- {
- await FireOrCloseAlarm(source, "lang-zh-cn:液阻压力过高预警lang-zh-tw:液阻壓力過高預警", "lang-zh-cn:液阻压力lang-zh-tw:液阻壓力",
- GenericAlarmSeverity.Error, $"lang-zh-cn:液阻压力lang-zh-tw:液阻壓力 {pressure:F2}");
- }
- else
- await FireOrCloseAlarm(source, "lang-zh-cn:液阻压力过高预警lang-zh-tw:液阻壓力過高預警", "lang-zh-cn:液阻压力lang-zh-tw:液阻壓力",
- GenericAlarmSeverity.Warning, $"lang-zh-cn:液阻压力lang-zh-tw:液阻壓力 {pressure:F2}");
- }
- else
- {
- responseDic["CurrentPointer"] = "3alarm";
- await FireOrCloseAlarm(source, "lang-zh-cn:液阻压力过高报警lang-zh-tw:液阻壓力過高報警", "lang-zh-cn:液阻压力lang-zh-tw:液阻壓力",
- GenericAlarmSeverity.Error, $"lang-zh-cn:液阻压力lang-zh-tw:液阻壓力 {pressure:F2}");
- }
- key = "LiquidPressure";
- }
- jsonDataDic.AddOrUpdate(key, responseDic, (existingKey, existingJsonData) => { return responseDic; });
- }
- else if (responseDic.ContainsKey("DeviceState"))
- {
- string deviceState = responseDic["DeviceState"].ToString();
- if (deviceState == "Disconnected")
- await FireOrCloseAlarm(source, "lang-zh-cn:压力监测设备连接断开lang-zh-tw:壓力監測設備連接斷開", "lang-zh-cn:压力监测设备lang-zh-tw:壓力監測設備",
- GenericAlarmSeverity.Warning, $"lang-zh-cn:压力监测设备连接断开lang-zh-tw:壓力監測設備連接斷開");
- else
- await FireOrCloseAlarm(source, "", "", GenericAlarmSeverity.Information, $"lang-zh-cn:压力监测设备连接断开lang-zh-tw:壓力監測設備連接斷開", true);
- }
- await Task.FromResult(true);
- }
- private async Task ProcessGasConcentrationsDataAsync(object sender, string message)
- {
- var source = sender as IProcessor;
- var responseDic = JsonSerializer.Deserialize(message, typeof(Dictionary<string, object>)) as Dictionary<string, object>;
- string id = responseDic["DeviceAddress"].ToString();
- if (responseDic.ContainsKey("Concentration"))
- {
- var concentrationConfig = this.appConfig.GasConcentrationGaugeMeterConfigs.Find(m => m.MeterDeviceHandlerIdentity == id);
- double concentr = double.Parse(responseDic["Concentration"].ToString());
- if (concentr < concentrationConfig.HighConcentrationWarningTheshold)
- {
- responseDic["CurrentPointer"] = "3low";
- if (lastGasConcentrationWarningTime.Year != 1)
- {
- lastGasConcentrationWarningTime = new DateTime();
- await SaveOrUpdateDbAsync("LastGasConcentrationWarningTime", new GenericData()
- {
- Type = "LastGasConcentrationWarningTime",
- CreatedTimeStamp = lastGasConcentrationWarningTime
- });
- }
- }
- else if (concentr < concentrationConfig.HighConcentrationAlarmTheshold)
- {
- responseDic["CurrentPointer"] = "3zero";
- if (lastGasConcentrationWarningTime.Year == 1)
- {
- lastGasConcentrationWarningTime = DateTime.Now;
- await SaveOrUpdateDbAsync("LastGasConcentrationWarningTime", new GenericData()
- {
- Type = "LastGasConcentrationWarningTime",
- CreatedTimeStamp = lastGasConcentrationWarningTime
- });
- }
- else if ((DateTime.Now - lastGasConcentrationWarningTime).TotalDays > concentrationConfig.WarningToAlarmStateLastingDaysThreshold)
- {
- responseDic["CurrentPointer"] = "3alarm";
- await FireOrCloseAlarm(source, "lang-zh-cn:回气口浓度报警lang-zh-tw:回氣口濃度報警", "lang-zh-cn:回气口浓度lang-zh-tw:回氣口濃度",
- GenericAlarmSeverity.Error, $"lang-zh-cn:回气口浓度 {concentr:F2}lang-zh-tw:回氣口濃度 {concentr:F2}");
- }
- else
- await FireOrCloseAlarm(source, "lang-zh-cn:回气口浓度报警lang-zh-tw:回氣口濃度報警", "lang-zh-cn:回气口浓度lang-zh-tw:回氣口濃度",
- GenericAlarmSeverity.Warning, $"lang-zh-cn:回气口浓度 {concentr:F2}lang-zh-tw:回氣口濃度 {concentr:F2}");
- }
- else // 大于等于 8000 umol/mol
- {
- responseDic["CurrentPointer"] = "3alarm";
- await FireOrCloseAlarm(source, "lang-zh-cn:回气口浓度报警lang-zh-tw:回氣口濃度報警", "lang-zh-cn:回气口浓度lang-zh-tw:回氣口濃度",
- GenericAlarmSeverity.Error, $"lang-zh-cn:回气口浓度 {concentr:F2}lang-zh-tw:回氣口濃度 {concentr:F2}");
- }
- string key = "GasConcentrations";
- jsonDataDic.AddOrUpdate(key, responseDic, (existingKey, existingJsonData) => { return responseDic; });
- }
- else if (responseDic.ContainsKey("DeviceState"))
- {
- string deviceState = responseDic["DeviceState"].ToString();
- if (deviceState == "Disconnected")
- await FireOrCloseAlarm(source, "lang-zh-cn:气体浓度设备连接断开lang-zh-tw:氣體濃度設備連接斷開", "lang-zh-cn:气体浓度设备lang-zh-tw:氣體濃度設備",
- GenericAlarmSeverity.Warning, $"lang-zh-cn:气体浓度设备连接断开lang-zh-tw:氣體濃度設備連接斷開");
- else
- await FireOrCloseAlarm(source, "", "", GenericAlarmSeverity.Information, $"lang-zh-cn:气体浓度设备连接断开lang-zh-tw:氣體濃度設備連接斷開", true);
- }
- await Task.FromResult(true);
- }
- public void Init(IEnumerable<IProcessor> processors)
- {
- var pressureProcessors = processors.Where(p => p.MetaConfigName.StartsWith("PressureGage_3051.SensorGroupHandler"));
- foreach (dynamic p in pressureProcessors)
- {
- var handlerList = p.Context.Handler.SensorHandlerList;
- foreach (dynamic handler in handlerList)
- {
- //logger.LogInformation($"SensorHandler with deviceAddress: {handler.DeviceAddress}");
- handler.OnJsonDataRecieved = new EventHandler<string>(async (s, d) =>
- {
- await ProcessPressureDataAsync(p, d);
- });
- }
- }
- var gasProcessors = processors.Where(p => p.MetaConfigName.StartsWith("GasConcentrations_Yt95h.SensorGroupHandl"));
- foreach (dynamic p in gasProcessors)
- {
- var handlerList = p.Context.Handler.SensorHandlerList;
- foreach (dynamic handler in handlerList)
- {
- //logger.LogInformation($"SensorHandler with deviceAddress: {handler.DeviceAddress}");
- handler.OnJsonDataRecieved = new EventHandler<string>(async (s, d) =>
- {
- await ProcessGasConcentrationsDataAsync(p, d);
- });
- }
- }
- this.vrBoardDeviceHandlers = processors.WithHandlerOrApp<Wayne_VaporRecoveryDataCollectorBoard.GroupHandler>()
- .SelectHandlerOrAppThenCast<Wayne_VaporRecoveryDataCollectorBoard.GroupHandler>();
- if (this.vrBoardDeviceHandlers == null || !this.vrBoardDeviceHandlers.Any())
- throw new InvalidOperationException("当前 APP 的运行必须要求系统中配置一个或者多个: '油枪气液比收集板' ,但当前没有获取到,请配置并启用相应 device handler 后重试");
- if (this.vrBoardDeviceHandlers.SelectMany(dh => dh.Boards).GroupBy(b => b.BoardPhysicalAddress).Any(g => g.Count() >= 2))
- throw new ArgumentException("发现多个 '油枪气液比收集板' 配置中有重复的硬件地址值,请检查相应的 device hanlder 以确保全站唯一硬件地址,再重试 ");
- var calibratedLatestPersistVrBoardNozzleInitParameters = this.ReadLatestPersistVrBoardNozzleInitParameters().Result;
- foreach (var dh in this.vrBoardDeviceHandlers)
- {
- foreach (var bc in dh.DeviceConfig.BoardConfigs)
- {
- foreach (var nc in bc.NozzleConfigs)
- {
- nc.ExternalInitParameter = calibratedLatestPersistVrBoardNozzleInitParameters
- .Where(p => p.SiteLevelNozzleId == nc.SiteLevelNozzleId)
- .Select(cal => new BoardInitParameterConfigV1()
- {
- 停止加油阀值 = cal.停止加油阀值,
- 加油脉冲当量 = cal.加油脉冲当量,
- 开始加油阀值 = cal.开始加油阀值,
- 最大未变化次数 = cal.最大未变化次数,
- 最小加油量 = cal.最小加油量,
- 气液比值 = cal.气液比值,
- 油气脉冲当量 = cal.油气脉冲当量
- }).FirstOrDefault();
- }
- }
- }
- var lastTankPressureWarningTimes = GetLastWarningTimeData("LastTankPressureWarningTime");
- if (lastTankPressureWarningTimes.Count() > 0)
- {
- lastTankPressureWarningTime = (DateTime)lastTankPressureWarningTimes.First().CreatedTimeStamp;
- }
- var lastTankPressureWarningDates = GetLastWarningTimeData("LastTankPressureWarningDate");
- if (lastTankPressureWarningDates.Count() > 0)
- {
- lastTankPressureWarningDate = (DateTime)lastTankPressureWarningDates.First().CreatedTimeStamp;
- tankPressureWarningTotalDays = (int)lastTankPressureWarningDates.First().IntProperty0;
- }
- var lastLowTankPressureWarningTimes = GetLastWarningTimeData("LastLowTankPressureWarningTime");
- if (lastLowTankPressureWarningTimes.Count() > 0)
- {
- lastLowTankPressureWarningTime = lastLowTankPressureWarningTimes.First().CreatedTimeStamp.Value;
- }
- var lastHighTankPressureWarningTimes = GetLastWarningTimeData("LastHighTankPressureWarningTime");
- if (lastHighTankPressureWarningTimes.Count() > 0)
- {
- lastHighTankPressureWarningTime = lastHighTankPressureWarningTimes.First().CreatedTimeStamp.Value;
- }
- var lastGasConcentrationWarningTimes = GetLastWarningTimeData("LastGasConcentrationWarningTime");
- if (lastGasConcentrationWarningTimes.Count() > 0)
- {
- lastGasConcentrationWarningTime = (DateTime)lastGasConcentrationWarningTimes.First().CreatedTimeStamp;
- }
- //fdc server app could be null as the pump control feature may not be required.
- //this.fdcServerHostApp = processors.OfType<FdcServerHostApp>().FirstOrDefault();
- //this.logger.LogInformation(this.fdcServerHostApp == null ? "Fdc server app is NOT present" : "Fdc Server app is present.");
- }
- public async Task<bool> Start()
- {
- this.vrBoardNozzles = this.vrBoardDeviceHandlers.SelectMany(g => g.Boards).SelectMany(b => b.Nozzles).Select(n => new VRBoardNozzle()
- {
- FuelingState = VRBoardNozzleFuelingStateEnum.IDLE,
- HealthState = VRBoardNozzleTrxHealthStateEnum.NOT_SET,
- SiteLevelNozzleId = n.SiteLevelNozzleId,
- PerNozzleVRGroupQualificationDefinitionGroupName = this.GetNozzleVRGroupQualificationConfig(n.SiteLevelNozzleId)?.GroupName,
- }).ToList();
- foreach (var vrBoardGroup in this.vrBoardDeviceHandlers)
- {
- vrBoardGroup.OnBoardStateChange += async (s, a) =>
- {
- logger.LogInformation($"Board with PhyAddr: {a.Board.BoardPhysicalAddress} state changed to: {a.Board.State}");
- if (a.Board.State == BoardStateEnum.Initializing || a.Board.State == BoardStateEnum.UnInit)
- {
- var vrNzls = a.Board.Nozzles.Join(this.vrBoardNozzles, outer => outer.SiteLevelNozzleId, inner => inner.SiteLevelNozzleId, (o, i) => i);
- vrNzls.ToList().ForEach(n => n.FuelingState = VRBoardNozzleFuelingStateEnum.IDLE);
- }
- else if (a.Board.State == BoardStateEnum.Initialized)
- {
- if (vrBoardGroup.Context == null) {/* indicates using internal in-memory simulator*/ }
- else
- {
- var historyIncoming = vrBoardGroup.Context.Incoming as HistoryKeepIncoming<DataCollectorMessageBase>;
- if (historyIncoming != null)
- historyIncoming.Due = 60 * 6;
- else
- throw new ArgumentException($"This App requires {nameof(GroupHandler)} to use HistoryKeepIncoming for caculate max and avg fueling air and liquid readings.");
- }
- }
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FireEvent(this, OnVaporRecoveryDataCollectorBoardStateChangeEventName, a.Board);
- };
- vrBoardGroup.OnDataRecieved += async (s, a) =>
- {
- await ProcessVRBoardDataAsync(s, a.Data);
- };
- vrBoardGroup.OnBoardNozzleStateChange += async (s, a) =>
- {
- if (this.logger.IsEnabled(LogLevel.Debug))
- logger.LogDebug($"Nozzle with sLNzlId: {a.BoardNozzle.SiteLevelNozzleId} state changed to: {a.BoardNozzle.NozzleState}");
- var vrNzl = this.vrBoardNozzles.FirstOrDefault(vrN => vrN.SiteLevelNozzleId == a.BoardNozzle.SiteLevelNozzleId);
- if (vrNzl != null)
- {
- vrNzl.FuelingState = a.BoardNozzle.NozzleState == NozzleStateEnum.Fuelling ?
- VRBoardNozzleFuelingStateEnum.FUELING : VRBoardNozzleFuelingStateEnum.IDLE;
- }
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FireEvent(this,
- OnVaporRecoveryDataCollectorBoardNozzleStateChangeEventName, vrNzl);
- };
- }
- #region retrieve latest nozzles' trx flowData from db and fill back to logical vr board nozzle.
- var latestNozzlesTrxFlowDatas = await dbHelperForVRBoard.GetLatestNozzlesTrxFlowDatas();
- if (latestNozzlesTrxFlowDatas != null)
- {
- foreach (var n in this.vrBoardNozzles)
- {
- n.LatestTrxFlowData = latestNozzlesTrxFlowDatas.FirstOrDefault(r => r.SiteLevelNozzleId == n.SiteLevelNozzleId);
- }
- }
- #endregion
- #region retrieve latest nozzles' alarms from db and fill back to logical vr board nozzle.
- var latestNozzlesAlarms = await dbHelperForVRBoard.GetLatestNozzlesAlarms();
- if (latestNozzlesAlarms == null)
- {
- this.vrBoardNozzles.ForEach(i =>
- {
- i.HealthState = VRBoardNozzleTrxHealthStateEnum.NORMAL;
- });
- }
- else
- {
- this.vrBoardNozzles.ForEach(i =>
- {
- var record = latestNozzlesAlarms.Where(r => i.SiteLevelNozzleId == r.SiteLevelNozzleId).FirstOrDefault();
- if (record != null)
- {
- switch (record.AlarmType)
- {
- case VRBoardAlarmType.NONE:
- i.HealthState = VRBoardNozzleTrxHealthStateEnum.NORMAL;
- break;
- case VRBoardAlarmType.WARNING:
- i.HealthState = VRBoardNozzleTrxHealthStateEnum.WARNING;
- break;
- case VRBoardAlarmType.ALARM:
- i.HealthState = VRBoardNozzleTrxHealthStateEnum.ALARM;
- break;
- default:
- break;
- }
- }
- });
- }
- #endregion
- ScheduleMinutelySaveDbTimer();
- //集中播报声光提醒的时间timer设置
- ScheduleDailyWarningPlayTimerForVRBoard();
- //集中检查,即回溯一天所有交易的气液比的整体状态,即是否不合格笔数超过某个限额
- ScheduleDailyWarningCheckTimerForVRBoard();
- ScheduleDailyWarningCheckTimerForTankPressure();
- #region Purge database data
- using (var scope = this.services.CreateScope())
- {
- try
- {
- /*VRBoardNozzleTrxFlowData*/
- var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
- var vrBoardNozzleTrxFlowDatas = dbContext.GenericDatas.Where(gd =>
- gd.Owner == AutoMapperProfile.VaporRecoveryOnlineWatchHubApp_MapToDbEntity_Owner
- && gd.Type == AutoMapperProfile.VRBoardNozzleTrxFlowData_MapToDbEntity_Type);
- var vrBoardNozzleTrxFlowDatasPurgeTimePoint = DateTime.Now.Subtract(new TimeSpan(365 * 2, 0, 0, 0, 0));
- dbContext.GenericDatas.RemoveRange(
- vrBoardNozzleTrxFlowDatas.Where(d => d.CreatedTimeStamp <= vrBoardNozzleTrxFlowDatasPurgeTimePoint));
- /*VRBoardAlarmRecord*/
- var vrBoardAlarmRecordDatas = dbContext.GenericDatas.Where(gd =>
- gd.Owner == AutoMapperProfile.VaporRecoveryOnlineWatchHubApp_MapToDbEntity_Owner
- && gd.Type == AutoMapperProfile.VRBoardAlarmRecord_MapToDbEntity_Type);
- var vrBoardAlarmRecordDatasPurgeTimePoint = DateTime.Now.Subtract(new TimeSpan(30 * 4, 0, 0, 0, 0));
- dbContext.GenericDatas.RemoveRange(
- vrBoardAlarmRecordDatas.Where(d => d.TimeStampProperty0 <= vrBoardAlarmRecordDatasPurgeTimePoint));
- /*calibration data should not be cleared directly by timestamp as the hardware replacement may occur at anytime.
- the purge should group by nozzle id then timestamp, since the data should be very limited, so leave it for now.*/
- //var vrBoardNozzleInitParametersDatas = dbContext.GenericDatas.Where(gd =>
- // gd.Owner == AutoMapperProfile.VaporRecoveryOnlineWatchHubApp_MapToDbEntity_Owner
- // && gd.Type == AutoMapperProfile.VRBoardNozzleInitParametersData_MapToDbEntity_Type);
- //dbContext.GenericDatas.RemoveRange(
- // vrBoardNozzleInitParametersDatas.Where(d => d.CreatedTimeStamp <= DateTime.Now.Subtract(new TimeSpan(30 * 48, 0, 0, 0, 0))));
- var removedRowCount = await dbContext.SaveChangesAsync();
- logger.LogInformation($"Total purged data row count: {removedRowCount}");
- }
- catch (Exception ex)
- {
- logger.LogError($"Exception In Purge database data, exception: {ex}");
- }
- }
- #endregion
- return true;
- }
- public async Task<bool> Stop()
- {
- this.dailyVRBoardNotifyWarningTimer?.Dispose();
- this.dailyVRBoardCheckingStateTimer?.Dispose();
- this.minutelySaveDbTimer?.Dispose();
- return true;
- }
- #region UniversalApi for VR board related.
- [UniversalApi(Description = "Get the persist trx flow datas for nozzle.")]
- public Task<IEnumerable<VRBoardNozzleTrxFlowData>> GetVRBoardNozzleTrxFlowDatas(DateTime? startDate, DateTime? endDate, int? siteLevelNozzleId,
- int pageIndex, int singlePageRowCount,
- string tankPressure, string liquidPressure, string gasConcentrations)
- {
- return dbHelperForVRBoard.GetNozzlesTrxFlowData(startDate ?? DateTime.Now.AddDays(-31), endDate ?? DateTime.MaxValue,
- siteLevelNozzleId ?? 0, pageIndex, singlePageRowCount,
- tankPressure, liquidPressure, gasConcentrations);
- }
- //[UniversalApi(Description = "Get the persist alarms with details for nozzle, if no due time specified, the latest 90 days alarms will be returned.")]
- //public Task<IEnumerable<VRBoardAlarmRecord>> GetVRBoardAlarms(DateTime? due, int? siteLevelNozzleId)
- //{
- // if (due == null)//with no search condition specified, return latest 1000 records.
- // {
- // return dbHelperForVRBoard.GetAllAlarms();
- // }
- // else
- // {
- // //var nozzleId = input.Get("nozzleid", 0);
- // //var startTime = input.Get("starttime", DateTime.Now.AddDays(-1));
- // //var endTime = input.Get("endtime", DateTime.Now);
- // //Logger.LogInformation($"GetAlarmList, nozzleId: {nozzleId}, startTime: {startTime}, endTime: {endTime}");
- // return dbHelperForVRBoard.GetAlarms(due.Value, DateTime.Now, siteLevelNozzleId ?? 0);
- // }
- //}
- [UniversalApi(Description = "Get all VR nozzles")]
- public async Task<List<VRBoardNozzle>> GetVRBoardNozzles()
- {
- return this.vrBoardNozzles;
- }
- [UniversalApi(Description = "获取与油枪气液比相关的(ConfigUI)配置")]
- public async Task<VaporRecoveryConfigV1> GetVaporRecoveryConfig()
- {
- return this.appConfig.VaporRecoveryConfig;
- }
- [UniversalApi(Description = "获取上一次(保存于数据库中)成功写入所有气液比数据收集板中的初始化参数值")]
- public Task<IEnumerable<VRBoardNozzleInitParametersData>> ReadLatestPersistVrBoardNozzleInitParameters()
- {
- var datas = this.dbHelperForVRBoard.GetLatestPersistBoardNozzleInitParameters();
- return datas;
- }
- [UniversalApi(Description = "从气液比数据收集板中读取初始化参数值")]
- public Task<READ_PARA_Response> ReadVrBoardNozzleInitParameters(int siteLevelNozzleId)
- {
- var nozzleAndBoardPairs = this.vrBoardDeviceHandlers.SelectMany(h => h.Boards.Select(b => new { Board = b, deviceHandler = h }))
- .SelectMany(bd => bd.Board.Nozzles.Select(n => new { n, bd })).Where(cp => cp.n.SiteLevelNozzleId == siteLevelNozzleId);
- var targetPair = nozzleAndBoardPairs.FirstOrDefault(c => c.n.SiteLevelNozzleId == siteLevelNozzleId);
- if (targetPair == null) throw new ArgumentException($"Could not find VRBoard device handler with siteLevelNozzleId: {siteLevelNozzleId} configurated on it.");
- return targetPair.bd.deviceHandler.ReadBoardNozzleInitParameters(targetPair.bd.Board.BoardPhysicalAddress, targetPair.n.NozzleNumberOnBoard);
- }
- [UniversalApi(Description = "写入初始化参数值至气液比数据收集板,如果成功的话则同时保存至数据库中")]
- public async Task<bool> WriteAndPersistVrBoardNozzleInitParameters(int siteLevelNozzleId, BoardInitParameterConfigV1 parametersConfig)
- {
- var nozzleAndBoardPairs = this.vrBoardDeviceHandlers.SelectMany(h => h.Boards.Select(b => new { Board = b, deviceHandler = h }))
- .SelectMany(bd => bd.Board.Nozzles.Select(n => new { n, bd })).Where(cp => cp.n.SiteLevelNozzleId == siteLevelNozzleId);
- var targetPair = nozzleAndBoardPairs.FirstOrDefault(c => c.n.SiteLevelNozzleId == siteLevelNozzleId);
- if (targetPair == null) throw new ArgumentException($"Could not find VRBoard device handler with siteLevelNozzleId: {siteLevelNozzleId} configurated on it.");
- var writeResult = await targetPair.bd.deviceHandler.WriteBoardNozzleInitParameters(targetPair.bd.Board.BoardPhysicalAddress, targetPair.n.NozzleNumberOnBoard, parametersConfig);
- if (writeResult)
- await this.SaveDbAsync(new VRBoardNozzleInitParametersData()
- {
- SiteLevelNozzleId = siteLevelNozzleId,
- DataCollectorDeviceAddress = targetPair.bd.Board.BoardPhysicalAddress,
- TimeStamp = DateTime.Now,
- 停止加油阀值 = parametersConfig.停止加油阀值,
- 加油脉冲当量 = parametersConfig.加油脉冲当量,
- 开始加油阀值 = parametersConfig.开始加油阀值,
- 最大未变化次数 = parametersConfig.最大未变化次数,
- 最小加油量 = parametersConfig.最小加油量,
- 气液比值 = parametersConfig.气液比值,
- 油气脉冲当量 = parametersConfig.油气脉冲当量
- });
- return writeResult;
- }
- public enum VrBoardNozzleInitParameterCalibrationType
- {
- 油气脉冲当量
- }
- [UniversalApi(Description = "校准VR board油枪的 初始化参数值.</br> " +
- "<b>calibrateType</b>:待校准的参数名称</br>" +
- "<b>currentConefficientValue</b>:待校准的参数的当前值,一般它是一个静态系数,业务数据的读数与其相关,校准的过程即通过对此值进行调整而进行</br>" +
- "<b>currentReadingValue</b>:与ConefficientValue相关的业务数据读数值,即当前系统未校准前(认为其不准确)的读数值</br>" +
- "<b>standardReadingValue</b>:与currentReadingValue相对的,由专业校准设备所读出的业务数据值,一般它与当前未校准前的业务数据读数不一致,校准即指将它两者进行靠近的调整过程</br>" +
- "<i>返回值</i>:校准后的系数值")]
- public async Task<double> CaculateVrBoardNozzleInitParameterCalibrationValue(VrBoardNozzleInitParameterCalibrationType calibrateType,
- double currentConefficientValue, double currentReadingValue, double standardReadingValue)
- {
- if (calibrateType == VrBoardNozzleInitParameterCalibrationType.油气脉冲当量)
- return (standardReadingValue / currentReadingValue) * currentConefficientValue;
- throw new ArgumentException("Only Support calibrate for 油气脉冲当量");
- }
- #endregion
- #region UniversalApi for Meters related.
- [UniversalApi(Description = "获取与油罐压力表相关的配置")]
- public async Task<TankPressureGaugeMeterConfigV1> GetTankPressureGaugeMeterConfigs()
- {
- return this.appConfig.TankPressureGaugeMeterConfig;
- }
- [UniversalApi(Description = "获取与液阻压力表相关的配置")]
- public async Task<LiquidPressureGaugeMeterConfigV1> GetLiquidPressureGaugeMeterConfigs()
- {
- return this.appConfig.LiquidPressureGaugeMeterConfig;
- }
- [UniversalApi(Description = "获取与危险气体浓度表相关的配置")]
- public async Task<List<GasConcentrationGaugeMeterConfigV1>> GetGasConcentrationGaugeMeterConfigs()
- {
- return this.appConfig.GasConcentrationGaugeMeterConfigs;
- }
- #endregion
- private async Task<bool> TrySendAlarmEmailsAsync(string title, string htmlBody)
- {
- if (string.IsNullOrEmpty(this.appConfig.StationMiscConfig?.StationName))
- return false;
- if (string.IsNullOrEmpty(this.appConfig.StationMiscConfig?.NotificationMailConfig?.RegionId)
- || string.IsNullOrEmpty(this.appConfig.StationMiscConfig?.NotificationMailConfig?.AccessKeyId)
- || string.IsNullOrEmpty(this.appConfig.StationMiscConfig?.NotificationMailConfig?.AccessKeySecret))
- return false;
- if (!(this.appConfig.StationMiscConfig?.NotificationMailConfig.ReceiverAddresses?.Any() ?? false))
- return false;
- try
- {
- //Create a client used for initiating a request
- IClientProfile profile = DefaultProfile.GetProfile(
- this.appConfig.StationMiscConfig?.NotificationMailConfig?.RegionId,
- this.appConfig.StationMiscConfig?.NotificationMailConfig?.AccessKeyId,
- this.appConfig.StationMiscConfig?.NotificationMailConfig?.AccessKeySecret);
- IAcsClient client = new DefaultAcsClient(profile);
- foreach (var mailAddress in this.appConfig.StationMiscConfig.NotificationMailConfig.ReceiverAddresses)
- {
- SingleSendMailRequest request = new SingleSendMailRequest();
- //try
- //{
- /*Sample key from aliyun RAM user under wayne account.
- AccessKey ID: LTAI5tFEVuW3DXDkmEdweM21
- AccessKey Secret: B25GYOqPgg0y9D74y5C5IB4Nkc1uGK
- */
- //Version must set to "2017-06-22" when the regionId is not "cn-hangzhou"
- //request.Version = "2017-06-22";
- if (!string.IsNullOrEmpty(this.appConfig.StationMiscConfig?.NotificationMailConfig?.ApiVersion))
- request.Version = this.appConfig.StationMiscConfig?.NotificationMailConfig?.ApiVersion;
- request.AccountName = "alarm@emailnotify.ipos.biz";
- request.FromAlias = "";
- request.AddressType = 1;
- request.TagName = "";// "控制台创建的标签";
- request.ReplyToAddress = true;
- request.ToAddress = mailAddress;// "johnson.shao@doverfs.com";
- request.Subject = title;// "加油站油气回收在线监测系统 - 异常报告";
- request.HtmlBody = htmlBody;// "邮件正文 from arm";
- SingleSendMailResponse httpResponse = client.GetAcsResponse(request);
- //}
- //catch (ServerException e)
- //{
- // System.Console.WriteLine(e.ToString());
- //}
- //catch (ClientException e)
- //{
- // System.Console.WriteLine(e.ToString());
- //}catch(Exception exxx)
- }
- return true;
- }
- catch (Exception exxx)
- {
- this.logger.LogInformation($"TrySendAlarmEmailAsync failed with error: {exxx}");
- return false;
- }
- }
- private async Task ProcessVRBoardDataAsync(object sender, DataCollectorMessageBase message)
- {
- /*气液比监测板的数据(from device handler)将在此进行处理*/
- var baordGroupDeviceHandler = sender as GroupHandler;
- int? operatingNozzleSiteLevelNozzleId = null;
- switch (message)
- {
- case READ_ONE_HISTORY_DATA_Response trxFlowDataResponse:
- try
- {
- /*一笔加油交易完成后的气液比*/
- operatingNozzleSiteLevelNozzleId =
- this.vrBoardDeviceHandlers.SelectMany(gb => gb.Boards)
- .FirstOrDefault(b => b.BoardPhysicalAddress == trxFlowDataResponse.Address).Nozzles
- .FirstOrDefault(n => n.NozzleNumberOnBoard == trxFlowDataResponse.NozzleNumber)?.SiteLevelNozzleId;
- var vaporVolumeWithDecimal = Util.RoundToDouble(trxFlowDataResponse.AirVolume / Math.Pow(10, this.appConfig.VaporRecoveryConfig.AirVolumeDigits));
- var liquidVolumeWithDecimal = Util.RoundToDouble(trxFlowDataResponse.LiquidVolume / Math.Pow(10, this.appConfig.VaporRecoveryConfig.LiquidVolumeDigits));
- double vaporAndLiquidRatio = 0;
- if (liquidVolumeWithDecimal != 0)
- vaporAndLiquidRatio = Util.RoundToDouble(vaporVolumeWithDecimal / liquidVolumeWithDecimal);
- logger.LogDebug($"VR trx data-> " +
- $"sLNzlId: {operatingNozzleSiteLevelNozzleId ?? -1}, " +
- $"liquid/vapor Vol(with decimal): {liquidVolumeWithDecimal}/{vaporVolumeWithDecimal}, " +
- $"startTime/endTime: {trxFlowDataResponse.FuellingStartTime.ToString("yyyy-MM-dd HH:mm:ss")}/{trxFlowDataResponse.FuellingEndTime.ToString("yyyy-MM-dd HH:mm:ss")}, " +
- $"duration: {trxFlowDataResponse.FuellingEndTime.Subtract(trxFlowDataResponse.FuellingStartTime).TotalSeconds}(s)");
- var perNozzleRule = this.GetNozzleVRGroupQualificationConfig(operatingNozzleSiteLevelNozzleId ?? -1);
- double liquidVolumeMinThreshold;
- if (perNozzleRule == null)
- liquidVolumeMinThreshold = this.appConfig.VaporRecoveryConfig.LiquidVolumeMinThreshold;
- else
- liquidVolumeMinThreshold = perNozzleRule.LiquidVolumeMinThreshold;
- double? maxAirFlowRateWithDecimal = null;
- double? maxLiquidFlowRateWithDecimal = null;
- double? avgAirFlowRateWithDecimal = null;
- double? avgLiquidFlowRateWithDecimal = null;
- #region Caculate max and avg value for air and liquid from fueling progress
- try
- {
- var historyIncoming = baordGroupDeviceHandler.Context?.Incoming as HistoryKeepIncoming<DataCollectorMessageBase>;
- if (historyIncoming != null)
- {
- var allFuelInfos = historyIncoming.History.Where(d => d.Item1 is READ_ONE_HISTORY_DATA_Response t
- && t.Address == trxFlowDataResponse.Address
- && t.NozzleNumber == trxFlowDataResponse.NozzleNumber).OrderByDescending(d => d.Item2);
- //logger.LogDebug($" ##debug siteLevelNozzleId: {operatingNozzleSiteLevelNozzleId ?? -1}, allFuelInfos: {allFuelInfos.Select(f => f.Item2 + " " + f.Item1.ToLogString()).Aggregate("", (acc, n) => acc + " || " + n)}");
- DateTime lastFuelReportDateTime = DateTime.MinValue;
- if (allFuelInfos.Count() > 1)
- {
- lastFuelReportDateTime = allFuelInfos.Skip(1).First().Item2;
- //logger.LogDebug($" ##debug siteLevelNozzleId: {operatingNozzleSiteLevelNozzleId ?? -1}, lastFuelReportDateTime set to: {lastFuelReportDateTime.ToLongTimeString()}");
- }
- var currentFuelProgressInfos =
- historyIncoming.History.Where(d => d.Item1 is READ_WORKING_AND_TYPE_Response p
- && p.Address == trxFlowDataResponse.Address
- && p.NozzleLiquidAndAirFlowData.Any(f => f.Item1 == trxFlowDataResponse.NozzleNumber)
- && d.Item2 >= lastFuelReportDateTime)
- .Select(h => h.Item1).Cast<READ_WORKING_AND_TYPE_Response>().SelectMany(d => d.NozzleLiquidAndAirFlowData).Where(f => f.Item1 == trxFlowDataResponse.NozzleNumber);
- if (currentFuelProgressInfos?.Any() ?? false)
- {
- maxAirFlowRateWithDecimal = Util.RoundToDouble(currentFuelProgressInfos.Select(i => i.Item3)?.Max() / Math.Pow(10, this.appConfig.VaporRecoveryConfig.AirVolumeDigits));
- maxLiquidFlowRateWithDecimal = Util.RoundToDouble(currentFuelProgressInfos.Select(i => i.Item2)?.Max() / Math.Pow(10, this.appConfig.VaporRecoveryConfig.LiquidVolumeDigits));
- avgAirFlowRateWithDecimal = Util.RoundToDouble(currentFuelProgressInfos.Select(i => i.Item3)?.Average() / Math.Pow(10, this.appConfig.VaporRecoveryConfig.AirVolumeDigits));
- avgLiquidFlowRateWithDecimal = Util.RoundToDouble(currentFuelProgressInfos.Select(i => i.Item2)?.Average() / Math.Pow(10, this.appConfig.VaporRecoveryConfig.LiquidVolumeDigits));
- }
- }
- }
- catch (Exception eee)
- {
- logger.LogInformation($"VR trx data-> siteLevelNozzleId: {operatingNozzleSiteLevelNozzleId ?? -1}, exceptioned for caculate max and avg rate related data: {eee}");
- }
- #endregion
- logger.LogDebug($" sLNzlId: {operatingNozzleSiteLevelNozzleId ?? -1}-> maxAirFlowRateWithDecimal: {maxAirFlowRateWithDecimal ?? -1}, maxLiquidFlowRateWithDecimal: {maxLiquidFlowRateWithDecimal ?? -1}, avgAirFlowRateWithDecimal: {avgAirFlowRateWithDecimal ?? -1}, avgLiquidFlowRateWithDecimal: {avgLiquidFlowRateWithDecimal ?? -1}");
- var trxData = new VRBoardNozzleTrxFlowData()
- {
- DataCollectorType = baordGroupDeviceHandler.CurrentDataCollectorType,
- DataCollectorDeviceAddress = trxFlowDataResponse.Address,
- VaporVolumeWithDecimal = vaporVolumeWithDecimal,
- LiquidVolumeWithDecimal = liquidVolumeWithDecimal,
- VaporLiquidRatio = vaporAndLiquidRatio,
- SiteLevelNozzleId = operatingNozzleSiteLevelNozzleId ?? -1,
- MaxAirFlowRateWithDecimal = maxAirFlowRateWithDecimal,
- MaxLiquidFlowRateWithDecimal = maxLiquidFlowRateWithDecimal,
- AvgAirFlowRateWithDecimal = avgAirFlowRateWithDecimal,
- AvgLiquidFlowRateWithDecimal = avgLiquidFlowRateWithDecimal,
- TimeStamp = DateTime.Now,
- FuellingStartTime = trxFlowDataResponse.FuellingStartTime,
- FuellingEndTime = trxFlowDataResponse.FuellingEndTime,
- //SensorId = dataCollectorDeviceHandler.HardwareIdentity,
- 气液比值是否正常 = Is气液比值是否正常(vaporAndLiquidRatio, operatingNozzleSiteLevelNozzleId),
- //VrState = VRState.NOT_SET
- TankPressure = jsonDataDic.ContainsKey("TankPressure") ? double.Parse(jsonDataDic["TankPressure"]["Pressure"].ToString()) : 0,
- LiquidPressure = jsonDataDic.ContainsKey("LiquidPressure") ? double.Parse(jsonDataDic["LiquidPressure"]["Pressure"].ToString()) : 0,
- GasConcentrations = jsonDataDic.ContainsKey("GasConcentrations") ? double.Parse(jsonDataDic["GasConcentrations"]["Concentration"].ToString()) : 0,
- };
- if (trxFlowDataResponse.LiquidVolume /
- Math.Pow(10, this.appConfig.VaporRecoveryConfig.LiquidVolumeDigits) > liquidVolumeMinThreshold)
- {
- await SaveDbAsync(trxData);
- }
- else
- {
- logger.LogInformation($"SiteLevelNozzleId: {operatingNozzleSiteLevelNozzleId ?? -1} report a complete air and liquid data reading with LiquidVolume: {trxFlowDataResponse.LiquidVolume}, which is lower than threashold, will discard the record in db.");
- }
- if (this.vrBoardNozzles.FirstOrDefault(n => n.SiteLevelNozzleId == trxData.SiteLevelNozzleId) is VRBoardNozzle n)
- n.LatestTrxFlowData = trxData;
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FireEvent(this, OnVaporRecoveryDataCollectorBoardNozzleFlowDataReadEventName, trxData);
- }
- catch (Exception exx)
- {
- logger.LogError($"SiteLevelNozzleId: {operatingNozzleSiteLevelNozzleId ?? -1} Prepare VRBoardNozzleTrxFlowData for saving to db exception, detail: {exx}");
- }
- break;
- case READ_WORKING_AND_TYPE_Response realTimeFlowDataResponse:
- try
- {
- foreach (var fuellingBoardNozzle in realTimeFlowDataResponse.NozzleStates.Where(s => s.Value == NozzleStateEnum.Fuelling))
- {
- /*加油过程中的实时气液比*/
- operatingNozzleSiteLevelNozzleId =
- this.vrBoardDeviceHandlers.SelectMany(gb => gb.Boards)
- .FirstOrDefault(b => b.BoardPhysicalAddress == realTimeFlowDataResponse.Address).Nozzles
- .FirstOrDefault(n => n.NozzleNumberOnBoard == fuellingBoardNozzle.Key)?.SiteLevelNozzleId;
- var liquidAndAirFlowData =
- realTimeFlowDataResponse.NozzleLiquidAndAirFlowData.FirstOrDefault(n => n.Item1 == fuellingBoardNozzle.Key);
- if (liquidAndAirFlowData != null)
- {
- var vaporVolumeWithDecimal = Util.RoundToDouble(liquidAndAirFlowData.Item3 / Math.Pow(10, this.appConfig.VaporRecoveryConfig.AirVolumeDigits));
- var liquidVolumeWithDecimal = Util.RoundToDouble(liquidAndAirFlowData.Item2 / Math.Pow(10, this.appConfig.VaporRecoveryConfig.LiquidVolumeDigits));
- double vaporAndLiquidRatio = 0;
- if (liquidVolumeWithDecimal != 0)
- vaporAndLiquidRatio = Util.RoundToDouble(vaporVolumeWithDecimal / liquidVolumeWithDecimal);
- if (this.logger.IsEnabled(LogLevel.Debug))
- logger.LogDebug($"VR trx flow data-> " +
- $"sLNzlId: {operatingNozzleSiteLevelNozzleId ?? -1}, " +
- $"boardNzlNo.: {liquidAndAirFlowData.Item1}, " +
- $"liquid/vapor Vol(with decimal): {liquidVolumeWithDecimal}/{vaporVolumeWithDecimal}");
- var realTimeFlowData = new VRBoardNozzleTrxFlowData()
- {
- DataCollectorType = baordGroupDeviceHandler.CurrentDataCollectorType,
- DataCollectorDeviceAddress = realTimeFlowDataResponse.Address,
- //DataCollectorNozzleNumber = response.NozzleNumber,
- TimeStamp = DateTime.Now,
- VaporVolumeWithDecimal = vaporVolumeWithDecimal,
- LiquidVolumeWithDecimal = liquidVolumeWithDecimal,
- VaporLiquidRatio = vaporAndLiquidRatio,
- 气液比值是否正常 = Is气液比值是否正常(vaporAndLiquidRatio, operatingNozzleSiteLevelNozzleId),
- SiteLevelNozzleId = operatingNozzleSiteLevelNozzleId ?? -1,
- };
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FireEvent(this, OnVaporRecoveryDataCollectorBoardNozzleFlowDataReadEventName, realTimeFlowData);
- }
- }
- }
- catch (Exception ex)
- {
- logger.LogError($"SiteLevelNozzleId: {operatingNozzleSiteLevelNozzleId ?? -1} Failed to parse READ_WORKING_AND_TYPE_Response, exception message: {ex.ToString()}");
- }
- break;
- default:
- throw new ArgumentException($"SiteLevelNozzleId: {operatingNozzleSiteLevelNozzleId ?? -1} Unexpected respone msg type: {message.GetType().FullName}");
- }
- }
- private void ScheduleMinutelySaveDbTimer()
- {
- int minutes = 3;
- if (minutelySaveDbTimer == null)
- {
- this.minutelySaveDbTimer = new Timer(new TimerCallback(async _ =>
- {
- if (jsonDataDic.ContainsKey("TankPressure"))
- {
- var newData = new GenericData() { Owner = "VaporRecoveryOnlineWatchHubApp", CreatedTimeStamp = DateTime.Now };
- newData.DoubleProperty0 = double.Parse(jsonDataDic["TankPressure"]["Pressure"].ToString());
- newData.Type = "TankPressure";
- await SaveDbAsync(newData);
- }
- if (jsonDataDic.ContainsKey("LiquidPressure"))
- {
- var newData = new GenericData() { Owner = "VaporRecoveryOnlineWatchHubApp", CreatedTimeStamp = DateTime.Now };
- newData.DoubleProperty1 = double.Parse(jsonDataDic["LiquidPressure"]["Pressure"].ToString());
- newData.Type = "LiquidPressure";
- await SaveDbAsync(newData);
- }
- if (jsonDataDic.ContainsKey("GasConcentrations"))
- {
- var newData = new GenericData() { Owner = "VaporRecoveryOnlineWatchHubApp", CreatedTimeStamp = DateTime.Now };
- newData.DoubleProperty2 = double.Parse(jsonDataDic["GasConcentrations"]["Concentration"].ToString());
- newData.Type = "GasConcentrations";
- await SaveDbAsync(newData);
- }
- }), null, TimeSpan.FromMinutes(minutes), TimeSpan.FromMinutes(minutes));
- }
- else
- {
- this.minutelySaveDbTimer?.Change(TimeSpan.FromMinutes(minutes), TimeSpan.FromMinutes(minutes));
- }
- }
- private void ScheduleDailyWarningPlayTimerForVRBoard()
- {
- var alarmPlayTime = this.appConfig.VaporRecoveryConfig.AlarmPlayingTimeInDayWith24HourStyle;
- var time = alarmPlayTime.Split(".");
- if (time.Length != 2)
- throw new ArgumentException($"illegal VRDataCollectorBoard alarmPlayTime value: ${alarmPlayTime ?? ""}");
- double.TryParse(time[0], out double hour);
- double.TryParse(time[1], out double minute);
- DateTime todayPlayTime = DateTime.Now.Date.AddHours(hour).AddMinutes(minute);
- DateTime nextPlayTime = DateTime.Now <= todayPlayTime ? todayPlayTime : todayPlayTime.AddDays(1);
- if (this.dailyVRBoardNotifyWarningTimer == null)
- {
- logger.LogInformation($"ScheduleDailyWarningPlayTimer next scheduled time: {nextPlayTime.ToString()}, " +
- $"due in {(nextPlayTime - DateTime.Now).TotalSeconds} seconds");
- this.dailyVRBoardNotifyWarningTimer = new Timer(async _ =>
- {
- logger.LogInformation($"Daily Warning Play Timer is kicking off...");
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- var alarmRecords = await dbHelperForVRBoard.GetLatestNozzlesAlarms();//.GetAlarmsByTimeRange((DateTime.Now.Date, DateTime.Now));
- logger.LogInformation($" Total {alarmRecords?.Count() ?? -1} data are read, they're(siteLevelNozzleId<->AlarmType): {string.Join(", ", alarmRecords?.Select(r => r.SiteLevelNozzleId + "<->" + r.AlarmType) ?? new string[] { })}");
- foreach (var alarm in alarmRecords.Where(al => al.AlarmType != VRBoardAlarmType.NONE))
- {
- await universalApiHub.FirePersistGenericAlarm(this,
- new GenericAlarm()
- {
- Title = $"lang-en-us:Nozzle {alarm.SiteLevelNozzleId} A/L daily ratio not qualifiedlang-zh-cn:{alarm.SiteLevelNozzleId}号油枪气液比数值当日不合格lang-zh-tw:{alarm.SiteLevelNozzleId}號油槍氣液比數值當日不合格",
- Category = "lang-en-us:Nozzle A/L ratiolang-zh-cn:油枪气液比lang-zh-tw:油槍氣液比",
- Severity = (alarm.AlarmType == VRBoardAlarmType.ALARM ? GenericAlarmSeverity.Error : GenericAlarmSeverity.Warning),
- Detail = (alarm.AlarmDetails ?? "") + (alarm.AlarmDescription ?? "")
- }, ga => ga.Title);
- }
- if (alarmRecords.Where(al => al.AlarmType == VRBoardAlarmType.ALARM).Any())
- {
- var nozzlesStr = string.Join(", ", alarmRecords.Where(al => al.AlarmType == VRBoardAlarmType.ALARM).Select(al => al.SiteLevelNozzleId.ToString()));
- var sendEmailResult = await TrySendAlarmEmailsAsync(
- $"lang-zh-cn:在线监测系统异常情况报告 - {this.appConfig.StationMiscConfig?.StationName ?? ""}lang-zh-tw:在線監測系統異常情況報告 - {this.appConfig.StationMiscConfig?.StationName ?? ""}".LocalizedContent("zh-tw"),
- $"lang-zh-cn:油枪: {nozzlesStr} 气液比数值<b>连续多日</b>不合格,请即时处理.lang-zh-tw:油槍: {nozzlesStr} 氣液比數值<b>連續多日</b>不合格,請即時處理.".LocalizedContent("zh-tw"));
- logger.LogInformation($"Firing alarm for VR, TrySendAlarmEmails with overall result: {sendEmailResult}");
- }
- await universalApiHub?.FireEvent(this, OnVaporRecoveryDataCollectorBoardAlarmsEventName, new VRBoardAlarmListEventArgs(alarmRecords));
- }, null, nextPlayTime - DateTime.Now, TimeSpan.FromHours(24));
- }
- else
- {
- logger.LogInformation($"ScheduleDailyWarningPlayTimer next rescheduled time: {nextPlayTime.ToString()}, " +
- $"due in {(nextPlayTime - DateTime.Now).TotalSeconds} seconds");
- this.dailyVRBoardNotifyWarningTimer.Change(nextPlayTime - DateTime.Now, TimeSpan.FromHours(24));
- }
- }
- private void ScheduleDailyWarningCheckTimerForTankPressure()
- {
- var nextDay = DateTime.Today.AddDays(1);
- var untilNextCheck = nextDay - DateTime.Now;
- if (dailyTankPressureWarningTimer == null)
- {
- logger.LogInformation($"ScheduleDailyWarningCheckTimerForTankPressure at : {nextDay.ToString()}, " +
- $"due in {(nextDay - DateTime.Now).TotalSeconds} seconds");
- this.dailyTankPressureWarningTimer = new Timer(async (_) =>
- {
- logger.LogInformation($"DailyTankPressureWarningTimer with lastTankPressureWarningTime: {lastTankPressureWarningTime}, " +
- $"lastTankPressureWarningDate {lastTankPressureWarningDate}");
- if (lastTankPressureWarningTime.Year != 1)
- {
- lastTankPressureWarningTime = new DateTime();
- await SaveOrUpdateDbAsync("LastTankPressureWarningTime", new GenericData()
- {
- Type = "LastTankPressureWarningTime",
- CreatedTimeStamp = lastTankPressureWarningTime
- });
- }
- var yesterday = DateTime.Today.AddDays(-1);
- if (lastTankPressureWarningDate.Year != 1 && !lastTankPressureWarningDate.Equals(yesterday))
- {
- tankPressureWarningTotalDays = 0;
- lastTankPressureWarningDate = new DateTime();
- await SaveOrUpdateDbAsync("LastTankPressureWarningDate", new GenericData()
- {
- Type = "LastTankPressureWarningDate",
- CreatedTimeStamp = lastTankPressureWarningDate,
- IntProperty0 = tankPressureWarningTotalDays
- });
- }
- }, null, untilNextCheck, TimeSpan.FromHours(24));
- }
- else
- {
- logger.LogInformation($"Rescheduled next TankPressure check will be triggered at : {nextDay.ToString()}, " +
- $"due in {(nextDay - DateTime.Now).TotalSeconds} seconds");
- this.dailyTankPressureWarningTimer?.Change(untilNextCheck, TimeSpan.FromHours(24));
- }
- }
- private void ScheduleDailyWarningCheckTimerForVRBoard()
- {
- var checkingTime = this.appConfig.VaporRecoveryConfig.RetrospectiveWarningCheckingTime;
- var time = checkingTime.Split(".");
- if (time.Length != 2)
- throw new ArgumentException($"illegal VRDataCollectorBoard WarningCheckingTime value: ${checkingTime ?? ""}");
- double.TryParse(time[0], out double hour);
- double.TryParse(time[1], out double minute);
- var now = DateTime.Now;
- DateTime today = now.Date.AddHours(hour).AddMinutes(minute);
- DateTime nextDay = now <= today ? today : today.AddDays(1);
- var untilNextCheck = nextDay.AddSeconds(-1) - DateTime.Now;
- if (dailyVRBoardCheckingStateTimer == null)
- {
- logger.LogInformation($"ScheduleDailyWarningCheckTimerForVRBoard at : {nextDay.ToString()}, " +
- $"due in {(nextDay - DateTime.Now).TotalSeconds} seconds");
- this.dailyVRBoardCheckingStateTimer = new Timer(async (_) =>
- {
- var startTime = DateTime.Now.Subtract(new TimeSpan(0, 0,
- (int)(this.appConfig.VaporRecoveryConfig.TurnToWarningStateInRecentHoursThreshold * 3600)));
- var endTime = DateTime.Now;
- logger.LogInformation($"Daily Warning Check Timer is kicking off(target time range is from: {startTime.ToString("yyyy-MM-dd HH:mm:ss")} to: {endTime.ToString("yyyy-MM-dd HH:mm:ss")})...");
- await CaculateAndPersistVRBoardHealthStateForNozzles(startTime, endTime);
- }, null, untilNextCheck, TimeSpan.FromHours(24));
- }
- else
- {
- logger.LogInformation($"Rescheduled next vr state check will be triggered at : {nextDay.ToString()}, " +
- $"due in {(nextDay - DateTime.Now).TotalSeconds} seconds");
- this.dailyVRBoardCheckingStateTimer?.Change(untilNextCheck, TimeSpan.FromHours(24));
- }
- }
- private async Task CaculateAndPersistVRBoardHealthStateForNozzles(DateTime startTime, DateTime endTime)
- {
- var nozzlesTrxFlowDatas = await dbHelperForVRBoard.GetNozzlesTrxFlowDatasByTimeRange((startTime, endTime));
- // todo change WarningTimeRangeFromNow to WarningTimeRange
- var nozzleWithTrxFlowDataRatiosGroup = nozzlesTrxFlowDatas.GroupBy(g => g.SiteLevelNozzleId).Select(g => new
- {
- SiteLevelNozzleId = g.Key,
- TrxFlowDataRatios = g.Select(x => x.VaporLiquidRatio).ToList()
- });
- foreach (var nozzle in this.vrBoardNozzles)
- {
- var alarm = new VRBoardAlarmRecord();
- var nozzleWithTrxFlowDataRatios = nozzleWithTrxFlowDataRatiosGroup.FirstOrDefault(g => g.SiteLevelNozzleId == nozzle.SiteLevelNozzleId);
- if (nozzleWithTrxFlowDataRatios == null) // no fueling during last warning period, typically 24 hours
- {
- logger.LogInformation($"Nozzle VR Health Check, SiteLevelNozzle: {nozzle.SiteLevelNozzleId} has no data at all, set HealthState to NORMAL");
- nozzle.HealthState = VRBoardNozzleTrxHealthStateEnum.NORMAL;
- alarm = CreateNoAlarmRecord(nozzle);
- }
- else
- {
- var perNozzleRule = this.GetNozzleVRGroupQualificationConfig(nozzle.SiteLevelNozzleId);
- double unqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold;
- int unqualifiedTrxCount;
- if (perNozzleRule == null)
- {
- unqualifiedTrxCount = nozzleWithTrxFlowDataRatios.TrxFlowDataRatios
- .Where(i => i < this.appConfig.VaporRecoveryConfig.QualifiedAirLiquidRatioMin
- || i > this.appConfig.VaporRecoveryConfig.QualifiedAirLiquidRatioMax).ToList().Count;
- unqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold = this.appConfig.VaporRecoveryConfig.UnqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold;
- }
- else
- {
- unqualifiedTrxCount = nozzleWithTrxFlowDataRatios.TrxFlowDataRatios
- .Where(i => i < perNozzleRule.QualifiedAirLiquidRatioMin
- || i > perNozzleRule.QualifiedAirLiquidRatioMax).ToList().Count;
- unqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold = perNozzleRule.UnqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold;
- }
- var totalTrxCount = nozzleWithTrxFlowDataRatios.TrxFlowDataRatios.ToList().Count;
- logger.LogInformation($"Nozzle VR Health Check, SiteLevelNozzle: {nozzle.SiteLevelNozzleId} using {(perNozzleRule == null ? "global" : "perNozzle")} rule, has {unqualifiedTrxCount} unqualified trx data, and total trx data count is {totalTrxCount}");
- if (totalTrxCount > 0)
- {
- var unqualifiedRatio = ((double)unqualifiedTrxCount) / totalTrxCount;
- var threshold = (unqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold / 100.0);
- if (unqualifiedRatio > threshold)
- {
- logger.LogInformation($"Nozzle VR Health Check, SiteLevelNozzle: {nozzle.SiteLevelNozzleId} will be set with HealthState to WARNING as unqualifiedRatio is: {unqualifiedRatio} which > threshold: {threshold}");
- nozzle.HealthState = VRBoardNozzleTrxHealthStateEnum.WARNING;
- alarm.AlarmTime = DateTime.Now.AddHours(-1);
- alarm.SiteLevelNozzleId = nozzleWithTrxFlowDataRatios.SiteLevelNozzleId;
- alarm.AlarmType = VRBoardAlarmType.WARNING;
- alarm.AlarmDescription = $"lang-zh-cn:{nozzleWithTrxFlowDataRatios.SiteLevelNozzleId}号枪气液比报警lang-zh-tw:{nozzleWithTrxFlowDataRatios.SiteLevelNozzleId}號槍氣液比報警";
- alarm.AlarmDetails =
- $"lang-zh-cn:{nozzleWithTrxFlowDataRatios.SiteLevelNozzleId}号枪,使用{(perNozzleRule == null ? "全局" : "单枪")}规则," +
- $"有效气液比{totalTrxCount}次,不合格{unqualifiedTrxCount}次,不合格占比{Util.Round(unqualifiedRatio) * 100}%," +
- $"平均气液比{Util.Round(nozzleWithTrxFlowDataRatios.TrxFlowDataRatios.Average())}" +
- $"lang-zh-tw:{nozzleWithTrxFlowDataRatios.SiteLevelNozzleId}號槍,使用{(perNozzleRule == null ? "全局" : "單槍")}規則," +
- $"有效氣液比{totalTrxCount}次,不合格{unqualifiedTrxCount}次,不合格占比{Util.Round(unqualifiedRatio) * 100}%," +
- $"平均氣液比{Util.Round(nozzleWithTrxFlowDataRatios.TrxFlowDataRatios.Average())}";
- alarm.TimeInterval = unqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold;
- alarm.RecordCount = totalTrxCount;
- alarm.AlarmCount = unqualifiedTrxCount;
- alarm.AlarmPercentage = Util.RoundToDouble(unqualifiedRatio);
- alarm.AverageVaporLiquidRatio = Util.RoundToDouble(nozzleWithTrxFlowDataRatios.TrxFlowDataRatios.Average());
- alarm.Acknowledged = 0;
- var alarmConditionCheckFrom = endTime.Date.Subtract(TimeSpan.FromHours(24 * (this.appConfig.VaporRecoveryConfig.WarningToAlarmStateLastingDaysThreshold - 1)));
- var alarmConditionCheckTo = endTime.Date;
- logger.LogInformation($"Nozzle VR Health Check, SiteLevelNozzle: {nozzle.SiteLevelNozzleId} Warning to Alarm state is on caculating(target time range is from: {alarmConditionCheckFrom.ToString("yyyy-MM-dd HH:mm:ss")} to: {alarmConditionCheckTo.ToString("yyyy-MM-dd HH:mm:ss")})...");
- var recentWarningOrAlarmRecords = await dbHelperForVRBoard.GetWarningOrAlarmStateRecordsByTimeRange(
- (alarmConditionCheckFrom, alarmConditionCheckTo),
- nozzleWithTrxFlowDataRatios.SiteLevelNozzleId);
- logger.LogInformation($"Nozzle VR Health Check, SiteLevelNozzle: {nozzle.SiteLevelNozzleId} recent WarningOrAlarm Records count is {recentWarningOrAlarmRecords.Count()}");
- if (recentWarningOrAlarmRecords.Count() >= (this.appConfig.VaporRecoveryConfig.WarningToAlarmStateLastingDaysThreshold - 1))
- {
- alarm.AlarmType = VRBoardAlarmType.ALARM;
- nozzle.HealthState = VRBoardNozzleTrxHealthStateEnum.ALARM;
- }
- }
- else
- {
- logger.LogInformation($"Nozzle VR Health Check, SiteLevelNozzle: {nozzle.SiteLevelNozzleId} will be set with HealthState to NORMAL as unqualifiedRatio is: {unqualifiedRatio} which <= threshold: {threshold}");
- alarm.AlarmTime = DateTime.Now.AddHours(-1);
- alarm.SiteLevelNozzleId = nozzleWithTrxFlowDataRatios.SiteLevelNozzleId;
- alarm.AlarmType = VRBoardAlarmType.NONE;
- alarm.AlarmDescription = $"{nozzleWithTrxFlowDataRatios.SiteLevelNozzleId}号枪气液比正常";
- alarm.AlarmDetails = $"{nozzleWithTrxFlowDataRatios.SiteLevelNozzleId}号枪," +
- $"有效气液比{totalTrxCount}次,不合格{unqualifiedTrxCount}次,不合格占比{Util.Round(unqualifiedRatio) * 100}%," +
- $"平均气液比{Util.Round(nozzleWithTrxFlowDataRatios.TrxFlowDataRatios.Average())}";
- alarm.TimeInterval = unqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold;
- alarm.RecordCount = totalTrxCount;
- alarm.AlarmCount = unqualifiedTrxCount;
- alarm.AlarmPercentage = Util.RoundToDouble(unqualifiedRatio);
- alarm.AverageVaporLiquidRatio = Util.RoundToDouble(nozzleWithTrxFlowDataRatios.TrxFlowDataRatios.Average());
- alarm.Acknowledged = 0;
- nozzle.HealthState = VRBoardNozzleTrxHealthStateEnum.NORMAL;
- }
- }
- else
- {
- alarm = CreateNoAlarmRecord(nozzle);
- nozzle.HealthState = VRBoardNozzleTrxHealthStateEnum.NORMAL;
- }
- }
- await SaveDbAsync(alarm);
- logger.LogInformation($"Nozzle VR Health Check, SiteLevelNozzle: {nozzle.SiteLevelNozzleId} done by save an Alarm with type: {alarm.AlarmType} into db.");
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FireEvent(this,
- OnVaporRecoveryDataCollectorBoardNozzleStateChangeEventName, nozzle);
- }
- }
- private VRBoardAlarmRecord CreateNoAlarmRecord(VRBoardNozzle nozzle)
- {
- var perNozzleRule = this.GetNozzleVRGroupQualificationConfig(nozzle.SiteLevelNozzleId);
- double unqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold;
- if (perNozzleRule == null)
- unqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold = this.appConfig.VaporRecoveryConfig.UnqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold;
- else
- unqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold = perNozzleRule.UnqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold;
- return new VRBoardAlarmRecord()
- {
- AlarmTime = DateTime.Now.AddHours(-1),
- SiteLevelNozzleId = nozzle.SiteLevelNozzleId,
- AlarmType = VRBoardAlarmType.NONE,
- AlarmDescription = $"{nozzle.SiteLevelNozzleId}号枪气液比正常(没有交易)",
- AlarmDetails = $"{nozzle.SiteLevelNozzleId}号枪, 有效气液比0次, 不合格0次, 不合格占比0%, 平均气液比0",
- TimeInterval = unqualifiedAirLiquidRatioRecordsPecentageTurnToWarningStateThreshold,
- RecordCount = 0,
- AlarmCount = 0,
- AlarmPercentage = 0,
- AverageVaporLiquidRatio = 0,
- Acknowledged = 0
- };
- }
- private bool Is气液比值是否正常(double vaporLiquidRatio, int? sourceNozzleSiteLevelId)
- {
- if (sourceNozzleSiteLevelId != null)
- {
- var rule = this.GetNozzleVRGroupQualificationConfig(sourceNozzleSiteLevelId.Value);
- if (rule != null)
- return vaporLiquidRatio >= rule.QualifiedAirLiquidRatioMin && vaporLiquidRatio <= rule.QualifiedAirLiquidRatioMax;
- }
- return vaporLiquidRatio >= this.appConfig.VaporRecoveryConfig.QualifiedAirLiquidRatioMin && vaporLiquidRatio <= this.appConfig.VaporRecoveryConfig.QualifiedAirLiquidRatioMax;
- }
- private PerNozzleVRGroupQualificationDefinitionV1 GetNozzleVRGroupQualificationConfig(int siteLevelNozzleId)
- {
- var definition = this.appConfig.VaporRecoveryConfig.PerNozzleVRConfig?.QualificationDefinitions
- ?.FirstOrDefault(m => m.NozzleIdsString.Split(';').Any(nId => nId == siteLevelNozzleId.ToString()));
- return definition;
- }
- private async Task SaveDbAsync<T>(T data)
- {
- var dbModel = this.objMapper.Map<GenericData>(data);
- using (var scope = this.services.CreateScope())
- {
- try
- {
- var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
- dbContext.GenericDatas.Add(dbModel);
- await dbContext.SaveChangesAsync();
- }
- catch (Exception ex)
- {
- logger.LogError($"Exception In SaveDbAsync for data: {data}, exception: {ex}");
- }
- }
- }
- private IEnumerable<GenericData> GetLastWarningTimeData(string type)
- {
- using (var scope = this.services.CreateScope())
- {
- var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
- var genericDatas = dbContext.GenericDatas.Where(gd =>
- gd.Type == type
- && gd.Owner == "VaporRecoveryOnlineWatchHubApp");
- return this.objMapper.Map<IEnumerable<GenericData>>(genericDatas.ToList());
- }
- }
- private async Task SaveOrUpdateDbAsync(string type, GenericData data)
- {
- using (var scope = this.services.CreateScope())
- {
- try
- {
- var dbContext = scope.ServiceProvider.GetRequiredService<SqliteDbContext>();
- var genericDatas = dbContext.GenericDatas.Where(gd =>
- gd.Type == type
- && gd.Owner == AutoMapperProfile.VaporRecoveryOnlineWatchHubApp_MapToDbEntity_Owner);
- if (genericDatas.Count() > 0)
- dbContext.GenericDatas.RemoveRange(genericDatas);
- data.Owner = AutoMapperProfile.VaporRecoveryOnlineWatchHubApp_MapToDbEntity_Owner;
- dbContext.GenericDatas.Add(data);
- await dbContext.SaveChangesAsync();
- }
- catch (Exception ex)
- {
- logger.LogError($"In SaveOrUpdateDbAsync: {ex}");
- }
- }
- }
- }
- }
|