12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127 |
- using Applications.FDC;
- using AutoMapper;
- using Edge.Core.Database;
- using Edge.Core.Processor;
- using Edge.Core.IndustryStandardInterface.ATG;
- using Edge.Core.UniversalApi;
- using Dfs.WayneChina.FairbanksRTData.Support;
- using Dfs.WayneChina.FairbanksRTData.UniversalApiModels;
- using FdcServerHost;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Logging;
- using Microsoft.Extensions.Logging.Abstractions;
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.IO;
- using System.IO.Compression;
- using System.Linq;
- using System.Reflection;
- using System.Text;
- using System.Text.Json;
- using System.Text.RegularExpressions;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Timers;
- using Edge.Core.Database.Models;
- using Edge.Core.Processor.Dispatcher.Attributes;
- namespace Dfs.WayneChina.FairbanksRTData
- {
- [MetaPartsDescriptor(
- "lang-zh-cn:Fairbanks实时数据上传应用lang-en-us:Fairbank real time data App",
- "lang-zh-cn:用于传输加油站实时数据,如:加油、液位仪、告警等数据到Fairbanks服务器" +
- "lang-en-us:Used for uploading real time data in specified format to Fairbanks server",
- new[] { "lang-zh-cn:Fairbankslang-en-us:Fairbanks" })]
- public class FairbanksRealTimeDataApp : IAppProcessor
- {
- #region Fields
- public string MetaConfigName { get; set; }
- private ILogger appLogger = NullLogger.Instance;
- private IServiceProvider services;
- private FdcServerHostApp fdcServer;
- private System.Timers.Timer tankReadTimer;
- private System.Timers.Timer uploadTimer;
- private string host;
- private int port;
- private string username;
- private string password;
- private string deviceId;
- private string siteName;
- private int tankReadInterval;
- private int uploadInterval;
- private string hostUploadFolder;
- private IEnumerable<IAutoTankGaugeController> autoTankGaugeControllers;
- private string transactionsFileName = "transactions";
- private string transactionsFilePath;
- private string tankReadingFileName = "stockLevels";
- private string stockLevelsFilePath;
- private string alarmsFileName = "alarms";
- private string alarmsFilePath;
- private string deliveriesFileName = "deliveries";
- private string deliveriesFilePath;
- private string requiredFolder;
- private string tempFolder;
- private string uploadedFilesFolder;
- private string folderSeperator = "/";
- private FairbanksSftpClient fbSftpClient;
- // Persistence
- private IMapper objMapper;
- private readonly SqliteDbContext dbContext;
- private AppConfig appConfig;
- private DbHelper dbHelper;
- private ConcurrentQueue<string> csvFileQueue = new ConcurrentQueue<string>();
- private ReaderWriterLockSlim fileLocker = new ReaderWriterLockSlim();
- private int sortToken = 0;
- #endregion
- #region Tank State Dictionary
- private Dictionary<int, TankState> tankStateDict = new Dictionary<int, TankState>();
- #endregion
- #region Logger
- NLog.Logger logger = NLog.LogManager.LoadConfiguration("NLog.config").GetLogger("Fairbanks");
- #endregion
- #region Constructor
- [ParamsJsonSchemas("appCtorParamsJsonSchema")]
- public FairbanksRealTimeDataApp(IServiceProvider services, FairbanksAppConfigV1 config)
- {
- this.services = services;
- var loggerFactory = services.GetRequiredService<ILoggerFactory>();
- appLogger = loggerFactory.CreateLogger("Application");
- objMapper = services.GetRequiredService<IMapper>();
- dbContext = services.GetRequiredService<SqliteDbContext>();
- if (services != null)
- dbHelper = new DbHelper(services);
- host = config.Host;
- port = config.Port;
- username = config.Username;
- password = config.Password;
- deviceId = config.DeviceId;
- siteName = config.SiteName;
- tankReadInterval = config.Interval;
- uploadInterval = config.UploadInterval;
- hostUploadFolder = config.HostUploadFolder;
- fbSftpClient = new FairbanksSftpClient(host, username, password);
- }
- public FairbanksRealTimeDataApp(IServiceProvider services, string host, int port, string username, string password,
- string deviceId, string siteName, int interval, int uploadInterval, string hostUploadFolder)
- {
- this.services = services;
- var loggerFactory = services.GetRequiredService<ILoggerFactory>();
- appLogger = loggerFactory.CreateLogger("Application");
- objMapper = services.GetRequiredService<IMapper>();
- dbContext = services.GetRequiredService<SqliteDbContext>();
- if (services != null)
- dbHelper = new DbHelper(services);
- this.host = host;
- this.port = port;
- this.username = username;
- this.password = password;
- this.deviceId = deviceId;
- this.siteName = siteName;
- this.tankReadInterval = interval;
- this.uploadInterval = uploadInterval;
- this.hostUploadFolder = hostUploadFolder;
- fbSftpClient = new FairbanksSftpClient(host, username, password);
- }
- #endregion
- #region Init
- public void Init(IEnumerable<IProcessor> processors)
- {
- //get the FdcServer instance during init
- foreach (dynamic processor in processors)
- {
- if (processor is IAppProcessor)
- {
- FdcServerHostApp fdcServer = processor as FdcServerHostApp;
- if (fdcServer != null)
- {
- logger.Info("FdcServerHostApp retrieved as an IApplication instance!");
- this.fdcServer = processor;
- }
- continue;
- }
- }
- if (fdcServer != null)
- logger.Info("Fdc Server instance alive");
- var atgControllers = processors.WithHandlerOrApp<IAutoTankGaugeController>()
- .Select(p => p.ProcessorDescriptor().DeviceHandlerOrApp)
- .Cast<IAutoTankGaugeController>();
- autoTankGaugeControllers = atgControllers;
- if (autoTankGaugeControllers != null)
- {
- foreach (var controller in autoTankGaugeControllers)
- {
- controller.OnStateChange += Controller_OnStateChange;
- controller.OnAlarm += Controller_OnAlarm;
- }
- }
- }
- #region Alarms
- private async void Controller_OnAlarm(object sender, AtgAlarmEventArg e)
- {
- logger.Info("Incoming alarm");
- var currentGauge = sender as IAutoTankGaugeController;
- if (currentGauge != null)
- {
- var alarms = new List<FbAlarm>();
- foreach (var alarm in e.Alarms)
- {
- logger.Info($"Gauge: {currentGauge.DeviceId}");
- logger.Info($" tank number: {alarm.TankNumber}, description: {alarm.Description}");
- logger.Info($" alarm id: {alarm.Id}, priority: {alarm.Priority}, type: {alarm.Type}");
- logger.Info($" created time: {alarm.CreatedTimeStamp}, cleared time: {alarm.ClearedTimeStamp}");
- var fbAlarm = new FbAlarm();
- fbAlarm.ibank = deviceId;
- fbAlarm.DateTime = alarm.CreatedTimeStamp;
- fbAlarm.CodedAlarm = "1";
- fbAlarm.Category = "TLS_ALARM";
- fbAlarm.Type = ""; // can be left blank or null
- fbAlarm.AlarmCode = string.Concat("TLS_", "02_", ((byte)alarm.Type).ToString().PadLeft(2, '0'));
- fbAlarm.TankAlarm = "1"; //1=tank alarm, 0=other device alarm or things
- fbAlarm.Tank = alarm.TankNumber;
- fbAlarm.State = 0; // active = 0, cleared = 1
- fbAlarm.Note = alarm.Description;
- alarms.Add(fbAlarm);
- }
- await RecordAlarmsAsync(alarms);
- }
- }
- private async Task RecordAlarmsAsync(IEnumerable<FbAlarm> alarms)
- {
- try
- {
- fileLocker.TryEnterReadLock(500);
- SetAlarmsFile(requiredFolder);
- }
- catch (Exception ex)
- {
- logger.Error(ex.ToString());
- }
- finally
- {
- fileLocker.ExitReadLock();
- }
- if (!File.Exists(alarmsFilePath))
- {
- logger.Info("Creating alarms file path");
- await WriteCsvAsync(new List<FbAlarm>(), alarmsFilePath);
- }
- logger.Info("Writing alarms info into file");
- await WriteCsvAsync(alarms, alarmsFilePath);
- }
- #endregion
- #region Fuel deliveries
- private async void Controller_OnStateChange(object sender, AtgStateChangeEventArg e)
- {
- if (e.State == AtgState.TanksReloaded)
- {
- logger.Info("Tank reloaded, ready for use");
- }
- if (e.TargetTank != null)
- {
- if (e.TargetTank.State == TankState.Delivering)
- {
- logger.Info("Delivering fuel...");
- if (!tankStateDict.ContainsKey(e.TargetTank.TankNumber))
- {
- logger.Info($"Adding tank {e.TargetTank.TankNumber.ToString()} to the tank state dict");
- tankStateDict.Add(e.TargetTank.TankNumber, e.TargetTank.State);
- }
- }
- else if (e.TargetTank.State == TankState.Idle)
- {
- logger.Info("Tank state: Idle");
- var currentTankState = tankStateDict[e.TargetTank.TankNumber];
- if (currentTankState == TankState.Delivering && e.TargetTank.State == TankState.Idle)
- {
- logger.Info($"Tank {e.TargetTank.TankNumber.ToString()} state changed, Delivering -> Idle, check fuel delivery");
- tankStateDict[e.TargetTank.TankNumber] = TankState.Idle;
- var currentAtg = sender as IAutoTankGaugeController;
- if (currentAtg != null)
- {
- await RecordFuelDelivery(currentAtg, e.TargetTank.TankNumber);
- }
- }
- }
- }
- }
- private async Task RecordFuelDelivery(IAutoTankGaugeController autoTankGauge, int tankNumber)
- {
- var deliveries = await autoTankGauge.GetTankDeliveryAsync(tankNumber);
- var fbFuelDeliveries = new List<FbFuelDelivery>();
- foreach (var delivery in deliveries)
- {
- logger.Info($"Tank: {delivery.TankNumber}, FuelHeight, start: {delivery.StartingFuelHeight}, end: {delivery.EndingFuelHeight}");
- logger.Info($" Fuel TC Volume, start: {delivery.StartingFuelTCVolume}, end: {delivery.EndingFuelTCVolume}");
- logger.Info($" Fuel Volume, start: {delivery.StartingFuelVolume}, end: {delivery.EndingFuelVolume}");
- var fbFuelDelivery = new FbFuelDelivery();
- fbFuelDelivery.ibank = deviceId;
- fbFuelDelivery.Tank = delivery.TankNumber;
- fbFuelDelivery.StartDateTime = delivery.StartingDateTime;
- fbFuelDelivery.StartVolume = (decimal)delivery.StartingFuelVolume;
- fbFuelDelivery.StartWater = (decimal)delivery.StartingWaterHeight;
- fbFuelDelivery.StartTemperature = (decimal)delivery.StartingTemperture;
- fbFuelDelivery.EndDateTime = (DateTime)delivery.EndingDateTime;
- fbFuelDelivery.EndVolume = (decimal)delivery.EndingFuelVolume;
- fbFuelDelivery.EndWater = (decimal)delivery.EndingWaterHeight;
- fbFuelDelivery.EndTemperature = (decimal)delivery.EndingTemperture;
- fbFuelDelivery.Quantity = 0m;
- fbFuelDelivery.ConfirmedQuantity = 0m;
- fbFuelDeliveries.Add(fbFuelDelivery);
- }
- try
- {
- fileLocker.TryEnterReadLock(500);
- SetDeliveriesFile(requiredFolder);
- }
- catch (Exception ex)
- {
- logger.Error(ex.ToString());
- }
- finally
- {
- fileLocker.ExitReadLock();
- }
- if (!File.Exists(deliveriesFilePath))
- {
- logger.Info("Creating deliveries file path");
- await WriteCsvAsync(new List<FbFuelDelivery>(), deliveriesFilePath);
- }
- logger.Info("Writing deliveries info into file");
- await WriteCsvAsync(fbFuelDeliveries, deliveriesFilePath);
- }
- #endregion
- #endregion
- #region Folder and file name handling
- private void EnsureFolder()
- {
- string path = Directory.GetCurrentDirectory();
- requiredFolder = path + folderSeperator + "RealTimeData";
- if (!Directory.Exists(requiredFolder))
- Directory.CreateDirectory(requiredFolder);
- tempFolder = requiredFolder + folderSeperator + "temp";
- if (!Directory.Exists(requiredFolder + folderSeperator + "temp"))
- {
- Directory.CreateDirectory(tempFolder);
- }
- uploadedFilesFolder = requiredFolder + folderSeperator + "archives";
- if (!Directory.Exists(uploadedFilesFolder))
- Directory.CreateDirectory(uploadedFilesFolder);
- SetAlarmsFile(requiredFolder);
- SetDeliveriesFile(requiredFolder);
- SetTransactionFile(requiredFolder);
- SetTankReadingFile(requiredFolder);
- }
- private string CreateFileNameWithoutExt(string fileTypeName, string dateSection, string hourSection)
- {
- return string.Concat(requiredFolder, folderSeperator, /*deviceId, "_",*/ fileTypeName, "_", dateSection, "_", hourSection);
- }
- private void SetAlarmsFile(string requiredFolder)
- {
- var dateSection = DateTime.Now.Date.ToString("yyyy-MM-dd");
- var hourSection = DateTime.Now.Hour.ToString("D2");
- var fileNameWithoutExtension = CreateFileNameWithoutExt(alarmsFileName, dateSection, hourSection);
- logger.Debug($"FileNameWithoutExt: {fileNameWithoutExtension}");
- var filesInProcessing = csvFileQueue.Where(f => f.Contains(dateSection + "_" + hourSection)).ToList();
- foreach (var file in filesInProcessing)
- {
- logger.Debug($" Alarms files in queue: {file}");
- }
- var candidateFiles = GetMatchingFilesFromFolder(alarmsFileName, uploadedFilesFolder)
- .Concat(filesInProcessing);
- foreach (var cf in candidateFiles)
- {
- logger.Debug($"\tcdi: {cf}");
- }
- if (candidateFiles.Any(f => f.Contains('[')))
- {
- Interlocked.Increment(ref sortToken);
- var currentSortToken = sortToken;
- logger.Info($"Sort [{currentSortToken}] the candidates to find last Alarms file name");
- var lastFile = candidateFiles.OrderBy(f => f, new FileNameSuffixComparer()).Last();
- logger.Info($"Sort [{currentSortToken}] done, Last alarms file full name: {lastFile}");
- int startIndex = lastFile.IndexOf('[');
- int endIndex = lastFile.IndexOf(']');
- int number = Convert.ToInt32(lastFile.Substring(startIndex + 1, endIndex - startIndex - 1));
- alarmsFilePath = fileNameWithoutExtension + "[" + Convert.ToString(number + 1) + "]" + ".csv";
- logger.Debug($"Expected new alarms file: {alarmsFilePath}");
- }
- else if (candidateFiles.Any(f => Path.GetFileNameWithoutExtension(f) == Path.GetFileNameWithoutExtension(fileNameWithoutExtension)
- || Path.GetFileNameWithoutExtension(f) == Path.GetFileNameWithoutExtension(fileNameWithoutExtension + ".csv")))
- {
- logger.Info("Found a alarms file in `processing file`");
- alarmsFilePath = fileNameWithoutExtension + "[0]" + ".csv";
- }
- else
- {
- logger.Info("No previous alarms file");
- alarmsFilePath = fileNameWithoutExtension + ".csv";
- }
- if (csvFileQueue.Count > 5)
- {
- string dqFile;
- if (csvFileQueue.TryDequeue(out dqFile))
- logger.Info($"Dequeued alarms file: {dqFile}");
- }
- }
- private void SetDeliveriesFile(string requiredFolder)
- {
- //It's not possible that within an hour there are multiple fuel deliveries, is it?
- var dateSection = DateTime.Now.Date.ToString("yyyy-MM-dd");
- var hourSection = DateTime.Now.Hour.ToString("D2");
- var fileNameWithoutExtension = requiredFolder + folderSeperator + /*deviceId + "_" +*/ deliveriesFileName + "_" + dateSection + "_" + hourSection;
- deliveriesFilePath = fileNameWithoutExtension + ".csv";
- }
- private void SetTransactionFile(string requiredFolder)
- {
- var dateSection = DateTime.Now.Date.ToString("yyyy-MM-dd");
- var hourSection = DateTime.Now.Hour.ToString("D2");
- var fileNameWithoutExtension = requiredFolder + folderSeperator + /*deviceId + "_" +*/ transactionsFileName + "_" + dateSection + "_" + hourSection;
- logger.Debug($"FileNameWithoutExt: {fileNameWithoutExtension}");
- var filesInProcessing = csvFileQueue.Where(f => f.Contains(dateSection + "_" + hourSection)).ToList();
- foreach (var file in filesInProcessing)
- {
- logger.Debug($" Transaction files in queue: {file}");
- }
- var candidateFiles = GetMatchingFilesFromFolder(transactionsFileName, uploadedFilesFolder)
- .Concat(filesInProcessing);
- foreach (var cf in candidateFiles)
- {
- logger.Debug($"\tcdi: {cf}");
- }
- if (candidateFiles.Any(f => f.Contains('[')))
- {
- Interlocked.Increment(ref sortToken);
- var currentSortToken = sortToken;
- logger.Info($"Sort [{currentSortToken}] the candidates to find last transaction file name");
- var lastFile = candidateFiles.OrderBy(f => f, new FileNameSuffixComparer()).Last();
- logger.Info($"Sort [{currentSortToken}] done, Last transaction file full name: {lastFile}");
- int startIndex = lastFile.IndexOf('[');
- int endIndex = lastFile.IndexOf(']');
- int number = Convert.ToInt32(lastFile.Substring(startIndex + 1, endIndex - startIndex - 1));
- transactionsFilePath = fileNameWithoutExtension + "[" + Convert.ToString(number + 1) + "]" + ".csv";
- logger.Debug($"Expected new transaction file: {stockLevelsFilePath}");
- }
- else if (candidateFiles.Any(f => Path.GetFileNameWithoutExtension(f) == Path.GetFileNameWithoutExtension(fileNameWithoutExtension)
- || Path.GetFileNameWithoutExtension(f) == Path.GetFileNameWithoutExtension(fileNameWithoutExtension + ".csv")))
- {
- logger.Info("Found a transaction file in `processing file`");
- transactionsFilePath = fileNameWithoutExtension + "[0]" + ".csv";
- }
- else
- {
- logger.Info("No previous transaction file");
- transactionsFilePath = fileNameWithoutExtension + ".csv";
- }
- if (csvFileQueue.Count > 5)
- {
- string dqFile;
- if (csvFileQueue.TryDequeue(out dqFile))
- logger.Info($"Dequeued transaction file: {dqFile}");
- }
- }
- private void SetTankReadingFile(string folder)
- {
- logger.Debug($"Determining TankReading file name, operating folder is: {folder}");
- var dateSection = DateTime.Now.Date.ToString("yyyy-MM-dd");
- var hourSection = DateTime.Now.Hour.ToString("D2");
- var fileNameWithoutExtension = folder + folderSeperator + /*deviceId + "_" +*/ tankReadingFileName + "_" + dateSection + "_" + hourSection;
- logger.Debug($"FileNameWithoutExt: {fileNameWithoutExtension}");
- var filesInProcessing = csvFileQueue.Where(f => f.Contains(dateSection + "_" + hourSection)).ToList();
- foreach (var file in filesInProcessing)
- {
- logger.Debug($" file in queue: {file}");
- }
- var candidateFiles = GetMatchingFilesFromFolder(tankReadingFileName, uploadedFilesFolder).Concat(filesInProcessing);
- foreach (var cf in candidateFiles)
- {
- logger.Debug($"\tcdi: {cf}");
- }
- if (candidateFiles.Any(f => f.Contains('[')))
- {
- Interlocked.Increment(ref sortToken);
- var currentSortToken = sortToken;
- logger.Info($"Sort [{currentSortToken}] the candidates to find last stock reading file name");
- var lastFile = candidateFiles.OrderBy(f => f, new FileNameSuffixComparer()).Last();
- logger.Info($"Sort [{currentSortToken}] done, Last stock reading file full name: {lastFile}");
- int startIndex = lastFile.IndexOf('[');
- int endIndex = lastFile.IndexOf(']');
- int number = Convert.ToInt32(lastFile.Substring(startIndex + 1, endIndex - startIndex - 1));
- stockLevelsFilePath = fileNameWithoutExtension + "[" + Convert.ToString(number + 1) + "]" + ".csv";
- logger.Debug($"Expected new stock reading file: {stockLevelsFilePath}");
- }
- else if (candidateFiles.Any(f => Path.GetFileNameWithoutExtension(f) == Path.GetFileNameWithoutExtension(fileNameWithoutExtension)
- || Path.GetFileNameWithoutExtension(f) == Path.GetFileNameWithoutExtension(fileNameWithoutExtension + ".csv")))
- {
- logger.Info("Found an in processing file");
- stockLevelsFilePath = fileNameWithoutExtension + "[0]" + ".csv";
- }
- else
- {
- logger.Info("No previous tank reading file");
- stockLevelsFilePath = fileNameWithoutExtension + ".csv";
- }
- if (csvFileQueue.Count > 5)
- {
- string dqFile;
- if (csvFileQueue.TryDequeue(out dqFile))
- logger.Info($"Dequeued file: {dqFile}");
- }
- }
- private void CleanOldFiles(string folder, string fileName)
- {
- var di = new DirectoryInfo(folder);
- var files = di.GetFiles().Where(f => f.Name.Contains(fileName)).OrderByDescending(f => f.LastAccessTime);
- var oldFiles = files.Skip(10);
- foreach (var file in oldFiles)
- {
- logger.Debug($"Deleting file: {file.FullName}");
- file.Delete();
- }
- }
- private List<string> GetMatchingFilesFromFolder(string fileName, string folder)
- {
- logger.Info($"Finding matching {fileName} in folder: {folder}");
- var dateSection = DateTime.Now.Date.ToString("yyyy-MM-dd");
- var hourSection = DateTime.Now.Hour.ToString("D2");
- CleanOldFiles(folder, fileName);
- var archivedFiles = Directory.GetFiles(folder, "*.zip");
- logger.Info($"Archived files count = {archivedFiles.Length}");
- var matchedFileNames = archivedFiles
- .Where(f => f.Contains(dateSection + "_" + hourSection) && f.Contains(fileName)
- && Regex.IsMatch(Path.GetFileNameWithoutExtension(f), @"[a-zA-Z]{11}_\d{4}-\d{2}-\d{2}_\d{2}\[(.*?)\]"));
- logger.Info($"Matched files count = {matchedFileNames.Count()}");
- if (!matchedFileNames.Any())
- {
- var fileNameWithoutExtension = folder + folderSeperator + fileName + "_" + dateSection + "_" + hourSection;
- if (File.Exists(fileNameWithoutExtension + ".zip"))
- return new List<string> { fileNameWithoutExtension };
- }
- return matchedFileNames.ToList();
- }
- private List<string> GetMatchingTransactionFilesFromFolder(string fileName, string folder)
- {
- logger.Info($"Finding matching {fileName} in folder: {folder}");
- var dateSection = DateTime.Now.Date.ToString("yyyy-MM-dd");
- var hourSection = DateTime.Now.Hour.ToString("D2");
- CleanOldFiles(folder, fileName);
- var archivedFiles = Directory.GetFiles(folder, "*.zip");
- logger.Info($"Archived files count = {archivedFiles.Length}");
- var matchedFileNames = archivedFiles
- .Where(f => f.Contains(dateSection + "_" + hourSection) && f.Contains(fileName)
- && Regex.IsMatch(Path.GetFileNameWithoutExtension(f), @"[a-zA-Z]{11}_\d{4}-\d{2}-\d{2}_\d{2}\[(.*?)\]"));
- logger.Info($"Matched files count = {matchedFileNames.Count()}");
- if (!matchedFileNames.Any())
- {
- var fileNameWithoutExtension = folder + folderSeperator + transactionsFileName + "_" + dateSection + "_" + hourSection;
- if (File.Exists(fileNameWithoutExtension + ".zip"))
- return new List<string> { fileNameWithoutExtension };
- }
- return matchedFileNames.ToList();
- }
- #endregion
- #region Start
- public Task<bool> Start()
- {
- EnsureFolder();
- if (fdcServer != null)
- fdcServer.OnCurrentFuellingStatusChange += FdcServer_OnCurrentFuellingStatusChange;
- uploadTimer = new System.Timers.Timer();
- uploadTimer.Interval = uploadInterval * 60 * 1000;
- uploadTimer.Elapsed += Timer_Elapsed;
- uploadTimer.Enabled = true;
- tankReadTimer = new System.Timers.Timer();
- tankReadTimer.Interval = tankReadInterval * 1000;
- tankReadTimer.Elapsed += TankReadTimer_Elapsed;
- tankReadTimer.Enabled = true;
- logger.Info("\n");
- logger.Info("Fairbanks real time data client started");
- var config = GetAppConfig();
- RefreshConfig(config);
- return Task.FromResult(true);
- }
- #endregion
- #region Process records
- //Tank reading
- private async void TankReadTimer_Elapsed(object sender, ElapsedEventArgs e)
- {
- if (autoTankGaugeControllers != null)
- {
- var readings = new List<FbTankReading>();
- var atgController = autoTankGaugeControllers.First();
- if (atgController.Tanks == null)
- return;
- foreach (var tank in atgController.Tanks)
- {
- try
- {
- var inventory = await atgController.GetTankInventoryAsync(tank.TankNumber, 1);
- if (inventory != null)
- {
- var record = inventory.First();
- logger.Info($"Tank No: {record.TankNumber}, Time stamp: {record.TimeStamp}, " +
- $"Fuel Volume: {record.FuelVolume}, Fuel TCVolume: {record.FuelTCVolume}, " +
- $"Fuel Height: {record.FuelHeight}, Temperature: {record.Temperture}," +
- $"Water Height: {record.WaterHeight}, Fuel Height: {record.FuelHeight}");
- var reading = new FbTankReading();
- reading.ibank = deviceId;
- reading.DateTime = record.TimeStamp;
- reading.Tank = record.TankNumber;
- reading.QTY = Math.Round(Convert.ToDecimal(record.FuelVolume), 2);
- reading.Temperature = Math.Round(Convert.ToDecimal(record.Temperture), 2);
- reading.Water = Math.Round(Convert.ToDecimal(record.WaterHeight), 2);
- reading.Height = Math.Round(Convert.ToDecimal(record.FuelHeight), 2);
- readings.Add(reading);
- }
- }
- catch (Exception ex)
- {
- logger.Error($"Exception in getting tank inventory on Tank {tank.TankNumber}, ex: " + ex.ToString());
- }
- }
- if (readings.Count > 0)
- {
- await AddTankReadingsAsync(readings);
- }
- }
- }
- //Fuelling transactions
- private async void FdcServer_OnCurrentFuellingStatusChange(object sender, FdcServerTransactionDoneEventArg e)
- {
- if (e.Transaction.Finished)
- {
- logger.Info("A finished filling arrived and block the upload timer");
-
- await AddFillingTransactionAsync(e);
- }
- }
- private async Task AddTankReadingsAsync(List<FbTankReading> readings)
- {
- try
- {
- fileLocker.TryEnterReadLock(500);
- SetTankReadingFile(requiredFolder);
- }
- catch (Exception ex)
- {
- logger.Error(ex.ToString());
- }
- finally
- {
- fileLocker.ExitReadLock();
- }
- if (!File.Exists(stockLevelsFilePath))
- await WriteCsvAsync(new List<FbTankReading>(), stockLevelsFilePath);
- await WriteCsvAsync(readings, stockLevelsFilePath);
- }
- private async Task AddFillingTransactionAsync(FdcServerTransactionDoneEventArg e)
- {
- var filling = new FbFillingTransaction();
- filling.ibank = deviceId;
- filling.StartDateTime = DateTime.Now;
- filling.EndDateTime = e.FuelingEndTime.HasValue ? e.FuelingEndTime.Value : DateTime.Now;
- filling.Pump = e.Transaction.Nozzle.PumpId;
- filling.Nozzle = e.Transaction.Nozzle.LogicalId;
- filling.QTY = Convert.ToDecimal(e.Transaction.Volumn) / 100;
- var fillings = new List<FbFillingTransaction>();
- fillings.Add(filling);
- logger.Info("Converted fueling transaction");
- try
- {
- fileLocker.TryEnterReadLock(500);
- SetTransactionFile(requiredFolder);
- }
- catch (Exception ex)
- {
- logger.Error("Exception in adding filling transaction, ex: " + ex.ToString());
- }
- finally
- {
- fileLocker.ExitReadLock();
- }
- if (!File.Exists(transactionsFilePath))
- await WriteCsvAsync(new List<FbFillingTransaction>(), transactionsFilePath);
- await WriteCsvAsync(fillings, transactionsFilePath);
- }
- public async Task WriteCsvAsync<T>(IEnumerable<T> objInstances, string fileName)
- {
- logger.Debug($"Started to write record to file, File name: {fileName}");
- Type itemType = typeof(T);
- var props = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
- using (FileStream fs = new FileStream(fileName, FileMode.Append, FileAccess.Write))
- using (var writer = new StreamWriter(fs, Encoding.UTF8))
- {
- if (objInstances.Count() == 0)
- {
- logger.Debug($"Formatting Header");
- await writer.WriteLineAsync(string.Join(",", props.Select(p => p.Name)));
- }
- else
- {
- foreach (var obj in objInstances)
- {
- var propertyValues = props.Select(p => p.GetValue(obj, null)).ToList();
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < propertyValues.Count(); i++)
- {
- if (propertyValues[i] is DateTime)
- sb.Append(((DateTime)propertyValues[i]).ToString("yyyy-MM-dd HH:mm:ss"));
- else
- sb.Append(propertyValues[i]);
- if (i <= propertyValues.Count() - 2)
- sb.Append(",");
- }
- //sb.Append("\r");
- await writer.WriteLineAsync(sb.ToString());
- }
- }
- }
- logger.Debug("Writing done");
- }
- private async void Timer_Elapsed(object sender, ElapsedEventArgs e)
- {
- List<string> files = new List<string>();
- try
- {
- fileLocker.TryEnterReadLock(500);
- files = Directory.GetFiles(requiredFolder, "*.csv").ToList();
- foreach (var file in files)
- {
- if (!csvFileQueue.Contains(file))
- {
- logger.Debug($" Enqueuing CSV file: {file}");
- csvFileQueue.Enqueue(file);
- }
- }
- }
- catch (Exception ex)
- {
- logger.Error(ex.ToString());
- }
- finally
- {
- fileLocker.ExitReadLock();
- }
- var fileCount = files.Count();
- logger.Debug($"File count: {fileCount}");
- fbSftpClient.Connect();
- foreach (var currentFile in files)
- {
- logger.Info($"File full name: {currentFile}");
- File.Move(currentFile, tempFolder + folderSeperator + Path.GetFileName(currentFile));
- var zippedFileName = Path.GetFileNameWithoutExtension(currentFile) + ".zip";
- ZipFile.CreateFromDirectory(tempFolder, requiredFolder + folderSeperator + zippedFileName);
- File.Delete(tempFolder + folderSeperator + Path.GetFileName(currentFile));
- var sendResult = fbSftpClient.UploadSingleFile(requiredFolder + folderSeperator + zippedFileName, hostUploadFolder);
- await SaveUploadHistory(zippedFileName, sendResult);
- ArchiveFile(requiredFolder + folderSeperator + zippedFileName);
- }
- fbSftpClient.Disconnect();
- }
- private void ArchiveFile(string currentFile)
- {
- logger.Info($"Archiving file: {currentFile}");
- try
- {
- File.Move(currentFile, uploadedFilesFolder + folderSeperator + Path.GetFileName(currentFile));
- }
- catch (IOException ex)
- {
- logger.Error("Error in archiving file: " + currentFile + "\n" + ex.ToString());
- }
- }
- #endregion
- #region Upload history
- [UniversalApi(Description = "Leave input <b>null</b> to retrieve latest Day's(24 hours) upload history" +
- "</br>input parameters are as follows, " +
- "</br>para.Name==\"startTime\", format should be yyyyMMddHHmmss")]
- public async Task<IEnumerable<UploadRecord>> GetUploadHistoryAsync(ApiData input)
- {
- if (input == null || (input?.IsEmpty() ?? false))
- {
- return await dbHelper.GetLatestUploadHistoryAsync(DateTime.Now.Subtract(new TimeSpan(24, 0, 0)));
- }
- else
- {
- var startTime = input.Get("starttime", DateTime.Now.Subtract(new TimeSpan(24, 0, 0)));
- return await dbHelper.GetLatestUploadHistoryAsync(startTime);
- }
- }
- #endregion
- #region Stop
- public Task<bool> Stop()
- {
- return Task.FromResult(true);
- }
- #endregion
- #region Config handling
- [UniversalApi(Description = "Leave input <b>null</b> to retrieve Fairbanks related Config" +
- "</br>First para.Name==\"template\" will return an sample Config for reference.")]
- public async Task<object> GetAppConfigAsync(ApiData input)
- {
- var uploadingConfig = new AppConfig();
- if (input?.Get("template") != null)
- {
- logger.Info($"GetAppConfigAsync with template");
- }
- else
- {
- uploadingConfig = GetAppConfig();
- }
- logger.Info($"GetConfigAsync, Fairbanks app config: {uploadingConfig.ToString()}");
- return await Task.FromResult(new[] { new { Name = "FairbanksAppConfig", Value = uploadingConfig } });
- }
- [UniversalApi(Description = "First para.Name is the updating config name, such as: FairbanksAppConfig, " +
- "</br>First para.Value is the new config's json serialized string value")]
- public async Task<object> PutConfigAsync(ApiData input)
- {
- //Check input
- if (input == null || input.Parameters == null || !input.Parameters.Any())
- {
- throw new ArgumentException(nameof(input));
- }
- try
- {
- AppConfig appConfig = GetConfig(input.Parameters.First().Name, input.Parameters.First().Value, new JsonSerializerOptions
- {
- PropertyNameCaseInsensitive = true,
- });
- bool appConfigExists = objMapper
- .Map<IEnumerable<AppConfig>>(dbContext.GenericDatas
- .Where(gd => gd.Type == AutoMapperProfile.UploadConfig && gd.Owner == AutoMapperProfile.AppDataOwner))
- .FirstOrDefault() != null;
- if (!appConfigExists)
- {
- //Create the config record
- await SaveDbAsync(appConfig);
- logger.Info($"PutConfigAsync created config type: {appConfig.GetType().Name}, details: {appConfig.ToString()}");
- }
- else
- {
- //Update the config record
- await UpdateDbAsync(appConfig);
- logger.Info($"PutConfigAsync updated config type: {appConfig.GetType().Name}, details: {appConfig.ToString()}");
- }
- RefreshConfig(appConfig);
- }
- catch (Exception ex)
- {
- logger.Error(ex.ToString());
- return await Task.FromResult(new { Name = "FairbanksAppConfig", Value = false });
- }
- return await Task.FromResult(new { Name = "FairbanksAppConfig", Value = true });
- }
- static AppConfig GetConfig(string name, string value, JsonSerializerOptions options) => name switch
- {
- "FairbanksAppConfig" => JsonSerializer.Deserialize<AppConfig>(value, options),
- _ => throw new InvalidOperationException("Unknown Config name: " + (name ?? ""))
- };
- public AppConfig GetAppConfig()
- {
- return objMapper
- .Map<IEnumerable<AppConfig>>(dbContext.GenericDatas
- .Where(d => d.Type == AutoMapperProfile.UploadConfig && d.Owner == AutoMapperProfile.AppDataOwner))
- .FirstOrDefault() ?? new AppConfig
- {
- DeviceId = this.deviceId,
- Host = this.host,
- Port = this.port,
- UserName = this.username,
- Password = this.password,
- SiteId = this.siteName,
- TankReadInterval = this.tankReadInterval,
- UploadInterval = this.uploadInterval
- };
- }
- #endregion
- #region Private methods
- private void RefreshConfig(AppConfig config)
- {
- if (config == null)
- return;
- logger.Info("Refreshing config");
- deviceId = config.DeviceId;
- siteName = config.SiteId;
- tankReadTimer.Stop();
- tankReadTimer.Interval = config.TankReadInterval * 1000;
- tankReadTimer.Start();
- uploadTimer.Stop();
- uploadTimer.Interval = config.UploadInterval * 60 * 1000;
- uploadTimer.Start();
- fbSftpClient.RefreshConfig(config.Host, config.UserName, config.Password);
- }
- private async Task SaveUploadHistory(string fileName, bool status)
- {
- var currentUploadRecord = new UploadRecord();
- currentUploadRecord.TimeStamp = DateTime.Now;
- currentUploadRecord.FileName = fileName;
- currentUploadRecord.Status = status ? "成功" : "失败";
- currentUploadRecord.Remark = dbHelper.RecordUploadTried(fileName) ? "重试" : "首次上传";
- await SaveDbAsync(currentUploadRecord);
- }
- private async Task SaveDbAsync<T>(T data)
- {
- var dbModel = objMapper.Map<GenericData>(data);
- dbContext.GenericDatas.Add(dbModel);
- await dbContext.SaveChangesAsync();
- }
- private async Task UpdateDbAsync<T>(T data)
- {
- var dbModel = objMapper.Map<GenericData>(data);
- dbContext.GenericDatas.Update(dbModel);
- await dbContext.SaveChangesAsync();
- }
- #endregion
- }
- public class FairbanksAppConfigV1
- {
- /// <summary>
- /// FTP host
- /// </summary>
- public string Host { get; set; }
- /// <summary>
- /// FTP port
- /// </summary>
- public int Port { get; set; }
- /// <summary>
- /// FTP user name
- /// </summary>
- public string Username { get; set; }
- /// <summary>
- /// FTP user password
- /// </summary>
- public string Password { get; set; }
- /// <summary>
- /// Device ID
- /// </summary>
- public string DeviceId { get; set; }
- /// <summary>
- /// Site name
- /// </summary>
- public string SiteName { get; set; }
- /// <summary>
- /// Tank reading interval, in seconds.
- /// </summary>
- public int Interval { get; set; }
- /// <summary>
- /// Upload interval, in minutes.
- /// </summary>
- public int UploadInterval { get; set; }
- /// <summary>
- /// Host folder path
- /// </summary>
- public string HostUploadFolder { get; set; }
- }
- }
|